feat: consolidate the last week of changes
- unfortunately forgot to do atomic commits - design improved in many many areas - app now crashes less - messages are now virtualised - remote images are now cached in-memory - more strings (most unused)
This commit is contained in:
parent
2eeb44e800
commit
8f068edeec
|
|
@ -26,7 +26,8 @@ android {
|
|||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,15 @@ suspend fun fetchSelf(): User {
|
|||
}
|
||||
|
||||
suspend fun fetchUser(id: String): User {
|
||||
val response = RevoltHttp.get("/users/$id") {
|
||||
val res = RevoltHttp.get("/users/$id") {
|
||||
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||
}
|
||||
.bodyAsText()
|
||||
|
||||
if (res.status.value == 404) {
|
||||
return User.getPlaceholder(id)
|
||||
}
|
||||
|
||||
val response = res.bodyAsText()
|
||||
|
||||
try {
|
||||
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,22 @@ data class User(
|
|||
online = partial.online ?: online
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getPlaceholder(forId: String) = User(
|
||||
id = forId,
|
||||
username = "Unknown User",
|
||||
avatar = null,
|
||||
badges = 0,
|
||||
status = null,
|
||||
profile = null,
|
||||
flags = 0,
|
||||
privileged = false,
|
||||
bot = null,
|
||||
relationship = null,
|
||||
online = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import androidx.compose.foundation.shape.CircleShape
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -75,7 +75,7 @@ fun MessageField(
|
|||
modifier = Modifier.height(56.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.KeyboardArrowLeft,
|
||||
Icons.Default.KeyboardArrowRight,
|
||||
contentDescription = stringResource(id = R.string.show_more_alt),
|
||||
modifier = Modifier
|
||||
.size(24.dp + 8.dp)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import coil.compose.AsyncImage
|
|||
import coil.decode.GifDecoder
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.decode.SvgDecoder
|
||||
import coil.memory.MemoryCache
|
||||
import coil.request.ImageRequest
|
||||
|
||||
@Composable
|
||||
|
|
@ -20,19 +21,28 @@ fun RemoteImage(
|
|||
modifier: Modifier = Modifier,
|
||||
contentScale: ContentScale = ContentScale.Crop
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(url)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
imageLoader = ImageLoader.Builder(LocalContext.current).components {
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder.Factory())
|
||||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
imageLoader = ImageLoader.Builder(context)
|
||||
.components {
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder.Factory())
|
||||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
}
|
||||
add(SvgDecoder.Factory())
|
||||
}
|
||||
add(SvgDecoder.Factory())
|
||||
}.build(),
|
||||
.memoryCache {
|
||||
MemoryCache.Builder(context)
|
||||
.maxSizePercent(.25)
|
||||
.build()
|
||||
}
|
||||
.build(),
|
||||
contentDescription = description,
|
||||
contentScale = contentScale,
|
||||
modifier = modifier
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package chat.revolt.components.screens.chat
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.revolt.api.schemas.ChannelType
|
||||
import chat.revolt.R
|
||||
|
||||
@Composable
|
||||
fun DrawerChannel(
|
||||
channelType: ChannelType,
|
||||
name: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(if (selected) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
) {
|
||||
when (channelType) {
|
||||
ChannelType.TextChannel -> {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
painter = painterResource(R.drawable.ic_pound_24dp),
|
||||
contentDescription = stringResource(R.string.channel_text)
|
||||
)
|
||||
}
|
||||
ChannelType.VoiceChannel -> {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
painter = painterResource(R.drawable.ic_volume_up_24dp),
|
||||
contentDescription = stringResource(R.string.channel_voice)
|
||||
)
|
||||
}
|
||||
ChannelType.SavedMessages -> {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
painter = painterResource(R.drawable.ic_note_24dp),
|
||||
contentDescription = stringResource(R.string.channel_notes)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
imageVector = Icons.Default.List,
|
||||
contentDescription = "Channel"
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = name,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,23 @@
|
|||
package chat.revolt.screens.chat
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
|
@ -19,19 +26,20 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import chat.revolt.api.REVOLT_FILES
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.components.generic.RemoteImage
|
||||
import chat.revolt.components.generic.drawableResource
|
||||
import chat.revolt.screens.chat.views.HomeScreen
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.schemas.ChannelType
|
||||
import chat.revolt.components.screens.chat.DrawerChannel
|
||||
import chat.revolt.screens.chat.views.ChannelScreen
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ChatRouterViewModel : ViewModel() {
|
||||
private var _currentServer =
|
||||
mutableStateOf(RevoltAPI.serverCache.values.firstOrNull()?.id ?: "home")
|
||||
private var _currentServer = mutableStateOf("home")
|
||||
val currentServer: String
|
||||
get() = _currentServer.value
|
||||
|
||||
|
|
@ -50,71 +58,133 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
|||
val channelDrawerState = rememberDrawerState(DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
val navController = rememberNavController()
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
|
||||
DismissibleNavigationDrawer(drawerState = channelDrawerState, drawerContent = {
|
||||
ModalDrawerSheet {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row {
|
||||
Column(Modifier.verticalScroll(rememberScrollState())) {
|
||||
RemoteImage(
|
||||
url = drawableResource(R.drawable.ic_launcher_monochrome),
|
||||
DismissibleNavigationDrawer(
|
||||
drawerState = channelDrawerState,
|
||||
drawerContent = {
|
||||
ModalDrawerSheet(drawerContainerColor = MaterialTheme.colorScheme.surfaceVariant) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable { viewModel.goToHome() },
|
||||
description = "Home",
|
||||
)
|
||||
RevoltAPI.serverCache.values.forEach { server ->
|
||||
server.icon?.let { icon ->
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/icons/${icon.id!!}/server.png?max_side=256",
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable { viewModel.setCurrentServer(server.id!!) },
|
||||
description = "${server.name}"
|
||||
.verticalScroll(rememberScrollState())
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { viewModel.goToHome() },
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Home,
|
||||
contentDescription = stringResource(id = R.string.home),
|
||||
modifier = Modifier.padding(4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
if (viewModel.currentServer != "home") {
|
||||
val server = RevoltAPI.serverCache[viewModel.currentServer]
|
||||
|
||||
Text(
|
||||
text = server?.name ?: "Unknown Server",
|
||||
fontWeight = FontWeight.Black,
|
||||
fontSize = 24.sp
|
||||
)
|
||||
RevoltAPI.serverCache.values.forEach { server ->
|
||||
if (server.name == null) return@forEach
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
server?.channels?.forEach { channelId ->
|
||||
RevoltAPI.channelCache[channelId]?.let {
|
||||
if (server.icon != null) {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/icons/${server.icon.id!!}/server.png?max_side=256",
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable { viewModel.setCurrentServer(server.id!!) },
|
||||
description = "${server.name}"
|
||||
)
|
||||
} else {
|
||||
// return a placeholder icon, currently the first letter of the server name in a circle
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.clickable { viewModel.setCurrentServer(server.id!!) }
|
||||
) {
|
||||
Text(
|
||||
text = it.name ?: "Unnamed Channel",
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch { channelDrawerState.close() }
|
||||
navController.navigate("channel/${it.id}")
|
||||
}
|
||||
text = server.name.first().toString(),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(text = "Home not implemented!")
|
||||
}
|
||||
|
||||
Crossfade(targetState = viewModel.currentServer) {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
if (it == "home") {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
RevoltAPI.channelCache.values.filter { it.channelType == ChannelType.DirectMessage }
|
||||
.forEach { channel ->
|
||||
DrawerChannel(
|
||||
name = "DM #${channel.id}", // TODO get user or group name
|
||||
channelType = ChannelType.DirectMessage,
|
||||
selected = channel.id == (navBackStackEntry?.arguments?.getString(
|
||||
"channelId"
|
||||
) ?: false),
|
||||
onClick = {
|
||||
navController.navigate("channel/${channel.id}")
|
||||
scope.launch {
|
||||
channelDrawerState.close()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val server = RevoltAPI.serverCache[it]
|
||||
|
||||
Text(
|
||||
text = server?.name ?: stringResource(R.string.unknown),
|
||||
fontWeight = FontWeight.Black,
|
||||
fontSize = 24.sp,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
server?.channels?.forEach { channelId ->
|
||||
RevoltAPI.channelCache[channelId]?.let { ch ->
|
||||
DrawerChannel(
|
||||
name = ch.name!!,
|
||||
channelType = ch.channelType!!,
|
||||
selected = navBackStackEntry?.arguments?.getString(
|
||||
"channelId"
|
||||
) == ch.id,
|
||||
onClick = {
|
||||
scope.launch { channelDrawerState.close() }
|
||||
navController.navigate("channel/${ch.id}")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}) {
|
||||
) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
NavHost(navController = navController, startDestination = "home") {
|
||||
composable("home") {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
package chat.revolt.screens.chat.views
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -27,6 +29,8 @@ import chat.revolt.api.schemas.Message as MessageSchema
|
|||
import chat.revolt.components.chat.Message
|
||||
import kotlinx.coroutines.launch
|
||||
import chat.revolt.R
|
||||
import chat.revolt.RevoltTweenFloat
|
||||
import chat.revolt.RevoltTweenInt
|
||||
import chat.revolt.api.routes.channel.fetchMessagesFromChannel
|
||||
import chat.revolt.components.chat.MessageField
|
||||
|
||||
|
|
@ -181,30 +185,48 @@ fun ChannelScreen(
|
|||
}
|
||||
|
||||
Column {
|
||||
Text(text = "#" + channel.name!!)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
Text(
|
||||
text = channel.name ?: channel.id!!,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
// Column nesting is needed to make the vertical scroll work properly
|
||||
Column(Modifier.weight(1f)) {
|
||||
Column(Modifier.verticalScroll(scrollState)) {
|
||||
viewModel.renderableMessages.forEach {
|
||||
Message(message = it)
|
||||
}
|
||||
LazyColumn(Modifier.weight(1f)) {
|
||||
items(viewModel.renderableMessages) { message ->
|
||||
Message(message)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = viewModel.typingUsers.isNotEmpty()) {
|
||||
AnimatedVisibility(
|
||||
visible = viewModel.typingUsers.isNotEmpty(),
|
||||
enter = slideInVertically(
|
||||
animationSpec = RevoltTweenInt,
|
||||
initialOffsetY = { it }
|
||||
) + fadeIn(animationSpec = RevoltTweenFloat),
|
||||
exit = slideOutVertically(
|
||||
animationSpec = RevoltTweenInt,
|
||||
targetOffsetY = { it }
|
||||
) + fadeOut(animationSpec = RevoltTweenFloat)
|
||||
) {
|
||||
Row(
|
||||
Modifier
|
||||
.padding(all = 4.dp)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.fillMaxWidth()
|
||||
.padding(all = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = viewModel.typingMessageResource(),
|
||||
viewModel.getTypingUsernames()
|
||||
)
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -218,7 +240,7 @@ fun ChannelScreen(
|
|||
onMessageContentChange = viewModel::setMessageContent,
|
||||
onSendMessage = viewModel::sendPendingMessage,
|
||||
channelType = it,
|
||||
channelName = channel.name
|
||||
channelName = channel.name ?: channel.id!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M11,9c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3s1.34,-3 3,-3s3,1.34 3,3m3,11H2v-2c0,-2.21 2.69,-4 6,-4s6,1.79 6,4m8,-6v2h-9v-2m9,-4v2h-9V8m9,-4v2h-9V4Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1s-2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,2.75c0.41,0 0.75,0.34 0.75,0.75s-0.34,0.75 -0.75,0.75s-0.75,-0.34 -0.75,-0.75s0.34,-0.75 0.75,-0.75zM9.1,17L7,17v-2.14l5.96,-5.96l2.12,2.12L9.1,17zM16.85,9.27l-1.06,1.06l-2.12,-2.12l1.06,-1.06c0.2,-0.2 0.51,-0.2 0.71,0l1.41,1.41c0.2,0.2 0.2,0.51 0,0.71z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<vector android:height="24dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="m5.41,21l0.71,-4h-4l0.35,-2h4l1.06,-6h-4l0.35,-2h4l0.71,-4h2l-0.71,4h6l0.71,-4h2l-0.71,4h4l-0.35,2h-4l-1.06,6h4l-0.35,2h-4l-0.71,4h-2l0.71,-4h-6l-0.71,4h-2M9.53,9l-1.06,6h6l1.06,-6h-6Z"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12A4.5,4.5 0,0 0,14 7.97v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
|
@ -74,8 +74,17 @@
|
|||
<string name="tutorial">Welcome to Revolt\'s in-progress Android experience!</string>
|
||||
<string name="select_channel">Select a server and channel by swiping from the left.</string>
|
||||
|
||||
<string name="unknown">Unknown</string>
|
||||
<string name="home">Home</string>
|
||||
|
||||
<string name="avatar_alt">%1$s\'s avatar</string>
|
||||
|
||||
<string name="channel_dm">Direct Message</string>
|
||||
<string name="channel_text">Text Channel</string>
|
||||
<string name="channel_voice">Voice Channel</string>
|
||||
<string name="channel_group">Group</string>
|
||||
<string name="channel_notes">Notes</string>
|
||||
|
||||
<string name="message_field_placeholder_dm">Message @%1$s</string>
|
||||
<string name="message_field_placeholder_text">Message #%1$s</string>
|
||||
<string name="message_field_placeholder_voice">Message #%1$s</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue