feat: c2dm prototype via fcm
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
cbd182df34
commit
ff1fc39475
|
|
@ -1,2 +1,3 @@
|
|||
/build
|
||||
/release
|
||||
/release
|
||||
google-services.json
|
||||
|
|
@ -11,6 +11,7 @@ plugins {
|
|||
|
||||
id 'kotlin-kapt'
|
||||
id 'kotlin-parcelize'
|
||||
id 'com.google.gms.google-services'
|
||||
}
|
||||
|
||||
def property(String fileName, String propertyName, String fallbackEnv = null) {
|
||||
|
|
@ -273,6 +274,10 @@ dependencies {
|
|||
// Livekit
|
||||
// FIXME temporarily not included, re-add when realtime media is to be implemented
|
||||
// implementation "io.livekit:livekit-android:$livekit_version"
|
||||
|
||||
// Firebase - Cloud Messaging
|
||||
implementation(platform("com.google.firebase:firebase-bom:33.1.1"))
|
||||
implementation("com.google.firebase:firebase-messaging")
|
||||
}
|
||||
|
||||
sqldelight {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<!-- Up to Android 10, we need the following to take photos from the camera. -->
|
||||
<uses-permission
|
||||
|
|
@ -49,6 +50,21 @@
|
|||
android:name="io.sentry.auto-init"
|
||||
android:value="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||
android:resource="@drawable/ic_notification_monochrome" />
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_color"
|
||||
android:resource="@color/primary" />
|
||||
|
||||
<service
|
||||
android:name=".c2dm.HandlerService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:exported="true"
|
||||
|
|
|
|||
|
|
@ -145,4 +145,29 @@ object ULID {
|
|||
|
||||
return timestamp
|
||||
}
|
||||
|
||||
/** Like asTimestamp, but for the entire ULID including the entropy, as an integer.
|
||||
Note that the number you receive will not be reversible to the original ULID, because
|
||||
it will overflow the `Int` type! Used for generating Android notification IDs.
|
||||
*/
|
||||
fun asInteger(ulid: String): Int {
|
||||
if (ulid.length != len) {
|
||||
throw IllegalArgumentException("ULID must be exactly $len characters")
|
||||
}
|
||||
|
||||
var integer = 0
|
||||
|
||||
for (i in 0 until len) {
|
||||
val char = ulid[i]
|
||||
val value = b32chars.indexOf(char)
|
||||
|
||||
if (value == -1) {
|
||||
throw IllegalArgumentException("Invalid character '$char' at position $i")
|
||||
}
|
||||
|
||||
integer = integer or (value shl (len - i - 1) * 5)
|
||||
}
|
||||
|
||||
return integer
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package chat.revolt.api.realtime
|
|||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import chat.revolt.RevoltApplication
|
||||
import chat.revolt.api.REVOLT_WEBSOCKET
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.RevoltHttp
|
||||
|
|
@ -37,6 +38,7 @@ import chat.revolt.api.schemas.Channel
|
|||
import chat.revolt.api.schemas.ChannelType
|
||||
import chat.revolt.api.settings.GlobalState
|
||||
import chat.revolt.api.settings.SyncedSettings
|
||||
import chat.revolt.c2dm.ChannelRegistrator
|
||||
import chat.revolt.persistence.Database
|
||||
import chat.revolt.persistence.SqlStorage
|
||||
import io.ktor.client.plugins.websocket.ws
|
||||
|
|
@ -63,6 +65,8 @@ object RealtimeSocket {
|
|||
val database = Database(SqlStorage.driver)
|
||||
var socket: WebSocketSession? = null
|
||||
|
||||
private val channelRegistrator = ChannelRegistrator(RevoltApplication.instance)
|
||||
|
||||
private var _disconnectionState = mutableStateOf(DisconnectionState.Reconnecting)
|
||||
val disconnectionState: DisconnectionState
|
||||
get() = _disconnectionState.value
|
||||
|
|
@ -201,6 +205,9 @@ object RealtimeSocket {
|
|||
val emojiMap = readyFrame.emojis.associateBy { it.id!! }
|
||||
RevoltAPI.emojiCache.putAll(emojiMap)
|
||||
|
||||
Log.d("RealtimeSocket", "Registering push notification channels.")
|
||||
channelRegistrator.register()
|
||||
|
||||
RevoltAPI.closeHydration()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,16 @@ import android.util.Log
|
|||
import chat.revolt.api.RevoltError
|
||||
import chat.revolt.api.RevoltHttp
|
||||
import chat.revolt.api.RevoltJson
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.serialization.*
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.contentType
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationException
|
||||
|
||||
@Serializable
|
||||
data class LoginNegotiation(
|
||||
|
|
@ -132,10 +138,12 @@ suspend fun negotiateAuthentication(email: String, password: String): EmailPassw
|
|||
"Success" -> EmailPasswordAssessment(
|
||||
firstUserHints = RevoltJson.decodeFromString(UserHints.serializer(), responseContent)
|
||||
)
|
||||
|
||||
"MFA" -> EmailPasswordAssessment(
|
||||
proceedMfa = true,
|
||||
mfaSpec = RevoltJson.decodeFromString(MfaLoginSpec.serializer(), responseContent)
|
||||
)
|
||||
|
||||
else -> throw Exception("Unknown result: ${responseJson.result}")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package chat.revolt.api.routes.push
|
||||
|
||||
import chat.revolt.api.RevoltHttp
|
||||
import chat.revolt.api.routes.account.WebPushData
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.contentType
|
||||
|
||||
suspend fun subscribePush(
|
||||
endpoint: String = "fcm",
|
||||
auth: String,
|
||||
p256diffieHellman: String? = null,
|
||||
) {
|
||||
val data = WebPushData(
|
||||
endpoint = endpoint,
|
||||
p256diffieHellman = p256diffieHellman ?: "",
|
||||
auth = auth
|
||||
)
|
||||
|
||||
RevoltHttp.post("/push/subscribe") {
|
||||
setBody(data)
|
||||
contentType(ContentType.Application.Json)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package chat.revolt.c2dm
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationChannelGroup
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import chat.revolt.R
|
||||
|
||||
// TODO
|
||||
// * Add the remaining groups.
|
||||
// * Add the remaining channels.
|
||||
// * Find out whether every conversation should have its own channel or if they should be grouped
|
||||
// together as one channel.
|
||||
|
||||
class ChannelRegistrator(val context: Context) {
|
||||
companion object {
|
||||
const val CHANNEL_ID_GROUP_CONVERSATIONS = "chat.revolt.c2dm.conversations"
|
||||
|
||||
const val CHANNEL_ID_GROUP_SOCIAL = "chat.revolt.c2dm.social"
|
||||
const val CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS = "chat.revolt.c2dm.social.friendrequests"
|
||||
}
|
||||
|
||||
private val notificationManager =
|
||||
getSystemService(context, NotificationManager::class.java) as NotificationManager
|
||||
|
||||
private fun registerGroups() {
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) return
|
||||
|
||||
notificationManager.createNotificationChannelGroup(
|
||||
NotificationChannelGroup(
|
||||
CHANNEL_ID_GROUP_CONVERSATIONS,
|
||||
context.getString(R.string.notification_channel_group_conversations)
|
||||
)
|
||||
)
|
||||
notificationManager.createNotificationChannelGroup(
|
||||
NotificationChannelGroup(
|
||||
CHANNEL_ID_GROUP_SOCIAL,
|
||||
context.getString(R.string.notification_channel_group_social)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun registerChannels() {
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) return
|
||||
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(
|
||||
CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS,
|
||||
context.getString(R.string.notification_channel_friend_requests),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
).apply {
|
||||
group = CHANNEL_ID_GROUP_SOCIAL
|
||||
description =
|
||||
context.getString(R.string.notification_channel_friend_requests_description)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun register() {
|
||||
registerGroups()
|
||||
registerChannels()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package chat.revolt.c2dm
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.Person
|
||||
import androidx.core.app.RemoteInput
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import chat.revolt.R
|
||||
import chat.revolt.activities.MainActivity
|
||||
import chat.revolt.api.internals.SpecialUsers
|
||||
import chat.revolt.api.internals.ULID
|
||||
import chat.revolt.api.routes.push.subscribePush
|
||||
import chat.revolt.c2dm.ChannelRegistrator.Companion.CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class HandlerService : FirebaseMessagingService() {
|
||||
override fun onNewToken(token: String) {
|
||||
super.onNewToken(token)
|
||||
runBlocking {
|
||||
subscribePush(auth = token)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage) {
|
||||
val integ = ULID.asInteger(ULID.makeNext())
|
||||
val bitmap = Glide.with(this)
|
||||
.asBitmap()
|
||||
.load("https://autumn.revolt.chat/avatars/GJXjHUC1X7tGEgHFhYOvtCByuNRd72qFwlztjKZUHP/bfe0842b74ae716139138574e8a1b749751e2968~3.jpg")
|
||||
.circleCrop()
|
||||
.submit()
|
||||
.get()
|
||||
val jen =
|
||||
Person.Builder()
|
||||
.setBot(true)
|
||||
.setKey(SpecialUsers.JENNIFER)
|
||||
.setIcon(IconCompat.createWithBitmap(bitmap))
|
||||
.setName("Jennifer")
|
||||
.build()
|
||||
var remoteInput = RemoteInput.Builder("Reply").run {
|
||||
setLabel("Reply")
|
||||
build()
|
||||
}
|
||||
var action: NotificationCompat.Action =
|
||||
NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_reply_24dp,
|
||||
"Reply", PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
Intent(this, MainActivity::class.java),
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
)
|
||||
.addRemoteInput(remoteInput)
|
||||
.build()
|
||||
var builder = NotificationCompat.Builder(this, CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS)
|
||||
.setSmallIcon(R.drawable.ic_message_text_24dp)
|
||||
.setContentTitle("Jennifer")
|
||||
.setContentText("Expand for details")
|
||||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||
.setStyle(
|
||||
NotificationCompat.MessagingStyle(jen)
|
||||
.setConversationTitle("#Genderal")
|
||||
.addMessage("hiii 👋", System.currentTimeMillis(), jen)
|
||||
)
|
||||
.addAction(action)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
|
||||
NotificationManagerCompat.from(this).apply {
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
this@HandlerService,
|
||||
android.Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return
|
||||
}
|
||||
notify(integ, builder.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ fun UserAvatar(
|
|||
) {
|
||||
if (avatar != null) {
|
||||
RemoteImage(
|
||||
url = rawUrl ?: "$REVOLT_FILES/avatars/${avatar.id!!}/user.png?max_side=256",
|
||||
url = rawUrl ?: "$REVOLT_FILES/avatars/${avatar.id}/user.png?max_side=256",
|
||||
contentScale = ContentScale.Crop,
|
||||
description = stringResource(id = R.string.avatar_alt, username),
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
package chat.revolt.dialogs
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
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.unit.dp
|
||||
import chat.revolt.R
|
||||
|
||||
@Composable
|
||||
fun NotificationRationaleDialog(
|
||||
onSelected: (Boolean) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(stringResource(id = R.string.spark_notifications_rationale))
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ux_notification_rationale),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.widthIn(max = 200.dp)
|
||||
.aspectRatio(1f)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.spark_notifications_rationale_description)
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = {
|
||||
onSelected(false)
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(id = R.string.spark_notifications_rationale_dismiss))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(onClick = {
|
||||
onSelected(true)
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(id = R.string.spark_notifications_rationale_cta))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
package chat.revolt.screens.settings
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -8,9 +13,11 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ElevatedButton
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
|
|
@ -24,10 +31,15 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
|
|
@ -35,9 +47,17 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.routes.push.subscribePush
|
||||
import chat.revolt.dialogs.NotificationRationaleDialog
|
||||
import chat.revolt.persistence.Database
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import chat.revolt.persistence.SqlStorage
|
||||
import chat.revolt.ui.theme.FragmentMono
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailabilityLight
|
||||
import com.google.android.gms.tasks.OnCompleteListener
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -72,8 +92,85 @@ fun DebugSettingsScreen(
|
|||
navController: NavController,
|
||||
viewModel: DebugSettingsScreenViewModel = hiltViewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
|
||||
var showNotificationsRationaleDialogue by remember { mutableStateOf(false) }
|
||||
val askNotificationsPermission =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||
if (isGranted) {
|
||||
FirebaseMessaging.getInstance().token.addOnCompleteListener(
|
||||
OnCompleteListener { task ->
|
||||
if (!task.isSuccessful) {
|
||||
Log.e(
|
||||
"DebugSettingsScreen",
|
||||
"Fetching FCM registration token failed",
|
||||
task.exception
|
||||
)
|
||||
return@OnCompleteListener
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
subscribePush(auth = task.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
var showC2dmDataDialogue by remember { mutableStateOf(false) }
|
||||
var fcmToken by remember { mutableStateOf("") }
|
||||
var playServicesAvailable by remember { mutableStateOf(false) }
|
||||
|
||||
if (showNotificationsRationaleDialogue) {
|
||||
NotificationRationaleDialog(
|
||||
onSelected = { accepted ->
|
||||
if (accepted) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
askNotificationsPermission.launch(android.Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Will not ask for permission on pre-Tiramisu devices!",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
showNotificationsRationaleDialogue = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showC2dmDataDialogue) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
showC2dmDataDialogue = false
|
||||
},
|
||||
title = { Text("Notification Properties") },
|
||||
text = {
|
||||
Column {
|
||||
Text("FCM token", style = MaterialTheme.typography.headlineSmall)
|
||||
SelectionContainer {
|
||||
Text(fcmToken, fontFamily = FragmentMono)
|
||||
}
|
||||
Text(
|
||||
"Google Play Services available",
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
Text(if (playServicesAvailable) "Yes" else "No")
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showC2dmDataDialogue = false
|
||||
}) {
|
||||
Text("OK")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
|
|
@ -126,6 +223,42 @@ fun DebugSettingsScreen(
|
|||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "Notifications",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.padding(bottom = 10.dp)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.horizontalScroll(rememberScrollState())
|
||||
) {
|
||||
ElevatedButton(onClick = { showNotificationsRationaleDialogue = true }) {
|
||||
Text("Show notification rationale dialog")
|
||||
}
|
||||
|
||||
ElevatedButton(onClick = {
|
||||
playServicesAvailable = GoogleApiAvailabilityLight.getInstance()
|
||||
.isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS
|
||||
FirebaseMessaging.getInstance().token.addOnCompleteListener(
|
||||
OnCompleteListener { task ->
|
||||
if (!task.isSuccessful) {
|
||||
Log.e(
|
||||
"DebugSettingsScreen",
|
||||
"Fetching FCM registration token failed",
|
||||
task.exception
|
||||
)
|
||||
fcmToken = "Fetching FCM registration token failed!"
|
||||
showC2dmDataDialogue = true
|
||||
return@OnCompleteListener
|
||||
}
|
||||
|
||||
fcmToken = task.result
|
||||
showC2dmDataDialogue = true
|
||||
})
|
||||
}) {
|
||||
Text("Show Notification Properties")
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "Changelogs",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
|
|
@ -154,7 +287,7 @@ fun DebugSettingsScreen(
|
|||
Text(
|
||||
text = viewModel.serverQueries[index].toString(),
|
||||
style = LocalTextStyle.current.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
fontFamily = FragmentMono
|
||||
)
|
||||
)
|
||||
HorizontalDivider(Modifier.padding(vertical = 10.dp))
|
||||
|
|
@ -169,7 +302,7 @@ fun DebugSettingsScreen(
|
|||
Text(
|
||||
text = viewModel.channelQueries[index].toString(),
|
||||
style = LocalTextStyle.current.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
fontFamily = FragmentMono
|
||||
)
|
||||
)
|
||||
HorizontalDivider(Modifier.padding(vertical = 10.dp))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="500"
|
||||
android:viewportHeight="500">
|
||||
<path
|
||||
android:pathData="M122.59,447C122.59,326.35 122.59,247.26 122.59,144.56L46,53H293.71C323.14,53 348.89,58.32 370.95,68.97C393.02,79.61 410.19,94.94 422.45,114.95C434.71,134.96 440.84,158.94 440.84,186.9C440.84,215.12 434.51,238.91 421.87,258.27C409.35,277.64 391.73,292.26 369.02,302.14C346.43,312.01 320.04,316.95 289.84,316.95H187.63V233.84H268.16C280.81,233.84 291.59,232.3 300.49,229.22C309.52,226.02 316.43,220.95 321.2,214.02C326.11,207.1 328.56,198.06 328.56,186.9C328.56,175.61 326.11,166.44 321.2,159.39C316.43,152.21 309.52,146.95 300.49,143.61C291.59,140.15 280.81,138.42 268.16,138.42H230.22V447H122.59ZM354.89,266.16L454,447H337.08L240.29,266.16H354.89Z"
|
||||
android:fillColor="@color/primary"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="512dp" android:viewportHeight="132.29" android:viewportWidth="132.29" android:width="512dp">
|
||||
|
||||
<path android:fillColor="?attr/colorSecondary" android:pathData="m103.09,21.16 l2.77,11.75c0.34,1.44 -0.56,2.89 -2,3.23l-18.27,4.31 -3.99,6.45 -5.54,-23.5c-0.34,-1.44 0.56,-2.89 2,-3.23l13.18,-3.11c-0.04,0.42 0.08,0.94 0.18,1.34 0.86,3.65 4.41,5.85 8.07,4.99 1.44,-0.34 2.75,-1.2 3.61,-2.23m-9.06,-3.37c0.52,2.22 2.62,3.52 4.84,2.99 2.22,-0.52 3.52,-2.62 2.99,-4.84 -0.52,-2.22 -2.62,-3.52 -4.84,-2.99 -2.22,0.52 -3.52,2.62 -2.99,4.84z" android:strokeWidth="1.34108"/>
|
||||
|
||||
<path android:fillColor="?attr/colorPrimary" android:pathData="m56.15,88.62v2.46H11.96v-2.46l4.91,-4.91V68.98c0,-7.61 4.98,-14.31 12.28,-16.47 0,-0.25 0,-0.47 0,-0.71a4.91,4.91 0,0 1,4.91 -4.91,4.91 4.91,0 0,1 4.91,4.91c0,0.25 0,0.47 0,0.71 7.29,2.16 12.28,8.86 12.28,16.47V83.71l4.91,4.91m-17.19,4.91a4.91,4.91 0,0 1,-4.91 4.91,4.91 4.91,0 0,1 -4.91,-4.91m23.94,-43.72 l-3.49,3.49c4.2,4.15 6.55,9.8 6.55,15.69h4.91c0,-7.19 -2.85,-14.12 -7.98,-19.17M7.05,68.98h4.91c0,-5.89 2.36,-11.54 6.55,-15.69L15.02,49.81C9.89,54.87 7.05,61.79 7.05,68.98Z" android:strokeColor="#00000000" android:strokeWidth="2.45499"/>
|
||||
|
||||
<path android:fillColor="?attr/colorTertiary" android:pathData="m117.49,66.53c-1.27,-0.59 -2.83,-0.03 -3.42,1.24 -0.59,1.27 -0.03,2.83 1.24,3.42 1.27,0.59 2.83,0.03 3.42,-1.24 0.59,-1.27 0.03,-2.83 -1.24,-3.42m-5.09,1.68 l-6.13,-2.88c-0.73,-0.34 -1.05,-1.22 -0.71,-1.96l4.38,-9.32c0.35,-0.74 1.22,-1.05 1.96,-0.71l0.67,0.31 0.63,-1.33 1.33,0.63 -0.63,1.33 5.33,2.5 0.63,-1.33 1.33,0.63 -0.63,1.33 0.67,0.31c0.73,0.34 1.05,1.22 0.71,1.96l-2.88,6.13c-0.29,-0.24 -0.6,-0.45 -0.93,-0.61 -0.11,-0.05 -0.22,-0.1 -0.34,-0.14l1.88,-4.02 -9.32,-4.38 -3.44,7.32 6.01,2.82c-0.07,0.1 -0.12,0.21 -0.18,0.32 -0.16,0.33 -0.26,0.69 -0.33,1.06z" android:strokeWidth="0.735562"/>
|
||||
|
||||
<path android:fillColor="?attr/colorError" android:pathData="m112.36,116.79 l-0.45,3.03 -18.17,-2.73 0.45,-3.03c0,0 0.91,-6.06 9.99,-4.69 9.08,1.36 8.17,7.42 8.17,7.42m-2.5,-14.31a4.59,4.59 8.54,1 0,-5.22 3.86,4.59 4.59,8.54 0,0 5.22,-3.86m3.69,8.39a8.57,8.57 0,0 1,1.83 6.37l-0.45,3.03 4.54,0.68 0.45,-3.03c0,0 0.78,-5.22 -6.37,-7.06m0.01,-12.47a4.45,4.45 0,0 0,-1.38 0.01,7.65 7.65,0 0,1 -1.3,8.66 4.45,4.45 0,0 0,1.32 0.41,4.59 4.59,8.54 0,0 1.36,-9.08m-16.27,5.3 l-4.54,-0.68 0.68,-4.54 -3.03,-0.45 -0.68,4.54 -4.54,-0.68 -0.45,3.03 4.54,0.68 -0.68,4.54 3.03,0.45 0.68,-4.54 4.54,0.68z" android:strokeWidth="1.53082"/>
|
||||
|
||||
</vector>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="primary">#FFDA4E5B</color>
|
||||
<color name="primary">#FFFE4654</color>
|
||||
<color name="background">#FF131722</color>
|
||||
<color name="splashscreen_background">#FFFFFFFF</color>
|
||||
<color name="foreground">#FF000000</color>
|
||||
|
|
|
|||
|
|
@ -491,6 +491,11 @@
|
|||
<string name="spark_sidebar_settings_tutorial_description_2">Then long tap your profile picture to open the settings.</string>
|
||||
<string name="spark_sidebar_settings_tutorial_acknowledge">Got it</string>
|
||||
|
||||
<string name="spark_notifications_rationale">Stay in the loop</string>
|
||||
<string name="spark_notifications_rationale_description">Enable notifications to be kept up to date with messages and mentions.</string>
|
||||
<string name="spark_notifications_rationale_cta">Enable notifications</string>
|
||||
<string name="spark_notifications_rationale_dismiss">Not now</string>
|
||||
|
||||
<string name="notice_platform_mod_dm_title">Important notice regarding your account</string>
|
||||
<string name="notice_platform_mod_dm_description">You have received an important notice regarding your account from our moderation team. Please read it carefully.</string>
|
||||
<string name="notice_platform_mod_dm_acknowledge">View</string>
|
||||
|
|
@ -611,4 +616,9 @@
|
|||
<string name="share_target_attachment_too_large">This attachment is too large for Revolt (max. $1$s).</string>
|
||||
<string name="share_target_search_channels">Search channels</string>
|
||||
<string name="share_target_select_channel">m select a channel to share to.</string>
|
||||
|
||||
<string name="notification_channel_group_conversations">Conversations</string>
|
||||
<string name="notification_channel_group_social">Friends and Social</string>
|
||||
<string name="notification_channel_friend_requests">Friend Requests</string>
|
||||
<string name="notification_channel_friend_requests_description">Incoming friend requests, as well as accepted requests you\'ve sent.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ plugins {
|
|||
id 'com.mikepenz.aboutlibraries.plugin' version "10.9.1" apply false
|
||||
id 'com.google.devtools.ksp' version '1.9.22-1.0.17' apply false
|
||||
id 'org.jmailen.kotlinter' version "4.0.0" apply false
|
||||
id("com.google.gms.google-services") version "4.4.2" apply false
|
||||
}
|
||||
|
||||
tasks.register('clean', Delete) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue