style: run ktlint on code
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
f15438708e
commit
fac7411eea
|
|
@ -0,0 +1,2 @@
|
|||
[*.{kt,kts}]
|
||||
ktlint_code_style = android_studio
|
||||
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ fun AppEntrypoint(windowSizeClass: WindowSizeClass) {
|
|||
val navController = rememberNavController()
|
||||
|
||||
RevoltTheme(
|
||||
requestedTheme = GlobalState.theme,
|
||||
requestedTheme = GlobalState.theme
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
|
|||
|
|
@ -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,7 +188,8 @@ fun ImageViewScreen(
|
|||
.fillMaxSize()
|
||||
) {
|
||||
Column {
|
||||
PageHeader(text = stringResource(
|
||||
PageHeader(
|
||||
text = stringResource(
|
||||
id = R.string.media_viewer_title_image,
|
||||
resource.filename ?: resource.id!!
|
||||
),
|
||||
|
|
@ -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,7 +268,7 @@ fun ImageViewScreen(
|
|||
},
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +204,8 @@ fun VideoViewScreen(
|
|||
.fillMaxSize()
|
||||
) {
|
||||
Column {
|
||||
PageHeader(text = stringResource(
|
||||
PageHeader(
|
||||
text = stringResource(
|
||||
id = R.string.media_viewer_title_video,
|
||||
resource.filename ?: resource.id!!
|
||||
),
|
||||
|
|
@ -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,7 +283,7 @@ fun VideoViewScreen(
|
|||
},
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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? {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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}"
|
||||
}
|
||||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -35,14 +35,20 @@ suspend fun uploadToAutumn(
|
|||
val uploadUrl = "$REVOLT_FILES/$tag"
|
||||
|
||||
val response = RevoltHttp.post(uploadUrl) {
|
||||
setBody(MultiPartFormDataContent(
|
||||
setBody(
|
||||
MultiPartFormDataContent(
|
||||
formData {
|
||||
append("file", file.readBytes(), Headers.build {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -50,5 +50,5 @@ data class AutumnId(
|
|||
|
||||
@Serializable
|
||||
data class AutumnError(
|
||||
val type: String,
|
||||
val type: String
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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,7 +90,7 @@ 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,15 @@ class Unreads {
|
|||
|
||||
suspend fun sync() {
|
||||
channels.clear()
|
||||
channels.putAll(syncUnreads().associate {
|
||||
channels.putAll(
|
||||
syncUnreads().associate {
|
||||
it.id.channel to ChannelUnread(
|
||||
id = it.id.channel,
|
||||
last_id = it.last_id,
|
||||
mentions = it.mentions
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
hasLoaded.value = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
if (embed.originalURL != null) {
|
||||
Modifier.clickable {
|
||||
onLinkClick(embed.originalURL)
|
||||
} else Modifier
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.aspectRatio(
|
||||
(it.width?.toFloat() ?: 0f) / (it.height?.toFloat() ?: 0f)
|
||||
|
|
@ -130,15 +136,10 @@ 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)
|
||||
|
|
|
|||
|
|
@ -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 ?: ""]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
if (searchQuery.isNotEmpty()) {
|
||||
Modifier.clickable {
|
||||
searchQuery = ""
|
||||
focusManager.clearFocus() // this prevents the text field Z-below from gaining focus
|
||||
} else Modifier
|
||||
}
|
||||
} 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,8 +610,11 @@ 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
|
||||
is Category.UnicodeEmojiCategory -> stringResource(
|
||||
item.category.definition.nameResource
|
||||
)
|
||||
is Category.ServerEmoteCategory ->
|
||||
item.category.server.name
|
||||
?: stringResource(R.string.unknown)
|
||||
},
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -31,11 +31,14 @@ fun IconPlaceholder(
|
|||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))
|
||||
.then(
|
||||
if (onClick != NoopHandler || onLongClick != NoopHandler) Modifier.combinedClickable(
|
||||
if (onClick != NoopHandler || onLongClick != NoopHandler) {
|
||||
Modifier.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
else Modifier
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
@ -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") }
|
||||
)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
if (onLongClick != null || onClick != null) {
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
else Modifier
|
||||
} 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
|
||||
if (onLongClick != null || onClick != null) {
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
else Modifier
|
||||
),
|
||||
} 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
|
||||
if (onLongClick != null || onClick != null) {
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
else Modifier
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
|
|
@ -164,12 +173,15 @@ fun GroupIcon(
|
|||
modifier = Modifier
|
||||
.size(size)
|
||||
.then(
|
||||
if (onLongClick != null || onClick != null) Modifier
|
||||
if (onLongClick != null || onClick != null) {
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
else Modifier
|
||||
} 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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
(
|
||||
webResourceRequest.url.host?.endsWith("revolt.chat") == true && webResourceRequest.url.path?.startsWith(
|
||||
"/invite"
|
||||
) == true)
|
||||
) == 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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,18 +97,18 @@ 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 = {}
|
||||
|
|
|
|||
|
|
@ -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,7 +96,7 @@ fun ChannelHeader(
|
|||
contentDescription = stringResource(R.string.menu),
|
||||
modifier = Modifier
|
||||
.size(18.dp)
|
||||
.alpha(0.4f),
|
||||
.alpha(0.4f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -47,17 +44,15 @@ fun StackedUserAvatars(
|
|||
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,7 +98,7 @@ fun TypingIndicator(
|
|||
),
|
||||
fontSize = 12.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ fun RowScope.ChannelList(
|
|||
currentChannel: String?,
|
||||
onChannelClick: (String) -> Unit,
|
||||
onSpecialClick: (String) -> Unit,
|
||||
onServerSheetOpenFor: (String) -> Unit,
|
||||
onServerSheetOpenFor: (String) -> Unit
|
||||
) {
|
||||
val lazyListState = rememberLazyListState()
|
||||
val enableSmallBanner by remember {
|
||||
|
|
@ -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,9 +238,15 @@ fun RowScope.ChannelList(
|
|||
val channel = dmAbleChannels.getOrNull(it) ?: return@items
|
||||
|
||||
val partner =
|
||||
if (channel.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner(
|
||||
if (channel.channelType == ChannelType.DirectMessage) {
|
||||
RevoltAPI.userCache[
|
||||
ChannelUtils.resolveDMPartner(
|
||||
channel
|
||||
)] else null
|
||||
)
|
||||
]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
DrawerChannel(
|
||||
name = partner?.let { p -> User.resolveDefaultName(p) } ?: channel.name
|
||||
|
|
@ -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(
|
||||
if (channel.channelType == ChannelType.DirectMessage) {
|
||||
RevoltAPI.userCache[
|
||||
ChannelUtils.resolveDMPartner(
|
||||
channel
|
||||
)] else null
|
||||
)
|
||||
]
|
||||
} 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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
if (large) {
|
||||
Modifier.padding(
|
||||
end = 12.dp,
|
||||
start = 4.dp,
|
||||
top = 4.dp,
|
||||
bottom = 4.dp
|
||||
) else Modifier.padding(end = 8.dp)
|
||||
)
|
||||
} else {
|
||||
Modifier.padding(end = 8.dp)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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,7 +158,7 @@ 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,19 +26,27 @@ 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +106,8 @@ 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 ->
|
||||
list.addAll(
|
||||
group.emoji.map { emoji ->
|
||||
EmojiPickerItem.UnicodeEmoji(
|
||||
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
|
||||
emoji.alternates.any { alternate ->
|
||||
|
|
@ -116,7 +117,8 @@ class EmojiImpl {
|
|||
},
|
||||
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,7 +228,8 @@ class EmojiImpl {
|
|||
val category =
|
||||
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
|
||||
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
|
||||
list.addAll(matchingEmoji.map { emoji ->
|
||||
list.addAll(
|
||||
matchingEmoji.map { emoji ->
|
||||
EmojiPickerItem.UnicodeEmoji(
|
||||
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
|
||||
emoji.alternates.any { alternate ->
|
||||
|
|
@ -230,7 +239,8 @@ class EmojiImpl {
|
|||
},
|
||||
emoji.alternates
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
(
|
||||
uri.host?.endsWith(Uri.parse(REVOLT_APP).host!!) == true && uri.path?.startsWith(
|
||||
"/invite"
|
||||
) == true)
|
||||
) == true
|
||||
)
|
||||
) {
|
||||
val intent = Intent(
|
||||
widget.context,
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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"}"
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ package chat.revolt.internals.markdown
|
|||
|
||||
import android.text.format.DateUtils
|
||||
import android.util.Log
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.toJavaInstant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.toJavaInstant
|
||||
|
||||
fun resolveTimestamp(timestamp: Long, modifier: String? = null): String {
|
||||
val normalisedModifier = modifier.orEmpty().removePrefix(":")
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import androidx.datastore.preferences.core.edit
|
|||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
val Context.revoltKVStorage: DataStore<Preferences> by preferencesDataStore(name = "revolt_kv")
|
||||
|
||||
|
|
@ -45,4 +45,3 @@ class KVStorage @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ import io.ktor.client.request.get
|
|||
import io.ktor.client.statement.readBytes
|
||||
import java.io.File
|
||||
|
||||
class AttachmentProvider : FileProvider(R.xml.file_paths) {
|
||||
}
|
||||
class AttachmentProvider : FileProvider(R.xml.file_paths)
|
||||
|
||||
suspend fun getAttachmentContentUri(
|
||||
context: Context,
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ import chat.revolt.persistence.KVStorage
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.ktor.client.request.get
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
|
|
@ -138,10 +138,7 @@ class SplashScreenViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun SplashScreen(
|
||||
navController: NavController,
|
||||
viewModel: SplashScreenViewModel = hiltViewModel()
|
||||
) {
|
||||
fun SplashScreen(navController: NavController, viewModel: SplashScreenViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val webChallengeActivityResult = rememberLauncherForActivityResult(
|
||||
|
|
|
|||
|
|
@ -51,12 +51,11 @@ import chat.revolt.api.routes.misc.getRootRoute
|
|||
import chat.revolt.components.generic.PageHeader
|
||||
import chat.revolt.components.generic.PrimaryTabs
|
||||
import chat.revolt.internals.Platform
|
||||
import java.net.URI
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.net.URI
|
||||
|
||||
class AboutViewModel(
|
||||
) : ViewModel() {
|
||||
class AboutViewModel() : ViewModel() {
|
||||
var root by mutableStateOf<Root?>(null)
|
||||
var selectedTabIndex by mutableIntStateOf(0)
|
||||
|
||||
|
|
@ -83,11 +82,7 @@ class AboutViewModel(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun VersionItem(
|
||||
key: String,
|
||||
value: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
fun VersionItem(key: String, value: String, modifier: Modifier = Modifier) {
|
||||
Row(modifier) {
|
||||
Text(
|
||||
text = key,
|
||||
|
|
@ -131,16 +126,15 @@ fun DebugInfo(viewModel: AboutViewModel) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun AboutScreen(
|
||||
navController: NavController,
|
||||
viewModel: AboutViewModel = viewModel()
|
||||
) {
|
||||
fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewModel()) {
|
||||
val context = LocalContext.current
|
||||
val clipboardManager: ClipboardManager =
|
||||
LocalClipboardManager.current
|
||||
|
||||
fun copyDebugInformation() {
|
||||
clipboardManager.setText(AnnotatedString(RevoltJson.encodeToString(viewModel.getDebugInformation())))
|
||||
clipboardManager.setText(
|
||||
AnnotatedString(RevoltJson.encodeToString(viewModel.getDebugInformation()))
|
||||
)
|
||||
|
||||
if (Platform.needsShowClipboardNotification()) {
|
||||
Toast.makeText(
|
||||
|
|
@ -161,7 +155,8 @@ fun AboutScreen(
|
|||
PageHeader(
|
||||
text = stringResource(R.string.about),
|
||||
showBackButton = true,
|
||||
onBackButtonClicked = { navController.popBackStack() })
|
||||
onBackButtonClicked = { navController.popBackStack() }
|
||||
)
|
||||
|
||||
PrimaryTabs(
|
||||
tabs = listOf(
|
||||
|
|
@ -169,7 +164,8 @@ fun AboutScreen(
|
|||
stringResource(R.string.about_tab_details)
|
||||
),
|
||||
currentIndex = viewModel.selectedTabIndex,
|
||||
onTabSelected = { viewModel.selectedTabIndex = it })
|
||||
onTabSelected = { viewModel.selectedTabIndex = it }
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ data class AboutLibraries(
|
|||
|
||||
@Serializable
|
||||
data class Metadata(
|
||||
val generated: String,
|
||||
val generated: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
@ -58,7 +58,7 @@ data class License(
|
|||
val internalHash: String? = null,
|
||||
val url: String,
|
||||
val spdxId: String? = null,
|
||||
val name: String,
|
||||
val name: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
@ -72,26 +72,26 @@ data class Library(
|
|||
val name: String,
|
||||
val licenses: List<String>,
|
||||
val website: String? = null,
|
||||
val organization: Organization? = null,
|
||||
val organization: Organization? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Organization(
|
||||
val url: String,
|
||||
val name: String,
|
||||
val name: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Developer(
|
||||
val organisationUrl: String? = null,
|
||||
val name: String? = null,
|
||||
val name: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Scm(
|
||||
val connection: String? = null,
|
||||
val url: String,
|
||||
val developerConnection: String? = null,
|
||||
val developerConnection: String? = null
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -151,7 +151,8 @@ fun AttributionScreen(navController: NavController) {
|
|||
PageHeader(
|
||||
text = stringResource(R.string.oss_attribution),
|
||||
showBackButton = true,
|
||||
onBackButtonClicked = { navController.popBackStack() })
|
||||
onBackButtonClicked = { navController.popBackStack() }
|
||||
)
|
||||
|
||||
libraries?.let {
|
||||
LazyColumn {
|
||||
|
|
@ -207,5 +208,4 @@ fun AttributionScreen(navController: NavController) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -109,10 +109,9 @@ import com.airbnb.lottie.compose.rememberLottieComposition
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.sentry.Sentry
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
|
|
@ -202,9 +201,9 @@ class ChatRouterViewModel @Inject constructor(
|
|||
if (!pure) setSaveCurrentChannel(channelId)
|
||||
|
||||
@FeatureFlag("ClosedBetaAccessControl")
|
||||
if (RevoltAPI.channelCache.size > 0
|
||||
&& FeatureFlags.closedBetaAccessControl is ClosedBetaAccessControlVariates.Restricted
|
||||
&& (FeatureFlags.closedBetaAccessControl as ClosedBetaAccessControlVariates.Restricted)
|
||||
if (RevoltAPI.channelCache.size > 0 &&
|
||||
FeatureFlags.closedBetaAccessControl is ClosedBetaAccessControlVariates.Restricted &&
|
||||
(FeatureFlags.closedBetaAccessControl as ClosedBetaAccessControlVariates.Restricted)
|
||||
.predicate()
|
||||
.not()
|
||||
) {
|
||||
|
|
@ -229,7 +228,9 @@ class ChatRouterViewModel @Inject constructor(
|
|||
.setFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
)
|
||||
context.startActivity(intent) // i'm just messing with the user at this point, they know what they did
|
||||
context.startActivity(
|
||||
intent
|
||||
) // i'm just messing with the user at this point, they know what they did
|
||||
|
||||
Pipebomb.doHardCrash()
|
||||
}
|
||||
|
|
@ -269,9 +270,11 @@ fun ChatRouterScreen(
|
|||
val navController = rememberNavController()
|
||||
|
||||
val showSidebarSpark = remember { mutableStateOf(false) }
|
||||
val sidebarSparkComposition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.open_settings_tutorial))
|
||||
val sidebarSparkComposition by rememberLottieComposition(
|
||||
LottieCompositionSpec.RawRes(R.raw.open_settings_tutorial)
|
||||
)
|
||||
val sidebarSparkProgress by animateLottieCompositionAsState(
|
||||
composition = sidebarSparkComposition,
|
||||
composition = sidebarSparkComposition
|
||||
)
|
||||
|
||||
var showPlatformModDMHint by remember { mutableStateOf(false) }
|
||||
|
|
@ -364,8 +367,8 @@ fun ChatRouterScreen(
|
|||
snapshotFlow { windowSizeClass }
|
||||
.distinctUntilChanged()
|
||||
.collect { sizeClass ->
|
||||
useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded
|
||||
&& sizeClass.heightSizeClass != WindowHeightSizeClass.Compact
|
||||
useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded &&
|
||||
sizeClass.heightSizeClass != WindowHeightSizeClass.Compact
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -416,7 +419,7 @@ fun ChatRouterScreen(
|
|||
sheetState = changelogSheetState,
|
||||
onDismissRequest = {
|
||||
viewModel.latestChangelogRead = true
|
||||
},
|
||||
}
|
||||
) {
|
||||
ChangelogSheet(
|
||||
version = viewModel.latestChangelog,
|
||||
|
|
@ -441,8 +444,12 @@ fun ChatRouterScreen(
|
|||
.aspectRatio(1f),
|
||||
renderMode = RenderMode.HARDWARE
|
||||
)
|
||||
Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1))
|
||||
Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2))
|
||||
Text(
|
||||
stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1)
|
||||
)
|
||||
Text(
|
||||
stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2)
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
|
|
@ -488,7 +495,7 @@ fun ChatRouterScreen(
|
|||
sheetState = statusSheetState,
|
||||
onDismissRequest = {
|
||||
showStatusSheet = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
StatusSheet(
|
||||
onBeforeNavigation = {
|
||||
|
|
@ -511,13 +518,12 @@ fun ChatRouterScreen(
|
|||
sheetState = addServerSheetState,
|
||||
onDismissRequest = {
|
||||
showAddServerSheet = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
AddServerSheet()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (showServerContextSheet) {
|
||||
val serverContextSheetState = rememberModalBottomSheetState()
|
||||
|
||||
|
|
@ -525,14 +531,14 @@ fun ChatRouterScreen(
|
|||
sheetState = serverContextSheetState,
|
||||
onDismissRequest = {
|
||||
showServerContextSheet = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
ServerContextSheet(
|
||||
serverId = serverContextSheetTarget,
|
||||
onHideSheet = {
|
||||
serverContextSheetState.hide()
|
||||
showServerContextSheet = false
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -544,7 +550,7 @@ fun ChatRouterScreen(
|
|||
sheetState = userContextSheetState,
|
||||
onDismissRequest = {
|
||||
showUserContextSheet = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
UserContextSheet(
|
||||
userId = userContextSheetTarget,
|
||||
|
|
@ -569,14 +575,14 @@ fun ChatRouterScreen(
|
|||
Text(
|
||||
text = stringResource(id = R.string.channel_link_invalid),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.channel_link_invalid_description),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
|
|
@ -596,7 +602,7 @@ fun ChatRouterScreen(
|
|||
sheetState = linkInfoSheetState,
|
||||
onDismissRequest = {
|
||||
showLinkInfoSheet = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
LinkInfoSheet(
|
||||
url = linkInfoSheetUrl,
|
||||
|
|
@ -614,7 +620,7 @@ fun ChatRouterScreen(
|
|||
sheetState = emoteInfoSheetState,
|
||||
onDismissRequest = {
|
||||
showEmoteInfoSheet = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
EmoteInfoSheet(
|
||||
id = emoteInfoSheetTarget,
|
||||
|
|
@ -630,19 +636,22 @@ fun ChatRouterScreen(
|
|||
.fillMaxWidth()
|
||||
.safeDrawingPadding()
|
||||
) {
|
||||
AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) {
|
||||
AnimatedVisibility(
|
||||
visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected
|
||||
) {
|
||||
DisconnectedNotice(
|
||||
state = RealtimeSocket.disconnectionState,
|
||||
onReconnect = {
|
||||
RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting)
|
||||
scope.launch { RevoltAPI.connectWS() }
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (useTabletAwareUI) {
|
||||
Row {
|
||||
DismissibleDrawerSheet(
|
||||
drawerContainerColor = Color.Transparent,
|
||||
drawerContainerColor = Color.Transparent
|
||||
) {
|
||||
Sidebar(
|
||||
viewModel = viewModel,
|
||||
|
|
@ -656,7 +665,7 @@ fun ChatRouterScreen(
|
|||
},
|
||||
onShowAddServerSheet = {
|
||||
showAddServerSheet = true
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
ChannelNavigator(
|
||||
|
|
@ -670,7 +679,7 @@ fun ChatRouterScreen(
|
|||
userContextSheetTarget = target
|
||||
userContextSheetServer = server
|
||||
showUserContextSheet = true
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -678,7 +687,7 @@ fun ChatRouterScreen(
|
|||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
DismissibleDrawerSheet(
|
||||
drawerContainerColor = Color.Transparent,
|
||||
drawerContainerColor = Color.Transparent
|
||||
) {
|
||||
Sidebar(
|
||||
viewModel = viewModel,
|
||||
|
|
@ -693,7 +702,7 @@ fun ChatRouterScreen(
|
|||
onShowAddServerSheet = {
|
||||
showAddServerSheet = true
|
||||
},
|
||||
drawerState = drawerState,
|
||||
drawerState = drawerState
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -711,10 +720,11 @@ fun ChatRouterScreen(
|
|||
userContextSheetTarget = target
|
||||
userContextSheetServer = server
|
||||
showUserContextSheet = true
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -726,7 +736,7 @@ fun Sidebar(
|
|||
drawerState: DrawerState? = null,
|
||||
onShowStatusSheet: () -> Unit,
|
||||
onShowServerContextSheet: (String) -> Unit,
|
||||
onShowAddServerSheet: () -> Unit,
|
||||
onShowAddServerSheet: () -> Unit
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
|
@ -786,9 +796,15 @@ fun Sidebar(
|
|||
|
||||
else -> {
|
||||
val partner =
|
||||
if (it.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner(
|
||||
if (it.channelType == ChannelType.DirectMessage) {
|
||||
RevoltAPI.userCache[
|
||||
ChannelUtils.resolveDMPartner(
|
||||
it
|
||||
)] else null
|
||||
)
|
||||
]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
UserAvatar(
|
||||
username = partner?.let { p ->
|
||||
|
|
@ -830,17 +846,22 @@ fun Sidebar(
|
|||
// - Sort the servers that are in the ordering using the ordering.
|
||||
// - Add the servers that aren't in the ordering to the end of the list.
|
||||
// - Sort the servers that aren't in the ordering by their ID (creation order).
|
||||
((RevoltAPI.serverCache.values.filter {
|
||||
(
|
||||
(
|
||||
RevoltAPI.serverCache.values.filter {
|
||||
SyncedSettings.ordering.servers.contains(
|
||||
it.id
|
||||
)
|
||||
}
|
||||
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }) + (RevoltAPI.serverCache.values.filter {
|
||||
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }
|
||||
) + (
|
||||
RevoltAPI.serverCache.values.filter {
|
||||
!SyncedSettings.ordering.servers.contains(
|
||||
it.id
|
||||
)
|
||||
}.sortedBy { it.id }
|
||||
))
|
||||
)
|
||||
)
|
||||
.forEach { server ->
|
||||
if (server.id == null || server.name == null) return@forEach
|
||||
|
||||
|
|
@ -854,7 +875,7 @@ fun Sidebar(
|
|||
/*serverContextSheetTarget = server.id
|
||||
showServerContextSheet = true*/
|
||||
onShowServerContextSheet(server.id)
|
||||
},
|
||||
}
|
||||
) {
|
||||
viewModel.navigateToServer(
|
||||
server.id,
|
||||
|
|
@ -864,7 +885,7 @@ fun Sidebar(
|
|||
}
|
||||
|
||||
DrawerServerlikeIcon(
|
||||
onClick = onShowAddServerSheet,
|
||||
onClick = onShowAddServerSheet
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
|
|
@ -892,7 +913,7 @@ fun Sidebar(
|
|||
},
|
||||
onServerSheetOpenFor = { target ->
|
||||
onShowServerContextSheet(target)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -906,7 +927,7 @@ fun ChannelNavigator(
|
|||
useDrawer: Boolean,
|
||||
drawerBackHandler: () -> Unit,
|
||||
drawerState: DrawerState? = null,
|
||||
onShowUserContextSheet: (String, String?) -> Unit,
|
||||
onShowUserContextSheet: (String, String?) -> Unit
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
|
@ -931,8 +952,11 @@ fun ChannelNavigator(
|
|||
channelId = channelId,
|
||||
onToggleDrawer = {
|
||||
scope.launch {
|
||||
if (drawerState?.isOpen == true) drawerState.close()
|
||||
else drawerState?.open()
|
||||
if (drawerState?.isOpen == true) {
|
||||
drawerState.close()
|
||||
} else {
|
||||
drawerState?.open()
|
||||
}
|
||||
}
|
||||
},
|
||||
onUserSheetOpenFor = { target, server ->
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ fun FeedbackDialog(navController: NavController) {
|
|||
onValueChange = {},
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_feedback_category),
|
||||
text = stringResource(id = R.string.settings_feedback_category)
|
||||
)
|
||||
},
|
||||
readOnly = true,
|
||||
|
|
@ -181,7 +181,9 @@ fun FeedbackDialog(navController: NavController) {
|
|||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowDropDown,
|
||||
contentDescription = stringResource(id = R.string.settings_feedback_category)
|
||||
contentDescription = stringResource(
|
||||
id = R.string.settings_feedback_category
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -192,7 +194,7 @@ fun FeedbackDialog(navController: NavController) {
|
|||
expanded = categoryDropdownExpanded.value,
|
||||
onDismissRequest = {
|
||||
categoryDropdownExpanded.value = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
category.forEach { (key, value) ->
|
||||
DropdownMenuItem(
|
||||
|
|
@ -218,7 +220,7 @@ fun FeedbackDialog(navController: NavController) {
|
|||
},
|
||||
supportingText = {
|
||||
Text(
|
||||
text = "${message.value.length}/1250",
|
||||
text = "${message.value.length}/1250"
|
||||
)
|
||||
},
|
||||
enabled = !sending.value,
|
||||
|
|
@ -233,7 +235,7 @@ fun FeedbackDialog(navController: NavController) {
|
|||
navController.popBackStack()
|
||||
},
|
||||
modifier = Modifier.testTag("feedback_cancel"),
|
||||
enabled = !sending.value,
|
||||
enabled = !sending.value
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.cancel))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,7 @@ enum class ReportFlowState {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ReportMessageDialog(
|
||||
navController: NavController,
|
||||
messageId: String,
|
||||
) {
|
||||
fun ReportMessageDialog(navController: NavController, messageId: String) {
|
||||
val message = RevoltAPI.messageCache[messageId]
|
||||
if (message == null) {
|
||||
navController.popBackStack()
|
||||
|
|
@ -84,7 +81,7 @@ fun ReportMessageDialog(
|
|||
"SpamAbuse" to stringResource(id = R.string.report_reason_content_spam_abuse),
|
||||
"Malware" to stringResource(id = R.string.report_reason_content_malware),
|
||||
"Harassment" to stringResource(id = R.string.report_reason_content_harassment),
|
||||
"Other" to stringResource(id = R.string.report_reason_content_other),
|
||||
"Other" to stringResource(id = R.string.report_reason_content_other)
|
||||
)
|
||||
val reasonDropdownExpanded = remember { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -146,7 +143,7 @@ fun ReportMessageDialog(
|
|||
},
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.report_reason),
|
||||
text = stringResource(id = R.string.report_reason)
|
||||
)
|
||||
},
|
||||
readOnly = true,
|
||||
|
|
@ -159,7 +156,9 @@ fun ReportMessageDialog(
|
|||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowDropDown,
|
||||
contentDescription = stringResource(id = R.string.report_reason)
|
||||
contentDescription = stringResource(
|
||||
id = R.string.report_reason
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -170,7 +169,7 @@ fun ReportMessageDialog(
|
|||
expanded = reasonDropdownExpanded.value,
|
||||
onDismissRequest = {
|
||||
reasonDropdownExpanded.value = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
reasons.forEach { (key, value) ->
|
||||
DropdownMenuItem(
|
||||
|
|
@ -196,7 +195,9 @@ fun ReportMessageDialog(
|
|||
},
|
||||
supportingText = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.report_reason_additional_hint)
|
||||
text = stringResource(
|
||||
id = R.string.report_reason_additional_hint
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -282,11 +283,11 @@ fun ReportMessageDialog(
|
|||
Text(
|
||||
text = stringResource(id = R.string.report_submit_success),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column() {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
|
|
@ -349,11 +350,11 @@ fun ReportMessageDialog(
|
|||
Text(
|
||||
text = stringResource(id = R.string.report_submit_error_header),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column() {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(id = R.string.report_submit_error),
|
||||
textAlign = TextAlign.Center
|
||||
|
|
|
|||
|
|
@ -55,12 +55,12 @@ fun HomeScreen(navController: NavController) {
|
|||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "🐈",
|
||||
fontSize = 100.sp,
|
||||
modifier = Modifier.rotate(catRotation),
|
||||
modifier = Modifier.rotate(catRotation)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,11 @@ fun HomeScreen(navController: NavController) {
|
|||
)
|
||||
},
|
||||
heading = { Text(text = stringResource(id = R.string.home_join_jenvolt)) },
|
||||
description = { Text(text = stringResource(id = R.string.home_join_jenvolt_description)) },
|
||||
description = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.home_join_jenvolt_description)
|
||||
)
|
||||
}
|
||||
) {
|
||||
context.startActivity(
|
||||
Intent(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ fun NoCurrentChannelScreen() {
|
|||
.fillMaxSize()
|
||||
.padding(64.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.no_active_channel),
|
||||
|
|
@ -34,7 +34,7 @@ fun NoCurrentChannelScreen() {
|
|||
Text(
|
||||
text = stringResource(R.string.no_active_channel_body),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -91,10 +91,10 @@ import chat.revolt.sheets.ChannelInfoSheet
|
|||
import chat.revolt.sheets.MessageContextSheet
|
||||
import com.discord.simpleast.core.simple.SimpleMarkdownRules
|
||||
import com.discord.simpleast.core.simple.SimpleRenderer
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -207,10 +207,10 @@ fun ChannelScreen(
|
|||
sheetState = channelInfoSheetState,
|
||||
onDismissRequest = {
|
||||
channelInfoSheetShown = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
ChannelInfoSheet(
|
||||
channelId = channelId,
|
||||
channelId = channelId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -222,7 +222,7 @@ fun ChannelScreen(
|
|||
sheetState = messageContextSheetState,
|
||||
onDismissRequest = {
|
||||
messageContextSheetShown = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
MessageContextSheet(
|
||||
messageId = messageContextSheetTarget,
|
||||
|
|
@ -232,7 +232,7 @@ fun ChannelScreen(
|
|||
},
|
||||
onReportMessage = {
|
||||
navController.navigate("report/message/$messageContextSheetTarget")
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -252,7 +252,7 @@ fun ChannelScreen(
|
|||
channelInfoSheetShown = true
|
||||
},
|
||||
onToggleDrawer = onToggleDrawer,
|
||||
useDrawer = useDrawer,
|
||||
useDrawer = useDrawer
|
||||
)
|
||||
|
||||
val isScrolledToBottom = remember(lazyListState) {
|
||||
|
|
@ -310,12 +310,12 @@ fun ChannelScreen(
|
|||
parse = {
|
||||
val parser = MarkdownParser()
|
||||
.addRules(
|
||||
SimpleMarkdownRules.createEscapeRule(),
|
||||
SimpleMarkdownRules.createEscapeRule()
|
||||
)
|
||||
.addRevoltRules(context)
|
||||
.addRules(
|
||||
createCodeRule(context, codeBlockColor.toArgb()),
|
||||
createInlineCodeRule(context, codeBlockColor.toArgb()),
|
||||
createInlineCodeRule(context, codeBlockColor.toArgb())
|
||||
)
|
||||
.addRules(
|
||||
SimpleMarkdownRules.createSimpleMarkdownRules(
|
||||
|
|
@ -366,7 +366,7 @@ fun ChannelScreen(
|
|||
return@Message
|
||||
}
|
||||
viewModel.replyToMessage(message)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -399,11 +399,11 @@ fun ChannelScreen(
|
|||
!isScrolledToBottom.value,
|
||||
enter = slideInHorizontally(
|
||||
animationSpec = RevoltTweenInt,
|
||||
initialOffsetX = { it },
|
||||
initialOffsetX = { it }
|
||||
) + fadeIn(animationSpec = RevoltTweenFloat),
|
||||
exit = slideOutHorizontally(
|
||||
animationSpec = RevoltTweenInt,
|
||||
targetOffsetX = { it },
|
||||
targetOffsetX = { it }
|
||||
) + fadeOut(animationSpec = RevoltTweenFloat),
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
|
|
@ -460,7 +460,7 @@ fun ChannelScreen(
|
|||
attachments = viewModel.pendingAttachments,
|
||||
uploading = viewModel.isSendingMessage,
|
||||
uploadProgress = viewModel.pendingUploadProgress,
|
||||
onRemove = { viewModel.pendingAttachments.remove(it) },
|
||||
onRemove = { viewModel.pendingAttachments.remove(it) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -525,12 +525,14 @@ fun ChannelScreen(
|
|||
if (nowFocused && viewModel.currentBottomPane != BottomPane.None) {
|
||||
viewModel.currentBottomPane = BottomPane.None
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = viewModel.currentBottomPane == BottomPane.InbuiltMediaPicker) {
|
||||
AnimatedVisibility(
|
||||
visible = viewModel.currentBottomPane == BottomPane.InbuiltMediaPicker
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
InbuiltMediaPicker(
|
||||
onOpenDocumentsUi = {
|
||||
|
|
@ -573,7 +575,9 @@ fun ChannelScreen(
|
|||
if (e is FileNotFoundException) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.file_picker_cannot_attach_file_invalid),
|
||||
context.getString(
|
||||
R.string.file_picker_cannot_attach_file_invalid
|
||||
),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
|
@ -582,7 +586,7 @@ fun ChannelScreen(
|
|||
pendingMedia = viewModel.pendingAttachments
|
||||
.filterNot { it.pickerIdentifier == null }
|
||||
.map { it.pickerIdentifier!! },
|
||||
disabled = viewModel.isSendingMessage,
|
||||
disabled = viewModel.isSendingMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -315,16 +315,19 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
currentMsg.id == it.id
|
||||
} ?: return@onEach // Message not found, ignore.
|
||||
|
||||
if (messageFrame.author != null)
|
||||
if (messageFrame.author != null) {
|
||||
addUserIfUnknown(messageFrame.author)
|
||||
}
|
||||
|
||||
regroupMessages(renderableMessages.map { currentMsg ->
|
||||
regroupMessages(
|
||||
renderableMessages.map { currentMsg ->
|
||||
if (currentMsg.id == it.id) {
|
||||
currentMsg.mergeWithPartial(messageFrame)
|
||||
} else {
|
||||
currentMsg
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
is MessageAppendFrame -> {
|
||||
|
|
@ -336,13 +339,15 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
|
||||
if (!hasMessage) return@onEach
|
||||
|
||||
regroupMessages(renderableMessages.map { currentMsg ->
|
||||
regroupMessages(
|
||||
renderableMessages.map { currentMsg ->
|
||||
if (currentMsg.id == it.id) {
|
||||
RevoltAPI.messageCache[it.id] ?: currentMsg
|
||||
} else {
|
||||
currentMsg
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
is MessageDeleteFrame -> {
|
||||
|
|
@ -469,9 +474,12 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
if (activeChannel == null) return
|
||||
|
||||
val selfUser = RevoltAPI.userCache[RevoltAPI.selfId] ?: return
|
||||
val selfMember = if (activeChannel!!.server == null) null else
|
||||
val selfMember = if (activeChannel!!.server == null) {
|
||||
null
|
||||
} else {
|
||||
activeChannel?.server?.let { RevoltAPI.members.getMember(it, selfUser.id!!) }
|
||||
?: fetchMember(activeChannel!!.server!!, selfUser.id!!)
|
||||
}
|
||||
|
||||
val hasPermission =
|
||||
Roles.permissionFor(activeChannel!!, selfUser, selfMember)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ fun LoginGreetingScreen(navController: NavController) {
|
|||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Text(
|
||||
|
|
@ -105,7 +105,7 @@ fun LoginGreetingScreen(navController: NavController) {
|
|||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
fontSize = 16.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
|
|
@ -157,7 +157,6 @@ fun LoginGreetingScreen(navController: NavController) {
|
|||
url = "$REVOLT_MARKETING/aup"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -49,12 +49,12 @@ import chat.revolt.components.generic.FormTextField
|
|||
import chat.revolt.components.generic.Weblink
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@HiltViewModel
|
||||
class LoginViewModel @Inject constructor(
|
||||
private val kvStorage: KVStorage,
|
||||
private val kvStorage: KVStorage
|
||||
) : ViewModel() {
|
||||
private var _email by mutableStateOf("")
|
||||
val email: String
|
||||
|
|
@ -137,10 +137,7 @@ class LoginViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun LoginScreen(
|
||||
navController: NavController,
|
||||
viewModel: LoginViewModel = hiltViewModel()
|
||||
) {
|
||||
fun LoginScreen(navController: NavController, viewModel: LoginViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(viewModel.navigateTo) {
|
||||
|
|
@ -196,10 +193,9 @@ fun LoginScreen(
|
|||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(270.dp),
|
||||
|
|
@ -216,7 +212,7 @@ fun LoginScreen(
|
|||
value = viewModel.password,
|
||||
label = stringResource(R.string.password),
|
||||
type = KeyboardType.Password,
|
||||
onChange = viewModel::setPassword,
|
||||
onChange = viewModel::setPassword
|
||||
)
|
||||
|
||||
AnyLink(
|
||||
|
|
|
|||
|
|
@ -48,12 +48,12 @@ import chat.revolt.components.generic.CollapsibleCard
|
|||
import chat.revolt.components.generic.FormTextField
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@HiltViewModel
|
||||
class MfaScreenViewModel @Inject constructor(
|
||||
private val kvStorage: KVStorage,
|
||||
private val kvStorage: KVStorage
|
||||
) : ViewModel() {
|
||||
private var _totpCode by mutableStateOf("")
|
||||
val totpCode: String
|
||||
|
|
@ -183,7 +183,7 @@ fun MfaScreen(
|
|||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Text(
|
||||
|
|
@ -193,7 +193,7 @@ fun MfaScreen(
|
|||
),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
|
|
@ -215,7 +215,6 @@ fun MfaScreen(
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
// Collapsible cards for each auth type
|
||||
allowedAuthTypes.forEach { authType ->
|
||||
when (authType) {
|
||||
|
|
@ -245,7 +244,7 @@ fun MfaScreen(
|
|||
),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
|
|
@ -256,7 +255,7 @@ fun MfaScreen(
|
|||
label = stringResource(R.string.mfa_totp_code),
|
||||
onChange = viewModel::setTotpCode,
|
||||
type = KeyboardType.Number,
|
||||
value = viewModel.totpCode,
|
||||
value = viewModel.totpCode
|
||||
)
|
||||
|
||||
Button(
|
||||
|
|
@ -267,7 +266,7 @@ fun MfaScreen(
|
|||
.testTag("do_totp_button")
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.next),
|
||||
text = stringResource(R.string.next)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -300,7 +299,7 @@ fun MfaScreen(
|
|||
),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
|
|
@ -310,7 +309,7 @@ fun MfaScreen(
|
|||
FormTextField(
|
||||
label = stringResource(R.string.mfa_recovery_code),
|
||||
onChange = viewModel::setRecoveryCode,
|
||||
value = viewModel.recoveryCode,
|
||||
value = viewModel.recoveryCode
|
||||
)
|
||||
|
||||
Button(
|
||||
|
|
@ -321,7 +320,7 @@ fun MfaScreen(
|
|||
.testTag("do_mfa_recovery_button")
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.next),
|
||||
text = stringResource(R.string.next)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ fun OnboardingScreen(navController: NavController) {
|
|||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 10.dp)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
|
@ -95,7 +95,7 @@ fun OnboardingScreen(navController: NavController) {
|
|||
),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 10.dp)
|
||||
|
|
@ -111,7 +111,7 @@ fun OnboardingScreen(navController: NavController) {
|
|||
),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 10.dp)
|
||||
|
|
@ -127,7 +127,7 @@ fun OnboardingScreen(navController: NavController) {
|
|||
),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 10.dp)
|
||||
|
|
@ -145,7 +145,7 @@ fun OnboardingScreen(navController: NavController) {
|
|||
FormTextField(
|
||||
value = username.value,
|
||||
onChange = { username.value = it },
|
||||
label = stringResource(R.string.onboarding_username),
|
||||
label = stringResource(R.string.onboarding_username)
|
||||
)
|
||||
|
||||
if (error.value.isNotBlank()) {
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class RegisterDetailsScreenViewModel : ViewModel() {
|
|||
val result = register(body)
|
||||
|
||||
if (result.ok) {
|
||||
navController.navigate("register/verify/${email}")
|
||||
navController.navigate("register/verify/$email")
|
||||
} else {
|
||||
error = result.unwrapError().type
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ fun RegisterDetailsScreen(
|
|||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 10.dp)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
|
@ -143,7 +143,7 @@ fun RegisterDetailsScreen(
|
|||
),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 10.dp)
|
||||
|
|
@ -161,7 +161,7 @@ fun RegisterDetailsScreen(
|
|||
FormTextField(
|
||||
value = viewModel.email,
|
||||
onChange = { viewModel.email = it },
|
||||
label = stringResource(R.string.register_email),
|
||||
label = stringResource(R.string.register_email)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.register_email_verification_hint),
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ fun RegisterGreetingScreen(navController: NavController) {
|
|||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
|
@ -74,7 +74,7 @@ fun RegisterGreetingScreen(navController: NavController) {
|
|||
),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
|
|
@ -99,6 +99,3 @@ fun RegisterGreetingScreen(navController: NavController) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@ import chat.revolt.R
|
|||
@Composable
|
||||
fun RegisterVerifyScreen(navController: NavController, email: String) {
|
||||
val intentLauncher =
|
||||
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {}
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) {}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
@ -53,7 +55,7 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
|
|||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
|
@ -65,7 +67,7 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
|
|||
),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp)
|
||||
|
|
@ -81,7 +83,7 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
|
|||
),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp)
|
||||
|
|
@ -100,6 +102,3 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ fun AppearanceSettingsScreen(
|
|||
showBackButton = true,
|
||||
onBackButtonClicked = {
|
||||
navController.popBackStack()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
@ -80,13 +81,13 @@ fun AppearanceSettingsScreen(
|
|||
|
||||
FlowRow(
|
||||
mainAxisSpacing = 10.dp,
|
||||
crossAxisSpacing = 10.dp,
|
||||
crossAxisSpacing = 10.dp
|
||||
) {
|
||||
ThemeChip(
|
||||
color = Color(0xff1c243c),
|
||||
text = stringResource(id = R.string.settings_appearance_theme_revolt),
|
||||
selected = GlobalState.theme == Theme.Revolt,
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_revolt"),
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_revolt")
|
||||
) {
|
||||
setNewTheme(Theme.Revolt)
|
||||
}
|
||||
|
|
@ -95,7 +96,7 @@ fun AppearanceSettingsScreen(
|
|||
color = Color(0xfff7f7f7),
|
||||
text = stringResource(id = R.string.settings_appearance_theme_light),
|
||||
selected = GlobalState.theme == Theme.Light,
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_light"),
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_light")
|
||||
) {
|
||||
setNewTheme(Theme.Light)
|
||||
}
|
||||
|
|
@ -104,7 +105,7 @@ fun AppearanceSettingsScreen(
|
|||
color = Color(0xff000000),
|
||||
text = stringResource(id = R.string.settings_appearance_theme_amoled),
|
||||
selected = GlobalState.theme == Theme.Amoled,
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_amoled"),
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_amoled")
|
||||
) {
|
||||
setNewTheme(Theme.Amoled)
|
||||
}
|
||||
|
|
@ -113,7 +114,7 @@ fun AppearanceSettingsScreen(
|
|||
color = if (isSystemInDarkTheme()) Color(0xff1c243c) else Color(0xfff7f7f7),
|
||||
text = stringResource(id = R.string.settings_appearance_theme_none),
|
||||
selected = GlobalState.theme == Theme.None,
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_none"),
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_none")
|
||||
) {
|
||||
setNewTheme(Theme.None)
|
||||
}
|
||||
|
|
@ -123,20 +124,24 @@ fun AppearanceSettingsScreen(
|
|||
color = dynamicDarkColorScheme(LocalContext.current).primary,
|
||||
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic),
|
||||
selected = GlobalState.theme == Theme.M3Dynamic,
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic"),
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic")
|
||||
) {
|
||||
setNewTheme(Theme.M3Dynamic)
|
||||
}
|
||||
} else {
|
||||
ThemeChip(
|
||||
color = Color(0xffa0a0a0),
|
||||
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic_unsupported),
|
||||
text = stringResource(
|
||||
id = R.string.settings_appearance_theme_m3dynamic_unsupported
|
||||
),
|
||||
selected = false,
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic_unsupported"),
|
||||
modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic_unsupported")
|
||||
) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.settings_appearance_theme_m3dynamic_unsupported_toast),
|
||||
context.getString(
|
||||
R.string.settings_appearance_theme_m3dynamic_unsupported_toast
|
||||
),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,8 @@ fun ChangelogsSettingsScreen(
|
|||
showBackButton = true,
|
||||
onBackButtonClicked = {
|
||||
navController.popBackStack()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
LazyColumn {
|
||||
items(
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ enum class UpdateState {
|
|||
NotChecked,
|
||||
Checking,
|
||||
RequestingUpdateToken,
|
||||
ErrorChecking,
|
||||
ErrorChecking
|
||||
}
|
||||
|
||||
fun viewUrlInBrowser(ctx: android.content.Context, url: String) {
|
||||
|
|
@ -77,7 +77,7 @@ fun viewUrlInBrowser(ctx: android.content.Context, url: String) {
|
|||
data class UpdaterBody(
|
||||
val author: String,
|
||||
@SerialName("current_build")
|
||||
val currentBuild: String,
|
||||
val currentBuild: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
@ -85,7 +85,7 @@ data class UpdaterResponse(
|
|||
val outdated: Boolean,
|
||||
@SerialName("newest_build")
|
||||
val newestBuild: Int?,
|
||||
val token: String?,
|
||||
val token: String?
|
||||
)
|
||||
|
||||
class ClosedBetaUpdaterScreenViewModel : ViewModel() {
|
||||
|
|
@ -98,7 +98,7 @@ class ClosedBetaUpdaterScreenViewModel : ViewModel() {
|
|||
|
||||
viewModelScope.launch {
|
||||
val outdatedResponse = RevoltHttp.post(
|
||||
"${BuildConfig.ANALYSIS_BASEURL}/api/distribution/android",
|
||||
"${BuildConfig.ANALYSIS_BASEURL}/api/distribution/android"
|
||||
) {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(
|
||||
|
|
@ -152,7 +152,8 @@ fun ClosedBetaUpdaterScreen(
|
|||
showBackButton = true,
|
||||
onBackButtonClicked = {
|
||||
navController.popBackStack()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
@ -210,7 +211,9 @@ fun ClosedBetaUpdaterScreen(
|
|||
modifier = Modifier.padding(vertical = 10.dp)
|
||||
)
|
||||
|
||||
AnimatedVisibility(visible = viewModel.updateState == UpdateState.UpdateAvailable) {
|
||||
AnimatedVisibility(
|
||||
visible = viewModel.updateState == UpdateState.UpdateAvailable
|
||||
) {
|
||||
ElevatedButton(onClick = {
|
||||
viewUrlInBrowser(
|
||||
ctx = context,
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ import androidx.navigation.NavController
|
|||
import chat.revolt.components.generic.PageHeader
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@HiltViewModel
|
||||
class DebugSettingsScreenViewModel @Inject constructor(
|
||||
private val kvStorage: KVStorage,
|
||||
private val kvStorage: KVStorage
|
||||
) : ViewModel() {
|
||||
fun forgetSidebarSparkShown() {
|
||||
viewModelScope.launch {
|
||||
|
|
@ -61,8 +61,8 @@ fun DebugSettingsScreen(
|
|||
showBackButton = true,
|
||||
onBackButtonClicked = {
|
||||
navController.popBackStack()
|
||||
})
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -34,8 +33,8 @@ import chat.revolt.components.generic.SheetClickable
|
|||
import chat.revolt.components.screens.settings.SelfUserOverview
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@HiltViewModel
|
||||
class SettingsScreenViewModel @Inject constructor(
|
||||
|
|
@ -65,7 +64,8 @@ fun SettingsScreen(
|
|||
showBackButton = true,
|
||||
onBackButtonClicked = {
|
||||
navController.popBackStack()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue