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
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.util.Log
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.view.inputmethod.InputMethodManager
import androidx.compose.animation.AnimatedVisibility
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.viewinterop.AndroidView
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.widget.addTextChangedListener
import chat.revolt.R
@ -59,6 +69,7 @@ fun NativeMessageField(
value: String,
onValueChange: (String) -> Unit,
onAddAttachment: () -> Unit,
onCommitAttachment: (Uri) -> Unit,
onPickEmoji: () -> Unit,
onSendMessage: () -> Unit,
channelType: ChannelType,
@ -86,7 +97,9 @@ fun NativeMessageField(
val density = LocalDensity.current
val selectionColour = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f).toArgb()
val cursorColour = MaterialTheme.colorScheme.primary.toArgb()
val contentColour = LocalContentColor.current.toArgb()
val placeholderColour = LocalContentColor.current.copy(alpha = 0.5f).toArgb()
LaunchedEffect(editMode) {
if (editMode) {
@ -129,13 +142,31 @@ fun NativeMessageField(
AndroidView(
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
textSize = 16f
setPadding((density.density * 16.dp.value).toInt())
// Propagate text changes to parent
addTextChangedListener {
onValueChange(it.toString())
}
// Hide/show keyboard on focus change and propagate to parent
onFocusChangeListener = android.view.View.OnFocusChangeListener { _, hasFocus ->
val keyboard =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
@ -145,23 +176,55 @@ fun NativeMessageField(
0
)
} else {
keyboard.hideSoftInputFromWindow(this.getWindowToken(), 0)
keyboard.hideSoftInputFromWindow(this.windowToken, 0)
}
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
isFocusableInTouchMode = true
typeface = ResourcesCompat.getFont(context, R.font.inter)
// Set colours
highlightColor = selectionColour
setTextColor(contentColour)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setTextCursorDrawable(null)
}
setHintTextColor(ColorStateList.valueOf(placeholderColour))
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 = {
this.clearFocus()
@ -247,6 +310,7 @@ fun NativeMessageFieldPreview() {
value = "Hello world!",
onValueChange = {},
onAddAttachment = {},
onCommitAttachment = {},
onPickEmoji = {},
onSendMessage = {},
channelType = ChannelType.DirectMessage,

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
@ -49,7 +50,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
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.qualifiers.ApplicationContext
import io.sentry.Sentry
import javax.inject.Inject
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
@SuppressLint("StaticFieldLeak")
@ -265,6 +268,8 @@ fun ChatRouterScreen(
) {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
val context = LocalContext.current
val view = LocalView.current
val keyboardController = LocalSoftwareKeyboardController.current
val navController = rememberNavController()
@ -317,6 +322,9 @@ fun ChatRouterScreen(
.collect { state ->
if (state == DrawerValue.Open) {
keyboardController?.hide()
val keyboard =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
keyboard.hideSoftInputFromWindow(view.windowToken, 0)
}
}
}
@ -368,7 +376,7 @@ fun ChatRouterScreen(
.distinctUntilChanged()
.collect { sizeClass ->
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.
// - Sort the servers that aren't in the ordering by their ID (creation order).
(
(
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 }
(
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 }
)
)
)
.forEach { server ->
if (server.id == null || server.name == null) return@forEach

View File

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