feat: do not attempt to fit panes to keyboard height when the keyboard is unreasonably short (phys kb, floating)
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
c69ffc3a7d
commit
c1053f0702
|
|
@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.ime
|
import androidx.compose.foundation.layout.ime
|
||||||
import androidx.compose.foundation.layout.imeAnimationTarget
|
import androidx.compose.foundation.layout.imeAnimationTarget
|
||||||
|
|
@ -50,6 +51,8 @@ import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material3.AssistChip
|
import androidx.compose.material3.AssistChip
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
|
@ -162,6 +165,8 @@ private fun pxAsDp(px: Int): Dp {
|
||||||
).dp
|
).dp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val NOT_ENOUGH_SPACE_FOR_PANES_THRESHOLD = 500
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelScreen(
|
fun ChannelScreen(
|
||||||
|
|
@ -206,6 +211,12 @@ fun ChannelScreen(
|
||||||
label = "keyboardHeight"
|
label = "keyboardHeight"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val notEnoughSpaceForPanes by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
viewModel.keyboardHeight < NOT_ENOUGH_SPACE_FOR_PANES_THRESHOLD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(imeTarget) {
|
LaunchedEffect(imeTarget) {
|
||||||
if (imeTarget > 0) {
|
if (imeTarget > 0) {
|
||||||
viewModel.updateSaveKeyboardHeight(imeTarget)
|
viewModel.updateSaveKeyboardHeight(imeTarget)
|
||||||
|
|
@ -279,6 +290,70 @@ fun ChannelScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val openCameraCallback = cb@{
|
||||||
|
// Create a new content URI to store the captured image.
|
||||||
|
val contentResolver =
|
||||||
|
context.contentResolver
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(
|
||||||
|
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||||
|
"RVL_${System.currentTimeMillis()}.jpg"
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
MediaStore.MediaColumns.MIME_TYPE,
|
||||||
|
"image/jpeg"
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||||
|
Environment.DIRECTORY_PICTURES
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
capturedPhotoUri.value =
|
||||||
|
contentResolver.insert(
|
||||||
|
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||||
|
contentValues
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(
|
||||||
|
R.string.file_picker_chip_camera_failed
|
||||||
|
),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
|
||||||
|
return@cb
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
capturedPhotoUri.value?.let { uri ->
|
||||||
|
pickCameraLauncher.launch(uri)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(
|
||||||
|
R.string.file_picker_chip_camera_none_installed
|
||||||
|
),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val openDocumentPickerCallback = {
|
||||||
|
pickFileLauncher.launch(arrayOf("*/*"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val openPhotoPickerCallback = {
|
||||||
|
pickMediaLauncher.launch(
|
||||||
|
PickVisualMediaRequest(
|
||||||
|
mediaType = ActivityResultContracts.PickVisualMedia.ImageAndVideo
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
// </editor-fold>
|
// </editor-fold>
|
||||||
// <editor-fold desc="UI elements">
|
// <editor-fold desc="UI elements">
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
@ -869,6 +944,53 @@ fun ChannelScreen(
|
||||||
channelId = channelId,
|
channelId = channelId,
|
||||||
failedValidation = viewModel.draftContent.length > 2000,
|
failedValidation = viewModel.draftContent.length > 2000,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = viewModel.activePane == ChannelScreenActivePane.AttachmentPicker && notEnoughSpaceForPanes,
|
||||||
|
onDismissRequest = {
|
||||||
|
viewModel.activePane = ChannelScreenActivePane.None
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_paperclip_24dp),
|
||||||
|
contentDescription = null // Provided by text below
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = { Text(stringResource(R.string.file_picker_chip_documents)) },
|
||||||
|
onClick = {
|
||||||
|
openDocumentPickerCallback()
|
||||||
|
viewModel.activePane = ChannelScreenActivePane.None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_camera_24dp),
|
||||||
|
contentDescription = null // Provided by text below
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = { Text(stringResource(R.string.file_picker_chip_camera)) },
|
||||||
|
onClick = {
|
||||||
|
openCameraCallback()
|
||||||
|
viewModel.activePane = ChannelScreenActivePane.None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_image_multiple_24dp),
|
||||||
|
contentDescription = null // Provided by text below
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = { Text(stringResource(R.string.file_picker_chip_photo_picker)) },
|
||||||
|
onClick = {
|
||||||
|
openPhotoPickerCallback()
|
||||||
|
viewModel.activePane = ChannelScreenActivePane.None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -893,137 +1015,126 @@ fun ChannelScreen(
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Box(
|
if (!notEnoughSpaceForPanes) {
|
||||||
Modifier
|
|
||||||
.heightIn(min = pxAsDp(fallbackKeyboardHeight))
|
|
||||||
) {
|
|
||||||
Box(
|
Box(
|
||||||
Modifier.then(
|
Modifier
|
||||||
if (emojiSearchFocused) {
|
.heightIn(min = pxAsDp(fallbackKeyboardHeight))
|
||||||
Modifier.requiredHeight(
|
) {
|
||||||
pxAsDp(
|
Box(
|
||||||
max(
|
Modifier.then(
|
||||||
imeCurrentInset * 2,
|
if (emojiSearchFocused) {
|
||||||
|
Modifier.requiredHeight(
|
||||||
|
pxAsDp(
|
||||||
|
max(
|
||||||
|
imeCurrentInset * 2,
|
||||||
|
fallbackKeyboardHeight
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Modifier.requiredHeight(
|
||||||
|
pxAsDp(
|
||||||
fallbackKeyboardHeight
|
fallbackKeyboardHeight
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
} else {
|
)
|
||||||
Modifier.requiredHeight(pxAsDp(fallbackKeyboardHeight))
|
) {
|
||||||
}
|
when (viewModel.activePane) {
|
||||||
)
|
ChannelScreenActivePane.EmojiPicker -> {
|
||||||
) {
|
BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.EmojiPicker) {
|
||||||
when (viewModel.activePane) {
|
viewModel.activePane =
|
||||||
ChannelScreenActivePane.EmojiPicker -> {
|
ChannelScreenActivePane.None
|
||||||
BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.EmojiPicker) {
|
}
|
||||||
viewModel.activePane = ChannelScreenActivePane.None
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
|
.padding(4.dp)
|
||||||
|
.navigationBarsPadding()
|
||||||
|
) {
|
||||||
|
EmojiPicker(
|
||||||
|
onEmojiSelected = viewModel::putAtCursorPosition,
|
||||||
|
bottomInset = pxAsDp(
|
||||||
|
max(
|
||||||
|
imeCurrentInset - navigationBarsInset,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
onSearchFocus = {
|
||||||
|
emojiSearchFocused = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
ChannelScreenActivePane.AttachmentPicker -> {
|
||||||
modifier = Modifier
|
BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.AttachmentPicker) {
|
||||||
.fillMaxWidth()
|
viewModel.activePane =
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
ChannelScreenActivePane.None
|
||||||
.padding(4.dp)
|
}
|
||||||
.navigationBarsPadding()
|
|
||||||
) {
|
MediaPickerGateway(
|
||||||
EmojiPicker(
|
onOpenPhotoPicker = {
|
||||||
onEmojiSelected = viewModel::putAtCursorPosition,
|
openPhotoPickerCallback()
|
||||||
bottomInset = pxAsDp(
|
viewModel.activePane =
|
||||||
max(
|
ChannelScreenActivePane.None
|
||||||
imeCurrentInset - navigationBarsInset,
|
},
|
||||||
0
|
onOpenDocumentPicker = {
|
||||||
)
|
openDocumentPickerCallback()
|
||||||
),
|
viewModel.activePane =
|
||||||
onSearchFocus = {
|
ChannelScreenActivePane.None
|
||||||
emojiSearchFocused = it
|
},
|
||||||
}
|
onOpenCamera = {
|
||||||
|
openCameraCallback()
|
||||||
|
viewModel.activePane =
|
||||||
|
ChannelScreenActivePane.None
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ChannelScreenActivePane.AttachmentPicker -> {
|
else -> {
|
||||||
BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.AttachmentPicker) {
|
// Do nothing
|
||||||
viewModel.activePane = ChannelScreenActivePane.None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaPickerGateway(
|
|
||||||
onOpenPhotoPicker = {
|
|
||||||
pickMediaLauncher.launch(
|
|
||||||
PickVisualMediaRequest(
|
|
||||||
mediaType = ActivityResultContracts.PickVisualMedia.ImageAndVideo
|
|
||||||
)
|
|
||||||
)
|
|
||||||
viewModel.activePane =
|
|
||||||
ChannelScreenActivePane.None
|
|
||||||
},
|
|
||||||
onOpenDocumentPicker = {
|
|
||||||
pickFileLauncher.launch(arrayOf("*/*"))
|
|
||||||
viewModel.activePane =
|
|
||||||
ChannelScreenActivePane.None
|
|
||||||
},
|
|
||||||
onOpenCamera = {
|
|
||||||
// Create a new content URI to store the captured image.
|
|
||||||
val contentResolver =
|
|
||||||
context.contentResolver
|
|
||||||
val contentValues = ContentValues().apply {
|
|
||||||
put(
|
|
||||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
|
||||||
"RVL_${System.currentTimeMillis()}.jpg"
|
|
||||||
)
|
|
||||||
put(
|
|
||||||
MediaStore.MediaColumns.MIME_TYPE,
|
|
||||||
"image/jpeg"
|
|
||||||
)
|
|
||||||
put(
|
|
||||||
MediaStore.MediaColumns.RELATIVE_PATH,
|
|
||||||
Environment.DIRECTORY_PICTURES
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
capturedPhotoUri.value =
|
|
||||||
contentResolver.insert(
|
|
||||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
|
||||||
contentValues
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(
|
|
||||||
R.string.file_picker_chip_camera_failed
|
|
||||||
),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
|
|
||||||
return@MediaPickerGateway
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
capturedPhotoUri.value?.let { uri ->
|
|
||||||
pickCameraLauncher.launch(uri)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(
|
|
||||||
R.string.file_picker_chip_camera_none_installed
|
|
||||||
),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.activePane =
|
|
||||||
ChannelScreenActivePane.None
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
// Do nothing
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Box(Modifier.imePadding())
|
||||||
}
|
}
|
||||||
Box(Modifier.imePadding())
|
} else {
|
||||||
|
if (viewModel.activePane == ChannelScreenActivePane.EmojiPicker) {
|
||||||
|
BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.EmojiPicker) {
|
||||||
|
viewModel.activePane =
|
||||||
|
ChannelScreenActivePane.None
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(600.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
|
.padding(4.dp)
|
||||||
|
.navigationBarsPadding()
|
||||||
|
) {
|
||||||
|
EmojiPicker(
|
||||||
|
onEmojiSelected = viewModel::putAtCursorPosition,
|
||||||
|
bottomInset = pxAsDp(
|
||||||
|
max(
|
||||||
|
imeCurrentInset - navigationBarsInset,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
onSearchFocus = {
|
||||||
|
emojiSearchFocused = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.imePadding()
|
||||||
|
.navigationBarsPadding()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -541,13 +541,6 @@
|
||||||
<string name="colour_picker_cancel">Cancel</string>
|
<string name="colour_picker_cancel">Cancel</string>
|
||||||
<string name="colour_picker_apply">Apply</string>
|
<string name="colour_picker_apply">Apply</string>
|
||||||
|
|
||||||
<string name="file_picker_cannot_attach_file_invalid">This file is invalid and cannot be attached.</string>
|
|
||||||
<string name="file_picker_permission_request_header">We need your permission to access photos and videos</string>
|
|
||||||
<string name="file_picker_permission_request_body">You will be able to attach photos and videos to your messages afterwards.</string>
|
|
||||||
<string name="file_picker_permission_request_cta">Allow access</string>
|
|
||||||
<string name="file_picker_permission_unpartialise_request_header">You have granted partial access to photos and videos</string>
|
|
||||||
<string name="file_picker_permission_unpartialise_request_body">To attach all your photos and videos, we need your permission</string>
|
|
||||||
<string name="file_picker_permission_unpartialise_request_cta">Allow full access</string>
|
|
||||||
<string name="file_picker_chip_photo_picker">Select Photos</string>
|
<string name="file_picker_chip_photo_picker">Select Photos</string>
|
||||||
<string name="file_picker_chip_documents">Files</string>
|
<string name="file_picker_chip_documents">Files</string>
|
||||||
<string name="file_picker_chip_camera">Camera</string>
|
<string name="file_picker_chip_camera">Camera</string>
|
||||||
|
|
@ -581,6 +574,10 @@
|
||||||
<string name="spark_notifications_rationale_cta">Enable notifications</string>
|
<string name="spark_notifications_rationale_cta">Enable notifications</string>
|
||||||
<string name="spark_notifications_rationale_dismiss">Not now</string>
|
<string name="spark_notifications_rationale_dismiss">Not now</string>
|
||||||
|
|
||||||
|
<string name="spark_keyboard_shortcuts">Using a keyboard?</string>
|
||||||
|
<string name="spark_keyboard_shortcuts_description">Revolt has keyboard shortcuts! Press %1$s to see them.</string>
|
||||||
|
<string name="spark_keyboard_shortcuts_cta">Open shortcuts</string>
|
||||||
|
|
||||||
<string name="notice_platform_mod_dm_title">Important notice regarding your account</string>
|
<string name="notice_platform_mod_dm_title">Important notice regarding your account</string>
|
||||||
<string name="notice_platform_mod_dm_description">You have received an important notice regarding your account from our moderation team. Please read it carefully.</string>
|
<string name="notice_platform_mod_dm_description">You have received an important notice regarding your account from our moderation team. Please read it carefully.</string>
|
||||||
<string name="notice_platform_mod_dm_acknowledge">View</string>
|
<string name="notice_platform_mod_dm_acknowledge">View</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue