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
|
||||
|
|
@ -14,4 +14,4 @@ class RevoltApplication : Application() {
|
|||
instance = this
|
||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ fun InviteScreen(
|
|||
contentScale = ContentScale.Crop,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
.fillMaxSize()
|
||||
)
|
||||
|
||||
Box(
|
||||
|
|
@ -214,11 +214,10 @@ fun InviteScreen(
|
|||
?: stringResource(id = R.string.unknown),
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.clip(CircleShape),
|
||||
.clip(CircleShape)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
|
|
@ -227,7 +226,7 @@ fun InviteScreen(
|
|||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 24.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
|
@ -235,7 +234,7 @@ fun InviteScreen(
|
|||
Text(
|
||||
text = stringResource(id = R.string.invite_message),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
|
@ -270,10 +269,7 @@ fun InviteScreen(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun InvalidInviteError(
|
||||
error: RevoltError? = null,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
fun InvalidInviteError(error: RevoltError? = null, onDismissRequest: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
icon = {
|
||||
|
|
@ -287,11 +283,11 @@ fun InvalidInviteError(
|
|||
Text(
|
||||
text = stringResource(id = R.string.invite_error_header),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column() {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = when (error?.type) {
|
||||
|
|
@ -301,7 +297,7 @@ fun InvalidInviteError(
|
|||
}
|
||||
),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -333,15 +329,15 @@ fun NoInviteSpecifiedError(onDismissRequest: () -> Unit) {
|
|||
Text(
|
||||
text = stringResource(id = R.string.invite_error_header),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column() {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(id = R.string.invite_error_no_invite),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -356,4 +352,4 @@ fun NoInviteSpecifiedError(onDismissRequest: () -> Unit) {
|
|||
},
|
||||
confirmButton = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ fun AppEntrypoint(windowSizeClass: WindowSizeClass) {
|
|||
val navController = rememberNavController()
|
||||
|
||||
RevoltTheme(
|
||||
requestedTheme = GlobalState.theme,
|
||||
requestedTheme = GlobalState.theme
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
|
|||
|
|
@ -49,4 +49,4 @@ class WebChallengeActivity : AppCompatActivity() {
|
|||
|
||||
binding.webView.loadUrl(REVOLT_BASE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,10 +79,7 @@ class ImageViewActivity : ComponentActivity() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ImageViewScreen(
|
||||
resource: AutumnResource,
|
||||
onClose: () -> Unit = {}
|
||||
) {
|
||||
fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
|
||||
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
|
||||
|
||||
val context = LocalContext.current
|
||||
|
|
@ -182,7 +179,7 @@ fun ImageViewScreen(
|
|||
|
||||
RevoltTheme(requestedTheme = GlobalState.theme) {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
|
||||
) { pv ->
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
|
|
@ -191,10 +188,11 @@ fun ImageViewScreen(
|
|||
.fillMaxSize()
|
||||
) {
|
||||
Column {
|
||||
PageHeader(text = stringResource(
|
||||
id = R.string.media_viewer_title_image,
|
||||
resource.filename ?: resource.id!!
|
||||
),
|
||||
PageHeader(
|
||||
text = stringResource(
|
||||
id = R.string.media_viewer_title_image,
|
||||
resource.filename ?: resource.id!!
|
||||
),
|
||||
showBackButton = true,
|
||||
onBackButtonClicked = onClose,
|
||||
maxLines = 1,
|
||||
|
|
@ -213,13 +211,16 @@ fun ImageViewScreen(
|
|||
expanded = shareSubmenuIsOpen.value,
|
||||
onDismissRequest = {
|
||||
shareSubmenuIsOpen.value = false
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
shareUrl()
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(id = R.string.media_viewer_share_url))
|
||||
Text(
|
||||
stringResource(id = R.string.media_viewer_share_url)
|
||||
)
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
|
|
@ -227,7 +228,11 @@ fun ImageViewScreen(
|
|||
shareImage()
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(id = R.string.media_viewer_share_image))
|
||||
Text(
|
||||
stringResource(
|
||||
id = R.string.media_viewer_share_image
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -237,11 +242,14 @@ fun ImageViewScreen(
|
|||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_download_24dp),
|
||||
contentDescription = stringResource(id = R.string.media_viewer_save)
|
||||
contentDescription = stringResource(
|
||||
id = R.string.media_viewer_save
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -260,11 +268,11 @@ fun ImageViewScreen(
|
|||
},
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,10 +83,7 @@ class VideoViewActivity : ComponentActivity() {
|
|||
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
@Composable
|
||||
fun VideoViewScreen(
|
||||
resource: AutumnResource,
|
||||
onClose: () -> Unit = {}
|
||||
) {
|
||||
fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
|
||||
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
|
||||
|
||||
val context = LocalContext.current
|
||||
|
|
@ -198,7 +195,7 @@ fun VideoViewScreen(
|
|||
|
||||
RevoltTheme(requestedTheme = GlobalState.theme) {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
|
||||
) { pv ->
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
|
|
@ -207,10 +204,11 @@ fun VideoViewScreen(
|
|||
.fillMaxSize()
|
||||
) {
|
||||
Column {
|
||||
PageHeader(text = stringResource(
|
||||
id = R.string.media_viewer_title_video,
|
||||
resource.filename ?: resource.id!!
|
||||
),
|
||||
PageHeader(
|
||||
text = stringResource(
|
||||
id = R.string.media_viewer_title_video,
|
||||
resource.filename ?: resource.id!!
|
||||
),
|
||||
showBackButton = true,
|
||||
onBackButtonClicked = onClose,
|
||||
maxLines = 1,
|
||||
|
|
@ -229,13 +227,16 @@ fun VideoViewScreen(
|
|||
expanded = shareSubmenuIsOpen.value,
|
||||
onDismissRequest = {
|
||||
shareSubmenuIsOpen.value = false
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
shareUrl()
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(id = R.string.media_viewer_share_url))
|
||||
Text(
|
||||
stringResource(id = R.string.media_viewer_share_url)
|
||||
)
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
|
|
@ -243,7 +244,11 @@ fun VideoViewScreen(
|
|||
shareVideo()
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(id = R.string.media_viewer_share_video))
|
||||
Text(
|
||||
stringResource(
|
||||
id = R.string.media_viewer_share_video
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -253,11 +258,14 @@ fun VideoViewScreen(
|
|||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_download_24dp),
|
||||
contentDescription = stringResource(id = R.string.media_viewer_save)
|
||||
contentDescription = stringResource(
|
||||
id = R.string.media_viewer_save
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -275,11 +283,11 @@ fun VideoViewScreen(
|
|||
},
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import chat.revolt.api.internals.Members
|
|||
import chat.revolt.api.realtime.DisconnectionState
|
||||
import chat.revolt.api.realtime.RealtimeSocket
|
||||
import chat.revolt.api.routes.user.fetchSelf
|
||||
import chat.revolt.api.schemas.Channel as ChannelSchema
|
||||
import chat.revolt.api.schemas.Emoji
|
||||
import chat.revolt.api.schemas.Message
|
||||
import chat.revolt.api.schemas.Server
|
||||
|
|
@ -38,7 +39,6 @@ import kotlinx.coroutines.withContext
|
|||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import chat.revolt.api.schemas.Channel as ChannelSchema
|
||||
|
||||
const val REVOLT_BASE = "https://api.revolt.chat"
|
||||
const val REVOLT_SUPPORT = "https://support.revolt.chat"
|
||||
|
|
@ -218,4 +218,4 @@ object RevoltAPI {
|
|||
}
|
||||
|
||||
@Serializable
|
||||
data class RevoltError(val type: String)
|
||||
data class RevoltError(val type: String)
|
||||
|
|
|
|||
|
|
@ -50,4 +50,4 @@ object ChannelUtils {
|
|||
|
||||
return output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ object DirectMessages {
|
|||
fun hasPlatformModerationDM(): Boolean {
|
||||
return unreadDMs().any {
|
||||
it.channelType == ChannelType.DirectMessage &&
|
||||
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
||||
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlatformModerationDM(): Channel? {
|
||||
return unreadDMs().firstOrNull {
|
||||
it.channelType == ChannelType.DirectMessage &&
|
||||
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
||||
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,4 +38,4 @@ class Members {
|
|||
member.nickname?.let { userId to member.nickname }
|
||||
}?.toMap() ?: emptyMap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,30 +76,30 @@ object BitDefaults {
|
|||
|
||||
val Default =
|
||||
ViewOnly +
|
||||
PermissionBit.SendMessage +
|
||||
PermissionBit.InviteOthers +
|
||||
PermissionBit.SendEmbeds +
|
||||
PermissionBit.UploadFiles +
|
||||
PermissionBit.Connect +
|
||||
PermissionBit.Speak
|
||||
PermissionBit.SendMessage +
|
||||
PermissionBit.InviteOthers +
|
||||
PermissionBit.SendEmbeds +
|
||||
PermissionBit.UploadFiles +
|
||||
PermissionBit.Connect +
|
||||
PermissionBit.Speak
|
||||
|
||||
val SavedMessages =
|
||||
PermissionBit.GrantAllSafe.value
|
||||
|
||||
val DirectMessages =
|
||||
Default +
|
||||
PermissionBit.ManageChannel +
|
||||
PermissionBit.React
|
||||
PermissionBit.ManageChannel +
|
||||
PermissionBit.React
|
||||
|
||||
val Server =
|
||||
Default +
|
||||
PermissionBit.React +
|
||||
PermissionBit.ChangeNickname +
|
||||
PermissionBit.ChangeAvatar
|
||||
PermissionBit.React +
|
||||
PermissionBit.ChangeNickname +
|
||||
PermissionBit.ChangeAvatar
|
||||
|
||||
val Webhook =
|
||||
PermissionBit.SendMessage +
|
||||
PermissionBit.SendEmbeds +
|
||||
PermissionBit.Masquerade +
|
||||
PermissionBit.React
|
||||
}
|
||||
PermissionBit.SendEmbeds +
|
||||
PermissionBit.Masquerade +
|
||||
PermissionBit.React
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ object Roles {
|
|||
|
||||
ChannelType.TextChannel, ChannelType.VoiceChannel -> {
|
||||
val server = RevoltAPI.serverCache[channel.server]
|
||||
// FIXME this is a stupid patch to prevent it from showing "no permission" on a channel on launch
|
||||
// FIXME this is a stupid patch to prevent it from showing "no permission" on a channel on launch
|
||||
?: return PermissionBit.GrantAllSafe.value
|
||||
|
||||
if (server.owner == user?.id) return PermissionBit.GrantAllSafe.value
|
||||
|
|
@ -114,4 +114,4 @@ object Roles {
|
|||
null -> 0L
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,17 @@ import android.content.Context
|
|||
import android.graphics.RuntimeShader
|
||||
import android.os.Build
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush as AndroidBrush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ShaderBrush
|
||||
import org.intellij.lang.annotations.Language
|
||||
import androidx.compose.ui.graphics.Brush as AndroidBrush
|
||||
|
||||
object SpecialUsers {
|
||||
val PLATFORM_MODERATION_USER = "01FC17E1WTM2BGE4F3ARN3FDAF"
|
||||
|
||||
val TRUSTED_MODERATION_BOTS = listOf(
|
||||
"01GXBYCNQ52A9QYCQ99RBPXPAW", // AutoMod
|
||||
"01FCXRNNVW69AMSHBE61W1M5T3", // AutoMod Nightly
|
||||
"01FCXRNNVW69AMSHBE61W1M5T3" // AutoMod Nightly
|
||||
)
|
||||
|
||||
sealed class TeamMemberFlair {
|
||||
|
|
@ -40,7 +40,7 @@ object SpecialUsers {
|
|||
Color(0xFFFF9B55),
|
||||
Color(0xFFFFFFFF),
|
||||
Color(0xFFD461A6),
|
||||
Color(0xFFA50062),
|
||||
Color(0xFFA50062)
|
||||
),
|
||||
start = Offset.Zero,
|
||||
end = Offset.Infinite
|
||||
|
|
@ -51,7 +51,7 @@ object SpecialUsers {
|
|||
AndroidBrush.linearGradient(
|
||||
listOf(
|
||||
Color(0xFF68224F),
|
||||
Color(0xFFC68235),
|
||||
Color(0xFFC68235)
|
||||
),
|
||||
start = Offset.Zero,
|
||||
end = Offset.Infinite
|
||||
|
|
@ -75,7 +75,7 @@ object SpecialUsers {
|
|||
Color(0xFF1000AF)
|
||||
)
|
||||
)
|
||||
), // zomatree
|
||||
) // zomatree
|
||||
)
|
||||
|
||||
fun teamFlairAsBrush(context: Context, id: String): AndroidBrush? {
|
||||
|
|
@ -93,4 +93,4 @@ object SpecialUsers {
|
|||
null -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,35 +45,67 @@ object ULID {
|
|||
// Entropy part (16 chars)
|
||||
chars[10] = b32chars[(entropy[0].toShort() and 0xff).toInt().ushr(3)]
|
||||
chars[11] =
|
||||
b32chars[(entropy[0].toInt() shl 2 or (entropy[1].toShort() and 0xff).toInt()
|
||||
.ushr(6) and 0x1f)]
|
||||
b32chars[
|
||||
(
|
||||
entropy[0].toInt() shl 2 or (entropy[1].toShort() and 0xff).toInt()
|
||||
.ushr(6) and 0x1f
|
||||
)
|
||||
]
|
||||
chars[12] = b32chars[((entropy[1].toShort() and 0xff).toInt().ushr(1) and 0x1f)]
|
||||
chars[13] =
|
||||
b32chars[(entropy[1].toInt() shl 4 or (entropy[2].toShort() and 0xff).toInt()
|
||||
.ushr(4) and 0x1f)]
|
||||
b32chars[
|
||||
(
|
||||
entropy[1].toInt() shl 4 or (entropy[2].toShort() and 0xff).toInt()
|
||||
.ushr(4) and 0x1f
|
||||
)
|
||||
]
|
||||
chars[14] =
|
||||
b32chars[(entropy[2].toInt() shl 5 or (entropy[3].toShort() and 0xff).toInt()
|
||||
.ushr(7) and 0x1f)]
|
||||
b32chars[
|
||||
(
|
||||
entropy[2].toInt() shl 5 or (entropy[3].toShort() and 0xff).toInt()
|
||||
.ushr(7) and 0x1f
|
||||
)
|
||||
]
|
||||
chars[15] = b32chars[((entropy[3].toShort() and 0xff).toInt().ushr(2) and 0x1f)]
|
||||
chars[16] =
|
||||
b32chars[(entropy[3].toInt() shl 3 or (entropy[4].toShort() and 0xff).toInt()
|
||||
.ushr(5) and 0x1f)]
|
||||
b32chars[
|
||||
(
|
||||
entropy[3].toInt() shl 3 or (entropy[4].toShort() and 0xff).toInt()
|
||||
.ushr(5) and 0x1f
|
||||
)
|
||||
]
|
||||
chars[17] = b32chars[(entropy[4].toInt() and 0x1f)]
|
||||
chars[18] = b32chars[(entropy[5].toShort() and 0xff).toInt().ushr(3)]
|
||||
chars[19] =
|
||||
b32chars[(entropy[5].toInt() shl 2 or (entropy[6].toShort() and 0xff).toInt()
|
||||
.ushr(6) and 0x1f)]
|
||||
b32chars[
|
||||
(
|
||||
entropy[5].toInt() shl 2 or (entropy[6].toShort() and 0xff).toInt()
|
||||
.ushr(6) and 0x1f
|
||||
)
|
||||
]
|
||||
chars[20] = b32chars[((entropy[6].toShort() and 0xff).toInt().ushr(1) and 0x1f)]
|
||||
chars[21] =
|
||||
b32chars[(entropy[6].toInt() shl 4 or (entropy[7].toShort() and 0xff).toInt()
|
||||
.ushr(4) and 0x1f)]
|
||||
b32chars[
|
||||
(
|
||||
entropy[6].toInt() shl 4 or (entropy[7].toShort() and 0xff).toInt()
|
||||
.ushr(4) and 0x1f
|
||||
)
|
||||
]
|
||||
chars[22] =
|
||||
b32chars[(entropy[7].toInt() shl 5 or (entropy[8].toShort() and 0xff).toInt()
|
||||
.ushr(7) and 0x1f)]
|
||||
b32chars[
|
||||
(
|
||||
entropy[7].toInt() shl 5 or (entropy[8].toShort() and 0xff).toInt()
|
||||
.ushr(7) and 0x1f
|
||||
)
|
||||
]
|
||||
chars[23] = b32chars[((entropy[8].toShort() and 0xff).toInt().ushr(2) and 0x1f)]
|
||||
chars[24] =
|
||||
b32chars[(entropy[8].toInt() shl 3 or (entropy[9].toShort() and 0xff).toInt()
|
||||
.ushr(5) and 0x1f)]
|
||||
b32chars[
|
||||
(
|
||||
entropy[8].toInt() shl 3 or (entropy[9].toShort() and 0xff).toInt()
|
||||
.ushr(5) and 0x1f
|
||||
)
|
||||
]
|
||||
chars[25] = b32chars[(entropy[9].toInt() and 0x1f)]
|
||||
|
||||
return String(chars)
|
||||
|
|
@ -109,4 +141,4 @@ object ULID {
|
|||
|
||||
return timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -199,4 +199,4 @@ object WebCompat {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -429,4 +429,4 @@ object RealtimeSocket {
|
|||
private suspend fun pushReconnectEvent() {
|
||||
RevoltAPI.wsFrameChannel.send(RealtimeSocketFrames.Reconnected())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
@ -36,4 +36,4 @@ suspend fun register(body: RegistrationBody): RsResult<Unit, RevoltError> {
|
|||
}
|
||||
|
||||
return RsResult.ok(Unit)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,12 +70,12 @@ data class SendMessageBody(
|
|||
val content: String,
|
||||
val nonce: String = ULID.makeNext(),
|
||||
val replies: List<SendMessageReply> = emptyList(),
|
||||
val attachments: List<String>?,
|
||||
val attachments: List<String>?
|
||||
)
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
data class EditMessageBody(
|
||||
val content: String?,
|
||||
val content: String?
|
||||
)
|
||||
|
||||
suspend fun sendMessage(
|
||||
|
|
@ -83,7 +83,7 @@ suspend fun sendMessage(
|
|||
content: String,
|
||||
nonce: String? = ULID.makeNext(),
|
||||
replies: List<SendMessageReply>? = null,
|
||||
attachments: List<String>? = null,
|
||||
attachments: List<String>? = null
|
||||
): String {
|
||||
val response = RevoltHttp.post("/channels/$channelId/messages") {
|
||||
contentType(ContentType.Application.Json)
|
||||
|
|
@ -101,11 +101,7 @@ suspend fun sendMessage(
|
|||
return response
|
||||
}
|
||||
|
||||
suspend fun editMessage(
|
||||
channelId: String,
|
||||
messageId: String,
|
||||
newContent: String? = null,
|
||||
) {
|
||||
suspend fun editMessage(channelId: String, messageId: String, newContent: String? = null) {
|
||||
val response = RevoltHttp.patch("/channels/$channelId/messages/$messageId") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(
|
||||
|
|
@ -146,4 +142,4 @@ suspend fun fetchGroupParticipants(channelId: String): List<User> {
|
|||
ListSerializer(User.serializer()),
|
||||
response
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@ suspend fun fetchEmoji(id: String): Emoji {
|
|||
Emoji.serializer(),
|
||||
response
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,4 +39,4 @@ suspend fun joinInviteByCode(code: String): RsResult<InviteJoined, RevoltError>
|
|||
|
||||
val invite = RevoltJson.decodeFromString(InviteJoined.serializer(), response)
|
||||
return RsResult.ok(invite)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,14 +35,20 @@ suspend fun uploadToAutumn(
|
|||
val uploadUrl = "$REVOLT_FILES/$tag"
|
||||
|
||||
val response = RevoltHttp.post(uploadUrl) {
|
||||
setBody(MultiPartFormDataContent(
|
||||
formData {
|
||||
append("file", file.readBytes(), Headers.build {
|
||||
append(HttpHeaders.ContentType, contentType.toString())
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"$name\"")
|
||||
})
|
||||
}
|
||||
))
|
||||
setBody(
|
||||
MultiPartFormDataContent(
|
||||
formData {
|
||||
append(
|
||||
"file",
|
||||
file.readBytes(),
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, contentType.toString())
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"$name\"")
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
onUpload { bytesSentTotal, contentLength ->
|
||||
onProgress(bytesSentTotal, contentLength)
|
||||
}
|
||||
|
|
@ -59,5 +65,4 @@ suspend fun uploadToAutumn(
|
|||
throw Exception("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,4 +51,4 @@ data class Voso(
|
|||
|
||||
suspend fun getRootRoute(): Root {
|
||||
return RevoltHttp.get("/").body()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ data class OnboardingResponse(
|
|||
val onboarding: Boolean
|
||||
)
|
||||
|
||||
|
||||
suspend fun needsOnboarding(
|
||||
sessionToken: String = RevoltAPI.sessionToken,
|
||||
): Boolean {
|
||||
suspend fun needsOnboarding(sessionToken: String = RevoltAPI.sessionToken): Boolean {
|
||||
val response = RevoltHttp.get("/onboard/hello") {
|
||||
header(RevoltAPI.TOKEN_HEADER_NAME, sessionToken)
|
||||
}
|
||||
|
|
@ -36,12 +33,12 @@ suspend fun needsOnboarding(
|
|||
|
||||
@Serializable
|
||||
data class OnboardingCompletionBody(
|
||||
val username: String,
|
||||
val username: String
|
||||
)
|
||||
|
||||
suspend fun completeOnboarding(
|
||||
body: OnboardingCompletionBody,
|
||||
sessionToken: String = RevoltAPI.sessionToken,
|
||||
sessionToken: String = RevoltAPI.sessionToken
|
||||
): RsResult<Unit, RevoltError> {
|
||||
val response = RevoltHttp.post("/onboard/complete") {
|
||||
setBody(body)
|
||||
|
|
@ -67,4 +64,4 @@ suspend fun completeOnboarding(
|
|||
}
|
||||
|
||||
return RsResult.ok(Unit)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ suspend fun putMessageReport(
|
|||
report_reason = reason,
|
||||
id = messageId
|
||||
),
|
||||
additional_context = additionalContext,
|
||||
additional_context = additionalContext
|
||||
)
|
||||
|
||||
val response = RevoltHttp.post("/safety/report") {
|
||||
|
|
@ -57,9 +57,9 @@ suspend fun putServerReport(
|
|||
content = ServerReport(
|
||||
type = "Server",
|
||||
report_reason = reason,
|
||||
id = serverId,
|
||||
id = serverId
|
||||
),
|
||||
additional_context = additionalContext,
|
||||
additional_context = additionalContext
|
||||
)
|
||||
|
||||
val response = RevoltHttp.post("/safety/report") {
|
||||
|
|
@ -89,9 +89,9 @@ suspend fun putUserReport(
|
|||
content = UserReport(
|
||||
type = "User",
|
||||
report_reason = reason,
|
||||
id = userId,
|
||||
id = userId
|
||||
),
|
||||
additional_context = additionalContext,
|
||||
additional_context = additionalContext
|
||||
)
|
||||
|
||||
val response = RevoltHttp.post("/safety/report") {
|
||||
|
|
@ -110,4 +110,4 @@ suspend fun putUserReport(
|
|||
} catch (e: SerializationException) {
|
||||
// Not an error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,4 +87,4 @@ suspend fun leaveOrDeleteServer(serverId: String, leaveSilently: Boolean = false
|
|||
RevoltHttp.delete("/servers/$serverId") {
|
||||
parameter("leave_silently", leaveSilently)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,4 +69,4 @@ suspend fun setKey(key: String, value: String) {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,4 +15,4 @@ suspend fun syncUnreads(): List<ChannelUnreadResponse> {
|
|||
ListSerializer(ChannelUnreadResponse.serializer()),
|
||||
response
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import chat.revolt.api.RevoltJson
|
|||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.put
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlin.collections.set
|
||||
import kotlinx.serialization.SerializationException
|
||||
|
||||
suspend fun blockUser(userId: String) {
|
||||
val response = RevoltHttp.put("/users/$userId/block")
|
||||
|
|
@ -38,4 +38,4 @@ suspend fun unblockUser(userId: String) {
|
|||
|
||||
val user = RevoltAPI.userCache[userId] ?: return
|
||||
RevoltAPI.userCache[userId] = user.copy(relationship = "None")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,4 +81,4 @@ suspend fun fetchUserProfile(id: String): Profile {
|
|||
}
|
||||
|
||||
return RevoltJson.decodeFromString(Profile.serializer(), response)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,11 +90,11 @@ data class Emoji(
|
|||
val name: String? = null,
|
||||
val animated: Boolean? = null,
|
||||
val nsfw: Boolean? = null,
|
||||
val type: String? = null, // this is _only_ used for websocket events!
|
||||
val type: String? = null // this is _only_ used for websocket events!
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EmojiParent(
|
||||
val type: String? = null,
|
||||
val id: String? = null
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,4 +49,4 @@ data class RsResult<V, E>(val value: V?, val error: E?) {
|
|||
return RsResult(null, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,4 +16,4 @@ object GlobalState {
|
|||
fun reset() {
|
||||
theme = getDefaultTheme()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,4 +48,4 @@ object SyncedSettings {
|
|||
_android.value = value
|
||||
setKey("android", RevoltJson.encodeToString(AndroidSpecificSettings.serializer(), value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,15 @@ class Unreads {
|
|||
|
||||
suspend fun sync() {
|
||||
channels.clear()
|
||||
channels.putAll(syncUnreads().associate {
|
||||
it.id.channel to ChannelUnread(
|
||||
id = it.id.channel,
|
||||
last_id = it.last_id,
|
||||
mentions = it.mentions
|
||||
)
|
||||
})
|
||||
channels.putAll(
|
||||
syncUnreads().associate {
|
||||
it.id.channel to ChannelUnread(
|
||||
id = it.id.channel,
|
||||
last_id = it.last_id,
|
||||
mentions = it.mentions
|
||||
)
|
||||
}
|
||||
)
|
||||
hasLoaded.value = true
|
||||
}
|
||||
|
||||
|
|
@ -83,4 +85,4 @@ class Unreads {
|
|||
channels.clear()
|
||||
hasLoaded.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@ sealed class Action {
|
|||
|
||||
val ActionChannel = Channel<Action>(
|
||||
Channel.BUFFERED
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ object UiCallbacks {
|
|||
suspend fun editMessage(messageId: String) {
|
||||
uiCallbackFlow.emit(UiCallback.EditMessage(messageId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import chat.revolt.ui.theme.Theme
|
|||
private val NON_MATERIAL_COLOURS = mapOf(
|
||||
DisconnectionState.Disconnected to (Color(0xff4E0C0C) to Color(0xffff1744)),
|
||||
DisconnectionState.Reconnecting to (Color(0xff5B5300) to Color(0xffffea00)),
|
||||
DisconnectionState.Connected to (Color(0xff0E2F10) to Color(0xff00e676)),
|
||||
DisconnectionState.Connected to (Color(0xff0E2F10) to Color(0xff00e676))
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
@ -50,7 +50,7 @@ private fun DisconnectedNoticeBase(
|
|||
.fillMaxWidth()
|
||||
.background(background)
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
|
|
@ -75,10 +75,7 @@ private fun DisconnectedNoticeBase(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun DisconnectedNotice(
|
||||
state: DisconnectionState,
|
||||
onReconnect: () -> Unit
|
||||
) {
|
||||
fun DisconnectedNotice(state: DisconnectionState, onReconnect: () -> Unit) {
|
||||
val retries = remember { mutableStateOf(0) }
|
||||
|
||||
LaunchedEffect(state) {
|
||||
|
|
@ -101,7 +98,7 @@ fun DisconnectedNotice(
|
|||
val materialColours = mapOf(
|
||||
DisconnectionState.Disconnected to (MaterialTheme.colorScheme.error to MaterialTheme.colorScheme.onError),
|
||||
DisconnectionState.Reconnecting to (MaterialTheme.colorScheme.secondary to MaterialTheme.colorScheme.onSecondary),
|
||||
DisconnectionState.Connected to (MaterialTheme.colorScheme.primary to MaterialTheme.colorScheme.onPrimary),
|
||||
DisconnectionState.Connected to (MaterialTheme.colorScheme.primary to MaterialTheme.colorScheme.onPrimary)
|
||||
)
|
||||
|
||||
val (background, foreground) = when (GlobalState.theme) {
|
||||
|
|
@ -123,14 +120,14 @@ fun DisconnectedNotice(
|
|||
background = background,
|
||||
foreground = foreground,
|
||||
icon = Icons.Default.Refresh,
|
||||
text = stringResource(id = R.string.reconnecting),
|
||||
text = stringResource(id = R.string.reconnecting)
|
||||
)
|
||||
|
||||
DisconnectionState.Connected -> DisconnectedNoticeBase(
|
||||
background = background,
|
||||
foreground = foreground,
|
||||
icon = Icons.Default.Done,
|
||||
text = stringResource(id = R.string.reconnected),
|
||||
text = stringResource(id = R.string.reconnected)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -160,4 +157,4 @@ private fun ReconnectedNoticePreview() {
|
|||
state = DisconnectionState.Connected,
|
||||
onReconnect = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ import chat.revolt.api.internals.WebCompat
|
|||
import chat.revolt.api.internals.solidColor
|
||||
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
|
||||
import chat.revolt.api.schemas.Embed
|
||||
import chat.revolt.api.schemas.Embed as EmbedSchema
|
||||
import chat.revolt.components.generic.RemoteImage
|
||||
import chat.revolt.components.generic.UIMarkdown
|
||||
import chat.revolt.api.schemas.Embed as EmbedSchema
|
||||
|
||||
@Composable
|
||||
fun RegularEmbed(
|
||||
|
|
@ -66,12 +66,14 @@ fun RegularEmbed(
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.then(
|
||||
if (embed.originalURL != null)
|
||||
if (embed.originalURL != null) {
|
||||
Modifier
|
||||
.clickable {
|
||||
onLinkClick(embed.originalURL)
|
||||
}
|
||||
else Modifier
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
|
|
@ -116,9 +118,13 @@ fun RegularEmbed(
|
|||
modifier = Modifier
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.then(
|
||||
if (embed.originalURL != null) Modifier.clickable {
|
||||
onLinkClick(embed.originalURL)
|
||||
} else Modifier
|
||||
if (embed.originalURL != null) {
|
||||
Modifier.clickable {
|
||||
onLinkClick(embed.originalURL)
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.aspectRatio(
|
||||
(it.width?.toFloat() ?: 0f) / (it.height?.toFloat() ?: 0f)
|
||||
|
|
@ -130,18 +136,13 @@ fun RegularEmbed(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Embed(
|
||||
embed: Embed,
|
||||
modifier: Modifier = Modifier,
|
||||
onLinkClick: (String) -> Unit
|
||||
) {
|
||||
fun Embed(embed: Embed, modifier: Modifier = Modifier, onLinkClick: (String) -> Unit) {
|
||||
Column {
|
||||
when (embed.type) {
|
||||
else -> RegularEmbed(embed = embed, modifier = modifier, onLinkClick = onLinkClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ fun InReplyTo(
|
|||
messageId: String,
|
||||
modifier: Modifier = Modifier,
|
||||
withMention: Boolean = false,
|
||||
onMessageClick: (String) -> Unit = { _ -> },
|
||||
onMessageClick: (String) -> Unit = { _ -> }
|
||||
) {
|
||||
val message = RevoltAPI.messageCache[messageId]
|
||||
val author = RevoltAPI.userCache[message?.author ?: ""]
|
||||
|
|
@ -116,4 +116,4 @@ fun InReplyTo(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ enum class InlineBadge {
|
|||
fun InlineBadge(
|
||||
badge: InlineBadge,
|
||||
modifier: Modifier = Modifier,
|
||||
colour: Color = Color.Unspecified,
|
||||
colour: Color = Color.Unspecified
|
||||
) {
|
||||
when (badge) {
|
||||
InlineBadge.Bot -> Icon(
|
||||
|
|
@ -62,7 +62,7 @@ fun InlineBadges(
|
|||
teamMember: Boolean = false,
|
||||
colour: Color = Color.Unspecified,
|
||||
precedingIfAny: @Composable () -> Unit = {},
|
||||
followingIfAny: @Composable () -> Unit = {},
|
||||
followingIfAny: @Composable () -> Unit = {}
|
||||
) {
|
||||
val hasBadges = bot || bridge || platformModeration || teamMember
|
||||
|
||||
|
|
@ -104,4 +104,4 @@ fun InlineBadges(
|
|||
if (hasBadges) {
|
||||
followingIfAny()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,11 +64,11 @@ import chat.revolt.api.internals.WebCompat
|
|||
import chat.revolt.api.internals.solidColor
|
||||
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
|
||||
import chat.revolt.api.schemas.AutumnResource
|
||||
import chat.revolt.api.schemas.Message as MessageSchema
|
||||
import chat.revolt.api.schemas.User
|
||||
import chat.revolt.components.generic.UserAvatar
|
||||
import chat.revolt.components.generic.UserAvatarWidthPlaceholder
|
||||
import chat.revolt.internals.markdown.LongClickableSpan
|
||||
import chat.revolt.api.schemas.Message as MessageSchema
|
||||
|
||||
@Composable
|
||||
fun authorColour(message: MessageSchema): Brush {
|
||||
|
|
@ -132,10 +132,7 @@ fun viewAttachmentInBrowser(ctx: android.content.Context, attachment: AutumnReso
|
|||
viewUrlInBrowser(ctx, url)
|
||||
}
|
||||
|
||||
|
||||
fun formatLongAsTime(
|
||||
time: Long
|
||||
): String {
|
||||
fun formatLongAsTime(time: Long): String {
|
||||
val date = java.util.Date(time)
|
||||
|
||||
val withinLastWeek = System.currentTimeMillis() - time < 604800000
|
||||
|
|
@ -167,7 +164,7 @@ fun Message(
|
|||
onMessageContextMenu: () -> Unit = {},
|
||||
onAvatarClick: () -> Unit = {},
|
||||
canReply: Boolean = false,
|
||||
onReply: () -> Unit = {},
|
||||
onReply: () -> Unit = {}
|
||||
) {
|
||||
val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator()
|
||||
val context = LocalContext.current
|
||||
|
|
@ -177,7 +174,8 @@ fun Message(
|
|||
contract = ActivityResultContracts.StartActivityForResult(),
|
||||
onResult = {
|
||||
// do nothing
|
||||
})
|
||||
}
|
||||
)
|
||||
Column {
|
||||
if (message.tail == false) {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
|
@ -237,7 +235,7 @@ fun Message(
|
|||
userId = author.id ?: message.id ?: ULID.makeSpecial(0),
|
||||
avatar = author.avatar,
|
||||
rawUrl = authorAvatarUrl(message),
|
||||
onClick = onAvatarClick,
|
||||
onClick = onAvatarClick
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -251,7 +249,7 @@ fun Message(
|
|||
text = authorName(message),
|
||||
style = LocalTextStyle.current.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
brush = authorColour(message),
|
||||
brush = authorColour(message)
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
|
|
@ -285,7 +283,9 @@ fun Message(
|
|||
Icon(
|
||||
imageVector = Icons.Default.Edit,
|
||||
contentDescription = stringResource(id = R.string.edited),
|
||||
tint = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.5f),
|
||||
tint = MaterialTheme.colorScheme.onBackground.copy(
|
||||
alpha = 0.5f
|
||||
),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
|
|
@ -343,7 +343,8 @@ fun Message(
|
|||
x.toFloat()
|
||||
)
|
||||
val link = spannedString.getSpans(
|
||||
off, off,
|
||||
off,
|
||||
off,
|
||||
LongClickableSpan::class.java
|
||||
)
|
||||
if (link.isNotEmpty()) {
|
||||
|
|
@ -443,4 +444,4 @@ fun Message(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,11 +45,11 @@ fun FileAttachment(attachment: AutumnResource) {
|
|||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
|
||||
.padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_file_24dp),
|
||||
contentDescription = null,
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
|
@ -58,13 +58,13 @@ fun FileAttachment(attachment: AutumnResource) {
|
|||
Text(
|
||||
text = attachment.filename ?: "File",
|
||||
maxLines = 1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = Formatter.formatShortFileSize(context, attachment.size ?: 0),
|
||||
maxLines = 1,
|
||||
color = LocalContentColor.current.copy(alpha = 0.7f),
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -80,8 +80,10 @@ fun ImageAttachment(attachment: AutumnResource) {
|
|||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()),
|
||||
description = attachment.filename ?: "Image",
|
||||
.aspectRatio(
|
||||
attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()
|
||||
),
|
||||
description = attachment.filename ?: "Image"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -100,8 +102,10 @@ fun VideoAttachment(attachment: AutumnResource) {
|
|||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()),
|
||||
description = attachment.filename ?: "Video",
|
||||
.aspectRatio(
|
||||
attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()
|
||||
),
|
||||
description = attachment.filename ?: "Video"
|
||||
)
|
||||
|
||||
Box(
|
||||
|
|
@ -109,7 +113,7 @@ fun VideoAttachment(attachment: AutumnResource) {
|
|||
.width(48.dp)
|
||||
.aspectRatio(1f)
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)),
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
|
||||
)
|
||||
|
||||
Icon(
|
||||
|
|
@ -117,7 +121,7 @@ fun VideoAttachment(attachment: AutumnResource) {
|
|||
contentDescription = stringResource(id = R.string.media_viewer_play),
|
||||
modifier = Modifier
|
||||
.width(32.dp)
|
||||
.aspectRatio(1f),
|
||||
.aspectRatio(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -128,7 +132,7 @@ fun AudioAttachment(attachment: AutumnResource) {
|
|||
AudioPlayer(
|
||||
url = url,
|
||||
filename = attachment.filename ?: "Audio",
|
||||
contentType = attachment.metadata?.type ?: "audio/mpeg",
|
||||
contentType = attachment.metadata?.type ?: "audio/mpeg"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -158,4 +162,4 @@ fun MessageAttachment(attachment: AutumnResource, onAttachmentClick: (AutumnReso
|
|||
else -> FileAttachment(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ fun MessageField(
|
|||
disabled: Boolean = false,
|
||||
editMode: Boolean = false,
|
||||
cancelEdit: () -> Unit = {},
|
||||
onFocusChange: (Boolean) -> Unit = {},
|
||||
onFocusChange: (Boolean) -> Unit = {}
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val placeholderResource = when (channelType) {
|
||||
|
|
@ -126,7 +126,7 @@ fun MessageField(
|
|||
channelName
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
colors = TextFieldDefaults.colors(
|
||||
|
|
@ -139,7 +139,7 @@ fun MessageField(
|
|||
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||
1.dp
|
||||
),
|
||||
focusedContainerColor = Color.Transparent,
|
||||
focusedContainerColor = Color.Transparent
|
||||
),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
leadingIcon = {
|
||||
|
|
@ -185,7 +185,8 @@ fun MessageField(
|
|||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
AnimatedVisibility(sendButtonVisible,
|
||||
AnimatedVisibility(
|
||||
sendButtonVisible,
|
||||
enter = expandIn(initialSize = { full ->
|
||||
IntSize(
|
||||
0,
|
||||
|
|
@ -203,7 +204,8 @@ fun MessageField(
|
|||
}) + slideOutHorizontally(
|
||||
animationSpec = RevoltTweenInt,
|
||||
targetOffsetX = { it }
|
||||
) + fadeOut(animationSpec = RevoltTweenFloat)) {
|
||||
) + fadeOut(animationSpec = RevoltTweenFloat)
|
||||
) {
|
||||
Icon(
|
||||
when {
|
||||
editMode -> Icons.Default.Edit
|
||||
|
|
@ -240,4 +242,4 @@ fun MessageFieldPreview() {
|
|||
channelType = ChannelType.TextChannel,
|
||||
channelName = "general"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,16 +13,12 @@ import androidx.compose.ui.graphics.Brush
|
|||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun RoleChip(
|
||||
label: String,
|
||||
brush: Brush,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun RoleChip(label: String, brush: Brush, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.background(
|
||||
brush = brush,
|
||||
brush = brush
|
||||
)
|
||||
.background(
|
||||
// darken the background a bit
|
||||
|
|
@ -37,4 +33,4 @@ fun RoleChip(
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,13 +39,11 @@ enum class SystemMessageType(val type: String) {
|
|||
USER_KICKED("user_kicked"),
|
||||
USER_LEFT("user_left"),
|
||||
USER_JOINED("user_joined"),
|
||||
TEXT("text"),
|
||||
TEXT("text")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SystemMessage(
|
||||
message: Message
|
||||
) {
|
||||
fun SystemMessage(message: Message) {
|
||||
if (message.system == null) return
|
||||
|
||||
val systemMessageType =
|
||||
|
|
@ -72,7 +70,9 @@ fun SystemMessage(
|
|||
|
||||
when (systemMessageType) {
|
||||
SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> {
|
||||
Text(text = "Channel ownership changed from ${message.system.from} to ${message.system.to}")
|
||||
Text(
|
||||
text = "Channel ownership changed from ${message.system.from} to ${message.system.to}"
|
||||
)
|
||||
}
|
||||
|
||||
SystemMessageType.CHANNEL_ICON_CHANGED -> {
|
||||
|
|
@ -120,11 +120,7 @@ fun SystemMessage(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun SystemMessageIcon(
|
||||
type: SystemMessageType,
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 24.dp
|
||||
) {
|
||||
fun SystemMessageIcon(type: SystemMessageType, modifier: Modifier = Modifier, size: Dp = 24.dp) {
|
||||
when (type) {
|
||||
SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> {
|
||||
Icon(
|
||||
|
|
@ -138,7 +134,9 @@ fun SystemMessageIcon(
|
|||
SystemMessageType.CHANNEL_ICON_CHANGED -> {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_image_multiple_24dp),
|
||||
contentDescription = stringResource(R.string.system_message_channel_icon_changed_alt),
|
||||
contentDescription = stringResource(
|
||||
R.string.system_message_channel_icon_changed_alt
|
||||
),
|
||||
tint = LocalContentColor.current,
|
||||
modifier = modifier.size(size)
|
||||
)
|
||||
|
|
@ -147,7 +145,9 @@ fun SystemMessageIcon(
|
|||
SystemMessageType.CHANNEL_DESCRIPTION_CHANGED -> {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_text_box_multiple_24dp),
|
||||
contentDescription = stringResource(R.string.system_message_channel_description_changed_alt),
|
||||
contentDescription = stringResource(
|
||||
R.string.system_message_channel_description_changed_alt
|
||||
),
|
||||
tint = LocalContentColor.current,
|
||||
modifier = modifier.size(size)
|
||||
)
|
||||
|
|
@ -242,4 +242,4 @@ fun SystemMessageIconWithBackground(
|
|||
) {
|
||||
SystemMessageIcon(type = type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,7 @@ import androidx.compose.ui.unit.sp
|
|||
import chat.revolt.R
|
||||
|
||||
@Composable
|
||||
fun TimeRift(
|
||||
modifier: Modifier = Modifier,
|
||||
onMessageLoad: () -> Unit,
|
||||
) {
|
||||
fun TimeRift(modifier: Modifier = Modifier, onMessageLoad: () -> Unit) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(vertical = 10.dp)
|
||||
|
|
@ -68,4 +65,4 @@ fun TimeRift(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,9 +81,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun EmojiPicker(
|
||||
onEmojiSelected: (String) -> Unit,
|
||||
) {
|
||||
fun EmojiPicker(onEmojiSelected: (String) -> Unit) {
|
||||
val view = LocalView.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
|
|
@ -104,7 +102,9 @@ fun EmojiPicker(
|
|||
derivedStateOf {
|
||||
val firstVisible = gridState.firstVisibleItemIndex
|
||||
val firstCategory =
|
||||
categorySpans.entries.firstOrNull { it.value.first <= firstVisible && it.value.second >= firstVisible }?.key
|
||||
categorySpans.entries.firstOrNull {
|
||||
it.value.first <= firstVisible && it.value.second >= firstVisible
|
||||
}?.key
|
||||
|
||||
firstCategory
|
||||
}
|
||||
|
|
@ -234,10 +234,14 @@ fun EmojiPicker(
|
|||
.padding(4.dp)
|
||||
.size(24.dp)
|
||||
.then(
|
||||
if (searchQuery.isNotEmpty()) Modifier.clickable {
|
||||
searchQuery = ""
|
||||
focusManager.clearFocus() // this prevents the text field Z-below from gaining focus
|
||||
} else Modifier
|
||||
if (searchQuery.isNotEmpty()) {
|
||||
Modifier.clickable {
|
||||
searchQuery = ""
|
||||
focusManager.clearFocus() // this prevents the text field Z-below from gaining focus
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.align(Alignment.CenterEnd)
|
||||
.alpha(clearQueryButtonOpacity.value)
|
||||
|
|
@ -259,7 +263,7 @@ fun EmojiPicker(
|
|||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
FitzpatrickSkinTone.entries.forEach { skinTone ->
|
||||
Text(
|
||||
|
|
@ -273,12 +277,24 @@ fun EmojiPicker(
|
|||
.clip(CircleShape)
|
||||
.clickable(
|
||||
onClickLabel = when (skinTone) {
|
||||
FitzpatrickSkinTone.None -> stringResource(R.string.emoji_picker_skin_tone_none)
|
||||
FitzpatrickSkinTone.Light -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_1_2)
|
||||
FitzpatrickSkinTone.MediumLight -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_3)
|
||||
FitzpatrickSkinTone.Medium -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_4)
|
||||
FitzpatrickSkinTone.MediumDark -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_5)
|
||||
FitzpatrickSkinTone.Dark -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_6)
|
||||
FitzpatrickSkinTone.None -> stringResource(
|
||||
R.string.emoji_picker_skin_tone_none
|
||||
)
|
||||
FitzpatrickSkinTone.Light -> stringResource(
|
||||
R.string.emoji_picker_skin_tone_fitzpatrick_1_2
|
||||
)
|
||||
FitzpatrickSkinTone.MediumLight -> stringResource(
|
||||
R.string.emoji_picker_skin_tone_fitzpatrick_3
|
||||
)
|
||||
FitzpatrickSkinTone.Medium -> stringResource(
|
||||
R.string.emoji_picker_skin_tone_fitzpatrick_4
|
||||
)
|
||||
FitzpatrickSkinTone.MediumDark -> stringResource(
|
||||
R.string.emoji_picker_skin_tone_fitzpatrick_5
|
||||
)
|
||||
FitzpatrickSkinTone.Dark -> stringResource(
|
||||
R.string.emoji_picker_skin_tone_fitzpatrick_6
|
||||
)
|
||||
}
|
||||
) {
|
||||
currentSkinTone = skinTone
|
||||
|
|
@ -286,7 +302,7 @@ fun EmojiPicker(
|
|||
focusManager.clearFocus() // this prevents the text field Z-below from gaining focus
|
||||
}
|
||||
.aspectRatio(1f),
|
||||
textAlign = TextAlign.Center,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -323,7 +339,9 @@ fun EmojiPicker(
|
|||
} else {
|
||||
Icons.Default.KeyboardArrowRight
|
||||
},
|
||||
contentDescription = stringResource(R.string.emoji_picker_close_skin_tone_menu),
|
||||
contentDescription = stringResource(
|
||||
R.string.emoji_picker_close_skin_tone_menu
|
||||
),
|
||||
tint = LocalContentColor.current,
|
||||
modifier = Modifier
|
||||
.alpha(skinToneMenuCloseHintIconOpacity)
|
||||
|
|
@ -340,7 +358,7 @@ fun EmojiPicker(
|
|||
.horizontalScroll(categoryRowScrollState)
|
||||
.height(37.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
servers.forEach { server ->
|
||||
Column(
|
||||
|
|
@ -350,13 +368,17 @@ fun EmojiPicker(
|
|||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
scope.launch {
|
||||
val index =
|
||||
pickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server }
|
||||
pickerList.indexOfFirst {
|
||||
it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server
|
||||
}
|
||||
gridState.animateScrollToItem(index)
|
||||
}
|
||||
}
|
||||
.then(
|
||||
if (currentCategory.value is Category.ServerEmoteCategory && (currentCategory.value as Category.ServerEmoteCategory).server == server) {
|
||||
Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f))
|
||||
Modifier.background(
|
||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
|
@ -364,7 +386,7 @@ fun EmojiPicker(
|
|||
.aspectRatio(1f)
|
||||
.padding(4.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
if (server.icon == null) {
|
||||
IconPlaceholder(
|
||||
|
|
@ -393,13 +415,17 @@ fun EmojiPicker(
|
|||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
scope.launch {
|
||||
val index =
|
||||
pickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == category }
|
||||
pickerList.indexOfFirst {
|
||||
it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == category
|
||||
}
|
||||
gridState.animateScrollToItem(index)
|
||||
}
|
||||
}
|
||||
.then(
|
||||
if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
|
||||
Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f))
|
||||
Modifier.background(
|
||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
|
@ -407,24 +433,44 @@ fun EmojiPicker(
|
|||
.aspectRatio(1f)
|
||||
.padding(4.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
painter = when (category) {
|
||||
UnicodeEmojiSection.Smileys -> painterResource(R.drawable.ic_emoticon_24dp)
|
||||
UnicodeEmojiSection.People -> painterResource(R.drawable.ic_human_greeting_variant_24dp)
|
||||
UnicodeEmojiSection.Animals -> painterResource(R.drawable.ic_snake_24dp)
|
||||
UnicodeEmojiSection.Food -> painterResource(R.drawable.ic_glass_mug_variant_24dp)
|
||||
UnicodeEmojiSection.Travel -> painterResource(R.drawable.ic_train_bus_24dp)
|
||||
UnicodeEmojiSection.Activities -> painterResource(R.drawable.ic_skate_24dp)
|
||||
UnicodeEmojiSection.Objects -> painterResource(R.drawable.ic_table_chair_24dp)
|
||||
UnicodeEmojiSection.Symbols -> painterResource(R.drawable.ic_symbol_24dp)
|
||||
UnicodeEmojiSection.Flags -> painterResource(R.drawable.ic_flag_24dp)
|
||||
UnicodeEmojiSection.Smileys -> painterResource(
|
||||
R.drawable.ic_emoticon_24dp
|
||||
)
|
||||
UnicodeEmojiSection.People -> painterResource(
|
||||
R.drawable.ic_human_greeting_variant_24dp
|
||||
)
|
||||
UnicodeEmojiSection.Animals -> painterResource(
|
||||
R.drawable.ic_snake_24dp
|
||||
)
|
||||
UnicodeEmojiSection.Food -> painterResource(
|
||||
R.drawable.ic_glass_mug_variant_24dp
|
||||
)
|
||||
UnicodeEmojiSection.Travel -> painterResource(
|
||||
R.drawable.ic_train_bus_24dp
|
||||
)
|
||||
UnicodeEmojiSection.Activities -> painterResource(
|
||||
R.drawable.ic_skate_24dp
|
||||
)
|
||||
UnicodeEmojiSection.Objects -> painterResource(
|
||||
R.drawable.ic_table_chair_24dp
|
||||
)
|
||||
UnicodeEmojiSection.Symbols -> painterResource(
|
||||
R.drawable.ic_symbol_24dp
|
||||
)
|
||||
UnicodeEmojiSection.Flags -> painterResource(
|
||||
R.drawable.ic_flag_24dp
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
tint = if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else LocalContentColor.current
|
||||
} else {
|
||||
LocalContentColor.current
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -443,7 +489,7 @@ fun EmojiPicker(
|
|||
key = "searchResultsHeader",
|
||||
span = {
|
||||
GridItemSpan(spanCount)
|
||||
},
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.emoji_picker_search_results_header),
|
||||
|
|
@ -481,7 +527,7 @@ fun EmojiPicker(
|
|||
key = "searchResultsFooter",
|
||||
span = {
|
||||
GridItemSpan(spanCount)
|
||||
},
|
||||
}
|
||||
) {
|
||||
Divider()
|
||||
}
|
||||
|
|
@ -516,7 +562,7 @@ fun ColumnScope.PickerItem(
|
|||
skinToneFactory: (EmojiPickerItem.UnicodeEmoji) -> String,
|
||||
onClick: (EmojiPickerItem) -> Unit,
|
||||
onServerEmoteInfo: (String) -> Unit,
|
||||
lesserHeaders: Boolean = false,
|
||||
lesserHeaders: Boolean = false
|
||||
) {
|
||||
when (item) {
|
||||
is EmojiPickerItem.UnicodeEmoji -> {
|
||||
|
|
@ -528,7 +574,7 @@ fun ColumnScope.PickerItem(
|
|||
.aspectRatio(1f)
|
||||
.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = skinToneFactory(item),
|
||||
|
|
@ -548,7 +594,7 @@ fun ColumnScope.PickerItem(
|
|||
.aspectRatio(1f)
|
||||
.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/emojis/${item.emote.id}/emoji.gif",
|
||||
|
|
@ -564,9 +610,12 @@ fun ColumnScope.PickerItem(
|
|||
is EmojiPickerItem.Section -> {
|
||||
Text(
|
||||
when (item.category) {
|
||||
is Category.UnicodeEmojiCategory -> stringResource(item.category.definition.nameResource)
|
||||
is Category.ServerEmoteCategory -> item.category.server.name
|
||||
?: stringResource(R.string.unknown)
|
||||
is Category.UnicodeEmojiCategory -> stringResource(
|
||||
item.category.definition.nameResource
|
||||
)
|
||||
is Category.ServerEmoteCategory ->
|
||||
item.category.server.name
|
||||
?: stringResource(R.string.unknown)
|
||||
},
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import androidx.compose.ui.unit.dp
|
|||
fun CollapsibleCard(
|
||||
header: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ fun CollapsibleCard(
|
|||
modifier = Modifier
|
||||
.clickable { expanded = !expanded }
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
.padding(16.dp)
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides MaterialTheme.typography.titleMedium.copy(
|
||||
|
|
@ -87,4 +87,4 @@ fun CollapsibleCardPreview() {
|
|||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ fun FormTextField(
|
|||
type: KeyboardType = KeyboardType.Text,
|
||||
supportingText: @Composable (() -> Unit)? = null,
|
||||
singleLine: Boolean = true,
|
||||
enabled: Boolean = true,
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
TextField(
|
||||
value = value,
|
||||
|
|
@ -34,4 +34,4 @@ fun FormTextField(
|
|||
enabled = enabled,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,14 @@ fun IconPlaceholder(
|
|||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))
|
||||
.then(
|
||||
if (onClick != NoopHandler || onLongClick != NoopHandler) Modifier.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
else Modifier
|
||||
if (onClick != NoopHandler || onLongClick != NoopHandler) {
|
||||
Modifier.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
|
|
@ -45,4 +48,4 @@ fun IconPlaceholder(
|
|||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -83,4 +83,4 @@ fun PageHeaderPreviewWithAdditionalButtons() {
|
|||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,4 +65,4 @@ fun PrimaryTabsPreview() {
|
|||
currentIndex = 0,
|
||||
onTabSelected = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,13 +21,17 @@ fun RemoteImage(
|
|||
modifier: Modifier = Modifier,
|
||||
contentScale: ContentScale = ContentScale.Crop,
|
||||
width: Int = 0,
|
||||
height: Int = 0,
|
||||
height: Int = 0
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
fun pxAsDp(px: Int): Dp {
|
||||
return (px / (context.resources
|
||||
.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).dp
|
||||
return (
|
||||
px / (
|
||||
context.resources
|
||||
.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT
|
||||
)
|
||||
).dp
|
||||
}
|
||||
|
||||
GlideImage(
|
||||
|
|
@ -39,4 +43,4 @@ fun RemoteImage(
|
|||
.height(pxAsDp(height)),
|
||||
transition = CrossFade
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ fun SheetClickable(
|
|||
label: @Composable (TextStyle) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
dangerous: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
CompositionLocalProvider(LocalContentColor provides if (dangerous) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onBackground) {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides if (dangerous) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onBackground
|
||||
) {
|
||||
Box(modifier = modifier.padding(vertical = 4.dp)) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -44,7 +46,7 @@ fun SheetClickable(
|
|||
label(
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = LocalContentColor.current,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -68,4 +70,4 @@ fun SettingsCategoryPreview() {
|
|||
},
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ fun UserAvatar(
|
|||
size: Dp = 40.dp,
|
||||
presenceSize: Dp = 16.dp,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
|
|
@ -94,29 +94,35 @@ fun UserAvatar(
|
|||
.clip(CircleShape)
|
||||
.size(size)
|
||||
.then(
|
||||
if (onLongClick != null || onClick != null) Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
else Modifier
|
||||
if (onLongClick != null || onClick != null) {
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_BASE/users/${userId}/default_avatar",
|
||||
url = "$REVOLT_BASE/users/$userId/default_avatar",
|
||||
description = stringResource(id = R.string.avatar_alt, username),
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(size)
|
||||
.then(
|
||||
if (onLongClick != null || onClick != null) Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
else Modifier
|
||||
),
|
||||
if (onLongClick != null || onClick != null) {
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +141,7 @@ fun GroupIcon(
|
|||
rawUrl: String? = null,
|
||||
size: Dp = 40.dp,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
|
|
@ -151,12 +157,15 @@ fun GroupIcon(
|
|||
.clip(MaterialTheme.shapes.small)
|
||||
.size(size)
|
||||
.then(
|
||||
if (onLongClick != null || onClick != null) Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
else Modifier
|
||||
if (onLongClick != null || onClick != null) {
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
|
|
@ -164,12 +173,15 @@ fun GroupIcon(
|
|||
modifier = Modifier
|
||||
.size(size)
|
||||
.then(
|
||||
if (onLongClick != null || onClick != null) Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
else Modifier
|
||||
if (onLongClick != null || onClick != null) {
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onClick?.invoke() },
|
||||
onLongClick = { onLongClick?.invoke() }
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.background(MaterialTheme.colorScheme.primary)
|
||||
|
|
@ -186,9 +198,7 @@ fun GroupIcon(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun UserAvatarWidthPlaceholder(
|
||||
size: Dp = 40.dp,
|
||||
) {
|
||||
fun UserAvatarWidthPlaceholder(size: Dp = 40.dp) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(size)
|
||||
|
|
@ -205,4 +215,4 @@ fun UserAvatarWithPresencePreview() {
|
|||
userId = "01F1WKM5TK2V6KCZWR6DGBJDTZ",
|
||||
presence = Presence.Online
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ fun WebMarkdown(
|
|||
text: String,
|
||||
maskLoading: Boolean = false,
|
||||
simpleLineBreaks: Boolean = true,
|
||||
modifier: Modifier = Modifier,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val contentColour = LocalContentColor.current
|
||||
val materialColourScheme = MaterialTheme.colorScheme
|
||||
|
|
@ -97,9 +97,11 @@ fun WebMarkdown(
|
|||
): Boolean {
|
||||
// Capture clicks on invite links
|
||||
if (webResourceRequest.url.host == "rvlt.gg" ||
|
||||
(webResourceRequest.url.host?.endsWith("revolt.chat") == true && webResourceRequest.url.path?.startsWith(
|
||||
"/invite"
|
||||
) == true)
|
||||
(
|
||||
webResourceRequest.url.host?.endsWith("revolt.chat") == true && webResourceRequest.url.path?.startsWith(
|
||||
"/invite"
|
||||
) == true
|
||||
)
|
||||
) {
|
||||
val intent = Intent(
|
||||
context,
|
||||
|
|
@ -129,7 +131,7 @@ fun WebMarkdown(
|
|||
}
|
||||
|
||||
loadUrl(
|
||||
"$REVOLT_APP/_android_assets/webmarkdown/renderer.html",
|
||||
"$REVOLT_APP/_android_assets/webmarkdown/renderer.html"
|
||||
)
|
||||
|
||||
settings.apply {
|
||||
|
|
@ -185,4 +187,4 @@ fun WebMarkdown(
|
|||
it.evaluateJavascript("renderMarkdown()", null)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,4 +66,4 @@ fun AnyLinkPreview() {
|
|||
AnyLink(text = "Click me! #${clicked.value}", action = {
|
||||
clicked.value++
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,11 +51,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
@Composable
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
fun AudioPlayer(
|
||||
url: String,
|
||||
filename: String,
|
||||
contentType: String,
|
||||
) {
|
||||
fun AudioPlayer(url: String, filename: String, contentType: String) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val showMenu = remember { mutableStateOf(false) }
|
||||
|
|
@ -201,7 +197,7 @@ fun AudioPlayer(
|
|||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 4.dp),
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = filename,
|
||||
|
|
@ -222,7 +218,7 @@ fun AudioPlayer(
|
|||
}
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(onClick = {
|
||||
if (isPlaying.value) {
|
||||
|
|
@ -237,12 +233,12 @@ fun AudioPlayer(
|
|||
if (isPlaying.value) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_pause_24dp),
|
||||
contentDescription = stringResource(R.string.media_viewer_pause),
|
||||
contentDescription = stringResource(R.string.media_viewer_pause)
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PlayArrow,
|
||||
contentDescription = stringResource(R.string.media_viewer_play),
|
||||
contentDescription = stringResource(R.string.media_viewer_play)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -270,7 +266,7 @@ fun AudioPlayer(
|
|||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.MoreVert,
|
||||
contentDescription = stringResource(R.string.media_viewer_more),
|
||||
contentDescription = stringResource(R.string.media_viewer_more)
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = showMenu.value,
|
||||
|
|
@ -298,4 +294,4 @@ fun AudioPlayer(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ private fun Long.formatAsLengthDuration(): String {
|
|||
if (days > 0) "$days:" else null,
|
||||
if (hours > 0) "$hours".padStart(2, '0') + ":" else null,
|
||||
"$minutes".padStart(2, '0') + ":",
|
||||
"$seconds".padStart(2, '0'),
|
||||
"$seconds".padStart(2, '0')
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ fun InbuiltMediaPicker(
|
|||
onClose: () -> Unit,
|
||||
onMediaSelected: (Media) -> Unit,
|
||||
pendingMedia: List<String>,
|
||||
disabled: Boolean = false,
|
||||
disabled: Boolean = false
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
|
|
@ -140,14 +140,16 @@ fun InbuiltMediaPicker(
|
|||
onResult = { mediaPermissionState ->
|
||||
hasPhotoPermission = mediaPermissionState[Manifest.permission.READ_MEDIA_IMAGES] == true
|
||||
hasVideoPermission = mediaPermissionState[Manifest.permission.READ_MEDIA_VIDEO] == true
|
||||
mediaPermissionIsPartial = if (!hasMediaPermissions
|
||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
||||
&& mediaPermissionState[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED] == true
|
||||
mediaPermissionIsPartial = if (!hasMediaPermissions &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
|
||||
mediaPermissionState[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED] == true
|
||||
) {
|
||||
true
|
||||
} else if (hasMediaPermissions) {
|
||||
false
|
||||
} else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -158,9 +160,9 @@ fun InbuiltMediaPicker(
|
|||
}
|
||||
|
||||
LaunchedEffect(hasMediaPermissions) {
|
||||
mediaPermissionIsPartial = if (!hasMediaPermissions
|
||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
||||
&& ContextCompat.checkSelfPermission(
|
||||
mediaPermissionIsPartial = if (!hasMediaPermissions &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
|
||||
ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
|
||||
) == PermissionChecker.PERMISSION_GRANTED
|
||||
|
|
@ -168,7 +170,9 @@ fun InbuiltMediaPicker(
|
|||
true
|
||||
} else if (hasMediaPermissions) {
|
||||
false
|
||||
} else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(hasMediaPermissions, mediaPermissionIsPartial) {
|
||||
|
|
@ -178,7 +182,7 @@ fun InbuiltMediaPicker(
|
|||
MediaStore.Images.ImageColumns.RESOLUTION,
|
||||
MediaStore.Images.ImageColumns.ORIENTATION,
|
||||
MediaStore.Images.ImageColumns.MIME_TYPE,
|
||||
MediaStore.Video.VideoColumns.DURATION,
|
||||
MediaStore.Video.VideoColumns.DURATION
|
||||
)
|
||||
|
||||
val selection: String? = null
|
||||
|
|
@ -199,16 +203,30 @@ fun InbuiltMediaPicker(
|
|||
while (cursor.moveToNext()) {
|
||||
try {
|
||||
val id =
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID))
|
||||
cursor.getLong(
|
||||
cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID)
|
||||
)
|
||||
val resolution =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.RESOLUTION))
|
||||
cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(
|
||||
MediaStore.Images.ImageColumns.RESOLUTION
|
||||
)
|
||||
)
|
||||
val orientation =
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.ORIENTATION))
|
||||
cursor.getInt(
|
||||
cursor.getColumnIndexOrThrow(
|
||||
MediaStore.Images.ImageColumns.ORIENTATION
|
||||
)
|
||||
)
|
||||
|
||||
val swapDimensions = orientation == 90 || orientation == 270
|
||||
|
||||
val isVideo =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.MIME_TYPE))
|
||||
cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(
|
||||
MediaStore.Images.ImageColumns.MIME_TYPE
|
||||
)
|
||||
)
|
||||
.startsWith("video")
|
||||
|
||||
val durationColumn =
|
||||
|
|
@ -293,7 +311,7 @@ fun InbuiltMediaPicker(
|
|||
alpha = 0.5f
|
||||
)
|
||||
),
|
||||
textAlign = TextAlign.Center,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
|
@ -311,12 +329,14 @@ fun InbuiltMediaPicker(
|
|||
permissionRequester.launch(
|
||||
arrayOf(
|
||||
Manifest.permission.READ_MEDIA_IMAGES,
|
||||
Manifest.permission.READ_MEDIA_VIDEO,
|
||||
Manifest.permission.READ_MEDIA_VIDEO
|
||||
)
|
||||
)
|
||||
}
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.file_picker_permission_request_cta))
|
||||
Text(
|
||||
text = stringResource(id = R.string.file_picker_permission_request_cta)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -324,7 +344,7 @@ fun InbuiltMediaPicker(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.padding(horizontal = 16.dp),
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
mediaPermissionIsPartial == true
|
||||
|
|
@ -343,7 +363,9 @@ fun InbuiltMediaPicker(
|
|||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ux_file_unpartialise_request),
|
||||
painter = painterResource(
|
||||
id = R.drawable.ux_file_unpartialise_request
|
||||
),
|
||||
modifier = Modifier
|
||||
.size(52.dp),
|
||||
contentDescription = null // decorative
|
||||
|
|
@ -354,14 +376,18 @@ fun InbuiltMediaPicker(
|
|||
.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.file_picker_permission_unpartialise_request_header),
|
||||
text = stringResource(
|
||||
id = R.string.file_picker_permission_unpartialise_request_header
|
||||
),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.file_picker_permission_unpartialise_request_body),
|
||||
text = stringResource(
|
||||
id = R.string.file_picker_permission_unpartialise_request_body
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = LocalContentColor.current.copy(
|
||||
alpha = 0.5f
|
||||
|
|
@ -384,12 +410,16 @@ fun InbuiltMediaPicker(
|
|||
permissionRequester.launch(
|
||||
arrayOf(
|
||||
Manifest.permission.READ_MEDIA_IMAGES,
|
||||
Manifest.permission.READ_MEDIA_VIDEO,
|
||||
Manifest.permission.READ_MEDIA_VIDEO
|
||||
)
|
||||
)
|
||||
}
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.file_picker_permission_unpartialise_request_cta))
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.file_picker_permission_unpartialise_request_cta
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -404,7 +434,9 @@ fun InbuiltMediaPicker(
|
|||
onOpenDocumentsUi()
|
||||
},
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.file_picker_chip_documents))
|
||||
Text(
|
||||
text = stringResource(id = R.string.file_picker_chip_documents)
|
||||
)
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
|
|
@ -468,14 +500,14 @@ fun InbuiltMediaPicker(
|
|||
}
|
||||
)
|
||||
.width(100.dp)
|
||||
.aspectRatio(images[image].aspectRatio),
|
||||
.aspectRatio(images[image].aspectRatio)
|
||||
) {
|
||||
GlideImage(
|
||||
model = images[image].uri.toString(),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.fillMaxSize(),
|
||||
.fillMaxSize()
|
||||
)
|
||||
|
||||
if (images[image].duration != null) {
|
||||
|
|
@ -502,4 +534,4 @@ fun InbuiltMediaPicker(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ fun AttachmentManager(
|
|||
attachments: List<FileArgs>,
|
||||
uploading: Boolean,
|
||||
uploadProgress: Float = 0f,
|
||||
onRemove: (FileArgs) -> Unit,
|
||||
onRemove: (FileArgs) -> Unit
|
||||
) {
|
||||
val animatedProgress by animateFloatAsState(
|
||||
targetValue = uploadProgress,
|
||||
|
|
@ -54,7 +54,6 @@ fun AttachmentManager(
|
|||
.horizontalScroll(rememberScrollState())
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
) {
|
||||
|
||||
attachments.forEach { attachment ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -98,20 +97,20 @@ fun AttachmentManagerPreview() {
|
|||
FileArgs(
|
||||
filename = "file1.png",
|
||||
contentType = "image/png",
|
||||
file = File("file1.png"),
|
||||
file = File("file1.png")
|
||||
),
|
||||
FileArgs(
|
||||
filename = "file2.png",
|
||||
contentType = "image/png",
|
||||
file = File("file2.png"),
|
||||
file = File("file2.png")
|
||||
),
|
||||
FileArgs(
|
||||
filename = "file3.png",
|
||||
contentType = "image/png",
|
||||
file = File("file3.png"),
|
||||
),
|
||||
file = File("file3.png")
|
||||
)
|
||||
),
|
||||
uploading = false,
|
||||
onRemove = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ fun ChannelHeader(
|
|||
channel: Channel,
|
||||
onChannelClick: (String) -> Unit,
|
||||
onToggleDrawer: () -> Unit,
|
||||
useDrawer: Boolean,
|
||||
useDrawer: Boolean
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -41,7 +41,7 @@ fun ChannelHeader(
|
|||
channel.id?.let { onChannelClick(it) }
|
||||
}
|
||||
.padding(vertical = 4.dp, horizontal = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (useDrawer) {
|
||||
IconButton(onClick = {
|
||||
|
|
@ -49,7 +49,7 @@ fun ChannelHeader(
|
|||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Menu,
|
||||
contentDescription = stringResource(R.string.menu),
|
||||
contentDescription = stringResource(R.string.menu)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ fun ChannelHeader(
|
|||
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = channel.name
|
||||
|
|
@ -86,7 +86,7 @@ fun ChannelHeader(
|
|||
},
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
|
@ -96,8 +96,8 @@ fun ChannelHeader(
|
|||
contentDescription = stringResource(R.string.menu),
|
||||
modifier = Modifier
|
||||
.size(18.dp)
|
||||
.alpha(0.4f),
|
||||
.alpha(0.4f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -107,4 +107,4 @@ fun getIconBackColour(channelId: String): Color {
|
|||
'Z' -> Color(0xFFFF8A80)
|
||||
else -> Color(0xFFFFFFFF)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,11 +47,7 @@ fun replyContentText(message: Message): String {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ManageableReply(
|
||||
reply: SendMessageReply,
|
||||
onToggleMention: () -> Unit,
|
||||
onRemove: () -> Unit,
|
||||
) {
|
||||
fun ManageableReply(reply: SendMessageReply, onToggleMention: () -> Unit, onRemove: () -> Unit) {
|
||||
val replyMessage = RevoltAPI.messageCache[reply.id] ?: return onRemove()
|
||||
val replyAuthor = RevoltAPI.userCache[replyMessage.author] ?: return onRemove()
|
||||
|
||||
|
|
@ -61,7 +57,7 @@ fun ManageableReply(
|
|||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
|
|
@ -72,7 +68,7 @@ fun ManageableReply(
|
|||
onRemove()
|
||||
}
|
||||
.padding(4.dp)
|
||||
.size(16.dp),
|
||||
.size(16.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
|
@ -97,7 +93,7 @@ fun ManageableReply(
|
|||
style = LocalTextStyle.current.copy(
|
||||
brush = authorColour(message = replyMessage),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 12.sp,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -133,7 +129,7 @@ fun ManageableReply(
|
|||
} else {
|
||||
MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f)
|
||||
},
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -142,7 +138,7 @@ fun ManageableReply(
|
|||
fun ReplyManager(
|
||||
replies: List<SendMessageReply>,
|
||||
onToggleMention: (SendMessageReply) -> Unit,
|
||||
onRemove: (SendMessageReply) -> Unit,
|
||||
onRemove: (SendMessageReply) -> Unit
|
||||
) {
|
||||
Column {
|
||||
replies.forEach { reply ->
|
||||
|
|
@ -153,4 +149,4 @@ fun ReplyManager(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,10 +30,7 @@ import chat.revolt.api.schemas.User
|
|||
import chat.revolt.components.generic.UserAvatar
|
||||
|
||||
@Composable
|
||||
fun StackedUserAvatars(
|
||||
users: List<String>,
|
||||
amount: Int = 3,
|
||||
) {
|
||||
fun StackedUserAvatars(users: List<String>, amount: Int = 3) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(16.dp + (8.dp * minOf(users.size, amount)), 16.dp)
|
||||
|
|
@ -43,21 +40,19 @@ fun StackedUserAvatars(
|
|||
UserAvatar(
|
||||
avatar = user?.avatar,
|
||||
userId = userId,
|
||||
username = user?.let {User.resolveDefaultName(it)} ?: stringResource(id = R.string.unknown),
|
||||
username = user?.let { User.resolveDefaultName(it) } ?: stringResource(id = R.string.unknown),
|
||||
size = 16.dp,
|
||||
modifier = Modifier
|
||||
.offset(
|
||||
x = (index * 8).dp,
|
||||
),
|
||||
x = (index * 8).dp
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TypingIndicator(
|
||||
users: List<String>,
|
||||
) {
|
||||
fun TypingIndicator(users: List<String>) {
|
||||
fun typingMessageResource(): Int {
|
||||
return when (users.size) {
|
||||
0 -> R.string.typing_blank
|
||||
|
|
@ -84,11 +79,11 @@ fun TypingIndicator(
|
|||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = 16.dp,
|
||||
topEnd = 16.dp,
|
||||
topEnd = 16.dp
|
||||
)
|
||||
)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(top = 4.dp, start = 16.dp, end = 16.dp),
|
||||
.padding(top = 4.dp, start = 16.dp, end = 16.dp)
|
||||
) {
|
||||
StackedUserAvatars(users = users)
|
||||
|
||||
|
|
@ -103,8 +98,8 @@ fun TypingIndicator(
|
|||
),
|
||||
fontSize = 12.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,13 +84,13 @@ fun RowScope.ChannelList(
|
|||
currentChannel: String?,
|
||||
onChannelClick: (String) -> Unit,
|
||||
onSpecialClick: (String) -> Unit,
|
||||
onServerSheetOpenFor: (String) -> Unit,
|
||||
onServerSheetOpenFor: (String) -> Unit
|
||||
) {
|
||||
val lazyListState = rememberLazyListState()
|
||||
val enableSmallBanner by remember {
|
||||
derivedStateOf {
|
||||
lazyListState.firstVisibleItemScrollOffset > 40 ||
|
||||
lazyListState.firstVisibleItemIndex > 0
|
||||
lazyListState.firstVisibleItemIndex > 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ fun RowScope.ChannelList(
|
|||
sheetState = channelContextSheetState,
|
||||
onDismissRequest = {
|
||||
channelContextSheetShown = false
|
||||
},
|
||||
}
|
||||
) {
|
||||
ChannelContextSheet(
|
||||
channelId = channelContextSheetTarget,
|
||||
|
|
@ -151,13 +151,13 @@ fun RowScope.ChannelList(
|
|||
modifier = Modifier
|
||||
.padding(start = 4.dp, top = 8.dp, bottom = 8.dp)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxSize(),
|
||||
state = lazyListState,
|
||||
state = lazyListState
|
||||
) {
|
||||
if (serverId == "home") {
|
||||
stickyHeader(
|
||||
|
|
@ -194,7 +194,7 @@ fun RowScope.ChannelList(
|
|||
onClick = {
|
||||
onSpecialClick("home")
|
||||
},
|
||||
large = true,
|
||||
large = true
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ fun RowScope.ChannelList(
|
|||
onClick = {
|
||||
onChannelClick(notesChannelId ?: return@DrawerChannel)
|
||||
},
|
||||
large = true,
|
||||
large = true
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -238,13 +238,19 @@ fun RowScope.ChannelList(
|
|||
val channel = dmAbleChannels.getOrNull(it) ?: return@items
|
||||
|
||||
val partner =
|
||||
if (channel.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner(
|
||||
channel
|
||||
)] else null
|
||||
if (channel.channelType == ChannelType.DirectMessage) {
|
||||
RevoltAPI.userCache[
|
||||
ChannelUtils.resolveDMPartner(
|
||||
channel
|
||||
)
|
||||
]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
DrawerChannel(
|
||||
name = partner?.let { p -> User.resolveDefaultName(p) } ?: channel.name
|
||||
?: stringResource(R.string.unknown),
|
||||
?: stringResource(R.string.unknown),
|
||||
channelType = channel.channelType ?: ChannelType.TextChannel,
|
||||
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
|
||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
||||
|
|
@ -313,7 +319,9 @@ fun RowScope.ChannelList(
|
|||
|
||||
Glide.with(this)
|
||||
.load("$REVOLT_FILES/banners/${server.banner.id}")
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.transition(
|
||||
DrawableTransitionOptions.withCrossFade()
|
||||
)
|
||||
.into(this)
|
||||
}
|
||||
},
|
||||
|
|
@ -348,21 +356,29 @@ fun RowScope.ChannelList(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.alpha(0.9f)
|
||||
.height(BANNER_HEIGHT_COMPACT.dp + 8.dp) // due to padding in Text
|
||||
.height(
|
||||
BANNER_HEIGHT_COMPACT.dp + 8.dp
|
||||
) // due to padding in Text
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Spacer(Modifier.width(16.dp))
|
||||
|
||||
if (server?.flags has ServerFlags.Official) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_revolt_decagram_24dp),
|
||||
contentDescription = stringResource(R.string.server_flag_official),
|
||||
painter = painterResource(
|
||||
id = R.drawable.ic_revolt_decagram_24dp
|
||||
),
|
||||
contentDescription = stringResource(
|
||||
R.string.server_flag_official
|
||||
),
|
||||
tint = if (server?.banner != null) {
|
||||
bannerTextColour
|
||||
} else {
|
||||
|
|
@ -375,8 +391,12 @@ fun RowScope.ChannelList(
|
|||
}
|
||||
if (server?.flags has ServerFlags.Verified) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_check_decagram_24dp),
|
||||
contentDescription = stringResource(R.string.server_flag_verified),
|
||||
painter = painterResource(
|
||||
id = R.drawable.ic_check_decagram_24dp
|
||||
),
|
||||
contentDescription = stringResource(
|
||||
R.string.server_flag_verified
|
||||
),
|
||||
tint = if (server?.banner != null) {
|
||||
bannerTextColour
|
||||
} else {
|
||||
|
|
@ -389,8 +409,10 @@ fun RowScope.ChannelList(
|
|||
}
|
||||
|
||||
Text(
|
||||
text = (server?.name
|
||||
?: stringResource(R.string.unknown)),
|
||||
text = (
|
||||
server?.name
|
||||
?: stringResource(R.string.unknown)
|
||||
),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = if (server?.banner != null) {
|
||||
bannerTextColour
|
||||
|
|
@ -438,7 +460,6 @@ fun RowScope.ChannelList(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (categorisedChannels.isNullOrEmpty()) {
|
||||
|
|
@ -458,7 +479,7 @@ fun RowScope.ChannelList(
|
|||
Text(
|
||||
text = stringResource(R.string.no_channels_body),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -480,9 +501,15 @@ fun RowScope.ChannelList(
|
|||
val channel = item.channel
|
||||
|
||||
val partner =
|
||||
if (channel.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner(
|
||||
channel
|
||||
)] else null
|
||||
if (channel.channelType == ChannelType.DirectMessage) {
|
||||
RevoltAPI.userCache[
|
||||
ChannelUtils.resolveDMPartner(
|
||||
channel
|
||||
)
|
||||
]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
DrawerChannel(
|
||||
name = partner?.let { p -> User.resolveDefaultName(p) }
|
||||
|
|
@ -498,7 +525,11 @@ fun RowScope.ChannelList(
|
|||
} ?: false,
|
||||
dmPartnerIcon = partner?.avatar ?: channel.icon,
|
||||
dmPartnerId = partner?.id,
|
||||
dmPartnerName = partner?.let { p -> User.resolveDefaultName(p) },
|
||||
dmPartnerName = partner?.let { p ->
|
||||
User.resolveDefaultName(
|
||||
p
|
||||
)
|
||||
},
|
||||
dmPartnerStatus = presenceFromStatus(
|
||||
status = partner?.status?.presence,
|
||||
online = partner?.online ?: false
|
||||
|
|
@ -538,4 +569,4 @@ fun RowScope.ChannelList(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,11 +46,14 @@ fun DrawerChannel(
|
|||
dmPartnerName: String? = null,
|
||||
dmPartnerIcon: AutumnResource? = null,
|
||||
dmPartnerId: String? = null,
|
||||
large: Boolean = false,
|
||||
large: Boolean = false
|
||||
) {
|
||||
val backgroundColor = animateColorAsState(
|
||||
if (selected) MaterialTheme.colorScheme.background
|
||||
else Color.Transparent,
|
||||
if (selected) {
|
||||
MaterialTheme.colorScheme.background
|
||||
} else {
|
||||
Color.Transparent
|
||||
},
|
||||
animationSpec = spring(),
|
||||
label = "Channel background colour"
|
||||
)
|
||||
|
|
@ -102,12 +105,16 @@ fun DrawerChannel(
|
|||
else -> ChannelIcon(
|
||||
channelType = channelType,
|
||||
modifier = Modifier.then(
|
||||
if (large) Modifier.padding(
|
||||
end = 12.dp,
|
||||
start = 4.dp,
|
||||
top = 4.dp,
|
||||
bottom = 4.dp
|
||||
) else Modifier.padding(end = 8.dp)
|
||||
if (large) {
|
||||
Modifier.padding(
|
||||
end = 12.dp,
|
||||
start = 4.dp,
|
||||
top = 4.dp,
|
||||
bottom = 4.dp
|
||||
)
|
||||
} else {
|
||||
Modifier.padding(end = 8.dp)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -136,4 +143,4 @@ fun DrawerChannel(
|
|||
Spacer(modifier = Modifier.size(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ fun DrawerServer(
|
|||
) {
|
||||
if (iconId != null) {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/icons/${iconId}/server.png?max_side=256",
|
||||
url = "$REVOLT_FILES/icons/$iconId/server.png?max_side=256",
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
|
|
@ -75,4 +75,4 @@ fun DrawerServer(
|
|||
.background(LocalContentColor.current)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,7 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun DrawerServerlikeIcon(
|
||||
onClick: () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
fun DrawerServerlikeIcon(onClick: () -> Unit, content: @Composable () -> Unit) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier
|
||||
|
|
@ -26,4 +23,4 @@ fun DrawerServerlikeIcon(
|
|||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,4 +23,4 @@ fun ServerDrawerSeparator() {
|
|||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ fun LinkOnHome(
|
|||
icon: @Composable (Modifier) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
description: @Composable () -> Unit = {},
|
||||
onClick: () -> Unit,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
|
|
@ -37,12 +37,12 @@ fun LinkOnHome(
|
|||
.clickable { onClick() }
|
||||
.padding(20.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 5.dp),
|
||||
.padding(vertical = 5.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
icon(Modifier.padding(end = 14.dp))
|
||||
|
||||
|
|
@ -78,4 +78,4 @@ fun LinkOnHomePreview() {
|
|||
icon = { mod -> Icon(Icons.Default.AccountBox, null, modifier = mod) },
|
||||
onClick = { }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,8 +158,8 @@ fun RawUserOverview(user: User, profile: Profile? = null) {
|
|||
append("#${user.discriminator}")
|
||||
pop()
|
||||
}.toAnnotatedString(),
|
||||
color = if (profile?.background != null) Color.White else LocalContentColor.current,
|
||||
color = if (profile?.background != null) Color.White else LocalContentColor.current
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,12 @@ fun ThemeChip(
|
|||
.clickable(onClick = onClick)
|
||||
.then(modifier)
|
||||
.then(
|
||||
if (selected)
|
||||
if (selected) {
|
||||
Modifier
|
||||
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f))
|
||||
else Modifier
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.padding(4.dp)
|
||||
.padding(8.dp)
|
||||
|
|
@ -58,4 +60,4 @@ fun SelectedThemeChipPreview() {
|
|||
selected = true,
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@ import androidx.compose.ui.unit.sp
|
|||
import chat.revolt.R
|
||||
|
||||
@Composable
|
||||
fun DisconnectedScreen(
|
||||
onRetry: () -> Unit
|
||||
) {
|
||||
fun DisconnectedScreen(onRetry: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -35,7 +33,7 @@ fun DisconnectedScreen(
|
|||
textAlign = TextAlign.Center
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Text(
|
||||
|
|
@ -43,7 +41,7 @@ fun DisconnectedScreen(
|
|||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Normal
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(vertical = 10.dp, horizontal = 20.dp)
|
||||
|
|
@ -60,4 +58,4 @@ fun DisconnectedScreen(
|
|||
@Composable
|
||||
fun DisconnectedScreenPreview() {
|
||||
DisconnectedScreen(onRetry = {})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
|
|||
data class Changelog(
|
||||
val summary: String,
|
||||
val version: String,
|
||||
val date: String,
|
||||
val date: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
@ -26,20 +26,28 @@ class Changelogs(val context: Context, val kvStorage: KVStorage? = null) {
|
|||
}
|
||||
|
||||
fun getChangelog(version: String): String {
|
||||
return context.assets.open("changelogs/${version}.md").use {
|
||||
return context.assets.open("changelogs/$version.md").use {
|
||||
it.reader().readText()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun hasSeenLatest(): Boolean {
|
||||
if (kvStorage == null) throw IllegalStateException("Not supported for non-KVStorage instances of Changelogs")
|
||||
if (kvStorage == null) {
|
||||
throw IllegalStateException(
|
||||
"Not supported for non-KVStorage instances of Changelogs"
|
||||
)
|
||||
}
|
||||
|
||||
return kvStorage.get("latestChangelogRead") == index.latest
|
||||
}
|
||||
|
||||
suspend fun markAsSeen() {
|
||||
if (kvStorage == null) throw IllegalStateException("Not supported for non-KVStorage instances of Changelogs")
|
||||
if (kvStorage == null) {
|
||||
throw IllegalStateException(
|
||||
"Not supported for non-KVStorage instances of Changelogs"
|
||||
)
|
||||
}
|
||||
|
||||
kvStorage.set("latestChangelogRead", index.latest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ data class Emoji(
|
|||
val alternates: List<List<Long>>,
|
||||
val emoticons: List<String>,
|
||||
val shortcodes: List<String>,
|
||||
val animated: Boolean,
|
||||
val animated: Boolean
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EmojiGroup(
|
||||
val group: String,
|
||||
val emoji: List<Emoji>,
|
||||
val emoji: List<Emoji>
|
||||
)
|
||||
|
||||
enum class FitzpatrickSkinTone(val modifierCodepoint: Int?) {
|
||||
|
|
@ -30,7 +30,7 @@ enum class FitzpatrickSkinTone(val modifierCodepoint: Int?) {
|
|||
MediumLight(0x1F3FC),
|
||||
Medium(0x1F3FD),
|
||||
MediumDark(0x1F3FE),
|
||||
Dark(0x1F3FF),
|
||||
Dark(0x1F3FF)
|
||||
}
|
||||
|
||||
enum class UnicodeEmojiSection(val googleName: String, val nameResource: Int) {
|
||||
|
|
@ -42,7 +42,7 @@ enum class UnicodeEmojiSection(val googleName: String, val nameResource: Int) {
|
|||
Activities("Activities and events", R.string.emoji_category_activities),
|
||||
Objects("Objects", R.string.emoji_category_objects),
|
||||
Symbols("Symbols", R.string.emoji_category_symbols),
|
||||
Flags("Flags", R.string.emoji_category_flags),
|
||||
Flags("Flags", R.string.emoji_category_flags)
|
||||
}
|
||||
|
||||
sealed class Category {
|
||||
|
|
@ -55,7 +55,7 @@ sealed class EmojiPickerItem {
|
|||
data class UnicodeEmoji(
|
||||
val character: String,
|
||||
val hasSkinTones: Boolean,
|
||||
val alternates: List<List<Long>>,
|
||||
val alternates: List<List<Long>>
|
||||
) : EmojiPickerItem()
|
||||
|
||||
data class ServerEmote(val emote: chat.revolt.api.schemas.Emoji) : EmojiPickerItem()
|
||||
|
|
@ -106,17 +106,19 @@ class EmojiImpl {
|
|||
val category =
|
||||
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
|
||||
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
|
||||
list.addAll(group.emoji.map { emoji ->
|
||||
EmojiPickerItem.UnicodeEmoji(
|
||||
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
|
||||
emoji.alternates.any { alternate ->
|
||||
alternate.any { codepoint ->
|
||||
codepoint in 0x1F3FB..0x1F3FF
|
||||
}
|
||||
},
|
||||
emoji.alternates
|
||||
)
|
||||
})
|
||||
list.addAll(
|
||||
group.emoji.map { emoji ->
|
||||
EmojiPickerItem.UnicodeEmoji(
|
||||
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
|
||||
emoji.alternates.any { alternate ->
|
||||
alternate.any { codepoint ->
|
||||
codepoint in 0x1F3FB..0x1F3FF
|
||||
}
|
||||
},
|
||||
emoji.alternates
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return list
|
||||
|
|
@ -143,7 +145,9 @@ class EmojiImpl {
|
|||
|
||||
for (server in serversWithEmotes()) {
|
||||
val index =
|
||||
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server }
|
||||
flatPickerList.indexOfFirst {
|
||||
it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server
|
||||
}
|
||||
val allEmotesInThatServer =
|
||||
RevoltAPI.emojiCache.values.filter { it.parent?.id == server.id }
|
||||
val lastIndex = index + allEmotesInThatServer.size
|
||||
|
|
@ -152,12 +156,16 @@ class EmojiImpl {
|
|||
}
|
||||
for (section in UnicodeEmojiSection.entries) {
|
||||
val index =
|
||||
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == section }
|
||||
flatPickerList.indexOfFirst {
|
||||
it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == section
|
||||
}
|
||||
val lastIndex = if (section == UnicodeEmojiSection.entries.last()) {
|
||||
Int.MAX_VALUE
|
||||
} else {
|
||||
val nextSection = UnicodeEmojiSection.entries[section.ordinal + 1]
|
||||
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == nextSection } - 1
|
||||
flatPickerList.indexOfFirst {
|
||||
it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == nextSection
|
||||
} - 1
|
||||
}
|
||||
output[Category.UnicodeEmojiCategory(section)] = Pair(index, lastIndex)
|
||||
}
|
||||
|
|
@ -220,17 +228,19 @@ class EmojiImpl {
|
|||
val category =
|
||||
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
|
||||
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
|
||||
list.addAll(matchingEmoji.map { emoji ->
|
||||
EmojiPickerItem.UnicodeEmoji(
|
||||
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
|
||||
emoji.alternates.any { alternate ->
|
||||
alternate.any { codepoint ->
|
||||
codepoint in 0x1F3FB..0x1F3FF
|
||||
}
|
||||
},
|
||||
emoji.alternates
|
||||
)
|
||||
})
|
||||
list.addAll(
|
||||
matchingEmoji.map { emoji ->
|
||||
EmojiPickerItem.UnicodeEmoji(
|
||||
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
|
||||
emoji.alternates.any { alternate ->
|
||||
alternate.any { codepoint ->
|
||||
codepoint in 0x1F3FB..0x1F3FF
|
||||
}
|
||||
},
|
||||
emoji.alternates
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -240,4 +250,4 @@ class EmojiImpl {
|
|||
init {
|
||||
metadata = initMetadata(RevoltApplication.instance.applicationContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ object Platform {
|
|||
fun needsShowClipboardNotification(): Boolean {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.S
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,4 +24,4 @@ fun LifecycleEffect(onEvent: (owner: LifecycleOwner, event: Lifecycle.Event) ->
|
|||
lifecycle.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import com.discord.simpleast.core.node.Node
|
|||
// https://github.com/discord/SimpleAST/blob/567b61c51056cbdec39e839100690c576c26a4c6/app/src/main/java/com/discord/simpleast/sample/spans/BlockBackgroundNode.kt
|
||||
// LICENSED UNDER THE APACHE LICENSE, VERSION 2.0
|
||||
// Adapted for Revolt.
|
||||
//
|
||||
//
|
||||
// Changes:
|
||||
// - Fill and stroke colours are now parameters
|
||||
// - Whether we are in a quote is no longer a boolean, but an integer with the quote depth
|
||||
|
|
@ -28,7 +28,7 @@ class BlockBackgroundNode<R>(
|
|||
private val quoteDepth: Int,
|
||||
private val fillColor: Int = Color.DKGRAY,
|
||||
private val strokeColor: Int = Color.BLACK,
|
||||
vararg children: Node<R>,
|
||||
vararg children: Node<R>
|
||||
) : Node.Parent<R>(*children) {
|
||||
|
||||
override fun render(builder: SpannableStringBuilder, renderContext: R) {
|
||||
|
|
@ -41,7 +41,8 @@ class BlockBackgroundNode<R>(
|
|||
ensureEndsWithNewline(builder)
|
||||
|
||||
val backgroundSpan = BlockBackgroundSpan(
|
||||
fillColor, strokeColor,
|
||||
fillColor,
|
||||
strokeColor,
|
||||
strokeWidth = 2,
|
||||
strokeRadius = 15,
|
||||
leftMargin = 40 * quoteDepth
|
||||
|
|
@ -131,4 +132,4 @@ class BlockBackgroundSpan(
|
|||
draw(canvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class EmoteSpan(drawable: Drawable) :
|
||||
ImageSpan(drawable, ALIGN_BOTTOM) {
|
||||
}
|
||||
ImageSpan(drawable, ALIGN_BOTTOM)
|
||||
|
||||
class EmoteClickableSpan(private val emoteId: String) : LongClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
|
|
@ -22,4 +21,4 @@ class EmoteClickableSpan(private val emoteId: String) : LongClickableSpan() {
|
|||
override fun onLongClick(view: View?) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ class LinkSpan(private val url: String, private val drawBackground: Boolean = fa
|
|||
|
||||
// Intercept invite links
|
||||
if (uri.host == Uri.parse(REVOLT_INVITES).host!! ||
|
||||
(uri.host?.endsWith(Uri.parse(REVOLT_APP).host!!) == true && uri.path?.startsWith(
|
||||
"/invite"
|
||||
) == true)
|
||||
(
|
||||
uri.host?.endsWith(Uri.parse(REVOLT_APP).host!!) == true && uri.path?.startsWith(
|
||||
"/invite"
|
||||
) == true
|
||||
)
|
||||
) {
|
||||
val intent = Intent(
|
||||
widget.context,
|
||||
|
|
@ -81,4 +83,4 @@ class LinkSpan(private val url: String, private val drawBackground: Boolean = fa
|
|||
ds.bgColor = ds.linkColor and 0x33ffffff // 20% alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,7 @@ abstract class LongClickableSpan : ClickableSpan() {
|
|||
class LongClickLinkMovementMethod : LinkMovementMethod() {
|
||||
private var longClickHandler: Handler? = null
|
||||
private var isLongPressed = false
|
||||
override fun onTouchEvent(
|
||||
widget: TextView,
|
||||
buffer: Spannable,
|
||||
event: MotionEvent
|
||||
): Boolean {
|
||||
override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
|
||||
val action = event.action
|
||||
if (action == MotionEvent.ACTION_CANCEL) {
|
||||
longClickHandler?.removeCallbacksAndMessages(null)
|
||||
|
|
@ -41,7 +37,8 @@ class LongClickLinkMovementMethod : LinkMovementMethod() {
|
|||
val line = layout.getLineForVertical(y)
|
||||
val off = layout.getOffsetForHorizontal(line, x.toFloat())
|
||||
val link = buffer.getSpans(
|
||||
off, off,
|
||||
off,
|
||||
off,
|
||||
LongClickableSpan::class.java
|
||||
)
|
||||
if (link.isNotEmpty()) {
|
||||
|
|
@ -82,4 +79,4 @@ class LongClickLinkMovementMethod : LinkMovementMethod() {
|
|||
|
||||
private var sInstance: LongClickLinkMovementMethod? = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"}"
|
||||
|
||||
|
|
@ -128,4 +128,4 @@ class LinkNode(val content: String, val url: String = content) : Node<MarkdownCo
|
|||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,8 @@ class TimestampRule<S>(private val context: Context) :
|
|||
matcher.group(2)
|
||||
),
|
||||
listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance))
|
||||
), state
|
||||
),
|
||||
state
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -113,10 +114,7 @@ fun <RC, S> createInlineCodeRule(context: Context, backgroundColor: Int): Rule<R
|
|||
)
|
||||
}
|
||||
|
||||
fun <RC> createCodeRule(
|
||||
context: Context,
|
||||
backgroundColor: Int
|
||||
): Rule<RC, Node<RC>, MarkdownState> {
|
||||
fun <RC> createCodeRule(context: Context, backgroundColor: Int): Rule<RC, Node<RC>, MarkdownState> {
|
||||
val codeStyleProviders = CodeStyleProviders<RC>(
|
||||
defaultStyleProvider = { listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) },
|
||||
commentStyleProvider = {
|
||||
|
|
@ -174,7 +172,7 @@ fun <RC> createCodeRule(
|
|||
R.style.Code_TextAppearance_Params
|
||||
)
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
val languageMap = CodeRules.createCodeLanguageMap<RC, MarkdownState>(codeStyleProviders)
|
||||
|
||||
|
|
@ -203,6 +201,6 @@ fun MarkdownParser.addRevoltRules(context: Context): MarkdownParser {
|
|||
CustomEmoteRule(context),
|
||||
TimestampRule(context),
|
||||
NamedLinkRule(),
|
||||
LinkRule(),
|
||||
LinkRule()
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue