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

@ -14,4 +14,4 @@ class RevoltApplication : Application() {
instance = this
DynamicColors.applyToActivitiesIfAvailable(this)
}
}
}

View File

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

View File

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

View File

@ -49,4 +49,4 @@ class WebChallengeActivity : AppCompatActivity() {
binding.webView.loadUrl(REVOLT_BASE)
}
}
}

View File

@ -79,10 +79,7 @@ class ImageViewActivity : ComponentActivity() {
}
@Composable
fun ImageViewScreen(
resource: AutumnResource,
onClose: () -> Unit = {}
) {
fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
val context = LocalContext.current
@ -182,7 +179,7 @@ fun ImageViewScreen(
RevoltTheme(requestedTheme = GlobalState.theme) {
Scaffold(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) { pv ->
Surface(
modifier = Modifier
@ -191,10 +188,11 @@ fun ImageViewScreen(
.fillMaxSize()
) {
Column {
PageHeader(text = stringResource(
id = R.string.media_viewer_title_image,
resource.filename ?: resource.id!!
),
PageHeader(
text = stringResource(
id = R.string.media_viewer_title_image,
resource.filename ?: resource.id!!
),
showBackButton = true,
onBackButtonClicked = onClose,
maxLines = 1,
@ -213,13 +211,16 @@ fun ImageViewScreen(
expanded = shareSubmenuIsOpen.value,
onDismissRequest = {
shareSubmenuIsOpen.value = false
}) {
}
) {
DropdownMenuItem(
onClick = {
shareUrl()
},
text = {
Text(stringResource(id = R.string.media_viewer_share_url))
Text(
stringResource(id = R.string.media_viewer_share_url)
)
}
)
DropdownMenuItem(
@ -227,7 +228,11 @@ fun ImageViewScreen(
shareImage()
},
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(
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(
modifier = Modifier
@ -260,11 +268,11 @@ fun ImageViewScreen(
},
modifier = Modifier
.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)
@Composable
fun VideoViewScreen(
resource: AutumnResource,
onClose: () -> Unit = {}
) {
fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
val context = LocalContext.current
@ -198,7 +195,7 @@ fun VideoViewScreen(
RevoltTheme(requestedTheme = GlobalState.theme) {
Scaffold(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) { pv ->
Surface(
modifier = Modifier
@ -207,10 +204,11 @@ fun VideoViewScreen(
.fillMaxSize()
) {
Column {
PageHeader(text = stringResource(
id = R.string.media_viewer_title_video,
resource.filename ?: resource.id!!
),
PageHeader(
text = stringResource(
id = R.string.media_viewer_title_video,
resource.filename ?: resource.id!!
),
showBackButton = true,
onBackButtonClicked = onClose,
maxLines = 1,
@ -229,13 +227,16 @@ fun VideoViewScreen(
expanded = shareSubmenuIsOpen.value,
onDismissRequest = {
shareSubmenuIsOpen.value = false
}) {
}
) {
DropdownMenuItem(
onClick = {
shareUrl()
},
text = {
Text(stringResource(id = R.string.media_viewer_share_url))
Text(
stringResource(id = R.string.media_viewer_share_url)
)
}
)
DropdownMenuItem(
@ -243,7 +244,11 @@ fun VideoViewScreen(
shareVideo()
},
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(
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(
modifier = Modifier
@ -275,11 +283,11 @@ fun VideoViewScreen(
},
modifier = Modifier
.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.RealtimeSocket
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.Message
import chat.revolt.api.schemas.Server
@ -38,7 +39,6 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import chat.revolt.api.schemas.Channel as ChannelSchema
const val REVOLT_BASE = "https://api.revolt.chat"
const val REVOLT_SUPPORT = "https://support.revolt.chat"
@ -218,4 +218,4 @@ object RevoltAPI {
}
@Serializable
data class RevoltError(val type: String)
data class RevoltError(val type: String)

View File

@ -50,4 +50,4 @@ object ChannelUtils {
return output
}
}
}

View File

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

View File

@ -38,4 +38,4 @@ class Members {
member.nickname?.let { userId to member.nickname }
}?.toMap() ?: emptyMap()
}
}
}

View File

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

View File

@ -80,7 +80,7 @@ object Roles {
ChannelType.TextChannel, ChannelType.VoiceChannel -> {
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
if (server.owner == user?.id) return PermissionBit.GrantAllSafe.value
@ -114,4 +114,4 @@ object Roles {
null -> 0L
}
}
}
}

View File

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

View File

@ -45,35 +45,67 @@ object ULID {
// Entropy part (16 chars)
chars[10] = b32chars[(entropy[0].toShort() and 0xff).toInt().ushr(3)]
chars[11] =
b32chars[(entropy[0].toInt() shl 2 or (entropy[1].toShort() and 0xff).toInt()
.ushr(6) and 0x1f)]
b32chars[
(
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[13] =
b32chars[(entropy[1].toInt() shl 4 or (entropy[2].toShort() and 0xff).toInt()
.ushr(4) and 0x1f)]
b32chars[
(
entropy[1].toInt() shl 4 or (entropy[2].toShort() and 0xff).toInt()
.ushr(4) and 0x1f
)
]
chars[14] =
b32chars[(entropy[2].toInt() shl 5 or (entropy[3].toShort() and 0xff).toInt()
.ushr(7) and 0x1f)]
b32chars[
(
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[16] =
b32chars[(entropy[3].toInt() shl 3 or (entropy[4].toShort() and 0xff).toInt()
.ushr(5) and 0x1f)]
b32chars[
(
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[18] = b32chars[(entropy[5].toShort() and 0xff).toInt().ushr(3)]
chars[19] =
b32chars[(entropy[5].toInt() shl 2 or (entropy[6].toShort() and 0xff).toInt()
.ushr(6) and 0x1f)]
b32chars[
(
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[21] =
b32chars[(entropy[6].toInt() shl 4 or (entropy[7].toShort() and 0xff).toInt()
.ushr(4) and 0x1f)]
b32chars[
(
entropy[6].toInt() shl 4 or (entropy[7].toShort() and 0xff).toInt()
.ushr(4) and 0x1f
)
]
chars[22] =
b32chars[(entropy[7].toInt() shl 5 or (entropy[8].toShort() and 0xff).toInt()
.ushr(7) and 0x1f)]
b32chars[
(
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[24] =
b32chars[(entropy[8].toInt() shl 3 or (entropy[9].toShort() and 0xff).toInt()
.ushr(5) and 0x1f)]
b32chars[
(
entropy[8].toInt() shl 3 or (entropy[9].toShort() and 0xff).toInt()
.ushr(5) and 0x1f
)
]
chars[25] = b32chars[(entropy[9].toInt() and 0x1f)]
return String(chars)
@ -109,4 +141,4 @@ object ULID {
return timestamp
}
}
}

View File

@ -9,7 +9,9 @@ object WebChallenge {
suspend fun needsCloudflare(): Boolean {
RevoltHttp.get(REVOLT_BASE).let {
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,
"inherit" to Color.Unspecified,
"initial" to Color.Unspecified,
"unset" to Color.Unspecified,
"unset" to Color.Unspecified
)
object WebCompat {
@ -199,4 +199,4 @@ object WebCompat {
}
}
}
}
}

View File

@ -429,4 +429,4 @@ object RealtimeSocket {
private suspend fun pushReconnectEvent() {
RevoltAPI.wsFrameChannel.send(RealtimeSocketFrames.Reconnected())
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -12,4 +12,4 @@ suspend fun fetchEmoji(id: String): Emoji {
Emoji.serializer(),
response
)
}
}

View File

@ -39,4 +39,4 @@ suspend fun joinInviteByCode(code: String): RsResult<InviteJoined, RevoltError>
val invite = RevoltJson.decodeFromString(InviteJoined.serializer(), response)
return RsResult.ok(invite)
}
}

View File

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

View File

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

View File

@ -51,4 +51,4 @@ data class Voso(
suspend fun getRootRoute(): Root {
return RevoltHttp.get("/").body()
}
}

View File

@ -21,10 +21,7 @@ data class OnboardingResponse(
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") {
header(RevoltAPI.TOKEN_HEADER_NAME, sessionToken)
}
@ -36,12 +33,12 @@ suspend fun needsOnboarding(
@Serializable
data class OnboardingCompletionBody(
val username: String,
val username: String
)
suspend fun completeOnboarding(
body: OnboardingCompletionBody,
sessionToken: String = RevoltAPI.sessionToken,
sessionToken: String = RevoltAPI.sessionToken
): RsResult<Unit, RevoltError> {
val response = RevoltHttp.post("/onboard/complete") {
setBody(body)
@ -67,4 +64,4 @@ suspend fun completeOnboarding(
}
return RsResult.ok(Unit)
}
}

View File

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

View File

@ -87,4 +87,4 @@ suspend fun leaveOrDeleteServer(serverId: String, leaveSilently: Boolean = false
RevoltHttp.delete("/servers/$serverId") {
parameter("leave_silently", leaveSilently)
}
}
}

View File

@ -69,4 +69,4 @@ suspend fun setKey(key: String, value: String) {
)
)
}
}
}

View File

@ -15,4 +15,4 @@ suspend fun syncUnreads(): List<ChannelUnreadResponse> {
ListSerializer(ChannelUnreadResponse.serializer()),
response
)
}
}

View File

@ -7,8 +7,8 @@ import chat.revolt.api.RevoltJson
import io.ktor.client.request.delete
import io.ktor.client.request.put
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerializationException
import kotlin.collections.set
import kotlinx.serialization.SerializationException
suspend fun blockUser(userId: String) {
val response = RevoltHttp.put("/users/$userId/block")
@ -38,4 +38,4 @@ suspend fun unblockUser(userId: String) {
val user = RevoltAPI.userCache[userId] ?: return
RevoltAPI.userCache[userId] = user.copy(relationship = "None")
}
}

View File

@ -81,4 +81,4 @@ suspend fun fetchUserProfile(id: String): Profile {
}
return RevoltJson.decodeFromString(Profile.serializer(), response)
}
}

View File

@ -20,7 +20,7 @@ data class MessagesInChannel(
@Serializable
data class ServerUserChoice(
val server: String,
val user: String,
val user: String
)
@Serializable
@ -35,7 +35,7 @@ data class Member(
val roles: List<String>? = null,
val nickname: String? = null,
val timeout: String? = null,
val timeout: String? = null
) {
fun mergeWithPartial(other: Member): Member {
return Member(
@ -44,7 +44,7 @@ data class Member(
avatar = other.avatar ?: avatar,
roles = other.roles ?: roles,
nickname = other.nickname ?: nickname,
timeout = other.timeout ?: timeout,
timeout = other.timeout ?: timeout
)
}
@ -75,7 +75,7 @@ data class Channel(
@SerialName("default_permissions")
val defaultPermissions: PermissionDescription? = 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 {
return Channel(
@ -135,7 +135,7 @@ enum class ChannelType(val value: String) {
@Serializable
data class ChannelUserChoice(
val channel: String,
val user: String,
val user: String
)
@Serializable
@ -143,7 +143,7 @@ data class ChannelUnreadResponse(
@SerialName("_id")
val id: ChannelUserChoice,
val last_id: String? = null,
val mentions: List<String>? = null,
val mentions: List<String>? = null
)
@Serializable
@ -151,5 +151,5 @@ data class ChannelUnread(
@SerialName("_id")
val id: String,
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
data class AutumnError(
val type: String,
)
val type: String
)

View File

@ -43,5 +43,5 @@ data class Invite(
data class InviteJoined(
val type: String? = 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 system: SystemInfo? = null,
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? {
return author?.let { RevoltAPI.userCache[it] }
@ -42,7 +42,7 @@ data class Message(
mentions = partial.mentions ?: mentions,
masquerade = partial.masquerade ?: masquerade,
type = partial.type ?: type,
tail = partial.tail ?: tail,
tail = partial.tail ?: tail
)
}
}
@ -100,5 +100,5 @@ data class SystemInfo(
val by: String? = null,
val from: 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(
val type: String,
val id: String,
val report_reason: ContentReportReason,
val report_reason: ContentReportReason
)
@Serializable
data class FullMessageReport(
val content: MessageReport,
val additional_context: String? = null,
val additional_context: String? = null
)
@Serializable
data class ServerReport(
val type: String,
val id: String,
val report_reason: ContentReportReason,
val report_reason: ContentReportReason
)
@Serializable
data class FullServerReport(
val content: ServerReport,
val additional_context: String? = null,
val additional_context: String? = null
)
@Serializable
data class UserReport(
val type: String,
val id: String,
val report_reason: UserReportReason,
val report_reason: UserReportReason
)
@Serializable
data class FullUserReport(
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 flags: Long? = null,
val analytics: Boolean? = null,
val discoverable: Boolean? = null,
val discoverable: Boolean? = null
) {
fun mergeWithPartial(other: Server): Server {
return Server(
@ -36,14 +36,14 @@ data class Server(
banner = other.banner ?: banner,
flags = other.flags ?: flags,
analytics = other.analytics ?: analytics,
discoverable = other.discoverable ?: discoverable,
discoverable = other.discoverable ?: discoverable
)
}
}
enum class ServerFlags(val value: Long) {
Official(1L shl 0),
Verified(1L shl 1),
Verified(1L shl 1)
}
infix fun Long?.has(flag: ServerFlags): Boolean {
@ -90,11 +90,11 @@ data class Emoji(
val name: String? = null,
val animated: 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
data class EmojiParent(
val type: String? = null,
val id: String? = null
)
)

View File

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

View File

@ -49,4 +49,4 @@ data class RsResult<V, E>(val value: V?, val error: E?) {
return RsResult(null, error)
}
}
}
}

View File

@ -10,7 +10,9 @@ annotation class Treatment(val description: String)
@FeatureFlag("ClosedBetaAccessControl")
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()
@Treatment("Allow access to the app to all users")
@ -22,6 +24,6 @@ object FeatureFlags {
var closedBetaAccessControl by mutableStateOf<ClosedBetaAccessControlVariates>(
ClosedBetaAccessControlVariates.Restricted {
RevoltAPI.channelCache.containsKey("01H7X2KRB0CA4QDSMB4N7WGERF")
})
}
)
}

View File

@ -16,4 +16,4 @@ object GlobalState {
fun reset() {
theme = getDefaultTheme()
}
}
}

View File

@ -48,4 +48,4 @@ object SyncedSettings {
_android.value = value
setKey("android", RevoltJson.encodeToString(AndroidSpecificSettings.serializer(), value))
}
}
}

View File

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

View File

@ -11,4 +11,4 @@ sealed class Action {
val ActionChannel = Channel<Action>(
Channel.BUFFERED
)
)

View File

@ -17,4 +17,4 @@ object UiCallbacks {
suspend fun editMessage(messageId: String) {
uiCallbackFlow.emit(UiCallback.EditMessage(messageId))
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,13 +39,11 @@ enum class SystemMessageType(val type: String) {
USER_KICKED("user_kicked"),
USER_LEFT("user_left"),
USER_JOINED("user_joined"),
TEXT("text"),
TEXT("text")
}
@Composable
fun SystemMessage(
message: Message
) {
fun SystemMessage(message: Message) {
if (message.system == null) return
val systemMessageType =
@ -72,7 +70,9 @@ fun SystemMessage(
when (systemMessageType) {
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 -> {
@ -120,11 +120,7 @@ fun SystemMessage(
}
@Composable
fun SystemMessageIcon(
type: SystemMessageType,
modifier: Modifier = Modifier,
size: Dp = 24.dp
) {
fun SystemMessageIcon(type: SystemMessageType, modifier: Modifier = Modifier, size: Dp = 24.dp) {
when (type) {
SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> {
Icon(
@ -138,7 +134,9 @@ fun SystemMessageIcon(
SystemMessageType.CHANNEL_ICON_CHANGED -> {
Icon(
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,
modifier = modifier.size(size)
)
@ -147,7 +145,9 @@ fun SystemMessageIcon(
SystemMessageType.CHANNEL_DESCRIPTION_CHANGED -> {
Icon(
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,
modifier = modifier.size(size)
)
@ -242,4 +242,4 @@ fun SystemMessageIconWithBackground(
) {
SystemMessageIcon(type = type)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -46,7 +46,7 @@ fun UIMarkdown(
text: String,
modifier: Modifier = Modifier,
fontSize: TextUnit = LocalTextStyle.current.fontSize,
maxLines: Int = Int.MAX_VALUE,
maxLines: Int = Int.MAX_VALUE
) {
val context = LocalContext.current
val foregroundColor = LocalContentColor.current
@ -61,7 +61,7 @@ fun UIMarkdown(
.addRevoltRules(context)
.addRules(
createCodeRule(context, codeBlockColor.toArgb()),
createInlineCodeRule(context, codeBlockColor.toArgb()),
createInlineCodeRule(context, codeBlockColor.toArgb())
)
.addRules(
SimpleMarkdownRules.createSimpleMarkdownRules(
@ -109,7 +109,7 @@ fun UIMarkdown(
modifier = modifier,
update = {
it.text = spannableStringBuilder.value
},
}
)
}
@ -118,6 +118,6 @@ fun UIMarkdown(
fun UIMarkdownPreview() {
UIMarkdown(
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(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
) {
actions()
}
@ -113,7 +113,7 @@ fun NonIdealStatePreviewNoActions() {
)
},
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)
)
},
title = { Text("No channels") },
title = { Text("No channels") }
)
}
}

View File

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

View File

@ -65,4 +65,4 @@ fun PrimaryTabsPreview() {
currentIndex = 0,
onTabSelected = {}
)
}
}

View File

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

View File

@ -27,9 +27,11 @@ fun SheetClickable(
label: @Composable (TextStyle) -> Unit,
modifier: Modifier = Modifier,
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)) {
Row(
modifier = Modifier
@ -44,7 +46,7 @@ fun SheetClickable(
label(
MaterialTheme.typography.bodyMedium.copy(
color = LocalContentColor.current,
fontWeight = FontWeight.SemiBold,
fontWeight = FontWeight.SemiBold
)
)
}
@ -68,4 +70,4 @@ fun SettingsCategoryPreview() {
},
onClick = {}
)
}
}

View File

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

View File

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

View File

@ -66,4 +66,4 @@ fun AnyLinkPreview() {
AnyLink(text = "Click me! #${clicked.value}", action = {
clicked.value++
})
}
}

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ fun ChannelHeader(
channel: Channel,
onChannelClick: (String) -> Unit,
onToggleDrawer: () -> Unit,
useDrawer: Boolean,
useDrawer: Boolean
) {
Row(
modifier = Modifier
@ -41,7 +41,7 @@ fun ChannelHeader(
channel.id?.let { onChannelClick(it) }
}
.padding(vertical = 4.dp, horizontal = 4.dp),
verticalAlignment = Alignment.CenterVertically,
verticalAlignment = Alignment.CenterVertically
) {
if (useDrawer) {
IconButton(onClick = {
@ -49,7 +49,7 @@ fun ChannelHeader(
}) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = stringResource(R.string.menu),
contentDescription = stringResource(R.string.menu)
)
}
@ -74,7 +74,7 @@ fun ChannelHeader(
Row(
modifier = Modifier.weight(1f),
verticalAlignment = Alignment.CenterVertically,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = channel.name
@ -86,7 +86,7 @@ fun ChannelHeader(
},
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.width(4.dp))
@ -96,8 +96,8 @@ fun ChannelHeader(
contentDescription = stringResource(R.string.menu),
modifier = Modifier
.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
@Composable
fun ChannelIcon(
channelType: ChannelType,
modifier: Modifier = Modifier,
) {
fun ChannelIcon(channelType: ChannelType, modifier: Modifier = Modifier) {
when (channelType) {
ChannelType.TextChannel -> {
Icon(
painter = painterResource(R.drawable.ic_pound_24dp),
contentDescription = stringResource(R.string.channel_text),
modifier = modifier,
modifier = modifier
)
}
ChannelType.VoiceChannel -> {
Icon(
painter = painterResource(R.drawable.ic_volume_up_24dp),
contentDescription = stringResource(R.string.channel_voice),
modifier = modifier,
modifier = modifier
)
}
ChannelType.SavedMessages -> {
Icon(
painter = painterResource(R.drawable.ic_note_24dp),
contentDescription = stringResource(R.string.channel_notes),
modifier = modifier,
modifier = modifier
)
}
ChannelType.DirectMessage -> {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = stringResource(R.string.channel_dm),
modifier = modifier,
modifier = modifier
)
}
ChannelType.Group -> {
Icon(
imageVector = Icons.Default.AccountBox,
contentDescription = stringResource(R.string.channel_group),
modifier = modifier,
modifier = modifier
)
}
}
@ -65,7 +62,7 @@ class ChannelTypeProvider : PreviewParameterProvider<ChannelType> {
ChannelType.VoiceChannel,
ChannelType.SavedMessages,
ChannelType.DirectMessage,
ChannelType.Group,
ChannelType.Group
)
override val count: Int

View File

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

View File

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

View File

@ -30,10 +30,7 @@ import chat.revolt.api.schemas.User
import chat.revolt.components.generic.UserAvatar
@Composable
fun StackedUserAvatars(
users: List<String>,
amount: Int = 3,
) {
fun StackedUserAvatars(users: List<String>, amount: Int = 3) {
Box(
modifier = Modifier
.size(16.dp + (8.dp * minOf(users.size, amount)), 16.dp)
@ -43,21 +40,19 @@ fun StackedUserAvatars(
UserAvatar(
avatar = user?.avatar,
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,
modifier = Modifier
.offset(
x = (index * 8).dp,
),
x = (index * 8).dp
)
)
}
}
}
@Composable
fun TypingIndicator(
users: List<String>,
) {
fun TypingIndicator(users: List<String>) {
fun typingMessageResource(): Int {
return when (users.size) {
0 -> R.string.typing_blank
@ -84,11 +79,11 @@ fun TypingIndicator(
.clip(
RoundedCornerShape(
topStart = 16.dp,
topEnd = 16.dp,
topEnd = 16.dp
)
)
.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)
@ -103,8 +98,8 @@ fun TypingIndicator(
),
fontSize = 12.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
overflow = TextOverflow.Ellipsis
)
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -23,4 +23,4 @@ fun ServerDrawerSeparator() {
)
)
)
}
}

View File

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

View File

@ -17,10 +17,7 @@ import chat.revolt.R
import chat.revolt.screens.about.Library
@Composable
fun AttributionItem(
library: Library,
onClick: () -> Unit
) {
fun AttributionItem(library: Library, onClick: () -> Unit) {
SelectionContainer {
Column(
modifier = Modifier
@ -30,16 +27,15 @@ fun AttributionItem(
) {
Text(
text = library.name,
style = MaterialTheme.typography.bodyLarge,
style = MaterialTheme.typography.bodyLarge
)
Spacer(modifier = Modifier.height(8.dp))
Text(
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,
brush = teamMemberFlair
?: Brush.solidColor(Color.Transparent),
shape = MaterialTheme.shapes.large,
shape = MaterialTheme.shapes.large
)
} else Modifier
} else {
Modifier
}
)
) {
profile?.background?.let { background ->
@ -138,7 +140,7 @@ fun RawUserOverview(user: User, profile: Profile? = null) {
userId = user.id ?: ULID.makeSpecial(0),
avatar = user.avatar,
size = 48.dp,
presence = presenceFromStatus(user.status?.presence),
presence = presenceFromStatus(user.status?.presence)
)
Spacer(modifier = Modifier.width(12.dp))
@ -156,8 +158,8 @@ fun RawUserOverview(user: User, profile: Profile? = null) {
append("#${user.discriminator}")
pop()
}.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)
.then(modifier)
.then(
if (selected)
if (selected) {
Modifier
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f))
else Modifier
} else {
Modifier
}
)
.padding(4.dp)
.padding(8.dp)
@ -58,4 +60,4 @@ fun SelectedThemeChipPreview() {
selected = true,
onClick = {}
)
}
}

View File

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

View File

@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
data class Changelog(
val summary: String,
val version: String,
val date: String,
val date: String
)
@Serializable
@ -26,20 +26,28 @@ class Changelogs(val context: Context, val kvStorage: KVStorage? = null) {
}
fun getChangelog(version: String): String {
return context.assets.open("changelogs/${version}.md").use {
return context.assets.open("changelogs/$version.md").use {
it.reader().readText()
}
}
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
}
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)
}
}
}

View File

@ -15,13 +15,13 @@ data class Emoji(
val alternates: List<List<Long>>,
val emoticons: List<String>,
val shortcodes: List<String>,
val animated: Boolean,
val animated: Boolean
)
@Serializable
data class EmojiGroup(
val group: String,
val emoji: List<Emoji>,
val emoji: List<Emoji>
)
enum class FitzpatrickSkinTone(val modifierCodepoint: Int?) {
@ -30,7 +30,7 @@ enum class FitzpatrickSkinTone(val modifierCodepoint: Int?) {
MediumLight(0x1F3FC),
Medium(0x1F3FD),
MediumDark(0x1F3FE),
Dark(0x1F3FF),
Dark(0x1F3FF)
}
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),
Objects("Objects", R.string.emoji_category_objects),
Symbols("Symbols", R.string.emoji_category_symbols),
Flags("Flags", R.string.emoji_category_flags),
Flags("Flags", R.string.emoji_category_flags)
}
sealed class Category {
@ -55,7 +55,7 @@ sealed class EmojiPickerItem {
data class UnicodeEmoji(
val character: String,
val hasSkinTones: Boolean,
val alternates: List<List<Long>>,
val alternates: List<List<Long>>
) : EmojiPickerItem()
data class ServerEmote(val emote: chat.revolt.api.schemas.Emoji) : EmojiPickerItem()
@ -106,17 +106,19 @@ class EmojiImpl {
val category =
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
list.addAll(group.emoji.map { emoji ->
EmojiPickerItem.UnicodeEmoji(
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
emoji.alternates.any { alternate ->
alternate.any { codepoint ->
codepoint in 0x1F3FB..0x1F3FF
}
},
emoji.alternates
)
})
list.addAll(
group.emoji.map { emoji ->
EmojiPickerItem.UnicodeEmoji(
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
emoji.alternates.any { alternate ->
alternate.any { codepoint ->
codepoint in 0x1F3FB..0x1F3FF
}
},
emoji.alternates
)
}
)
}
return list
@ -143,7 +145,9 @@ class EmojiImpl {
for (server in serversWithEmotes()) {
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 =
RevoltAPI.emojiCache.values.filter { it.parent?.id == server.id }
val lastIndex = index + allEmotesInThatServer.size
@ -152,12 +156,16 @@ class EmojiImpl {
}
for (section in UnicodeEmojiSection.entries) {
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()) {
Int.MAX_VALUE
} else {
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)
}
@ -220,17 +228,19 @@ class EmojiImpl {
val category =
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
list.addAll(matchingEmoji.map { emoji ->
EmojiPickerItem.UnicodeEmoji(
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
emoji.alternates.any { alternate ->
alternate.any { codepoint ->
codepoint in 0x1F3FB..0x1F3FF
}
},
emoji.alternates
)
})
list.addAll(
matchingEmoji.map { emoji ->
EmojiPickerItem.UnicodeEmoji(
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
emoji.alternates.any { alternate ->
alternate.any { codepoint ->
codepoint in 0x1F3FB..0x1F3FF
}
},
emoji.alternates
)
}
)
}
}
@ -240,4 +250,4 @@ class EmojiImpl {
init {
metadata = initMetadata(RevoltApplication.instance.applicationContext)
}
}
}

View File

@ -6,4 +6,4 @@ object Platform {
fun needsShowClipboardNotification(): Boolean {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.S
}
}
}

View File

@ -24,4 +24,4 @@ fun LifecycleEffect(onEvent: (owner: LifecycleOwner, event: Lifecycle.Event) ->
lifecycle.removeObserver(observer)
}
}
}
}

View File

@ -15,7 +15,7 @@ import com.discord.simpleast.core.node.Node
// https://github.com/discord/SimpleAST/blob/567b61c51056cbdec39e839100690c576c26a4c6/app/src/main/java/com/discord/simpleast/sample/spans/BlockBackgroundNode.kt
// LICENSED UNDER THE APACHE LICENSE, VERSION 2.0
// Adapted for Revolt.
//
//
// Changes:
// - Fill and stroke colours are now parameters
// - Whether we are in a quote is no longer a boolean, but an integer with the quote depth
@ -28,7 +28,7 @@ class BlockBackgroundNode<R>(
private val quoteDepth: Int,
private val fillColor: Int = Color.DKGRAY,
private val strokeColor: Int = Color.BLACK,
vararg children: Node<R>,
vararg children: Node<R>
) : Node.Parent<R>(*children) {
override fun render(builder: SpannableStringBuilder, renderContext: R) {
@ -41,7 +41,8 @@ class BlockBackgroundNode<R>(
ensureEndsWithNewline(builder)
val backgroundSpan = BlockBackgroundSpan(
fillColor, strokeColor,
fillColor,
strokeColor,
strokeWidth = 2,
strokeRadius = 15,
leftMargin = 40 * quoteDepth
@ -131,4 +132,4 @@ class BlockBackgroundSpan(
draw(canvas)
}
}
}
}

View File

@ -9,8 +9,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
class EmoteSpan(drawable: Drawable) :
ImageSpan(drawable, ALIGN_BOTTOM) {
}
ImageSpan(drawable, ALIGN_BOTTOM)
class EmoteClickableSpan(private val emoteId: String) : LongClickableSpan() {
override fun onClick(widget: View) {
@ -22,4 +21,4 @@ class EmoteClickableSpan(private val emoteId: String) : LongClickableSpan() {
override fun onLongClick(view: View?) {
// no-op
}
}
}

View File

@ -20,9 +20,11 @@ class LinkSpan(private val url: String, private val drawBackground: Boolean = fa
// Intercept invite links
if (uri.host == Uri.parse(REVOLT_INVITES).host!! ||
(uri.host?.endsWith(Uri.parse(REVOLT_APP).host!!) == true && uri.path?.startsWith(
"/invite"
) == true)
(
uri.host?.endsWith(Uri.parse(REVOLT_APP).host!!) == true && uri.path?.startsWith(
"/invite"
) == true
)
) {
val intent = Intent(
widget.context,
@ -81,4 +83,4 @@ class LinkSpan(private val url: String, private val drawBackground: Boolean = fa
ds.bgColor = ds.linkColor and 0x33ffffff // 20% alpha
}
}
}
}

View File

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

View File

@ -17,5 +17,5 @@ data class MarkdownContext(
val channelMap: Map<String, String>,
val emojiMap: Map<String, Emoji>,
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) {
val content = renderContext.memberMap[userId]?.let { "@$it" }
?: renderContext.userMap[userId]?.let { "@${it.username}" }
?: "<@${userId}>"
?: "<@$userId>"
builder.append(content)
builder.setSpan(
@ -58,7 +58,7 @@ class CustomEmoteNode(private val emoteId: String, private val context: Context)
Node<MarkdownContext>() {
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
val content = renderContext.emojiMap[emoteId]?.let { ":${it.name}:" }
?: ":${emoteId}:"
?: ":$emoteId:"
val isGif = renderContext.emojiMap[emoteId]?.animated ?: false
val emoteUrl = "$REVOLT_FILES/emojis/$emoteId/emote${if (isGif) ".gif" else ".png"}"
@ -128,4 +128,4 @@ class LinkNode(val content: String, val url: String = content) : Node<MarkdownCo
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
}

View File

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

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