feat: allow content commit into message field

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-10-22 18:31:00 +02:00
parent 0c90c8c0e6
commit f1e92544c9
3 changed files with 97 additions and 22 deletions

View File

@ -1,7 +1,14 @@
package chat.revolt.components.chat package chat.revolt.components.chat
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RectShape
import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandIn import androidx.compose.animation.expandIn
@ -46,6 +53,9 @@ import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.core.view.inputmethod.InputConnectionCompat
import androidx.core.view.setPadding import androidx.core.view.setPadding
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import chat.revolt.R import chat.revolt.R
@ -59,6 +69,7 @@ fun NativeMessageField(
value: String, value: String,
onValueChange: (String) -> Unit, onValueChange: (String) -> Unit,
onAddAttachment: () -> Unit, onAddAttachment: () -> Unit,
onCommitAttachment: (Uri) -> Unit,
onPickEmoji: () -> Unit, onPickEmoji: () -> Unit,
onSendMessage: () -> Unit, onSendMessage: () -> Unit,
channelType: ChannelType, channelType: ChannelType,
@ -86,7 +97,9 @@ fun NativeMessageField(
val density = LocalDensity.current val density = LocalDensity.current
val selectionColour = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f).toArgb() val selectionColour = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f).toArgb()
val cursorColour = MaterialTheme.colorScheme.primary.toArgb()
val contentColour = LocalContentColor.current.toArgb() val contentColour = LocalContentColor.current.toArgb()
val placeholderColour = LocalContentColor.current.copy(alpha = 0.5f).toArgb()
LaunchedEffect(editMode) { LaunchedEffect(editMode) {
if (editMode) { if (editMode) {
@ -129,13 +142,31 @@ fun NativeMessageField(
AndroidView( AndroidView(
factory = { context -> factory = { context ->
com.google.android.material.textfield.TextInputEditText(context).apply { object : androidx.appcompat.widget.AppCompatEditText(context) {
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
var ic = super.onCreateInputConnection(outAttrs)
EditorInfoCompat.setContentMimeTypes(
outAttrs,
arrayOf("image/*")
)
val mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this)
if (mimeTypes != null) {
EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes)
ic = ic?.let { InputConnectionCompat.createWrapper(this, it, outAttrs) }
}
return ic
}
}.apply {
background = null background = null
textSize = 16f textSize = 16f
setPadding((density.density * 16.dp.value).toInt()) setPadding((density.density * 16.dp.value).toInt())
// Propagate text changes to parent
addTextChangedListener { addTextChangedListener {
onValueChange(it.toString()) onValueChange(it.toString())
} }
// Hide/show keyboard on focus change and propagate to parent
onFocusChangeListener = android.view.View.OnFocusChangeListener { _, hasFocus -> onFocusChangeListener = android.view.View.OnFocusChangeListener { _, hasFocus ->
val keyboard = val keyboard =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
@ -145,23 +176,55 @@ fun NativeMessageField(
0 0
) )
} else { } else {
keyboard.hideSoftInputFromWindow(this.getWindowToken(), 0) keyboard.hideSoftInputFromWindow(this.windowToken, 0)
} }
onFocusChange(hasFocus) onFocusChange(hasFocus)
} }
ViewCompat.setOnReceiveContentListener(
this,
arrayOf("image/*")
) { _, payload ->
// Check mimetype
if (payload.clip.description.hasMimeType("image/*")) {
// Get image
val item = payload.clip.getItemAt(0)
val uri = item.uri
if (uri == null) {
Log.e("MessageField", "Received payload with null uri")
return@setOnReceiveContentListener payload
}
onCommitAttachment(uri)
return@setOnReceiveContentListener null
}
payload
}
isFocusable = true isFocusable = true
isFocusableInTouchMode = true isFocusableInTouchMode = true
typeface = ResourcesCompat.getFont(context, R.font.inter) typeface = ResourcesCompat.getFont(context, R.font.inter)
// Set colours
highlightColor = selectionColour highlightColor = selectionColour
setTextColor(contentColour) setTextColor(contentColour)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { setHintTextColor(ColorStateList.valueOf(placeholderColour))
setTextCursorDrawable(null)
}
this.alpha = 1f // Caret colour and size
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val shapeDrawable = ShapeDrawable(RectShape())
shapeDrawable.paint.color = cursorColour
val sizeInDp = 1
val sizeInPixels = (sizeInDp * resources.displayMetrics.density).toInt()
shapeDrawable.intrinsicWidth = sizeInPixels
shapeDrawable.intrinsicHeight = sizeInPixels
setTextCursorDrawable(shapeDrawable)
}
clearFocus = { clearFocus = {
this.clearFocus() this.clearFocus()
@ -247,6 +310,7 @@ fun NativeMessageFieldPreview() {
value = "Hello world!", value = "Hello world!",
onValueChange = {}, onValueChange = {},
onAddAttachment = {}, onAddAttachment = {},
onCommitAttachment = {},
onPickEmoji = {}, onPickEmoji = {},
onSendMessage = {}, onSendMessage = {},
channelType = ChannelType.DirectMessage, channelType = ChannelType.DirectMessage,

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@ -49,7 +50,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -109,9 +112,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")
@ -265,6 +268,8 @@ fun ChatRouterScreen(
) { ) {
val drawerState = rememberDrawerState(DrawerValue.Closed) val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current
val view = LocalView.current
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val navController = rememberNavController() val navController = rememberNavController()
@ -317,6 +322,9 @@ fun ChatRouterScreen(
.collect { state -> .collect { state ->
if (state == DrawerValue.Open) { if (state == DrawerValue.Open) {
keyboardController?.hide() keyboardController?.hide()
val keyboard =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
keyboard.hideSoftInputFromWindow(view.windowToken, 0)
} }
} }
} }
@ -368,7 +376,7 @@ fun ChatRouterScreen(
.distinctUntilChanged() .distinctUntilChanged()
.collect { sizeClass -> .collect { sizeClass ->
useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded && useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded &&
sizeClass.heightSizeClass != WindowHeightSizeClass.Compact sizeClass.heightSizeClass != WindowHeightSizeClass.Compact
} }
} }
@ -847,21 +855,21 @@ fun Sidebar(
// - 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 { RevoltAPI.serverCache.values.filter {
SyncedSettings.ordering.servers.contains( SyncedSettings.ordering.servers.contains(
it.id it.id
) )
} }
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) } .sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }
) + ( ) + (
RevoltAPI.serverCache.values.filter { RevoltAPI.serverCache.values.filter {
!SyncedSettings.ordering.servers.contains( !SyncedSettings.ordering.servers.contains(
it.id it.id
) )
}.sortedBy { 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

View File

@ -503,6 +503,9 @@ fun ChannelScreen(
} }
} }
}, },
onCommitAttachment = { uri ->
processFileUri(uri)
},
onPickEmoji = { onPickEmoji = {
focusManager.clearFocus() focusManager.clearFocus()
if (viewModel.currentBottomPane == BottomPane.EmojiPicker) { if (viewModel.currentBottomPane == BottomPane.EmojiPicker) {