style: run ktlint on code
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
f15438708e
commit
fac7411eea
|
|
@ -0,0 +1,2 @@
|
||||||
|
[*.{kt,kts}]
|
||||||
|
ktlint_code_style = android_studio
|
||||||
|
|
@ -180,7 +180,7 @@ fun InviteScreen(
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize()
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -214,11 +214,10 @@ fun InviteScreen(
|
||||||
?: stringResource(id = R.string.unknown),
|
?: stringResource(id = R.string.unknown),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(64.dp)
|
.size(64.dp)
|
||||||
.clip(CircleShape),
|
.clip(CircleShape)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -227,7 +226,7 @@ fun InviteScreen(
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
@ -235,7 +234,7 @@ fun InviteScreen(
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.invite_message),
|
text = stringResource(id = R.string.invite_message),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
@ -270,10 +269,7 @@ fun InviteScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InvalidInviteError(
|
fun InvalidInviteError(error: RevoltError? = null, onDismissRequest: () -> Unit) {
|
||||||
error: RevoltError? = null,
|
|
||||||
onDismissRequest: () -> Unit
|
|
||||||
) {
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
icon = {
|
icon = {
|
||||||
|
|
@ -287,11 +283,11 @@ fun InvalidInviteError(
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.invite_error_header),
|
text = stringResource(id = R.string.invite_error_header),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column() {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(
|
text = stringResource(
|
||||||
id = when (error?.type) {
|
id = when (error?.type) {
|
||||||
|
|
@ -301,7 +297,7 @@ fun InvalidInviteError(
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -333,15 +329,15 @@ fun NoInviteSpecifiedError(onDismissRequest: () -> Unit) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.invite_error_header),
|
text = stringResource(id = R.string.invite_error_header),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column() {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.invite_error_no_invite),
|
text = stringResource(id = R.string.invite_error_no_invite),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ fun AppEntrypoint(windowSizeClass: WindowSizeClass) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
RevoltTheme(
|
RevoltTheme(
|
||||||
requestedTheme = GlobalState.theme,
|
requestedTheme = GlobalState.theme
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|
|
||||||
|
|
@ -79,10 +79,7 @@ class ImageViewActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ImageViewScreen(
|
fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
|
||||||
resource: AutumnResource,
|
|
||||||
onClose: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
|
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -182,7 +179,7 @@ fun ImageViewScreen(
|
||||||
|
|
||||||
RevoltTheme(requestedTheme = GlobalState.theme) {
|
RevoltTheme(requestedTheme = GlobalState.theme) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
|
||||||
) { pv ->
|
) { pv ->
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -191,10 +188,11 @@ fun ImageViewScreen(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
PageHeader(text = stringResource(
|
PageHeader(
|
||||||
id = R.string.media_viewer_title_image,
|
text = stringResource(
|
||||||
resource.filename ?: resource.id!!
|
id = R.string.media_viewer_title_image,
|
||||||
),
|
resource.filename ?: resource.id!!
|
||||||
|
),
|
||||||
showBackButton = true,
|
showBackButton = true,
|
||||||
onBackButtonClicked = onClose,
|
onBackButtonClicked = onClose,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
|
@ -213,13 +211,16 @@ fun ImageViewScreen(
|
||||||
expanded = shareSubmenuIsOpen.value,
|
expanded = shareSubmenuIsOpen.value,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
shareSubmenuIsOpen.value = false
|
shareSubmenuIsOpen.value = false
|
||||||
}) {
|
}
|
||||||
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
onClick = {
|
onClick = {
|
||||||
shareUrl()
|
shareUrl()
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(stringResource(id = R.string.media_viewer_share_url))
|
Text(
|
||||||
|
stringResource(id = R.string.media_viewer_share_url)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
|
|
@ -227,7 +228,11 @@ fun ImageViewScreen(
|
||||||
shareImage()
|
shareImage()
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(stringResource(id = R.string.media_viewer_share_image))
|
Text(
|
||||||
|
stringResource(
|
||||||
|
id = R.string.media_viewer_share_image
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -237,11 +242,14 @@ fun ImageViewScreen(
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_download_24dp),
|
painter = painterResource(id = R.drawable.ic_download_24dp),
|
||||||
contentDescription = stringResource(id = R.string.media_viewer_save)
|
contentDescription = stringResource(
|
||||||
|
id = R.string.media_viewer_save
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -260,7 +268,7 @@ fun ImageViewScreen(
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.background),
|
.background(MaterialTheme.colorScheme.background)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,7 @@ class VideoViewActivity : ComponentActivity() {
|
||||||
|
|
||||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoViewScreen(
|
fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
|
||||||
resource: AutumnResource,
|
|
||||||
onClose: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
|
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -198,7 +195,7 @@ fun VideoViewScreen(
|
||||||
|
|
||||||
RevoltTheme(requestedTheme = GlobalState.theme) {
|
RevoltTheme(requestedTheme = GlobalState.theme) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
|
||||||
) { pv ->
|
) { pv ->
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -207,10 +204,11 @@ fun VideoViewScreen(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
PageHeader(text = stringResource(
|
PageHeader(
|
||||||
id = R.string.media_viewer_title_video,
|
text = stringResource(
|
||||||
resource.filename ?: resource.id!!
|
id = R.string.media_viewer_title_video,
|
||||||
),
|
resource.filename ?: resource.id!!
|
||||||
|
),
|
||||||
showBackButton = true,
|
showBackButton = true,
|
||||||
onBackButtonClicked = onClose,
|
onBackButtonClicked = onClose,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
|
@ -229,13 +227,16 @@ fun VideoViewScreen(
|
||||||
expanded = shareSubmenuIsOpen.value,
|
expanded = shareSubmenuIsOpen.value,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
shareSubmenuIsOpen.value = false
|
shareSubmenuIsOpen.value = false
|
||||||
}) {
|
}
|
||||||
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
onClick = {
|
onClick = {
|
||||||
shareUrl()
|
shareUrl()
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(stringResource(id = R.string.media_viewer_share_url))
|
Text(
|
||||||
|
stringResource(id = R.string.media_viewer_share_url)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
|
|
@ -243,7 +244,11 @@ fun VideoViewScreen(
|
||||||
shareVideo()
|
shareVideo()
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(stringResource(id = R.string.media_viewer_share_video))
|
Text(
|
||||||
|
stringResource(
|
||||||
|
id = R.string.media_viewer_share_video
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -253,11 +258,14 @@ fun VideoViewScreen(
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_download_24dp),
|
painter = painterResource(id = R.drawable.ic_download_24dp),
|
||||||
contentDescription = stringResource(id = R.string.media_viewer_save)
|
contentDescription = stringResource(
|
||||||
|
id = R.string.media_viewer_save
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -275,7 +283,7 @@ fun VideoViewScreen(
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.background),
|
.background(MaterialTheme.colorScheme.background)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import chat.revolt.api.internals.Members
|
||||||
import chat.revolt.api.realtime.DisconnectionState
|
import chat.revolt.api.realtime.DisconnectionState
|
||||||
import chat.revolt.api.realtime.RealtimeSocket
|
import chat.revolt.api.realtime.RealtimeSocket
|
||||||
import chat.revolt.api.routes.user.fetchSelf
|
import chat.revolt.api.routes.user.fetchSelf
|
||||||
|
import chat.revolt.api.schemas.Channel as ChannelSchema
|
||||||
import chat.revolt.api.schemas.Emoji
|
import chat.revolt.api.schemas.Emoji
|
||||||
import chat.revolt.api.schemas.Message
|
import chat.revolt.api.schemas.Message
|
||||||
import chat.revolt.api.schemas.Server
|
import chat.revolt.api.schemas.Server
|
||||||
|
|
@ -38,7 +39,6 @@ import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import chat.revolt.api.schemas.Channel as ChannelSchema
|
|
||||||
|
|
||||||
const val REVOLT_BASE = "https://api.revolt.chat"
|
const val REVOLT_BASE = "https://api.revolt.chat"
|
||||||
const val REVOLT_SUPPORT = "https://support.revolt.chat"
|
const val REVOLT_SUPPORT = "https://support.revolt.chat"
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ object DirectMessages {
|
||||||
fun hasPlatformModerationDM(): Boolean {
|
fun hasPlatformModerationDM(): Boolean {
|
||||||
return unreadDMs().any {
|
return unreadDMs().any {
|
||||||
it.channelType == ChannelType.DirectMessage &&
|
it.channelType == ChannelType.DirectMessage &&
|
||||||
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlatformModerationDM(): Channel? {
|
fun getPlatformModerationDM(): Channel? {
|
||||||
return unreadDMs().firstOrNull {
|
return unreadDMs().firstOrNull {
|
||||||
it.channelType == ChannelType.DirectMessage &&
|
it.channelType == ChannelType.DirectMessage &&
|
||||||
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -76,30 +76,30 @@ object BitDefaults {
|
||||||
|
|
||||||
val Default =
|
val Default =
|
||||||
ViewOnly +
|
ViewOnly +
|
||||||
PermissionBit.SendMessage +
|
PermissionBit.SendMessage +
|
||||||
PermissionBit.InviteOthers +
|
PermissionBit.InviteOthers +
|
||||||
PermissionBit.SendEmbeds +
|
PermissionBit.SendEmbeds +
|
||||||
PermissionBit.UploadFiles +
|
PermissionBit.UploadFiles +
|
||||||
PermissionBit.Connect +
|
PermissionBit.Connect +
|
||||||
PermissionBit.Speak
|
PermissionBit.Speak
|
||||||
|
|
||||||
val SavedMessages =
|
val SavedMessages =
|
||||||
PermissionBit.GrantAllSafe.value
|
PermissionBit.GrantAllSafe.value
|
||||||
|
|
||||||
val DirectMessages =
|
val DirectMessages =
|
||||||
Default +
|
Default +
|
||||||
PermissionBit.ManageChannel +
|
PermissionBit.ManageChannel +
|
||||||
PermissionBit.React
|
PermissionBit.React
|
||||||
|
|
||||||
val Server =
|
val Server =
|
||||||
Default +
|
Default +
|
||||||
PermissionBit.React +
|
PermissionBit.React +
|
||||||
PermissionBit.ChangeNickname +
|
PermissionBit.ChangeNickname +
|
||||||
PermissionBit.ChangeAvatar
|
PermissionBit.ChangeAvatar
|
||||||
|
|
||||||
val Webhook =
|
val Webhook =
|
||||||
PermissionBit.SendMessage +
|
PermissionBit.SendMessage +
|
||||||
PermissionBit.SendEmbeds +
|
PermissionBit.SendEmbeds +
|
||||||
PermissionBit.Masquerade +
|
PermissionBit.Masquerade +
|
||||||
PermissionBit.React
|
PermissionBit.React
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +80,7 @@ object Roles {
|
||||||
|
|
||||||
ChannelType.TextChannel, ChannelType.VoiceChannel -> {
|
ChannelType.TextChannel, ChannelType.VoiceChannel -> {
|
||||||
val server = RevoltAPI.serverCache[channel.server]
|
val server = RevoltAPI.serverCache[channel.server]
|
||||||
// FIXME this is a stupid patch to prevent it from showing "no permission" on a channel on launch
|
// FIXME this is a stupid patch to prevent it from showing "no permission" on a channel on launch
|
||||||
?: return PermissionBit.GrantAllSafe.value
|
?: return PermissionBit.GrantAllSafe.value
|
||||||
|
|
||||||
if (server.owner == user?.id) return PermissionBit.GrantAllSafe.value
|
if (server.owner == user?.id) return PermissionBit.GrantAllSafe.value
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@ import android.content.Context
|
||||||
import android.graphics.RuntimeShader
|
import android.graphics.RuntimeShader
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Brush as AndroidBrush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ShaderBrush
|
import androidx.compose.ui.graphics.ShaderBrush
|
||||||
import org.intellij.lang.annotations.Language
|
import org.intellij.lang.annotations.Language
|
||||||
import androidx.compose.ui.graphics.Brush as AndroidBrush
|
|
||||||
|
|
||||||
object SpecialUsers {
|
object SpecialUsers {
|
||||||
val PLATFORM_MODERATION_USER = "01FC17E1WTM2BGE4F3ARN3FDAF"
|
val PLATFORM_MODERATION_USER = "01FC17E1WTM2BGE4F3ARN3FDAF"
|
||||||
|
|
||||||
val TRUSTED_MODERATION_BOTS = listOf(
|
val TRUSTED_MODERATION_BOTS = listOf(
|
||||||
"01GXBYCNQ52A9QYCQ99RBPXPAW", // AutoMod
|
"01GXBYCNQ52A9QYCQ99RBPXPAW", // AutoMod
|
||||||
"01FCXRNNVW69AMSHBE61W1M5T3", // AutoMod Nightly
|
"01FCXRNNVW69AMSHBE61W1M5T3" // AutoMod Nightly
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed class TeamMemberFlair {
|
sealed class TeamMemberFlair {
|
||||||
|
|
@ -40,7 +40,7 @@ object SpecialUsers {
|
||||||
Color(0xFFFF9B55),
|
Color(0xFFFF9B55),
|
||||||
Color(0xFFFFFFFF),
|
Color(0xFFFFFFFF),
|
||||||
Color(0xFFD461A6),
|
Color(0xFFD461A6),
|
||||||
Color(0xFFA50062),
|
Color(0xFFA50062)
|
||||||
),
|
),
|
||||||
start = Offset.Zero,
|
start = Offset.Zero,
|
||||||
end = Offset.Infinite
|
end = Offset.Infinite
|
||||||
|
|
@ -51,7 +51,7 @@ object SpecialUsers {
|
||||||
AndroidBrush.linearGradient(
|
AndroidBrush.linearGradient(
|
||||||
listOf(
|
listOf(
|
||||||
Color(0xFF68224F),
|
Color(0xFF68224F),
|
||||||
Color(0xFFC68235),
|
Color(0xFFC68235)
|
||||||
),
|
),
|
||||||
start = Offset.Zero,
|
start = Offset.Zero,
|
||||||
end = Offset.Infinite
|
end = Offset.Infinite
|
||||||
|
|
@ -75,7 +75,7 @@ object SpecialUsers {
|
||||||
Color(0xFF1000AF)
|
Color(0xFF1000AF)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
), // zomatree
|
) // zomatree
|
||||||
)
|
)
|
||||||
|
|
||||||
fun teamFlairAsBrush(context: Context, id: String): AndroidBrush? {
|
fun teamFlairAsBrush(context: Context, id: String): AndroidBrush? {
|
||||||
|
|
|
||||||
|
|
@ -45,35 +45,67 @@ object ULID {
|
||||||
// Entropy part (16 chars)
|
// Entropy part (16 chars)
|
||||||
chars[10] = b32chars[(entropy[0].toShort() and 0xff).toInt().ushr(3)]
|
chars[10] = b32chars[(entropy[0].toShort() and 0xff).toInt().ushr(3)]
|
||||||
chars[11] =
|
chars[11] =
|
||||||
b32chars[(entropy[0].toInt() shl 2 or (entropy[1].toShort() and 0xff).toInt()
|
b32chars[
|
||||||
.ushr(6) and 0x1f)]
|
(
|
||||||
|
entropy[0].toInt() shl 2 or (entropy[1].toShort() and 0xff).toInt()
|
||||||
|
.ushr(6) and 0x1f
|
||||||
|
)
|
||||||
|
]
|
||||||
chars[12] = b32chars[((entropy[1].toShort() and 0xff).toInt().ushr(1) and 0x1f)]
|
chars[12] = b32chars[((entropy[1].toShort() and 0xff).toInt().ushr(1) and 0x1f)]
|
||||||
chars[13] =
|
chars[13] =
|
||||||
b32chars[(entropy[1].toInt() shl 4 or (entropy[2].toShort() and 0xff).toInt()
|
b32chars[
|
||||||
.ushr(4) and 0x1f)]
|
(
|
||||||
|
entropy[1].toInt() shl 4 or (entropy[2].toShort() and 0xff).toInt()
|
||||||
|
.ushr(4) and 0x1f
|
||||||
|
)
|
||||||
|
]
|
||||||
chars[14] =
|
chars[14] =
|
||||||
b32chars[(entropy[2].toInt() shl 5 or (entropy[3].toShort() and 0xff).toInt()
|
b32chars[
|
||||||
.ushr(7) and 0x1f)]
|
(
|
||||||
|
entropy[2].toInt() shl 5 or (entropy[3].toShort() and 0xff).toInt()
|
||||||
|
.ushr(7) and 0x1f
|
||||||
|
)
|
||||||
|
]
|
||||||
chars[15] = b32chars[((entropy[3].toShort() and 0xff).toInt().ushr(2) and 0x1f)]
|
chars[15] = b32chars[((entropy[3].toShort() and 0xff).toInt().ushr(2) and 0x1f)]
|
||||||
chars[16] =
|
chars[16] =
|
||||||
b32chars[(entropy[3].toInt() shl 3 or (entropy[4].toShort() and 0xff).toInt()
|
b32chars[
|
||||||
.ushr(5) and 0x1f)]
|
(
|
||||||
|
entropy[3].toInt() shl 3 or (entropy[4].toShort() and 0xff).toInt()
|
||||||
|
.ushr(5) and 0x1f
|
||||||
|
)
|
||||||
|
]
|
||||||
chars[17] = b32chars[(entropy[4].toInt() and 0x1f)]
|
chars[17] = b32chars[(entropy[4].toInt() and 0x1f)]
|
||||||
chars[18] = b32chars[(entropy[5].toShort() and 0xff).toInt().ushr(3)]
|
chars[18] = b32chars[(entropy[5].toShort() and 0xff).toInt().ushr(3)]
|
||||||
chars[19] =
|
chars[19] =
|
||||||
b32chars[(entropy[5].toInt() shl 2 or (entropy[6].toShort() and 0xff).toInt()
|
b32chars[
|
||||||
.ushr(6) and 0x1f)]
|
(
|
||||||
|
entropy[5].toInt() shl 2 or (entropy[6].toShort() and 0xff).toInt()
|
||||||
|
.ushr(6) and 0x1f
|
||||||
|
)
|
||||||
|
]
|
||||||
chars[20] = b32chars[((entropy[6].toShort() and 0xff).toInt().ushr(1) and 0x1f)]
|
chars[20] = b32chars[((entropy[6].toShort() and 0xff).toInt().ushr(1) and 0x1f)]
|
||||||
chars[21] =
|
chars[21] =
|
||||||
b32chars[(entropy[6].toInt() shl 4 or (entropy[7].toShort() and 0xff).toInt()
|
b32chars[
|
||||||
.ushr(4) and 0x1f)]
|
(
|
||||||
|
entropy[6].toInt() shl 4 or (entropy[7].toShort() and 0xff).toInt()
|
||||||
|
.ushr(4) and 0x1f
|
||||||
|
)
|
||||||
|
]
|
||||||
chars[22] =
|
chars[22] =
|
||||||
b32chars[(entropy[7].toInt() shl 5 or (entropy[8].toShort() and 0xff).toInt()
|
b32chars[
|
||||||
.ushr(7) and 0x1f)]
|
(
|
||||||
|
entropy[7].toInt() shl 5 or (entropy[8].toShort() and 0xff).toInt()
|
||||||
|
.ushr(7) and 0x1f
|
||||||
|
)
|
||||||
|
]
|
||||||
chars[23] = b32chars[((entropy[8].toShort() and 0xff).toInt().ushr(2) and 0x1f)]
|
chars[23] = b32chars[((entropy[8].toShort() and 0xff).toInt().ushr(2) and 0x1f)]
|
||||||
chars[24] =
|
chars[24] =
|
||||||
b32chars[(entropy[8].toInt() shl 3 or (entropy[9].toShort() and 0xff).toInt()
|
b32chars[
|
||||||
.ushr(5) and 0x1f)]
|
(
|
||||||
|
entropy[8].toInt() shl 3 or (entropy[9].toShort() and 0xff).toInt()
|
||||||
|
.ushr(5) and 0x1f
|
||||||
|
)
|
||||||
|
]
|
||||||
chars[25] = b32chars[(entropy[9].toInt() and 0x1f)]
|
chars[25] = b32chars[(entropy[9].toInt() and 0x1f)]
|
||||||
|
|
||||||
return String(chars)
|
return String(chars)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ object WebChallenge {
|
||||||
suspend fun needsCloudflare(): Boolean {
|
suspend fun needsCloudflare(): Boolean {
|
||||||
RevoltHttp.get(REVOLT_BASE).let {
|
RevoltHttp.get(REVOLT_BASE).let {
|
||||||
val text = it.bodyAsText()
|
val text = it.bodyAsText()
|
||||||
return text.contains("window._cf_chl_opt") // FIXME Naive, prone to captcha page changing
|
return text.contains(
|
||||||
|
"window._cf_chl_opt"
|
||||||
|
) // FIXME Naive, prone to captcha page changing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ private val ADDITIONAL_WEB_COLOURS = mapOf(
|
||||||
"transparent" to Color.Transparent,
|
"transparent" to Color.Transparent,
|
||||||
"inherit" to Color.Unspecified,
|
"inherit" to Color.Unspecified,
|
||||||
"initial" to Color.Unspecified,
|
"initial" to Color.Unspecified,
|
||||||
"unset" to Color.Unspecified,
|
"unset" to Color.Unspecified
|
||||||
)
|
)
|
||||||
|
|
||||||
object WebCompat {
|
object WebCompat {
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ import kotlinx.serialization.json.JsonObject
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AnyFrame(
|
data class AnyFrame(
|
||||||
val type: String,
|
val type: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ErrorFrame(
|
data class ErrorFrame(
|
||||||
val type: String = "Error",
|
val type: String = "Error",
|
||||||
val error: String,
|
val error: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -49,7 +49,7 @@ data class MessageUpdateFrame(
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Appendable(
|
data class Appendable(
|
||||||
val embeds: List<Embed>? = null,
|
val embeds: List<Embed>? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -73,7 +73,7 @@ data class MessageReactFrame(
|
||||||
val id: String,
|
val id: String,
|
||||||
val channel_id: String,
|
val channel_id: String,
|
||||||
val user_id: String,
|
val user_id: String,
|
||||||
val emoji_id: String,
|
val emoji_id: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -82,7 +82,7 @@ data class MessageUnreactFrame(
|
||||||
val id: String,
|
val id: String,
|
||||||
val channel_id: String,
|
val channel_id: String,
|
||||||
val user_id: String,
|
val user_id: String,
|
||||||
val emoji_id: String,
|
val emoji_id: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -90,7 +90,7 @@ data class MessageRemoveReactionFrame(
|
||||||
val type: String = "MessageRemoveReaction",
|
val type: String = "MessageRemoveReaction",
|
||||||
val id: String,
|
val id: String,
|
||||||
val channel_id: String,
|
val channel_id: String,
|
||||||
val emoji_id: String,
|
val emoji_id: String
|
||||||
)
|
)
|
||||||
|
|
||||||
/* ChannelCreate: we already have a "type" property in channel so we just alias the type */
|
/* ChannelCreate: we already have a "type" property in channel so we just alias the type */
|
||||||
|
|
@ -152,7 +152,7 @@ data class ServerCreateFrame(
|
||||||
val type: String = "ServerCreate",
|
val type: String = "ServerCreate",
|
||||||
val id: String,
|
val id: String,
|
||||||
val server: Server,
|
val server: Server,
|
||||||
val channels: List<Channel>,
|
val channels: List<Channel>
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -222,7 +222,7 @@ data class UserRelationshipFrame(
|
||||||
val type: String = "UserRelationship",
|
val type: String = "UserRelationship",
|
||||||
val id: String,
|
val id: String,
|
||||||
val user: User,
|
val user: User,
|
||||||
val status: String,
|
val status: String
|
||||||
)
|
)
|
||||||
|
|
||||||
typealias EmojiCreateFrame = Emoji
|
typealias EmojiCreateFrame = Emoji
|
||||||
|
|
@ -230,5 +230,5 @@ typealias EmojiCreateFrame = Emoji
|
||||||
@Serializable
|
@Serializable
|
||||||
data class EmojiDeleteFrame(
|
data class EmojiDeleteFrame(
|
||||||
val type: String = "EmojiDelete",
|
val type: String = "EmojiDelete",
|
||||||
val id: String,
|
val id: String
|
||||||
)
|
)
|
||||||
|
|
@ -17,7 +17,7 @@ data class LoginNegotiation(
|
||||||
|
|
||||||
@SerialName("friendly_name")
|
@SerialName("friendly_name")
|
||||||
val friendlyName: String,
|
val friendlyName: String,
|
||||||
val captcha: String? = null,
|
val captcha: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -111,7 +111,6 @@ suspend fun negotiateAuthentication(email: String, password: String): EmailPassw
|
||||||
val responseContent = response.bodyAsText()
|
val responseContent = response.bodyAsText()
|
||||||
Log.d("Revolt", "negotiateAuthentication: $responseContent")
|
Log.d("Revolt", "negotiateAuthentication: $responseContent")
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val error = RevoltJson.decodeFromString(RevoltError.serializer(), responseContent)
|
val error = RevoltJson.decodeFromString(RevoltError.serializer(), responseContent)
|
||||||
return EmailPasswordAssessment(error = error)
|
return EmailPasswordAssessment(error = error)
|
||||||
|
|
@ -122,7 +121,7 @@ suspend fun negotiateAuthentication(email: String, password: String): EmailPassw
|
||||||
if (response.status == HttpStatusCode.InternalServerError) {
|
if (response.status == HttpStatusCode.InternalServerError) {
|
||||||
return EmailPasswordAssessment(
|
return EmailPasswordAssessment(
|
||||||
error = RevoltError(
|
error = RevoltError(
|
||||||
"InternalServerError",
|
"InternalServerError"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +142,7 @@ suspend fun negotiateAuthentication(email: String, password: String): EmailPassw
|
||||||
|
|
||||||
suspend fun authenticateWithMfaTotpCode(
|
suspend fun authenticateWithMfaTotpCode(
|
||||||
mfaTicket: String,
|
mfaTicket: String,
|
||||||
mfaResponse: MfaResponseTotpCode,
|
mfaResponse: MfaResponseTotpCode
|
||||||
): EmailPasswordAssessment {
|
): EmailPasswordAssessment {
|
||||||
val response: HttpResponse = RevoltHttp.post("/auth/session/login") {
|
val response: HttpResponse = RevoltHttp.post("/auth/session/login") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
|
|
@ -167,7 +166,7 @@ suspend fun authenticateWithMfaTotpCode(
|
||||||
|
|
||||||
suspend fun authenticateWithMfaRecoveryCode(
|
suspend fun authenticateWithMfaRecoveryCode(
|
||||||
mfaTicket: String,
|
mfaTicket: String,
|
||||||
mfaResponse: MfaResponseRecoveryCode,
|
mfaResponse: MfaResponseRecoveryCode
|
||||||
): EmailPasswordAssessment {
|
): EmailPasswordAssessment {
|
||||||
val response: HttpResponse = RevoltHttp.post("/auth/session/login") {
|
val response: HttpResponse = RevoltHttp.post("/auth/session/login") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
|
|
@ -189,7 +188,6 @@ suspend fun authenticateWithMfaRecoveryCode(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun friendlySessionName(): String {
|
fun friendlySessionName(): String {
|
||||||
return "Revolt Android on ${Build.MANUFACTURER} ${Build.MODEL}"
|
return "Revolt Android on ${Build.MANUFACTURER} ${Build.MODEL}"
|
||||||
}
|
}
|
||||||
|
|
@ -17,7 +17,7 @@ data class RegistrationBody(
|
||||||
val email: String,
|
val email: String,
|
||||||
val password: String,
|
val password: String,
|
||||||
val invite: String? = null,
|
val invite: String? = null,
|
||||||
val captcha: String,
|
val captcha: String
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun register(body: RegistrationBody): RsResult<Unit, RevoltError> {
|
suspend fun register(body: RegistrationBody): RsResult<Unit, RevoltError> {
|
||||||
|
|
|
||||||
|
|
@ -70,12 +70,12 @@ data class SendMessageBody(
|
||||||
val content: String,
|
val content: String,
|
||||||
val nonce: String = ULID.makeNext(),
|
val nonce: String = ULID.makeNext(),
|
||||||
val replies: List<SendMessageReply> = emptyList(),
|
val replies: List<SendMessageReply> = emptyList(),
|
||||||
val attachments: List<String>?,
|
val attachments: List<String>?
|
||||||
)
|
)
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
@kotlinx.serialization.Serializable
|
||||||
data class EditMessageBody(
|
data class EditMessageBody(
|
||||||
val content: String?,
|
val content: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun sendMessage(
|
suspend fun sendMessage(
|
||||||
|
|
@ -83,7 +83,7 @@ suspend fun sendMessage(
|
||||||
content: String,
|
content: String,
|
||||||
nonce: String? = ULID.makeNext(),
|
nonce: String? = ULID.makeNext(),
|
||||||
replies: List<SendMessageReply>? = null,
|
replies: List<SendMessageReply>? = null,
|
||||||
attachments: List<String>? = null,
|
attachments: List<String>? = null
|
||||||
): String {
|
): String {
|
||||||
val response = RevoltHttp.post("/channels/$channelId/messages") {
|
val response = RevoltHttp.post("/channels/$channelId/messages") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
|
|
@ -101,11 +101,7 @@ suspend fun sendMessage(
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun editMessage(
|
suspend fun editMessage(channelId: String, messageId: String, newContent: String? = null) {
|
||||||
channelId: String,
|
|
||||||
messageId: String,
|
|
||||||
newContent: String? = null,
|
|
||||||
) {
|
|
||||||
val response = RevoltHttp.patch("/channels/$channelId/messages/$messageId") {
|
val response = RevoltHttp.patch("/channels/$channelId/messages/$messageId") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(
|
setBody(
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,20 @@ suspend fun uploadToAutumn(
|
||||||
val uploadUrl = "$REVOLT_FILES/$tag"
|
val uploadUrl = "$REVOLT_FILES/$tag"
|
||||||
|
|
||||||
val response = RevoltHttp.post(uploadUrl) {
|
val response = RevoltHttp.post(uploadUrl) {
|
||||||
setBody(MultiPartFormDataContent(
|
setBody(
|
||||||
formData {
|
MultiPartFormDataContent(
|
||||||
append("file", file.readBytes(), Headers.build {
|
formData {
|
||||||
append(HttpHeaders.ContentType, contentType.toString())
|
append(
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"$name\"")
|
"file",
|
||||||
})
|
file.readBytes(),
|
||||||
}
|
Headers.build {
|
||||||
))
|
append(HttpHeaders.ContentType, contentType.toString())
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=\"$name\"")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
onUpload { bytesSentTotal, contentLength ->
|
onUpload { bytesSentTotal, contentLength ->
|
||||||
onProgress(bytesSentTotal, contentLength)
|
onProgress(bytesSentTotal, contentLength)
|
||||||
}
|
}
|
||||||
|
|
@ -59,5 +65,4 @@ suspend fun uploadToAutumn(
|
||||||
throw Exception("Unknown error")
|
throw Exception("Unknown error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,5 +3,5 @@ package chat.revolt.api.routes.microservices.january
|
||||||
import chat.revolt.api.REVOLT_JANUARY
|
import chat.revolt.api.REVOLT_JANUARY
|
||||||
|
|
||||||
fun asJanuaryProxyUrl(url: String): String {
|
fun asJanuaryProxyUrl(url: String): String {
|
||||||
return "$REVOLT_JANUARY/proxy?url=${url}"
|
return "$REVOLT_JANUARY/proxy?url=$url"
|
||||||
}
|
}
|
||||||
|
|
@ -21,10 +21,7 @@ data class OnboardingResponse(
|
||||||
val onboarding: Boolean
|
val onboarding: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun needsOnboarding(sessionToken: String = RevoltAPI.sessionToken): Boolean {
|
||||||
suspend fun needsOnboarding(
|
|
||||||
sessionToken: String = RevoltAPI.sessionToken,
|
|
||||||
): Boolean {
|
|
||||||
val response = RevoltHttp.get("/onboard/hello") {
|
val response = RevoltHttp.get("/onboard/hello") {
|
||||||
header(RevoltAPI.TOKEN_HEADER_NAME, sessionToken)
|
header(RevoltAPI.TOKEN_HEADER_NAME, sessionToken)
|
||||||
}
|
}
|
||||||
|
|
@ -36,12 +33,12 @@ suspend fun needsOnboarding(
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class OnboardingCompletionBody(
|
data class OnboardingCompletionBody(
|
||||||
val username: String,
|
val username: String
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun completeOnboarding(
|
suspend fun completeOnboarding(
|
||||||
body: OnboardingCompletionBody,
|
body: OnboardingCompletionBody,
|
||||||
sessionToken: String = RevoltAPI.sessionToken,
|
sessionToken: String = RevoltAPI.sessionToken
|
||||||
): RsResult<Unit, RevoltError> {
|
): RsResult<Unit, RevoltError> {
|
||||||
val response = RevoltHttp.post("/onboard/complete") {
|
val response = RevoltHttp.post("/onboard/complete") {
|
||||||
setBody(body)
|
setBody(body)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ suspend fun putMessageReport(
|
||||||
report_reason = reason,
|
report_reason = reason,
|
||||||
id = messageId
|
id = messageId
|
||||||
),
|
),
|
||||||
additional_context = additionalContext,
|
additional_context = additionalContext
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = RevoltHttp.post("/safety/report") {
|
val response = RevoltHttp.post("/safety/report") {
|
||||||
|
|
@ -57,9 +57,9 @@ suspend fun putServerReport(
|
||||||
content = ServerReport(
|
content = ServerReport(
|
||||||
type = "Server",
|
type = "Server",
|
||||||
report_reason = reason,
|
report_reason = reason,
|
||||||
id = serverId,
|
id = serverId
|
||||||
),
|
),
|
||||||
additional_context = additionalContext,
|
additional_context = additionalContext
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = RevoltHttp.post("/safety/report") {
|
val response = RevoltHttp.post("/safety/report") {
|
||||||
|
|
@ -89,9 +89,9 @@ suspend fun putUserReport(
|
||||||
content = UserReport(
|
content = UserReport(
|
||||||
type = "User",
|
type = "User",
|
||||||
report_reason = reason,
|
report_reason = reason,
|
||||||
id = userId,
|
id = userId
|
||||||
),
|
),
|
||||||
additional_context = additionalContext,
|
additional_context = additionalContext
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = RevoltHttp.post("/safety/report") {
|
val response = RevoltHttp.post("/safety/report") {
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import chat.revolt.api.RevoltJson
|
||||||
import io.ktor.client.request.delete
|
import io.ktor.client.request.delete
|
||||||
import io.ktor.client.request.put
|
import io.ktor.client.request.put
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
import kotlinx.serialization.SerializationException
|
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
|
||||||
suspend fun blockUser(userId: String) {
|
suspend fun blockUser(userId: String) {
|
||||||
val response = RevoltHttp.put("/users/$userId/block")
|
val response = RevoltHttp.put("/users/$userId/block")
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ data class MessagesInChannel(
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ServerUserChoice(
|
data class ServerUserChoice(
|
||||||
val server: String,
|
val server: String,
|
||||||
val user: String,
|
val user: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -35,7 +35,7 @@ data class Member(
|
||||||
val roles: List<String>? = null,
|
val roles: List<String>? = null,
|
||||||
val nickname: String? = null,
|
val nickname: String? = null,
|
||||||
|
|
||||||
val timeout: String? = null,
|
val timeout: String? = null
|
||||||
) {
|
) {
|
||||||
fun mergeWithPartial(other: Member): Member {
|
fun mergeWithPartial(other: Member): Member {
|
||||||
return Member(
|
return Member(
|
||||||
|
|
@ -44,7 +44,7 @@ data class Member(
|
||||||
avatar = other.avatar ?: avatar,
|
avatar = other.avatar ?: avatar,
|
||||||
roles = other.roles ?: roles,
|
roles = other.roles ?: roles,
|
||||||
nickname = other.nickname ?: nickname,
|
nickname = other.nickname ?: nickname,
|
||||||
timeout = other.timeout ?: timeout,
|
timeout = other.timeout ?: timeout
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ data class Channel(
|
||||||
@SerialName("default_permissions")
|
@SerialName("default_permissions")
|
||||||
val defaultPermissions: PermissionDescription? = null,
|
val defaultPermissions: PermissionDescription? = null,
|
||||||
val nsfw: Boolean? = null,
|
val nsfw: Boolean? = null,
|
||||||
val type: String? = null, // this is _only_ used for websocket events!
|
val type: String? = null // this is _only_ used for websocket events!
|
||||||
) {
|
) {
|
||||||
fun mergeWithPartial(partial: Channel): Channel {
|
fun mergeWithPartial(partial: Channel): Channel {
|
||||||
return Channel(
|
return Channel(
|
||||||
|
|
@ -135,7 +135,7 @@ enum class ChannelType(val value: String) {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ChannelUserChoice(
|
data class ChannelUserChoice(
|
||||||
val channel: String,
|
val channel: String,
|
||||||
val user: String,
|
val user: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -143,7 +143,7 @@ data class ChannelUnreadResponse(
|
||||||
@SerialName("_id")
|
@SerialName("_id")
|
||||||
val id: ChannelUserChoice,
|
val id: ChannelUserChoice,
|
||||||
val last_id: String? = null,
|
val last_id: String? = null,
|
||||||
val mentions: List<String>? = null,
|
val mentions: List<String>? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -151,5 +151,5 @@ data class ChannelUnread(
|
||||||
@SerialName("_id")
|
@SerialName("_id")
|
||||||
val id: String,
|
val id: String,
|
||||||
val last_id: String? = null,
|
val last_id: String? = null,
|
||||||
val mentions: List<String>? = null,
|
val mentions: List<String>? = null
|
||||||
)
|
)
|
||||||
|
|
@ -50,5 +50,5 @@ data class AutumnId(
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AutumnError(
|
data class AutumnError(
|
||||||
val type: String,
|
val type: String
|
||||||
)
|
)
|
||||||
|
|
@ -43,5 +43,5 @@ data class Invite(
|
||||||
data class InviteJoined(
|
data class InviteJoined(
|
||||||
val type: String? = null,
|
val type: String? = null,
|
||||||
val channels: List<Channel>? = null,
|
val channels: List<Channel>? = null,
|
||||||
val server: Server? = null,
|
val server: Server? = null
|
||||||
)
|
)
|
||||||
|
|
@ -21,7 +21,7 @@ data class Message(
|
||||||
val masquerade: Masquerade? = null,
|
val masquerade: Masquerade? = null,
|
||||||
val system: SystemInfo? = null,
|
val system: SystemInfo? = null,
|
||||||
val type: String? = null, // this is _only_ used for websocket events!
|
val type: String? = null, // this is _only_ used for websocket events!
|
||||||
val tail: Boolean? = null, // this is used to determine if the message is the last in a message group
|
val tail: Boolean? = null // this is used to determine if the message is the last in a message group
|
||||||
) {
|
) {
|
||||||
fun getAuthor(): User? {
|
fun getAuthor(): User? {
|
||||||
return author?.let { RevoltAPI.userCache[it] }
|
return author?.let { RevoltAPI.userCache[it] }
|
||||||
|
|
@ -42,7 +42,7 @@ data class Message(
|
||||||
mentions = partial.mentions ?: mentions,
|
mentions = partial.mentions ?: mentions,
|
||||||
masquerade = partial.masquerade ?: masquerade,
|
masquerade = partial.masquerade ?: masquerade,
|
||||||
type = partial.type ?: type,
|
type = partial.type ?: type,
|
||||||
tail = partial.tail ?: tail,
|
tail = partial.tail ?: tail
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,5 +100,5 @@ data class SystemInfo(
|
||||||
val by: String? = null,
|
val by: String? = null,
|
||||||
val from: String? = null,
|
val from: String? = null,
|
||||||
val to: String? = null,
|
val to: String? = null,
|
||||||
val content: String? = null,
|
val content: String? = null
|
||||||
)
|
)
|
||||||
|
|
@ -82,37 +82,37 @@ enum class UserReportReason(val value: String) {
|
||||||
data class MessageReport(
|
data class MessageReport(
|
||||||
val type: String,
|
val type: String,
|
||||||
val id: String,
|
val id: String,
|
||||||
val report_reason: ContentReportReason,
|
val report_reason: ContentReportReason
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class FullMessageReport(
|
data class FullMessageReport(
|
||||||
val content: MessageReport,
|
val content: MessageReport,
|
||||||
val additional_context: String? = null,
|
val additional_context: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ServerReport(
|
data class ServerReport(
|
||||||
val type: String,
|
val type: String,
|
||||||
val id: String,
|
val id: String,
|
||||||
val report_reason: ContentReportReason,
|
val report_reason: ContentReportReason
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class FullServerReport(
|
data class FullServerReport(
|
||||||
val content: ServerReport,
|
val content: ServerReport,
|
||||||
val additional_context: String? = null,
|
val additional_context: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UserReport(
|
data class UserReport(
|
||||||
val type: String,
|
val type: String,
|
||||||
val id: String,
|
val id: String,
|
||||||
val report_reason: UserReportReason,
|
val report_reason: UserReportReason
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class FullUserReport(
|
data class FullUserReport(
|
||||||
val content: UserReport,
|
val content: UserReport,
|
||||||
val additional_context: String? = null,
|
val additional_context: String? = null
|
||||||
)
|
)
|
||||||
|
|
@ -19,7 +19,7 @@ data class Server(
|
||||||
val banner: AutumnResource? = null,
|
val banner: AutumnResource? = null,
|
||||||
val flags: Long? = null,
|
val flags: Long? = null,
|
||||||
val analytics: Boolean? = null,
|
val analytics: Boolean? = null,
|
||||||
val discoverable: Boolean? = null,
|
val discoverable: Boolean? = null
|
||||||
) {
|
) {
|
||||||
fun mergeWithPartial(other: Server): Server {
|
fun mergeWithPartial(other: Server): Server {
|
||||||
return Server(
|
return Server(
|
||||||
|
|
@ -36,14 +36,14 @@ data class Server(
|
||||||
banner = other.banner ?: banner,
|
banner = other.banner ?: banner,
|
||||||
flags = other.flags ?: flags,
|
flags = other.flags ?: flags,
|
||||||
analytics = other.analytics ?: analytics,
|
analytics = other.analytics ?: analytics,
|
||||||
discoverable = other.discoverable ?: discoverable,
|
discoverable = other.discoverable ?: discoverable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ServerFlags(val value: Long) {
|
enum class ServerFlags(val value: Long) {
|
||||||
Official(1L shl 0),
|
Official(1L shl 0),
|
||||||
Verified(1L shl 1),
|
Verified(1L shl 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
infix fun Long?.has(flag: ServerFlags): Boolean {
|
infix fun Long?.has(flag: ServerFlags): Boolean {
|
||||||
|
|
@ -90,7 +90,7 @@ data class Emoji(
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
val animated: Boolean? = null,
|
val animated: Boolean? = null,
|
||||||
val nsfw: Boolean? = null,
|
val nsfw: Boolean? = null,
|
||||||
val type: String? = null, // this is _only_ used for websocket events!
|
val type: String? = null // this is _only_ used for websocket events!
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class OrderingSettings(
|
data class OrderingSettings(
|
||||||
val servers: List<String> = emptyList(),
|
val servers: List<String> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -13,5 +13,5 @@ data class AndroidSpecificSettings(
|
||||||
* The theme to use for the app.
|
* The theme to use for the app.
|
||||||
* Can be one of `{ None, Revolt, Light, M3Dynamic, Amoled }`
|
* Can be one of `{ None, Revolt, Light, M3Dynamic, Amoled }`
|
||||||
*/
|
*/
|
||||||
var theme: String? = null,
|
var theme: String? = null
|
||||||
)
|
)
|
||||||
|
|
@ -10,7 +10,9 @@ annotation class Treatment(val description: String)
|
||||||
|
|
||||||
@FeatureFlag("ClosedBetaAccessControl")
|
@FeatureFlag("ClosedBetaAccessControl")
|
||||||
sealed class ClosedBetaAccessControlVariates {
|
sealed class ClosedBetaAccessControlVariates {
|
||||||
@Treatment("Restrict access to the app to users that meet certain or all criteria (implementation-specific)")
|
@Treatment(
|
||||||
|
"Restrict access to the app to users that meet certain or all criteria (implementation-specific)"
|
||||||
|
)
|
||||||
data class Restricted(val predicate: () -> Boolean) : ClosedBetaAccessControlVariates()
|
data class Restricted(val predicate: () -> Boolean) : ClosedBetaAccessControlVariates()
|
||||||
|
|
||||||
@Treatment("Allow access to the app to all users")
|
@Treatment("Allow access to the app to all users")
|
||||||
|
|
@ -22,6 +24,6 @@ object FeatureFlags {
|
||||||
var closedBetaAccessControl by mutableStateOf<ClosedBetaAccessControlVariates>(
|
var closedBetaAccessControl by mutableStateOf<ClosedBetaAccessControlVariates>(
|
||||||
ClosedBetaAccessControlVariates.Restricted {
|
ClosedBetaAccessControlVariates.Restricted {
|
||||||
RevoltAPI.channelCache.containsKey("01H7X2KRB0CA4QDSMB4N7WGERF")
|
RevoltAPI.channelCache.containsKey("01H7X2KRB0CA4QDSMB4N7WGERF")
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,15 @@ class Unreads {
|
||||||
|
|
||||||
suspend fun sync() {
|
suspend fun sync() {
|
||||||
channels.clear()
|
channels.clear()
|
||||||
channels.putAll(syncUnreads().associate {
|
channels.putAll(
|
||||||
it.id.channel to ChannelUnread(
|
syncUnreads().associate {
|
||||||
id = it.id.channel,
|
it.id.channel to ChannelUnread(
|
||||||
last_id = it.last_id,
|
id = it.id.channel,
|
||||||
mentions = it.mentions
|
last_id = it.last_id,
|
||||||
)
|
mentions = it.mentions
|
||||||
})
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
hasLoaded.value = true
|
hasLoaded.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import chat.revolt.ui.theme.Theme
|
||||||
private val NON_MATERIAL_COLOURS = mapOf(
|
private val NON_MATERIAL_COLOURS = mapOf(
|
||||||
DisconnectionState.Disconnected to (Color(0xff4E0C0C) to Color(0xffff1744)),
|
DisconnectionState.Disconnected to (Color(0xff4E0C0C) to Color(0xffff1744)),
|
||||||
DisconnectionState.Reconnecting to (Color(0xff5B5300) to Color(0xffffea00)),
|
DisconnectionState.Reconnecting to (Color(0xff5B5300) to Color(0xffffea00)),
|
||||||
DisconnectionState.Connected to (Color(0xff0E2F10) to Color(0xff00e676)),
|
DisconnectionState.Connected to (Color(0xff0E2F10) to Color(0xff00e676))
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -50,7 +50,7 @@ private fun DisconnectedNoticeBase(
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(background)
|
.background(background)
|
||||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
|
@ -75,10 +75,7 @@ private fun DisconnectedNoticeBase(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisconnectedNotice(
|
fun DisconnectedNotice(state: DisconnectionState, onReconnect: () -> Unit) {
|
||||||
state: DisconnectionState,
|
|
||||||
onReconnect: () -> Unit
|
|
||||||
) {
|
|
||||||
val retries = remember { mutableStateOf(0) }
|
val retries = remember { mutableStateOf(0) }
|
||||||
|
|
||||||
LaunchedEffect(state) {
|
LaunchedEffect(state) {
|
||||||
|
|
@ -101,7 +98,7 @@ fun DisconnectedNotice(
|
||||||
val materialColours = mapOf(
|
val materialColours = mapOf(
|
||||||
DisconnectionState.Disconnected to (MaterialTheme.colorScheme.error to MaterialTheme.colorScheme.onError),
|
DisconnectionState.Disconnected to (MaterialTheme.colorScheme.error to MaterialTheme.colorScheme.onError),
|
||||||
DisconnectionState.Reconnecting to (MaterialTheme.colorScheme.secondary to MaterialTheme.colorScheme.onSecondary),
|
DisconnectionState.Reconnecting to (MaterialTheme.colorScheme.secondary to MaterialTheme.colorScheme.onSecondary),
|
||||||
DisconnectionState.Connected to (MaterialTheme.colorScheme.primary to MaterialTheme.colorScheme.onPrimary),
|
DisconnectionState.Connected to (MaterialTheme.colorScheme.primary to MaterialTheme.colorScheme.onPrimary)
|
||||||
)
|
)
|
||||||
|
|
||||||
val (background, foreground) = when (GlobalState.theme) {
|
val (background, foreground) = when (GlobalState.theme) {
|
||||||
|
|
@ -123,14 +120,14 @@ fun DisconnectedNotice(
|
||||||
background = background,
|
background = background,
|
||||||
foreground = foreground,
|
foreground = foreground,
|
||||||
icon = Icons.Default.Refresh,
|
icon = Icons.Default.Refresh,
|
||||||
text = stringResource(id = R.string.reconnecting),
|
text = stringResource(id = R.string.reconnecting)
|
||||||
)
|
)
|
||||||
|
|
||||||
DisconnectionState.Connected -> DisconnectedNoticeBase(
|
DisconnectionState.Connected -> DisconnectedNoticeBase(
|
||||||
background = background,
|
background = background,
|
||||||
foreground = foreground,
|
foreground = foreground,
|
||||||
icon = Icons.Default.Done,
|
icon = Icons.Default.Done,
|
||||||
text = stringResource(id = R.string.reconnected),
|
text = stringResource(id = R.string.reconnected)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ import chat.revolt.api.internals.WebCompat
|
||||||
import chat.revolt.api.internals.solidColor
|
import chat.revolt.api.internals.solidColor
|
||||||
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
|
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
|
||||||
import chat.revolt.api.schemas.Embed
|
import chat.revolt.api.schemas.Embed
|
||||||
|
import chat.revolt.api.schemas.Embed as EmbedSchema
|
||||||
import chat.revolt.components.generic.RemoteImage
|
import chat.revolt.components.generic.RemoteImage
|
||||||
import chat.revolt.components.generic.UIMarkdown
|
import chat.revolt.components.generic.UIMarkdown
|
||||||
import chat.revolt.api.schemas.Embed as EmbedSchema
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RegularEmbed(
|
fun RegularEmbed(
|
||||||
|
|
@ -66,12 +66,14 @@ fun RegularEmbed(
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.then(
|
.then(
|
||||||
if (embed.originalURL != null)
|
if (embed.originalURL != null) {
|
||||||
Modifier
|
Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
onLinkClick(embed.originalURL)
|
onLinkClick(embed.originalURL)
|
||||||
}
|
}
|
||||||
else Modifier
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
|
@ -116,9 +118,13 @@ fun RegularEmbed(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
.then(
|
.then(
|
||||||
if (embed.originalURL != null) Modifier.clickable {
|
if (embed.originalURL != null) {
|
||||||
onLinkClick(embed.originalURL)
|
Modifier.clickable {
|
||||||
} else Modifier
|
onLinkClick(embed.originalURL)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.aspectRatio(
|
.aspectRatio(
|
||||||
(it.width?.toFloat() ?: 0f) / (it.height?.toFloat() ?: 0f)
|
(it.width?.toFloat() ?: 0f) / (it.height?.toFloat() ?: 0f)
|
||||||
|
|
@ -130,15 +136,10 @@ fun RegularEmbed(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Embed(
|
fun Embed(embed: Embed, modifier: Modifier = Modifier, onLinkClick: (String) -> Unit) {
|
||||||
embed: Embed,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onLinkClick: (String) -> Unit
|
|
||||||
) {
|
|
||||||
Column {
|
Column {
|
||||||
when (embed.type) {
|
when (embed.type) {
|
||||||
else -> RegularEmbed(embed = embed, modifier = modifier, onLinkClick = onLinkClick)
|
else -> RegularEmbed(embed = embed, modifier = modifier, onLinkClick = onLinkClick)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ fun InReplyTo(
|
||||||
messageId: String,
|
messageId: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
withMention: Boolean = false,
|
withMention: Boolean = false,
|
||||||
onMessageClick: (String) -> Unit = { _ -> },
|
onMessageClick: (String) -> Unit = { _ -> }
|
||||||
) {
|
) {
|
||||||
val message = RevoltAPI.messageCache[messageId]
|
val message = RevoltAPI.messageCache[messageId]
|
||||||
val author = RevoltAPI.userCache[message?.author ?: ""]
|
val author = RevoltAPI.userCache[message?.author ?: ""]
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ enum class InlineBadge {
|
||||||
fun InlineBadge(
|
fun InlineBadge(
|
||||||
badge: InlineBadge,
|
badge: InlineBadge,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
colour: Color = Color.Unspecified,
|
colour: Color = Color.Unspecified
|
||||||
) {
|
) {
|
||||||
when (badge) {
|
when (badge) {
|
||||||
InlineBadge.Bot -> Icon(
|
InlineBadge.Bot -> Icon(
|
||||||
|
|
@ -62,7 +62,7 @@ fun InlineBadges(
|
||||||
teamMember: Boolean = false,
|
teamMember: Boolean = false,
|
||||||
colour: Color = Color.Unspecified,
|
colour: Color = Color.Unspecified,
|
||||||
precedingIfAny: @Composable () -> Unit = {},
|
precedingIfAny: @Composable () -> Unit = {},
|
||||||
followingIfAny: @Composable () -> Unit = {},
|
followingIfAny: @Composable () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val hasBadges = bot || bridge || platformModeration || teamMember
|
val hasBadges = bot || bridge || platformModeration || teamMember
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,11 +64,11 @@ import chat.revolt.api.internals.WebCompat
|
||||||
import chat.revolt.api.internals.solidColor
|
import chat.revolt.api.internals.solidColor
|
||||||
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
|
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
|
||||||
import chat.revolt.api.schemas.AutumnResource
|
import chat.revolt.api.schemas.AutumnResource
|
||||||
|
import chat.revolt.api.schemas.Message as MessageSchema
|
||||||
import chat.revolt.api.schemas.User
|
import chat.revolt.api.schemas.User
|
||||||
import chat.revolt.components.generic.UserAvatar
|
import chat.revolt.components.generic.UserAvatar
|
||||||
import chat.revolt.components.generic.UserAvatarWidthPlaceholder
|
import chat.revolt.components.generic.UserAvatarWidthPlaceholder
|
||||||
import chat.revolt.internals.markdown.LongClickableSpan
|
import chat.revolt.internals.markdown.LongClickableSpan
|
||||||
import chat.revolt.api.schemas.Message as MessageSchema
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun authorColour(message: MessageSchema): Brush {
|
fun authorColour(message: MessageSchema): Brush {
|
||||||
|
|
@ -132,10 +132,7 @@ fun viewAttachmentInBrowser(ctx: android.content.Context, attachment: AutumnReso
|
||||||
viewUrlInBrowser(ctx, url)
|
viewUrlInBrowser(ctx, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formatLongAsTime(time: Long): String {
|
||||||
fun formatLongAsTime(
|
|
||||||
time: Long
|
|
||||||
): String {
|
|
||||||
val date = java.util.Date(time)
|
val date = java.util.Date(time)
|
||||||
|
|
||||||
val withinLastWeek = System.currentTimeMillis() - time < 604800000
|
val withinLastWeek = System.currentTimeMillis() - time < 604800000
|
||||||
|
|
@ -167,7 +164,7 @@ fun Message(
|
||||||
onMessageContextMenu: () -> Unit = {},
|
onMessageContextMenu: () -> Unit = {},
|
||||||
onAvatarClick: () -> Unit = {},
|
onAvatarClick: () -> Unit = {},
|
||||||
canReply: Boolean = false,
|
canReply: Boolean = false,
|
||||||
onReply: () -> Unit = {},
|
onReply: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator()
|
val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -177,7 +174,8 @@ fun Message(
|
||||||
contract = ActivityResultContracts.StartActivityForResult(),
|
contract = ActivityResultContracts.StartActivityForResult(),
|
||||||
onResult = {
|
onResult = {
|
||||||
// do nothing
|
// do nothing
|
||||||
})
|
}
|
||||||
|
)
|
||||||
Column {
|
Column {
|
||||||
if (message.tail == false) {
|
if (message.tail == false) {
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
@ -237,7 +235,7 @@ fun Message(
|
||||||
userId = author.id ?: message.id ?: ULID.makeSpecial(0),
|
userId = author.id ?: message.id ?: ULID.makeSpecial(0),
|
||||||
avatar = author.avatar,
|
avatar = author.avatar,
|
||||||
rawUrl = authorAvatarUrl(message),
|
rawUrl = authorAvatarUrl(message),
|
||||||
onClick = onAvatarClick,
|
onClick = onAvatarClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -251,7 +249,7 @@ fun Message(
|
||||||
text = authorName(message),
|
text = authorName(message),
|
||||||
style = LocalTextStyle.current.copy(
|
style = LocalTextStyle.current.copy(
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
brush = authorColour(message),
|
brush = authorColour(message)
|
||||||
),
|
),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
|
|
@ -285,7 +283,9 @@ fun Message(
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Edit,
|
imageVector = Icons.Default.Edit,
|
||||||
contentDescription = stringResource(id = R.string.edited),
|
contentDescription = stringResource(id = R.string.edited),
|
||||||
tint = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.5f),
|
tint = MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
),
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -343,7 +343,8 @@ fun Message(
|
||||||
x.toFloat()
|
x.toFloat()
|
||||||
)
|
)
|
||||||
val link = spannedString.getSpans(
|
val link = spannedString.getSpans(
|
||||||
off, off,
|
off,
|
||||||
|
off,
|
||||||
LongClickableSpan::class.java
|
LongClickableSpan::class.java
|
||||||
)
|
)
|
||||||
if (link.isNotEmpty()) {
|
if (link.isNotEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,11 @@ fun FileAttachment(attachment: AutumnResource) {
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_file_24dp),
|
painter = painterResource(id = R.drawable.ic_file_24dp),
|
||||||
contentDescription = null,
|
contentDescription = null
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
@ -58,13 +58,13 @@ fun FileAttachment(attachment: AutumnResource) {
|
||||||
Text(
|
Text(
|
||||||
text = attachment.filename ?: "File",
|
text = attachment.filename ?: "File",
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = Formatter.formatShortFileSize(context, attachment.size ?: 0),
|
text = Formatter.formatShortFileSize(context, attachment.size ?: 0),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
color = LocalContentColor.current.copy(alpha = 0.7f),
|
color = LocalContentColor.current.copy(alpha = 0.7f),
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -80,8 +80,10 @@ fun ImageAttachment(attachment: AutumnResource) {
|
||||||
contentScale = ContentScale.Fit,
|
contentScale = ContentScale.Fit,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.aspectRatio(attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()),
|
.aspectRatio(
|
||||||
description = attachment.filename ?: "Image",
|
attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()
|
||||||
|
),
|
||||||
|
description = attachment.filename ?: "Image"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,8 +102,10 @@ fun VideoAttachment(attachment: AutumnResource) {
|
||||||
contentScale = ContentScale.Fit,
|
contentScale = ContentScale.Fit,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.aspectRatio(attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()),
|
.aspectRatio(
|
||||||
description = attachment.filename ?: "Video",
|
attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()
|
||||||
|
),
|
||||||
|
description = attachment.filename ?: "Video"
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -109,7 +113,7 @@ fun VideoAttachment(attachment: AutumnResource) {
|
||||||
.width(48.dp)
|
.width(48.dp)
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)),
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
|
||||||
)
|
)
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
|
|
@ -117,7 +121,7 @@ fun VideoAttachment(attachment: AutumnResource) {
|
||||||
contentDescription = stringResource(id = R.string.media_viewer_play),
|
contentDescription = stringResource(id = R.string.media_viewer_play),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(32.dp)
|
.width(32.dp)
|
||||||
.aspectRatio(1f),
|
.aspectRatio(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +132,7 @@ fun AudioAttachment(attachment: AutumnResource) {
|
||||||
AudioPlayer(
|
AudioPlayer(
|
||||||
url = url,
|
url = url,
|
||||||
filename = attachment.filename ?: "Audio",
|
filename = attachment.filename ?: "Audio",
|
||||||
contentType = attachment.metadata?.type ?: "audio/mpeg",
|
contentType = attachment.metadata?.type ?: "audio/mpeg"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ fun MessageField(
|
||||||
disabled: Boolean = false,
|
disabled: Boolean = false,
|
||||||
editMode: Boolean = false,
|
editMode: Boolean = false,
|
||||||
cancelEdit: () -> Unit = {},
|
cancelEdit: () -> Unit = {},
|
||||||
onFocusChange: (Boolean) -> Unit = {},
|
onFocusChange: (Boolean) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val placeholderResource = when (channelType) {
|
val placeholderResource = when (channelType) {
|
||||||
|
|
@ -126,7 +126,7 @@ fun MessageField(
|
||||||
channelName
|
channelName
|
||||||
),
|
),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
colors = TextFieldDefaults.colors(
|
colors = TextFieldDefaults.colors(
|
||||||
|
|
@ -139,7 +139,7 @@ fun MessageField(
|
||||||
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
1.dp
|
1.dp
|
||||||
),
|
),
|
||||||
focusedContainerColor = Color.Transparent,
|
focusedContainerColor = Color.Transparent
|
||||||
),
|
),
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
|
|
@ -185,7 +185,8 @@ fun MessageField(
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
AnimatedVisibility(sendButtonVisible,
|
AnimatedVisibility(
|
||||||
|
sendButtonVisible,
|
||||||
enter = expandIn(initialSize = { full ->
|
enter = expandIn(initialSize = { full ->
|
||||||
IntSize(
|
IntSize(
|
||||||
0,
|
0,
|
||||||
|
|
@ -203,7 +204,8 @@ fun MessageField(
|
||||||
}) + slideOutHorizontally(
|
}) + slideOutHorizontally(
|
||||||
animationSpec = RevoltTweenInt,
|
animationSpec = RevoltTweenInt,
|
||||||
targetOffsetX = { it }
|
targetOffsetX = { it }
|
||||||
) + fadeOut(animationSpec = RevoltTweenFloat)) {
|
) + fadeOut(animationSpec = RevoltTweenFloat)
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
when {
|
when {
|
||||||
editMode -> Icons.Default.Edit
|
editMode -> Icons.Default.Edit
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,12 @@ import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RoleChip(
|
fun RoleChip(label: String, brush: Brush, modifier: Modifier = Modifier) {
|
||||||
label: String,
|
|
||||||
brush: Brush,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clip(MaterialTheme.shapes.small)
|
.clip(MaterialTheme.shapes.small)
|
||||||
.background(
|
.background(
|
||||||
brush = brush,
|
brush = brush
|
||||||
)
|
)
|
||||||
.background(
|
.background(
|
||||||
// darken the background a bit
|
// darken the background a bit
|
||||||
|
|
|
||||||
|
|
@ -39,13 +39,11 @@ enum class SystemMessageType(val type: String) {
|
||||||
USER_KICKED("user_kicked"),
|
USER_KICKED("user_kicked"),
|
||||||
USER_LEFT("user_left"),
|
USER_LEFT("user_left"),
|
||||||
USER_JOINED("user_joined"),
|
USER_JOINED("user_joined"),
|
||||||
TEXT("text"),
|
TEXT("text")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SystemMessage(
|
fun SystemMessage(message: Message) {
|
||||||
message: Message
|
|
||||||
) {
|
|
||||||
if (message.system == null) return
|
if (message.system == null) return
|
||||||
|
|
||||||
val systemMessageType =
|
val systemMessageType =
|
||||||
|
|
@ -72,7 +70,9 @@ fun SystemMessage(
|
||||||
|
|
||||||
when (systemMessageType) {
|
when (systemMessageType) {
|
||||||
SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> {
|
SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> {
|
||||||
Text(text = "Channel ownership changed from ${message.system.from} to ${message.system.to}")
|
Text(
|
||||||
|
text = "Channel ownership changed from ${message.system.from} to ${message.system.to}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemMessageType.CHANNEL_ICON_CHANGED -> {
|
SystemMessageType.CHANNEL_ICON_CHANGED -> {
|
||||||
|
|
@ -120,11 +120,7 @@ fun SystemMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SystemMessageIcon(
|
fun SystemMessageIcon(type: SystemMessageType, modifier: Modifier = Modifier, size: Dp = 24.dp) {
|
||||||
type: SystemMessageType,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
size: Dp = 24.dp
|
|
||||||
) {
|
|
||||||
when (type) {
|
when (type) {
|
||||||
SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> {
|
SystemMessageType.CHANNEL_OWNERSHIP_CHANGED -> {
|
||||||
Icon(
|
Icon(
|
||||||
|
|
@ -138,7 +134,9 @@ fun SystemMessageIcon(
|
||||||
SystemMessageType.CHANNEL_ICON_CHANGED -> {
|
SystemMessageType.CHANNEL_ICON_CHANGED -> {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_image_multiple_24dp),
|
painter = painterResource(R.drawable.ic_image_multiple_24dp),
|
||||||
contentDescription = stringResource(R.string.system_message_channel_icon_changed_alt),
|
contentDescription = stringResource(
|
||||||
|
R.string.system_message_channel_icon_changed_alt
|
||||||
|
),
|
||||||
tint = LocalContentColor.current,
|
tint = LocalContentColor.current,
|
||||||
modifier = modifier.size(size)
|
modifier = modifier.size(size)
|
||||||
)
|
)
|
||||||
|
|
@ -147,7 +145,9 @@ fun SystemMessageIcon(
|
||||||
SystemMessageType.CHANNEL_DESCRIPTION_CHANGED -> {
|
SystemMessageType.CHANNEL_DESCRIPTION_CHANGED -> {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_text_box_multiple_24dp),
|
painter = painterResource(R.drawable.ic_text_box_multiple_24dp),
|
||||||
contentDescription = stringResource(R.string.system_message_channel_description_changed_alt),
|
contentDescription = stringResource(
|
||||||
|
R.string.system_message_channel_description_changed_alt
|
||||||
|
),
|
||||||
tint = LocalContentColor.current,
|
tint = LocalContentColor.current,
|
||||||
modifier = modifier.size(size)
|
modifier = modifier.size(size)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,7 @@ import androidx.compose.ui.unit.sp
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimeRift(
|
fun TimeRift(modifier: Modifier = Modifier, onMessageLoad: () -> Unit) {
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onMessageLoad: () -> Unit,
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(vertical = 10.dp)
|
.padding(vertical = 10.dp)
|
||||||
|
|
|
||||||
|
|
@ -81,9 +81,7 @@ import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun EmojiPicker(
|
fun EmojiPicker(onEmojiSelected: (String) -> Unit) {
|
||||||
onEmojiSelected: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
||||||
|
|
@ -104,7 +102,9 @@ fun EmojiPicker(
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
val firstVisible = gridState.firstVisibleItemIndex
|
val firstVisible = gridState.firstVisibleItemIndex
|
||||||
val firstCategory =
|
val firstCategory =
|
||||||
categorySpans.entries.firstOrNull { it.value.first <= firstVisible && it.value.second >= firstVisible }?.key
|
categorySpans.entries.firstOrNull {
|
||||||
|
it.value.first <= firstVisible && it.value.second >= firstVisible
|
||||||
|
}?.key
|
||||||
|
|
||||||
firstCategory
|
firstCategory
|
||||||
}
|
}
|
||||||
|
|
@ -234,10 +234,14 @@ fun EmojiPicker(
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
.then(
|
.then(
|
||||||
if (searchQuery.isNotEmpty()) Modifier.clickable {
|
if (searchQuery.isNotEmpty()) {
|
||||||
searchQuery = ""
|
Modifier.clickable {
|
||||||
focusManager.clearFocus() // this prevents the text field Z-below from gaining focus
|
searchQuery = ""
|
||||||
} else Modifier
|
focusManager.clearFocus() // this prevents the text field Z-below from gaining focus
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.align(Alignment.CenterEnd)
|
.align(Alignment.CenterEnd)
|
||||||
.alpha(clearQueryButtonOpacity.value)
|
.alpha(clearQueryButtonOpacity.value)
|
||||||
|
|
@ -259,7 +263,7 @@ fun EmojiPicker(
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
FitzpatrickSkinTone.entries.forEach { skinTone ->
|
FitzpatrickSkinTone.entries.forEach { skinTone ->
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -273,12 +277,24 @@ fun EmojiPicker(
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.clickable(
|
.clickable(
|
||||||
onClickLabel = when (skinTone) {
|
onClickLabel = when (skinTone) {
|
||||||
FitzpatrickSkinTone.None -> stringResource(R.string.emoji_picker_skin_tone_none)
|
FitzpatrickSkinTone.None -> stringResource(
|
||||||
FitzpatrickSkinTone.Light -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_1_2)
|
R.string.emoji_picker_skin_tone_none
|
||||||
FitzpatrickSkinTone.MediumLight -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_3)
|
)
|
||||||
FitzpatrickSkinTone.Medium -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_4)
|
FitzpatrickSkinTone.Light -> stringResource(
|
||||||
FitzpatrickSkinTone.MediumDark -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_5)
|
R.string.emoji_picker_skin_tone_fitzpatrick_1_2
|
||||||
FitzpatrickSkinTone.Dark -> stringResource(R.string.emoji_picker_skin_tone_fitzpatrick_6)
|
)
|
||||||
|
FitzpatrickSkinTone.MediumLight -> stringResource(
|
||||||
|
R.string.emoji_picker_skin_tone_fitzpatrick_3
|
||||||
|
)
|
||||||
|
FitzpatrickSkinTone.Medium -> stringResource(
|
||||||
|
R.string.emoji_picker_skin_tone_fitzpatrick_4
|
||||||
|
)
|
||||||
|
FitzpatrickSkinTone.MediumDark -> stringResource(
|
||||||
|
R.string.emoji_picker_skin_tone_fitzpatrick_5
|
||||||
|
)
|
||||||
|
FitzpatrickSkinTone.Dark -> stringResource(
|
||||||
|
R.string.emoji_picker_skin_tone_fitzpatrick_6
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
currentSkinTone = skinTone
|
currentSkinTone = skinTone
|
||||||
|
|
@ -286,7 +302,7 @@ fun EmojiPicker(
|
||||||
focusManager.clearFocus() // this prevents the text field Z-below from gaining focus
|
focusManager.clearFocus() // this prevents the text field Z-below from gaining focus
|
||||||
}
|
}
|
||||||
.aspectRatio(1f),
|
.aspectRatio(1f),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -323,7 +339,9 @@ fun EmojiPicker(
|
||||||
} else {
|
} else {
|
||||||
Icons.Default.KeyboardArrowRight
|
Icons.Default.KeyboardArrowRight
|
||||||
},
|
},
|
||||||
contentDescription = stringResource(R.string.emoji_picker_close_skin_tone_menu),
|
contentDescription = stringResource(
|
||||||
|
R.string.emoji_picker_close_skin_tone_menu
|
||||||
|
),
|
||||||
tint = LocalContentColor.current,
|
tint = LocalContentColor.current,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.alpha(skinToneMenuCloseHintIconOpacity)
|
.alpha(skinToneMenuCloseHintIconOpacity)
|
||||||
|
|
@ -340,7 +358,7 @@ fun EmojiPicker(
|
||||||
.horizontalScroll(categoryRowScrollState)
|
.horizontalScroll(categoryRowScrollState)
|
||||||
.height(37.dp),
|
.height(37.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
servers.forEach { server ->
|
servers.forEach { server ->
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -350,13 +368,17 @@ fun EmojiPicker(
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val index =
|
val index =
|
||||||
pickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server }
|
pickerList.indexOfFirst {
|
||||||
|
it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server
|
||||||
|
}
|
||||||
gridState.animateScrollToItem(index)
|
gridState.animateScrollToItem(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.then(
|
.then(
|
||||||
if (currentCategory.value is Category.ServerEmoteCategory && (currentCategory.value as Category.ServerEmoteCategory).server == server) {
|
if (currentCategory.value is Category.ServerEmoteCategory && (currentCategory.value as Category.ServerEmoteCategory).server == server) {
|
||||||
Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f))
|
Modifier.background(
|
||||||
|
MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
|
@ -364,7 +386,7 @@ fun EmojiPicker(
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.padding(4.dp),
|
.padding(4.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
if (server.icon == null) {
|
if (server.icon == null) {
|
||||||
IconPlaceholder(
|
IconPlaceholder(
|
||||||
|
|
@ -393,13 +415,17 @@ fun EmojiPicker(
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val index =
|
val index =
|
||||||
pickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == category }
|
pickerList.indexOfFirst {
|
||||||
|
it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == category
|
||||||
|
}
|
||||||
gridState.animateScrollToItem(index)
|
gridState.animateScrollToItem(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.then(
|
.then(
|
||||||
if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
|
if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
|
||||||
Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f))
|
Modifier.background(
|
||||||
|
MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
|
@ -407,24 +433,44 @@ fun EmojiPicker(
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.padding(4.dp),
|
.padding(4.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = when (category) {
|
painter = when (category) {
|
||||||
UnicodeEmojiSection.Smileys -> painterResource(R.drawable.ic_emoticon_24dp)
|
UnicodeEmojiSection.Smileys -> painterResource(
|
||||||
UnicodeEmojiSection.People -> painterResource(R.drawable.ic_human_greeting_variant_24dp)
|
R.drawable.ic_emoticon_24dp
|
||||||
UnicodeEmojiSection.Animals -> painterResource(R.drawable.ic_snake_24dp)
|
)
|
||||||
UnicodeEmojiSection.Food -> painterResource(R.drawable.ic_glass_mug_variant_24dp)
|
UnicodeEmojiSection.People -> painterResource(
|
||||||
UnicodeEmojiSection.Travel -> painterResource(R.drawable.ic_train_bus_24dp)
|
R.drawable.ic_human_greeting_variant_24dp
|
||||||
UnicodeEmojiSection.Activities -> painterResource(R.drawable.ic_skate_24dp)
|
)
|
||||||
UnicodeEmojiSection.Objects -> painterResource(R.drawable.ic_table_chair_24dp)
|
UnicodeEmojiSection.Animals -> painterResource(
|
||||||
UnicodeEmojiSection.Symbols -> painterResource(R.drawable.ic_symbol_24dp)
|
R.drawable.ic_snake_24dp
|
||||||
UnicodeEmojiSection.Flags -> painterResource(R.drawable.ic_flag_24dp)
|
)
|
||||||
|
UnicodeEmojiSection.Food -> painterResource(
|
||||||
|
R.drawable.ic_glass_mug_variant_24dp
|
||||||
|
)
|
||||||
|
UnicodeEmojiSection.Travel -> painterResource(
|
||||||
|
R.drawable.ic_train_bus_24dp
|
||||||
|
)
|
||||||
|
UnicodeEmojiSection.Activities -> painterResource(
|
||||||
|
R.drawable.ic_skate_24dp
|
||||||
|
)
|
||||||
|
UnicodeEmojiSection.Objects -> painterResource(
|
||||||
|
R.drawable.ic_table_chair_24dp
|
||||||
|
)
|
||||||
|
UnicodeEmojiSection.Symbols -> painterResource(
|
||||||
|
R.drawable.ic_symbol_24dp
|
||||||
|
)
|
||||||
|
UnicodeEmojiSection.Flags -> painterResource(
|
||||||
|
R.drawable.ic_flag_24dp
|
||||||
|
)
|
||||||
},
|
},
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
|
tint = if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
|
||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary
|
||||||
} else LocalContentColor.current
|
} else {
|
||||||
|
LocalContentColor.current
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -443,7 +489,7 @@ fun EmojiPicker(
|
||||||
key = "searchResultsHeader",
|
key = "searchResultsHeader",
|
||||||
span = {
|
span = {
|
||||||
GridItemSpan(spanCount)
|
GridItemSpan(spanCount)
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.emoji_picker_search_results_header),
|
text = stringResource(R.string.emoji_picker_search_results_header),
|
||||||
|
|
@ -481,7 +527,7 @@ fun EmojiPicker(
|
||||||
key = "searchResultsFooter",
|
key = "searchResultsFooter",
|
||||||
span = {
|
span = {
|
||||||
GridItemSpan(spanCount)
|
GridItemSpan(spanCount)
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
@ -516,7 +562,7 @@ fun ColumnScope.PickerItem(
|
||||||
skinToneFactory: (EmojiPickerItem.UnicodeEmoji) -> String,
|
skinToneFactory: (EmojiPickerItem.UnicodeEmoji) -> String,
|
||||||
onClick: (EmojiPickerItem) -> Unit,
|
onClick: (EmojiPickerItem) -> Unit,
|
||||||
onServerEmoteInfo: (String) -> Unit,
|
onServerEmoteInfo: (String) -> Unit,
|
||||||
lesserHeaders: Boolean = false,
|
lesserHeaders: Boolean = false
|
||||||
) {
|
) {
|
||||||
when (item) {
|
when (item) {
|
||||||
is EmojiPickerItem.UnicodeEmoji -> {
|
is EmojiPickerItem.UnicodeEmoji -> {
|
||||||
|
|
@ -528,7 +574,7 @@ fun ColumnScope.PickerItem(
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = skinToneFactory(item),
|
text = skinToneFactory(item),
|
||||||
|
|
@ -548,7 +594,7 @@ fun ColumnScope.PickerItem(
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
RemoteImage(
|
RemoteImage(
|
||||||
url = "$REVOLT_FILES/emojis/${item.emote.id}/emoji.gif",
|
url = "$REVOLT_FILES/emojis/${item.emote.id}/emoji.gif",
|
||||||
|
|
@ -564,9 +610,12 @@ fun ColumnScope.PickerItem(
|
||||||
is EmojiPickerItem.Section -> {
|
is EmojiPickerItem.Section -> {
|
||||||
Text(
|
Text(
|
||||||
when (item.category) {
|
when (item.category) {
|
||||||
is Category.UnicodeEmojiCategory -> stringResource(item.category.definition.nameResource)
|
is Category.UnicodeEmojiCategory -> stringResource(
|
||||||
is Category.ServerEmoteCategory -> item.category.server.name
|
item.category.definition.nameResource
|
||||||
?: stringResource(R.string.unknown)
|
)
|
||||||
|
is Category.ServerEmoteCategory ->
|
||||||
|
item.category.server.name
|
||||||
|
?: stringResource(R.string.unknown)
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import androidx.compose.ui.unit.dp
|
||||||
fun CollapsibleCard(
|
fun CollapsibleCard(
|
||||||
header: @Composable () -> Unit,
|
header: @Composable () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ fun CollapsibleCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable { expanded = !expanded }
|
.clickable { expanded = !expanded }
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalTextStyle provides MaterialTheme.typography.titleMedium.copy(
|
LocalTextStyle provides MaterialTheme.typography.titleMedium.copy(
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ fun FormTextField(
|
||||||
type: KeyboardType = KeyboardType.Text,
|
type: KeyboardType = KeyboardType.Text,
|
||||||
supportingText: @Composable (() -> Unit)? = null,
|
supportingText: @Composable (() -> Unit)? = null,
|
||||||
singleLine: Boolean = true,
|
singleLine: Boolean = true,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true
|
||||||
) {
|
) {
|
||||||
TextField(
|
TextField(
|
||||||
value = value,
|
value = value,
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,14 @@ fun IconPlaceholder(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))
|
||||||
.then(
|
.then(
|
||||||
if (onClick != NoopHandler || onLongClick != NoopHandler) Modifier.combinedClickable(
|
if (onClick != NoopHandler || onLongClick != NoopHandler) {
|
||||||
onClick = onClick,
|
Modifier.combinedClickable(
|
||||||
onLongClick = onLongClick
|
onClick = onClick,
|
||||||
)
|
onLongClick = onLongClick
|
||||||
else Modifier
|
)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ fun UIMarkdown(
|
||||||
text: String,
|
text: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
fontSize: TextUnit = LocalTextStyle.current.fontSize,
|
fontSize: TextUnit = LocalTextStyle.current.fontSize,
|
||||||
maxLines: Int = Int.MAX_VALUE,
|
maxLines: Int = Int.MAX_VALUE
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val foregroundColor = LocalContentColor.current
|
val foregroundColor = LocalContentColor.current
|
||||||
|
|
@ -61,7 +61,7 @@ fun UIMarkdown(
|
||||||
.addRevoltRules(context)
|
.addRevoltRules(context)
|
||||||
.addRules(
|
.addRules(
|
||||||
createCodeRule(context, codeBlockColor.toArgb()),
|
createCodeRule(context, codeBlockColor.toArgb()),
|
||||||
createInlineCodeRule(context, codeBlockColor.toArgb()),
|
createInlineCodeRule(context, codeBlockColor.toArgb())
|
||||||
)
|
)
|
||||||
.addRules(
|
.addRules(
|
||||||
SimpleMarkdownRules.createSimpleMarkdownRules(
|
SimpleMarkdownRules.createSimpleMarkdownRules(
|
||||||
|
|
@ -109,7 +109,7 @@ fun UIMarkdown(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
update = {
|
update = {
|
||||||
it.text = spannableStringBuilder.value
|
it.text = spannableStringBuilder.value
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,6 +118,6 @@ fun UIMarkdown(
|
||||||
fun UIMarkdownPreview() {
|
fun UIMarkdownPreview() {
|
||||||
UIMarkdown(
|
UIMarkdown(
|
||||||
text = "Hello, **world**! <@01F1WKM5TK2V6KCZWR6DGBJDTZ> [link](https://google.com) `code`\n\n```kt\nfun main() {\n println(\"Hello, world!\")\n}\n```",
|
text = "Hello, **world**! <@01F1WKM5TK2V6KCZWR6DGBJDTZ> [link](https://google.com) `code`\n\n```kt\nfun main() {\n println(\"Hello, world!\")\n}\n```",
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +72,7 @@ fun NonIdealState(
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
|
||||||
) {
|
) {
|
||||||
actions()
|
actions()
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +113,7 @@ fun NonIdealStatePreviewNoActions() {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
title = { Text("Error") },
|
title = { Text("Error") },
|
||||||
description = { Text("Could not load channels.") },
|
description = { Text("Could not load channels.") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,6 +128,6 @@ fun NonIdealStatePreviewNoDescription() {
|
||||||
modifier = Modifier.size(it)
|
modifier = Modifier.size(it)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
title = { Text("No channels") },
|
title = { Text("No channels") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +28,7 @@ fun PageHeader(
|
||||||
showBackButton: Boolean = false,
|
showBackButton: Boolean = false,
|
||||||
onBackButtonClicked: () -> Unit = {},
|
onBackButtonClicked: () -> Unit = {},
|
||||||
additionalButtons: @Composable () -> Unit = {},
|
additionalButtons: @Composable () -> Unit = {},
|
||||||
maxLines: Int = Int.MAX_VALUE,
|
maxLines: Int = Int.MAX_VALUE
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
|
@ -53,7 +53,7 @@ fun PageHeader(
|
||||||
),
|
),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(horizontal = 15.dp, vertical = 15.dp)
|
.padding(horizontal = 15.dp, vertical = 15.dp)
|
||||||
.weight(1f),
|
.weight(1f)
|
||||||
)
|
)
|
||||||
additionalButtons()
|
additionalButtons()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,17 @@ fun RemoteImage(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
contentScale: ContentScale = ContentScale.Crop,
|
contentScale: ContentScale = ContentScale.Crop,
|
||||||
width: Int = 0,
|
width: Int = 0,
|
||||||
height: Int = 0,
|
height: Int = 0
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
fun pxAsDp(px: Int): Dp {
|
fun pxAsDp(px: Int): Dp {
|
||||||
return (px / (context.resources
|
return (
|
||||||
.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).dp
|
px / (
|
||||||
|
context.resources
|
||||||
|
.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT
|
||||||
|
)
|
||||||
|
).dp
|
||||||
}
|
}
|
||||||
|
|
||||||
GlideImage(
|
GlideImage(
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,11 @@ fun SheetClickable(
|
||||||
label: @Composable (TextStyle) -> Unit,
|
label: @Composable (TextStyle) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
dangerous: Boolean = false,
|
dangerous: Boolean = false,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(LocalContentColor provides if (dangerous) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onBackground) {
|
CompositionLocalProvider(
|
||||||
|
LocalContentColor provides if (dangerous) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onBackground
|
||||||
|
) {
|
||||||
Box(modifier = modifier.padding(vertical = 4.dp)) {
|
Box(modifier = modifier.padding(vertical = 4.dp)) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -44,7 +46,7 @@ fun SheetClickable(
|
||||||
label(
|
label(
|
||||||
MaterialTheme.typography.bodyMedium.copy(
|
MaterialTheme.typography.bodyMedium.copy(
|
||||||
color = LocalContentColor.current,
|
color = LocalContentColor.current,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ fun UserAvatar(
|
||||||
size: Dp = 40.dp,
|
size: Dp = 40.dp,
|
||||||
presenceSize: Dp = 16.dp,
|
presenceSize: Dp = 16.dp,
|
||||||
onLongClick: (() -> Unit)? = null,
|
onLongClick: (() -> Unit)? = null,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
@ -94,29 +94,35 @@ fun UserAvatar(
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.size(size)
|
.size(size)
|
||||||
.then(
|
.then(
|
||||||
if (onLongClick != null || onClick != null) Modifier
|
if (onLongClick != null || onClick != null) {
|
||||||
.combinedClickable(
|
Modifier
|
||||||
onClick = { onClick?.invoke() },
|
.combinedClickable(
|
||||||
onLongClick = { onLongClick?.invoke() }
|
onClick = { onClick?.invoke() },
|
||||||
)
|
onLongClick = { onLongClick?.invoke() }
|
||||||
else Modifier
|
)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
RemoteImage(
|
RemoteImage(
|
||||||
url = "$REVOLT_BASE/users/${userId}/default_avatar",
|
url = "$REVOLT_BASE/users/$userId/default_avatar",
|
||||||
description = stringResource(id = R.string.avatar_alt, username),
|
description = stringResource(id = R.string.avatar_alt, username),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.size(size)
|
.size(size)
|
||||||
.then(
|
.then(
|
||||||
if (onLongClick != null || onClick != null) Modifier
|
if (onLongClick != null || onClick != null) {
|
||||||
.combinedClickable(
|
Modifier
|
||||||
onClick = { onClick?.invoke() },
|
.combinedClickable(
|
||||||
onLongClick = { onLongClick?.invoke() }
|
onClick = { onClick?.invoke() },
|
||||||
)
|
onLongClick = { onLongClick?.invoke() }
|
||||||
else Modifier
|
)
|
||||||
),
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,7 +141,7 @@ fun GroupIcon(
|
||||||
rawUrl: String? = null,
|
rawUrl: String? = null,
|
||||||
size: Dp = 40.dp,
|
size: Dp = 40.dp,
|
||||||
onLongClick: (() -> Unit)? = null,
|
onLongClick: (() -> Unit)? = null,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
@ -151,12 +157,15 @@ fun GroupIcon(
|
||||||
.clip(MaterialTheme.shapes.small)
|
.clip(MaterialTheme.shapes.small)
|
||||||
.size(size)
|
.size(size)
|
||||||
.then(
|
.then(
|
||||||
if (onLongClick != null || onClick != null) Modifier
|
if (onLongClick != null || onClick != null) {
|
||||||
.combinedClickable(
|
Modifier
|
||||||
onClick = { onClick?.invoke() },
|
.combinedClickable(
|
||||||
onLongClick = { onLongClick?.invoke() }
|
onClick = { onClick?.invoke() },
|
||||||
)
|
onLongClick = { onLongClick?.invoke() }
|
||||||
else Modifier
|
)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -164,12 +173,15 @@ fun GroupIcon(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(size)
|
.size(size)
|
||||||
.then(
|
.then(
|
||||||
if (onLongClick != null || onClick != null) Modifier
|
if (onLongClick != null || onClick != null) {
|
||||||
.combinedClickable(
|
Modifier
|
||||||
onClick = { onClick?.invoke() },
|
.combinedClickable(
|
||||||
onLongClick = { onLongClick?.invoke() }
|
onClick = { onClick?.invoke() },
|
||||||
)
|
onLongClick = { onLongClick?.invoke() }
|
||||||
else Modifier
|
)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.clip(MaterialTheme.shapes.small)
|
.clip(MaterialTheme.shapes.small)
|
||||||
.background(MaterialTheme.colorScheme.primary)
|
.background(MaterialTheme.colorScheme.primary)
|
||||||
|
|
@ -186,9 +198,7 @@ fun GroupIcon(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserAvatarWidthPlaceholder(
|
fun UserAvatarWidthPlaceholder(size: Dp = 40.dp) {
|
||||||
size: Dp = 40.dp,
|
|
||||||
) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(size)
|
.width(size)
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ fun WebMarkdown(
|
||||||
text: String,
|
text: String,
|
||||||
maskLoading: Boolean = false,
|
maskLoading: Boolean = false,
|
||||||
simpleLineBreaks: Boolean = true,
|
simpleLineBreaks: Boolean = true,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val contentColour = LocalContentColor.current
|
val contentColour = LocalContentColor.current
|
||||||
val materialColourScheme = MaterialTheme.colorScheme
|
val materialColourScheme = MaterialTheme.colorScheme
|
||||||
|
|
@ -97,9 +97,11 @@ fun WebMarkdown(
|
||||||
): Boolean {
|
): Boolean {
|
||||||
// Capture clicks on invite links
|
// Capture clicks on invite links
|
||||||
if (webResourceRequest.url.host == "rvlt.gg" ||
|
if (webResourceRequest.url.host == "rvlt.gg" ||
|
||||||
(webResourceRequest.url.host?.endsWith("revolt.chat") == true && webResourceRequest.url.path?.startsWith(
|
(
|
||||||
"/invite"
|
webResourceRequest.url.host?.endsWith("revolt.chat") == true && webResourceRequest.url.path?.startsWith(
|
||||||
) == true)
|
"/invite"
|
||||||
|
) == true
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
val intent = Intent(
|
val intent = Intent(
|
||||||
context,
|
context,
|
||||||
|
|
@ -129,7 +131,7 @@ fun WebMarkdown(
|
||||||
}
|
}
|
||||||
|
|
||||||
loadUrl(
|
loadUrl(
|
||||||
"$REVOLT_APP/_android_assets/webmarkdown/renderer.html",
|
"$REVOLT_APP/_android_assets/webmarkdown/renderer.html"
|
||||||
)
|
)
|
||||||
|
|
||||||
settings.apply {
|
settings.apply {
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,7 @@ import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
fun AudioPlayer(
|
fun AudioPlayer(url: String, filename: String, contentType: String) {
|
||||||
url: String,
|
|
||||||
filename: String,
|
|
||||||
contentType: String,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val showMenu = remember { mutableStateOf(false) }
|
val showMenu = remember { mutableStateOf(false) }
|
||||||
|
|
@ -201,7 +197,7 @@ fun AudioPlayer(
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 4.dp),
|
modifier = Modifier.padding(horizontal = 4.dp, vertical = 4.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = filename,
|
text = filename,
|
||||||
|
|
@ -222,7 +218,7 @@ fun AudioPlayer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
if (isPlaying.value) {
|
if (isPlaying.value) {
|
||||||
|
|
@ -237,12 +233,12 @@ fun AudioPlayer(
|
||||||
if (isPlaying.value) {
|
if (isPlaying.value) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_pause_24dp),
|
painter = painterResource(R.drawable.ic_pause_24dp),
|
||||||
contentDescription = stringResource(R.string.media_viewer_pause),
|
contentDescription = stringResource(R.string.media_viewer_pause)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.PlayArrow,
|
imageVector = Icons.Filled.PlayArrow,
|
||||||
contentDescription = stringResource(R.string.media_viewer_play),
|
contentDescription = stringResource(R.string.media_viewer_play)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -270,7 +266,7 @@ fun AudioPlayer(
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.MoreVert,
|
imageVector = Icons.Filled.MoreVert,
|
||||||
contentDescription = stringResource(R.string.media_viewer_more),
|
contentDescription = stringResource(R.string.media_viewer_more)
|
||||||
)
|
)
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = showMenu.value,
|
expanded = showMenu.value,
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ private fun Long.formatAsLengthDuration(): String {
|
||||||
if (days > 0) "$days:" else null,
|
if (days > 0) "$days:" else null,
|
||||||
if (hours > 0) "$hours".padStart(2, '0') + ":" else null,
|
if (hours > 0) "$hours".padStart(2, '0') + ":" else null,
|
||||||
"$minutes".padStart(2, '0') + ":",
|
"$minutes".padStart(2, '0') + ":",
|
||||||
"$seconds".padStart(2, '0'),
|
"$seconds".padStart(2, '0')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ fun InbuiltMediaPicker(
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
onMediaSelected: (Media) -> Unit,
|
onMediaSelected: (Media) -> Unit,
|
||||||
pendingMedia: List<String>,
|
pendingMedia: List<String>,
|
||||||
disabled: Boolean = false,
|
disabled: Boolean = false
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
|
@ -140,14 +140,16 @@ fun InbuiltMediaPicker(
|
||||||
onResult = { mediaPermissionState ->
|
onResult = { mediaPermissionState ->
|
||||||
hasPhotoPermission = mediaPermissionState[Manifest.permission.READ_MEDIA_IMAGES] == true
|
hasPhotoPermission = mediaPermissionState[Manifest.permission.READ_MEDIA_IMAGES] == true
|
||||||
hasVideoPermission = mediaPermissionState[Manifest.permission.READ_MEDIA_VIDEO] == true
|
hasVideoPermission = mediaPermissionState[Manifest.permission.READ_MEDIA_VIDEO] == true
|
||||||
mediaPermissionIsPartial = if (!hasMediaPermissions
|
mediaPermissionIsPartial = if (!hasMediaPermissions &&
|
||||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
|
||||||
&& mediaPermissionState[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED] == true
|
mediaPermissionState[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED] == true
|
||||||
) {
|
) {
|
||||||
true
|
true
|
||||||
} else if (hasMediaPermissions) {
|
} else if (hasMediaPermissions) {
|
||||||
false
|
false
|
||||||
} else null
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -158,9 +160,9 @@ fun InbuiltMediaPicker(
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(hasMediaPermissions) {
|
LaunchedEffect(hasMediaPermissions) {
|
||||||
mediaPermissionIsPartial = if (!hasMediaPermissions
|
mediaPermissionIsPartial = if (!hasMediaPermissions &&
|
||||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
|
||||||
&& ContextCompat.checkSelfPermission(
|
ContextCompat.checkSelfPermission(
|
||||||
context,
|
context,
|
||||||
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
|
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
|
||||||
) == PermissionChecker.PERMISSION_GRANTED
|
) == PermissionChecker.PERMISSION_GRANTED
|
||||||
|
|
@ -168,7 +170,9 @@ fun InbuiltMediaPicker(
|
||||||
true
|
true
|
||||||
} else if (hasMediaPermissions) {
|
} else if (hasMediaPermissions) {
|
||||||
false
|
false
|
||||||
} else null
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(hasMediaPermissions, mediaPermissionIsPartial) {
|
LaunchedEffect(hasMediaPermissions, mediaPermissionIsPartial) {
|
||||||
|
|
@ -178,7 +182,7 @@ fun InbuiltMediaPicker(
|
||||||
MediaStore.Images.ImageColumns.RESOLUTION,
|
MediaStore.Images.ImageColumns.RESOLUTION,
|
||||||
MediaStore.Images.ImageColumns.ORIENTATION,
|
MediaStore.Images.ImageColumns.ORIENTATION,
|
||||||
MediaStore.Images.ImageColumns.MIME_TYPE,
|
MediaStore.Images.ImageColumns.MIME_TYPE,
|
||||||
MediaStore.Video.VideoColumns.DURATION,
|
MediaStore.Video.VideoColumns.DURATION
|
||||||
)
|
)
|
||||||
|
|
||||||
val selection: String? = null
|
val selection: String? = null
|
||||||
|
|
@ -199,16 +203,30 @@ fun InbuiltMediaPicker(
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
try {
|
try {
|
||||||
val id =
|
val id =
|
||||||
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID))
|
cursor.getLong(
|
||||||
|
cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID)
|
||||||
|
)
|
||||||
val resolution =
|
val resolution =
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.RESOLUTION))
|
cursor.getString(
|
||||||
|
cursor.getColumnIndexOrThrow(
|
||||||
|
MediaStore.Images.ImageColumns.RESOLUTION
|
||||||
|
)
|
||||||
|
)
|
||||||
val orientation =
|
val orientation =
|
||||||
cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.ORIENTATION))
|
cursor.getInt(
|
||||||
|
cursor.getColumnIndexOrThrow(
|
||||||
|
MediaStore.Images.ImageColumns.ORIENTATION
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val swapDimensions = orientation == 90 || orientation == 270
|
val swapDimensions = orientation == 90 || orientation == 270
|
||||||
|
|
||||||
val isVideo =
|
val isVideo =
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.MIME_TYPE))
|
cursor.getString(
|
||||||
|
cursor.getColumnIndexOrThrow(
|
||||||
|
MediaStore.Images.ImageColumns.MIME_TYPE
|
||||||
|
)
|
||||||
|
)
|
||||||
.startsWith("video")
|
.startsWith("video")
|
||||||
|
|
||||||
val durationColumn =
|
val durationColumn =
|
||||||
|
|
@ -293,7 +311,7 @@ fun InbuiltMediaPicker(
|
||||||
alpha = 0.5f
|
alpha = 0.5f
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
@ -311,12 +329,14 @@ fun InbuiltMediaPicker(
|
||||||
permissionRequester.launch(
|
permissionRequester.launch(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
Manifest.permission.READ_MEDIA_IMAGES,
|
Manifest.permission.READ_MEDIA_IMAGES,
|
||||||
Manifest.permission.READ_MEDIA_VIDEO,
|
Manifest.permission.READ_MEDIA_VIDEO
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(id = R.string.file_picker_permission_request_cta))
|
Text(
|
||||||
|
text = stringResource(id = R.string.file_picker_permission_request_cta)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -324,7 +344,7 @@ fun InbuiltMediaPicker(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
mediaPermissionIsPartial == true
|
mediaPermissionIsPartial == true
|
||||||
|
|
@ -343,7 +363,9 @@ fun InbuiltMediaPicker(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.drawable.ux_file_unpartialise_request),
|
painter = painterResource(
|
||||||
|
id = R.drawable.ux_file_unpartialise_request
|
||||||
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(52.dp),
|
.size(52.dp),
|
||||||
contentDescription = null // decorative
|
contentDescription = null // decorative
|
||||||
|
|
@ -354,14 +376,18 @@ fun InbuiltMediaPicker(
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.file_picker_permission_unpartialise_request_header),
|
text = stringResource(
|
||||||
|
id = R.string.file_picker_permission_unpartialise_request_header
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.file_picker_permission_unpartialise_request_body),
|
text = stringResource(
|
||||||
|
id = R.string.file_picker_permission_unpartialise_request_body
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
color = LocalContentColor.current.copy(
|
color = LocalContentColor.current.copy(
|
||||||
alpha = 0.5f
|
alpha = 0.5f
|
||||||
|
|
@ -384,12 +410,16 @@ fun InbuiltMediaPicker(
|
||||||
permissionRequester.launch(
|
permissionRequester.launch(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
Manifest.permission.READ_MEDIA_IMAGES,
|
Manifest.permission.READ_MEDIA_IMAGES,
|
||||||
Manifest.permission.READ_MEDIA_VIDEO,
|
Manifest.permission.READ_MEDIA_VIDEO
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(id = R.string.file_picker_permission_unpartialise_request_cta))
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = R.string.file_picker_permission_unpartialise_request_cta
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -404,7 +434,9 @@ fun InbuiltMediaPicker(
|
||||||
onOpenDocumentsUi()
|
onOpenDocumentsUi()
|
||||||
},
|
},
|
||||||
label = {
|
label = {
|
||||||
Text(text = stringResource(id = R.string.file_picker_chip_documents))
|
Text(
|
||||||
|
text = stringResource(id = R.string.file_picker_chip_documents)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
|
|
@ -468,14 +500,14 @@ fun InbuiltMediaPicker(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.width(100.dp)
|
.width(100.dp)
|
||||||
.aspectRatio(images[image].aspectRatio),
|
.aspectRatio(images[image].aspectRatio)
|
||||||
) {
|
) {
|
||||||
GlideImage(
|
GlideImage(
|
||||||
model = images[image].uri.toString(),
|
model = images[image].uri.toString(),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
.fillMaxSize(),
|
.fillMaxSize()
|
||||||
)
|
)
|
||||||
|
|
||||||
if (images[image].duration != null) {
|
if (images[image].duration != null) {
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ fun AttachmentManager(
|
||||||
attachments: List<FileArgs>,
|
attachments: List<FileArgs>,
|
||||||
uploading: Boolean,
|
uploading: Boolean,
|
||||||
uploadProgress: Float = 0f,
|
uploadProgress: Float = 0f,
|
||||||
onRemove: (FileArgs) -> Unit,
|
onRemove: (FileArgs) -> Unit
|
||||||
) {
|
) {
|
||||||
val animatedProgress by animateFloatAsState(
|
val animatedProgress by animateFloatAsState(
|
||||||
targetValue = uploadProgress,
|
targetValue = uploadProgress,
|
||||||
|
|
@ -54,7 +54,6 @@ fun AttachmentManager(
|
||||||
.horizontalScroll(rememberScrollState())
|
.horizontalScroll(rememberScrollState())
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
attachments.forEach { attachment ->
|
attachments.forEach { attachment ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -98,18 +97,18 @@ fun AttachmentManagerPreview() {
|
||||||
FileArgs(
|
FileArgs(
|
||||||
filename = "file1.png",
|
filename = "file1.png",
|
||||||
contentType = "image/png",
|
contentType = "image/png",
|
||||||
file = File("file1.png"),
|
file = File("file1.png")
|
||||||
),
|
),
|
||||||
FileArgs(
|
FileArgs(
|
||||||
filename = "file2.png",
|
filename = "file2.png",
|
||||||
contentType = "image/png",
|
contentType = "image/png",
|
||||||
file = File("file2.png"),
|
file = File("file2.png")
|
||||||
),
|
),
|
||||||
FileArgs(
|
FileArgs(
|
||||||
filename = "file3.png",
|
filename = "file3.png",
|
||||||
contentType = "image/png",
|
contentType = "image/png",
|
||||||
file = File("file3.png"),
|
file = File("file3.png")
|
||||||
),
|
)
|
||||||
),
|
),
|
||||||
uploading = false,
|
uploading = false,
|
||||||
onRemove = {}
|
onRemove = {}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ fun ChannelHeader(
|
||||||
channel: Channel,
|
channel: Channel,
|
||||||
onChannelClick: (String) -> Unit,
|
onChannelClick: (String) -> Unit,
|
||||||
onToggleDrawer: () -> Unit,
|
onToggleDrawer: () -> Unit,
|
||||||
useDrawer: Boolean,
|
useDrawer: Boolean
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -41,7 +41,7 @@ fun ChannelHeader(
|
||||||
channel.id?.let { onChannelClick(it) }
|
channel.id?.let { onChannelClick(it) }
|
||||||
}
|
}
|
||||||
.padding(vertical = 4.dp, horizontal = 4.dp),
|
.padding(vertical = 4.dp, horizontal = 4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (useDrawer) {
|
if (useDrawer) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
|
|
@ -49,7 +49,7 @@ fun ChannelHeader(
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Menu,
|
imageVector = Icons.Default.Menu,
|
||||||
contentDescription = stringResource(R.string.menu),
|
contentDescription = stringResource(R.string.menu)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ fun ChannelHeader(
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = channel.name
|
text = channel.name
|
||||||
|
|
@ -86,7 +86,7 @@ fun ChannelHeader(
|
||||||
},
|
},
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
|
@ -96,7 +96,7 @@ fun ChannelHeader(
|
||||||
contentDescription = stringResource(R.string.menu),
|
contentDescription = stringResource(R.string.menu),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(18.dp)
|
.size(18.dp)
|
||||||
.alpha(0.4f),
|
.alpha(0.4f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,44 +15,41 @@ import chat.revolt.R
|
||||||
import chat.revolt.api.schemas.ChannelType
|
import chat.revolt.api.schemas.ChannelType
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelIcon(
|
fun ChannelIcon(channelType: ChannelType, modifier: Modifier = Modifier) {
|
||||||
channelType: ChannelType,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
when (channelType) {
|
when (channelType) {
|
||||||
ChannelType.TextChannel -> {
|
ChannelType.TextChannel -> {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_pound_24dp),
|
painter = painterResource(R.drawable.ic_pound_24dp),
|
||||||
contentDescription = stringResource(R.string.channel_text),
|
contentDescription = stringResource(R.string.channel_text),
|
||||||
modifier = modifier,
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ChannelType.VoiceChannel -> {
|
ChannelType.VoiceChannel -> {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_volume_up_24dp),
|
painter = painterResource(R.drawable.ic_volume_up_24dp),
|
||||||
contentDescription = stringResource(R.string.channel_voice),
|
contentDescription = stringResource(R.string.channel_voice),
|
||||||
modifier = modifier,
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ChannelType.SavedMessages -> {
|
ChannelType.SavedMessages -> {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_note_24dp),
|
painter = painterResource(R.drawable.ic_note_24dp),
|
||||||
contentDescription = stringResource(R.string.channel_notes),
|
contentDescription = stringResource(R.string.channel_notes),
|
||||||
modifier = modifier,
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ChannelType.DirectMessage -> {
|
ChannelType.DirectMessage -> {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.AccountCircle,
|
imageVector = Icons.Default.AccountCircle,
|
||||||
contentDescription = stringResource(R.string.channel_dm),
|
contentDescription = stringResource(R.string.channel_dm),
|
||||||
modifier = modifier,
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ChannelType.Group -> {
|
ChannelType.Group -> {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.AccountBox,
|
imageVector = Icons.Default.AccountBox,
|
||||||
contentDescription = stringResource(R.string.channel_group),
|
contentDescription = stringResource(R.string.channel_group),
|
||||||
modifier = modifier,
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +62,7 @@ class ChannelTypeProvider : PreviewParameterProvider<ChannelType> {
|
||||||
ChannelType.VoiceChannel,
|
ChannelType.VoiceChannel,
|
||||||
ChannelType.SavedMessages,
|
ChannelType.SavedMessages,
|
||||||
ChannelType.DirectMessage,
|
ChannelType.DirectMessage,
|
||||||
ChannelType.Group,
|
ChannelType.Group
|
||||||
)
|
)
|
||||||
|
|
||||||
override val count: Int
|
override val count: Int
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ fun ChannelSheetHeader(
|
||||||
channelName: String,
|
channelName: String,
|
||||||
channelIcon: AutumnResource? = null,
|
channelIcon: AutumnResource? = null,
|
||||||
channelType: ChannelType,
|
channelType: ChannelType,
|
||||||
channelId: String,
|
channelId: String
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(vertical = 4.dp),
|
modifier = Modifier.padding(vertical = 4.dp),
|
||||||
|
|
@ -65,7 +65,7 @@ fun ChannelSheetHeader(
|
||||||
text = channelName,
|
text = channelName,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,7 @@ fun replyContentText(message: Message): String {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ManageableReply(
|
fun ManageableReply(reply: SendMessageReply, onToggleMention: () -> Unit, onRemove: () -> Unit) {
|
||||||
reply: SendMessageReply,
|
|
||||||
onToggleMention: () -> Unit,
|
|
||||||
onRemove: () -> Unit,
|
|
||||||
) {
|
|
||||||
val replyMessage = RevoltAPI.messageCache[reply.id] ?: return onRemove()
|
val replyMessage = RevoltAPI.messageCache[reply.id] ?: return onRemove()
|
||||||
val replyAuthor = RevoltAPI.userCache[replyMessage.author] ?: return onRemove()
|
val replyAuthor = RevoltAPI.userCache[replyMessage.author] ?: return onRemove()
|
||||||
|
|
||||||
|
|
@ -61,7 +57,7 @@ fun ManageableReply(
|
||||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
||||||
.horizontalScroll(rememberScrollState())
|
.horizontalScroll(rememberScrollState())
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Default.Close,
|
||||||
|
|
@ -72,7 +68,7 @@ fun ManageableReply(
|
||||||
onRemove()
|
onRemove()
|
||||||
}
|
}
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
.size(16.dp),
|
.size(16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
@ -97,7 +93,7 @@ fun ManageableReply(
|
||||||
style = LocalTextStyle.current.copy(
|
style = LocalTextStyle.current.copy(
|
||||||
brush = authorColour(message = replyMessage),
|
brush = authorColour(message = replyMessage),
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -133,7 +129,7 @@ fun ManageableReply(
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f)
|
MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f)
|
||||||
},
|
},
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +138,7 @@ fun ManageableReply(
|
||||||
fun ReplyManager(
|
fun ReplyManager(
|
||||||
replies: List<SendMessageReply>,
|
replies: List<SendMessageReply>,
|
||||||
onToggleMention: (SendMessageReply) -> Unit,
|
onToggleMention: (SendMessageReply) -> Unit,
|
||||||
onRemove: (SendMessageReply) -> Unit,
|
onRemove: (SendMessageReply) -> Unit
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
replies.forEach { reply ->
|
replies.forEach { reply ->
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,7 @@ import chat.revolt.api.schemas.User
|
||||||
import chat.revolt.components.generic.UserAvatar
|
import chat.revolt.components.generic.UserAvatar
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StackedUserAvatars(
|
fun StackedUserAvatars(users: List<String>, amount: Int = 3) {
|
||||||
users: List<String>,
|
|
||||||
amount: Int = 3,
|
|
||||||
) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(16.dp + (8.dp * minOf(users.size, amount)), 16.dp)
|
.size(16.dp + (8.dp * minOf(users.size, amount)), 16.dp)
|
||||||
|
|
@ -43,21 +40,19 @@ fun StackedUserAvatars(
|
||||||
UserAvatar(
|
UserAvatar(
|
||||||
avatar = user?.avatar,
|
avatar = user?.avatar,
|
||||||
userId = userId,
|
userId = userId,
|
||||||
username = user?.let {User.resolveDefaultName(it)} ?: stringResource(id = R.string.unknown),
|
username = user?.let { User.resolveDefaultName(it) } ?: stringResource(id = R.string.unknown),
|
||||||
size = 16.dp,
|
size = 16.dp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.offset(
|
.offset(
|
||||||
x = (index * 8).dp,
|
x = (index * 8).dp
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TypingIndicator(
|
fun TypingIndicator(users: List<String>) {
|
||||||
users: List<String>,
|
|
||||||
) {
|
|
||||||
fun typingMessageResource(): Int {
|
fun typingMessageResource(): Int {
|
||||||
return when (users.size) {
|
return when (users.size) {
|
||||||
0 -> R.string.typing_blank
|
0 -> R.string.typing_blank
|
||||||
|
|
@ -84,11 +79,11 @@ fun TypingIndicator(
|
||||||
.clip(
|
.clip(
|
||||||
RoundedCornerShape(
|
RoundedCornerShape(
|
||||||
topStart = 16.dp,
|
topStart = 16.dp,
|
||||||
topEnd = 16.dp,
|
topEnd = 16.dp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.background(MaterialTheme.colorScheme.background)
|
.background(MaterialTheme.colorScheme.background)
|
||||||
.padding(top = 4.dp, start = 16.dp, end = 16.dp),
|
.padding(top = 4.dp, start = 16.dp, end = 16.dp)
|
||||||
) {
|
) {
|
||||||
StackedUserAvatars(users = users)
|
StackedUserAvatars(users = users)
|
||||||
|
|
||||||
|
|
@ -103,7 +98,7 @@ fun TypingIndicator(
|
||||||
),
|
),
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,13 +84,13 @@ fun RowScope.ChannelList(
|
||||||
currentChannel: String?,
|
currentChannel: String?,
|
||||||
onChannelClick: (String) -> Unit,
|
onChannelClick: (String) -> Unit,
|
||||||
onSpecialClick: (String) -> Unit,
|
onSpecialClick: (String) -> Unit,
|
||||||
onServerSheetOpenFor: (String) -> Unit,
|
onServerSheetOpenFor: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
val enableSmallBanner by remember {
|
val enableSmallBanner by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
lazyListState.firstVisibleItemScrollOffset > 40 ||
|
lazyListState.firstVisibleItemScrollOffset > 40 ||
|
||||||
lazyListState.firstVisibleItemIndex > 0
|
lazyListState.firstVisibleItemIndex > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,7 +120,7 @@ fun RowScope.ChannelList(
|
||||||
sheetState = channelContextSheetState,
|
sheetState = channelContextSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
channelContextSheetShown = false
|
channelContextSheetShown = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
ChannelContextSheet(
|
ChannelContextSheet(
|
||||||
channelId = channelContextSheetTarget,
|
channelId = channelContextSheetTarget,
|
||||||
|
|
@ -151,13 +151,13 @@ fun RowScope.ChannelList(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 4.dp, top = 8.dp, bottom = 8.dp)
|
.padding(start = 4.dp, top = 8.dp, bottom = 8.dp)
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
Modifier
|
Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
state = lazyListState,
|
state = lazyListState
|
||||||
) {
|
) {
|
||||||
if (serverId == "home") {
|
if (serverId == "home") {
|
||||||
stickyHeader(
|
stickyHeader(
|
||||||
|
|
@ -194,7 +194,7 @@ fun RowScope.ChannelList(
|
||||||
onClick = {
|
onClick = {
|
||||||
onSpecialClick("home")
|
onSpecialClick("home")
|
||||||
},
|
},
|
||||||
large = true,
|
large = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,7 +212,7 @@ fun RowScope.ChannelList(
|
||||||
onClick = {
|
onClick = {
|
||||||
onChannelClick(notesChannelId ?: return@DrawerChannel)
|
onChannelClick(notesChannelId ?: return@DrawerChannel)
|
||||||
},
|
},
|
||||||
large = true,
|
large = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,13 +238,19 @@ fun RowScope.ChannelList(
|
||||||
val channel = dmAbleChannels.getOrNull(it) ?: return@items
|
val channel = dmAbleChannels.getOrNull(it) ?: return@items
|
||||||
|
|
||||||
val partner =
|
val partner =
|
||||||
if (channel.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner(
|
if (channel.channelType == ChannelType.DirectMessage) {
|
||||||
channel
|
RevoltAPI.userCache[
|
||||||
)] else null
|
ChannelUtils.resolveDMPartner(
|
||||||
|
channel
|
||||||
|
)
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
DrawerChannel(
|
DrawerChannel(
|
||||||
name = partner?.let { p -> User.resolveDefaultName(p) } ?: channel.name
|
name = partner?.let { p -> User.resolveDefaultName(p) } ?: channel.name
|
||||||
?: stringResource(R.string.unknown),
|
?: stringResource(R.string.unknown),
|
||||||
channelType = channel.channelType ?: ChannelType.TextChannel,
|
channelType = channel.channelType ?: ChannelType.TextChannel,
|
||||||
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
|
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
|
||||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
||||||
|
|
@ -313,7 +319,9 @@ fun RowScope.ChannelList(
|
||||||
|
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.load("$REVOLT_FILES/banners/${server.banner.id}")
|
.load("$REVOLT_FILES/banners/${server.banner.id}")
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
.transition(
|
||||||
|
DrawableTransitionOptions.withCrossFade()
|
||||||
|
)
|
||||||
.into(this)
|
.into(this)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -348,21 +356,29 @@ fun RowScope.ChannelList(
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.alpha(0.9f)
|
.alpha(0.9f)
|
||||||
.height(BANNER_HEIGHT_COMPACT.dp + 8.dp) // due to padding in Text
|
.height(
|
||||||
|
BANNER_HEIGHT_COMPACT.dp + 8.dp
|
||||||
|
) // due to padding in Text
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Spacer(Modifier.width(16.dp))
|
Spacer(Modifier.width(16.dp))
|
||||||
|
|
||||||
if (server?.flags has ServerFlags.Official) {
|
if (server?.flags has ServerFlags.Official) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_revolt_decagram_24dp),
|
painter = painterResource(
|
||||||
contentDescription = stringResource(R.string.server_flag_official),
|
id = R.drawable.ic_revolt_decagram_24dp
|
||||||
|
),
|
||||||
|
contentDescription = stringResource(
|
||||||
|
R.string.server_flag_official
|
||||||
|
),
|
||||||
tint = if (server?.banner != null) {
|
tint = if (server?.banner != null) {
|
||||||
bannerTextColour
|
bannerTextColour
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -375,8 +391,12 @@ fun RowScope.ChannelList(
|
||||||
}
|
}
|
||||||
if (server?.flags has ServerFlags.Verified) {
|
if (server?.flags has ServerFlags.Verified) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_check_decagram_24dp),
|
painter = painterResource(
|
||||||
contentDescription = stringResource(R.string.server_flag_verified),
|
id = R.drawable.ic_check_decagram_24dp
|
||||||
|
),
|
||||||
|
contentDescription = stringResource(
|
||||||
|
R.string.server_flag_verified
|
||||||
|
),
|
||||||
tint = if (server?.banner != null) {
|
tint = if (server?.banner != null) {
|
||||||
bannerTextColour
|
bannerTextColour
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -389,8 +409,10 @@ fun RowScope.ChannelList(
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = (server?.name
|
text = (
|
||||||
?: stringResource(R.string.unknown)),
|
server?.name
|
||||||
|
?: stringResource(R.string.unknown)
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
color = if (server?.banner != null) {
|
color = if (server?.banner != null) {
|
||||||
bannerTextColour
|
bannerTextColour
|
||||||
|
|
@ -438,7 +460,6 @@ fun RowScope.ChannelList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (categorisedChannels.isNullOrEmpty()) {
|
if (categorisedChannels.isNullOrEmpty()) {
|
||||||
|
|
@ -458,7 +479,7 @@ fun RowScope.ChannelList(
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.no_channels_body),
|
text = stringResource(R.string.no_channels_body),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -480,9 +501,15 @@ fun RowScope.ChannelList(
|
||||||
val channel = item.channel
|
val channel = item.channel
|
||||||
|
|
||||||
val partner =
|
val partner =
|
||||||
if (channel.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner(
|
if (channel.channelType == ChannelType.DirectMessage) {
|
||||||
channel
|
RevoltAPI.userCache[
|
||||||
)] else null
|
ChannelUtils.resolveDMPartner(
|
||||||
|
channel
|
||||||
|
)
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
DrawerChannel(
|
DrawerChannel(
|
||||||
name = partner?.let { p -> User.resolveDefaultName(p) }
|
name = partner?.let { p -> User.resolveDefaultName(p) }
|
||||||
|
|
@ -498,7 +525,11 @@ fun RowScope.ChannelList(
|
||||||
} ?: false,
|
} ?: false,
|
||||||
dmPartnerIcon = partner?.avatar ?: channel.icon,
|
dmPartnerIcon = partner?.avatar ?: channel.icon,
|
||||||
dmPartnerId = partner?.id,
|
dmPartnerId = partner?.id,
|
||||||
dmPartnerName = partner?.let { p -> User.resolveDefaultName(p) },
|
dmPartnerName = partner?.let { p ->
|
||||||
|
User.resolveDefaultName(
|
||||||
|
p
|
||||||
|
)
|
||||||
|
},
|
||||||
dmPartnerStatus = presenceFromStatus(
|
dmPartnerStatus = presenceFromStatus(
|
||||||
status = partner?.status?.presence,
|
status = partner?.status?.presence,
|
||||||
online = partner?.online ?: false
|
online = partner?.online ?: false
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,14 @@ fun DrawerChannel(
|
||||||
dmPartnerName: String? = null,
|
dmPartnerName: String? = null,
|
||||||
dmPartnerIcon: AutumnResource? = null,
|
dmPartnerIcon: AutumnResource? = null,
|
||||||
dmPartnerId: String? = null,
|
dmPartnerId: String? = null,
|
||||||
large: Boolean = false,
|
large: Boolean = false
|
||||||
) {
|
) {
|
||||||
val backgroundColor = animateColorAsState(
|
val backgroundColor = animateColorAsState(
|
||||||
if (selected) MaterialTheme.colorScheme.background
|
if (selected) {
|
||||||
else Color.Transparent,
|
MaterialTheme.colorScheme.background
|
||||||
|
} else {
|
||||||
|
Color.Transparent
|
||||||
|
},
|
||||||
animationSpec = spring(),
|
animationSpec = spring(),
|
||||||
label = "Channel background colour"
|
label = "Channel background colour"
|
||||||
)
|
)
|
||||||
|
|
@ -102,12 +105,16 @@ fun DrawerChannel(
|
||||||
else -> ChannelIcon(
|
else -> ChannelIcon(
|
||||||
channelType = channelType,
|
channelType = channelType,
|
||||||
modifier = Modifier.then(
|
modifier = Modifier.then(
|
||||||
if (large) Modifier.padding(
|
if (large) {
|
||||||
end = 12.dp,
|
Modifier.padding(
|
||||||
start = 4.dp,
|
end = 12.dp,
|
||||||
top = 4.dp,
|
start = 4.dp,
|
||||||
bottom = 4.dp
|
top = 4.dp,
|
||||||
) else Modifier.padding(end = 8.dp)
|
bottom = 4.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Modifier.padding(end = 8.dp)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ fun DrawerServer(
|
||||||
) {
|
) {
|
||||||
if (iconId != null) {
|
if (iconId != null) {
|
||||||
RemoteImage(
|
RemoteImage(
|
||||||
url = "$REVOLT_FILES/icons/${iconId}/server.png?max_side=256",
|
url = "$REVOLT_FILES/icons/$iconId/server.png?max_side=256",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,7 @@ import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DrawerServerlikeIcon(
|
fun DrawerServerlikeIcon(onClick: () -> Unit, content: @Composable () -> Unit) {
|
||||||
onClick: () -> Unit,
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ fun LinkOnHome(
|
||||||
icon: @Composable (Modifier) -> Unit,
|
icon: @Composable (Modifier) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
description: @Composable () -> Unit = {},
|
description: @Composable () -> Unit = {},
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
@ -37,12 +37,12 @@ fun LinkOnHome(
|
||||||
.clickable { onClick() }
|
.clickable { onClick() }
|
||||||
.padding(20.dp)
|
.padding(20.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 5.dp),
|
.padding(vertical = 5.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
icon(Modifier.padding(end = 14.dp))
|
icon(Modifier.padding(end = 14.dp))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,7 @@ import chat.revolt.R
|
||||||
import chat.revolt.screens.about.Library
|
import chat.revolt.screens.about.Library
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AttributionItem(
|
fun AttributionItem(library: Library, onClick: () -> Unit) {
|
||||||
library: Library,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
SelectionContainer {
|
SelectionContainer {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -30,16 +27,15 @@ fun AttributionItem(
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = library.name,
|
text = library.name,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.oss_attribution_tap_to_view_license),
|
text = stringResource(id = R.string.oss_attribution_tap_to_view_license),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,9 +97,11 @@ fun RawUserOverview(user: User, profile: Profile? = null) {
|
||||||
width = 4.dp,
|
width = 4.dp,
|
||||||
brush = teamMemberFlair
|
brush = teamMemberFlair
|
||||||
?: Brush.solidColor(Color.Transparent),
|
?: Brush.solidColor(Color.Transparent),
|
||||||
shape = MaterialTheme.shapes.large,
|
shape = MaterialTheme.shapes.large
|
||||||
)
|
)
|
||||||
} else Modifier
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
profile?.background?.let { background ->
|
profile?.background?.let { background ->
|
||||||
|
|
@ -138,7 +140,7 @@ fun RawUserOverview(user: User, profile: Profile? = null) {
|
||||||
userId = user.id ?: ULID.makeSpecial(0),
|
userId = user.id ?: ULID.makeSpecial(0),
|
||||||
avatar = user.avatar,
|
avatar = user.avatar,
|
||||||
size = 48.dp,
|
size = 48.dp,
|
||||||
presence = presenceFromStatus(user.status?.presence),
|
presence = presenceFromStatus(user.status?.presence)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
@ -156,7 +158,7 @@ fun RawUserOverview(user: User, profile: Profile? = null) {
|
||||||
append("#${user.discriminator}")
|
append("#${user.discriminator}")
|
||||||
pop()
|
pop()
|
||||||
}.toAnnotatedString(),
|
}.toAnnotatedString(),
|
||||||
color = if (profile?.background != null) Color.White else LocalContentColor.current,
|
color = if (profile?.background != null) Color.White else LocalContentColor.current
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,12 @@ fun ThemeChip(
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
.then(
|
.then(
|
||||||
if (selected)
|
if (selected) {
|
||||||
Modifier
|
Modifier
|
||||||
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f))
|
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f))
|
||||||
else Modifier
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,7 @@ import androidx.compose.ui.unit.sp
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisconnectedScreen(
|
fun DisconnectedScreen(onRetry: () -> Unit) {
|
||||||
onRetry: () -> Unit
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -35,7 +33,7 @@ fun DisconnectedScreen(
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -43,7 +41,7 @@ fun DisconnectedScreen(
|
||||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
|
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 10.dp, horizontal = 20.dp)
|
.padding(vertical = 10.dp, horizontal = 20.dp)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
|
||||||
data class Changelog(
|
data class Changelog(
|
||||||
val summary: String,
|
val summary: String,
|
||||||
val version: String,
|
val version: String,
|
||||||
val date: String,
|
val date: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -26,19 +26,27 @@ class Changelogs(val context: Context, val kvStorage: KVStorage? = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getChangelog(version: String): String {
|
fun getChangelog(version: String): String {
|
||||||
return context.assets.open("changelogs/${version}.md").use {
|
return context.assets.open("changelogs/$version.md").use {
|
||||||
it.reader().readText()
|
it.reader().readText()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun hasSeenLatest(): Boolean {
|
suspend fun hasSeenLatest(): Boolean {
|
||||||
if (kvStorage == null) throw IllegalStateException("Not supported for non-KVStorage instances of Changelogs")
|
if (kvStorage == null) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"Not supported for non-KVStorage instances of Changelogs"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return kvStorage.get("latestChangelogRead") == index.latest
|
return kvStorage.get("latestChangelogRead") == index.latest
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAsSeen() {
|
suspend fun markAsSeen() {
|
||||||
if (kvStorage == null) throw IllegalStateException("Not supported for non-KVStorage instances of Changelogs")
|
if (kvStorage == null) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"Not supported for non-KVStorage instances of Changelogs"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
kvStorage.set("latestChangelogRead", index.latest)
|
kvStorage.set("latestChangelogRead", index.latest)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ data class Emoji(
|
||||||
val alternates: List<List<Long>>,
|
val alternates: List<List<Long>>,
|
||||||
val emoticons: List<String>,
|
val emoticons: List<String>,
|
||||||
val shortcodes: List<String>,
|
val shortcodes: List<String>,
|
||||||
val animated: Boolean,
|
val animated: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class EmojiGroup(
|
data class EmojiGroup(
|
||||||
val group: String,
|
val group: String,
|
||||||
val emoji: List<Emoji>,
|
val emoji: List<Emoji>
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class FitzpatrickSkinTone(val modifierCodepoint: Int?) {
|
enum class FitzpatrickSkinTone(val modifierCodepoint: Int?) {
|
||||||
|
|
@ -30,7 +30,7 @@ enum class FitzpatrickSkinTone(val modifierCodepoint: Int?) {
|
||||||
MediumLight(0x1F3FC),
|
MediumLight(0x1F3FC),
|
||||||
Medium(0x1F3FD),
|
Medium(0x1F3FD),
|
||||||
MediumDark(0x1F3FE),
|
MediumDark(0x1F3FE),
|
||||||
Dark(0x1F3FF),
|
Dark(0x1F3FF)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class UnicodeEmojiSection(val googleName: String, val nameResource: Int) {
|
enum class UnicodeEmojiSection(val googleName: String, val nameResource: Int) {
|
||||||
|
|
@ -42,7 +42,7 @@ enum class UnicodeEmojiSection(val googleName: String, val nameResource: Int) {
|
||||||
Activities("Activities and events", R.string.emoji_category_activities),
|
Activities("Activities and events", R.string.emoji_category_activities),
|
||||||
Objects("Objects", R.string.emoji_category_objects),
|
Objects("Objects", R.string.emoji_category_objects),
|
||||||
Symbols("Symbols", R.string.emoji_category_symbols),
|
Symbols("Symbols", R.string.emoji_category_symbols),
|
||||||
Flags("Flags", R.string.emoji_category_flags),
|
Flags("Flags", R.string.emoji_category_flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Category {
|
sealed class Category {
|
||||||
|
|
@ -55,7 +55,7 @@ sealed class EmojiPickerItem {
|
||||||
data class UnicodeEmoji(
|
data class UnicodeEmoji(
|
||||||
val character: String,
|
val character: String,
|
||||||
val hasSkinTones: Boolean,
|
val hasSkinTones: Boolean,
|
||||||
val alternates: List<List<Long>>,
|
val alternates: List<List<Long>>
|
||||||
) : EmojiPickerItem()
|
) : EmojiPickerItem()
|
||||||
|
|
||||||
data class ServerEmote(val emote: chat.revolt.api.schemas.Emoji) : EmojiPickerItem()
|
data class ServerEmote(val emote: chat.revolt.api.schemas.Emoji) : EmojiPickerItem()
|
||||||
|
|
@ -106,17 +106,19 @@ class EmojiImpl {
|
||||||
val category =
|
val category =
|
||||||
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
|
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
|
||||||
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
|
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
|
||||||
list.addAll(group.emoji.map { emoji ->
|
list.addAll(
|
||||||
EmojiPickerItem.UnicodeEmoji(
|
group.emoji.map { emoji ->
|
||||||
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
|
EmojiPickerItem.UnicodeEmoji(
|
||||||
emoji.alternates.any { alternate ->
|
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
|
||||||
alternate.any { codepoint ->
|
emoji.alternates.any { alternate ->
|
||||||
codepoint in 0x1F3FB..0x1F3FF
|
alternate.any { codepoint ->
|
||||||
}
|
codepoint in 0x1F3FB..0x1F3FF
|
||||||
},
|
}
|
||||||
emoji.alternates
|
},
|
||||||
)
|
emoji.alternates
|
||||||
})
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return list
|
return list
|
||||||
|
|
@ -143,7 +145,9 @@ class EmojiImpl {
|
||||||
|
|
||||||
for (server in serversWithEmotes()) {
|
for (server in serversWithEmotes()) {
|
||||||
val index =
|
val index =
|
||||||
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server }
|
flatPickerList.indexOfFirst {
|
||||||
|
it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server
|
||||||
|
}
|
||||||
val allEmotesInThatServer =
|
val allEmotesInThatServer =
|
||||||
RevoltAPI.emojiCache.values.filter { it.parent?.id == server.id }
|
RevoltAPI.emojiCache.values.filter { it.parent?.id == server.id }
|
||||||
val lastIndex = index + allEmotesInThatServer.size
|
val lastIndex = index + allEmotesInThatServer.size
|
||||||
|
|
@ -152,12 +156,16 @@ class EmojiImpl {
|
||||||
}
|
}
|
||||||
for (section in UnicodeEmojiSection.entries) {
|
for (section in UnicodeEmojiSection.entries) {
|
||||||
val index =
|
val index =
|
||||||
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == section }
|
flatPickerList.indexOfFirst {
|
||||||
|
it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == section
|
||||||
|
}
|
||||||
val lastIndex = if (section == UnicodeEmojiSection.entries.last()) {
|
val lastIndex = if (section == UnicodeEmojiSection.entries.last()) {
|
||||||
Int.MAX_VALUE
|
Int.MAX_VALUE
|
||||||
} else {
|
} else {
|
||||||
val nextSection = UnicodeEmojiSection.entries[section.ordinal + 1]
|
val nextSection = UnicodeEmojiSection.entries[section.ordinal + 1]
|
||||||
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == nextSection } - 1
|
flatPickerList.indexOfFirst {
|
||||||
|
it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == nextSection
|
||||||
|
} - 1
|
||||||
}
|
}
|
||||||
output[Category.UnicodeEmojiCategory(section)] = Pair(index, lastIndex)
|
output[Category.UnicodeEmojiCategory(section)] = Pair(index, lastIndex)
|
||||||
}
|
}
|
||||||
|
|
@ -220,17 +228,19 @@ class EmojiImpl {
|
||||||
val category =
|
val category =
|
||||||
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
|
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
|
||||||
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
|
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
|
||||||
list.addAll(matchingEmoji.map { emoji ->
|
list.addAll(
|
||||||
EmojiPickerItem.UnicodeEmoji(
|
matchingEmoji.map { emoji ->
|
||||||
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
|
EmojiPickerItem.UnicodeEmoji(
|
||||||
emoji.alternates.any { alternate ->
|
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) },
|
||||||
alternate.any { codepoint ->
|
emoji.alternates.any { alternate ->
|
||||||
codepoint in 0x1F3FB..0x1F3FF
|
alternate.any { codepoint ->
|
||||||
}
|
codepoint in 0x1F3FB..0x1F3FF
|
||||||
},
|
}
|
||||||
emoji.alternates
|
},
|
||||||
)
|
emoji.alternates
|
||||||
})
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class BlockBackgroundNode<R>(
|
||||||
private val quoteDepth: Int,
|
private val quoteDepth: Int,
|
||||||
private val fillColor: Int = Color.DKGRAY,
|
private val fillColor: Int = Color.DKGRAY,
|
||||||
private val strokeColor: Int = Color.BLACK,
|
private val strokeColor: Int = Color.BLACK,
|
||||||
vararg children: Node<R>,
|
vararg children: Node<R>
|
||||||
) : Node.Parent<R>(*children) {
|
) : Node.Parent<R>(*children) {
|
||||||
|
|
||||||
override fun render(builder: SpannableStringBuilder, renderContext: R) {
|
override fun render(builder: SpannableStringBuilder, renderContext: R) {
|
||||||
|
|
@ -41,7 +41,8 @@ class BlockBackgroundNode<R>(
|
||||||
ensureEndsWithNewline(builder)
|
ensureEndsWithNewline(builder)
|
||||||
|
|
||||||
val backgroundSpan = BlockBackgroundSpan(
|
val backgroundSpan = BlockBackgroundSpan(
|
||||||
fillColor, strokeColor,
|
fillColor,
|
||||||
|
strokeColor,
|
||||||
strokeWidth = 2,
|
strokeWidth = 2,
|
||||||
strokeRadius = 15,
|
strokeRadius = 15,
|
||||||
leftMargin = 40 * quoteDepth
|
leftMargin = 40 * quoteDepth
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
class EmoteSpan(drawable: Drawable) :
|
class EmoteSpan(drawable: Drawable) :
|
||||||
ImageSpan(drawable, ALIGN_BOTTOM) {
|
ImageSpan(drawable, ALIGN_BOTTOM)
|
||||||
}
|
|
||||||
|
|
||||||
class EmoteClickableSpan(private val emoteId: String) : LongClickableSpan() {
|
class EmoteClickableSpan(private val emoteId: String) : LongClickableSpan() {
|
||||||
override fun onClick(widget: View) {
|
override fun onClick(widget: View) {
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,11 @@ class LinkSpan(private val url: String, private val drawBackground: Boolean = fa
|
||||||
|
|
||||||
// Intercept invite links
|
// Intercept invite links
|
||||||
if (uri.host == Uri.parse(REVOLT_INVITES).host!! ||
|
if (uri.host == Uri.parse(REVOLT_INVITES).host!! ||
|
||||||
(uri.host?.endsWith(Uri.parse(REVOLT_APP).host!!) == true && uri.path?.startsWith(
|
(
|
||||||
"/invite"
|
uri.host?.endsWith(Uri.parse(REVOLT_APP).host!!) == true && uri.path?.startsWith(
|
||||||
) == true)
|
"/invite"
|
||||||
|
) == true
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
val intent = Intent(
|
val intent = Intent(
|
||||||
widget.context,
|
widget.context,
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,7 @@ abstract class LongClickableSpan : ClickableSpan() {
|
||||||
class LongClickLinkMovementMethod : LinkMovementMethod() {
|
class LongClickLinkMovementMethod : LinkMovementMethod() {
|
||||||
private var longClickHandler: Handler? = null
|
private var longClickHandler: Handler? = null
|
||||||
private var isLongPressed = false
|
private var isLongPressed = false
|
||||||
override fun onTouchEvent(
|
override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
|
||||||
widget: TextView,
|
|
||||||
buffer: Spannable,
|
|
||||||
event: MotionEvent
|
|
||||||
): Boolean {
|
|
||||||
val action = event.action
|
val action = event.action
|
||||||
if (action == MotionEvent.ACTION_CANCEL) {
|
if (action == MotionEvent.ACTION_CANCEL) {
|
||||||
longClickHandler?.removeCallbacksAndMessages(null)
|
longClickHandler?.removeCallbacksAndMessages(null)
|
||||||
|
|
@ -41,7 +37,8 @@ class LongClickLinkMovementMethod : LinkMovementMethod() {
|
||||||
val line = layout.getLineForVertical(y)
|
val line = layout.getLineForVertical(y)
|
||||||
val off = layout.getOffsetForHorizontal(line, x.toFloat())
|
val off = layout.getOffsetForHorizontal(line, x.toFloat())
|
||||||
val link = buffer.getSpans(
|
val link = buffer.getSpans(
|
||||||
off, off,
|
off,
|
||||||
|
off,
|
||||||
LongClickableSpan::class.java
|
LongClickableSpan::class.java
|
||||||
)
|
)
|
||||||
if (link.isNotEmpty()) {
|
if (link.isNotEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,5 @@ data class MarkdownContext(
|
||||||
val channelMap: Map<String, String>,
|
val channelMap: Map<String, String>,
|
||||||
val emojiMap: Map<String, Emoji>,
|
val emojiMap: Map<String, Emoji>,
|
||||||
val serverId: String?,
|
val serverId: String?,
|
||||||
val useLargeEmojis: Boolean,
|
val useLargeEmojis: Boolean
|
||||||
)
|
)
|
||||||
|
|
@ -17,7 +17,7 @@ class UserMentionNode(private val userId: String) : Node<MarkdownContext>() {
|
||||||
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
|
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
|
||||||
val content = renderContext.memberMap[userId]?.let { "@$it" }
|
val content = renderContext.memberMap[userId]?.let { "@$it" }
|
||||||
?: renderContext.userMap[userId]?.let { "@${it.username}" }
|
?: renderContext.userMap[userId]?.let { "@${it.username}" }
|
||||||
?: "<@${userId}>"
|
?: "<@$userId>"
|
||||||
|
|
||||||
builder.append(content)
|
builder.append(content)
|
||||||
builder.setSpan(
|
builder.setSpan(
|
||||||
|
|
@ -58,7 +58,7 @@ class CustomEmoteNode(private val emoteId: String, private val context: Context)
|
||||||
Node<MarkdownContext>() {
|
Node<MarkdownContext>() {
|
||||||
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
|
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
|
||||||
val content = renderContext.emojiMap[emoteId]?.let { ":${it.name}:" }
|
val content = renderContext.emojiMap[emoteId]?.let { ":${it.name}:" }
|
||||||
?: ":${emoteId}:"
|
?: ":$emoteId:"
|
||||||
val isGif = renderContext.emojiMap[emoteId]?.animated ?: false
|
val isGif = renderContext.emojiMap[emoteId]?.animated ?: false
|
||||||
val emoteUrl = "$REVOLT_FILES/emojis/$emoteId/emote${if (isGif) ".gif" else ".png"}"
|
val emoteUrl = "$REVOLT_FILES/emojis/$emoteId/emote${if (isGif) ".gif" else ".png"}"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,8 @@ class TimestampRule<S>(private val context: Context) :
|
||||||
matcher.group(2)
|
matcher.group(2)
|
||||||
),
|
),
|
||||||
listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance))
|
listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance))
|
||||||
), state
|
),
|
||||||
|
state
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,10 +114,7 @@ fun <RC, S> createInlineCodeRule(context: Context, backgroundColor: Int): Rule<R
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <RC> createCodeRule(
|
fun <RC> createCodeRule(context: Context, backgroundColor: Int): Rule<RC, Node<RC>, MarkdownState> {
|
||||||
context: Context,
|
|
||||||
backgroundColor: Int
|
|
||||||
): Rule<RC, Node<RC>, MarkdownState> {
|
|
||||||
val codeStyleProviders = CodeStyleProviders<RC>(
|
val codeStyleProviders = CodeStyleProviders<RC>(
|
||||||
defaultStyleProvider = { listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) },
|
defaultStyleProvider = { listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) },
|
||||||
commentStyleProvider = {
|
commentStyleProvider = {
|
||||||
|
|
@ -174,7 +172,7 @@ fun <RC> createCodeRule(
|
||||||
R.style.Code_TextAppearance_Params
|
R.style.Code_TextAppearance_Params
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
val languageMap = CodeRules.createCodeLanguageMap<RC, MarkdownState>(codeStyleProviders)
|
val languageMap = CodeRules.createCodeLanguageMap<RC, MarkdownState>(codeStyleProviders)
|
||||||
|
|
||||||
|
|
@ -203,6 +201,6 @@ fun MarkdownParser.addRevoltRules(context: Context): MarkdownParser {
|
||||||
CustomEmoteRule(context),
|
CustomEmoteRule(context),
|
||||||
TimestampRule(context),
|
TimestampRule(context),
|
||||||
NamedLinkRule(),
|
NamedLinkRule(),
|
||||||
LinkRule(),
|
LinkRule()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ package chat.revolt.internals.markdown
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import kotlinx.datetime.Clock
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import kotlinx.datetime.toJavaInstant
|
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import kotlinx.datetime.toJavaInstant
|
||||||
|
|
||||||
fun resolveTimestamp(timestamp: Long, modifier: String? = null): String {
|
fun resolveTimestamp(timestamp: Long, modifier: String? = null): String {
|
||||||
val normalisedModifier = modifier.orEmpty().removePrefix(":")
|
val normalisedModifier = modifier.orEmpty().removePrefix(":")
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
|
||||||
val Context.revoltKVStorage: DataStore<Preferences> by preferencesDataStore(name = "revolt_kv")
|
val Context.revoltKVStorage: DataStore<Preferences> by preferencesDataStore(name = "revolt_kv")
|
||||||
|
|
||||||
|
|
@ -45,4 +45,3 @@ class KVStorage @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.readBytes
|
import io.ktor.client.statement.readBytes
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class AttachmentProvider : FileProvider(R.xml.file_paths) {
|
class AttachmentProvider : FileProvider(R.xml.file_paths)
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getAttachmentContentUri(
|
suspend fun getAttachmentContentUri(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ import chat.revolt.persistence.KVStorage
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
|
@ -138,10 +138,7 @@ class SplashScreenViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SplashScreen(
|
fun SplashScreen(navController: NavController, viewModel: SplashScreenViewModel = hiltViewModel()) {
|
||||||
navController: NavController,
|
|
||||||
viewModel: SplashScreenViewModel = hiltViewModel()
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val webChallengeActivityResult = rememberLauncherForActivityResult(
|
val webChallengeActivityResult = rememberLauncherForActivityResult(
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,11 @@ import chat.revolt.api.routes.misc.getRootRoute
|
||||||
import chat.revolt.components.generic.PageHeader
|
import chat.revolt.components.generic.PageHeader
|
||||||
import chat.revolt.components.generic.PrimaryTabs
|
import chat.revolt.components.generic.PrimaryTabs
|
||||||
import chat.revolt.internals.Platform
|
import chat.revolt.internals.Platform
|
||||||
|
import java.net.URI
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
class AboutViewModel(
|
class AboutViewModel() : ViewModel() {
|
||||||
) : ViewModel() {
|
|
||||||
var root by mutableStateOf<Root?>(null)
|
var root by mutableStateOf<Root?>(null)
|
||||||
var selectedTabIndex by mutableIntStateOf(0)
|
var selectedTabIndex by mutableIntStateOf(0)
|
||||||
|
|
||||||
|
|
@ -83,11 +82,7 @@ class AboutViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VersionItem(
|
fun VersionItem(key: String, value: String, modifier: Modifier = Modifier) {
|
||||||
key: String,
|
|
||||||
value: String,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Row(modifier) {
|
Row(modifier) {
|
||||||
Text(
|
Text(
|
||||||
text = key,
|
text = key,
|
||||||
|
|
@ -131,16 +126,15 @@ fun DebugInfo(viewModel: AboutViewModel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AboutScreen(
|
fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewModel()) {
|
||||||
navController: NavController,
|
|
||||||
viewModel: AboutViewModel = viewModel()
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val clipboardManager: ClipboardManager =
|
val clipboardManager: ClipboardManager =
|
||||||
LocalClipboardManager.current
|
LocalClipboardManager.current
|
||||||
|
|
||||||
fun copyDebugInformation() {
|
fun copyDebugInformation() {
|
||||||
clipboardManager.setText(AnnotatedString(RevoltJson.encodeToString(viewModel.getDebugInformation())))
|
clipboardManager.setText(
|
||||||
|
AnnotatedString(RevoltJson.encodeToString(viewModel.getDebugInformation()))
|
||||||
|
)
|
||||||
|
|
||||||
if (Platform.needsShowClipboardNotification()) {
|
if (Platform.needsShowClipboardNotification()) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
@ -161,7 +155,8 @@ fun AboutScreen(
|
||||||
PageHeader(
|
PageHeader(
|
||||||
text = stringResource(R.string.about),
|
text = stringResource(R.string.about),
|
||||||
showBackButton = true,
|
showBackButton = true,
|
||||||
onBackButtonClicked = { navController.popBackStack() })
|
onBackButtonClicked = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
|
||||||
PrimaryTabs(
|
PrimaryTabs(
|
||||||
tabs = listOf(
|
tabs = listOf(
|
||||||
|
|
@ -169,7 +164,8 @@ fun AboutScreen(
|
||||||
stringResource(R.string.about_tab_details)
|
stringResource(R.string.about_tab_details)
|
||||||
),
|
),
|
||||||
currentIndex = viewModel.selectedTabIndex,
|
currentIndex = viewModel.selectedTabIndex,
|
||||||
onTabSelected = { viewModel.selectedTabIndex = it })
|
onTabSelected = { viewModel.selectedTabIndex = it }
|
||||||
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ data class AboutLibraries(
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Metadata(
|
data class Metadata(
|
||||||
val generated: String,
|
val generated: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -58,7 +58,7 @@ data class License(
|
||||||
val internalHash: String? = null,
|
val internalHash: String? = null,
|
||||||
val url: String,
|
val url: String,
|
||||||
val spdxId: String? = null,
|
val spdxId: String? = null,
|
||||||
val name: String,
|
val name: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -72,26 +72,26 @@ data class Library(
|
||||||
val name: String,
|
val name: String,
|
||||||
val licenses: List<String>,
|
val licenses: List<String>,
|
||||||
val website: String? = null,
|
val website: String? = null,
|
||||||
val organization: Organization? = null,
|
val organization: Organization? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Organization(
|
data class Organization(
|
||||||
val url: String,
|
val url: String,
|
||||||
val name: String,
|
val name: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Developer(
|
data class Developer(
|
||||||
val organisationUrl: String? = null,
|
val organisationUrl: String? = null,
|
||||||
val name: String? = null,
|
val name: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Scm(
|
data class Scm(
|
||||||
val connection: String? = null,
|
val connection: String? = null,
|
||||||
val url: String,
|
val url: String,
|
||||||
val developerConnection: String? = null,
|
val developerConnection: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -151,7 +151,8 @@ fun AttributionScreen(navController: NavController) {
|
||||||
PageHeader(
|
PageHeader(
|
||||||
text = stringResource(R.string.oss_attribution),
|
text = stringResource(R.string.oss_attribution),
|
||||||
showBackButton = true,
|
showBackButton = true,
|
||||||
onBackButtonClicked = { navController.popBackStack() })
|
onBackButtonClicked = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
|
||||||
libraries?.let {
|
libraries?.let {
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
|
|
@ -207,5 +208,4 @@ fun AttributionScreen(navController: NavController) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -109,10 +109,9 @@ import com.airbnb.lottie.compose.rememberLottieComposition
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.sentry.Sentry
|
import io.sentry.Sentry
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
|
@ -202,9 +201,9 @@ class ChatRouterViewModel @Inject constructor(
|
||||||
if (!pure) setSaveCurrentChannel(channelId)
|
if (!pure) setSaveCurrentChannel(channelId)
|
||||||
|
|
||||||
@FeatureFlag("ClosedBetaAccessControl")
|
@FeatureFlag("ClosedBetaAccessControl")
|
||||||
if (RevoltAPI.channelCache.size > 0
|
if (RevoltAPI.channelCache.size > 0 &&
|
||||||
&& FeatureFlags.closedBetaAccessControl is ClosedBetaAccessControlVariates.Restricted
|
FeatureFlags.closedBetaAccessControl is ClosedBetaAccessControlVariates.Restricted &&
|
||||||
&& (FeatureFlags.closedBetaAccessControl as ClosedBetaAccessControlVariates.Restricted)
|
(FeatureFlags.closedBetaAccessControl as ClosedBetaAccessControlVariates.Restricted)
|
||||||
.predicate()
|
.predicate()
|
||||||
.not()
|
.not()
|
||||||
) {
|
) {
|
||||||
|
|
@ -229,7 +228,9 @@ class ChatRouterViewModel @Inject constructor(
|
||||||
.setFlags(
|
.setFlags(
|
||||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
)
|
)
|
||||||
context.startActivity(intent) // i'm just messing with the user at this point, they know what they did
|
context.startActivity(
|
||||||
|
intent
|
||||||
|
) // i'm just messing with the user at this point, they know what they did
|
||||||
|
|
||||||
Pipebomb.doHardCrash()
|
Pipebomb.doHardCrash()
|
||||||
}
|
}
|
||||||
|
|
@ -269,9 +270,11 @@ fun ChatRouterScreen(
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
val showSidebarSpark = remember { mutableStateOf(false) }
|
val showSidebarSpark = remember { mutableStateOf(false) }
|
||||||
val sidebarSparkComposition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.open_settings_tutorial))
|
val sidebarSparkComposition by rememberLottieComposition(
|
||||||
|
LottieCompositionSpec.RawRes(R.raw.open_settings_tutorial)
|
||||||
|
)
|
||||||
val sidebarSparkProgress by animateLottieCompositionAsState(
|
val sidebarSparkProgress by animateLottieCompositionAsState(
|
||||||
composition = sidebarSparkComposition,
|
composition = sidebarSparkComposition
|
||||||
)
|
)
|
||||||
|
|
||||||
var showPlatformModDMHint by remember { mutableStateOf(false) }
|
var showPlatformModDMHint by remember { mutableStateOf(false) }
|
||||||
|
|
@ -364,8 +367,8 @@ fun ChatRouterScreen(
|
||||||
snapshotFlow { windowSizeClass }
|
snapshotFlow { windowSizeClass }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.collect { sizeClass ->
|
.collect { sizeClass ->
|
||||||
useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded
|
useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded &&
|
||||||
&& sizeClass.heightSizeClass != WindowHeightSizeClass.Compact
|
sizeClass.heightSizeClass != WindowHeightSizeClass.Compact
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -416,7 +419,7 @@ fun ChatRouterScreen(
|
||||||
sheetState = changelogSheetState,
|
sheetState = changelogSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
viewModel.latestChangelogRead = true
|
viewModel.latestChangelogRead = true
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
ChangelogSheet(
|
ChangelogSheet(
|
||||||
version = viewModel.latestChangelog,
|
version = viewModel.latestChangelog,
|
||||||
|
|
@ -441,8 +444,12 @@ fun ChatRouterScreen(
|
||||||
.aspectRatio(1f),
|
.aspectRatio(1f),
|
||||||
renderMode = RenderMode.HARDWARE
|
renderMode = RenderMode.HARDWARE
|
||||||
)
|
)
|
||||||
Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1))
|
Text(
|
||||||
Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2))
|
stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
|
@ -488,7 +495,7 @@ fun ChatRouterScreen(
|
||||||
sheetState = statusSheetState,
|
sheetState = statusSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showStatusSheet = false
|
showStatusSheet = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
StatusSheet(
|
StatusSheet(
|
||||||
onBeforeNavigation = {
|
onBeforeNavigation = {
|
||||||
|
|
@ -511,13 +518,12 @@ fun ChatRouterScreen(
|
||||||
sheetState = addServerSheetState,
|
sheetState = addServerSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showAddServerSheet = false
|
showAddServerSheet = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
AddServerSheet()
|
AddServerSheet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (showServerContextSheet) {
|
if (showServerContextSheet) {
|
||||||
val serverContextSheetState = rememberModalBottomSheetState()
|
val serverContextSheetState = rememberModalBottomSheetState()
|
||||||
|
|
||||||
|
|
@ -525,14 +531,14 @@ fun ChatRouterScreen(
|
||||||
sheetState = serverContextSheetState,
|
sheetState = serverContextSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showServerContextSheet = false
|
showServerContextSheet = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
ServerContextSheet(
|
ServerContextSheet(
|
||||||
serverId = serverContextSheetTarget,
|
serverId = serverContextSheetTarget,
|
||||||
onHideSheet = {
|
onHideSheet = {
|
||||||
serverContextSheetState.hide()
|
serverContextSheetState.hide()
|
||||||
showServerContextSheet = false
|
showServerContextSheet = false
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -544,7 +550,7 @@ fun ChatRouterScreen(
|
||||||
sheetState = userContextSheetState,
|
sheetState = userContextSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showUserContextSheet = false
|
showUserContextSheet = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
UserContextSheet(
|
UserContextSheet(
|
||||||
userId = userContextSheetTarget,
|
userId = userContextSheetTarget,
|
||||||
|
|
@ -569,14 +575,14 @@ fun ChatRouterScreen(
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.channel_link_invalid),
|
text = stringResource(id = R.string.channel_link_invalid),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.channel_link_invalid_description),
|
text = stringResource(id = R.string.channel_link_invalid_description),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
|
@ -596,7 +602,7 @@ fun ChatRouterScreen(
|
||||||
sheetState = linkInfoSheetState,
|
sheetState = linkInfoSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showLinkInfoSheet = false
|
showLinkInfoSheet = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
LinkInfoSheet(
|
LinkInfoSheet(
|
||||||
url = linkInfoSheetUrl,
|
url = linkInfoSheetUrl,
|
||||||
|
|
@ -614,7 +620,7 @@ fun ChatRouterScreen(
|
||||||
sheetState = emoteInfoSheetState,
|
sheetState = emoteInfoSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showEmoteInfoSheet = false
|
showEmoteInfoSheet = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
EmoteInfoSheet(
|
EmoteInfoSheet(
|
||||||
id = emoteInfoSheetTarget,
|
id = emoteInfoSheetTarget,
|
||||||
|
|
@ -630,19 +636,22 @@ fun ChatRouterScreen(
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.safeDrawingPadding()
|
.safeDrawingPadding()
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) {
|
AnimatedVisibility(
|
||||||
|
visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected
|
||||||
|
) {
|
||||||
DisconnectedNotice(
|
DisconnectedNotice(
|
||||||
state = RealtimeSocket.disconnectionState,
|
state = RealtimeSocket.disconnectionState,
|
||||||
onReconnect = {
|
onReconnect = {
|
||||||
RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting)
|
RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting)
|
||||||
scope.launch { RevoltAPI.connectWS() }
|
scope.launch { RevoltAPI.connectWS() }
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useTabletAwareUI) {
|
if (useTabletAwareUI) {
|
||||||
Row {
|
Row {
|
||||||
DismissibleDrawerSheet(
|
DismissibleDrawerSheet(
|
||||||
drawerContainerColor = Color.Transparent,
|
drawerContainerColor = Color.Transparent
|
||||||
) {
|
) {
|
||||||
Sidebar(
|
Sidebar(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
|
@ -656,7 +665,7 @@ fun ChatRouterScreen(
|
||||||
},
|
},
|
||||||
onShowAddServerSheet = {
|
onShowAddServerSheet = {
|
||||||
showAddServerSheet = true
|
showAddServerSheet = true
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ChannelNavigator(
|
ChannelNavigator(
|
||||||
|
|
@ -670,7 +679,7 @@ fun ChatRouterScreen(
|
||||||
userContextSheetTarget = target
|
userContextSheetTarget = target
|
||||||
userContextSheetServer = server
|
userContextSheetServer = server
|
||||||
showUserContextSheet = true
|
showUserContextSheet = true
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -678,7 +687,7 @@ fun ChatRouterScreen(
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
DismissibleDrawerSheet(
|
DismissibleDrawerSheet(
|
||||||
drawerContainerColor = Color.Transparent,
|
drawerContainerColor = Color.Transparent
|
||||||
) {
|
) {
|
||||||
Sidebar(
|
Sidebar(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
|
@ -693,7 +702,7 @@ fun ChatRouterScreen(
|
||||||
onShowAddServerSheet = {
|
onShowAddServerSheet = {
|
||||||
showAddServerSheet = true
|
showAddServerSheet = true
|
||||||
},
|
},
|
||||||
drawerState = drawerState,
|
drawerState = drawerState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -711,10 +720,11 @@ fun ChatRouterScreen(
|
||||||
userContextSheetTarget = target
|
userContextSheetTarget = target
|
||||||
userContextSheetServer = server
|
userContextSheetServer = server
|
||||||
showUserContextSheet = true
|
showUserContextSheet = true
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -726,7 +736,7 @@ fun Sidebar(
|
||||||
drawerState: DrawerState? = null,
|
drawerState: DrawerState? = null,
|
||||||
onShowStatusSheet: () -> Unit,
|
onShowStatusSheet: () -> Unit,
|
||||||
onShowServerContextSheet: (String) -> Unit,
|
onShowServerContextSheet: (String) -> Unit,
|
||||||
onShowAddServerSheet: () -> Unit,
|
onShowAddServerSheet: () -> Unit
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
|
@ -786,9 +796,15 @@ fun Sidebar(
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val partner =
|
val partner =
|
||||||
if (it.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner(
|
if (it.channelType == ChannelType.DirectMessage) {
|
||||||
it
|
RevoltAPI.userCache[
|
||||||
)] else null
|
ChannelUtils.resolveDMPartner(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
UserAvatar(
|
UserAvatar(
|
||||||
username = partner?.let { p ->
|
username = partner?.let { p ->
|
||||||
|
|
@ -830,17 +846,22 @@ fun Sidebar(
|
||||||
// - Sort the servers that are in the ordering using the ordering.
|
// - Sort the servers that are in the ordering using the ordering.
|
||||||
// - Add the servers that aren't in the ordering to the end of the list.
|
// - Add the servers that aren't in the ordering to the end of the list.
|
||||||
// - Sort the servers that aren't in the ordering by their ID (creation order).
|
// - Sort the servers that aren't in the ordering by their ID (creation order).
|
||||||
((RevoltAPI.serverCache.values.filter {
|
(
|
||||||
SyncedSettings.ordering.servers.contains(
|
(
|
||||||
it.id
|
RevoltAPI.serverCache.values.filter {
|
||||||
|
SyncedSettings.ordering.servers.contains(
|
||||||
|
it.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }
|
||||||
|
) + (
|
||||||
|
RevoltAPI.serverCache.values.filter {
|
||||||
|
!SyncedSettings.ordering.servers.contains(
|
||||||
|
it.id
|
||||||
|
)
|
||||||
|
}.sortedBy { it.id }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }) + (RevoltAPI.serverCache.values.filter {
|
|
||||||
!SyncedSettings.ordering.servers.contains(
|
|
||||||
it.id
|
|
||||||
)
|
|
||||||
}.sortedBy { it.id }
|
|
||||||
))
|
|
||||||
.forEach { server ->
|
.forEach { server ->
|
||||||
if (server.id == null || server.name == null) return@forEach
|
if (server.id == null || server.name == null) return@forEach
|
||||||
|
|
||||||
|
|
@ -854,7 +875,7 @@ fun Sidebar(
|
||||||
/*serverContextSheetTarget = server.id
|
/*serverContextSheetTarget = server.id
|
||||||
showServerContextSheet = true*/
|
showServerContextSheet = true*/
|
||||||
onShowServerContextSheet(server.id)
|
onShowServerContextSheet(server.id)
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
viewModel.navigateToServer(
|
viewModel.navigateToServer(
|
||||||
server.id,
|
server.id,
|
||||||
|
|
@ -864,7 +885,7 @@ fun Sidebar(
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawerServerlikeIcon(
|
DrawerServerlikeIcon(
|
||||||
onClick = onShowAddServerSheet,
|
onClick = onShowAddServerSheet
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Add,
|
Icons.Default.Add,
|
||||||
|
|
@ -892,7 +913,7 @@ fun Sidebar(
|
||||||
},
|
},
|
||||||
onServerSheetOpenFor = { target ->
|
onServerSheetOpenFor = { target ->
|
||||||
onShowServerContextSheet(target)
|
onShowServerContextSheet(target)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -906,7 +927,7 @@ fun ChannelNavigator(
|
||||||
useDrawer: Boolean,
|
useDrawer: Boolean,
|
||||||
drawerBackHandler: () -> Unit,
|
drawerBackHandler: () -> Unit,
|
||||||
drawerState: DrawerState? = null,
|
drawerState: DrawerState? = null,
|
||||||
onShowUserContextSheet: (String, String?) -> Unit,
|
onShowUserContextSheet: (String, String?) -> Unit
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
|
@ -931,8 +952,11 @@ fun ChannelNavigator(
|
||||||
channelId = channelId,
|
channelId = channelId,
|
||||||
onToggleDrawer = {
|
onToggleDrawer = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (drawerState?.isOpen == true) drawerState.close()
|
if (drawerState?.isOpen == true) {
|
||||||
else drawerState?.open()
|
drawerState.close()
|
||||||
|
} else {
|
||||||
|
drawerState?.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUserSheetOpenFor = { target, server ->
|
onUserSheetOpenFor = { target, server ->
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ fun FeedbackDialog(navController: NavController) {
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.settings_feedback_category),
|
text = stringResource(id = R.string.settings_feedback_category)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
|
|
@ -181,7 +181,9 @@ fun FeedbackDialog(navController: NavController) {
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ArrowDropDown,
|
imageVector = Icons.Default.ArrowDropDown,
|
||||||
contentDescription = stringResource(id = R.string.settings_feedback_category)
|
contentDescription = stringResource(
|
||||||
|
id = R.string.settings_feedback_category
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -192,7 +194,7 @@ fun FeedbackDialog(navController: NavController) {
|
||||||
expanded = categoryDropdownExpanded.value,
|
expanded = categoryDropdownExpanded.value,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
categoryDropdownExpanded.value = false
|
categoryDropdownExpanded.value = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
category.forEach { (key, value) ->
|
category.forEach { (key, value) ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
|
|
@ -218,7 +220,7 @@ fun FeedbackDialog(navController: NavController) {
|
||||||
},
|
},
|
||||||
supportingText = {
|
supportingText = {
|
||||||
Text(
|
Text(
|
||||||
text = "${message.value.length}/1250",
|
text = "${message.value.length}/1250"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
enabled = !sending.value,
|
enabled = !sending.value,
|
||||||
|
|
@ -233,7 +235,7 @@ fun FeedbackDialog(navController: NavController) {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
},
|
},
|
||||||
modifier = Modifier.testTag("feedback_cancel"),
|
modifier = Modifier.testTag("feedback_cancel"),
|
||||||
enabled = !sending.value,
|
enabled = !sending.value
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(id = R.string.cancel))
|
Text(text = stringResource(id = R.string.cancel))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,7 @@ enum class ReportFlowState {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReportMessageDialog(
|
fun ReportMessageDialog(navController: NavController, messageId: String) {
|
||||||
navController: NavController,
|
|
||||||
messageId: String,
|
|
||||||
) {
|
|
||||||
val message = RevoltAPI.messageCache[messageId]
|
val message = RevoltAPI.messageCache[messageId]
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
|
|
@ -84,7 +81,7 @@ fun ReportMessageDialog(
|
||||||
"SpamAbuse" to stringResource(id = R.string.report_reason_content_spam_abuse),
|
"SpamAbuse" to stringResource(id = R.string.report_reason_content_spam_abuse),
|
||||||
"Malware" to stringResource(id = R.string.report_reason_content_malware),
|
"Malware" to stringResource(id = R.string.report_reason_content_malware),
|
||||||
"Harassment" to stringResource(id = R.string.report_reason_content_harassment),
|
"Harassment" to stringResource(id = R.string.report_reason_content_harassment),
|
||||||
"Other" to stringResource(id = R.string.report_reason_content_other),
|
"Other" to stringResource(id = R.string.report_reason_content_other)
|
||||||
)
|
)
|
||||||
val reasonDropdownExpanded = remember { mutableStateOf(false) }
|
val reasonDropdownExpanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
@ -146,7 +143,7 @@ fun ReportMessageDialog(
|
||||||
},
|
},
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.report_reason),
|
text = stringResource(id = R.string.report_reason)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
|
|
@ -159,7 +156,9 @@ fun ReportMessageDialog(
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ArrowDropDown,
|
imageVector = Icons.Default.ArrowDropDown,
|
||||||
contentDescription = stringResource(id = R.string.report_reason)
|
contentDescription = stringResource(
|
||||||
|
id = R.string.report_reason
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -170,7 +169,7 @@ fun ReportMessageDialog(
|
||||||
expanded = reasonDropdownExpanded.value,
|
expanded = reasonDropdownExpanded.value,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
reasonDropdownExpanded.value = false
|
reasonDropdownExpanded.value = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
reasons.forEach { (key, value) ->
|
reasons.forEach { (key, value) ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
|
|
@ -196,7 +195,9 @@ fun ReportMessageDialog(
|
||||||
},
|
},
|
||||||
supportingText = {
|
supportingText = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.report_reason_additional_hint)
|
text = stringResource(
|
||||||
|
id = R.string.report_reason_additional_hint
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -282,11 +283,11 @@ fun ReportMessageDialog(
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.report_submit_success),
|
text = stringResource(id = R.string.report_submit_success),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column() {
|
Column {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
|
@ -349,11 +350,11 @@ fun ReportMessageDialog(
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.report_submit_error_header),
|
text = stringResource(id = R.string.report_submit_error_header),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column() {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.report_submit_error),
|
text = stringResource(id = R.string.report_submit_error),
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,12 @@ fun HomeScreen(navController: NavController) {
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "🐈",
|
text = "🐈",
|
||||||
fontSize = 100.sp,
|
fontSize = 100.sp,
|
||||||
modifier = Modifier.rotate(catRotation),
|
modifier = Modifier.rotate(catRotation)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +74,11 @@ fun HomeScreen(navController: NavController) {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
heading = { Text(text = stringResource(id = R.string.home_join_jenvolt)) },
|
heading = { Text(text = stringResource(id = R.string.home_join_jenvolt)) },
|
||||||
description = { Text(text = stringResource(id = R.string.home_join_jenvolt_description)) },
|
description = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.home_join_jenvolt_description)
|
||||||
|
)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
context.startActivity(
|
context.startActivity(
|
||||||
Intent(
|
Intent(
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ fun NoCurrentChannelScreen() {
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(64.dp),
|
.padding(64.dp),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.no_active_channel),
|
text = stringResource(R.string.no_active_channel),
|
||||||
|
|
@ -34,7 +34,7 @@ fun NoCurrentChannelScreen() {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.no_active_channel_body),
|
text = stringResource(R.string.no_active_channel_body),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,10 +91,10 @@ import chat.revolt.sheets.ChannelInfoSheet
|
||||||
import chat.revolt.sheets.MessageContextSheet
|
import chat.revolt.sheets.MessageContextSheet
|
||||||
import com.discord.simpleast.core.simple.SimpleMarkdownRules
|
import com.discord.simpleast.core.simple.SimpleMarkdownRules
|
||||||
import com.discord.simpleast.core.simple.SimpleRenderer
|
import com.discord.simpleast.core.simple.SimpleRenderer
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -207,10 +207,10 @@ fun ChannelScreen(
|
||||||
sheetState = channelInfoSheetState,
|
sheetState = channelInfoSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
channelInfoSheetShown = false
|
channelInfoSheetShown = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
ChannelInfoSheet(
|
ChannelInfoSheet(
|
||||||
channelId = channelId,
|
channelId = channelId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -222,7 +222,7 @@ fun ChannelScreen(
|
||||||
sheetState = messageContextSheetState,
|
sheetState = messageContextSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
messageContextSheetShown = false
|
messageContextSheetShown = false
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
MessageContextSheet(
|
MessageContextSheet(
|
||||||
messageId = messageContextSheetTarget,
|
messageId = messageContextSheetTarget,
|
||||||
|
|
@ -232,7 +232,7 @@ fun ChannelScreen(
|
||||||
},
|
},
|
||||||
onReportMessage = {
|
onReportMessage = {
|
||||||
navController.navigate("report/message/$messageContextSheetTarget")
|
navController.navigate("report/message/$messageContextSheetTarget")
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +252,7 @@ fun ChannelScreen(
|
||||||
channelInfoSheetShown = true
|
channelInfoSheetShown = true
|
||||||
},
|
},
|
||||||
onToggleDrawer = onToggleDrawer,
|
onToggleDrawer = onToggleDrawer,
|
||||||
useDrawer = useDrawer,
|
useDrawer = useDrawer
|
||||||
)
|
)
|
||||||
|
|
||||||
val isScrolledToBottom = remember(lazyListState) {
|
val isScrolledToBottom = remember(lazyListState) {
|
||||||
|
|
@ -310,12 +310,12 @@ fun ChannelScreen(
|
||||||
parse = {
|
parse = {
|
||||||
val parser = MarkdownParser()
|
val parser = MarkdownParser()
|
||||||
.addRules(
|
.addRules(
|
||||||
SimpleMarkdownRules.createEscapeRule(),
|
SimpleMarkdownRules.createEscapeRule()
|
||||||
)
|
)
|
||||||
.addRevoltRules(context)
|
.addRevoltRules(context)
|
||||||
.addRules(
|
.addRules(
|
||||||
createCodeRule(context, codeBlockColor.toArgb()),
|
createCodeRule(context, codeBlockColor.toArgb()),
|
||||||
createInlineCodeRule(context, codeBlockColor.toArgb()),
|
createInlineCodeRule(context, codeBlockColor.toArgb())
|
||||||
)
|
)
|
||||||
.addRules(
|
.addRules(
|
||||||
SimpleMarkdownRules.createSimpleMarkdownRules(
|
SimpleMarkdownRules.createSimpleMarkdownRules(
|
||||||
|
|
@ -366,7 +366,7 @@ fun ChannelScreen(
|
||||||
return@Message
|
return@Message
|
||||||
}
|
}
|
||||||
viewModel.replyToMessage(message)
|
viewModel.replyToMessage(message)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -399,11 +399,11 @@ fun ChannelScreen(
|
||||||
!isScrolledToBottom.value,
|
!isScrolledToBottom.value,
|
||||||
enter = slideInHorizontally(
|
enter = slideInHorizontally(
|
||||||
animationSpec = RevoltTweenInt,
|
animationSpec = RevoltTweenInt,
|
||||||
initialOffsetX = { it },
|
initialOffsetX = { it }
|
||||||
) + fadeIn(animationSpec = RevoltTweenFloat),
|
) + fadeIn(animationSpec = RevoltTweenFloat),
|
||||||
exit = slideOutHorizontally(
|
exit = slideOutHorizontally(
|
||||||
animationSpec = RevoltTweenInt,
|
animationSpec = RevoltTweenInt,
|
||||||
targetOffsetX = { it },
|
targetOffsetX = { it }
|
||||||
) + fadeOut(animationSpec = RevoltTweenFloat),
|
) + fadeOut(animationSpec = RevoltTweenFloat),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
|
|
@ -460,7 +460,7 @@ fun ChannelScreen(
|
||||||
attachments = viewModel.pendingAttachments,
|
attachments = viewModel.pendingAttachments,
|
||||||
uploading = viewModel.isSendingMessage,
|
uploading = viewModel.isSendingMessage,
|
||||||
uploadProgress = viewModel.pendingUploadProgress,
|
uploadProgress = viewModel.pendingUploadProgress,
|
||||||
onRemove = { viewModel.pendingAttachments.remove(it) },
|
onRemove = { viewModel.pendingAttachments.remove(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -525,12 +525,14 @@ fun ChannelScreen(
|
||||||
if (nowFocused && viewModel.currentBottomPane != BottomPane.None) {
|
if (nowFocused && viewModel.currentBottomPane != BottomPane.None) {
|
||||||
viewModel.currentBottomPane = BottomPane.None
|
viewModel.currentBottomPane = BottomPane.None
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(visible = viewModel.currentBottomPane == BottomPane.InbuiltMediaPicker) {
|
AnimatedVisibility(
|
||||||
|
visible = viewModel.currentBottomPane == BottomPane.InbuiltMediaPicker
|
||||||
|
) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
InbuiltMediaPicker(
|
InbuiltMediaPicker(
|
||||||
onOpenDocumentsUi = {
|
onOpenDocumentsUi = {
|
||||||
|
|
@ -573,7 +575,9 @@ fun ChannelScreen(
|
||||||
if (e is FileNotFoundException) {
|
if (e is FileNotFoundException) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.file_picker_cannot_attach_file_invalid),
|
context.getString(
|
||||||
|
R.string.file_picker_cannot_attach_file_invalid
|
||||||
|
),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
@ -582,7 +586,7 @@ fun ChannelScreen(
|
||||||
pendingMedia = viewModel.pendingAttachments
|
pendingMedia = viewModel.pendingAttachments
|
||||||
.filterNot { it.pickerIdentifier == null }
|
.filterNot { it.pickerIdentifier == null }
|
||||||
.map { it.pickerIdentifier!! },
|
.map { it.pickerIdentifier!! },
|
||||||
disabled = viewModel.isSendingMessage,
|
disabled = viewModel.isSendingMessage
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -315,16 +315,19 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
currentMsg.id == it.id
|
currentMsg.id == it.id
|
||||||
} ?: return@onEach // Message not found, ignore.
|
} ?: return@onEach // Message not found, ignore.
|
||||||
|
|
||||||
if (messageFrame.author != null)
|
if (messageFrame.author != null) {
|
||||||
addUserIfUnknown(messageFrame.author)
|
addUserIfUnknown(messageFrame.author)
|
||||||
|
}
|
||||||
|
|
||||||
regroupMessages(renderableMessages.map { currentMsg ->
|
regroupMessages(
|
||||||
if (currentMsg.id == it.id) {
|
renderableMessages.map { currentMsg ->
|
||||||
currentMsg.mergeWithPartial(messageFrame)
|
if (currentMsg.id == it.id) {
|
||||||
} else {
|
currentMsg.mergeWithPartial(messageFrame)
|
||||||
currentMsg
|
} else {
|
||||||
|
currentMsg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is MessageAppendFrame -> {
|
is MessageAppendFrame -> {
|
||||||
|
|
@ -336,13 +339,15 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
|
|
||||||
if (!hasMessage) return@onEach
|
if (!hasMessage) return@onEach
|
||||||
|
|
||||||
regroupMessages(renderableMessages.map { currentMsg ->
|
regroupMessages(
|
||||||
if (currentMsg.id == it.id) {
|
renderableMessages.map { currentMsg ->
|
||||||
RevoltAPI.messageCache[it.id] ?: currentMsg
|
if (currentMsg.id == it.id) {
|
||||||
} else {
|
RevoltAPI.messageCache[it.id] ?: currentMsg
|
||||||
currentMsg
|
} else {
|
||||||
|
currentMsg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is MessageDeleteFrame -> {
|
is MessageDeleteFrame -> {
|
||||||
|
|
@ -458,8 +463,8 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val newContent = currentContent.substring(0, currentSelection.start) +
|
val newContent = currentContent.substring(0, currentSelection.start) +
|
||||||
content +
|
content +
|
||||||
currentContent.substring(currentSelection.end)
|
currentContent.substring(currentSelection.end)
|
||||||
|
|
||||||
pendingMessageContent = newContent
|
pendingMessageContent = newContent
|
||||||
textSelection = TextRange(currentSelection.start + content.length)
|
textSelection = TextRange(currentSelection.start + content.length)
|
||||||
|
|
@ -469,9 +474,12 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
if (activeChannel == null) return
|
if (activeChannel == null) return
|
||||||
|
|
||||||
val selfUser = RevoltAPI.userCache[RevoltAPI.selfId] ?: return
|
val selfUser = RevoltAPI.userCache[RevoltAPI.selfId] ?: return
|
||||||
val selfMember = if (activeChannel!!.server == null) null else
|
val selfMember = if (activeChannel!!.server == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
activeChannel?.server?.let { RevoltAPI.members.getMember(it, selfUser.id!!) }
|
activeChannel?.server?.let { RevoltAPI.members.getMember(it, selfUser.id!!) }
|
||||||
?: fetchMember(activeChannel!!.server!!, selfUser.id!!)
|
?: fetchMember(activeChannel!!.server!!, selfUser.id!!)
|
||||||
|
}
|
||||||
|
|
||||||
val hasPermission =
|
val hasPermission =
|
||||||
Roles.permissionFor(activeChannel!!, selfUser, selfMember)
|
Roles.permissionFor(activeChannel!!, selfUser, selfMember)
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ fun LoginGreetingScreen(navController: NavController) {
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -105,7 +105,7 @@ fun LoginGreetingScreen(navController: NavController) {
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||||
|
|
@ -157,7 +157,6 @@ fun LoginGreetingScreen(navController: NavController) {
|
||||||
url = "$REVOLT_MARKETING/aup"
|
url = "$REVOLT_MARKETING/aup"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -49,12 +49,12 @@ import chat.revolt.components.generic.FormTextField
|
||||||
import chat.revolt.components.generic.Weblink
|
import chat.revolt.components.generic.Weblink
|
||||||
import chat.revolt.persistence.KVStorage
|
import chat.revolt.persistence.KVStorage
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class LoginViewModel @Inject constructor(
|
class LoginViewModel @Inject constructor(
|
||||||
private val kvStorage: KVStorage,
|
private val kvStorage: KVStorage
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private var _email by mutableStateOf("")
|
private var _email by mutableStateOf("")
|
||||||
val email: String
|
val email: String
|
||||||
|
|
@ -137,10 +137,7 @@ class LoginViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreen(
|
fun LoginScreen(navController: NavController, viewModel: LoginViewModel = hiltViewModel()) {
|
||||||
navController: NavController,
|
|
||||||
viewModel: LoginViewModel = hiltViewModel()
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
LaunchedEffect(viewModel.navigateTo) {
|
LaunchedEffect(viewModel.navigateTo) {
|
||||||
|
|
@ -196,10 +193,9 @@ fun LoginScreen(
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(270.dp),
|
.width(270.dp),
|
||||||
|
|
@ -216,7 +212,7 @@ fun LoginScreen(
|
||||||
value = viewModel.password,
|
value = viewModel.password,
|
||||||
label = stringResource(R.string.password),
|
label = stringResource(R.string.password),
|
||||||
type = KeyboardType.Password,
|
type = KeyboardType.Password,
|
||||||
onChange = viewModel::setPassword,
|
onChange = viewModel::setPassword
|
||||||
)
|
)
|
||||||
|
|
||||||
AnyLink(
|
AnyLink(
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,12 @@ import chat.revolt.components.generic.CollapsibleCard
|
||||||
import chat.revolt.components.generic.FormTextField
|
import chat.revolt.components.generic.FormTextField
|
||||||
import chat.revolt.persistence.KVStorage
|
import chat.revolt.persistence.KVStorage
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MfaScreenViewModel @Inject constructor(
|
class MfaScreenViewModel @Inject constructor(
|
||||||
private val kvStorage: KVStorage,
|
private val kvStorage: KVStorage
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private var _totpCode by mutableStateOf("")
|
private var _totpCode by mutableStateOf("")
|
||||||
val totpCode: String
|
val totpCode: String
|
||||||
|
|
@ -183,7 +183,7 @@ fun MfaScreen(
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp)
|
.padding(horizontal = 20.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -193,7 +193,7 @@ fun MfaScreen(
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||||
|
|
@ -215,7 +215,6 @@ fun MfaScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Collapsible cards for each auth type
|
// Collapsible cards for each auth type
|
||||||
allowedAuthTypes.forEach { authType ->
|
allowedAuthTypes.forEach { authType ->
|
||||||
when (authType) {
|
when (authType) {
|
||||||
|
|
@ -245,7 +244,7 @@ fun MfaScreen(
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||||
|
|
@ -256,7 +255,7 @@ fun MfaScreen(
|
||||||
label = stringResource(R.string.mfa_totp_code),
|
label = stringResource(R.string.mfa_totp_code),
|
||||||
onChange = viewModel::setTotpCode,
|
onChange = viewModel::setTotpCode,
|
||||||
type = KeyboardType.Number,
|
type = KeyboardType.Number,
|
||||||
value = viewModel.totpCode,
|
value = viewModel.totpCode
|
||||||
)
|
)
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
|
|
@ -267,7 +266,7 @@ fun MfaScreen(
|
||||||
.testTag("do_totp_button")
|
.testTag("do_totp_button")
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.next),
|
text = stringResource(R.string.next)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -300,7 +299,7 @@ fun MfaScreen(
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||||
|
|
@ -310,7 +309,7 @@ fun MfaScreen(
|
||||||
FormTextField(
|
FormTextField(
|
||||||
label = stringResource(R.string.mfa_recovery_code),
|
label = stringResource(R.string.mfa_recovery_code),
|
||||||
onChange = viewModel::setRecoveryCode,
|
onChange = viewModel::setRecoveryCode,
|
||||||
value = viewModel.recoveryCode,
|
value = viewModel.recoveryCode
|
||||||
)
|
)
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
|
|
@ -321,7 +320,7 @@ fun MfaScreen(
|
||||||
.testTag("do_mfa_recovery_button")
|
.testTag("do_mfa_recovery_button")
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.next),
|
text = stringResource(R.string.next)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ fun OnboardingScreen(navController: NavController) {
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 10.dp)
|
.padding(horizontal = 10.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
@ -95,7 +95,7 @@ fun OnboardingScreen(navController: NavController) {
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 10.dp)
|
.padding(horizontal = 10.dp)
|
||||||
|
|
@ -111,7 +111,7 @@ fun OnboardingScreen(navController: NavController) {
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 10.dp)
|
.padding(horizontal = 10.dp)
|
||||||
|
|
@ -127,7 +127,7 @@ fun OnboardingScreen(navController: NavController) {
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 10.dp)
|
.padding(horizontal = 10.dp)
|
||||||
|
|
@ -145,7 +145,7 @@ fun OnboardingScreen(navController: NavController) {
|
||||||
FormTextField(
|
FormTextField(
|
||||||
value = username.value,
|
value = username.value,
|
||||||
onChange = { username.value = it },
|
onChange = { username.value = it },
|
||||||
label = stringResource(R.string.onboarding_username),
|
label = stringResource(R.string.onboarding_username)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (error.value.isNotBlank()) {
|
if (error.value.isNotBlank()) {
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ class RegisterDetailsScreenViewModel : ViewModel() {
|
||||||
val result = register(body)
|
val result = register(body)
|
||||||
|
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
navController.navigate("register/verify/${email}")
|
navController.navigate("register/verify/$email")
|
||||||
} else {
|
} else {
|
||||||
error = result.unwrapError().type
|
error = result.unwrapError().type
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +131,7 @@ fun RegisterDetailsScreen(
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 10.dp)
|
.padding(horizontal = 10.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
@ -143,7 +143,7 @@ fun RegisterDetailsScreen(
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 10.dp)
|
.padding(horizontal = 10.dp)
|
||||||
|
|
@ -161,7 +161,7 @@ fun RegisterDetailsScreen(
|
||||||
FormTextField(
|
FormTextField(
|
||||||
value = viewModel.email,
|
value = viewModel.email,
|
||||||
onChange = { viewModel.email = it },
|
onChange = { viewModel.email = it },
|
||||||
label = stringResource(R.string.register_email),
|
label = stringResource(R.string.register_email)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.register_email_verification_hint),
|
text = stringResource(R.string.register_email_verification_hint),
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ fun RegisterGreetingScreen(navController: NavController) {
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
@ -74,7 +74,7 @@ fun RegisterGreetingScreen(navController: NavController) {
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||||
|
|
@ -99,6 +99,3 @@ fun RegisterGreetingScreen(navController: NavController) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@ import chat.revolt.R
|
||||||
@Composable
|
@Composable
|
||||||
fun RegisterVerifyScreen(navController: NavController, email: String) {
|
fun RegisterVerifyScreen(navController: NavController, email: String) {
|
||||||
val intentLauncher =
|
val intentLauncher =
|
||||||
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {}
|
rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
|
) {}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -53,7 +55,7 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp)
|
.padding(horizontal = 20.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
@ -65,7 +67,7 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp)
|
.padding(horizontal = 20.dp)
|
||||||
|
|
@ -81,7 +83,7 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp)
|
.padding(horizontal = 20.dp)
|
||||||
|
|
@ -100,6 +102,3 @@ fun RegisterVerifyScreen(navController: NavController, email: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,8 @@ fun AppearanceSettingsScreen(
|
||||||
showBackButton = true,
|
showBackButton = true,
|
||||||
onBackButtonClicked = {
|
onBackButtonClicked = {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -80,13 +81,13 @@ fun AppearanceSettingsScreen(
|
||||||
|
|
||||||
FlowRow(
|
FlowRow(
|
||||||
mainAxisSpacing = 10.dp,
|
mainAxisSpacing = 10.dp,
|
||||||
crossAxisSpacing = 10.dp,
|
crossAxisSpacing = 10.dp
|
||||||
) {
|
) {
|
||||||
ThemeChip(
|
ThemeChip(
|
||||||
color = Color(0xff1c243c),
|
color = Color(0xff1c243c),
|
||||||
text = stringResource(id = R.string.settings_appearance_theme_revolt),
|
text = stringResource(id = R.string.settings_appearance_theme_revolt),
|
||||||
selected = GlobalState.theme == Theme.Revolt,
|
selected = GlobalState.theme == Theme.Revolt,
|
||||||
modifier = Modifier.weight(1f).testTag("set_theme_revolt"),
|
modifier = Modifier.weight(1f).testTag("set_theme_revolt")
|
||||||
) {
|
) {
|
||||||
setNewTheme(Theme.Revolt)
|
setNewTheme(Theme.Revolt)
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +96,7 @@ fun AppearanceSettingsScreen(
|
||||||
color = Color(0xfff7f7f7),
|
color = Color(0xfff7f7f7),
|
||||||
text = stringResource(id = R.string.settings_appearance_theme_light),
|
text = stringResource(id = R.string.settings_appearance_theme_light),
|
||||||
selected = GlobalState.theme == Theme.Light,
|
selected = GlobalState.theme == Theme.Light,
|
||||||
modifier = Modifier.weight(1f).testTag("set_theme_light"),
|
modifier = Modifier.weight(1f).testTag("set_theme_light")
|
||||||
) {
|
) {
|
||||||
setNewTheme(Theme.Light)
|
setNewTheme(Theme.Light)
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +105,7 @@ fun AppearanceSettingsScreen(
|
||||||
color = Color(0xff000000),
|
color = Color(0xff000000),
|
||||||
text = stringResource(id = R.string.settings_appearance_theme_amoled),
|
text = stringResource(id = R.string.settings_appearance_theme_amoled),
|
||||||
selected = GlobalState.theme == Theme.Amoled,
|
selected = GlobalState.theme == Theme.Amoled,
|
||||||
modifier = Modifier.weight(1f).testTag("set_theme_amoled"),
|
modifier = Modifier.weight(1f).testTag("set_theme_amoled")
|
||||||
) {
|
) {
|
||||||
setNewTheme(Theme.Amoled)
|
setNewTheme(Theme.Amoled)
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +114,7 @@ fun AppearanceSettingsScreen(
|
||||||
color = if (isSystemInDarkTheme()) Color(0xff1c243c) else Color(0xfff7f7f7),
|
color = if (isSystemInDarkTheme()) Color(0xff1c243c) else Color(0xfff7f7f7),
|
||||||
text = stringResource(id = R.string.settings_appearance_theme_none),
|
text = stringResource(id = R.string.settings_appearance_theme_none),
|
||||||
selected = GlobalState.theme == Theme.None,
|
selected = GlobalState.theme == Theme.None,
|
||||||
modifier = Modifier.weight(1f).testTag("set_theme_none"),
|
modifier = Modifier.weight(1f).testTag("set_theme_none")
|
||||||
) {
|
) {
|
||||||
setNewTheme(Theme.None)
|
setNewTheme(Theme.None)
|
||||||
}
|
}
|
||||||
|
|
@ -123,20 +124,24 @@ fun AppearanceSettingsScreen(
|
||||||
color = dynamicDarkColorScheme(LocalContext.current).primary,
|
color = dynamicDarkColorScheme(LocalContext.current).primary,
|
||||||
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic),
|
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic),
|
||||||
selected = GlobalState.theme == Theme.M3Dynamic,
|
selected = GlobalState.theme == Theme.M3Dynamic,
|
||||||
modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic"),
|
modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic")
|
||||||
) {
|
) {
|
||||||
setNewTheme(Theme.M3Dynamic)
|
setNewTheme(Theme.M3Dynamic)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ThemeChip(
|
ThemeChip(
|
||||||
color = Color(0xffa0a0a0),
|
color = Color(0xffa0a0a0),
|
||||||
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic_unsupported),
|
text = stringResource(
|
||||||
|
id = R.string.settings_appearance_theme_m3dynamic_unsupported
|
||||||
|
),
|
||||||
selected = false,
|
selected = false,
|
||||||
modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic_unsupported"),
|
modifier = Modifier.weight(1f).testTag("set_theme_m3dynamic_unsupported")
|
||||||
) {
|
) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.settings_appearance_theme_m3dynamic_unsupported_toast),
|
context.getString(
|
||||||
|
R.string.settings_appearance_theme_m3dynamic_unsupported_toast
|
||||||
|
),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,8 @@ fun ChangelogsSettingsScreen(
|
||||||
showBackButton = true,
|
showBackButton = true,
|
||||||
onBackButtonClicked = {
|
onBackButtonClicked = {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
items(
|
items(
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue