more features and better performance

This commit is contained in:
Avently
2024-10-15 16:44:35 +07:00
parent 134d115fbb
commit ee4a26ed0a
26 changed files with 212 additions and 273 deletions
@@ -285,28 +285,28 @@ class SimplexApp: Application(), LifecycleEventObserver {
@Suppress("DEPRECATION")
val windowInsetController = ViewCompat.getWindowInsetsController(window.decorView)
// Blend status bar color to the animated color
val colors = CurrentColors.value.colors
val baseBackgroundColor = if (toolbarOnTop) colors.background.mixWith(colors.onBackground, 0.97f) else colors.background
var statusBar = baseBackgroundColor.mixWith(drawerShadingColor.copy(1f), 1 - drawerShadingColor.alpha).toArgb()
// val colors = CurrentColors.value.colors
// val baseBackgroundColor = if (toolbarOnTop) colors.background.mixWith(colors.onBackground, 0.97f) else colors.background
// var statusBar = baseBackgroundColor.mixWith(drawerShadingColor.copy(1f), 1 - drawerShadingColor.alpha).toArgb()
var statusBarLight = isLight
// SimplexGreen while in call
//
// // SimplexGreen while in call
if (window.statusBarColor == SimplexGreen.toArgb()) {
statusBarColorAfterCall.intValue = statusBar
statusBar = SimplexGreen.toArgb()
// statusBarColorAfterCall.intValue = statusBar
// statusBar = SimplexGreen.toArgb()
statusBarLight = false
}
window.statusBarColor = statusBar
val navBar = navBarColor.toArgb()
// window.statusBarColor = statusBar
// val navBar = navBarColor.toArgb()
if (windowInsetController?.isAppearanceLightStatusBars != statusBarLight) {
windowInsetController?.isAppearanceLightStatusBars = statusBarLight
}
if (window.navigationBarColor != navBar) {
window.navigationBarColor = navBar
}
if (windowInsetController?.isAppearanceLightNavigationBars != isLight) {
windowInsetController?.isAppearanceLightNavigationBars = isLight
}
// if (window.navigationBarColor != navBar) {
// window.navigationBarColor = navBar
// }
// if (windowInsetController?.isAppearanceLightNavigationBars != isLight) {
// windowInsetController?.isAppearanceLightNavigationBars = isLight
// }
}
override fun androidSetStatusAndNavBarColors(isLight: Boolean, backgroundColor: Color, hasTop: Boolean, hasBottom: Boolean) {
@@ -64,7 +64,6 @@ kotlin {
implementation("androidx.activity:activity-compose:1.9.1")
val workVersion = "2.9.1"
implementation("androidx.work:work-runtime-ktx:$workVersion")
implementation("com.google.accompanist:accompanist-insets:0.30.1")
// Video support
implementation("com.google.android.exoplayer:exoplayer:2.19.1")
@@ -3,20 +3,8 @@ package chat.simplex.common.platform
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import com.google.accompanist.insets.navigationBarsWithImePadding
import java.io.File
actual fun Modifier.navigationBarsWithImePadding(): Modifier = navigationBarsWithImePadding()
@Composable
actual fun ProvideWindowInsets(
consumeWindowInsets: Boolean,
windowInsetsAnimationsEnabled: Boolean,
content: @Composable () -> Unit
) {
com.google.accompanist.insets.ProvideWindowInsets(content = content)
}
@Composable
actual fun Modifier.desktopOnExternalDrag(
enabled: Boolean,
@@ -7,10 +7,9 @@ import androidx.compose.foundation.lazy.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.platform.LocalLayoutDirection
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.views.helpers.*
import kotlinx.coroutines.flow.filter
import kotlin.math.absoluteValue
@@ -40,19 +39,15 @@ actual fun LazyColumnWithScrollBar(
}
}
}
val direction = LocalLayoutDirection.current
val paddingEnd = WindowInsets.navigationBars.asPaddingValues().calculateEndPadding(direction)
if (connection != null) {
LazyColumn(modifier.nestedScroll(connection), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled) {
LazyColumn(modifier.nestedScroll(connection).padding(end = paddingEnd), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled) {
content()
item {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}
}
} else {
LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled) {
LazyColumn(modifier.padding(end = paddingEnd), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled) {
content()
item {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}
}
}
}
@@ -66,6 +61,7 @@ actual fun ColumnWithScrollBar(
maxIntrinsicSize: Boolean,
content: @Composable() (ColumnScope.() -> Unit)
) {
val modifier = modifier.imePadding()
val state = state ?: LocalAppBarHandler.current?.scrollState ?: rememberScrollState()
val connection = LocalAppBarHandler.current?.connection
LaunchedEffect(Unit) {
@@ -78,21 +74,22 @@ actual fun ColumnWithScrollBar(
}
}
}
val direction = LocalLayoutDirection.current
if (connection != null) {
Column(
if (maxIntrinsicSize) {
modifier.nestedScroll(connection).verticalScroll(state).height(IntrinsicSize.Max)
modifier.nestedScroll(connection).verticalScroll(state).height(IntrinsicSize.Max).padding(end = WindowInsets.navigationBars.asPaddingValues().calculateEndPadding(direction))
} else {
modifier.nestedScroll(connection).verticalScroll(state)
modifier.nestedScroll(connection).verticalScroll(state).padding(end = WindowInsets.navigationBars.asPaddingValues().calculateEndPadding(direction))
}, verticalArrangement, horizontalAlignment) {
content()
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}
} else {
Column(if (maxIntrinsicSize) {
modifier.verticalScroll(state).height(IntrinsicSize.Max)
modifier.verticalScroll(state).height(IntrinsicSize.Max).padding(end = WindowInsets.navigationBars.asPaddingValues().calculateEndPadding(direction))
} else {
modifier.verticalScroll(state)
modifier.verticalScroll(state).padding(end = WindowInsets.navigationBars.asPaddingValues().calculateEndPadding(direction))
}, verticalArrangement, horizontalAlignment) {
content()
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
@@ -14,15 +14,6 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.flow.filter
import java.io.File
expect fun Modifier.navigationBarsWithImePadding(): Modifier
@Composable
expect fun ProvideWindowInsets(
consumeWindowInsets: Boolean = true,
windowInsetsAnimationsEnabled: Boolean = true,
content: @Composable () -> Unit
)
@Composable
expect fun Modifier.desktopOnExternalDrag(
enabled: Boolean = true,
@@ -78,7 +78,6 @@ fun TerminalLayout(
composeState.value = composeState.value.copy(message = s)
}
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Scaffold(
topBar = { CloseSheetBar(close) },
bottomBar = {
@@ -112,7 +111,7 @@ fun TerminalLayout(
}
},
contentColor = LocalContentColor.current,
modifier = Modifier.navigationBarsWithImePadding()
modifier = Modifier.navigationBarsPadding().imePadding()
) { contentPadding ->
Surface(
modifier = Modifier
@@ -124,7 +123,6 @@ fun TerminalLayout(
TerminalLog(floating)
}
}
}
}
@Composable
@@ -172,7 +170,7 @@ fun TerminalLog(floating: Boolean) {
ModalManager.start
}
modalPlace.showModal(endButtons = { ShareButton { clipboard.shareText(item.details) } }) {
SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) {
SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
val details = item.details
.let {
if (it.length < 100_000) it
@@ -40,8 +40,6 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
val scrollState = rememberScrollState()
val keyboardState by getKeyboardState()
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Box(
modifier = Modifier
.fillMaxSize()
@@ -102,7 +100,6 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
}
}
}
}
}
@Composable
@@ -115,7 +112,6 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
CompositionLocalProvider(
LocalAppBarHandler provides handler
) {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Column(
modifier = Modifier
.fillMaxSize()
@@ -178,7 +174,6 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
}
}
}
}
}
fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) {
@@ -255,7 +250,6 @@ fun ProfileNameField(name: MutableState<String>, placeholder: String = "", isVal
val modifier = Modifier
.fillMaxWidth()
.heightIn(min = 50.dp)
.navigationBarsWithImePadding()
.onFocusChanged { focused = it.isFocused }
Column(
Modifier
@@ -107,6 +107,7 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == activeChatInfo.value?.id }?.chatStats?.unreadCount ?: 0
}
}
println("LALAL TOP")
val clipboard = LocalClipboardManager.current
when (chatInfo) {
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
@@ -634,6 +635,7 @@ fun ChatLayout(
showViaProxy: Boolean,
showSearch: MutableState<Boolean>
) {
println("LALAL CHATLAYOUT TOP")
val scope = rememberCoroutineScope()
val attachmentDisabled = remember { derivedStateOf { composeState.value.attachmentDisabled } }
Box(
@@ -656,66 +658,62 @@ fun ChatLayout(
},
)
) {
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
sheetElevation = 0.dp,
sheetContent = {
ChooseAttachmentView(
attachmentOption,
hide = { scope.launch { attachmentBottomSheetState.hide() } }
)
},
sheetState = attachmentBottomSheetState,
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
) {
val floatingButton: MutableState<@Composable () -> Unit> = remember { mutableStateOf({}) }
val setFloatingButton = { button: @Composable () -> Unit ->
floatingButton.value = button
}
println("LALAL CHATLAYOUT TOP 1")
Scaffold(
topBar = {
if (selectedChatItems.value == null) {
val chatInfo = chatInfo.value
if (chatInfo != null) {
ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
}
} else {
SelectedItemsTopToolbar(selectedChatItems)
}
},
bottomBar = composeView,
floatingActionButton = { floatingButton.value() },
contentColor = LocalContentColor.current,
backgroundColor = Color.Unspecified
) { contentPadding ->
val wallpaperImage = MaterialTheme.wallpaper.type.image
val wallpaperType = MaterialTheme.wallpaper.type
val backgroundColor = MaterialTheme.wallpaper.background ?: wallpaperType.defaultBackgroundColor(CurrentColors.value.base, MaterialTheme.colors.background)
val tintColor = MaterialTheme.wallpaper.tint ?: wallpaperType.defaultTintColor(CurrentColors.value.base)
BoxWithConstraints(Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
.then(if (wallpaperImage != null)
Modifier.drawWithCache { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) }
else
Modifier)
.padding(contentPadding)
) {
val remoteHostId = remember { remoteHostId }.value
val chatInfo = remember { chatInfo }.value
if (chatInfo != null) {
ChatItemsList(
remoteHostId, chatInfo, unreadCount, composeState, searchValue,
useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages,
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
setReaction, showItemDetails, markRead, setFloatingButton, onComposed, developerTools, showViaProxy,
)
}
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
sheetElevation = 0.dp,
sheetContent = {
ChooseAttachmentView(
attachmentOption,
hide = { scope.launch { attachmentBottomSheetState.hide() } }
)
},
sheetState = attachmentBottomSheetState,
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
) {
println("LALAL CHATLAYOUT TOP 2")
val wallpaperImage = MaterialTheme.wallpaper.type.image
val wallpaperType = MaterialTheme.wallpaper.type
val backgroundColor = MaterialTheme.wallpaper.background ?: wallpaperType.defaultBackgroundColor(CurrentColors.value.base, MaterialTheme.colors.background)
val tintColor = MaterialTheme.wallpaper.tint ?: wallpaperType.defaultTintColor(CurrentColors.value.base)
Column(
Modifier
.background(MaterialTheme.colors.background)
.then(if (wallpaperImage != null)
Modifier.drawWithCache { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) }
else
Modifier)
) {
if (selectedChatItems.value == null) {
val chatInfo = chatInfo.value
if (chatInfo != null) {
ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
}
} else {
SelectedItemsTopToolbar(selectedChatItems)
}
Box(
Modifier
.fillMaxWidth()
.weight(1f)
) {
println("LALAL CHATLAYOUT")
val remoteHostId = remember { remoteHostId }.value
val chatInfo = remember { chatInfo }.value
if (chatInfo != null) {
ChatItemsList(
remoteHostId, chatInfo, unreadCount, composeState, searchValue,
useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages,
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
setReaction, showItemDetails, markRead, onComposed, developerTools, showViaProxy,
)
}
}
composeView()
}
}
}
}
@@ -882,14 +880,12 @@ fun ChatInfoToolbar(
onSearchValueChanged = onSearchValueChanged,
buttons = barButtons
)
Divider(Modifier.statusBarsPadding().padding(top = AppBarHeight * fontSizeSqrtMultiplier))
Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd).offset(y = AppBarHeight * fontSizeSqrtMultiplier)) {
Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd)) {
DefaultDropdownMenu(showMenu) {
menuItems.forEach { it() }
}
}
Divider()
}
@Composable
@@ -931,7 +927,7 @@ private fun ContactVerifiedShield() {
}
@Composable
fun BoxWithConstraintsScope.ChatItemsList(
fun BoxScope.ChatItemsList(
remoteHostId: Long?,
chatInfo: ChatInfo,
unreadCount: State<Int>,
@@ -960,11 +956,11 @@ fun BoxWithConstraintsScope.ChatItemsList(
setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit,
showItemDetails: (ChatInfo, ChatItem) -> Unit,
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
setFloatingButton: (@Composable () -> Unit) -> Unit,
onComposed: suspend (chatId: String) -> Unit,
developerTools: Boolean,
showViaProxy: Boolean
) {
println("LALAL CHATITEMSLIST")
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
ScrollToBottom(chatInfo.id, listState, chatModel.chatItems)
@@ -985,11 +981,11 @@ fun BoxWithConstraintsScope.ChatItemsList(
Spacer(Modifier.size(8.dp))
val reversedChatItems by remember { derivedStateOf { chatModel.chatItems.asReversed() } }
val maxHeightRounded = with(LocalDensity.current) { maxHeight.roundToPx() }
val maxHeight = remember { derivedStateOf { listState.layoutInfo.viewportSize.height } }
val scrollToItem: (Long) -> Unit = { itemId: Long ->
val index = reversedChatItems.indexOfFirst { it.id == itemId }
if (index != -1) {
scope.launch { listState.animateScrollToItem(kotlin.math.min(reversedChatItems.lastIndex, index + 1), -maxHeightRounded) }
scope.launch { listState.animateScrollToItem(kotlin.math.min(reversedChatItems.lastIndex, index + 1), -maxHeight.value) }
}
}
// TODO: Having this block on desktop makes ChatItemsList() to recompose twice on chatModel.chatId update instead of once
@@ -1010,6 +1006,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
)
LazyColumnWithScrollBar(Modifier.align(Alignment.BottomCenter), state = listState, reverseLayout = true) {
itemsIndexed(reversedChatItems, key = { _, item -> item.id to item.meta.createdAt.toEpochMilliseconds() }) { i, cItem ->
println("LALAL ITEM $i")
CompositionLocalProvider(
// Makes horizontal and vertical scrolling to coexist nicely.
// With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view
@@ -1020,7 +1017,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
scope.launch {
listState.scrollToItem(
kotlin.math.min(reversedChatItems.lastIndex, indexInReversed + 1),
-maxHeightRounded
-maxHeight.value
)
}
}
@@ -1264,7 +1261,9 @@ fun BoxWithConstraintsScope.ChatItemsList(
}
}
}
FloatingButtons(chatModel.chatItems, unreadCount, remoteHostId, chatInfo, searchValue, markRead, setFloatingButton, listState)
println("LALAL REDRAW TOP")
FloatingButtons(chatModel.chatItems, unreadCount, remoteHostId, chatInfo, searchValue, markRead, listState)
FloatingDate(
Modifier.padding(top = 10.dp).align(Alignment.TopCenter),
@@ -1322,87 +1321,61 @@ private fun ScrollToBottom(chatId: ChatId, listState: LazyListState, chatItems:
}
@Composable
fun BoxWithConstraintsScope.FloatingButtons(
fun BoxScope.FloatingButtons(
chatItems: State<List<ChatItem>>,
unreadCount: State<Int>,
remoteHostId: Long?,
chatInfo: ChatInfo,
searchValue: State<String>,
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
setFloatingButton: (@Composable () -> Unit) -> Unit,
listState: LazyListState
) {
val scope = rememberCoroutineScope()
var firstVisibleIndex by remember { mutableStateOf(listState.firstVisibleItemIndex) }
var lastIndexOfVisibleItems by remember { mutableStateOf(listState.layoutInfo.visibleItemsInfo.lastIndex) }
var firstItemIsVisible by remember { mutableStateOf(firstVisibleIndex == 0) }
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.distinctUntilChanged()
.collect {
firstVisibleIndex = it
firstItemIsVisible = firstVisibleIndex == 0
}
val maxHeight = remember { derivedStateOf { listState.layoutInfo.viewportSize.height } }
val maxWidth = remember { derivedStateOf { listState.layoutInfo.viewportSize.width } }
SideEffect {
println("LALAL REDRAW ${maxHeight.value}")
}
LaunchedEffect(listState) {
// When both snapshotFlows located in one LaunchedEffect second block will never be called because coroutine is paused on first block
// so separate them into two LaunchedEffects
snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastIndex }
.distinctUntilChanged()
.collect {
lastIndexOfVisibleItems = it
}
}
val bottomUnreadCount by remember {
val bottomUnreadCount = remember {
derivedStateOf {
if (unreadCount.value == 0) return@derivedStateOf 0
val items = chatItems.value
val from = items.lastIndex - firstVisibleIndex - lastIndexOfVisibleItems
val from = items.lastIndex - listState.firstVisibleItemIndex - listState.layoutInfo.visibleItemsInfo.lastIndex
if (items.size <= from || from < 0) return@derivedStateOf 0
items.subList(from, items.size).count { it.isRcvNew }
}
}
val firstVisibleOffset = (-with(LocalDensity.current) { maxHeight.roundToPx() } * 0.8).toInt()
LaunchedEffect(bottomUnreadCount, firstItemIsVisible) {
val showButtonWithCounter = bottomUnreadCount > 0 && !firstItemIsVisible && searchValue.value.isEmpty()
val showButtonWithArrow = !showButtonWithCounter && !firstItemIsVisible
setFloatingButton(
bottomEndFloatingButton(
bottomUnreadCount,
showButtonWithCounter,
showButtonWithArrow,
onClickArrowDown = {
scope.launch { listState.animateScrollToItem(0) }
},
onClickCounter = {
scope.launch { listState.animateScrollToItem(kotlin.math.max(0, bottomUnreadCount - 1), firstVisibleOffset) }
}
))
}
val showBottomButtonWithCounter = remember { derivedStateOf { bottomUnreadCount.value > 0 && listState.firstVisibleItemIndex != 0 && searchValue.value.isEmpty() } }
val showBottomButtonWithArrow = remember { derivedStateOf { !showBottomButtonWithCounter.value && listState.firstVisibleItemIndex != 0 } }
BottomEndFloatingButton(
bottomUnreadCount,
showBottomButtonWithCounter,
showBottomButtonWithArrow,
onClickArrowDown = {
scope.launch { listState.animateScrollToItem(0) }
},
onClickCounter = {
val firstVisibleOffset = (-maxHeight.value * 0.8).toInt()
scope.launch { listState.animateScrollToItem(kotlin.math.max(0, bottomUnreadCount.value - 1), firstVisibleOffset) }
}
)
// Don't show top FAB if is in search
if (searchValue.value.isNotEmpty()) return
val fabSize = 56.dp
val topUnreadCount by remember {
derivedStateOf { unreadCount.value - bottomUnreadCount }
}
val showButtonWithCounter = topUnreadCount > 0
val height = with(LocalDensity.current) { maxHeight.toPx() }
val topUnreadCount = remember { derivedStateOf { unreadCount.value - bottomUnreadCount.value } }
val showDropDown = remember { mutableStateOf(false) }
TopEndFloatingButton(
Modifier.padding(end = DEFAULT_PADDING, top = 24.dp).align(Alignment.TopEnd),
topUnreadCount,
showButtonWithCounter,
onClick = { scope.launch { listState.animateScrollBy(height) } },
onClick = { scope.launch { listState.animateScrollBy(maxHeight.value.toFloat()) } },
onLongClick = { showDropDown.value = true }
)
Box {
DefaultDropdownMenu(showDropDown, offset = DpOffset(this@FloatingButtons.maxWidth - DEFAULT_PADDING, 24.dp + fabSize)) {
Box(Modifier.offset { IntOffset(maxWidth.value - DEFAULT_PADDING.roundToPx(), (24.dp + fabSize).roundToPx()) }) {
DefaultDropdownMenu(showDropDown) {
ItemAction(
generalGetString(MR.strings.mark_read),
painterResource(MR.images.ic_check),
@@ -1410,7 +1383,7 @@ fun BoxWithConstraintsScope.FloatingButtons(
val minUnreadItemId = chatModel.chats.value.firstOrNull { it.remoteHostId == remoteHostId && it.id == chatInfo.id }?.chatStats?.minUnreadItemId ?: return@ItemAction
markRead(
CC.ItemRange(minUnreadItemId, chatItems.value[chatItems.value.size - listState.layoutInfo.visibleItemsInfo.lastIndex - 1].id - 1),
bottomUnreadCount
bottomUnreadCount.value
)
showDropDown.value = false
})
@@ -1472,12 +1445,11 @@ fun MemberImage(member: GroupMember) {
@Composable
private fun TopEndFloatingButton(
modifier: Modifier = Modifier,
unreadCount: Int,
showButtonWithCounter: Boolean,
unreadCount: State<Int>,
onClick: () -> Unit,
onLongClick: () -> Unit
) = when {
showButtonWithCounter -> {
unreadCount.value > 0 -> {
val interactionSource = interactionSourceWithDetection(onClick, onLongClick)
FloatingActionButton(
{}, // no action here
@@ -1487,7 +1459,7 @@ private fun TopEndFloatingButton(
interactionSource = interactionSource,
) {
Text(
unreadCountStr(unreadCount),
unreadCountStr(unreadCount.value),
color = MaterialTheme.colors.primary,
fontSize = 14.sp,
)
@@ -1694,48 +1666,43 @@ fun openGroupLink(groupInfo: GroupInfo, rhId: Long?, view: Any? = null, close: (
}
}
private fun bottomEndFloatingButton(
unreadCount: Int,
showButtonWithCounter: Boolean,
showButtonWithArrow: Boolean,
@Composable
private fun BoxScope.BottomEndFloatingButton(
unreadCount: State<Int>,
showButtonWithCounter: State<Boolean>,
showButtonWithArrow: State<Boolean>,
onClickArrowDown: () -> Unit,
onClickCounter: () -> Unit
): @Composable () -> Unit = when {
showButtonWithCounter -> {
{
FloatingActionButton(
onClick = onClickCounter,
elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp),
modifier = Modifier.size(48.dp),
backgroundColor = MaterialTheme.colors.secondaryVariant,
) {
Text(
unreadCountStr(unreadCount),
color = MaterialTheme.colors.primary,
fontSize = 14.sp,
)
}
) = when {
showButtonWithCounter.value -> {
FloatingActionButton(
onClick = onClickCounter,
elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp),
modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING).align(Alignment.BottomEnd).size(48.dp),
backgroundColor = MaterialTheme.colors.secondaryVariant,
) {
Text(
unreadCountStr(unreadCount.value),
color = MaterialTheme.colors.primary,
fontSize = 14.sp,
)
}
}
showButtonWithArrow -> {
{
FloatingActionButton(
onClick = onClickArrowDown,
elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp),
modifier = Modifier.size(48.dp),
backgroundColor = MaterialTheme.colors.secondaryVariant,
) {
Icon(
painter = painterResource(MR.images.ic_keyboard_arrow_down),
contentDescription = null,
tint = MaterialTheme.colors.primary
)
}
showButtonWithArrow.value -> {
FloatingActionButton(
onClick = onClickArrowDown,
elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp),
modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING).align(Alignment.BottomEnd).size(48.dp),
backgroundColor = MaterialTheme.colors.secondaryVariant,
) {
Icon(
painter = painterResource(MR.images.ic_keyboard_arrow_down),
contentDescription = null,
tint = MaterialTheme.colors.primary
)
}
}
else -> {
{}
}
else -> {}
}
@Composable
@@ -1045,6 +1045,7 @@ fun ComposeView(
)
}
Spacer(Modifier.imePadding().navigationBarsPadding())
// WindowInsets.ime.asPaddingValues().calculateBottomPadding()
}
}
}
@@ -43,7 +43,7 @@ fun SelectedItemsTopToolbar(selectedChatItems: MutableState<Set<Long>?>) {
onTop = true,
onSearchValueChanged = {},
)
Divider(Modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier))
Divider()
}
@Composable
@@ -397,6 +397,7 @@ fun ModalData.GroupChatInfoLayout(
}
}
SectionBottomSpacer()
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}
}
}
@@ -82,10 +82,9 @@ fun GroupProfileLayout(
}, close)
}
}
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
modifier = Modifier.navigationBarsWithImePadding(),
modifier = Modifier.imePadding(),
sheetContent = {
GetImageBottomSheet(
chosenImage,
@@ -177,7 +176,6 @@ fun GroupProfileLayout(
}
}
}
}
}
private fun canUpdateProfile(displayName: String, groupProfile: GroupProfile): Boolean =
@@ -591,6 +591,11 @@ enum class ScrollDirection {
Up, Down, Idle
}
@Composable
fun StatusBarBackground() {
Box(Modifier.background(MaterialTheme.colors.background).windowInsetsTopHeight(WindowInsets.statusBars))
}
@Composable
private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldValue>) {
val listState = rememberLazyListState(lazyListState.first, lazyListState.second)
@@ -630,8 +635,12 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldVal
val searchShowingSimplexLink = remember { mutableStateOf(false) }
val searchChatFilteredBySimplexLink = remember { mutableStateOf<String?>(null) }
val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.value.toList())
Column {
if (oneHandUI.value) {
StatusBarBackground()
}
LazyColumnWithScrollBar(
Modifier.fillMaxSize(),
Modifier.fillMaxSize().then(if (!oneHandUI.value) Modifier.imePadding() else Modifier),
listState,
reverseLayout = oneHandUI.value
) {
@@ -679,6 +688,10 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldVal
ToggleChatListCard()
}
}
if (!oneHandUI.value) {
item { Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) }
}
}
}
if (chats.isEmpty() && chatModel.chats.value.isNotEmpty()) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
@@ -35,7 +35,7 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
},
bottomBar = {
if (oneHandUI.value) {
Column {
Column(Modifier.imePadding()) {
Divider()
ShareListToolbar(chatModel, stopped) { searchInList = it.trim() }
}
@@ -73,6 +73,9 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
Column(
modifier = Modifier.fillMaxSize()
) {
if (oneHandUI.value) {
StatusBarBackground()
}
if (chatModel.chats.value.isNotEmpty()) {
ShareList(
chatModel,
@@ -213,7 +216,7 @@ private fun ShareList(
}
}
LazyColumnWithScrollBar(
modifier = Modifier.fillMaxSize(),
modifier = Modifier.fillMaxSize().then(if (!oneHandUI.value) Modifier.imePadding() else Modifier),
reverseLayout = oneHandUI.value
) {
items(chats) { chat ->
@@ -225,5 +228,10 @@ private fun ShareList(
hasSimplexLink = hasSimplexLink,
)
}
if (!oneHandUI.value) {
item {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
}
}
}
}
@@ -143,6 +143,7 @@ fun UserPicker(
.fillMaxWidth()
.then(if (newChat.isVisible()) Modifier.shadow(8.dp, clip = true) else Modifier)
.background(if (appPlatform.isAndroid) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, alpha = 1 - userPickerAlpha()) else MaterialTheme.colors.surface)
.navigationBarsPadding()
.padding(bottom = USER_PICKER_SECTION_SPACING - DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
pickerState = userPickerState
) {
@@ -382,6 +382,7 @@ private fun drawToBitmap(image: ImageBitmap, imageScale: Float, tint: Color, siz
}
fun CacheDrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, background: Color, tint: Color): DrawResult {
println("LALAL SIZE $size")
val imageScale = if (imageType is WallpaperType.Preset) {
(imageType.scale ?: 1f) * imageType.predefinedImageScale
} else if (imageType is WallpaperType.Image && imageType.scaleType == WallpaperScaleType.REPEAT) {
@@ -396,6 +397,7 @@ fun CacheDrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperTy
}
return onDrawBehind {
println("LALAL SIZE2 $size")
val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low
drawRect(background)
when (imageType) {
@@ -12,6 +12,7 @@ import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
import androidx.compose.ui.draw.*
import androidx.compose.ui.graphics.*
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.*
import chat.simplex.common.platform.appPlatform
@@ -99,7 +100,8 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co
} else {
Spacer(Modifier.weight(1f))
}
Row {
val direction = LocalLayoutDirection.current
Row(Modifier.padding(end = WindowInsets.navigationBars.asPaddingValues().calculateEndPadding(direction))) {
endButtons()
}
}
@@ -57,7 +57,6 @@ fun TextEditor(
) {
val textFieldModifier = modifier
.fillMaxWidth()
.navigationBarsWithImePadding()
.onFocusChanged { focused = it.isFocused }
.padding(10.dp)
@@ -84,10 +84,9 @@ fun AddGroupLayout(
val focusRequester = remember { FocusRequester() }
val incognito = remember { mutableStateOf(incognitoPref.get()) }
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
modifier = Modifier.navigationBarsWithImePadding(),
modifier = Modifier.imePadding(),
sheetContent = {
GetImageBottomSheet(
chosenImage,
@@ -170,7 +169,6 @@ fun AddGroupLayout(
}
}
}
}
}
fun canCreateProfile(displayName: String): Boolean = displayName.trim().isNotEmpty() && isValidDisplayName(displayName.trim())
@@ -1,9 +1,7 @@
package chat.simplex.common.views.newchat
import SectionDivider
import SectionDividerSpaced
import SectionItemView
import SectionSpacer
import SectionView
import TextIconSpaced
import androidx.compose.desktop.ui.tooling.preview.Preview
@@ -33,24 +31,22 @@ import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chatlist.ScrollDirection
import chat.simplex.common.views.chatlist.StatusBarBackground
import chat.simplex.common.views.contacts.*
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import java.net.URI
@Composable
fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) {
val oneHandUI = remember { appPrefs.oneHandUI.state }
val keyboardState by getKeyboardState()
val showToolbarInOneHandUI = remember { derivedStateOf { keyboardState == KeyboardState.Closed && oneHandUI.value } }
Scaffold(
bottomBar = {
if (showToolbarInOneHandUI.value) {
Column {
if (oneHandUI.value) {
Column(Modifier.imePadding()) {
Divider()
CloseSheetBar(
close = close,
@@ -187,8 +183,12 @@ private fun ModalData.NewChatSheetLayout(
derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) }
}
Column {
if (oneHandUI.value) {
StatusBarBackground()
}
LazyColumnWithScrollBar(
Modifier.fillMaxSize(),
Modifier.fillMaxSize().then(if (!oneHandUI.value) Modifier.imePadding() else Modifier),
listState,
reverseLayout = oneHandUI.value
) {
@@ -242,8 +242,6 @@ private fun ModalData.NewChatSheetLayout(
)
if (!oneHandUI.value) {
Divider()
} else {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}
}
}
@@ -353,6 +351,7 @@ private fun ModalData.NewChatSheetLayout(
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true)
}
}
}
}
@Composable
@@ -556,13 +555,10 @@ private fun contactTypesSearchTargets(baseContactTypes: List<ContactType>, searc
@Composable
private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats: () -> Unit, close: () -> Unit) {
val oneHandUI = remember { appPrefs.oneHandUI.state }
val keyboardState by getKeyboardState()
val showToolbarInOneHandUI = remember { derivedStateOf { keyboardState == KeyboardState.Closed && oneHandUI.value } }
Scaffold(
bottomBar = {
if (showToolbarInOneHandUI.value) {
Column {
if (oneHandUI.value) {
Column(Modifier.imePadding()) {
Divider()
CloseSheetBar(
close = closeDeletedChats,
@@ -592,8 +588,12 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats
contactChats = allChats
)
Column {
if (oneHandUI.value) {
StatusBarBackground()
}
LazyColumnWithScrollBar(
Modifier.fillMaxSize(),
Modifier.fillMaxSize().then(if (!oneHandUI.value) Modifier.imePadding() else Modifier),
contentPadding = contentPadding,
reverseLayout = oneHandUI.value,
) {
@@ -612,8 +612,6 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats
item {
if (!oneHandUI.value) {
Divider()
} else {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}
ContactsSearchBar(
listState = listState,
@@ -647,6 +645,7 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = false)
}
}
}
}
}
@@ -434,6 +434,9 @@ fun ActiveProfilePicker(
ProfilePickerUserOption(p)
}
}
item {
Spacer(Modifier.imePadding())
}
}
}
if (progressByTimeout) {
@@ -123,7 +123,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) {
.padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING.times(0.75f))
) {
AppBarTitle(String.format(generalGetString(MR.strings.new_in_version), v.version), bottomPadding = DEFAULT_PADDING)
AppBarTitle(String.format(generalGetString(MR.strings.new_in_version), v.version), withPadding = false, bottomPadding = DEFAULT_PADDING)
v.features.forEach { feature ->
if (feature.show) {
@@ -912,6 +912,7 @@ object AppearanceScope {
ColumnWithScrollBar(
Modifier
.fillMaxWidth()
.imePadding()
) {
AppBarTitle(name.text)
@@ -39,7 +39,6 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, close: (
val user = chatModel.currentUser.value
val stopped = chatModel.chatRunning.value == false
SettingsLayout(
profile = user?.profile,
stopped,
chatModel.chatDbEncrypted.value == true,
remember { chatModel.controller.appPrefs.storeDBPassphrase.state }.value,
@@ -80,7 +79,6 @@ val simplexTeamUri =
@Composable
fun SettingsLayout(
profile: LocalProfile?,
stopped: Boolean,
encrypted: Boolean,
passphraseSaved: Boolean,
@@ -94,17 +92,14 @@ fun SettingsLayout(
showVersion: () -> Unit,
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit,
) {
val scope = rememberCoroutineScope()
val view = LocalMultiplatformView()
LaunchedEffect(Unit) {
hideKeyboard(view)
}
val theme = CurrentColors.collectAsState()
val uriHandler = LocalUriHandler.current
ColumnWithScrollBar(
Modifier
.fillMaxSize()
.themedBackground(theme.value.base)
) {
AppBarTitle(stringResource(MR.strings.your_settings))
@@ -488,7 +483,6 @@ private fun runAuth(title: String, desc: String, onFinish: (success: Boolean) ->
fun PreviewSettingsLayout() {
SimpleXTheme {
SettingsLayout(
profile = LocalProfile.sampleData,
stopped = false,
encrypted = false,
passphraseSaved = false,
@@ -71,10 +71,8 @@ fun UserProfileLayout(
val keyboardState by getKeyboardState()
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
val focusRequester = remember { FocusRequester() }
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
modifier = Modifier.navigationBarsWithImePadding(),
sheetContent = {
GetImageBottomSheet(
chosenImage,
@@ -90,7 +88,6 @@ fun UserProfileLayout(
displayName.value == profile.displayName &&
fullName.value == profile.fullName &&
profile.image == profileImage.value
val closeWithAlert = {
if (dataUnchanged || !canSaveProfile(displayName.value, profile)) {
close()
@@ -103,7 +100,7 @@ fun UserProfileLayout(
Modifier
.padding(horizontal = DEFAULT_PADDING),
) {
AppBarTitle(stringResource(MR.strings.your_current_profile))
AppBarTitle(stringResource(MR.strings.your_current_profile), withPadding = false)
ReadableText(generalGetString(MR.strings.your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it), TextAlign.Center)
Column(
Modifier
@@ -170,7 +167,6 @@ fun UserProfileLayout(
}
}
}
}
}
@Composable
@@ -8,17 +8,6 @@ import androidx.compose.ui.input.pointer.*
import java.io.File
import java.net.URI
actual fun Modifier.navigationBarsWithImePadding(): Modifier = this
@Composable
actual fun ProvideWindowInsets(
consumeWindowInsets: Boolean,
windowInsetsAnimationsEnabled: Boolean,
content: @Composable () -> Unit
) {
content()
}
@Composable
actual fun Modifier.desktopOnExternalDrag(
enabled: Boolean,