feat: spoilered images (send and recv)
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
bdd706fda5
commit
a7c1a20fbc
|
|
@ -22,13 +22,13 @@ plugins {
|
|||
val composeBomVersion = "2025.03.00"
|
||||
val accompanistVersion = "0.34.0"
|
||||
val okhttpVersion = "4.12.0"
|
||||
val navVersion = "2.8.7"
|
||||
val navVersion = "2.9.0"
|
||||
val hiltVersion = "2.52"
|
||||
val glideVersion = "4.16.0"
|
||||
val ktorVersion = "3.0.0-beta-2"
|
||||
val media3Version = "1.5.0"
|
||||
val media3Version = "1.7.1"
|
||||
val livekitVersion = "2.2.0"
|
||||
val material3Version = "1.4.0-alpha10"
|
||||
val material3Version = "1.4.0-alpha15"
|
||||
val androidXTestVersion = "1.6.1"
|
||||
|
||||
fun property(fileName: String, propertyName: String, fallbackEnv: String? = null): String? {
|
||||
|
|
@ -184,14 +184,14 @@ sentry {
|
|||
|
||||
dependencies {
|
||||
// Android/Kotlin Core
|
||||
implementation("androidx.core:core-ktx:1.15.0")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect:2.0.10")
|
||||
implementation("androidx.core:core-ktx:1.16.0")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect:2.0.20")
|
||||
|
||||
// Kotlinx - various first-party extensions for Kotlin
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.6.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
||||
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
|
||||
implementation("androidx.profileinstaller:profileinstaller:1.4.1")
|
||||
|
||||
// Compose BOM
|
||||
val composeBom = platform("androidx.compose:compose-bom:$composeBomVersion")
|
||||
|
|
@ -200,16 +200,16 @@ dependencies {
|
|||
androidTestImplementation(composeBom)
|
||||
|
||||
// Jetpack Compose
|
||||
implementation("androidx.compose.ui:ui:1.8.0-rc01")
|
||||
implementation("androidx.compose.ui:ui:1.8.2")
|
||||
implementation("androidx.compose.ui:ui-util")
|
||||
implementation("androidx.compose.material3:material3:$material3Version")
|
||||
implementation("androidx.compose.material3:material3-window-size-class:$material3Version")
|
||||
implementation("androidx.compose.material:material-icons-core:1.7.8")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation("androidx.compose.runtime:runtime-livedata")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
|
||||
implementation("androidx.activity:activity-compose:1.10.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.1")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.1")
|
||||
implementation("androidx.activity:activity-compose:1.10.1")
|
||||
|
||||
// Accompanist - Jetpack Compose Extensions
|
||||
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
|
||||
|
|
@ -243,26 +243,26 @@ dependencies {
|
|||
implementation("com.mikepenz:aboutlibraries-core:11.3.0-rc02")
|
||||
|
||||
// Sentry - crash reporting
|
||||
implementation("io.sentry:sentry-android:7.16.0")
|
||||
implementation("io.sentry:sentry-compose-android:7.16.0")
|
||||
implementation("io.sentry:sentry-android:8.13.2")
|
||||
implementation("io.sentry:sentry-compose-android:8.13.2")
|
||||
|
||||
// Other AndroidX libraries - used for various things and never seem to have a consistent version
|
||||
implementation("androidx.documentfile:documentfile:1.0.1")
|
||||
// Other AndroidX libraries
|
||||
implementation("androidx.documentfile:documentfile:1.1.0")
|
||||
implementation("androidx.browser:browser:1.8.0")
|
||||
implementation("androidx.webkit:webkit:1.12.1")
|
||||
implementation("androidx.core:core-splashscreen:1.2.0-beta01")
|
||||
implementation("androidx.webkit:webkit:1.14.0")
|
||||
implementation("androidx.core:core-splashscreen:1.2.0-beta02")
|
||||
implementation("androidx.palette:palette-ktx:1.0.0")
|
||||
|
||||
// Libraries used for legacy View-based UI
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
|
||||
// hCaptcha - captcha provider
|
||||
implementation("com.github.hcaptcha:hcaptcha-android-sdk:3.8.1")
|
||||
|
||||
// JDK Desugaring - polyfill for new Java APIs
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||
|
||||
// AndroidX Media3 w/ ExoPlayer
|
||||
implementation("androidx.media3:media3-exoplayer:$media3Version")
|
||||
|
|
@ -273,15 +273,17 @@ dependencies {
|
|||
// Compose libraries
|
||||
implementation("me.saket.telephoto:zoomable-image:1.0.0-alpha02")
|
||||
implementation("me.saket.telephoto:zoomable-image-glide:1.0.0-alpha02")
|
||||
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.1")
|
||||
implementation("dev.chrisbanes.haze:haze:1.6.4")
|
||||
implementation("dev.chrisbanes.haze:haze-materials:1.6.4")
|
||||
|
||||
// ZXing - QR Code generation
|
||||
implementation("com.google.zxing:core:3.5.3")
|
||||
|
||||
// Persistence
|
||||
implementation("app.cash.sqldelight:android-driver:2.0.1")
|
||||
implementation("androidx.datastore:datastore:1.1.2")
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.2")
|
||||
implementation("androidx.datastore:datastore:1.1.7")
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.7")
|
||||
|
||||
// Markup
|
||||
implementation("org.jetbrains:markdown:0.7.3")
|
||||
|
|
@ -292,7 +294,7 @@ dependencies {
|
|||
// implementation "io.livekit:livekit-android:$livekit_version"
|
||||
|
||||
// Firebase - Cloud Messaging
|
||||
implementation(platform("com.google.firebase:firebase-bom:33.9.0"))
|
||||
implementation(platform("com.google.firebase:firebase-bom:33.15.0"))
|
||||
implementation("com.google.firebase:firebase-messaging")
|
||||
|
||||
// Shimmer - loading animations
|
||||
|
|
|
|||
|
|
@ -377,6 +377,16 @@ fun ShareTargetScreen(
|
|||
attachments = viewModel.attachments,
|
||||
uploading = viewModel.attachmentsUploading,
|
||||
uploadProgress = viewModel.attachmentProgress,
|
||||
onToggleSpoiler = {
|
||||
val index = viewModel.attachments
|
||||
.indexOfFirst { a -> a.pickerIdentifier == it.pickerIdentifier }
|
||||
if (index != -1) {
|
||||
val attachment = viewModel.attachments[index]
|
||||
viewModel.attachments[index] = attachment.copy(
|
||||
spoiler = !attachment.spoiler
|
||||
)
|
||||
}
|
||||
},
|
||||
onRemove = {},
|
||||
canRemove = false
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ data class FileArgs(
|
|||
val file: File,
|
||||
val filename: String,
|
||||
val contentType: String,
|
||||
val pickerIdentifier: String? = null
|
||||
val spoiler: Boolean = false,
|
||||
val pickerIdentifier: String? = null,
|
||||
)
|
||||
|
||||
suspend fun uploadToAutumn(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package chat.revolt.composables.chat
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.text.format.Formatter
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -20,6 +21,10 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -34,6 +39,11 @@ import chat.revolt.api.REVOLT_FILES
|
|||
import chat.revolt.api.schemas.AutumnResource
|
||||
import chat.revolt.composables.generic.RemoteImage
|
||||
import chat.revolt.composables.media.AudioPlayer
|
||||
import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
|
||||
@Composable
|
||||
fun FileAttachment(attachment: AutumnResource) {
|
||||
|
|
@ -71,9 +81,14 @@ fun FileAttachment(attachment: AutumnResource) {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalHazeMaterialsApi::class)
|
||||
@SuppressLint("UnusedBoxWithConstraintsScope")
|
||||
@Composable
|
||||
fun ImageAttachment(attachment: AutumnResource) {
|
||||
val url = "$REVOLT_FILES/attachments/${attachment.id}/${attachment.filename}"
|
||||
var spoilerShown by remember { mutableStateOf(false) }
|
||||
val hazeState =
|
||||
if (attachment.filename?.startsWith("SPOILER_") == true) rememberHazeState() else null
|
||||
|
||||
BoxWithConstraints {
|
||||
RemoteImage(
|
||||
|
|
@ -83,9 +98,34 @@ fun ImageAttachment(attachment: AutumnResource) {
|
|||
.width(attachment.metadata?.width?.toInt()?.dp ?: maxWidth)
|
||||
.aspectRatio(
|
||||
attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()
|
||||
)
|
||||
.then(
|
||||
if (hazeState != null) Modifier.hazeSource(state = hazeState)
|
||||
else Modifier
|
||||
),
|
||||
description = attachment.filename ?: "Image"
|
||||
)
|
||||
if (attachment.filename?.startsWith("SPOILER_") == true && !spoilerShown) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.hazeEffect(state = hazeState, style = HazeMaterials.ultraThin())
|
||||
.width(attachment.metadata?.width?.toInt()?.dp ?: maxWidth)
|
||||
.aspectRatio(
|
||||
attachment.metadata!!.width!!.toFloat() / attachment.metadata.height!!.toFloat()
|
||||
)
|
||||
.clickable { spoilerShown = true },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.hazeEffect(state = hazeState, style = HazeMaterials.regular())
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.attachment_spoiler))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,6 +148,7 @@ fun VideoPlayButton() {
|
|||
)
|
||||
}
|
||||
|
||||
@SuppressLint("UnusedBoxWithConstraintsScope")
|
||||
@Composable
|
||||
fun VideoAttachment(attachment: AutumnResource) {
|
||||
val url = "$REVOLT_FILES/attachments/${attachment.id}/${attachment.filename}"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -21,10 +22,13 @@ import androidx.compose.material3.Button
|
|||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
|
|
@ -37,6 +41,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
@ -53,10 +58,16 @@ import java.io.File
|
|||
|
||||
@Composable
|
||||
fun FilePreviewSheet(
|
||||
args: FileArgs, canRemove: Boolean, onRemove: () -> Unit, onDismiss: () -> Unit
|
||||
args: FileArgs,
|
||||
canRemove: Boolean,
|
||||
onRemove: () -> Unit,
|
||||
onToggleSpoiler: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
var localIsSpoiler by remember { mutableStateOf(args.spoiler) }
|
||||
|
||||
Column(
|
||||
Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
|
|
@ -80,6 +91,39 @@ fun FilePreviewSheet(
|
|||
color = LocalContentColor.current.copy(alpha = 0.6f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.clickable {
|
||||
onToggleSpoiler()
|
||||
localIsSpoiler = !localIsSpoiler
|
||||
}
|
||||
.padding(top = 8.dp)
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
stringResource(R.string.attachment_preview_spoiler)
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
stringResource(R.string.attachment_preview_spoiler_description)
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = localIsSpoiler,
|
||||
onCheckedChange = null,
|
||||
)
|
||||
},
|
||||
colors = ListItemDefaults.colors().copy(
|
||||
containerColor = Color.Transparent,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(onClick = {
|
||||
onDismiss()
|
||||
|
|
@ -111,6 +155,7 @@ fun AttachmentManager(
|
|||
uploading: Boolean,
|
||||
uploadProgress: Float = 0f,
|
||||
onRemove: (FileArgs) -> Unit,
|
||||
onToggleSpoiler: (FileArgs) -> Unit,
|
||||
canRemove: Boolean = true,
|
||||
canPreview: Boolean = true
|
||||
) {
|
||||
|
|
@ -126,18 +171,26 @@ fun AttachmentManager(
|
|||
}, sheetState = sheetState
|
||||
) {
|
||||
previewingAttachment?.let {
|
||||
FilePreviewSheet(args = it, canRemove = canRemove, onRemove = {
|
||||
onRemove(it)
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
showPreviewSheet = false
|
||||
FilePreviewSheet(
|
||||
args = it,
|
||||
canRemove = canRemove,
|
||||
onRemove = {
|
||||
onRemove(it)
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
showPreviewSheet = false
|
||||
}
|
||||
},
|
||||
onToggleSpoiler = {
|
||||
onToggleSpoiler(it)
|
||||
},
|
||||
onDismiss = {
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
showPreviewSheet = false
|
||||
}
|
||||
}
|
||||
}, onDismiss = {
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
showPreviewSheet = false
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -159,20 +212,21 @@ fun AttachmentManager(
|
|||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
) {
|
||||
attachments.forEach { attachment ->
|
||||
Row(modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.clickable {
|
||||
if (canPreview) {
|
||||
previewingAttachment = attachment
|
||||
showPreviewSheet = true
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.clickable {
|
||||
if (canPreview) {
|
||||
previewingAttachment = attachment
|
||||
showPreviewSheet = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
shape = MaterialTheme.shapes.small
|
||||
)
|
||||
.padding(8.dp)) {
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
shape = MaterialTheme.shapes.small
|
||||
)
|
||||
.padding(8.dp)) {
|
||||
Text(attachment.filename, maxLines = 1)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
|
@ -190,13 +244,14 @@ fun AttachmentManager(
|
|||
@Preview
|
||||
@Composable
|
||||
fun AttachmentManagerPreview() {
|
||||
AttachmentManager(attachments = listOf(
|
||||
FileArgs(
|
||||
filename = "file1.png", contentType = "image/png", file = File("file1.png")
|
||||
), FileArgs(
|
||||
filename = "file2.png", contentType = "image/png", file = File("file2.png")
|
||||
), FileArgs(
|
||||
filename = "file3.png", contentType = "image/png", file = File("file3.png")
|
||||
)
|
||||
), uploading = false, onRemove = {})
|
||||
AttachmentManager(
|
||||
attachments = listOf(
|
||||
FileArgs(
|
||||
filename = "file1.png", contentType = "image/png", file = File("file1.png")
|
||||
), FileArgs(
|
||||
filename = "file2.png", contentType = "image/png", file = File("file2.png")
|
||||
), FileArgs(
|
||||
filename = "file3.png", contentType = "image/png", file = File("file3.png")
|
||||
)
|
||||
), uploading = false, onToggleSpoiler = {}, onRemove = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ import androidx.compose.material3.AssistChip
|
|||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
|
@ -95,7 +94,6 @@ import androidx.compose.ui.draw.alpha
|
|||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
|
|
@ -155,6 +153,7 @@ import com.valentinilk.shimmer.shimmer
|
|||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.Instant
|
||||
import logcat.logcat
|
||||
import java.io.File
|
||||
import kotlin.math.max
|
||||
|
||||
|
|
@ -957,6 +956,21 @@ fun ChannelScreen(
|
|||
canPreview = true,
|
||||
onRemove = {
|
||||
viewModel.draftAttachments.remove(it)
|
||||
},
|
||||
onToggleSpoiler = {
|
||||
val index = viewModel.draftAttachments
|
||||
.indexOfFirst { a -> a.pickerIdentifier == it.pickerIdentifier }
|
||||
logcat {
|
||||
"Toggling spoiler for attachment at index $index"
|
||||
}
|
||||
if (index != -1) {
|
||||
val attachment =
|
||||
viewModel.draftAttachments[index]
|
||||
viewModel.draftAttachments[index] =
|
||||
attachment.copy(
|
||||
spoiler = !attachment.spoiler
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ class ChannelScreenViewModel @Inject constructor(
|
|||
try {
|
||||
val id = uploadToAutumn(
|
||||
it.file,
|
||||
it.filename,
|
||||
if (it.spoiler) "SPOILER_${it.filename}" else it.filename,
|
||||
"attachments",
|
||||
ContentType.parse(it.contentType),
|
||||
onProgress = { current, total ->
|
||||
|
|
|
|||
|
|
@ -113,8 +113,12 @@
|
|||
<string name="reply_mention_off">\@ off</string>
|
||||
<string name="too_many_replies">You can only reply to %1$d messages at a time.</string>
|
||||
|
||||
<string name="attachment_spoiler">Spoiler</string>
|
||||
|
||||
<string name="attachment_preview_remove">Remove</string>
|
||||
<string name="attachment_preview_close">Close</string>
|
||||
<string name="attachment_preview_spoiler">Mark as spoiler</string>
|
||||
<string name="attachment_preview_spoiler_description">This attachment will only be revealed when tapped.</string>
|
||||
|
||||
<string name="emoji_category_smileys">Smileys & Emotions</string>
|
||||
<string name="emoji_category_people">People & Body</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue