From f1e92544c9831383c9e871b0544dbe0f59a12b2b Mon Sep 17 00:00:00 2001 From: Infi Date: Sun, 22 Oct 2023 18:31:00 +0200 Subject: [PATCH] feat: allow content commit into message field Signed-off-by: Infi --- .../components/chat/NativeMessageField.kt | 76 +++++++++++++++++-- .../revolt/screens/chat/ChatRouterScreen.kt | 40 ++++++---- .../chat/views/channel/ChannelScreen.kt | 3 + 3 files changed, 97 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/chat/revolt/components/chat/NativeMessageField.kt b/app/src/main/java/chat/revolt/components/chat/NativeMessageField.kt index cc748a8f..aa570e3d 100644 --- a/app/src/main/java/chat/revolt/components/chat/NativeMessageField.kt +++ b/app/src/main/java/chat/revolt/components/chat/NativeMessageField.kt @@ -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, diff --git a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt index 9fc1bfde..7d5f4e63 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -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 diff --git a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt index 92d1cf8b..1d9da221 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt @@ -503,6 +503,9 @@ fun ChannelScreen( } } }, + onCommitAttachment = { uri -> + processFileUri(uri) + }, onPickEmoji = { focusManager.clearFocus() if (viewModel.currentBottomPane == BottomPane.EmojiPicker) {