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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.ime
|
||||
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.material3.AssistChip
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
|
|
@ -162,6 +165,8 @@ private fun pxAsDp(px: Int): Dp {
|
|||
).dp
|
||||
}
|
||||
|
||||
private const val NOT_ENOUGH_SPACE_FOR_PANES_THRESHOLD = 500
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun ChannelScreen(
|
||||
|
|
@ -206,6 +211,12 @@ fun ChannelScreen(
|
|||
label = "keyboardHeight"
|
||||
)
|
||||
|
||||
val notEnoughSpaceForPanes by remember {
|
||||
derivedStateOf {
|
||||
viewModel.keyboardHeight < NOT_ENOUGH_SPACE_FOR_PANES_THRESHOLD
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(imeTarget) {
|
||||
if (imeTarget > 0) {
|
||||
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 desc="UI elements">
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
|
@ -869,6 +944,53 @@ fun ChannelScreen(
|
|||
channelId = channelId,
|
||||
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 {
|
||||
Box(
|
||||
|
|
@ -893,137 +1015,126 @@ fun ChannelScreen(
|
|||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
Modifier
|
||||
.heightIn(min = pxAsDp(fallbackKeyboardHeight))
|
||||
) {
|
||||
if (!notEnoughSpaceForPanes) {
|
||||
Box(
|
||||
Modifier.then(
|
||||
if (emojiSearchFocused) {
|
||||
Modifier.requiredHeight(
|
||||
pxAsDp(
|
||||
max(
|
||||
imeCurrentInset * 2,
|
||||
Modifier
|
||||
.heightIn(min = pxAsDp(fallbackKeyboardHeight))
|
||||
) {
|
||||
Box(
|
||||
Modifier.then(
|
||||
if (emojiSearchFocused) {
|
||||
Modifier.requiredHeight(
|
||||
pxAsDp(
|
||||
max(
|
||||
imeCurrentInset * 2,
|
||||
fallbackKeyboardHeight
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Modifier.requiredHeight(
|
||||
pxAsDp(
|
||||
fallbackKeyboardHeight
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Modifier.requiredHeight(pxAsDp(fallbackKeyboardHeight))
|
||||
}
|
||||
)
|
||||
) {
|
||||
when (viewModel.activePane) {
|
||||
ChannelScreenActivePane.EmojiPicker -> {
|
||||
BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.EmojiPicker) {
|
||||
viewModel.activePane = ChannelScreenActivePane.None
|
||||
}
|
||||
)
|
||||
) {
|
||||
when (viewModel.activePane) {
|
||||
ChannelScreenActivePane.EmojiPicker -> {
|
||||
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(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
.padding(4.dp)
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
EmojiPicker(
|
||||
onEmojiSelected = viewModel::putAtCursorPosition,
|
||||
bottomInset = pxAsDp(
|
||||
max(
|
||||
imeCurrentInset - navigationBarsInset,
|
||||
0
|
||||
)
|
||||
),
|
||||
onSearchFocus = {
|
||||
emojiSearchFocused = it
|
||||
}
|
||||
ChannelScreenActivePane.AttachmentPicker -> {
|
||||
BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.AttachmentPicker) {
|
||||
viewModel.activePane =
|
||||
ChannelScreenActivePane.None
|
||||
}
|
||||
|
||||
MediaPickerGateway(
|
||||
onOpenPhotoPicker = {
|
||||
openPhotoPickerCallback()
|
||||
viewModel.activePane =
|
||||
ChannelScreenActivePane.None
|
||||
},
|
||||
onOpenDocumentPicker = {
|
||||
openDocumentPickerCallback()
|
||||
viewModel.activePane =
|
||||
ChannelScreenActivePane.None
|
||||
},
|
||||
onOpenCamera = {
|
||||
openCameraCallback()
|
||||
viewModel.activePane =
|
||||
ChannelScreenActivePane.None
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ChannelScreenActivePane.AttachmentPicker -> {
|
||||
BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.AttachmentPicker) {
|
||||
viewModel.activePane = ChannelScreenActivePane.None
|
||||
else -> {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
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_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_documents">Files</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_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_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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue