feat: allow content commit into message field
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
0c90c8c0e6
commit
f1e92544c9
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue