style: run ktlint on code

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-10-21 16:27:05 +02:00
parent f15438708e
commit fac7411eea
140 changed files with 1230 additions and 989 deletions

2
.editorconfig Normal file
View File

@ -0,0 +1,2 @@
[*.{kt,kts}]
ktlint_code_style = android_studio

View File

@ -180,7 +180,7 @@ fun InviteScreen(
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier
.fillMaxSize(), .fillMaxSize()
) )
Box( Box(
@ -214,11 +214,10 @@ fun InviteScreen(
?: stringResource(id = R.string.unknown), ?: stringResource(id = R.string.unknown),
modifier = Modifier modifier = Modifier
.size(64.dp) .size(64.dp)
.clip(CircleShape), .clip(CircleShape)
) )
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
@ -227,7 +226,7 @@ fun InviteScreen(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 24.sp, fontSize = 24.sp,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@ -235,7 +234,7 @@ fun InviteScreen(
Text( Text(
text = stringResource(id = R.string.invite_message), text = stringResource(id = R.string.invite_message),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@ -270,10 +269,7 @@ fun InviteScreen(
} }
@Composable @Composable
fun InvalidInviteError( fun InvalidInviteError(error: RevoltError? = null, onDismissRequest: () -> Unit) {
error: RevoltError? = null,
onDismissRequest: () -> Unit
) {
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
icon = { icon = {
@ -287,11 +283,11 @@ fun InvalidInviteError(
Text( Text(
text = stringResource(id = R.string.invite_error_header), text = stringResource(id = R.string.invite_error_header),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
) )
}, },
text = { text = {
Column() { Column {
Text( Text(
text = stringResource( text = stringResource(
id = when (error?.type) { id = when (error?.type) {
@ -301,7 +297,7 @@ fun InvalidInviteError(
} }
), ),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
) )
} }
}, },
@ -333,15 +329,15 @@ fun NoInviteSpecifiedError(onDismissRequest: () -> Unit) {
Text( Text(
text = stringResource(id = R.string.invite_error_header), text = stringResource(id = R.string.invite_error_header),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
) )
}, },
text = { text = {
Column() { Column {
Text( Text(
text = stringResource(id = R.string.invite_error_no_invite), text = stringResource(id = R.string.invite_error_no_invite),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
) )
} }
}, },

View File

@ -84,7 +84,7 @@ fun AppEntrypoint(windowSizeClass: WindowSizeClass) {
val navController = rememberNavController() val navController = rememberNavController()
RevoltTheme( RevoltTheme(
requestedTheme = GlobalState.theme, requestedTheme = GlobalState.theme
) { ) {
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),

View File

@ -79,10 +79,7 @@ class ImageViewActivity : ComponentActivity() {
} }
@Composable @Composable
fun ImageViewScreen( fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
resource: AutumnResource,
onClose: () -> Unit = {}
) {
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}" val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
val context = LocalContext.current val context = LocalContext.current
@ -182,7 +179,7 @@ fun ImageViewScreen(
RevoltTheme(requestedTheme = GlobalState.theme) { RevoltTheme(requestedTheme = GlobalState.theme) {
Scaffold( Scaffold(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) { pv -> ) { pv ->
Surface( Surface(
modifier = Modifier modifier = Modifier
@ -191,10 +188,11 @@ fun ImageViewScreen(
.fillMaxSize() .fillMaxSize()
) { ) {
Column { Column {
PageHeader(text = stringResource( PageHeader(
id = R.string.media_viewer_title_image, text = stringResource(
resource.filename ?: resource.id!! id = R.string.media_viewer_title_image,
), resource.filename ?: resource.id!!
),
showBackButton = true, showBackButton = true,
onBackButtonClicked = onClose, onBackButtonClicked = onClose,
maxLines = 1, maxLines = 1,
@ -213,13 +211,16 @@ fun ImageViewScreen(
expanded = shareSubmenuIsOpen.value, expanded = shareSubmenuIsOpen.value,
onDismissRequest = { onDismissRequest = {
shareSubmenuIsOpen.value = false shareSubmenuIsOpen.value = false
}) { }
) {
DropdownMenuItem( DropdownMenuItem(
onClick = { onClick = {
shareUrl() shareUrl()
}, },
text = { text = {
Text(stringResource(id = R.string.media_viewer_share_url)) Text(
stringResource(id = R.string.media_viewer_share_url)
)
} }
) )
DropdownMenuItem( DropdownMenuItem(
@ -227,7 +228,11 @@ fun ImageViewScreen(
shareImage() shareImage()
}, },
text = { text = {
Text(stringResource(id = R.string.media_viewer_share_image)) Text(
stringResource(
id = R.string.media_viewer_share_image
)
)
} }
) )
} }
@ -237,11 +242,14 @@ fun ImageViewScreen(
}) { }) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_download_24dp), painter = painterResource(id = R.drawable.ic_download_24dp),
contentDescription = stringResource(id = R.string.media_viewer_save) contentDescription = stringResource(
id = R.string.media_viewer_save
)
) )
} }
} }
}) }
)
Box( Box(
modifier = Modifier modifier = Modifier
@ -260,7 +268,7 @@ fun ImageViewScreen(
}, },
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colorScheme.background), .background(MaterialTheme.colorScheme.background)
) )
} }
} }

View File

@ -83,10 +83,7 @@ class VideoViewActivity : ComponentActivity() {
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
@Composable @Composable
fun VideoViewScreen( fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
resource: AutumnResource,
onClose: () -> Unit = {}
) {
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}" val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
val context = LocalContext.current val context = LocalContext.current
@ -198,7 +195,7 @@ fun VideoViewScreen(
RevoltTheme(requestedTheme = GlobalState.theme) { RevoltTheme(requestedTheme = GlobalState.theme) {
Scaffold( Scaffold(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) { pv -> ) { pv ->
Surface( Surface(
modifier = Modifier modifier = Modifier
@ -207,10 +204,11 @@ fun VideoViewScreen(
.fillMaxSize() .fillMaxSize()
) { ) {
Column { Column {
PageHeader(text = stringResource( PageHeader(
id = R.string.media_viewer_title_video, text = stringResource(
resource.filename ?: resource.id!! id = R.string.media_viewer_title_video,
), resource.filename ?: resource.id!!
),
showBackButton = true, showBackButton = true,
onBackButtonClicked = onClose, onBackButtonClicked = onClose,
maxLines = 1, maxLines = 1,
@ -229,13 +227,16 @@ fun VideoViewScreen(
expanded = shareSubmenuIsOpen.value, expanded = shareSubmenuIsOpen.value,
onDismissRequest = { onDismissRequest = {
shareSubmenuIsOpen.value = false shareSubmenuIsOpen.value = false
}) { }
) {
DropdownMenuItem( DropdownMenuItem(
onClick = { onClick = {
shareUrl() shareUrl()
}, },
text = { text = {
Text(stringResource(id = R.string.media_viewer_share_url)) Text(
stringResource(id = R.string.media_viewer_share_url)
)
} }
) )
DropdownMenuItem( DropdownMenuItem(
@ -243,7 +244,11 @@ fun VideoViewScreen(
shareVideo() shareVideo()
}, },
text = { text = {
Text(stringResource(id = R.string.media_viewer_share_video)) Text(
stringResource(
id = R.string.media_viewer_share_video
)
)
} }
) )
} }
@ -253,11 +258,14 @@ fun VideoViewScreen(
}) { }) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_download_24dp), painter = painterResource(id = R.drawable.ic_download_24dp),
contentDescription = stringResource(id = R.string.media_viewer_save) contentDescription = stringResource(
id = R.string.media_viewer_save
)
) )
} }
} }
}) }
)
Box( Box(
modifier = Modifier modifier = Modifier
@ -275,7 +283,7 @@ fun VideoViewScreen(
}, },
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colorScheme.background), .background(MaterialTheme.colorScheme.background)
) )
} }
} }

View File

@ -9,6 +9,7 @@ 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
import chat.revolt.api.routes.user.fetchSelf import chat.revolt.api.routes.user.fetchSelf
import chat.revolt.api.schemas.Channel as ChannelSchema
import chat.revolt.api.schemas.Emoji import chat.revolt.api.schemas.Emoji
import chat.revolt.api.schemas.Message import chat.revolt.api.schemas.Message
import chat.revolt.api.schemas.Server import chat.revolt.api.schemas.Server
@ -38,7 +39,6 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import chat.revolt.api.schemas.Channel as ChannelSchema
const val REVOLT_BASE = "https://api.revolt.chat" const val REVOLT_BASE = "https://api.revolt.chat"
const val REVOLT_SUPPORT = "https://support.revolt.chat" const val REVOLT_SUPPORT = "https://support.revolt.chat"

View File

@ -21,14 +21,14 @@ object DirectMessages {
fun hasPlatformModerationDM(): Boolean { fun hasPlatformModerationDM(): Boolean {
return unreadDMs().any { return unreadDMs().any {
it.channelType == ChannelType.DirectMessage && it.channelType == ChannelType.DirectMessage &&
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
} }
} }
fun getPlatformModerationDM(): Channel? { fun getPlatformModerationDM(): Channel? {
return unreadDMs().firstOrNull { return unreadDMs().firstOrNull {
it.channelType == ChannelType.DirectMessage && it.channelType == ChannelType.DirectMessage &&
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
} }
} }
} }

View File

@ -76,30 +76,30 @@ object BitDefaults {
val Default = val Default =
ViewOnly + ViewOnly +
PermissionBit.SendMessage + PermissionBit.SendMessage +
PermissionBit.InviteOthers + PermissionBit.InviteOthers +
PermissionBit.SendEmbeds + PermissionBit.SendEmbeds +
PermissionBit.UploadFiles + PermissionBit.UploadFiles +
PermissionBit.Connect + PermissionBit.Connect +
PermissionBit.Speak PermissionBit.Speak
val SavedMessages = val SavedMessages =
PermissionBit.GrantAllSafe.value PermissionBit.GrantAllSafe.value
val DirectMessages = val DirectMessages =
Default + Default +
PermissionBit.ManageChannel + PermissionBit.ManageChannel +
PermissionBit.React PermissionBit.React
val Server = val Server =
Default + Default +
PermissionBit.React + PermissionBit.React +
PermissionBit.ChangeNickname + PermissionBit.ChangeNickname +
PermissionBit.ChangeAvatar PermissionBit.ChangeAvatar
val Webhook = val Webhook =
PermissionBit.SendMessage + PermissionBit.SendMessage +
PermissionBit.SendEmbeds + PermissionBit.SendEmbeds +
PermissionBit.Masquerade + PermissionBit.Masquerade +
PermissionBit.React PermissionBit.React
} }

View File

@ -80,7 +80,7 @@ object Roles {
ChannelType.TextChannel, ChannelType.VoiceChannel -> { ChannelType.TextChannel, ChannelType.VoiceChannel -> {
val server = RevoltAPI.serverCache[channel.server] val server = RevoltAPI.serverCache[channel.server]
// FIXME this is a stupid patch to prevent it from showing "no permission" on a channel on launch // FIXME this is a stupid patch to prevent it from showing "no permission" on a channel on launch
?: return PermissionBit.GrantAllSafe.value ?: return PermissionBit.GrantAllSafe.value
if (server.owner == user?.id) return PermissionBit.GrantAllSafe.value if (server.owner == user?.id) return PermissionBit.GrantAllSafe.value

View File

@ -4,17 +4,17 @@ import android.content.Context
import android.graphics.RuntimeShader import android.graphics.RuntimeShader
import android.os.Build import android.os.Build
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush as AndroidBrush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.ShaderBrush
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import androidx.compose.ui.graphics.Brush as AndroidBrush
object SpecialUsers { object SpecialUsers {
val PLATFORM_MODERATION_USER = "01FC17E1WTM2BGE4F3ARN3FDAF" val PLATFORM_MODERATION_USER = "01FC17E1WTM2BGE4F3ARN3FDAF"
val TRUSTED_MODERATION_BOTS = listOf( val TRUSTED_MODERATION_BOTS = listOf(
"01GXBYCNQ52A9QYCQ99RBPXPAW", // AutoMod "01GXBYCNQ52A9QYCQ99RBPXPAW", // AutoMod
"01FCXRNNVW69AMSHBE61W1M5T3", // AutoMod Nightly "01FCXRNNVW69AMSHBE61W1M5T3" // AutoMod Nightly
) )
sealed class TeamMemberFlair { sealed class TeamMemberFlair {
@ -40,7 +40,7 @@ object SpecialUsers {
Color(0xFFFF9B55), Color(0xFFFF9B55),
Color(0xFFFFFFFF), Color(0xFFFFFFFF),
Color(0xFFD461A6), Color(0xFFD461A6),
Color(0xFFA50062), Color(0xFFA50062)
), ),
start = Offset.Zero, start = Offset.Zero,
end = Offset.Infinite end = Offset.Infinite
@ -51,7 +51,7 @@ object SpecialUsers {
AndroidBrush.linearGradient( AndroidBrush.linearGradient(
listOf( listOf(
Color(0xFF68224F), Color(0xFF68224F),
Color(0xFFC68235), Color(0xFFC68235)
), ),
start = Offset.Zero, start = Offset.Zero,
end = Offset.Infinite end = Offset.Infinite
@ -75,7 +75,7 @@ object SpecialUsers {
Color(0xFF1000AF) Color(0xFF1000AF)
) )
) )
), // zomatree ) // zomatree
) )
fun teamFlairAsBrush(context: Context, id: String): AndroidBrush? { fun teamFlairAsBrush(context: Context, id: String): AndroidBrush? {

View File

@ -45,35 +45,67 @@ object ULID {
// Entropy part (16 chars) // Entropy part (16 chars)
chars[10] = b32chars[(entropy[0].toShort() and 0xff).toInt().ushr(3)] chars[10] = b32chars[(entropy[0].toShort() and 0xff).toInt().ushr(3)]
chars[11] = chars[11] =
b32chars[(entropy[0].toInt() shl 2 or (entropy[1].toShort() and 0xff).toInt() b32chars[
.ushr(6) and 0x1f)] (
entropy[0].toInt() shl 2 or (entropy[1].toShort() and 0xff).toInt()
.ushr(6) and 0x1f
)
]
chars[12] = b32chars[((entropy[1].toShort() and 0xff).toInt().ushr(1) and 0x1f)] chars[12] = b32chars[((entropy[1].toShort() and 0xff).toInt().ushr(1) and 0x1f)]
chars[13] = chars[13] =
b32chars[(entropy[1].toInt() shl 4 or (entropy[2].toShort() and 0xff).toInt() b32chars[
.ushr(4) and 0x1f)] (
entropy[1].toInt() shl 4 or (entropy[2].toShort() and 0xff).toInt()
.ushr(4) and 0x1f
)
]
chars[14] = chars[14] =
b32chars[(entropy[2].toInt() shl 5 or (entropy[3].toShort() and 0xff).toInt() b32chars[
.ushr(7) and 0x1f)] (
entropy[2].toInt() shl 5 or (entropy[3].toShort() and 0xff).toInt()
.ushr(7) and 0x1f
)
]
chars[15] = b32chars[((entropy[3].toShort() and 0xff).toInt().ushr(2) and 0x1f)] chars[15] = b32chars[((entropy[3].toShort() and 0xff).toInt().ushr(2) and 0x1f)]
chars[16] = chars[16] =
b32chars[(entropy[3].toInt() shl 3 or (entropy[4].toShort() and 0xff).toInt() b32chars[
.ushr(5) and 0x1f)] (
entropy[3].toInt() shl 3 or (entropy[4].toShort() and 0xff).toInt()
.ushr(5) and 0x1f
)
]
chars[17] = b32chars[(entropy[4].toInt() and 0x1f)] chars[17] = b32chars[(entropy[4].toInt() and 0x1f)]
chars[18] = b32chars[(entropy[5].toShort() and 0xff).toInt().ushr(3)] chars[18] = b32chars[(entropy[5].toShort() and 0xff).toInt().ushr(3)]
chars[19] = chars[19] =
b32chars[(entropy[5].toInt() shl 2 or (entropy[6].toShort() and 0xff).toInt() b32chars[
.ushr(6) and 0x1f)] (
entropy[5].toInt() shl 2 or (entropy[6].toShort() and 0xff).toInt()
.ushr(6) and 0x1f
)
]
chars[20] = b32chars[((entropy[6].toShort() and 0xff).toInt().ushr(1) and 0x1f)] chars[20] = b32chars[((entropy[6].toShort() and 0xff).toInt().ushr(1) and 0x1f)]
chars[21] = chars[21] =
b32chars[(entropy[6].toInt() shl 4 or (entropy[7].toShort() and 0xff).toInt() b32chars[
.ushr(4) and 0x1f)] (
entropy[6].toInt() shl 4 or (entropy[7].toShort() and 0xff).toInt()
.ushr(4) and 0x1f
)
]
chars[22] = chars[22] =
b32chars[(entropy[7].toInt() shl 5 or (entropy[8].toShort() and 0xff).toInt() b32chars[
.ushr(7) and 0x1f)] (
entropy[7].toInt() shl 5 or (entropy[8].toShort() and 0xff).toInt()
.ushr(7) and 0x1f
)
]
chars[23] = b32chars[((entropy[8].toShort() and 0xff).toInt().ushr(2) and 0x1f)] chars[23] = b32chars[((entropy[8].toShort() and 0xff).toInt().ushr(2) and 0x1f)]
chars[24] = chars[24] =
b32chars[(entropy[8].toInt() shl 3 or (entropy[9].toShort() and 0xff).toInt() b32chars[
.ushr(5) and 0x1f)] (
entropy[8].toInt() shl 3 or (entropy[9].toShort() and 0xff).toInt()
.ushr(5) and 0x1f
)
]
chars[25] = b32chars[(entropy[9].toInt() and 0x1f)] chars[25] = b32chars[(entropy[9].toInt() and 0x1f)]
return String(chars) return String(chars)

View File

@ -9,7 +9,9 @@ object WebChallenge {
suspend fun needsCloudflare(): Boolean { suspend fun needsCloudflare(): Boolean {
RevoltHttp.get(REVOLT_BASE).let { RevoltHttp.get(REVOLT_BASE).let {
val text = it.bodyAsText() val text = it.bodyAsText()
return text.contains("window._cf_chl_opt") // FIXME Naive, prone to captcha page changing return text.contains(
"window._cf_chl_opt"
) // FIXME Naive, prone to captcha page changing
} }
} }
} }

View File

@ -26,7 +26,7 @@ private val ADDITIONAL_WEB_COLOURS = mapOf(
"transparent" to Color.Transparent, "transparent" to Color.Transparent,
"inherit" to Color.Unspecified, "inherit" to Color.Unspecified,
"initial" to Color.Unspecified, "initial" to Color.Unspecified,
"unset" to Color.Unspecified, "unset" to Color.Unspecified
) )
object WebCompat { object WebCompat {

View File

@ -7,13 +7,13 @@ import kotlinx.serialization.json.JsonObject
@Serializable @Serializable
data class AnyFrame( data class AnyFrame(
val type: String, val type: String
) )
@Serializable @Serializable
data class ErrorFrame( data class ErrorFrame(
val type: String = "Error", val type: String = "Error",
val error: String, val error: String
) )
@Serializable @Serializable
@ -49,7 +49,7 @@ data class MessageUpdateFrame(
@Serializable @Serializable
data class Appendable( data class Appendable(
val embeds: List<Embed>? = null, val embeds: List<Embed>? = null
) )
@Serializable @Serializable
@ -73,7 +73,7 @@ data class MessageReactFrame(
val id: String, val id: String,
val channel_id: String, val channel_id: String,
val user_id: String, val user_id: String,
val emoji_id: String, val emoji_id: String
) )
@Serializable @Serializable
@ -82,7 +82,7 @@ data class MessageUnreactFrame(
val id: String, val id: String,
val channel_id: String, val channel_id: String,
val user_id: String, val user_id: String,
val emoji_id: String, val emoji_id: String
) )
@Serializable @Serializable
@ -90,7 +90,7 @@ data class MessageRemoveReactionFrame(
val type: String = "MessageRemoveReaction", val type: String = "MessageRemoveReaction",
val id: String, val id: String,
val channel_id: String, val channel_id: String,
val emoji_id: String, val emoji_id: String
) )
/* ChannelCreate: we already have a "type" property in channel so we just alias the type */ /* ChannelCreate: we already have a "type" property in channel so we just alias the type */
@ -152,7 +152,7 @@ data class ServerCreateFrame(
val type: String = "ServerCreate", val type: String = "ServerCreate",
val id: String, val id: String,
val server: Server, val server: Server,
val channels: List<Channel>, val channels: List<Channel>
) )
@Serializable @Serializable
@ -222,7 +222,7 @@ data class UserRelationshipFrame(
val type: String = "UserRelationship", val type: String = "UserRelationship",
val id: String, val id: String,
val user: User, val user: User,
val status: String, val status: String
) )
typealias EmojiCreateFrame = Emoji typealias EmojiCreateFrame = Emoji
@ -230,5 +230,5 @@ typealias EmojiCreateFrame = Emoji
@Serializable @Serializable
data class EmojiDeleteFrame( data class EmojiDeleteFrame(
val type: String = "EmojiDelete", val type: String = "EmojiDelete",
val id: String, val id: String
) )

View File

@ -17,7 +17,7 @@ data class LoginNegotiation(
@SerialName("friendly_name") @SerialName("friendly_name")
val friendlyName: String, val friendlyName: String,
val captcha: String? = null, val captcha: String? = null
) )
@Serializable @Serializable
@ -111,7 +111,6 @@ suspend fun negotiateAuthentication(email: String, password: String): EmailPassw
val responseContent = response.bodyAsText() val responseContent = response.bodyAsText()
Log.d("Revolt", "negotiateAuthentication: $responseContent") Log.d("Revolt", "negotiateAuthentication: $responseContent")
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), responseContent) val error = RevoltJson.decodeFromString(RevoltError.serializer(), responseContent)
return EmailPasswordAssessment(error = error) return EmailPasswordAssessment(error = error)
@ -122,7 +121,7 @@ suspend fun negotiateAuthentication(email: String, password: String): EmailPassw
if (response.status == HttpStatusCode.InternalServerError) { if (response.status == HttpStatusCode.InternalServerError) {
return EmailPasswordAssessment( return EmailPasswordAssessment(
error = RevoltError( error = RevoltError(
"InternalServerError", "InternalServerError"
) )
) )
} }
@ -143,7 +142,7 @@ suspend fun negotiateAuthentication(email: String, password: String): EmailPassw
suspend fun authenticateWithMfaTotpCode( suspend fun authenticateWithMfaTotpCode(
mfaTicket: String, mfaTicket: String,
mfaResponse: MfaResponseTotpCode, mfaResponse: MfaResponseTotpCode
): EmailPasswordAssessment { ): EmailPasswordAssessment {
val response: HttpResponse = RevoltHttp.post("/auth/session/login") { val response: HttpResponse = RevoltHttp.post("/auth/session/login") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
@ -167,7 +166,7 @@ suspend fun authenticateWithMfaTotpCode(
suspend fun authenticateWithMfaRecoveryCode( suspend fun authenticateWithMfaRecoveryCode(
mfaTicket: String, mfaTicket: String,
mfaResponse: MfaResponseRecoveryCode, mfaResponse: MfaResponseRecoveryCode
): EmailPasswordAssessment { ): EmailPasswordAssessment {
val response: HttpResponse = RevoltHttp.post("/auth/session/login") { val response: HttpResponse = RevoltHttp.post("/auth/session/login") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
@ -189,7 +188,6 @@ suspend fun authenticateWithMfaRecoveryCode(
) )
} }
fun friendlySessionName(): String { fun friendlySessionName(): String {
return "Revolt Android on ${Build.MANUFACTURER} ${Build.MODEL}" return "Revolt Android on ${Build.MANUFACTURER} ${Build.MODEL}"
} }

View File

@ -17,7 +17,7 @@ data class RegistrationBody(
val email: String, val email: String,
val password: String, val password: String,
val invite: String? = null, val invite: String? = null,
val captcha: String, val captcha: String
) )
suspend fun register(body: RegistrationBody): RsResult<Unit, RevoltError> { suspend fun register(body: RegistrationBody): RsResult<Unit, RevoltError> {

View File

@ -70,12 +70,12 @@ data class SendMessageBody(
val content: String, val content: String,
val nonce: String = ULID.makeNext(), val nonce: String = ULID.makeNext(),
val replies: List<SendMessageReply> = emptyList(), val replies: List<SendMessageReply> = emptyList(),
val attachments: List<String>?, val attachments: List<String>?
) )
@kotlinx.serialization.Serializable @kotlinx.serialization.Serializable
data class EditMessageBody( data class EditMessageBody(
val content: String?, val content: String?
) )
suspend fun sendMessage( suspend fun sendMessage(
@ -83,7 +83,7 @@ suspend fun sendMessage(
content: String, content: String,
nonce: String? = ULID.makeNext(), nonce: String? = ULID.makeNext(),
replies: List<SendMessageReply>? = null, replies: List<SendMessageReply>? = null,
attachments: List<String>? = null, attachments: List<String>? = null
): String { ): String {
val response = RevoltHttp.post("/channels/$channelId/messages") { val response = RevoltHttp.post("/channels/$channelId/messages") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
@ -101,11 +101,7 @@ suspend fun sendMessage(
return response return response
} }
suspend fun editMessage( suspend fun editMessage(channelId: String, messageId: String, newContent: String? = null) {
channelId: String,
messageId: String,
newContent: String? = null,
) {
val response = RevoltHttp.patch("/channels/$channelId/messages/$messageId") { val response = RevoltHttp.patch("/channels/$channelId/messages/$messageId") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody( setBody(

View File

@ -35,14 +35,20 @@ suspend fun uploadToAutumn(
val uploadUrl = "$REVOLT_FILES/$tag" val uploadUrl = "$REVOLT_FILES/$tag"
val response = RevoltHttp.post(uploadUrl) { val response = RevoltHttp.post(uploadUrl) {
setBody(MultiPartFormDataContent( setBody(
formData { MultiPartFormDataContent(
append("file", file.readBytes(), Headers.build { formData {
append(HttpHeaders.ContentType, contentType.toString()) append(
append(HttpHeaders.ContentDisposition, "filename=\"$name\"") "file",
}) file.readBytes(),
} Headers.build {
)) append(HttpHeaders.ContentType, contentType.toString())
append(HttpHeaders.ContentDisposition, "filename=\"$name\"")
}
)
}
)
)
onUpload { bytesSentTotal, contentLength -> onUpload { bytesSentTotal, contentLength ->
onProgress(bytesSentTotal, contentLength) onProgress(bytesSentTotal, contentLength)
} }
@ -59,5 +65,4 @@ suspend fun uploadToAutumn(
throw Exception("Unknown error") throw Exception("Unknown error")
} }
} }
} }

View File

@ -3,5 +3,5 @@ package chat.revolt.api.routes.microservices.january
import chat.revolt.api.REVOLT_JANUARY import chat.revolt.api.REVOLT_JANUARY
fun asJanuaryProxyUrl(url: String): String { fun asJanuaryProxyUrl(url: String): String {
return "$REVOLT_JANUARY/proxy?url=${url}" return "$REVOLT_JANUARY/proxy?url=$url"
} }

View File

@ -21,10 +21,7 @@ data class OnboardingResponse(
val onboarding: Boolean val onboarding: Boolean
) )
suspend fun needsOnboarding(sessionToken: String = RevoltAPI.sessionToken): Boolean {
suspend fun needsOnboarding(
sessionToken: String = RevoltAPI.sessionToken,
): Boolean {
val response = RevoltHttp.get("/onboard/hello") { val response = RevoltHttp.get("/onboard/hello") {
header(RevoltAPI.TOKEN_HEADER_NAME, sessionToken) header(RevoltAPI.TOKEN_HEADER_NAME, sessionToken)
} }
@ -36,12 +33,12 @@ suspend fun needsOnboarding(
@Serializable @Serializable
data class OnboardingCompletionBody( data class OnboardingCompletionBody(
val username: String, val username: String
) )
suspend fun completeOnboarding( suspend fun completeOnboarding(
body: OnboardingCompletionBody, body: OnboardingCompletionBody,
sessionToken: String = RevoltAPI.sessionToken, sessionToken: String = RevoltAPI.sessionToken
): RsResult<Unit, RevoltError> { ): RsResult<Unit, RevoltError> {
val response = RevoltHttp.post("/onboard/complete") { val response = RevoltHttp.post("/onboard/complete") {
setBody(body) setBody(body)

View File

@ -27,7 +27,7 @@ suspend fun putMessageReport(
report_reason = reason, report_reason = reason,
id = messageId id = messageId
), ),
additional_context = additionalContext, additional_context = additionalContext
) )
val response = RevoltHttp.post("/safety/report") { val response = RevoltHttp.post("/safety/report") {
@ -57,9 +57,9 @@ suspend fun putServerReport(
content = ServerReport( content = ServerReport(
type = "Server", type = "Server",
report_reason = reason, report_reason = reason,
id = serverId, id = serverId
), ),
additional_context = additionalContext, additional_context = additionalContext
) )
val response = RevoltHttp.post("/safety/report") { val response = RevoltHttp.post("/safety/report") {
@ -89,9 +89,9 @@ suspend fun putUserReport(
content = UserReport( content = UserReport(
type = "User", type = "User",
report_reason = reason, report_reason = reason,
id = userId, id = userId
), ),
additional_context = additionalContext, additional_context = additionalContext
) )
val response = RevoltHttp.post("/safety/report") { val response = RevoltHttp.post("/safety/report") {

View File

@ -7,8 +7,8 @@ import chat.revolt.api.RevoltJson
import io.ktor.client.request.delete import io.ktor.client.request.delete
import io.ktor.client.request.put import io.ktor.client.request.put
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerializationException
import kotlin.collections.set import kotlin.collections.set
import kotlinx.serialization.SerializationException
suspend fun blockUser(userId: String) { suspend fun blockUser(userId: String) {
val response = RevoltHttp.put("/users/$userId/block") val response = RevoltHttp.put("/users/$userId/block")

View File

@ -20,7 +20,7 @@ data class MessagesInChannel(
@Serializable @Serializable
data class ServerUserChoice( data class ServerUserChoice(
val server: String, val server: String,
val user: String, val user: String
) )
@Serializable @Serializable
@ -35,7 +35,7 @@ data class Member(
val roles: List<String>? = null, val roles: List<String>? = null,
val nickname: String? = null, val nickname: String? = null,
val timeout: String? = null, val timeout: String? = null
) { ) {
fun mergeWithPartial(other: Member): Member { fun mergeWithPartial(other: Member): Member {
return Member( return Member(
@ -44,7 +44,7 @@ data class Member(
avatar = other.avatar ?: avatar, avatar = other.avatar ?: avatar,
roles = other.roles ?: roles, roles = other.roles ?: roles,
nickname = other.nickname ?: nickname, nickname = other.nickname ?: nickname,
timeout = other.timeout ?: timeout, timeout = other.timeout ?: timeout
) )
} }
@ -75,7 +75,7 @@ data class Channel(
@SerialName("default_permissions") @SerialName("default_permissions")
val defaultPermissions: PermissionDescription? = null, val defaultPermissions: PermissionDescription? = null,
val nsfw: Boolean? = null, val nsfw: Boolean? = null,
val type: String? = null, // this is _only_ used for websocket events! val type: String? = null // this is _only_ used for websocket events!
) { ) {
fun mergeWithPartial(partial: Channel): Channel { fun mergeWithPartial(partial: Channel): Channel {
return Channel( return Channel(
@ -135,7 +135,7 @@ enum class ChannelType(val value: String) {
@Serializable @Serializable
data class ChannelUserChoice( data class ChannelUserChoice(
val channel: String, val channel: String,
val user: String, val user: String
) )
@Serializable @Serializable
@ -143,7 +143,7 @@ data class ChannelUnreadResponse(
@SerialName("_id") @SerialName("_id")
val id: ChannelUserChoice, val id: ChannelUserChoice,
val last_id: String? = null, val last_id: String? = null,
val mentions: List<String>? = null, val mentions: List<String>? = null
) )
@Serializable @Serializable
@ -151,5 +151,5 @@ data class ChannelUnread(
@SerialName("_id") @SerialName("_id")
val id: String, val id: String,
val last_id: String? = null, val last_id: String? = null,
val mentions: List<String>? = null, val mentions: List<String>? = null
) )

View File

@ -50,5 +50,5 @@ data class AutumnId(
@Serializable @Serializable
data class AutumnError( data class AutumnError(
val type: String, val type: String
) )

View File

@ -43,5 +43,5 @@ data class Invite(
data class InviteJoined( data class InviteJoined(
val type: String? = null, val type: String? = null,
val channels: List<Channel>? = null, val channels: List<Channel>? = null,
val server: Server? = null, val server: Server? = null
) )

View File

@ -21,7 +21,7 @@ data class Message(
val masquerade: Masquerade? = null, val masquerade: Masquerade? = null,
val system: SystemInfo? = null, val system: SystemInfo? = null,
val type: String? = null, // this is _only_ used for websocket events! val type: String? = null, // this is _only_ used for websocket events!
val tail: Boolean? = null, // this is used to determine if the message is the last in a message group val tail: Boolean? = null // this is used to determine if the message is the last in a message group
) { ) {
fun getAuthor(): User? { fun getAuthor(): User? {
return author?.let { RevoltAPI.userCache[it] } return author?.let { RevoltAPI.userCache[it] }
@ -42,7 +42,7 @@ data class Message(
mentions = partial.mentions ?: mentions, mentions = partial.mentions ?: mentions,
masquerade = partial.masquerade ?: masquerade, masquerade = partial.masquerade ?: masquerade,
type = partial.type ?: type, type = partial.type ?: type,
tail = partial.tail ?: tail, tail = partial.tail ?: tail
) )
} }
} }
@ -100,5 +100,5 @@ data class SystemInfo(
val by: String? = null, val by: String? = null,
val from: String? = null, val from: String? = null,
val to: String? = null, val to: String? = null,
val content: String? = null, val content: String? = null
) )

View File

@ -82,37 +82,37 @@ enum class UserReportReason(val value: String) {
data class MessageReport( data class MessageReport(
val type: String, val type: String,
val id: String, val id: String,
val report_reason: ContentReportReason, val report_reason: ContentReportReason
) )
@Serializable @Serializable
data class FullMessageReport( data class FullMessageReport(
val content: MessageReport, val content: MessageReport,
val additional_context: String? = null, val additional_context: String? = null
) )
@Serializable @Serializable
data class ServerReport( data class ServerReport(
val type: String, val type: String,
val id: String, val id: String,
val report_reason: ContentReportReason, val report_reason: ContentReportReason
) )
@Serializable @Serializable
data class FullServerReport( data class FullServerReport(
val content: ServerReport, val content: ServerReport,
val additional_context: String? = null, val additional_context: String? = null
) )
@Serializable @Serializable
data class UserReport( data class UserReport(
val type: String, val type: String,
val id: String, val id: String,
val report_reason: UserReportReason, val report_reason: UserReportReason
) )
@Serializable @Serializable
data class FullUserReport( data class FullUserReport(
val content: UserReport, val content: UserReport,
val additional_context: String? = null, val additional_context: String? = null
) )

View File

@ -19,7 +19,7 @@ data class Server(
val banner: AutumnResource? = null, val banner: AutumnResource? = null,
val flags: Long? = null, val flags: Long? = null,
val analytics: Boolean? = null, val analytics: Boolean? = null,
val discoverable: Boolean? = null, val discoverable: Boolean? = null
) { ) {
fun mergeWithPartial(other: Server): Server { fun mergeWithPartial(other: Server): Server {
return Server( return Server(
@ -36,14 +36,14 @@ data class Server(
banner = other.banner ?: banner, banner = other.banner ?: banner,
flags = other.flags ?: flags, flags = other.flags ?: flags,
analytics = other.analytics ?: analytics, analytics = other.analytics ?: analytics,
discoverable = other.discoverable ?: discoverable, discoverable = other.discoverable ?: discoverable
) )
} }
} }
enum class ServerFlags(val value: Long) { enum class ServerFlags(val value: Long) {
Official(1L shl 0), Official(1L shl 0),
Verified(1L shl 1), Verified(1L shl 1)
} }
infix fun Long?.has(flag: ServerFlags): Boolean { infix fun Long?.has(flag: ServerFlags): Boolean {
@ -90,7 +90,7 @@ data class Emoji(
val name: String? = null, val name: String? = null,
val animated: Boolean? = null, val animated: Boolean? = null,
val nsfw: Boolean? = null, val nsfw: Boolean? = null,
val type: String? = null, // this is _only_ used for websocket events! val type: String? = null // this is _only_ used for websocket events!
) )
@Serializable @Serializable

View File

@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class OrderingSettings( data class OrderingSettings(
val servers: List<String> = emptyList(), val servers: List<String> = emptyList()
) )
@Serializable @Serializable
@ -13,5 +13,5 @@ data class AndroidSpecificSettings(
* The theme to use for the app. * The theme to use for the app.
* Can be one of `{ None, Revolt, Light, M3Dynamic, Amoled }` * Can be one of `{ None, Revolt, Light, M3Dynamic, Amoled }`
*/ */
var theme: String? = null, var theme: String? = null
) )

View File

@ -10,7 +10,9 @@ annotation class Treatment(val description: String)
@FeatureFlag("ClosedBetaAccessControl") @FeatureFlag("ClosedBetaAccessControl")
sealed class ClosedBetaAccessControlVariates { sealed class ClosedBetaAccessControlVariates {
@Treatment("Restrict access to the app to users that meet certain or all criteria (implementation-specific)") @Treatment(
"Restrict access to the app to users that meet certain or all criteria (implementation-specific)"
)
data class Restricted(val predicate: () -> Boolean) : ClosedBetaAccessControlVariates() data class Restricted(val predicate: () -> Boolean) : ClosedBetaAccessControlVariates()
@Treatment("Allow access to the app to all users") @Treatment("Allow access to the app to all users")
@ -22,6 +24,6 @@ object FeatureFlags {
var closedBetaAccessControl by mutableStateOf<ClosedBetaAccessControlVariates>( var closedBetaAccessControl by mutableStateOf<ClosedBetaAccessControlVariates>(
ClosedBetaAccessControlVariates.Restricted { ClosedBetaAccessControlVariates.Restricted {
RevoltAPI.channelCache.containsKey("01H7X2KRB0CA4QDSMB4N7WGERF") RevoltAPI.channelCache.containsKey("01H7X2KRB0CA4QDSMB4N7WGERF")
}) }
)
} }

View File

@ -16,13 +16,15 @@ class Unreads {
suspend fun sync() { suspend fun sync() {
channels.clear() channels.clear()
channels.putAll(syncUnreads().associate { channels.putAll(
it.id.channel to ChannelUnread( syncUnreads().associate {
id = it.id.channel, it.id.channel to ChannelUnread(
last_id = it.last_id, id = it.id.channel,
mentions = it.mentions last_id = it.last_id,
) mentions = it.mentions
}) )
}
)
hasLoaded.value = true hasLoaded.value = true
} }

View File

@ -32,7 +32,7 @@ import chat.revolt.ui.theme.Theme
private val NON_MATERIAL_COLOURS = mapOf( private val NON_MATERIAL_COLOURS = mapOf(
DisconnectionState.Disconnected to (Color(0xff4E0C0C) to Color(0xffff1744)), DisconnectionState.Disconnected to (Color(0xff4E0C0C) to Color(0xffff1744)),
DisconnectionState.Reconnecting to (Color(0xff5B5300) to Color(0xffffea00)), DisconnectionState.Reconnecting to (Color(0xff5B5300) to Color(0xffffea00)),
DisconnectionState.Connected to (Color(0xff0E2F10) to Color(0xff00e676)), DisconnectionState.Connected to (Color(0xff0E2F10) to Color(0xff00e676))
) )
@Composable @Composable
@ -50,7 +50,7 @@ private fun DisconnectedNoticeBase(
.fillMaxWidth() .fillMaxWidth()
.background(background) .background(background)
.padding(vertical = 8.dp, horizontal = 16.dp), .padding(vertical = 8.dp, horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
modifier = Modifier.padding(end = 8.dp), modifier = Modifier.padding(end = 8.dp),
@ -75,10 +75,7 @@ private fun DisconnectedNoticeBase(
} }
@Composable @Composable
fun DisconnectedNotice( fun DisconnectedNotice(state: DisconnectionState, onReconnect: () -> Unit) {
state: DisconnectionState,
onReconnect: () -> Unit
) {
val retries = remember { mutableStateOf(0) } val retries = remember { mutableStateOf(0) }
LaunchedEffect(state) { LaunchedEffect(state) {
@ -101,7 +98,7 @@ fun DisconnectedNotice(
val materialColours = mapOf( val materialColours = mapOf(
DisconnectionState.Disconnected to (MaterialTheme.colorScheme.error to MaterialTheme.colorScheme.onError), DisconnectionState.Disconnected to (MaterialTheme.colorScheme.error to MaterialTheme.colorScheme.onError),
DisconnectionState.Reconnecting to (MaterialTheme.colorScheme.secondary to MaterialTheme.colorScheme.onSecondary), DisconnectionState.Reconnecting to (MaterialTheme.colorScheme.secondary to MaterialTheme.colorScheme.onSecondary),
DisconnectionState.Connected to (MaterialTheme.colorScheme.primary to MaterialTheme.colorScheme.onPrimary), DisconnectionState.Connected to (MaterialTheme.colorScheme.primary to MaterialTheme.colorScheme.onPrimary)
) )
val (background, foreground) = when (GlobalState.theme) { val (background, foreground) = when (GlobalState.theme) {
@ -123,14 +120,14 @@ fun DisconnectedNotice(
background = background, background = background,
foreground = foreground, foreground = foreground,
icon = Icons.Default.Refresh, icon = Icons.Default.Refresh,
text = stringResource(id = R.string.reconnecting), text = stringResource(id = R.string.reconnecting)
) )
DisconnectionState.Connected -> DisconnectedNoticeBase( DisconnectionState.Connected -> DisconnectedNoticeBase(
background = background, background = background,
foreground = foreground, foreground = foreground,
icon = Icons.Default.Done, icon = Icons.Default.Done,
text = stringResource(id = R.string.reconnected), text = stringResource(id = R.string.reconnected)
) )
} }
} }

View File

@ -28,9 +28,9 @@ import chat.revolt.api.internals.WebCompat
import chat.revolt.api.internals.solidColor import chat.revolt.api.internals.solidColor
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
import chat.revolt.api.schemas.Embed import chat.revolt.api.schemas.Embed
import chat.revolt.api.schemas.Embed as EmbedSchema
import chat.revolt.components.generic.RemoteImage import chat.revolt.components.generic.RemoteImage
import chat.revolt.components.generic.UIMarkdown import chat.revolt.components.generic.UIMarkdown
import chat.revolt.api.schemas.Embed as EmbedSchema
@Composable @Composable
fun RegularEmbed( fun RegularEmbed(
@ -66,12 +66,14 @@ fun RegularEmbed(
Row( Row(
modifier = Modifier modifier = Modifier
.then( .then(
if (embed.originalURL != null) if (embed.originalURL != null) {
Modifier Modifier
.clickable { .clickable {
onLinkClick(embed.originalURL) onLinkClick(embed.originalURL)
} }
else Modifier } else {
Modifier
}
) )
.fillMaxWidth(), .fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@ -116,9 +118,13 @@ fun RegularEmbed(
modifier = Modifier modifier = Modifier
.clip(MaterialTheme.shapes.medium) .clip(MaterialTheme.shapes.medium)
.then( .then(
if (embed.originalURL != null) Modifier.clickable { if (embed.originalURL != null) {
onLinkClick(embed.originalURL) Modifier.clickable {
} else Modifier onLinkClick(embed.originalURL)
}
} else {
Modifier
}
) )
.aspectRatio( .aspectRatio(
(it.width?.toFloat() ?: 0f) / (it.height?.toFloat() ?: 0f) (it.width?.toFloat() ?: 0f) / (it.height?.toFloat() ?: 0f)
@ -130,15 +136,10 @@ fun RegularEmbed(
} }
} }
} }
} }
@Composable @Composable
fun Embed( fun Embed(embed: Embed, modifier: Modifier = Modifier, onLinkClick: (String) -> Unit) {
embed: Embed,
modifier: Modifier = Modifier,
onLinkClick: (String) -> Unit
) {
Column { Column {
when (embed.type) { when (embed.type) {
else -> RegularEmbed(embed = embed, modifier = modifier, onLinkClick = onLinkClick) else -> RegularEmbed(embed = embed, modifier = modifier, onLinkClick = onLinkClick)

View File

@ -33,7 +33,7 @@ fun InReplyTo(
messageId: String, messageId: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
withMention: Boolean = false, withMention: Boolean = false,
onMessageClick: (String) -> Unit = { _ -> }, onMessageClick: (String) -> Unit = { _ -> }
) { ) {
val message = RevoltAPI.messageCache[messageId] val message = RevoltAPI.messageCache[messageId]
val author = RevoltAPI.userCache[message?.author ?: ""] val author = RevoltAPI.userCache[message?.author ?: ""]

View File

@ -20,7 +20,7 @@ enum class InlineBadge {
fun InlineBadge( fun InlineBadge(
badge: InlineBadge, badge: InlineBadge,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colour: Color = Color.Unspecified, colour: Color = Color.Unspecified
) { ) {
when (badge) { when (badge) {
InlineBadge.Bot -> Icon( InlineBadge.Bot -> Icon(
@ -62,7 +62,7 @@ fun InlineBadges(
teamMember: Boolean = false, teamMember: Boolean = false,
colour: Color = Color.Unspecified, colour: Color = Color.Unspecified,
precedingIfAny: @Composable () -> Unit = {}, precedingIfAny: @Composable () -> Unit = {},
followingIfAny: @Composable () -> Unit = {}, followingIfAny: @Composable () -> Unit = {}
) { ) {
val hasBadges = bot || bridge || platformModeration || teamMember val hasBadges = bot || bridge || platformModeration || teamMember

View File

@ -64,11 +64,11 @@ import chat.revolt.api.internals.WebCompat
import chat.revolt.api.internals.solidColor import chat.revolt.api.internals.solidColor
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
import chat.revolt.api.schemas.AutumnResource import chat.revolt.api.schemas.AutumnResource
import chat.revolt.api.schemas.Message as MessageSchema
import chat.revolt.api.schemas.User import chat.revolt.api.schemas.User
import chat.revolt.components.generic.UserAvatar import chat.revolt.components.generic.UserAvatar
import chat.revolt.components.generic.UserAvatarWidthPlaceholder import chat.revolt.components.generic.UserAvatarWidthPlaceholder
import chat.revolt.internals.markdown.LongClickableSpan import chat.revolt.internals.markdown.LongClickableSpan
import chat.revolt.api.schemas.Message as MessageSchema
@Composable @Composable
fun authorColour(message: MessageSchema): Brush { fun authorColour(message: MessageSchema): Brush {
@ -132,10 +132,7 @@ fun viewAttachmentInBrowser(ctx: android.content.Context, attachment: AutumnReso
viewUrlInBrowser(ctx, url) viewUrlInBrowser(ctx, url)
} }
fun formatLongAsTime(time: Long): String {
fun formatLongAsTime(
time: Long
): String {
val date = java.util.Date(time) val date = java.util.Date(time)
val withinLastWeek = System.currentTimeMillis() - time < 604800000 val withinLastWeek = System.currentTimeMillis() - time < 604800000
@ -167,7 +164,7 @@ fun Message(
onMessageContextMenu: () -> Unit = {}, onMessageContextMenu: () -> Unit = {},
onAvatarClick: () -> Unit = {}, onAvatarClick: () -> Unit = {},
canReply: Boolean = false, canReply: Boolean = false,
onReply: () -> Unit = {}, onReply: () -> Unit = {}
) { ) {
val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator() val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator()
val context = LocalContext.current val context = LocalContext.current
@ -177,7 +174,8 @@ fun Message(
contract = ActivityResultContracts.StartActivityForResult(), contract = ActivityResultContracts.StartActivityForResult(),
onResult = { onResult = {
// do nothing // do nothing
}) }
)
Column { Column {
if (message.tail == false) { if (message.tail == false) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
@ -237,7 +235,7 @@ fun Message(
userId = author.id ?: message.id ?: ULID.makeSpecial(0), userId = author.id ?: message.id ?: ULID.makeSpecial(0),
avatar = author.avatar, avatar = author.avatar,
rawUrl = authorAvatarUrl(message), rawUrl = authorAvatarUrl(message),
onClick = onAvatarClick, onClick = onAvatarClick
) )
} }
} else { } else {
@ -251,7 +249,7 @@ fun Message(
text = authorName(message), text = authorName(message),
style = LocalTextStyle.current.copy( style = LocalTextStyle.current.copy(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
brush = authorColour(message), brush = authorColour(message)
), ),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@ -285,7 +283,9 @@ fun Message(
Icon( Icon(
imageVector = Icons.Default.Edit, imageVector = Icons.Default.Edit,
contentDescription = stringResource(id = R.string.edited), contentDescription = stringResource(id = R.string.edited),
tint = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.5f), tint = MaterialTheme.colorScheme.onBackground.copy(
alpha = 0.5f
),
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
) )
} }
@ -343,7 +343,8 @@ fun Message(
x.toFloat() x.toFloat()
) )
val link = spannedString.getSpans( val link = spannedString.getSpans(
off, off, off,
off,
LongClickableSpan::class.java LongClickableSpan::class.java
) )
if (link.isNotEmpty()) { if (link.isNotEmpty()) {

View File

@ -45,11 +45,11 @@ fun FileAttachment(attachment: AutumnResource) {
.fillMaxWidth() .fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)) .background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
.padding(8.dp), .padding(8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_file_24dp), painter = painterResource(id = R.drawable.ic_file_24dp),
contentDescription = null, contentDescription = null
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
@ -58,13 +58,13 @@ fun FileAttachment(attachment: AutumnResource) {
Text( Text(
text = attachment.filename ?: "File", text = attachment.filename ?: "File",
maxLines = 1, maxLines = 1,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold
) )
Text( Text(
text = Formatter.formatShortFileSize(context, attachment.size ?: 0), text = Formatter.formatShortFileSize(context, attachment.size ?: 0),
maxLines = 1, maxLines = 1,
color = LocalContentColor.current.copy(alpha = 0.7f), color = LocalContentColor.current.copy(alpha = 0.7f),
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
) )
} }
} }
@ -80,8 +80,10 @@ fun ImageAttachment(attachment: AutumnResource) {
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()), .aspectRatio(
description = attachment.filename ?: "Image", attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()
),
description = attachment.filename ?: "Image"
) )
} }
@ -100,8 +102,10 @@ fun VideoAttachment(attachment: AutumnResource) {
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()), .aspectRatio(
description = attachment.filename ?: "Video", attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()
),
description = attachment.filename ?: "Video"
) )
Box( Box(
@ -109,7 +113,7 @@ fun VideoAttachment(attachment: AutumnResource) {
.width(48.dp) .width(48.dp)
.aspectRatio(1f) .aspectRatio(1f)
.clip(MaterialTheme.shapes.medium) .clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)), .background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
) )
Icon( Icon(
@ -117,7 +121,7 @@ fun VideoAttachment(attachment: AutumnResource) {
contentDescription = stringResource(id = R.string.media_viewer_play), contentDescription = stringResource(id = R.string.media_viewer_play),
modifier = Modifier modifier = Modifier
.width(32.dp) .width(32.dp)
.aspectRatio(1f), .aspectRatio(1f)
) )
} }
} }
@ -128,7 +132,7 @@ fun AudioAttachment(attachment: AutumnResource) {
AudioPlayer( AudioPlayer(
url = url, url = url,
filename = attachment.filename ?: "Audio", filename = attachment.filename ?: "Audio",
contentType = attachment.metadata?.type ?: "audio/mpeg", contentType = attachment.metadata?.type ?: "audio/mpeg"
) )
} }

View File

@ -71,7 +71,7 @@ fun MessageField(
disabled: Boolean = false, disabled: Boolean = false,
editMode: Boolean = false, editMode: Boolean = false,
cancelEdit: () -> Unit = {}, cancelEdit: () -> Unit = {},
onFocusChange: (Boolean) -> Unit = {}, onFocusChange: (Boolean) -> Unit = {}
) { ) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
val placeholderResource = when (channelType) { val placeholderResource = when (channelType) {
@ -126,7 +126,7 @@ fun MessageField(
channelName channelName
), ),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis
) )
}, },
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
@ -139,7 +139,7 @@ fun MessageField(
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( unfocusedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
1.dp 1.dp
), ),
focusedContainerColor = Color.Transparent, focusedContainerColor = Color.Transparent
), ),
contentPadding = PaddingValues(16.dp), contentPadding = PaddingValues(16.dp),
leadingIcon = { leadingIcon = {
@ -185,7 +185,8 @@ fun MessageField(
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
AnimatedVisibility(sendButtonVisible, AnimatedVisibility(
sendButtonVisible,
enter = expandIn(initialSize = { full -> enter = expandIn(initialSize = { full ->
IntSize( IntSize(
0, 0,
@ -203,7 +204,8 @@ fun MessageField(
}) + slideOutHorizontally( }) + slideOutHorizontally(
animationSpec = RevoltTweenInt, animationSpec = RevoltTweenInt,
targetOffsetX = { it } targetOffsetX = { it }
) + fadeOut(animationSpec = RevoltTweenFloat)) { ) + fadeOut(animationSpec = RevoltTweenFloat)
) {
Icon( Icon(
when { when {
editMode -> Icons.Default.Edit editMode -> Icons.Default.Edit

View File

@ -13,16 +13,12 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@Composable @Composable
fun RoleChip( fun RoleChip(label: String, brush: Brush, modifier: Modifier = Modifier) {
label: String,
brush: Brush,
modifier: Modifier = Modifier,
) {
Box( Box(
modifier = modifier modifier = modifier
.clip(MaterialTheme.shapes.small) .clip(MaterialTheme.shapes.small)
.background( .background(
brush = brush, brush = brush
) )
.background( .background(
// darken the background a bit // darken the background a bit

View File

@ -39,13 +39,11 @@ enum class SystemMessageType(val type: String) {
USER_KICKED("user_kicked"), USER_KICKED("user_kicked"),
USER_LEFT("user_left"), USER_LEFT("user_left"),
USER_JOINED("user_joined"), USER_JOINED("user_joined"),
TEXT("text"), TEXT("text")
} }
@Composable @Composable
fun SystemMessage( fun SystemMessage(message: Message) {
message: Message
) {
if (message.system == null) return if (message.system == null) return
val systemMessageType = val systemMessageType =
@ -72,7 +70,9 @@ fun SystemMessage(
when (systemMessageType) { when (systemMessageType) {
SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> { SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> {
Text(text = "Channel ownership changed from ${message.system.from} to ${message.system.to}") Text(
text = "Channel ownership changed from ${message.system.from} to ${message.system.to}"
)
} }
SystemMessageType.CHANNEL_ICON_CHANGED -> { SystemMessageType.CHANNEL_ICON_CHANGED -> {
@ -120,11 +120,7 @@ fun SystemMessage(
} }
@Composable @Composable
fun SystemMessageIcon( fun SystemMessageIcon(type: SystemMessageType, modifier: Modifier = Modifier, size: Dp = 24.dp) {
type: SystemMessageType,
modifier: Modifier = Modifier,
size: Dp = 24.dp
) {
when (type) { when (type) {
SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> { SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> {
Icon( Icon(
@ -138,7 +134,9 @@ fun SystemMessageIcon(
SystemMessageType.CHANNEL_ICON_CHANGED -> { SystemMessageType.CHANNEL_ICON_CHANGED -> {
Icon( Icon(
painter = painterResource(R.drawable.ic_image_multiple_24dp), painter = painterResource(R.drawable.ic_image_multiple_24dp),
contentDescription = stringResource(R.string.system_message_channel_icon_changed_alt), contentDescription = stringResource(
R.string.system_message_channel_icon_changed_alt
),
tint = LocalContentColor.current, tint = LocalContentColor.current,
modifier = modifier.size(size) modifier = modifier.size(size)
) )
@ -147,7 +145,9 @@ fun SystemMessageIcon(
SystemMessageType.CHANNEL_DESCRIPTION_CHANGED -> { SystemMessageType.CHANNEL_DESCRIPTION_CHANGED -> {
Icon( Icon(
painter = painterResource(R.drawable.ic_text_box_multiple_24dp), painter = painterResource(R.drawable.ic_text_box_multiple_24dp),
contentDescription = stringResource(R.string.system_message_channel_description_changed_alt), contentDescription = stringResource(
R.string.system_message_channel_description_changed_alt
),
tint = LocalContentColor.current, tint = LocalContentColor.current,
modifier = modifier.size(size) modifier = modifier.size(size)
) )

View File

@ -23,10 +23,7 @@ import androidx.compose.ui.unit.sp
import chat.revolt.R import chat.revolt.R
@Composable @Composable
fun TimeRift( fun TimeRift(modifier: Modifier = Modifier, onMessageLoad: () -> Unit) {
modifier: Modifier = Modifier,
onMessageLoad: () -> Unit,
) {
Column( Column(
modifier = modifier modifier = modifier
.padding(vertical = 10.dp) .padding(vertical = 10.dp)

View File

@ -81,9 +81,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun EmojiPicker( fun EmojiPicker(onEmojiSelected: (String) -> Unit) {
onEmojiSelected: (String) -> Unit,
) {
val view = LocalView.current val view = LocalView.current
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
@ -104,7 +102,9 @@ fun EmojiPicker(
derivedStateOf { derivedStateOf {
val firstVisible = gridState.firstVisibleItemIndex val firstVisible = gridState.firstVisibleItemIndex
val firstCategory = val firstCategory =
categorySpans.entries.firstOrNull { it.value.first <= firstVisible && it.value.second >= firstVisible }?.key categorySpans.entries.firstOrNull {
it.value.first <= firstVisible && it.value.second >= firstVisible
}?.key
firstCategory firstCategory
} }
@ -234,10 +234,14 @@ fun EmojiPicker(
.padding(4.dp) .padding(4.dp)
.size(24.dp) .size(24.dp)
.then( .then(
if (searchQuery.isNotEmpty()) Modifier.clickable { if (searchQuery.isNotEmpty()) {
searchQuery = "" Modifier.clickable {
focusManager.clearFocus() // this prevents the text field Z-below from gaining focus searchQuery = ""
} else Modifier focusManager.clearFocus() // this prevents the text field Z-below from gaining focus
}
} else {
Modifier
}
) )
.align(Alignment.CenterEnd) .align(Alignment.CenterEnd)
.alpha(clearQueryButtonOpacity.value) .alpha(clearQueryButtonOpacity.value)
@ -259,7 +263,7 @@ fun EmojiPicker(
) { ) {
Row( Row(
horizontalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
) { ) {
FitzpatrickSkinTone.entries.forEach { skinTone -> FitzpatrickSkinTone.entries.forEach { skinTone ->
Text( Text(
@ -273,12 +277,24 @@ fun EmojiPicker(
.clip(CircleShape) .clip(CircleShape)
.clickable( .clickable(
onClickLabel = when (skinTone) { onClickLabel = when (skinTone) {
FitzpatrickSkinTone.None -> stringResource(R.string.emoji_picker_skin_tone_none) FitzpatrickSkinTone.None -> stringResource(
FitzpatrickSkinTone.Light -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_1_2) R.string.emoji_picker_skin_tone_none
FitzpatrickSkinTone.MediumLight -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_3) )
FitzpatrickSkinTone.Medium -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_4) FitzpatrickSkinTone.Light -> stringResource(
FitzpatrickSkinTone.MediumDark -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_5) R.string.emoji_picker_skin_tone_fitzpatrick_1_2
FitzpatrickSkinTone.Dark -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_6) )
FitzpatrickSkinTone.MediumLight -> stringResource(
R.string.emoji_picker_skin_tone_fitzpatrick_3
)
FitzpatrickSkinTone.Medium -> stringResource(
R.string.emoji_picker_skin_tone_fitzpatrick_4
)
FitzpatrickSkinTone.MediumDark -> stringResource(
R.string.emoji_picker_skin_tone_fitzpatrick_5
)
FitzpatrickSkinTone.Dark -> stringResource(
R.string.emoji_picker_skin_tone_fitzpatrick_6
)
} }
) { ) {
currentSkinTone = skinTone currentSkinTone = skinTone
@ -286,7 +302,7 @@ fun EmojiPicker(
focusManager.clearFocus() // this prevents the text field Z-below from gaining focus focusManager.clearFocus() // this prevents the text field Z-below from gaining focus
} }
.aspectRatio(1f), .aspectRatio(1f),
textAlign = TextAlign.Center, textAlign = TextAlign.Center
) )
} }
} }
@ -323,7 +339,9 @@ fun EmojiPicker(
} else { } else {
Icons.Default.KeyboardArrowRight Icons.Default.KeyboardArrowRight
}, },
contentDescription = stringResource(R.string.emoji_picker_close_skin_tone_menu), contentDescription = stringResource(
R.string.emoji_picker_close_skin_tone_menu
),
tint = LocalContentColor.current, tint = LocalContentColor.current,
modifier = Modifier modifier = Modifier
.alpha(skinToneMenuCloseHintIconOpacity) .alpha(skinToneMenuCloseHintIconOpacity)
@ -340,7 +358,7 @@ fun EmojiPicker(
.horizontalScroll(categoryRowScrollState) .horizontalScroll(categoryRowScrollState)
.height(37.dp), .height(37.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
) { ) {
servers.forEach { server -> servers.forEach { server ->
Column( Column(
@ -350,13 +368,17 @@ fun EmojiPicker(
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
scope.launch { scope.launch {
val index = val index =
pickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server } pickerList.indexOfFirst {
it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server
}
gridState.animateScrollToItem(index) gridState.animateScrollToItem(index)
} }
} }
.then( .then(
if (currentCategory.value is Category.ServerEmoteCategory && (currentCategory.value as Category.ServerEmoteCategory).server == server) { if (currentCategory.value is Category.ServerEmoteCategory && (currentCategory.value as Category.ServerEmoteCategory).server == server) {
Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)) Modifier.background(
MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
)
} else { } else {
Modifier Modifier
} }
@ -364,7 +386,7 @@ fun EmojiPicker(
.aspectRatio(1f) .aspectRatio(1f)
.padding(4.dp), .padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center
) { ) {
if (server.icon == null) { if (server.icon == null) {
IconPlaceholder( IconPlaceholder(
@ -393,13 +415,17 @@ fun EmojiPicker(
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
scope.launch { scope.launch {
val index = val index =
pickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == category } pickerList.indexOfFirst {
it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == category
}
gridState.animateScrollToItem(index) gridState.animateScrollToItem(index)
} }
} }
.then( .then(
if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) { if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)) Modifier.background(
MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
)
} else { } else {
Modifier Modifier
} }
@ -407,24 +433,44 @@ fun EmojiPicker(
.aspectRatio(1f) .aspectRatio(1f)
.padding(4.dp), .padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center
) { ) {
Icon( Icon(
painter = when (category) { painter = when (category) {
UnicodeEmojiSection.Smileys -> painterResource(R.drawable.ic_emoticon_24dp) UnicodeEmojiSection.Smileys -> painterResource(
UnicodeEmojiSection.People -> painterResource(R.drawable.ic_human_greeting_variant_24dp) R.drawable.ic_emoticon_24dp
UnicodeEmojiSection.Animals -> painterResource(R.drawable.ic_snake_24dp) )
UnicodeEmojiSection.Food -> painterResource(R.drawable.ic_glass_mug_variant_24dp) UnicodeEmojiSection.People -> painterResource(
UnicodeEmojiSection.Travel -> painterResource(R.drawable.ic_train_bus_24dp) R.drawable.ic_human_greeting_variant_24dp
UnicodeEmojiSection.Activities -> painterResource(R.drawable.ic_skate_24dp) )
UnicodeEmojiSection.Objects -> painterResource(R.drawable.ic_table_chair_24dp) UnicodeEmojiSection.Animals -> painterResource(
UnicodeEmojiSection.Symbols -> painterResource(R.drawable.ic_symbol_24dp) R.drawable.ic_snake_24dp
UnicodeEmojiSection.Flags -> painterResource(R.drawable.ic_flag_24dp) )
UnicodeEmojiSection.Food -> painterResource(
R.drawable.ic_glass_mug_variant_24dp
)
UnicodeEmojiSection.Travel -> painterResource(
R.drawable.ic_train_bus_24dp
)
UnicodeEmojiSection.Activities -> painterResource(
R.drawable.ic_skate_24dp
)
UnicodeEmojiSection.Objects -> painterResource(
R.drawable.ic_table_chair_24dp
)
UnicodeEmojiSection.Symbols -> painterResource(
R.drawable.ic_symbol_24dp
)
UnicodeEmojiSection.Flags -> painterResource(
R.drawable.ic_flag_24dp
)
}, },
contentDescription = null, contentDescription = null,
tint = if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) { tint = if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
MaterialTheme.colorScheme.primary MaterialTheme.colorScheme.primary
} else LocalContentColor.current } else {
LocalContentColor.current
}
) )
} }
} }
@ -443,7 +489,7 @@ fun EmojiPicker(
key = "searchResultsHeader", key = "searchResultsHeader",
span = { span = {
GridItemSpan(spanCount) GridItemSpan(spanCount)
}, }
) { ) {
Text( Text(
text = stringResource(R.string.emoji_picker_search_results_header), text = stringResource(R.string.emoji_picker_search_results_header),
@ -481,7 +527,7 @@ fun EmojiPicker(
key = "searchResultsFooter", key = "searchResultsFooter",
span = { span = {
GridItemSpan(spanCount) GridItemSpan(spanCount)
}, }
) { ) {
Divider() Divider()
} }
@ -516,7 +562,7 @@ fun ColumnScope.PickerItem(
skinToneFactory: (EmojiPickerItem.UnicodeEmoji) -> String, skinToneFactory: (EmojiPickerItem.UnicodeEmoji) -> String,
onClick: (EmojiPickerItem) -> Unit, onClick: (EmojiPickerItem) -> Unit,
onServerEmoteInfo: (String) -> Unit, onServerEmoteInfo: (String) -> Unit,
lesserHeaders: Boolean = false, lesserHeaders: Boolean = false
) { ) {
when (item) { when (item) {
is EmojiPickerItem.UnicodeEmoji -> { is EmojiPickerItem.UnicodeEmoji -> {
@ -528,7 +574,7 @@ fun ColumnScope.PickerItem(
.aspectRatio(1f) .aspectRatio(1f)
.weight(1f), .weight(1f),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center
) { ) {
Text( Text(
text = skinToneFactory(item), text = skinToneFactory(item),
@ -548,7 +594,7 @@ fun ColumnScope.PickerItem(
.aspectRatio(1f) .aspectRatio(1f)
.weight(1f), .weight(1f),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center
) { ) {
RemoteImage( RemoteImage(
url = "$REVOLT_FILES/emojis/${item.emote.id}/emoji.gif", url = "$REVOLT_FILES/emojis/${item.emote.id}/emoji.gif",
@ -564,9 +610,12 @@ fun ColumnScope.PickerItem(
is EmojiPickerItem.Section -> { is EmojiPickerItem.Section -> {
Text( Text(
when (item.category) { when (item.category) {
is Category.UnicodeEmojiCategory -> stringResource(item.category.definition.nameResource) is Category.UnicodeEmojiCategory -> stringResource(
is Category.ServerEmoteCategory -> item.category.server.name item.category.definition.nameResource
?: stringResource(R.string.unknown) )
is Category.ServerEmoteCategory ->
item.category.server.name
?: stringResource(R.string.unknown)
}, },
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
modifier = Modifier modifier = Modifier

View File

@ -31,7 +31,7 @@ import androidx.compose.ui.unit.dp
fun CollapsibleCard( fun CollapsibleCard(
header: @Composable () -> Unit, header: @Composable () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
content: @Composable () -> Unit, content: @Composable () -> Unit
) { ) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
@ -45,7 +45,7 @@ fun CollapsibleCard(
modifier = Modifier modifier = Modifier
.clickable { expanded = !expanded } .clickable { expanded = !expanded }
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp), .padding(16.dp)
) { ) {
CompositionLocalProvider( CompositionLocalProvider(
LocalTextStyle provides MaterialTheme.typography.titleMedium.copy( LocalTextStyle provides MaterialTheme.typography.titleMedium.copy(

View File

@ -21,7 +21,7 @@ fun FormTextField(
type: KeyboardType = KeyboardType.Text, type: KeyboardType = KeyboardType.Text,
supportingText: @Composable (() -> Unit)? = null, supportingText: @Composable (() -> Unit)? = null,
singleLine: Boolean = true, singleLine: Boolean = true,
enabled: Boolean = true, enabled: Boolean = true
) { ) {
TextField( TextField(
value = value, value = value,

View File

@ -31,11 +31,14 @@ fun IconPlaceholder(
modifier = modifier modifier = modifier
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)) .background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))
.then( .then(
if (onClick != NoopHandler || onLongClick != NoopHandler) Modifier.combinedClickable( if (onClick != NoopHandler || onLongClick != NoopHandler) {
onClick = onClick, Modifier.combinedClickable(
onLongClick = onLongClick onClick = onClick,
) onLongClick = onLongClick
else Modifier )
} else {
Modifier
}
) )
) { ) {
Text( Text(

View File

@ -46,7 +46,7 @@ fun UIMarkdown(
text: String, text: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
fontSize: TextUnit = LocalTextStyle.current.fontSize, fontSize: TextUnit = LocalTextStyle.current.fontSize,
maxLines: Int = Int.MAX_VALUE, maxLines: Int = Int.MAX_VALUE
) { ) {
val context = LocalContext.current val context = LocalContext.current
val foregroundColor = LocalContentColor.current val foregroundColor = LocalContentColor.current
@ -61,7 +61,7 @@ fun UIMarkdown(
.addRevoltRules(context) .addRevoltRules(context)
.addRules( .addRules(
createCodeRule(context, codeBlockColor.toArgb()), createCodeRule(context, codeBlockColor.toArgb()),
createInlineCodeRule(context, codeBlockColor.toArgb()), createInlineCodeRule(context, codeBlockColor.toArgb())
) )
.addRules( .addRules(
SimpleMarkdownRules.createSimpleMarkdownRules( SimpleMarkdownRules.createSimpleMarkdownRules(
@ -109,7 +109,7 @@ fun UIMarkdown(
modifier = modifier, modifier = modifier,
update = { update = {
it.text = spannableStringBuilder.value it.text = spannableStringBuilder.value
}, }
) )
} }
@ -118,6 +118,6 @@ fun UIMarkdown(
fun UIMarkdownPreview() { fun UIMarkdownPreview() {
UIMarkdown( UIMarkdown(
text = "Hello, **world**! <@01F1WKM5TK2V6KCZWR6DGBJDTZ> [link](https://google.com) `code`\n\n```kt\nfun main() {\n println(\"Hello, world!\")\n}\n```", text = "Hello, **world**! <@01F1WKM5TK2V6KCZWR6DGBJDTZ> [link](https://google.com) `code`\n\n```kt\nfun main() {\n println(\"Hello, world!\")\n}\n```",
fontSize = 16.sp, fontSize = 16.sp
) )
} }

View File

@ -72,7 +72,7 @@ fun NonIdealState(
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
) { ) {
actions() actions()
} }
@ -113,7 +113,7 @@ fun NonIdealStatePreviewNoActions() {
) )
}, },
title = { Text("Error") }, title = { Text("Error") },
description = { Text("Could not load channels.") }, description = { Text("Could not load channels.") }
) )
} }
@ -128,6 +128,6 @@ fun NonIdealStatePreviewNoDescription() {
modifier = Modifier.size(it) modifier = Modifier.size(it)
) )
}, },
title = { Text("No channels") }, title = { Text("No channels") }
) )
} }

View File

@ -28,7 +28,7 @@ fun PageHeader(
showBackButton: Boolean = false, showBackButton: Boolean = false,
onBackButtonClicked: () -> Unit = {}, onBackButtonClicked: () -> Unit = {},
additionalButtons: @Composable () -> Unit = {}, additionalButtons: @Composable () -> Unit = {},
maxLines: Int = Int.MAX_VALUE, maxLines: Int = Int.MAX_VALUE
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@ -53,7 +53,7 @@ fun PageHeader(
), ),
modifier = modifier modifier = modifier
.padding(horizontal = 15.dp, vertical = 15.dp) .padding(horizontal = 15.dp, vertical = 15.dp)
.weight(1f), .weight(1f)
) )
additionalButtons() additionalButtons()
} }

View File

@ -21,13 +21,17 @@ fun RemoteImage(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Crop, contentScale: ContentScale = ContentScale.Crop,
width: Int = 0, width: Int = 0,
height: Int = 0, height: Int = 0
) { ) {
val context = LocalContext.current val context = LocalContext.current
fun pxAsDp(px: Int): Dp { fun pxAsDp(px: Int): Dp {
return (px / (context.resources return (
.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).dp px / (
context.resources
.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT
)
).dp
} }
GlideImage( GlideImage(

View File

@ -27,9 +27,11 @@ fun SheetClickable(
label: @Composable (TextStyle) -> Unit, label: @Composable (TextStyle) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
dangerous: Boolean = false, dangerous: Boolean = false,
onClick: () -> Unit, onClick: () -> Unit
) { ) {
CompositionLocalProvider(LocalContentColor provides if (dangerous) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onBackground) { CompositionLocalProvider(
LocalContentColor provides if (dangerous) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onBackground
) {
Box(modifier = modifier.padding(vertical = 4.dp)) { Box(modifier = modifier.padding(vertical = 4.dp)) {
Row( Row(
modifier = Modifier modifier = Modifier
@ -44,7 +46,7 @@ fun SheetClickable(
label( label(
MaterialTheme.typography.bodyMedium.copy( MaterialTheme.typography.bodyMedium.copy(
color = LocalContentColor.current, color = LocalContentColor.current,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold
) )
) )
} }

View File

@ -78,7 +78,7 @@ fun UserAvatar(
size: Dp = 40.dp, size: Dp = 40.dp,
presenceSize: Dp = 16.dp, presenceSize: Dp = 16.dp,
onLongClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null
) { ) {
Box( Box(
modifier = modifier modifier = modifier
@ -94,29 +94,35 @@ fun UserAvatar(
.clip(CircleShape) .clip(CircleShape)
.size(size) .size(size)
.then( .then(
if (onLongClick != null || onClick != null) Modifier if (onLongClick != null || onClick != null) {
.combinedClickable( Modifier
onClick = { onClick?.invoke() }, .combinedClickable(
onLongClick = { onLongClick?.invoke() } onClick = { onClick?.invoke() },
) onLongClick = { onLongClick?.invoke() }
else Modifier )
} else {
Modifier
}
) )
) )
} else { } else {
RemoteImage( RemoteImage(
url = "$REVOLT_BASE/users/${userId}/default_avatar", url = "$REVOLT_BASE/users/$userId/default_avatar",
description = stringResource(id = R.string.avatar_alt, username), description = stringResource(id = R.string.avatar_alt, username),
modifier = Modifier modifier = Modifier
.clip(CircleShape) .clip(CircleShape)
.size(size) .size(size)
.then( .then(
if (onLongClick != null || onClick != null) Modifier if (onLongClick != null || onClick != null) {
.combinedClickable( Modifier
onClick = { onClick?.invoke() }, .combinedClickable(
onLongClick = { onLongClick?.invoke() } onClick = { onClick?.invoke() },
) onLongClick = { onLongClick?.invoke() }
else Modifier )
), } else {
Modifier
}
)
) )
} }
@ -135,7 +141,7 @@ fun GroupIcon(
rawUrl: String? = null, rawUrl: String? = null,
size: Dp = 40.dp, size: Dp = 40.dp,
onLongClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null
) { ) {
Box( Box(
modifier = modifier modifier = modifier
@ -151,12 +157,15 @@ fun GroupIcon(
.clip(MaterialTheme.shapes.small) .clip(MaterialTheme.shapes.small)
.size(size) .size(size)
.then( .then(
if (onLongClick != null || onClick != null) Modifier if (onLongClick != null || onClick != null) {
.combinedClickable( Modifier
onClick = { onClick?.invoke() }, .combinedClickable(
onLongClick = { onLongClick?.invoke() } onClick = { onClick?.invoke() },
) onLongClick = { onLongClick?.invoke() }
else Modifier )
} else {
Modifier
}
) )
) )
} else { } else {
@ -164,12 +173,15 @@ fun GroupIcon(
modifier = Modifier modifier = Modifier
.size(size) .size(size)
.then( .then(
if (onLongClick != null || onClick != null) Modifier if (onLongClick != null || onClick != null) {
.combinedClickable( Modifier
onClick = { onClick?.invoke() }, .combinedClickable(
onLongClick = { onLongClick?.invoke() } onClick = { onClick?.invoke() },
) onLongClick = { onLongClick?.invoke() }
else Modifier )
} else {
Modifier
}
) )
.clip(MaterialTheme.shapes.small) .clip(MaterialTheme.shapes.small)
.background(MaterialTheme.colorScheme.primary) .background(MaterialTheme.colorScheme.primary)
@ -186,9 +198,7 @@ fun GroupIcon(
} }
@Composable @Composable
fun UserAvatarWidthPlaceholder( fun UserAvatarWidthPlaceholder(size: Dp = 40.dp) {
size: Dp = 40.dp,
) {
Box( Box(
modifier = Modifier modifier = Modifier
.width(size) .width(size)

View File

@ -50,7 +50,7 @@ fun WebMarkdown(
text: String, text: String,
maskLoading: Boolean = false, maskLoading: Boolean = false,
simpleLineBreaks: Boolean = true, simpleLineBreaks: Boolean = true,
modifier: Modifier = Modifier, modifier: Modifier = Modifier
) { ) {
val contentColour = LocalContentColor.current val contentColour = LocalContentColor.current
val materialColourScheme = MaterialTheme.colorScheme val materialColourScheme = MaterialTheme.colorScheme
@ -97,9 +97,11 @@ fun WebMarkdown(
): Boolean { ): Boolean {
// Capture clicks on invite links // Capture clicks on invite links
if (webResourceRequest.url.host == "rvlt.gg" || if (webResourceRequest.url.host == "rvlt.gg" ||
(webResourceRequest.url.host?.endsWith("revolt.chat") == true && webResourceRequest.url.path?.startsWith( (
"/invite" webResourceRequest.url.host?.endsWith("revolt.chat") == true && webResourceRequest.url.path?.startsWith(
) == true) "/invite"
) == true
)
) { ) {
val intent = Intent( val intent = Intent(
context, context,
@ -129,7 +131,7 @@ fun WebMarkdown(
} }
loadUrl( loadUrl(
"$REVOLT_APP/_android_assets/webmarkdown/renderer.html", "$REVOLT_APP/_android_assets/webmarkdown/renderer.html"
) )
settings.apply { settings.apply {

View File

@ -51,11 +51,7 @@ import kotlinx.coroutines.launch
@Composable @Composable
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
fun AudioPlayer( fun AudioPlayer(url: String, filename: String, contentType: String) {
url: String,
filename: String,
contentType: String,
) {
val context = LocalContext.current val context = LocalContext.current
val showMenu = remember { mutableStateOf(false) } val showMenu = remember { mutableStateOf(false) }
@ -201,7 +197,7 @@ fun AudioPlayer(
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 4.dp), modifier = Modifier.padding(horizontal = 4.dp, vertical = 4.dp)
) { ) {
Text( Text(
text = filename, text = filename,
@ -222,7 +218,7 @@ fun AudioPlayer(
} }
} }
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
) { ) {
IconButton(onClick = { IconButton(onClick = {
if (isPlaying.value) { if (isPlaying.value) {
@ -237,12 +233,12 @@ fun AudioPlayer(
if (isPlaying.value) { if (isPlaying.value) {
Icon( Icon(
painter = painterResource(R.drawable.ic_pause_24dp), painter = painterResource(R.drawable.ic_pause_24dp),
contentDescription = stringResource(R.string.media_viewer_pause), contentDescription = stringResource(R.string.media_viewer_pause)
) )
} else { } else {
Icon( Icon(
imageVector = Icons.Filled.PlayArrow, imageVector = Icons.Filled.PlayArrow,
contentDescription = stringResource(R.string.media_viewer_play), contentDescription = stringResource(R.string.media_viewer_play)
) )
} }
} }
@ -270,7 +266,7 @@ fun AudioPlayer(
}) { }) {
Icon( Icon(
imageVector = Icons.Filled.MoreVert, imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(R.string.media_viewer_more), contentDescription = stringResource(R.string.media_viewer_more)
) )
DropdownMenu( DropdownMenu(
expanded = showMenu.value, expanded = showMenu.value,

View File

@ -86,7 +86,7 @@ private fun Long.formatAsLengthDuration(): String {
if (days > 0) "$days:" else null, if (days > 0) "$days:" else null,
if (hours > 0) "$hours".padStart(2, '0') + ":" else null, if (hours > 0) "$hours".padStart(2, '0') + ":" else null,
"$minutes".padStart(2, '0') + ":", "$minutes".padStart(2, '0') + ":",
"$seconds".padStart(2, '0'), "$seconds".padStart(2, '0')
) )
} }
@ -102,7 +102,7 @@ fun InbuiltMediaPicker(
onClose: () -> Unit, onClose: () -> Unit,
onMediaSelected: (Media) -> Unit, onMediaSelected: (Media) -> Unit,
pendingMedia: List<String>, pendingMedia: List<String>,
disabled: Boolean = false, disabled: Boolean = false
) { ) {
val context = LocalContext.current val context = LocalContext.current
@ -140,14 +140,16 @@ fun InbuiltMediaPicker(
onResult = { mediaPermissionState -> onResult = { mediaPermissionState ->
hasPhotoPermission = mediaPermissionState[Manifest.permission.READ_MEDIA_IMAGES] == true hasPhotoPermission = mediaPermissionState[Manifest.permission.READ_MEDIA_IMAGES] == true
hasVideoPermission = mediaPermissionState[Manifest.permission.READ_MEDIA_VIDEO] == true hasVideoPermission = mediaPermissionState[Manifest.permission.READ_MEDIA_VIDEO] == true
mediaPermissionIsPartial = if (!hasMediaPermissions mediaPermissionIsPartial = if (!hasMediaPermissions &&
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
&& mediaPermissionState[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED] == true mediaPermissionState[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED] == true
) { ) {
true true
} else if (hasMediaPermissions) { } else if (hasMediaPermissions) {
false false
} else null } else {
null
}
} }
) )
@ -158,9 +160,9 @@ fun InbuiltMediaPicker(
} }
LaunchedEffect(hasMediaPermissions) { LaunchedEffect(hasMediaPermissions) {
mediaPermissionIsPartial = if (!hasMediaPermissions mediaPermissionIsPartial = if (!hasMediaPermissions &&
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
&& ContextCompat.checkSelfPermission( ContextCompat.checkSelfPermission(
context, context,
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
) == PermissionChecker.PERMISSION_GRANTED ) == PermissionChecker.PERMISSION_GRANTED
@ -168,7 +170,9 @@ fun InbuiltMediaPicker(
true true
} else if (hasMediaPermissions) { } else if (hasMediaPermissions) {
false false
} else null } else {
null
}
} }
LaunchedEffect(hasMediaPermissions, mediaPermissionIsPartial) { LaunchedEffect(hasMediaPermissions, mediaPermissionIsPartial) {
@ -178,7 +182,7 @@ fun InbuiltMediaPicker(
MediaStore.Images.ImageColumns.RESOLUTION, MediaStore.Images.ImageColumns.RESOLUTION,
MediaStore.Images.ImageColumns.ORIENTATION, MediaStore.Images.ImageColumns.ORIENTATION,
MediaStore.Images.ImageColumns.MIME_TYPE, MediaStore.Images.ImageColumns.MIME_TYPE,
MediaStore.Video.VideoColumns.DURATION, MediaStore.Video.VideoColumns.DURATION
) )
val selection: String? = null val selection: String? = null
@ -199,16 +203,30 @@ fun InbuiltMediaPicker(
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
try { try {
val id = val id =
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID)) cursor.getLong(
cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID)
)
val resolution = val resolution =
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.RESOLUTION)) cursor.getString(
cursor.getColumnIndexOrThrow(
MediaStore.Images.ImageColumns.RESOLUTION
)
)
val orientation = val orientation =
cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.ORIENTATION)) cursor.getInt(
cursor.getColumnIndexOrThrow(
MediaStore.Images.ImageColumns.ORIENTATION
)
)
val swapDimensions = orientation == 90 || orientation == 270 val swapDimensions = orientation == 90 || orientation == 270
val isVideo = val isVideo =
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.MIME_TYPE)) cursor.getString(
cursor.getColumnIndexOrThrow(
MediaStore.Images.ImageColumns.MIME_TYPE
)
)
.startsWith("video") .startsWith("video")
val durationColumn = val durationColumn =
@ -293,7 +311,7 @@ fun InbuiltMediaPicker(
alpha = 0.5f alpha = 0.5f
) )
), ),
textAlign = TextAlign.Center, textAlign = TextAlign.Center
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@ -311,12 +329,14 @@ fun InbuiltMediaPicker(
permissionRequester.launch( permissionRequester.launch(
arrayOf( arrayOf(
Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_VIDEO
) )
) )
} }
}) { }) {
Text(text = stringResource(id = R.string.file_picker_permission_request_cta)) Text(
text = stringResource(id = R.string.file_picker_permission_request_cta)
)
} }
} }
} else { } else {
@ -324,7 +344,7 @@ fun InbuiltMediaPicker(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight() .fillMaxHeight()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp)
) { ) {
AnimatedVisibility( AnimatedVisibility(
mediaPermissionIsPartial == true mediaPermissionIsPartial == true
@ -343,7 +363,9 @@ fun InbuiltMediaPicker(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Image( Image(
painter = painterResource(id = R.drawable.ux_file_unpartialise_request), painter = painterResource(
id = R.drawable.ux_file_unpartialise_request
),
modifier = Modifier modifier = Modifier
.size(52.dp), .size(52.dp),
contentDescription = null // decorative contentDescription = null // decorative
@ -354,14 +376,18 @@ fun InbuiltMediaPicker(
.weight(1f) .weight(1f)
) { ) {
Text( Text(
text = stringResource(id = R.string.file_picker_permission_unpartialise_request_header), text = stringResource(
id = R.string.file_picker_permission_unpartialise_request_header
),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Text( Text(
text = stringResource(id = R.string.file_picker_permission_unpartialise_request_body), text = stringResource(
id = R.string.file_picker_permission_unpartialise_request_body
),
style = MaterialTheme.typography.bodyMedium.copy( style = MaterialTheme.typography.bodyMedium.copy(
color = LocalContentColor.current.copy( color = LocalContentColor.current.copy(
alpha = 0.5f alpha = 0.5f
@ -384,12 +410,16 @@ fun InbuiltMediaPicker(
permissionRequester.launch( permissionRequester.launch(
arrayOf( arrayOf(
Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_VIDEO
) )
) )
} }
}) { }) {
Text(text = stringResource(id = R.string.file_picker_permission_unpartialise_request_cta)) Text(
text = stringResource(
id = R.string.file_picker_permission_unpartialise_request_cta
)
)
} }
} }
} }
@ -404,7 +434,9 @@ fun InbuiltMediaPicker(
onOpenDocumentsUi() onOpenDocumentsUi()
}, },
label = { label = {
Text(text = stringResource(id = R.string.file_picker_chip_documents)) Text(
text = stringResource(id = R.string.file_picker_chip_documents)
)
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
@ -468,14 +500,14 @@ fun InbuiltMediaPicker(
} }
) )
.width(100.dp) .width(100.dp)
.aspectRatio(images[image].aspectRatio), .aspectRatio(images[image].aspectRatio)
) { ) {
GlideImage( GlideImage(
model = images[image].uri.toString(), model = images[image].uri.toString(),
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier
.clip(MaterialTheme.shapes.medium) .clip(MaterialTheme.shapes.medium)
.fillMaxSize(), .fillMaxSize()
) )
if (images[image].duration != null) { if (images[image].duration != null) {

View File

@ -36,7 +36,7 @@ fun AttachmentManager(
attachments: List<FileArgs>, attachments: List<FileArgs>,
uploading: Boolean, uploading: Boolean,
uploadProgress: Float = 0f, uploadProgress: Float = 0f,
onRemove: (FileArgs) -> Unit, onRemove: (FileArgs) -> Unit
) { ) {
val animatedProgress by animateFloatAsState( val animatedProgress by animateFloatAsState(
targetValue = uploadProgress, targetValue = uploadProgress,
@ -54,7 +54,6 @@ fun AttachmentManager(
.horizontalScroll(rememberScrollState()) .horizontalScroll(rememberScrollState())
.padding(horizontal = 8.dp, vertical = 4.dp) .padding(horizontal = 8.dp, vertical = 4.dp)
) { ) {
attachments.forEach { attachment -> attachments.forEach { attachment ->
Row( Row(
modifier = Modifier modifier = Modifier
@ -98,18 +97,18 @@ fun AttachmentManagerPreview() {
FileArgs( FileArgs(
filename = "file1.png", filename = "file1.png",
contentType = "image/png", contentType = "image/png",
file = File("file1.png"), file = File("file1.png")
), ),
FileArgs( FileArgs(
filename = "file2.png", filename = "file2.png",
contentType = "image/png", contentType = "image/png",
file = File("file2.png"), file = File("file2.png")
), ),
FileArgs( FileArgs(
filename = "file3.png", filename = "file3.png",
contentType = "image/png", contentType = "image/png",
file = File("file3.png"), file = File("file3.png")
), )
), ),
uploading = false, uploading = false,
onRemove = {} onRemove = {}

View File

@ -32,7 +32,7 @@ fun ChannelHeader(
channel: Channel, channel: Channel,
onChannelClick: (String) -> Unit, onChannelClick: (String) -> Unit,
onToggleDrawer: () -> Unit, onToggleDrawer: () -> Unit,
useDrawer: Boolean, useDrawer: Boolean
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@ -41,7 +41,7 @@ fun ChannelHeader(
channel.id?.let { onChannelClick(it) } channel.id?.let { onChannelClick(it) }
} }
.padding(vertical = 4.dp, horizontal = 4.dp), .padding(vertical = 4.dp, horizontal = 4.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
) { ) {
if (useDrawer) { if (useDrawer) {
IconButton(onClick = { IconButton(onClick = {
@ -49,7 +49,7 @@ fun ChannelHeader(
}) { }) {
Icon( Icon(
imageVector = Icons.Default.Menu, imageVector = Icons.Default.Menu,
contentDescription = stringResource(R.string.menu), contentDescription = stringResource(R.string.menu)
) )
} }
@ -74,7 +74,7 @@ fun ChannelHeader(
Row( Row(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = channel.name text = channel.name
@ -86,7 +86,7 @@ fun ChannelHeader(
}, },
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
@ -96,7 +96,7 @@ fun ChannelHeader(
contentDescription = stringResource(R.string.menu), contentDescription = stringResource(R.string.menu),
modifier = Modifier modifier = Modifier
.size(18.dp) .size(18.dp)
.alpha(0.4f), .alpha(0.4f)
) )
} }
} }

View File

@ -15,44 +15,41 @@ import chat.revolt.R
import chat.revolt.api.schemas.ChannelType import chat.revolt.api.schemas.ChannelType
@Composable @Composable
fun ChannelIcon( fun ChannelIcon(channelType: ChannelType, modifier: Modifier = Modifier) {
channelType: ChannelType,
modifier: Modifier = Modifier,
) {
when (channelType) { when (channelType) {
ChannelType.TextChannel -> { ChannelType.TextChannel -> {
Icon( Icon(
painter = painterResource(R.drawable.ic_pound_24dp), painter = painterResource(R.drawable.ic_pound_24dp),
contentDescription = stringResource(R.string.channel_text), contentDescription = stringResource(R.string.channel_text),
modifier = modifier, modifier = modifier
) )
} }
ChannelType.VoiceChannel -> { ChannelType.VoiceChannel -> {
Icon( Icon(
painter = painterResource(R.drawable.ic_volume_up_24dp), painter = painterResource(R.drawable.ic_volume_up_24dp),
contentDescription = stringResource(R.string.channel_voice), contentDescription = stringResource(R.string.channel_voice),
modifier = modifier, modifier = modifier
) )
} }
ChannelType.SavedMessages -> { ChannelType.SavedMessages -> {
Icon( Icon(
painter = painterResource(R.drawable.ic_note_24dp), painter = painterResource(R.drawable.ic_note_24dp),
contentDescription = stringResource(R.string.channel_notes), contentDescription = stringResource(R.string.channel_notes),
modifier = modifier, modifier = modifier
) )
} }
ChannelType.DirectMessage -> { ChannelType.DirectMessage -> {
Icon( Icon(
imageVector = Icons.Default.AccountCircle, imageVector = Icons.Default.AccountCircle,
contentDescription = stringResource(R.string.channel_dm), contentDescription = stringResource(R.string.channel_dm),
modifier = modifier, modifier = modifier
) )
} }
ChannelType.Group -> { ChannelType.Group -> {
Icon( Icon(
imageVector = Icons.Default.AccountBox, imageVector = Icons.Default.AccountBox,
contentDescription = stringResource(R.string.channel_group), contentDescription = stringResource(R.string.channel_group),
modifier = modifier, modifier = modifier
) )
} }
} }
@ -65,7 +62,7 @@ class ChannelTypeProvider : PreviewParameterProvider<ChannelType> {
ChannelType.VoiceChannel, ChannelType.VoiceChannel,
ChannelType.SavedMessages, ChannelType.SavedMessages,
ChannelType.DirectMessage, ChannelType.DirectMessage,
ChannelType.Group, ChannelType.Group
) )
override val count: Int override val count: Int

View File

@ -28,7 +28,7 @@ fun ChannelSheetHeader(
channelName: String, channelName: String,
channelIcon: AutumnResource? = null, channelIcon: AutumnResource? = null,
channelType: ChannelType, channelType: ChannelType,
channelId: String, channelId: String
) { ) {
Row( Row(
modifier = Modifier.padding(vertical = 4.dp), modifier = Modifier.padding(vertical = 4.dp),
@ -65,7 +65,7 @@ fun ChannelSheetHeader(
text = channelName, text = channelName,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis
) )
} }
} }

View File

@ -47,11 +47,7 @@ fun replyContentText(message: Message): String {
} }
@Composable @Composable
fun ManageableReply( fun ManageableReply(reply: SendMessageReply, onToggleMention: () -> Unit, onRemove: () -> Unit) {
reply: SendMessageReply,
onToggleMention: () -> Unit,
onRemove: () -> Unit,
) {
val replyMessage = RevoltAPI.messageCache[reply.id] ?: return onRemove() val replyMessage = RevoltAPI.messageCache[reply.id] ?: return onRemove()
val replyAuthor = RevoltAPI.userCache[replyMessage.author] ?: return onRemove() val replyAuthor = RevoltAPI.userCache[replyMessage.author] ?: return onRemove()
@ -61,7 +57,7 @@ fun ManageableReply(
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)) .background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
.horizontalScroll(rememberScrollState()) .horizontalScroll(rememberScrollState())
.padding(horizontal = 8.dp, vertical = 4.dp), .padding(horizontal = 8.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
@ -72,7 +68,7 @@ fun ManageableReply(
onRemove() onRemove()
} }
.padding(4.dp) .padding(4.dp)
.size(16.dp), .size(16.dp)
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
@ -97,7 +93,7 @@ fun ManageableReply(
style = LocalTextStyle.current.copy( style = LocalTextStyle.current.copy(
brush = authorColour(message = replyMessage), brush = authorColour(message = replyMessage),
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 12.sp, fontSize = 12.sp
) )
) )
@ -133,7 +129,7 @@ fun ManageableReply(
} else { } else {
MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f) MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f)
}, },
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold
) )
} }
} }
@ -142,7 +138,7 @@ fun ManageableReply(
fun ReplyManager( fun ReplyManager(
replies: List<SendMessageReply>, replies: List<SendMessageReply>,
onToggleMention: (SendMessageReply) -> Unit, onToggleMention: (SendMessageReply) -> Unit,
onRemove: (SendMessageReply) -> Unit, onRemove: (SendMessageReply) -> Unit
) { ) {
Column { Column {
replies.forEach { reply -> replies.forEach { reply ->

View File

@ -30,10 +30,7 @@ import chat.revolt.api.schemas.User
import chat.revolt.components.generic.UserAvatar import chat.revolt.components.generic.UserAvatar
@Composable @Composable
fun StackedUserAvatars( fun StackedUserAvatars(users: List<String>, amount: Int = 3) {
users: List<String>,
amount: Int = 3,
) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(16.dp + (8.dp * minOf(users.size, amount)), 16.dp) .size(16.dp + (8.dp * minOf(users.size, amount)), 16.dp)
@ -43,21 +40,19 @@ fun StackedUserAvatars(
UserAvatar( UserAvatar(
avatar = user?.avatar, avatar = user?.avatar,
userId = userId, userId = userId,
username = user?.let {User.resolveDefaultName(it)} ?: stringResource(id = R.string.unknown), username = user?.let { User.resolveDefaultName(it) } ?: stringResource(id = R.string.unknown),
size = 16.dp, size = 16.dp,
modifier = Modifier modifier = Modifier
.offset( .offset(
x = (index * 8).dp, x = (index * 8).dp
), )
) )
} }
} }
} }
@Composable @Composable
fun TypingIndicator( fun TypingIndicator(users: List<String>) {
users: List<String>,
) {
fun typingMessageResource(): Int { fun typingMessageResource(): Int {
return when (users.size) { return when (users.size) {
0 -> R.string.typing_blank 0 -> R.string.typing_blank
@ -84,11 +79,11 @@ fun TypingIndicator(
.clip( .clip(
RoundedCornerShape( RoundedCornerShape(
topStart = 16.dp, topStart = 16.dp,
topEnd = 16.dp, topEnd = 16.dp
) )
) )
.background(MaterialTheme.colorScheme.background) .background(MaterialTheme.colorScheme.background)
.padding(top = 4.dp, start = 16.dp, end = 16.dp), .padding(top = 4.dp, start = 16.dp, end = 16.dp)
) { ) {
StackedUserAvatars(users = users) StackedUserAvatars(users = users)
@ -103,7 +98,7 @@ fun TypingIndicator(
), ),
fontSize = 12.sp, fontSize = 12.sp,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis
) )
} }
} }

View File

@ -84,13 +84,13 @@ fun RowScope.ChannelList(
currentChannel: String?, currentChannel: String?,
onChannelClick: (String) -> Unit, onChannelClick: (String) -> Unit,
onSpecialClick: (String) -> Unit, onSpecialClick: (String) -> Unit,
onServerSheetOpenFor: (String) -> Unit, onServerSheetOpenFor: (String) -> Unit
) { ) {
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val enableSmallBanner by remember { val enableSmallBanner by remember {
derivedStateOf { derivedStateOf {
lazyListState.firstVisibleItemScrollOffset > 40 || lazyListState.firstVisibleItemScrollOffset > 40 ||
lazyListState.firstVisibleItemIndex > 0 lazyListState.firstVisibleItemIndex > 0
} }
} }
@ -120,7 +120,7 @@ fun RowScope.ChannelList(
sheetState = channelContextSheetState, sheetState = channelContextSheetState,
onDismissRequest = { onDismissRequest = {
channelContextSheetShown = false channelContextSheetShown = false
}, }
) { ) {
ChannelContextSheet( ChannelContextSheet(
channelId = channelContextSheetTarget, channelId = channelContextSheetTarget,
@ -151,13 +151,13 @@ fun RowScope.ChannelList(
modifier = Modifier modifier = Modifier
.padding(start = 4.dp, top = 8.dp, bottom = 8.dp) .padding(start = 4.dp, top = 8.dp, bottom = 8.dp)
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(16.dp))
.fillMaxWidth(), .fillMaxWidth()
) { ) {
LazyColumn( LazyColumn(
Modifier Modifier
.weight(1f) .weight(1f)
.fillMaxSize(), .fillMaxSize(),
state = lazyListState, state = lazyListState
) { ) {
if (serverId == "home") { if (serverId == "home") {
stickyHeader( stickyHeader(
@ -194,7 +194,7 @@ fun RowScope.ChannelList(
onClick = { onClick = {
onSpecialClick("home") onSpecialClick("home")
}, },
large = true, large = true
) )
} }
@ -212,7 +212,7 @@ fun RowScope.ChannelList(
onClick = { onClick = {
onChannelClick(notesChannelId ?: return@DrawerChannel) onChannelClick(notesChannelId ?: return@DrawerChannel)
}, },
large = true, large = true
) )
} }
@ -238,13 +238,19 @@ fun RowScope.ChannelList(
val channel = dmAbleChannels.getOrNull(it) ?: return@items val channel = dmAbleChannels.getOrNull(it) ?: return@items
val partner = val partner =
if (channel.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner( if (channel.channelType == ChannelType.DirectMessage) {
channel RevoltAPI.userCache[
)] else null ChannelUtils.resolveDMPartner(
channel
)
]
} else {
null
}
DrawerChannel( DrawerChannel(
name = partner?.let { p -> User.resolveDefaultName(p) } ?: channel.name name = partner?.let { p -> User.resolveDefaultName(p) } ?: channel.name
?: stringResource(R.string.unknown), ?: stringResource(R.string.unknown),
channelType = channel.channelType ?: ChannelType.TextChannel, channelType = channel.channelType ?: ChannelType.TextChannel,
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id, selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
hasUnread = channel.lastMessageID?.let { lastMessageID -> hasUnread = channel.lastMessageID?.let { lastMessageID ->
@ -313,7 +319,9 @@ fun RowScope.ChannelList(
Glide.with(this) Glide.with(this)
.load("$REVOLT_FILES/banners/${server.banner.id}") .load("$REVOLT_FILES/banners/${server.banner.id}")
.transition(DrawableTransitionOptions.withCrossFade()) .transition(
DrawableTransitionOptions.withCrossFade()
)
.into(this) .into(this)
} }
}, },
@ -348,21 +356,29 @@ fun RowScope.ChannelList(
Box( Box(
modifier = Modifier modifier = Modifier
.alpha(0.9f) .alpha(0.9f)
.height(BANNER_HEIGHT_COMPACT.dp + 8.dp) // due to padding in Text .height(
BANNER_HEIGHT_COMPACT.dp + 8.dp
) // due to padding in Text
.fillMaxWidth() .fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)) .background(
MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
)
) )
} }
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
) { ) {
Spacer(Modifier.width(16.dp)) Spacer(Modifier.width(16.dp))
if (server?.flags has ServerFlags.Official) { if (server?.flags has ServerFlags.Official) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_revolt_decagram_24dp), painter = painterResource(
contentDescription = stringResource(R.string.server_flag_official), id = R.drawable.ic_revolt_decagram_24dp
),
contentDescription = stringResource(
R.string.server_flag_official
),
tint = if (server?.banner != null) { tint = if (server?.banner != null) {
bannerTextColour bannerTextColour
} else { } else {
@ -375,8 +391,12 @@ fun RowScope.ChannelList(
} }
if (server?.flags has ServerFlags.Verified) { if (server?.flags has ServerFlags.Verified) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_check_decagram_24dp), painter = painterResource(
contentDescription = stringResource(R.string.server_flag_verified), id = R.drawable.ic_check_decagram_24dp
),
contentDescription = stringResource(
R.string.server_flag_verified
),
tint = if (server?.banner != null) { tint = if (server?.banner != null) {
bannerTextColour bannerTextColour
} else { } else {
@ -389,8 +409,10 @@ fun RowScope.ChannelList(
} }
Text( Text(
text = (server?.name text = (
?: stringResource(R.string.unknown)), server?.name
?: stringResource(R.string.unknown)
),
style = MaterialTheme.typography.labelLarge, style = MaterialTheme.typography.labelLarge,
color = if (server?.banner != null) { color = if (server?.banner != null) {
bannerTextColour bannerTextColour
@ -438,7 +460,6 @@ fun RowScope.ChannelList(
} }
} }
} }
} }
if (categorisedChannels.isNullOrEmpty()) { if (categorisedChannels.isNullOrEmpty()) {
@ -458,7 +479,7 @@ fun RowScope.ChannelList(
Text( Text(
text = stringResource(R.string.no_channels_body), text = stringResource(R.string.no_channels_body),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center, textAlign = TextAlign.Center
) )
} }
} }
@ -480,9 +501,15 @@ fun RowScope.ChannelList(
val channel = item.channel val channel = item.channel
val partner = val partner =
if (channel.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner( if (channel.channelType == ChannelType.DirectMessage) {
channel RevoltAPI.userCache[
)] else null ChannelUtils.resolveDMPartner(
channel
)
]
} else {
null
}
DrawerChannel( DrawerChannel(
name = partner?.let { p -> User.resolveDefaultName(p) } name = partner?.let { p -> User.resolveDefaultName(p) }
@ -498,7 +525,11 @@ fun RowScope.ChannelList(
} ?: false, } ?: false,
dmPartnerIcon = partner?.avatar ?: channel.icon, dmPartnerIcon = partner?.avatar ?: channel.icon,
dmPartnerId = partner?.id, dmPartnerId = partner?.id,
dmPartnerName = partner?.let { p -> User.resolveDefaultName(p) }, dmPartnerName = partner?.let { p ->
User.resolveDefaultName(
p
)
},
dmPartnerStatus = presenceFromStatus( dmPartnerStatus = presenceFromStatus(
status = partner?.status?.presence, status = partner?.status?.presence,
online = partner?.online ?: false online = partner?.online ?: false

View File

@ -46,11 +46,14 @@ fun DrawerChannel(
dmPartnerName: String? = null, dmPartnerName: String? = null,
dmPartnerIcon: AutumnResource? = null, dmPartnerIcon: AutumnResource? = null,
dmPartnerId: String? = null, dmPartnerId: String? = null,
large: Boolean = false, large: Boolean = false
) { ) {
val backgroundColor = animateColorAsState( val backgroundColor = animateColorAsState(
if (selected) MaterialTheme.colorScheme.background if (selected) {
else Color.Transparent, MaterialTheme.colorScheme.background
} else {
Color.Transparent
},
animationSpec = spring(), animationSpec = spring(),
label = "Channel background colour" label = "Channel background colour"
) )
@ -102,12 +105,16 @@ fun DrawerChannel(
else -> ChannelIcon( else -> ChannelIcon(
channelType = channelType, channelType = channelType,
modifier = Modifier.then( modifier = Modifier.then(
if (large) Modifier.padding( if (large) {
end = 12.dp, Modifier.padding(
start = 4.dp, end = 12.dp,
top = 4.dp, start = 4.dp,
bottom = 4.dp top = 4.dp,
) else Modifier.padding(end = 8.dp) bottom = 4.dp
)
} else {
Modifier.padding(end = 8.dp)
}
) )
) )
} }

View File

@ -41,7 +41,7 @@ fun DrawerServer(
) { ) {
if (iconId != null) { if (iconId != null) {
RemoteImage( RemoteImage(
url = "$REVOLT_FILES/icons/${iconId}/server.png?max_side=256", url = "$REVOLT_FILES/icons/$iconId/server.png?max_side=256",
modifier = Modifier modifier = Modifier
.padding(8.dp) .padding(8.dp)
.size(48.dp) .size(48.dp)

View File

@ -12,10 +12,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@Composable @Composable
fun DrawerServerlikeIcon( fun DrawerServerlikeIcon(onClick: () -> Unit, content: @Composable () -> Unit) {
onClick: () -> Unit,
content: @Composable () -> Unit
) {
IconButton( IconButton(
onClick = onClick, onClick = onClick,
modifier = Modifier modifier = Modifier

View File

@ -29,7 +29,7 @@ fun LinkOnHome(
icon: @Composable (Modifier) -> Unit, icon: @Composable (Modifier) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
description: @Composable () -> Unit = {}, description: @Composable () -> Unit = {},
onClick: () -> Unit, onClick: () -> Unit
) { ) {
Box( Box(
modifier = modifier modifier = modifier
@ -37,12 +37,12 @@ fun LinkOnHome(
.clickable { onClick() } .clickable { onClick() }
.padding(20.dp) .padding(20.dp)
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 5.dp), .padding(vertical = 5.dp)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
) { ) {
icon(Modifier.padding(end = 14.dp)) icon(Modifier.padding(end = 14.dp))

View File

@ -17,10 +17,7 @@ import chat.revolt.R
import chat.revolt.screens.about.Library import chat.revolt.screens.about.Library
@Composable @Composable
fun AttributionItem( fun AttributionItem(library: Library, onClick: () -> Unit) {
library: Library,
onClick: () -> Unit
) {
SelectionContainer { SelectionContainer {
Column( Column(
modifier = Modifier modifier = Modifier
@ -30,16 +27,15 @@ fun AttributionItem(
) { ) {
Text( Text(
text = library.name, text = library.name,
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(id = R.string.oss_attribution_tap_to_view_license), text = stringResource(id = R.string.oss_attribution_tap_to_view_license),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall
) )
} }
} }
} }

View File

@ -97,9 +97,11 @@ fun RawUserOverview(user: User, profile: Profile? = null) {
width = 4.dp, width = 4.dp,
brush = teamMemberFlair brush = teamMemberFlair
?: Brush.solidColor(Color.Transparent), ?: Brush.solidColor(Color.Transparent),
shape = MaterialTheme.shapes.large, shape = MaterialTheme.shapes.large
) )
} else Modifier } else {
Modifier
}
) )
) { ) {
profile?.background?.let { background -> profile?.background?.let { background ->
@ -138,7 +140,7 @@ fun RawUserOverview(user: User, profile: Profile? = null) {
userId = user.id ?: ULID.makeSpecial(0), userId = user.id ?: ULID.makeSpecial(0),
avatar = user.avatar, avatar = user.avatar,
size = 48.dp, size = 48.dp,
presence = presenceFromStatus(user.status?.presence), presence = presenceFromStatus(user.status?.presence)
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
@ -156,7 +158,7 @@ fun RawUserOverview(user: User, profile: Profile? = null) {
append("#${user.discriminator}") append("#${user.discriminator}")
pop() pop()
}.toAnnotatedString(), }.toAnnotatedString(),
color = if (profile?.background != null) Color.White else LocalContentColor.current, color = if (profile?.background != null) Color.White else LocalContentColor.current
) )
} }
} }

View File

@ -26,10 +26,12 @@ fun ThemeChip(
.clickable(onClick = onClick) .clickable(onClick = onClick)
.then(modifier) .then(modifier)
.then( .then(
if (selected) if (selected) {
Modifier Modifier
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)) .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f))
else Modifier } else {
Modifier
}
) )
.padding(4.dp) .padding(4.dp)
.padding(8.dp) .padding(8.dp)

View File

@ -17,9 +17,7 @@ import androidx.compose.ui.unit.sp
import chat.revolt.R import chat.revolt.R
@Composable @Composable
fun DisconnectedScreen( fun DisconnectedScreen(onRetry: () -> Unit) {
onRetry: () -> Unit
) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@ -35,7 +33,7 @@ fun DisconnectedScreen(
textAlign = TextAlign.Center textAlign = TextAlign.Center
), ),
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth()
) )
Text( Text(
@ -43,7 +41,7 @@ fun DisconnectedScreen(
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f), color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(vertical = 10.dp, horizontal = 20.dp) .padding(vertical = 10.dp, horizontal = 20.dp)

View File

@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
data class Changelog( data class Changelog(
val summary: String, val summary: String,
val version: String, val version: String,
val date: String, val date: String
) )
@Serializable @Serializable
@ -26,19 +26,27 @@ class Changelogs(val context: Context, val kvStorage: KVStorage? = null) {
} }
fun getChangelog(version: String): String { fun getChangelog(version: String): String {
return context.assets.open("changelogs/${version}.md").use { return context.assets.open("changelogs/$version.md").use {
it.reader().readText() it.reader().readText()
} }
} }
suspend fun hasSeenLatest(): Boolean { suspend fun hasSeenLatest(): Boolean {
if (kvStorage == null) throw IllegalStateException("Not supported for non-KVStorage instances of Changelogs") if (kvStorage == null) {
throw IllegalStateException(
"Not supported for non-KVStorage instances of Changelogs"
)
}
return kvStorage.get("latestChangelogRead") == index.latest return kvStorage.get("latestChangelogRead") == index.latest
} }
suspend fun markAsSeen() { suspend fun markAsSeen() {
if (kvStorage == null) throw IllegalStateException("Not supported for non-KVStorage instances of Changelogs") if (kvStorage == null) {
throw IllegalStateException(
"Not supported for non-KVStorage instances of Changelogs"
)
}
kvStorage.set("latestChangelogRead", index.latest) kvStorage.set("latestChangelogRead", index.latest)
} }

View File

@ -15,13 +15,13 @@ data class Emoji(
val alternates: List<List<Long>>, val alternates: List<List<Long>>,
val emoticons: List<String>, val emoticons: List<String>,
val shortcodes: List<String>, val shortcodes: List<String>,
val animated: Boolean, val animated: Boolean
) )
@Serializable @Serializable
data class EmojiGroup( data class EmojiGroup(
val group: String, val group: String,
val emoji: List<Emoji>, val emoji: List<Emoji>
) )
enum class FitzpatrickSkinTone(val modifierCodepoint: Int?) { enum class FitzpatrickSkinTone(val modifierCodepoint: Int?) {
@ -30,7 +30,7 @@ enum class FitzpatrickSkinTone(val modifierCodepoint: Int?) {
MediumLight(0x1F3FC), MediumLight(0x1F3FC),
Medium(0x1F3FD), Medium(0x1F3FD),
MediumDark(0x1F3FE), MediumDark(0x1F3FE),
Dark(0x1F3FF), Dark(0x1F3FF)
} }
enum class UnicodeEmojiSection(val googleName: String, val nameResource: Int) { enum class UnicodeEmojiSection(val googleName: String, val nameResource: Int) {
@ -42,7 +42,7 @@ enum class UnicodeEmojiSection(val googleName: String, val nameResource: Int) {
Activities("Activities and events", R.string.emoji_category_activities), Activities("Activities and events", R.string.emoji_category_activities),
Objects("Objects", R.string.emoji_category_objects), Objects("Objects", R.string.emoji_category_objects),
Symbols("Symbols", R.string.emoji_category_symbols), Symbols("Symbols", R.string.emoji_category_symbols),
Flags("Flags", R.string.emoji_category_flags), Flags("Flags", R.string.emoji_category_flags)
} }
sealed class Category { sealed class Category {
@ -55,7 +55,7 @@ sealed class EmojiPickerItem {
data class UnicodeEmoji( data class UnicodeEmoji(
val character: String, val character: String,
val hasSkinTones: Boolean, val hasSkinTones: Boolean,
val alternates: List<List<Long>>, val alternates: List<List<Long>>
) : EmojiPickerItem() ) : EmojiPickerItem()
data class ServerEmote(val emote: chat.revolt.api.schemas.Emoji) : EmojiPickerItem() data class ServerEmote(val emote: chat.revolt.api.schemas.Emoji) : EmojiPickerItem()
@ -106,17 +106,19 @@ class EmojiImpl {
val category = val category =
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category))) list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
list.addAll(group.emoji.map { emoji -> list.addAll(
EmojiPickerItem.UnicodeEmoji( group.emoji.map { emoji ->
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) }, EmojiPickerItem.UnicodeEmoji(
emoji.alternates.any { alternate -> emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
alternate.any { codepoint -> emoji.alternates.any { alternate ->
codepoint in 0x1F3FB..0x1F3FF alternate.any { codepoint ->
} codepoint in 0x1F3FB..0x1F3FF
}, }
emoji.alternates },
) emoji.alternates
}) )
}
)
} }
return list return list
@ -143,7 +145,9 @@ class EmojiImpl {
for (server in serversWithEmotes()) { for (server in serversWithEmotes()) {
val index = val index =
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server } flatPickerList.indexOfFirst {
it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server
}
val allEmotesInThatServer = val allEmotesInThatServer =
RevoltAPI.emojiCache.values.filter { it.parent?.id == server.id } RevoltAPI.emojiCache.values.filter { it.parent?.id == server.id }
val lastIndex = index + allEmotesInThatServer.size val lastIndex = index + allEmotesInThatServer.size
@ -152,12 +156,16 @@ class EmojiImpl {
} }
for (section in UnicodeEmojiSection.entries) { for (section in UnicodeEmojiSection.entries) {
val index = val index =
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == section } flatPickerList.indexOfFirst {
it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == section
}
val lastIndex = if (section == UnicodeEmojiSection.entries.last()) { val lastIndex = if (section == UnicodeEmojiSection.entries.last()) {
Int.MAX_VALUE Int.MAX_VALUE
} else { } else {
val nextSection = UnicodeEmojiSection.entries[section.ordinal + 1] val nextSection = UnicodeEmojiSection.entries[section.ordinal + 1]
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == nextSection } - 1 flatPickerList.indexOfFirst {
it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == nextSection
} - 1
} }
output[Category.UnicodeEmojiCategory(section)] = Pair(index, lastIndex) output[Category.UnicodeEmojiCategory(section)] = Pair(index, lastIndex)
} }
@ -220,17 +228,19 @@ class EmojiImpl {
val category = val category =
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category))) list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
list.addAll(matchingEmoji.map { emoji -> list.addAll(
EmojiPickerItem.UnicodeEmoji( matchingEmoji.map { emoji ->
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) }, EmojiPickerItem.UnicodeEmoji(
emoji.alternates.any { alternate -> emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
alternate.any { codepoint -> emoji.alternates.any { alternate ->
codepoint in 0x1F3FB..0x1F3FF alternate.any { codepoint ->
} codepoint in 0x1F3FB..0x1F3FF
}, }
emoji.alternates },
) emoji.alternates
}) )
}
)
} }
} }

View File

@ -28,7 +28,7 @@ class BlockBackgroundNode<R>(
private val quoteDepth: Int, private val quoteDepth: Int,
private val fillColor: Int = Color.DKGRAY, private val fillColor: Int = Color.DKGRAY,
private val strokeColor: Int = Color.BLACK, private val strokeColor: Int = Color.BLACK,
vararg children: Node<R>, vararg children: Node<R>
) : Node.Parent<R>(*children) { ) : Node.Parent<R>(*children) {
override fun render(builder: SpannableStringBuilder, renderContext: R) { override fun render(builder: SpannableStringBuilder, renderContext: R) {
@ -41,7 +41,8 @@ class BlockBackgroundNode<R>(
ensureEndsWithNewline(builder) ensureEndsWithNewline(builder)
val backgroundSpan = BlockBackgroundSpan( val backgroundSpan = BlockBackgroundSpan(
fillColor, strokeColor, fillColor,
strokeColor,
strokeWidth = 2, strokeWidth = 2,
strokeRadius = 15, strokeRadius = 15,
leftMargin = 40 * quoteDepth leftMargin = 40 * quoteDepth

View File

@ -9,8 +9,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class EmoteSpan(drawable: Drawable) : class EmoteSpan(drawable: Drawable) :
ImageSpan(drawable, ALIGN_BOTTOM) { ImageSpan(drawable, ALIGN_BOTTOM)
}
class EmoteClickableSpan(private val emoteId: String) : LongClickableSpan() { class EmoteClickableSpan(private val emoteId: String) : LongClickableSpan() {
override fun onClick(widget: View) { override fun onClick(widget: View) {

View File

@ -20,9 +20,11 @@ class LinkSpan(private val url: String, private val drawBackground: Boolean = fa
// Intercept invite links // Intercept invite links
if (uri.host == Uri.parse(REVOLT_INVITES).host!! || if (uri.host == Uri.parse(REVOLT_INVITES).host!! ||
(uri.host?.endsWith(Uri.parse(REVOLT_APP).host!!) == true && uri.path?.startsWith( (
"/invite" uri.host?.endsWith(Uri.parse(REVOLT_APP).host!!) == true && uri.path?.startsWith(
) == true) "/invite"
) == true
)
) { ) {
val intent = Intent( val intent = Intent(
widget.context, widget.context,

View File

@ -19,11 +19,7 @@ abstract class LongClickableSpan : ClickableSpan() {
class LongClickLinkMovementMethod : LinkMovementMethod() { class LongClickLinkMovementMethod : LinkMovementMethod() {
private var longClickHandler: Handler? = null private var longClickHandler: Handler? = null
private var isLongPressed = false private var isLongPressed = false
override fun onTouchEvent( override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
widget: TextView,
buffer: Spannable,
event: MotionEvent
): Boolean {
val action = event.action val action = event.action
if (action == MotionEvent.ACTION_CANCEL) { if (action == MotionEvent.ACTION_CANCEL) {
longClickHandler?.removeCallbacksAndMessages(null) longClickHandler?.removeCallbacksAndMessages(null)
@ -41,7 +37,8 @@ class LongClickLinkMovementMethod : LinkMovementMethod() {
val line = layout.getLineForVertical(y) val line = layout.getLineForVertical(y)
val off = layout.getOffsetForHorizontal(line, x.toFloat()) val off = layout.getOffsetForHorizontal(line, x.toFloat())
val link = buffer.getSpans( val link = buffer.getSpans(
off, off, off,
off,
LongClickableSpan::class.java LongClickableSpan::class.java
) )
if (link.isNotEmpty()) { if (link.isNotEmpty()) {

View File

@ -17,5 +17,5 @@ data class MarkdownContext(
val channelMap: Map<String, String>, val channelMap: Map<String, String>,
val emojiMap: Map<String, Emoji>, val emojiMap: Map<String, Emoji>,
val serverId: String?, val serverId: String?,
val useLargeEmojis: Boolean, val useLargeEmojis: Boolean
) )

View File

@ -17,7 +17,7 @@ class UserMentionNode(private val userId: String) : Node<MarkdownContext>() {
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) { override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
val content = renderContext.memberMap[userId]?.let { "@$it" } val content = renderContext.memberMap[userId]?.let { "@$it" }
?: renderContext.userMap[userId]?.let { "@${it.username}" } ?: renderContext.userMap[userId]?.let { "@${it.username}" }
?: "<@${userId}>" ?: "<@$userId>"
builder.append(content) builder.append(content)
builder.setSpan( builder.setSpan(
@ -58,7 +58,7 @@ class CustomEmoteNode(private val emoteId: String, private val context: Context)
Node<MarkdownContext>() { Node<MarkdownContext>() {
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) { override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
val content = renderContext.emojiMap[emoteId]?.let { ":${it.name}:" } val content = renderContext.emojiMap[emoteId]?.let { ":${it.name}:" }
?: ":${emoteId}:" ?: ":$emoteId:"
val isGif = renderContext.emojiMap[emoteId]?.animated ?: false val isGif = renderContext.emojiMap[emoteId]?.animated ?: false
val emoteUrl = "$REVOLT_FILES/emojis/$emoteId/emote${if (isGif) ".gif" else ".png"}" val emoteUrl = "$REVOLT_FILES/emojis/$emoteId/emote${if (isGif) ".gif" else ".png"}"

View File

@ -65,7 +65,8 @@ class TimestampRule<S>(private val context: Context) :
matcher.group(2) matcher.group(2)
), ),
listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance))
), state ),
state
) )
} }
} }
@ -113,10 +114,7 @@ fun <RC, S> createInlineCodeRule(context: Context, backgroundColor: Int): Rule<R
) )
} }
fun <RC> createCodeRule( fun <RC> createCodeRule(context: Context, backgroundColor: Int): Rule<RC, Node<RC>, MarkdownState> {
context: Context,
backgroundColor: Int
): Rule<RC, Node<RC>, MarkdownState> {
val codeStyleProviders = CodeStyleProviders<RC>( val codeStyleProviders = CodeStyleProviders<RC>(
defaultStyleProvider = { listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) }, defaultStyleProvider = { listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) },
commentStyleProvider = { commentStyleProvider = {
@ -174,7 +172,7 @@ fun <RC> createCodeRule(
R.style.Code_TextAppearance_Params R.style.Code_TextAppearance_Params
) )
) )
}, }
) )
val languageMap = CodeRules.createCodeLanguageMap<RC, MarkdownState>(codeStyleProviders) val languageMap = CodeRules.createCodeLanguageMap<RC, MarkdownState>(codeStyleProviders)
@ -203,6 +201,6 @@ fun MarkdownParser.addRevoltRules(context: Context): MarkdownParser {
CustomEmoteRule(context), CustomEmoteRule(context),
TimestampRule(context), TimestampRule(context),
NamedLinkRule(), NamedLinkRule(),
LinkRule(), LinkRule()
) )
} }

View File

@ -2,12 +2,12 @@ package chat.revolt.internals.markdown
import android.text.format.DateUtils import android.text.format.DateUtils
import android.util.Log import android.util.Log
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.toJavaInstant
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Locale import java.util.Locale
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.toJavaInstant
fun resolveTimestamp(timestamp: Long, modifier: String? = null): String { fun resolveTimestamp(timestamp: Long, modifier: String? = null): String {
val normalisedModifier = modifier.orEmpty().removePrefix(":") val normalisedModifier = modifier.orEmpty().removePrefix(":")

View File

@ -7,9 +7,9 @@ import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlinx.coroutines.flow.firstOrNull
val Context.revoltKVStorage: DataStore<Preferences> by preferencesDataStore(name = "revolt_kv") val Context.revoltKVStorage: DataStore<Preferences> by preferencesDataStore(name = "revolt_kv")
@ -45,4 +45,3 @@ class KVStorage @Inject constructor(
} }
} }
} }

View File

@ -10,8 +10,7 @@ import io.ktor.client.request.get
import io.ktor.client.statement.readBytes import io.ktor.client.statement.readBytes
import java.io.File import java.io.File
class AttachmentProvider : FileProvider(R.xml.file_paths) { class AttachmentProvider : FileProvider(R.xml.file_paths)
}
suspend fun getAttachmentContentUri( suspend fun getAttachmentContentUri(
context: Context, context: Context,

View File

@ -37,8 +37,8 @@ import chat.revolt.persistence.KVStorage
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.ktor.client.request.get import io.ktor.client.request.get
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.launch
@HiltViewModel @HiltViewModel
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
@ -138,10 +138,7 @@ class SplashScreenViewModel @Inject constructor(
} }
@Composable @Composable
fun SplashScreen( fun SplashScreen(navController: NavController, viewModel: SplashScreenViewModel = hiltViewModel()) {
navController: NavController,
viewModel: SplashScreenViewModel = hiltViewModel()
) {
val context = LocalContext.current val context = LocalContext.current
val webChallengeActivityResult = rememberLauncherForActivityResult( val webChallengeActivityResult = rememberLauncherForActivityResult(

View File

@ -51,12 +51,11 @@ import chat.revolt.api.routes.misc.getRootRoute
import chat.revolt.components.generic.PageHeader import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.PrimaryTabs import chat.revolt.components.generic.PrimaryTabs
import chat.revolt.internals.Platform import chat.revolt.internals.Platform
import java.net.URI
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import java.net.URI
class AboutViewModel( class AboutViewModel() : ViewModel() {
) : ViewModel() {
var root by mutableStateOf<Root?>(null) var root by mutableStateOf<Root?>(null)
var selectedTabIndex by mutableIntStateOf(0) var selectedTabIndex by mutableIntStateOf(0)
@ -83,11 +82,7 @@ class AboutViewModel(
} }
@Composable @Composable
fun VersionItem( fun VersionItem(key: String, value: String, modifier: Modifier = Modifier) {
key: String,
value: String,
modifier: Modifier = Modifier
) {
Row(modifier) { Row(modifier) {
Text( Text(
text = key, text = key,
@ -131,16 +126,15 @@ fun DebugInfo(viewModel: AboutViewModel) {
} }
@Composable @Composable
fun AboutScreen( fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewModel()) {
navController: NavController,
viewModel: AboutViewModel = viewModel()
) {
val context = LocalContext.current val context = LocalContext.current
val clipboardManager: ClipboardManager = val clipboardManager: ClipboardManager =
LocalClipboardManager.current LocalClipboardManager.current
fun copyDebugInformation() { fun copyDebugInformation() {
clipboardManager.setText(AnnotatedString(RevoltJson.encodeToString(viewModel.getDebugInformation()))) clipboardManager.setText(
AnnotatedString(RevoltJson.encodeToString(viewModel.getDebugInformation()))
)
if (Platform.needsShowClipboardNotification()) { if (Platform.needsShowClipboardNotification()) {
Toast.makeText( Toast.makeText(
@ -161,7 +155,8 @@ fun AboutScreen(
PageHeader( PageHeader(
text = stringResource(R.string.about), text = stringResource(R.string.about),
showBackButton = true, showBackButton = true,
onBackButtonClicked = { navController.popBackStack() }) onBackButtonClicked = { navController.popBackStack() }
)
PrimaryTabs( PrimaryTabs(
tabs = listOf( tabs = listOf(
@ -169,7 +164,8 @@ fun AboutScreen(
stringResource(R.string.about_tab_details) stringResource(R.string.about_tab_details)
), ),
currentIndex = viewModel.selectedTabIndex, currentIndex = viewModel.selectedTabIndex,
onTabSelected = { viewModel.selectedTabIndex = it }) onTabSelected = { viewModel.selectedTabIndex = it }
)
Column( Column(
modifier = Modifier modifier = Modifier

View File

@ -48,7 +48,7 @@ data class AboutLibraries(
@Serializable @Serializable
data class Metadata( data class Metadata(
val generated: String, val generated: String
) )
@Serializable @Serializable
@ -58,7 +58,7 @@ data class License(
val internalHash: String? = null, val internalHash: String? = null,
val url: String, val url: String,
val spdxId: String? = null, val spdxId: String? = null,
val name: String, val name: String
) )
@Serializable @Serializable
@ -72,26 +72,26 @@ data class Library(
val name: String, val name: String,
val licenses: List<String>, val licenses: List<String>,
val website: String? = null, val website: String? = null,
val organization: Organization? = null, val organization: Organization? = null
) )
@Serializable @Serializable
data class Organization( data class Organization(
val url: String, val url: String,
val name: String, val name: String
) )
@Serializable @Serializable
data class Developer( data class Developer(
val organisationUrl: String? = null, val organisationUrl: String? = null,
val name: String? = null, val name: String? = null
) )
@Serializable @Serializable
data class Scm( data class Scm(
val connection: String? = null, val connection: String? = null,
val url: String, val url: String,
val developerConnection: String? = null, val developerConnection: String? = null
) )
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -151,7 +151,8 @@ fun AttributionScreen(navController: NavController) {
PageHeader( PageHeader(
text = stringResource(R.string.oss_attribution), text = stringResource(R.string.oss_attribution),
showBackButton = true, showBackButton = true,
onBackButtonClicked = { navController.popBackStack() }) onBackButtonClicked = { navController.popBackStack() }
)
libraries?.let { libraries?.let {
LazyColumn { LazyColumn {
@ -207,5 +208,4 @@ fun AttributionScreen(navController: NavController) {
} }
} }
} }
} }

View File

@ -109,10 +109,9 @@ import com.airbnb.lottie.compose.rememberLottieComposition
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 io.sentry.Sentry
import javax.inject.Inject
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel @HiltViewModel
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
@ -202,9 +201,9 @@ class ChatRouterViewModel @Inject constructor(
if (!pure) setSaveCurrentChannel(channelId) if (!pure) setSaveCurrentChannel(channelId)
@FeatureFlag("ClosedBetaAccessControl") @FeatureFlag("ClosedBetaAccessControl")
if (RevoltAPI.channelCache.size > 0 if (RevoltAPI.channelCache.size > 0 &&
&& FeatureFlags.closedBetaAccessControl is ClosedBetaAccessControlVariates.Restricted FeatureFlags.closedBetaAccessControl is ClosedBetaAccessControlVariates.Restricted &&
&& (FeatureFlags.closedBetaAccessControl as ClosedBetaAccessControlVariates.Restricted) (FeatureFlags.closedBetaAccessControl as ClosedBetaAccessControlVariates.Restricted)
.predicate() .predicate()
.not() .not()
) { ) {
@ -229,7 +228,9 @@ class ChatRouterViewModel @Inject constructor(
.setFlags( .setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK
) )
context.startActivity(intent) // i'm just messing with the user at this point, they know what they did context.startActivity(
intent
) // i'm just messing with the user at this point, they know what they did
Pipebomb.doHardCrash() Pipebomb.doHardCrash()
} }
@ -269,9 +270,11 @@ fun ChatRouterScreen(
val navController = rememberNavController() val navController = rememberNavController()
val showSidebarSpark = remember { mutableStateOf(false) } val showSidebarSpark = remember { mutableStateOf(false) }
val sidebarSparkComposition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.open_settings_tutorial)) val sidebarSparkComposition by rememberLottieComposition(
LottieCompositionSpec.RawRes(R.raw.open_settings_tutorial)
)
val sidebarSparkProgress by animateLottieCompositionAsState( val sidebarSparkProgress by animateLottieCompositionAsState(
composition = sidebarSparkComposition, composition = sidebarSparkComposition
) )
var showPlatformModDMHint by remember { mutableStateOf(false) } var showPlatformModDMHint by remember { mutableStateOf(false) }
@ -364,8 +367,8 @@ fun ChatRouterScreen(
snapshotFlow { windowSizeClass } snapshotFlow { windowSizeClass }
.distinctUntilChanged() .distinctUntilChanged()
.collect { sizeClass -> .collect { sizeClass ->
useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded &&
&& sizeClass.heightSizeClass != WindowHeightSizeClass.Compact sizeClass.heightSizeClass != WindowHeightSizeClass.Compact
} }
} }
@ -416,7 +419,7 @@ fun ChatRouterScreen(
sheetState = changelogSheetState, sheetState = changelogSheetState,
onDismissRequest = { onDismissRequest = {
viewModel.latestChangelogRead = true viewModel.latestChangelogRead = true
}, }
) { ) {
ChangelogSheet( ChangelogSheet(
version = viewModel.latestChangelog, version = viewModel.latestChangelog,
@ -441,8 +444,12 @@ fun ChatRouterScreen(
.aspectRatio(1f), .aspectRatio(1f),
renderMode = RenderMode.HARDWARE renderMode = RenderMode.HARDWARE
) )
Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1)) Text(
Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2)) stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1)
)
Text(
stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2)
)
} }
}, },
confirmButton = { confirmButton = {
@ -488,7 +495,7 @@ fun ChatRouterScreen(
sheetState = statusSheetState, sheetState = statusSheetState,
onDismissRequest = { onDismissRequest = {
showStatusSheet = false showStatusSheet = false
}, }
) { ) {
StatusSheet( StatusSheet(
onBeforeNavigation = { onBeforeNavigation = {
@ -511,13 +518,12 @@ fun ChatRouterScreen(
sheetState = addServerSheetState, sheetState = addServerSheetState,
onDismissRequest = { onDismissRequest = {
showAddServerSheet = false showAddServerSheet = false
}, }
) { ) {
AddServerSheet() AddServerSheet()
} }
} }
if (showServerContextSheet) { if (showServerContextSheet) {
val serverContextSheetState = rememberModalBottomSheetState() val serverContextSheetState = rememberModalBottomSheetState()
@ -525,14 +531,14 @@ fun ChatRouterScreen(
sheetState = serverContextSheetState, sheetState = serverContextSheetState,
onDismissRequest = { onDismissRequest = {
showServerContextSheet = false showServerContextSheet = false
}, }
) { ) {
ServerContextSheet( ServerContextSheet(
serverId = serverContextSheetTarget, serverId = serverContextSheetTarget,
onHideSheet = { onHideSheet = {
serverContextSheetState.hide() serverContextSheetState.hide()
showServerContextSheet = false showServerContextSheet = false
}, }
) )
} }
} }
@ -544,7 +550,7 @@ fun ChatRouterScreen(
sheetState = userContextSheetState, sheetState = userContextSheetState,
onDismissRequest = { onDismissRequest = {
showUserContextSheet = false showUserContextSheet = false
}, }
) { ) {
UserContextSheet( UserContextSheet(
userId = userContextSheetTarget, userId = userContextSheetTarget,
@ -569,14 +575,14 @@ fun ChatRouterScreen(
Text( Text(
text = stringResource(id = R.string.channel_link_invalid), text = stringResource(id = R.string.channel_link_invalid),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
) )
}, },
text = { text = {
Text( Text(
text = stringResource(id = R.string.channel_link_invalid_description), text = stringResource(id = R.string.channel_link_invalid_description),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
) )
}, },
confirmButton = { confirmButton = {
@ -596,7 +602,7 @@ fun ChatRouterScreen(
sheetState = linkInfoSheetState, sheetState = linkInfoSheetState,
onDismissRequest = { onDismissRequest = {
showLinkInfoSheet = false showLinkInfoSheet = false
}, }
) { ) {
LinkInfoSheet( LinkInfoSheet(
url = linkInfoSheetUrl, url = linkInfoSheetUrl,
@ -614,7 +620,7 @@ fun ChatRouterScreen(
sheetState = emoteInfoSheetState, sheetState = emoteInfoSheetState,
onDismissRequest = { onDismissRequest = {
showEmoteInfoSheet = false showEmoteInfoSheet = false
}, }
) { ) {
EmoteInfoSheet( EmoteInfoSheet(
id = emoteInfoSheetTarget, id = emoteInfoSheetTarget,
@ -630,19 +636,22 @@ fun ChatRouterScreen(
.fillMaxWidth() .fillMaxWidth()
.safeDrawingPadding() .safeDrawingPadding()
) { ) {
AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) { AnimatedVisibility(
visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected
) {
DisconnectedNotice( DisconnectedNotice(
state = RealtimeSocket.disconnectionState, state = RealtimeSocket.disconnectionState,
onReconnect = { onReconnect = {
RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting) RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting)
scope.launch { RevoltAPI.connectWS() } scope.launch { RevoltAPI.connectWS() }
}) }
)
} }
if (useTabletAwareUI) { if (useTabletAwareUI) {
Row { Row {
DismissibleDrawerSheet( DismissibleDrawerSheet(
drawerContainerColor = Color.Transparent, drawerContainerColor = Color.Transparent
) { ) {
Sidebar( Sidebar(
viewModel = viewModel, viewModel = viewModel,
@ -656,7 +665,7 @@ fun ChatRouterScreen(
}, },
onShowAddServerSheet = { onShowAddServerSheet = {
showAddServerSheet = true showAddServerSheet = true
}, }
) )
} }
ChannelNavigator( ChannelNavigator(
@ -670,7 +679,7 @@ fun ChatRouterScreen(
userContextSheetTarget = target userContextSheetTarget = target
userContextSheetServer = server userContextSheetServer = server
showUserContextSheet = true showUserContextSheet = true
}, }
) )
} }
} else { } else {
@ -678,7 +687,7 @@ fun ChatRouterScreen(
drawerState = drawerState, drawerState = drawerState,
drawerContent = { drawerContent = {
DismissibleDrawerSheet( DismissibleDrawerSheet(
drawerContainerColor = Color.Transparent, drawerContainerColor = Color.Transparent
) { ) {
Sidebar( Sidebar(
viewModel = viewModel, viewModel = viewModel,
@ -693,7 +702,7 @@ fun ChatRouterScreen(
onShowAddServerSheet = { onShowAddServerSheet = {
showAddServerSheet = true showAddServerSheet = true
}, },
drawerState = drawerState, drawerState = drawerState
) )
} }
}, },
@ -711,10 +720,11 @@ fun ChatRouterScreen(
userContextSheetTarget = target userContextSheetTarget = target
userContextSheetServer = server userContextSheetServer = server
showUserContextSheet = true showUserContextSheet = true
}, }
) )
} }
}) }
)
} }
} }
} }
@ -726,7 +736,7 @@ fun Sidebar(
drawerState: DrawerState? = null, drawerState: DrawerState? = null,
onShowStatusSheet: () -> Unit, onShowStatusSheet: () -> Unit,
onShowServerContextSheet: (String) -> Unit, onShowServerContextSheet: (String) -> Unit,
onShowAddServerSheet: () -> Unit, onShowAddServerSheet: () -> Unit
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -786,9 +796,15 @@ fun Sidebar(
else -> { else -> {
val partner = val partner =
if (it.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner( if (it.channelType == ChannelType.DirectMessage) {
it RevoltAPI.userCache[
)] else null ChannelUtils.resolveDMPartner(
it
)
]
} else {
null
}
UserAvatar( UserAvatar(
username = partner?.let { p -> username = partner?.let { p ->
@ -830,17 +846,22 @@ fun Sidebar(
// - Sort the servers that are in the ordering using the ordering. // - Sort the servers that are in the ordering using the ordering.
// - Add the servers that aren't in the ordering to the end of the list. // - Add the servers that aren't in the ordering to the end of the list.
// - Sort the servers that aren't in the ordering by their ID (creation order). // - Sort the servers that aren't in the ordering by their ID (creation order).
((RevoltAPI.serverCache.values.filter { (
SyncedSettings.ordering.servers.contains( (
it.id RevoltAPI.serverCache.values.filter {
SyncedSettings.ordering.servers.contains(
it.id
)
}
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }
) + (
RevoltAPI.serverCache.values.filter {
!SyncedSettings.ordering.servers.contains(
it.id
)
}.sortedBy { it.id }
)
) )
}
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }) + (RevoltAPI.serverCache.values.filter {
!SyncedSettings.ordering.servers.contains(
it.id
)
}.sortedBy { it.id }
))
.forEach { server -> .forEach { server ->
if (server.id == null || server.name == null) return@forEach if (server.id == null || server.name == null) return@forEach
@ -854,7 +875,7 @@ fun Sidebar(
/*serverContextSheetTarget = server.id /*serverContextSheetTarget = server.id
showServerContextSheet = true*/ showServerContextSheet = true*/
onShowServerContextSheet(server.id) onShowServerContextSheet(server.id)
}, }
) { ) {
viewModel.navigateToServer( viewModel.navigateToServer(
server.id, server.id,
@ -864,7 +885,7 @@ fun Sidebar(
} }
DrawerServerlikeIcon( DrawerServerlikeIcon(
onClick = onShowAddServerSheet, onClick = onShowAddServerSheet
) { ) {
Icon( Icon(
Icons.Default.Add, Icons.Default.Add,
@ -892,7 +913,7 @@ fun Sidebar(
}, },
onServerSheetOpenFor = { target -> onServerSheetOpenFor = { target ->
onShowServerContextSheet(target) onShowServerContextSheet(target)
}, }
) )
} }
} }
@ -906,7 +927,7 @@ fun ChannelNavigator(
useDrawer: Boolean, useDrawer: Boolean,
drawerBackHandler: () -> Unit, drawerBackHandler: () -> Unit,
drawerState: DrawerState? = null, drawerState: DrawerState? = null,
onShowUserContextSheet: (String, String?) -> Unit, onShowUserContextSheet: (String, String?) -> Unit
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -931,8 +952,11 @@ fun ChannelNavigator(
channelId = channelId, channelId = channelId,
onToggleDrawer = { onToggleDrawer = {
scope.launch { scope.launch {
if (drawerState?.isOpen == true) drawerState.close() if (drawerState?.isOpen == true) {
else drawerState?.open() drawerState.close()
} else {
drawerState?.open()
}
} }
}, },
onUserSheetOpenFor = { target, server -> onUserSheetOpenFor = { target, server ->

View File

@ -168,7 +168,7 @@ fun FeedbackDialog(navController: NavController) {
onValueChange = {}, onValueChange = {},
label = { label = {
Text( Text(
text = stringResource(id = R.string.settings_feedback_category), text = stringResource(id = R.string.settings_feedback_category)
) )
}, },
readOnly = true, readOnly = true,
@ -181,7 +181,9 @@ fun FeedbackDialog(navController: NavController) {
) { ) {
Icon( Icon(
imageVector = Icons.Default.ArrowDropDown, imageVector = Icons.Default.ArrowDropDown,
contentDescription = stringResource(id = R.string.settings_feedback_category) contentDescription = stringResource(
id = R.string.settings_feedback_category
)
) )
} }
}, },
@ -192,7 +194,7 @@ fun FeedbackDialog(navController: NavController) {
expanded = categoryDropdownExpanded.value, expanded = categoryDropdownExpanded.value,
onDismissRequest = { onDismissRequest = {
categoryDropdownExpanded.value = false categoryDropdownExpanded.value = false
}, }
) { ) {
category.forEach { (key, value) -> category.forEach { (key, value) ->
DropdownMenuItem( DropdownMenuItem(
@ -218,7 +220,7 @@ fun FeedbackDialog(navController: NavController) {
}, },
supportingText = { supportingText = {
Text( Text(
text = "${message.value.length}/1250", text = "${message.value.length}/1250"
) )
}, },
enabled = !sending.value, enabled = !sending.value,
@ -233,7 +235,7 @@ fun FeedbackDialog(navController: NavController) {
navController.popBackStack() navController.popBackStack()
}, },
modifier = Modifier.testTag("feedback_cancel"), modifier = Modifier.testTag("feedback_cancel"),
enabled = !sending.value, enabled = !sending.value
) { ) {
Text(text = stringResource(id = R.string.cancel)) Text(text = stringResource(id = R.string.cancel))
} }

View File

@ -58,10 +58,7 @@ enum class ReportFlowState {
} }
@Composable @Composable
fun ReportMessageDialog( fun ReportMessageDialog(navController: NavController, messageId: String) {
navController: NavController,
messageId: String,
) {
val message = RevoltAPI.messageCache[messageId] val message = RevoltAPI.messageCache[messageId]
if (message == null) { if (message == null) {
navController.popBackStack() navController.popBackStack()
@ -84,7 +81,7 @@ fun ReportMessageDialog(
"SpamAbuse" to stringResource(id = R.string.report_reason_content_spam_abuse), "SpamAbuse" to stringResource(id = R.string.report_reason_content_spam_abuse),
"Malware" to stringResource(id = R.string.report_reason_content_malware), "Malware" to stringResource(id = R.string.report_reason_content_malware),
"Harassment" to stringResource(id = R.string.report_reason_content_harassment), "Harassment" to stringResource(id = R.string.report_reason_content_harassment),
"Other" to stringResource(id = R.string.report_reason_content_other), "Other" to stringResource(id = R.string.report_reason_content_other)
) )
val reasonDropdownExpanded = remember { mutableStateOf(false) } val reasonDropdownExpanded = remember { mutableStateOf(false) }
@ -146,7 +143,7 @@ fun ReportMessageDialog(
}, },
label = { label = {
Text( Text(
text = stringResource(id = R.string.report_reason), text = stringResource(id = R.string.report_reason)
) )
}, },
readOnly = true, readOnly = true,
@ -159,7 +156,9 @@ fun ReportMessageDialog(
) { ) {
Icon( Icon(
imageVector = Icons.Default.ArrowDropDown, imageVector = Icons.Default.ArrowDropDown,
contentDescription = stringResource(id = R.string.report_reason) contentDescription = stringResource(
id = R.string.report_reason
)
) )
} }
}, },
@ -170,7 +169,7 @@ fun ReportMessageDialog(
expanded = reasonDropdownExpanded.value, expanded = reasonDropdownExpanded.value,
onDismissRequest = { onDismissRequest = {
reasonDropdownExpanded.value = false reasonDropdownExpanded.value = false
}, }
) { ) {
reasons.forEach { (key, value) -> reasons.forEach { (key, value) ->
DropdownMenuItem( DropdownMenuItem(
@ -196,7 +195,9 @@ fun ReportMessageDialog(
}, },
supportingText = { supportingText = {
Text( Text(
text = stringResource(id = R.string.report_reason_additional_hint) text = stringResource(
id = R.string.report_reason_additional_hint
)
) )
} }
) )
@ -282,11 +283,11 @@ fun ReportMessageDialog(
Text( Text(
text = stringResource(id = R.string.report_submit_success), text = stringResource(id = R.string.report_submit_success),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
) )
}, },
text = { text = {
Column() { Column {
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
@ -349,11 +350,11 @@ fun ReportMessageDialog(
Text( Text(
text = stringResource(id = R.string.report_submit_error_header), text = stringResource(id = R.string.report_submit_error_header),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
) )
}, },
text = { text = {
Column() { Column {
Text( Text(
text = stringResource(id = R.string.report_submit_error), text = stringResource(id = R.string.report_submit_error),
textAlign = TextAlign.Center textAlign = TextAlign.Center

View File

@ -55,12 +55,12 @@ fun HomeScreen(navController: NavController) {
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.fillMaxSize(), .fillMaxSize(),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = "🐈", text = "🐈",
fontSize = 100.sp, fontSize = 100.sp,
modifier = Modifier.rotate(catRotation), modifier = Modifier.rotate(catRotation)
) )
} }
@ -74,7 +74,11 @@ fun HomeScreen(navController: NavController) {
) )
}, },
heading = { Text(text = stringResource(id = R.string.home_join_jenvolt)) }, heading = { Text(text = stringResource(id = R.string.home_join_jenvolt)) },
description = { Text(text = stringResource(id = R.string.home_join_jenvolt_description)) }, description = {
Text(
text = stringResource(id = R.string.home_join_jenvolt_description)
)
}
) { ) {
context.startActivity( context.startActivity(
Intent( Intent(

View File

@ -22,7 +22,7 @@ fun NoCurrentChannelScreen() {
.fillMaxSize() .fillMaxSize()
.padding(64.dp), .padding(64.dp),
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text( Text(
text = stringResource(R.string.no_active_channel), text = stringResource(R.string.no_active_channel),
@ -34,7 +34,7 @@ fun NoCurrentChannelScreen() {
Text( Text(
text = stringResource(R.string.no_active_channel_body), text = stringResource(R.string.no_active_channel_body),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center, textAlign = TextAlign.Center
) )
} }
} }

View File

@ -91,10 +91,10 @@ import chat.revolt.sheets.ChannelInfoSheet
import chat.revolt.sheets.MessageContextSheet import chat.revolt.sheets.MessageContextSheet
import com.discord.simpleast.core.simple.SimpleMarkdownRules import com.discord.simpleast.core.simple.SimpleMarkdownRules
import com.discord.simpleast.core.simple.SimpleRenderer import com.discord.simpleast.core.simple.SimpleRenderer
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -207,10 +207,10 @@ fun ChannelScreen(
sheetState = channelInfoSheetState, sheetState = channelInfoSheetState,
onDismissRequest = { onDismissRequest = {
channelInfoSheetShown = false channelInfoSheetShown = false
}, }
) { ) {
ChannelInfoSheet( ChannelInfoSheet(
channelId = channelId, channelId = channelId
) )
} }
} }
@ -222,7 +222,7 @@ fun ChannelScreen(
sheetState = messageContextSheetState, sheetState = messageContextSheetState,
onDismissRequest = { onDismissRequest = {
messageContextSheetShown = false messageContextSheetShown = false
}, }
) { ) {
MessageContextSheet( MessageContextSheet(
messageId = messageContextSheetTarget, messageId = messageContextSheetTarget,
@ -232,7 +232,7 @@ fun ChannelScreen(
}, },
onReportMessage = { onReportMessage = {
navController.navigate("report/message/$messageContextSheetTarget") navController.navigate("report/message/$messageContextSheetTarget")
}, }
) )
} }
} }
@ -252,7 +252,7 @@ fun ChannelScreen(
channelInfoSheetShown = true channelInfoSheetShown = true
}, },
onToggleDrawer = onToggleDrawer, onToggleDrawer = onToggleDrawer,
useDrawer = useDrawer, useDrawer = useDrawer
) )
val isScrolledToBottom = remember(lazyListState) { val isScrolledToBottom = remember(lazyListState) {
@ -310,12 +310,12 @@ fun ChannelScreen(
parse = { parse = {
val parser = MarkdownParser() val parser = MarkdownParser()
.addRules( .addRules(
SimpleMarkdownRules.createEscapeRule(), SimpleMarkdownRules.createEscapeRule()
) )
.addRevoltRules(context) .addRevoltRules(context)
.addRules( .addRules(
createCodeRule(context, codeBlockColor.toArgb()), createCodeRule(context, codeBlockColor.toArgb()),
createInlineCodeRule(context, codeBlockColor.toArgb()), createInlineCodeRule(context, codeBlockColor.toArgb())
) )
.addRules( .addRules(
SimpleMarkdownRules.createSimpleMarkdownRules( SimpleMarkdownRules.createSimpleMarkdownRules(
@ -366,7 +366,7 @@ fun ChannelScreen(
return@Message return@Message
} }
viewModel.replyToMessage(message) viewModel.replyToMessage(message)
}, }
) )
} }
} }
@ -399,11 +399,11 @@ fun ChannelScreen(
!isScrolledToBottom.value, !isScrolledToBottom.value,
enter = slideInHorizontally( enter = slideInHorizontally(
animationSpec = RevoltTweenInt, animationSpec = RevoltTweenInt,
initialOffsetX = { it }, initialOffsetX = { it }
) + fadeIn(animationSpec = RevoltTweenFloat), ) + fadeIn(animationSpec = RevoltTweenFloat),
exit = slideOutHorizontally( exit = slideOutHorizontally(
animationSpec = RevoltTweenInt, animationSpec = RevoltTweenInt,
targetOffsetX = { it }, targetOffsetX = { it }
) + fadeOut(animationSpec = RevoltTweenFloat), ) + fadeOut(animationSpec = RevoltTweenFloat),
modifier = Modifier modifier = Modifier
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
@ -460,7 +460,7 @@ fun ChannelScreen(
attachments = viewModel.pendingAttachments, attachments = viewModel.pendingAttachments,
uploading = viewModel.isSendingMessage, uploading = viewModel.isSendingMessage,
uploadProgress = viewModel.pendingUploadProgress, uploadProgress = viewModel.pendingUploadProgress,
onRemove = { viewModel.pendingAttachments.remove(it) }, onRemove = { viewModel.pendingAttachments.remove(it) }
) )
} }
@ -525,12 +525,14 @@ fun ChannelScreen(
if (nowFocused && viewModel.currentBottomPane != BottomPane.None) { if (nowFocused && viewModel.currentBottomPane != BottomPane.None) {
viewModel.currentBottomPane = BottomPane.None viewModel.currentBottomPane = BottomPane.None
} }
}, }
) )
} }
} }
AnimatedVisibility(visible = viewModel.currentBottomPane == BottomPane.InbuiltMediaPicker) { AnimatedVisibility(
visible = viewModel.currentBottomPane == BottomPane.InbuiltMediaPicker
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
InbuiltMediaPicker( InbuiltMediaPicker(
onOpenDocumentsUi = { onOpenDocumentsUi = {
@ -573,7 +575,9 @@ fun ChannelScreen(
if (e is FileNotFoundException) { if (e is FileNotFoundException) {
Toast.makeText( Toast.makeText(
context, context,
context.getString(R.string.file_picker_cannot_attach_file_invalid), context.getString(
R.string.file_picker_cannot_attach_file_invalid
),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }
@ -582,7 +586,7 @@ fun ChannelScreen(
pendingMedia = viewModel.pendingAttachments pendingMedia = viewModel.pendingAttachments
.filterNot { it.pickerIdentifier == null } .filterNot { it.pickerIdentifier == null }
.map { it.pickerIdentifier!! }, .map { it.pickerIdentifier!! },
disabled = viewModel.isSendingMessage, disabled = viewModel.isSendingMessage
) )
} }
} }

View File

@ -315,16 +315,19 @@ class ChannelScreenViewModel : ViewModel() {
currentMsg.id == it.id currentMsg.id == it.id
} ?: return@onEach // Message not found, ignore. } ?: return@onEach // Message not found, ignore.
if (messageFrame.author != null) if (messageFrame.author != null) {
addUserIfUnknown(messageFrame.author) addUserIfUnknown(messageFrame.author)
}
regroupMessages(renderableMessages.map { currentMsg -> regroupMessages(
if (currentMsg.id == it.id) { renderableMessages.map { currentMsg ->
currentMsg.mergeWithPartial(messageFrame) if (currentMsg.id == it.id) {
} else { currentMsg.mergeWithPartial(messageFrame)
currentMsg } else {
currentMsg
}
} }
}) )
} }
is MessageAppendFrame -> { is MessageAppendFrame -> {
@ -336,13 +339,15 @@ class ChannelScreenViewModel : ViewModel() {
if (!hasMessage) return@onEach if (!hasMessage) return@onEach
regroupMessages(renderableMessages.map { currentMsg -> regroupMessages(
if (currentMsg.id == it.id) { renderableMessages.map { currentMsg ->
RevoltAPI.messageCache[it.id] ?: currentMsg if (currentMsg.id == it.id) {
} else { RevoltAPI.messageCache[it.id] ?: currentMsg
currentMsg } else {
currentMsg
}
} }
}) )
} }
is MessageDeleteFrame -> { is MessageDeleteFrame -> {
@ -458,8 +463,8 @@ class ChannelScreenViewModel : ViewModel() {
} }
val newContent = currentContent.substring(0, currentSelection.start) + val newContent = currentContent.substring(0, currentSelection.start) +
content + content +
currentContent.substring(currentSelection.end) currentContent.substring(currentSelection.end)
pendingMessageContent = newContent pendingMessageContent = newContent
textSelection = TextRange(currentSelection.start + content.length) textSelection = TextRange(currentSelection.start + content.length)
@ -469,9 +474,12 @@ class ChannelScreenViewModel : ViewModel() {
if (activeChannel == null) return if (activeChannel == null) return
val selfUser = RevoltAPI.userCache[RevoltAPI.selfId] ?: return val selfUser = RevoltAPI.userCache[RevoltAPI.selfId] ?: return
val selfMember = if (activeChannel!!.server == null) null else val selfMember = if (activeChannel!!.server == null) {
null
} else {
activeChannel?.server?.let { RevoltAPI.members.getMember(it, selfUser.id!!) } activeChannel?.server?.let { RevoltAPI.members.getMember(it, selfUser.id!!) }
?: fetchMember(activeChannel!!.server!!, selfUser.id!!) ?: fetchMember(activeChannel!!.server!!, selfUser.id!!)
}
val hasPermission = val hasPermission =
Roles.permissionFor(activeChannel!!, selfUser, selfMember) Roles.permissionFor(activeChannel!!, selfUser, selfMember)

View File

@ -94,7 +94,7 @@ fun LoginGreetingScreen(navController: NavController) {
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp, vertical = 10.dp) .padding(horizontal = 20.dp, vertical = 10.dp)
.fillMaxWidth(), .fillMaxWidth()
) )
Text( Text(
@ -105,7 +105,7 @@ fun LoginGreetingScreen(navController: NavController) {
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
fontSize = 16.sp, fontSize = 16.sp,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp, vertical = 10.dp) .padding(horizontal = 20.dp, vertical = 10.dp)
@ -157,7 +157,6 @@ fun LoginGreetingScreen(navController: NavController) {
url = "$REVOLT_MARKETING/aup" url = "$REVOLT_MARKETING/aup"
) )
} }
} }
} }
} }

View File

@ -49,12 +49,12 @@ import chat.revolt.components.generic.FormTextField
import chat.revolt.components.generic.Weblink import chat.revolt.components.generic.Weblink
import chat.revolt.persistence.KVStorage import chat.revolt.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.launch
@HiltViewModel @HiltViewModel
class LoginViewModel @Inject constructor( class LoginViewModel @Inject constructor(
private val kvStorage: KVStorage, private val kvStorage: KVStorage
) : ViewModel() { ) : ViewModel() {
private var _email by mutableStateOf("") private var _email by mutableStateOf("")
val email: String val email: String
@ -137,10 +137,7 @@ class LoginViewModel @Inject constructor(
} }
@Composable @Composable
fun LoginScreen( fun LoginScreen(navController: NavController, viewModel: LoginViewModel = hiltViewModel()) {
navController: NavController,
viewModel: LoginViewModel = hiltViewModel()
) {
val context = LocalContext.current val context = LocalContext.current
LaunchedEffect(viewModel.navigateTo) { LaunchedEffect(viewModel.navigateTo) {
@ -196,10 +193,9 @@ fun LoginScreen(
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp, vertical = 10.dp) .padding(horizontal = 20.dp, vertical = 10.dp)
.fillMaxWidth(), .fillMaxWidth()
) )
Column( Column(
modifier = Modifier modifier = Modifier
.width(270.dp), .width(270.dp),
@ -216,7 +212,7 @@ fun LoginScreen(
value = viewModel.password, value = viewModel.password,
label = stringResource(R.string.password), label = stringResource(R.string.password),
type = KeyboardType.Password, type = KeyboardType.Password,
onChange = viewModel::setPassword, onChange = viewModel::setPassword
) )
AnyLink( AnyLink(

View File

@ -48,12 +48,12 @@ import chat.revolt.components.generic.CollapsibleCard
import chat.revolt.components.generic.FormTextField import chat.revolt.components.generic.FormTextField
import chat.revolt.persistence.KVStorage import chat.revolt.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.launch
@HiltViewModel @HiltViewModel
class MfaScreenViewModel @Inject constructor( class MfaScreenViewModel @Inject constructor(
private val kvStorage: KVStorage, private val kvStorage: KVStorage
) : ViewModel() { ) : ViewModel() {
private var _totpCode by mutableStateOf("") private var _totpCode by mutableStateOf("")
val totpCode: String val totpCode: String
@ -183,7 +183,7 @@ fun MfaScreen(
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp) .padding(horizontal = 20.dp)
.fillMaxWidth(), .fillMaxWidth()
) )
Text( Text(
@ -193,7 +193,7 @@ fun MfaScreen(
), ),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp, vertical = 10.dp) .padding(horizontal = 20.dp, vertical = 10.dp)
@ -215,7 +215,6 @@ fun MfaScreen(
) )
} }
// Collapsible cards for each auth type // Collapsible cards for each auth type
allowedAuthTypes.forEach { authType -> allowedAuthTypes.forEach { authType ->
when (authType) { when (authType) {
@ -245,7 +244,7 @@ fun MfaScreen(
), ),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp, vertical = 10.dp) .padding(horizontal = 20.dp, vertical = 10.dp)
@ -256,7 +255,7 @@ fun MfaScreen(
label = stringResource(R.string.mfa_totp_code), label = stringResource(R.string.mfa_totp_code),
onChange = viewModel::setTotpCode, onChange = viewModel::setTotpCode,
type = KeyboardType.Number, type = KeyboardType.Number,
value = viewModel.totpCode, value = viewModel.totpCode
) )
Button( Button(
@ -267,7 +266,7 @@ fun MfaScreen(
.testTag("do_totp_button") .testTag("do_totp_button")
) { ) {
Text( Text(
text = stringResource(R.string.next), text = stringResource(R.string.next)
) )
} }
} }
@ -300,7 +299,7 @@ fun MfaScreen(
), ),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp, vertical = 10.dp) .padding(horizontal = 20.dp, vertical = 10.dp)
@ -310,7 +309,7 @@ fun MfaScreen(
FormTextField( FormTextField(
label = stringResource(R.string.mfa_recovery_code), label = stringResource(R.string.mfa_recovery_code),
onChange = viewModel::setRecoveryCode, onChange = viewModel::setRecoveryCode,
value = viewModel.recoveryCode, value = viewModel.recoveryCode
) )
Button( Button(
@ -321,7 +320,7 @@ fun MfaScreen(
.testTag("do_mfa_recovery_button") .testTag("do_mfa_recovery_button")
) { ) {
Text( Text(
text = stringResource(R.string.next), text = stringResource(R.string.next)
) )
} }
} }

View File

@ -83,7 +83,7 @@ fun OnboardingScreen(navController: NavController) {
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 10.dp) .padding(horizontal = 10.dp)
.fillMaxWidth(), .fillMaxWidth()
) )
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
@ -95,7 +95,7 @@ fun OnboardingScreen(navController: NavController) {
), ),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 10.dp) .padding(horizontal = 10.dp)
@ -111,7 +111,7 @@ fun OnboardingScreen(navController: NavController) {
), ),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 10.dp) .padding(horizontal = 10.dp)
@ -127,7 +127,7 @@ fun OnboardingScreen(navController: NavController) {
), ),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 10.dp) .padding(horizontal = 10.dp)
@ -145,7 +145,7 @@ fun OnboardingScreen(navController: NavController) {
FormTextField( FormTextField(
value = username.value, value = username.value,
onChange = { username.value = it }, onChange = { username.value = it },
label = stringResource(R.string.onboarding_username), label = stringResource(R.string.onboarding_username)
) )
if (error.value.isNotBlank()) { if (error.value.isNotBlank()) {

View File

@ -92,7 +92,7 @@ class RegisterDetailsScreenViewModel : ViewModel() {
val result = register(body) val result = register(body)
if (result.ok) { if (result.ok) {
navController.navigate("register/verify/${email}") navController.navigate("register/verify/$email")
} else { } else {
error = result.unwrapError().type error = result.unwrapError().type
} }
@ -131,7 +131,7 @@ fun RegisterDetailsScreen(
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 10.dp) .padding(horizontal = 10.dp)
.fillMaxWidth(), .fillMaxWidth()
) )
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
@ -143,7 +143,7 @@ fun RegisterDetailsScreen(
), ),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 10.dp) .padding(horizontal = 10.dp)
@ -161,7 +161,7 @@ fun RegisterDetailsScreen(
FormTextField( FormTextField(
value = viewModel.email, value = viewModel.email,
onChange = { viewModel.email = it }, onChange = { viewModel.email = it },
label = stringResource(R.string.register_email), label = stringResource(R.string.register_email)
) )
Text( Text(
text = stringResource(R.string.register_email_verification_hint), text = stringResource(R.string.register_email_verification_hint),

View File

@ -53,7 +53,7 @@ fun RegisterGreetingScreen(navController: NavController) {
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp, vertical = 10.dp) .padding(horizontal = 20.dp, vertical = 10.dp)
.fillMaxWidth(), .fillMaxWidth()
) )
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
@ -74,7 +74,7 @@ fun RegisterGreetingScreen(navController: NavController) {
), ),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp, vertical = 10.dp) .padding(horizontal = 20.dp, vertical = 10.dp)
@ -99,6 +99,3 @@ fun RegisterGreetingScreen(navController: NavController) {
} }
} }
} }

View File

@ -28,7 +28,9 @@ import chat.revolt.R
@Composable @Composable
fun RegisterVerifyScreen(navController: NavController, email: String) { fun RegisterVerifyScreen(navController: NavController, email: String) {
val intentLauncher = val intentLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {} rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {}
Column( Column(
modifier = Modifier modifier = Modifier
@ -53,7 +55,7 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp) .padding(horizontal = 20.dp)
.fillMaxWidth(), .fillMaxWidth()
) )
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
@ -65,7 +67,7 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
), ),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp) .padding(horizontal = 20.dp)
@ -81,7 +83,7 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
), ),
style = MaterialTheme.typography.titleMedium.copy( style = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal
), ),
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp) .padding(horizontal = 20.dp)
@ -100,6 +102,3 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
} }
} }
} }

View File

@ -64,7 +64,8 @@ fun AppearanceSettingsScreen(
showBackButton = true, showBackButton = true,
onBackButtonClicked = { onBackButtonClicked = {
navController.popBackStack() navController.popBackStack()
}) }
)
Column( Column(
modifier = Modifier modifier = Modifier
@ -80,13 +81,13 @@ fun AppearanceSettingsScreen(
FlowRow( FlowRow(
mainAxisSpacing = 10.dp, mainAxisSpacing = 10.dp,
crossAxisSpacing = 10.dp, crossAxisSpacing = 10.dp
) { ) {
ThemeChip( ThemeChip(
color = Color(0xff1c243c), color = Color(0xff1c243c),
text = stringResource(id = R.string.settings_appearance_theme_revolt), text = stringResource(id = R.string.settings_appearance_theme_revolt),
selected = GlobalState.theme == Theme.Revolt, selected = GlobalState.theme == Theme.Revolt,
modifier = Modifier.weight(1f).testTag("set_theme_revolt"), modifier = Modifier.weight(1f).testTag("set_theme_revolt")
) { ) {
setNewTheme(Theme.Revolt) setNewTheme(Theme.Revolt)
} }
@ -95,7 +96,7 @@ fun AppearanceSettingsScreen(
color = Color(0xfff7f7f7), color = Color(0xfff7f7f7),
text = stringResource(id = R.string.settings_appearance_theme_light), text = stringResource(id = R.string.settings_appearance_theme_light),
selected = GlobalState.theme == Theme.Light, selected = GlobalState.theme == Theme.Light,
modifier = Modifier.weight(1f).testTag("set_theme_light"), modifier = Modifier.weight(1f).testTag("set_theme_light")
) { ) {
setNewTheme(Theme.Light) setNewTheme(Theme.Light)
} }
@ -104,7 +105,7 @@ fun AppearanceSettingsScreen(
color = Color(0xff000000), color = Color(0xff000000),
text = stringResource(id = R.string.settings_appearance_theme_amoled), text = stringResource(id = R.string.settings_appearance_theme_amoled),
selected = GlobalState.theme == Theme.Amoled, selected = GlobalState.theme == Theme.Amoled,
modifier = Modifier.weight(1f).testTag("set_theme_amoled"), modifier = Modifier.weight(1f).testTag("set_theme_amoled")
) { ) {
setNewTheme(Theme.Amoled) setNewTheme(Theme.Amoled)
} }
@ -113,7 +114,7 @@ fun AppearanceSettingsScreen(
color = if (isSystemInDarkTheme()) Color(0xff1c243c) else Color(0xfff7f7f7), color = if (isSystemInDarkTheme()) Color(0xff1c243c) else Color(0xfff7f7f7),
text = stringResource(id = R.string.settings_appearance_theme_none), text = stringResource(id = R.string.settings_appearance_theme_none),
selected = GlobalState.theme == Theme.None, selected = GlobalState.theme == Theme.None,
modifier = Modifier.weight(1f).testTag("set_theme_none"), modifier = Modifier.weight(1f).testTag("set_theme_none")
) { ) {
setNewTheme(Theme.None) setNewTheme(Theme.None)
} }
@ -123,20 +124,24 @@ fun AppearanceSettingsScreen(
color = dynamicDarkColorScheme(LocalContext.current).primary, color = dynamicDarkColorScheme(LocalContext.current).primary,
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic), text = stringResource(id = R.string.settings_appearance_theme_m3dynamic),
selected = GlobalState.theme == Theme.M3Dynamic, selected = GlobalState.theme == Theme.M3Dynamic,
modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic"), modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic")
) { ) {
setNewTheme(Theme.M3Dynamic) setNewTheme(Theme.M3Dynamic)
} }
} else { } else {
ThemeChip( ThemeChip(
color = Color(0xffa0a0a0), color = Color(0xffa0a0a0),
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic_unsupported), text = stringResource(
id = R.string.settings_appearance_theme_m3dynamic_unsupported
),
selected = false, selected = false,
modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic_unsupported"), modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic_unsupported")
) { ) {
Toast.makeText( Toast.makeText(
context, context,
context.getString(R.string.settings_appearance_theme_m3dynamic_unsupported_toast), context.getString(
R.string.settings_appearance_theme_m3dynamic_unsupported_toast
),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }

View File

@ -75,7 +75,8 @@ fun ChangelogsSettingsScreen(
showBackButton = true, showBackButton = true,
onBackButtonClicked = { onBackButtonClicked = {
navController.popBackStack() navController.popBackStack()
}) }
)
LazyColumn { LazyColumn {
items( items(

Some files were not shown because too many files have changed in this diff Show More