UI improvements and fix navigation

This commit is contained in:
hayk888997
2026-04-04 15:31:35 +04:00
parent 18f66f6894
commit 40af625a98
9 changed files with 854 additions and 237 deletions
+1 -1
View File
@@ -148,4 +148,4 @@
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>
</component>
@@ -151,45 +151,120 @@ fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow<Animate
val connectSheetScope = rememberCoroutineScope()
val onConnectClick: () -> Unit = { connectSheetScope.launch { connectSheetState.show() } }
var showOneTimeLinkSheet by remember { mutableStateOf(false) }
val oneTimeLinkSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
val oneTimeLinkSheetScope = rememberCoroutineScope()
val onOneTimeLinkClick: () -> Unit = {
showOneTimeLinkSheet = true
oneTimeLinkSheetScope.launch { oneTimeLinkSheetState.show() }
}
LaunchedEffect(oneTimeLinkSheetState.currentValue) {
if (oneTimeLinkSheetState.currentValue == ModalBottomSheetValue.Hidden && showOneTimeLinkSheet) {
showOneTimeLinkSheet = false
}
}
var showConnectBannerNewChatSheet by remember { mutableStateOf(false) }
var connectBannerSheetSelection by remember { mutableStateOf(NewChatOption.INVITE) }
var connectBannerSheetShowQR by remember { mutableStateOf(false) }
val connectBannerSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
val connectBannerSheetScope = rememberCoroutineScope()
LaunchedEffect(connectBannerSheetState.currentValue) {
if (connectBannerSheetState.currentValue == ModalBottomSheetValue.Hidden && showConnectBannerNewChatSheet) {
showConnectBannerNewChatSheet = false
}
}
val onBannerInviteClick: () -> Unit = {
connectBannerSheetSelection = NewChatOption.INVITE
connectBannerSheetShowQR = false
showConnectBannerNewChatSheet = true
connectBannerSheetScope.launch { connectBannerSheetState.show() }
}
val onBannerScanPasteClick: () -> Unit = {
connectBannerSheetSelection = NewChatOption.CONNECT
connectBannerSheetShowQR = appPlatform.isAndroid
showConnectBannerNewChatSheet = true
connectBannerSheetScope.launch { connectBannerSheetState.show() }
}
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
sheetState = connectSheetState,
sheetState = connectBannerSheetState,
sheetShape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
sheetBackgroundColor = MaterialTheme.colors.secondaryVariant,
sheetContent = {
ModalData().ConnectViewLinkOrQrModal(
rhId = chatModel.currentRemoteHost.value?.remoteHostId,
close = { connectSheetScope.launch { connectSheetState.hide() } }
)
if (showConnectBannerNewChatSheet) {
ModalData().NewChatViewSheet(
rh = chatModel.currentRemoteHost.value,
selection = connectBannerSheetSelection,
showQRCodeScanner = connectBannerSheetShowQR,
close = { connectBannerSheetScope.launch { connectBannerSheetState.hide() } }
)
} else {
Spacer(Modifier.height(1.dp))
}
}
) {
Box(Modifier.fillMaxSize()) {
if (oneHandUI.value) {
ChatListWithLoadingScreen(searchText, listState, onConnectClick)
Column(Modifier.align(Alignment.BottomCenter)) {
ChatListToolbar(
userPickerState,
listState,
stopped,
setPerformLA,
)
}
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
sheetState = oneTimeLinkSheetState,
sheetShape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
sheetBackgroundColor = MaterialTheme.colors.secondaryVariant,
sheetContent = {
if (showOneTimeLinkSheet) {
OneTimeLinkBottomSheet(
rhId = chatModel.currentRemoteHost.value?.remoteHostId,
close = { oneTimeLinkSheetScope.launch { oneTimeLinkSheetState.hide() } }
)
} else {
ChatListWithLoadingScreen(searchText, listState, onConnectClick)
Column {
ChatListToolbar(
userPickerState,
listState,
stopped,
setPerformLA,
)
}
if (searchText.value.text.isEmpty() && !chatModel.desktopNoUserNoRemote && chatModel.chatRunning.value == true) {
NewChatSheetFloatingButton(oneHandUI, stopped)
Spacer(Modifier.height(1.dp))
}
}
) {
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
sheetState = connectSheetState,
sheetShape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
sheetBackgroundColor = MaterialTheme.colors.secondaryVariant,
sheetContent = {
ModalData().ConnectViewLinkOrQrModal(
rhId = chatModel.currentRemoteHost.value?.remoteHostId,
close = { connectSheetScope.launch { connectSheetState.hide() } }
)
}
) {
Box(Modifier.fillMaxSize()) {
if (oneHandUI.value) {
ChatListWithLoadingScreen(searchText, listState, onConnectClick, onOneTimeLinkClick, onBannerInviteClick, onBannerScanPasteClick)
Column(Modifier.align(Alignment.BottomCenter)) {
ChatListToolbar(
userPickerState,
listState,
stopped,
setPerformLA,
)
}
} else {
ChatListWithLoadingScreen(searchText, listState, onConnectClick, onOneTimeLinkClick, onBannerInviteClick, onBannerScanPasteClick)
Column {
ChatListToolbar(
userPickerState,
listState,
stopped,
setPerformLA,
)
}
if (searchText.value.text.isEmpty() && !chatModel.desktopNoUserNoRemote && chatModel.chatRunning.value == true) {
NewChatSheetFloatingButton(oneHandUI, stopped)
}
}
}
}
}
} // closes ModalBottomSheetLayout(connectBannerSheetState)
if (searchText.value.text.isEmpty()) {
if (appPlatform.isDesktop && !oneHandUI.value) {
@@ -305,9 +380,13 @@ private fun AddressCreationCard() {
}
@Composable
private fun BoxScope.ChatListWithLoadingScreen(searchText: MutableState<TextFieldValue>, listState: LazyListState, onConnectClick: () -> Unit) {
private fun BoxScope.ChatListWithLoadingScreen(searchText: MutableState<TextFieldValue>, listState: LazyListState, onConnectClick: () -> Unit, onOneTimeLinkClick: () -> Unit, onBannerInviteClick: () -> Unit, onBannerScanPasteClick: () -> Unit) {
if (!chatModel.desktopNoUserNoRemote) {
EmptyChatListView(onConnectClick)
if (chatModel.chats.value.isEmpty()) {
EmptyChatListView(onConnectClick = onConnectClick, onOneTimeLinkClick = onOneTimeLinkClick)
} else {
ChatList(searchText = searchText, listState, onBannerInviteClick, onBannerScanPasteClick)
}
return
}
@@ -319,7 +398,7 @@ private fun BoxScope.ChatListWithLoadingScreen(searchText: MutableState<TextFiel
color = MaterialTheme.colors.secondary
)
} else {
EmptyChatListView(onConnectClick)
EmptyChatListView(onConnectClick = onConnectClick, onOneTimeLinkClick = onOneTimeLinkClick)
}
}
}
@@ -770,7 +849,7 @@ fun BoxScope.NavigationBarBackground(modifier: Modifier, color: Color = Material
}
@Composable
private fun BoxScope.ChatList(searchText: MutableState<TextFieldValue>, listState: LazyListState) {
private fun BoxScope.ChatList(searchText: MutableState<TextFieldValue>, listState: LazyListState, onBannerInviteClick: () -> Unit, onBannerScanPasteClick: () -> Unit) {
var scrollDirection by remember { mutableStateOf(ScrollDirection.Idle) }
var previousIndex by remember { mutableStateOf(0) }
var previousScrollOffset by remember { mutableStateOf(0) }
@@ -859,7 +938,7 @@ private fun BoxScope.ChatList(searchText: MutableState<TextFieldValue>, listStat
}
if (!oneHandUICardShown.value || !addressCreationCardShown.value || !connectBannerCardShown.value) {
item {
ChatListFeatureCards()
ChatListFeatureCards(onBannerInviteClick, onBannerScanPasteClick)
}
}
if (appPlatform.isAndroid) {
@@ -929,7 +1008,7 @@ private fun NoChatsView(searchText: MutableState<TextFieldValue>) {
}
@Composable
private fun ChatListFeatureCards() {
private fun ChatListFeatureCards(onBannerInviteClick: () -> Unit, onBannerScanPasteClick: () -> Unit) {
val oneHandUI = remember { appPrefs.oneHandUI.state }
val oneHandUICardShown = remember { appPrefs.oneHandUICardShown.state }
val addressCreationCardShown = remember { appPrefs.addressCreationCardShown.state }
@@ -937,14 +1016,14 @@ private fun ChatListFeatureCards() {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
if (!connectBannerCardShown.value) {
ConnectBannerCard()
ConnectBannerCard(onBannerInviteClick, onBannerScanPasteClick)
}
if (!oneHandUICardShown.value && !oneHandUI.value) {
ToggleChatListCard()
}
if (!addressCreationCardShown.value) {
AddressCreationCard()
}
// if (!addressCreationCardShown.value) {
// AddressCreationCard()
// }
if (!oneHandUICardShown.value && oneHandUI.value) {
ToggleChatListCard()
}
@@ -15,50 +15,58 @@ import androidx.compose.ui.unit.dp
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.newchat.*
import chat.simplex.res.MR
@Composable
fun ConnectBannerCard() {
val closeAll = { ModalManager.start.closeModals() }
Surface(
shape = RoundedCornerShape(18.dp),
color = MaterialTheme.appColors.sentMessage,
modifier = Modifier.fillMaxWidth()
) {
Box {
fun ConnectBannerCard(onInviteClick: () -> Unit, onScanPasteClick: () -> Unit) {
Column {
IconButton(
onClick = { appPrefs.connectBannerCardShown.set(true) },
modifier = Modifier.align(Alignment.End)
) {
Icon(
painterResource(MR.images.ic_close),
stringResource(MR.strings.back),
tint = MaterialTheme.colors.secondary
)
}
Surface(
shape = RoundedCornerShape(18.dp),
color = MaterialTheme.appColors.sentMessage,
modifier = Modifier.fillMaxWidth()
) {
Column {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Image(
painterResource(MR.images.ic_invitation_card_invite_someone),
contentDescription = stringResource(MR.strings.create_link_or_qr),
Box(
modifier = Modifier
.weight(1f)
.clickable {
ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ ->
NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll)
}
},
contentScale = ContentScale.FillWidth
)
Image(
painterResource(MR.images.ic_invitation_card_one_time_link),
contentDescription = stringResource(MR.strings.paste_link_scan),
.aspectRatio(1.6f)
.clickable { onInviteClick() },
) {
Image(
painterResource(MR.images.ic_invitation_card_invite_someone),
contentDescription = stringResource(MR.strings.create_link_or_qr),
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
}
Box(
modifier = Modifier
.weight(1f)
.clickable {
ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ ->
NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll)
}
},
contentScale = ContentScale.FillWidth
)
.aspectRatio(1.6f)
.clickable { onScanPasteClick() },
) {
Image(
painterResource(MR.images.ic_invitation_card_one_time_link),
contentDescription = stringResource(MR.strings.paste_link_scan),
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
}
}
Divider(color = MaterialTheme.colors.onSurface.copy(alpha = 0.06f))
Row(
@@ -68,11 +76,7 @@ fun ConnectBannerCard() {
Row(
Modifier
.weight(1f)
.clickable {
ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ ->
NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll)
}
},
.clickable { onInviteClick() },
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
@@ -91,11 +95,7 @@ fun ConnectBannerCard() {
Row(
Modifier
.weight(1f)
.clickable {
ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ ->
NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll)
}
},
.clickable { onScanPasteClick() },
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
@@ -113,16 +113,6 @@ fun ConnectBannerCard() {
}
}
}
IconButton(
onClick = { appPrefs.connectBannerCardShown.set(true) },
modifier = Modifier.align(Alignment.TopEnd).padding(4.dp)
) {
Icon(
painterResource(MR.images.ic_close),
stringResource(MR.strings.back),
tint = MaterialTheme.colors.secondary
)
}
}
}
}
@@ -131,6 +121,6 @@ fun ConnectBannerCard() {
@Composable
fun PreviewConnectBannerCard() {
SimpleXTheme {
ConnectBannerCard()
ConnectBannerCard(onInviteClick = {}, onScanPasteClick = {})
}
}
@@ -2,44 +2,73 @@ package chat.simplex.common.views.invitation_redesign
import SectionBottomSpacer
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.UserAddressView
import chat.simplex.res.MR
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun InviteSomeoneWithPicturesView(close: () -> Unit) {
var showOneTimeLinkSheet by remember { mutableStateOf(false) }
val oneTimeLinkSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
val sheetScope = rememberCoroutineScope()
LaunchedEffect(oneTimeLinkSheetState.currentValue) {
if (oneTimeLinkSheetState.currentValue == ModalBottomSheetValue.Hidden && showOneTimeLinkSheet) {
showOneTimeLinkSheet = false
}
}
ModalView(close) {
ColumnWithScrollBar(
Modifier.fillMaxSize().padding(bottom = DEFAULT_BOTTOM_PADDING).background(Color.White),
horizontalAlignment = Alignment.CenterHorizontally
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
sheetState = oneTimeLinkSheetState,
sheetShape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
sheetBackgroundColor = MaterialTheme.colors.secondaryVariant,
sheetContent = {
if (showOneTimeLinkSheet) {
OneTimeLinkBottomSheet(
rhId = chatModel.currentRemoteHost.value?.remoteHostId,
close = { sheetScope.launch { oneTimeLinkSheetState.hide() } }
)
} else {
Spacer(Modifier.height(1.dp))
}
}
) {
AppBarTitle(stringResource(MR.strings.invite_someone), withPadding = false)
Spacer(Modifier.height(DEFAULT_PADDING))
InviteSomeoneWithPicturesContent()
SectionBottomSpacer()
ColumnWithScrollBar(
Modifier.fillMaxSize().padding(bottom = DEFAULT_BOTTOM_PADDING).background(Color.White),
horizontalAlignment = Alignment.CenterHorizontally
) {
AppBarTitle(stringResource(MR.strings.invite_someone), withPadding = false)
Spacer(Modifier.height(DEFAULT_PADDING))
InviteSomeoneWithPicturesContent(
onOneTimeLinkClick = {
showOneTimeLinkSheet = true
sheetScope.launch { oneTimeLinkSheetState.show() }
}
)
SectionBottomSpacer()
}
}
}
}
@Composable
fun InviteSomeoneWithPicturesContent() {
fun InviteSomeoneWithPicturesContent(onOneTimeLinkClick: () -> Unit) {
InvitationCardView(
mainImageResource = MR.images.ic_invitation_card_one_time_link,
@@ -49,11 +78,7 @@ fun InviteSomeoneWithPicturesContent() {
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING)
.clickable {
ModalManager.start.showModalCloseable { close ->
OneTimeLinkView(rhId = chatModel.currentRemoteHost.value?.remoteHostId, close = close)
}
}
.clickable { onOneTimeLinkClick() }
)
Spacer(Modifier.height(DEFAULT_PADDING))
@@ -66,15 +91,11 @@ fun InviteSomeoneWithPicturesContent() {
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING)
.clickable {
// TODO, Hayk replace with page when data will be available
ModalManager.start.showModalCloseable { close ->
OneTimeLinkView(rhId = chatModel.currentRemoteHost.value?.remoteHostId, close = close)
}
}
.clickable { onOneTimeLinkClick() }
)
}
@OptIn(ExperimentalMaterialApi::class)
@Preview
@Composable
private fun PreviewInviteSomeoneView() {
@@ -85,7 +106,7 @@ private fun PreviewInviteSomeoneView() {
) {
AppBarTitle(stringResource(MR.strings.invite_someone), withPadding = false)
Spacer(Modifier.height(DEFAULT_PADDING))
InviteSomeoneWithPicturesContent()
InviteSomeoneWithPicturesContent(onOneTimeLinkClick = {})
SectionBottomSpacer()
}
}
@@ -19,7 +19,7 @@ import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
@Composable
fun EmptyChatListView(onConnectClick: () -> Unit) {
fun EmptyChatListView(onConnectClick: () -> Unit, onOneTimeLinkClick: () -> Unit) {
var showInviteSomeone by remember { mutableStateOf(false) }
if (showInviteSomeone) {
@@ -34,14 +34,14 @@ fun EmptyChatListView(onConnectClick: () -> Unit) {
Row(Modifier.fillMaxWidth()) {
NavigationButtonBack(onButtonClicked = { showInviteSomeone = false })
}
Spacer(Modifier.height(DEFAULT_BIG_PADDING))
Spacer(Modifier.height(DEFAULT_PADDING))
Text(
stringResource(MR.strings.invite_someone),
style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Bold),
)
Spacer(Modifier.height(DEFAULT_PADDING))
if (SHOW_PICTURES) {
InviteSomeoneWithPicturesContent()
InviteSomeoneWithPicturesContent(onOneTimeLinkClick = onOneTimeLinkClick)
} else {
InviteSomeoneContent()
}
@@ -52,7 +52,7 @@ fun EmptyChatListView(onConnectClick: () -> Unit) {
Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING)
.padding(top = AppBarHeight + DEFAULT_BIG_PADDING),
.padding(top = AppBarHeight),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
@@ -83,7 +83,7 @@ fun EmptyChatListView(onConnectClick: () -> Unit) {
fun PreviewEmptyChatListView() {
SimpleXTheme {
Box(Modifier.fillMaxSize()) {
EmptyChatListView(onConnectClick = {})
EmptyChatListView(onConnectClick = {}, onOneTimeLinkClick = {})
}
}
}
@@ -4,7 +4,9 @@ import SectionBottomSpacer
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
@@ -89,6 +91,140 @@ fun OneTimeLinkView(rhId: Long?, close: () -> Unit) {
}
}
@Composable
fun OneTimeLinkBottomSheet(rhId: Long?, close: () -> Unit) {
val contactConnection: MutableState<PendingContactConnection?> = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(chatModel.showingInvitation.value?.conn) }
val connLinkInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connLink ?: CreatedConnLink("", null) } }
val creatingConnReq = rememberSaveable { mutableStateOf(false) }
LaunchedEffect(Unit) {
if (
connLinkInvitation.connFullLink.isEmpty()
&& contactConnection.value == null
&& !creatingConnReq.value
) {
creatingConnReq.value = true
withBGApi {
val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get())
if (r != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnection(rhId, r.second)
chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connLink = r.first, connChatUsed = false, conn = r.second)
contactConnection.value = r.second
}
} else {
creatingConnReq.value = false
if (alert != null) {
alert()
}
}
}
}
}
DisposableEffect(Unit) {
onDispose {
if (chatModel.showingInvitation.value != null) {
val conn = contactConnection.value
if (chatModel.showingInvitation.value?.connChatUsed == false && conn != null) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.keep_unused_invitation_question),
text = generalGetString(MR.strings.you_can_view_invitation_link_again),
confirmText = generalGetString(MR.strings.delete_verb),
dismissText = generalGetString(MR.strings.keep_invitation_link),
destructive = true,
onConfirm = {
withBGApi {
val chatInfo = ChatInfo.ContactConnection(conn)
controller.deleteChat(Chat(remoteHostId = rhId, chatInfo = chatInfo, chatItems = listOf()))
if (chatModel.chatId.value == chatInfo.id) {
chatModel.chatId.value = null
ModalManager.start.closeModals()
}
}
}
)
}
chatModel.showingInvitation.value = null
}
}
}
OneTimeLinkBottomSheetContent(connLinkInvitation)
}
@Composable
fun OneTimeLinkBottomSheetContent(connLinkInvitation: CreatedConnLink) {
val showShortLink = remember { mutableStateOf(true) }
Column(
Modifier
.fillMaxWidth()
.wrapContentHeight()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(Modifier.height(DEFAULT_PADDING))
Image(
painterResource(MR.images.ic_invitation_one_time_link),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING * 3)
)
Spacer(Modifier.height(DEFAULT_PADDING))
Text(
stringResource(MR.strings.send_1_time_link_description),
style = MaterialTheme.typography.body1,
modifier = Modifier.padding(horizontal = DEFAULT_PADDING)
)
Spacer(Modifier.height(DEFAULT_PADDING))
if (connLinkInvitation.connFullLink.isNotEmpty()) {
OneTimeLinkBar(connLinkInvitation, showShortLink.value)
Spacer(Modifier.height(DEFAULT_PADDING))
Text(
stringResource(MR.strings.or_show_qr_code_in_person_or_video_call),
style = MaterialTheme.typography.body1,
modifier = Modifier.padding(horizontal = DEFAULT_PADDING)
)
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
Surface(
shape = RoundedCornerShape(18.dp),
color = MaterialTheme.colors.background,
modifier = Modifier
.fillMaxWidth()
.padding(DEFAULT_PADDING)
) {
SimpleXCreatedLinkQRCode(
connLinkInvitation,
padding = PaddingValues(DEFAULT_PADDING),
short = showShortLink.value,
onShare = { chatModel.markShowingInvitationUsed() }
)
}
} else {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
CircularProgressIndicator(
Modifier.size(36.dp).padding(4.dp),
color = MaterialTheme.colors.secondary,
strokeWidth = 3.dp
)
}
}
SectionBottomSpacer()
}
}
@Composable
fun OneTimeLinkContent(connLinkInvitation: CreatedConnLink) {
val showShortLink = remember { mutableStateOf(true) }
@@ -165,8 +301,8 @@ private fun OneTimeLinkBar(connLinkInvitation: CreatedConnLink, short: Boolean)
val link = connLinkInvitation.simplexChatUri(short)
Surface(
shape = RoundedCornerShape(24.dp),
color = MaterialTheme.appColors.sentMessage,
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colors.background,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING)
@@ -1,5 +1,6 @@
package chat.simplex.common.views.newchat
import SectionBottomSpacer
import SectionTextFooter
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.*
@@ -186,6 +187,161 @@ fun AddGroupLayout(
fun canCreateProfile(displayName: String): Boolean = displayName.trim().isNotEmpty() && isValidDisplayName(displayName.trim())
@Composable
fun AddGroupViewSheet(
chatModel: ChatModel,
rh: RemoteHostInfo?,
profileImage: MutableState<String?>,
onEditImage: () -> Unit,
close: () -> Unit,
closeAll: () -> Unit
) {
val rhId = rh?.remoteHostId
val view = LocalMultiplatformView()
AddGroupSheetLayout(
createGroup = { incognito, groupProfile ->
hideKeyboard(view)
withBGApi {
val groupInfo = chatModel.controller.apiNewGroup(rhId, incognito, groupProfile)
if (groupInfo != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId = rhId, groupInfo)
openGroupChat(rhId, groupInfo.groupId)
}
setGroupMembers(rhId, groupInfo, chatModel)
closeAll.invoke()
if (!groupInfo.incognito) {
ModalManager.end.showModalCloseable(true) { close ->
AddGroupMembersView(rhId, groupInfo, creatingGroup = true, chatModel, close)
}
} else {
ModalManager.end.showModalCloseable(true) { close ->
GroupLinkView(chatModel, rhId, groupInfo, groupLink = null, onGroupLinkUpdated = null, creatingGroup = true, close)
}
}
}
}
},
incognitoPref = chatModel.controller.appPrefs.incognito,
profileImage = profileImage,
onEditImage = onEditImage
)
}
@Composable
fun AddGroupSheetLayout(
createGroup: (Boolean, GroupProfile) -> Unit,
incognitoPref: SharedPreference<Boolean>,
profileImage: MutableState<String?>,
onEditImage: () -> Unit
) {
val displayName = rememberSaveable { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
val incognito = remember { mutableStateOf(incognitoPref.get()) }
Column(
Modifier
.fillMaxWidth()
.wrapContentHeight()
.background(MaterialTheme.colors.background)
.imePadding()
.verticalScroll(rememberScrollState()),
) {
Text(
stringResource(MR.strings.create_secret_group_title),
Modifier
.fillMaxWidth()
.padding(top = DEFAULT_BIG_PADDING, bottom = DEFAULT_PADDING),
color = Color.Black,
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
style = MaterialTheme.typography.h1
)
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING * 3),
verticalAlignment = Alignment.CenterVertically
) {
Box(
Modifier
.weight(1f)
.padding(bottom = 24.dp),
contentAlignment = Alignment.Center
) {
Box(contentAlignment = Alignment.TopEnd) {
Box(contentAlignment = Alignment.Center) {
ProfileImage(108.dp, image = profileImage.value)
EditImageButton { onEditImage() }
}
if (profileImage.value != null) {
DeleteImageButton { profileImage.value = null }
}
}
}
Image(
painterResource(MR.images.ic_invitation_create_group),
contentDescription = null,
modifier = Modifier.weight(1f)
)
}
Row(Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(
stringResource(MR.strings.group_display_name_field),
fontSize = 16.sp
)
if (!isValidDisplayName(displayName.value.trim())) {
Spacer(Modifier.size(DEFAULT_PADDING_HALF))
IconButton({ showInvalidNameAlert(mkValidName(displayName.value.trim()), displayName) }, Modifier.size(20.dp)) {
Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error)
}
}
}
Box(Modifier.padding(horizontal = DEFAULT_PADDING)) {
ProfileNameField(displayName, "", { isValidDisplayName(it.trim()) }, focusRequester)
}
Spacer(Modifier.height(8.dp))
SettingsActionItem(
painterResource(MR.images.ic_check),
stringResource(MR.strings.create_group_button),
click = {
createGroup(
incognito.value, GroupProfile(
displayName = displayName.value.trim(),
fullName = "",
shortDescr = null,
image = profileImage.value,
groupPreferences = GroupPreferences(history = GroupPreference(GroupFeatureEnabled.ON))
)
)
},
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary,
disabled = !canCreateProfile(displayName.value)
)
IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } }
SectionTextFooter(
buildAnnotatedString {
append(sharedProfileInfo(chatModel, incognito.value))
append("\n")
append(annotatedStringResource(MR.strings.group_is_decentralized))
}
)
SectionBottomSpacer()
LaunchedEffect(Unit) {
delay(1000)
focusRequester.requestFocus()
}
}
}
@Preview
@Composable
fun PreviewAddGroupLayout() {
@@ -38,7 +38,10 @@ import chat.simplex.res.MR
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import java.net.URI
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) {
DisposableEffect(Unit) {
@@ -46,34 +49,105 @@ fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) {
connectProgressManager.cancelConnectProgress()
}
}
val oneHandUI = remember { appPrefs.oneHandUI.state }
var showNewChatViewSheet by remember { mutableStateOf(false) }
var showAddGroupSheet by remember { mutableStateOf(false) }
var newChatSheetSelection by remember { mutableStateOf(NewChatOption.INVITE) }
var newChatSheetShowQR by remember { mutableStateOf(false) }
val newChatSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
val sheetScope = rememberCoroutineScope()
val closeAll = { ModalManager.start.closeModals() }
// Image picker state for AddGroup sheet
val addGroupChosenImage = rememberSaveable { mutableStateOf<URI?>(null) }
val addGroupProfileImage = rememberSaveable { mutableStateOf<String?>(null) }
val imagePickerSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
Box {
val closeAll = { ModalManager.start.closeModals() }
Column(modifier = Modifier.fillMaxSize()) {
NewChatSheetLayout(
addContact = {
ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll) }
},
scanPaste = {
ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll) }
},
createGroup = {
ModalManager.start.showCustomModal { close -> AddGroupView(chatModel, chatModel.currentRemoteHost.value, close, closeAll) }
},
rh = rh,
close = close
)
LaunchedEffect(newChatSheetState.currentValue) {
if (newChatSheetState.currentValue == ModalBottomSheetValue.Hidden) {
if (showNewChatViewSheet) showNewChatViewSheet = false
if (showAddGroupSheet) {
showAddGroupSheet = false
addGroupChosenImage.value = null
addGroupProfileImage.value = null
}
}
if (oneHandUI.value) {
Column(Modifier.align(Alignment.BottomCenter)) {
DefaultAppBar(
navigationButton = { NavigationButtonBack(onButtonClicked = close) },
fixedTitleText = generalGetString(MR.strings.new_message),
onTop = false,
}
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
sheetState = imagePickerSheetState,
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp),
sheetContent = {
if (showAddGroupSheet) {
GetImageBottomSheet(
addGroupChosenImage,
onImageChange = { bitmap -> addGroupProfileImage.value = resizeImageToStrSize(cropToSquare(bitmap), maxDataSize = 12500) },
hideBottomSheet = { sheetScope.launch { imagePickerSheetState.hide() } }
)
} else {
Spacer(Modifier.height(1.dp))
}
}
) {
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
sheetState = newChatSheetState,
sheetShape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
sheetBackgroundColor = MaterialTheme.colors.secondaryVariant,
sheetContent = {
if (showNewChatViewSheet) {
ModalData().NewChatViewSheet(
rh = chatModel.currentRemoteHost.value,
selection = newChatSheetSelection,
showQRCodeScanner = newChatSheetShowQR,
close = { sheetScope.launch { newChatSheetState.hide() } }
)
} else if (showAddGroupSheet) {
AddGroupViewSheet(
chatModel = chatModel,
rh = chatModel.currentRemoteHost.value,
profileImage = addGroupProfileImage,
onEditImage = { sheetScope.launch { imagePickerSheetState.show() } },
close = { sheetScope.launch { newChatSheetState.hide() } },
closeAll = closeAll
)
} else {
Spacer(Modifier.height(1.dp))
}
}
) {
Box {
Column(modifier = Modifier.fillMaxSize()) {
NewChatSheetLayout(
addContact = {
newChatSheetSelection = NewChatOption.INVITE
newChatSheetShowQR = false
showNewChatViewSheet = true
sheetScope.launch { newChatSheetState.show() }
},
scanPaste = {
newChatSheetSelection = NewChatOption.CONNECT
newChatSheetShowQR = appPlatform.isAndroid
showNewChatViewSheet = true
sheetScope.launch { newChatSheetState.show() }
},
createGroup = {
showAddGroupSheet = true
sheetScope.launch { newChatSheetState.show() }
},
rh = rh,
close = close
)
}
if (oneHandUI.value) {
Column(Modifier.align(Alignment.BottomCenter)) {
DefaultAppBar(
navigationButton = { NavigationButtonBack(onButtonClicked = close) },
fixedTitleText = generalGetString(MR.strings.new_message),
onTop = false,
)
}
}
}
}
}
@@ -96,6 +170,7 @@ fun chatContactType(chat: Chat): ContactType {
else -> ContactType.UNLISTED
}
}
else -> ContactType.UNLISTED
}
}
@@ -122,7 +197,7 @@ private fun ModalData.NewChatSheetLayout(
val total = listState.layoutInfo.totalItemsCount
if (prevIndex == 0 && prevOffset == 0) return@LaunchedEffect
snapshotFlow { listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
.filter { it == 0 to 0 }
.filter { it == 0 to 0 }
.collect {
if (total <= listState.layoutInfo.totalItemsCount) {
listState.scrollToItem(prevIndex, prevOffset)
@@ -163,7 +238,6 @@ private fun ModalData.NewChatSheetLayout(
previousIndex = currentIndex
previousScrollOffset = currentScrollOffset
}
val filteredContactChats = filteredContactChats(
showUnreadAndFavorites = showUnreadAndFavorites,
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
@@ -171,13 +245,11 @@ private fun ModalData.NewChatSheetLayout(
searchText = searchText.value.text,
contactChats = allChats
)
val sectionModifier = Modifier.fillMaxWidth()
val deletedContactTypes = listOf(ContactType.CHAT_DELETED)
val deletedChats by remember(chatModel.chats.value, deletedContactTypes) {
derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) }
}
val actionButtonsOriginal = listOf(
Triple(
painterResource(MR.images.ic_add_link),
@@ -485,7 +557,6 @@ private fun ContactsSearchBar(
CIFileViewScope.progressIndicator(sizeMultiplier = 0.75f)
}
}
val hasText = remember { derivedStateOf { searchText.value.text.isNotEmpty() } }
if (hasText.value) {
val hideSearchOnBack: () -> Unit = { searchText.value = TextFieldValue() }
@@ -626,7 +697,6 @@ private val chatsByTypeComparator = Comparator<Chat> { chat1, chat2 ->
when {
chat1Type.ordinal < chat2Type.ordinal -> -1
chat1Type.ordinal > chat2Type.ordinal -> 1
else -> chat2.chatInfo.chatTs.compareTo(chat1.chatInfo.chatTs)
}
}
@@ -23,7 +23,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
@@ -98,9 +97,10 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC
}
}
val tabTitles = NewChatOption.values().map {
when(it) {
when (it) {
NewChatOption.INVITE ->
stringResource(MR.strings.one_time_link_short)
NewChatOption.CONNECT ->
stringResource(MR.strings.connect_via_link)
}
@@ -155,6 +155,7 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC
NewChatOption.INVITE.ordinal -> {
PrepareAndInviteView(rh?.remoteHostId, contactConnection, connLinkInvitation, creatingConnReq)
}
NewChatOption.CONNECT.ordinal -> {
ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close)
}
@@ -166,6 +167,124 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC
}
}
@Composable
fun ModalData.NewChatViewSheet(rh: RemoteHostInfo?, selection: NewChatOption, showQRCodeScanner: Boolean = false, close: () -> Unit) {
val selection = remember { stateGetOrPut("selection") { selection } }
val showQRCodeScanner = remember { stateGetOrPut("showQRCodeScanner") { showQRCodeScanner } }
val contactConnection: MutableState<PendingContactConnection?> = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(chatModel.showingInvitation.value?.conn) }
val connLinkInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connLink ?: CreatedConnLink("", null) } }
val creatingConnReq = rememberSaveable { mutableStateOf(false) }
val pastedLink = rememberSaveable { mutableStateOf("") }
LaunchedEffect(selection.value) {
if (
selection.value == NewChatOption.INVITE
&& connLinkInvitation.connFullLink.isEmpty()
&& contactConnection.value == null
&& !creatingConnReq.value
) {
createInvitation(rh?.remoteHostId, creatingConnReq, connLinkInvitation, contactConnection)
}
}
DisposableEffect(Unit) {
onDispose {
if (chatModel.showingInvitation.value != null) {
val conn = contactConnection.value
if (chatModel.showingInvitation.value?.connChatUsed == false && conn != null) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.keep_unused_invitation_question),
text = generalGetString(MR.strings.you_can_view_invitation_link_again),
confirmText = generalGetString(MR.strings.delete_verb),
dismissText = generalGetString(MR.strings.keep_invitation_link),
destructive = true,
onConfirm = {
withBGApi {
val chatInfo = ChatInfo.ContactConnection(conn)
controller.deleteChat(Chat(remoteHostId = rh?.remoteHostId, chatInfo = chatInfo, chatItems = listOf()))
if (chatModel.chatId.value == chatInfo.id) {
chatModel.chatId.value = null
ModalManager.start.closeModals()
}
}
}
)
}
chatModel.showingInvitation.value = null
}
}
}
val tabTitles = NewChatOption.values().map {
when (it) {
NewChatOption.INVITE ->
stringResource(MR.strings.one_time_link_short)
NewChatOption.CONNECT ->
stringResource(MR.strings.connect_via_link)
}
}
Column(
Modifier
.fillMaxWidth()
.wrapContentHeight()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(Modifier.height(DEFAULT_PADDING))
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(
initialPage = selection.value.ordinal,
initialPageOffsetFraction = 0f
) { NewChatOption.values().size }
KeyChangeEffect(pagerState.currentPage) {
selection.value = NewChatOption.values()[pagerState.currentPage]
}
TabRow(
selectedTabIndex = pagerState.currentPage,
backgroundColor = Color.Transparent,
contentColor = MaterialTheme.colors.primary,
) {
tabTitles.forEachIndexed { index, it ->
LeadingIconTab(
selected = pagerState.currentPage == index,
onClick = {
scope.launch {
pagerState.animateScrollToPage(index)
}
},
text = { Text(it, fontSize = 13.sp) },
icon = {
Icon(
if (NewChatOption.INVITE.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_qr_code),
it
)
},
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = MaterialTheme.colors.secondary,
)
}
}
HorizontalPager(state = pagerState, Modifier, verticalAlignment = Alignment.Top, userScrollEnabled = appPlatform.isAndroid) { index ->
Column(
Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.Top
) {
Spacer(Modifier.height(DEFAULT_PADDING))
when (index) {
NewChatOption.INVITE.ordinal -> {
PrepareAndInviteView(rh?.remoteHostId, contactConnection, connLinkInvitation, creatingConnReq)
}
NewChatOption.CONNECT.ordinal -> {
ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close)
}
}
SectionBottomSpacer()
}
}
}
}
@Composable
private fun PrepareAndInviteView(rhId: Long?, contactConnection: MutableState<PendingContactConnection?>, connLinkInvitation: CreatedConnLink, creatingConnReq: MutableState<Boolean>) {
if (connLinkInvitation.connFullLink.isNotEmpty()) {
@@ -233,7 +352,8 @@ private fun ProfilePickerOption(
Text(title, modifier = Modifier.align(Alignment.CenterVertically))
if (onInfo != null) {
Spacer(Modifier.padding(6.dp))
Column(Modifier
Column(
Modifier
.size(48.dp)
.clip(CircleShape)
.clickable(
@@ -288,7 +408,6 @@ fun ActiveProfilePicker(
val filteredProfiles = remember(searchTextOrPassword.value) {
filteredProfiles(chatModel.users.map { it.user }.sortedBy { !it.activeUser }, searchTextOrPassword.value)
}
var progressByTimeout by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(switchingProfile.value) {
@@ -333,8 +452,10 @@ fun ActiveProfilePicker(
)
if (chatModel.currentUser.value?.userId != user.userId) {
AlertManager.shared.showAlertMsg(generalGetString(
MR.strings.switching_profile_error_title),
AlertManager.shared.showAlertMsg(
generalGetString(
MR.strings.switching_profile_error_title
),
String.format(generalGetString(MR.strings.switching_profile_error_message), user.chatViewName)
)
}
@@ -352,7 +473,7 @@ fun ActiveProfilePicker(
}
}
},
image = { ProfileImage(size = 42.dp, image = user.image) }
image = { ProfileImage(size = 42.dp, image = user.image) }
)
}
@@ -389,7 +510,7 @@ fun ActiveProfilePicker(
)
}
BoxWithConstraints {
Box {
Column(
Modifier
.fillMaxSize()
@@ -414,10 +535,12 @@ fun ActiveProfilePicker(
when {
!showIncognito ->
ProfilePickerUserOption(activeProfile)
incognito -> {
IncognitoUserOption()
ProfilePickerUserOption(activeProfile)
}
else -> {
ProfilePickerUserOption(activeProfile)
IncognitoUserOption()
@@ -453,34 +576,56 @@ fun ActiveProfilePicker(
private fun InviteView(rhId: Long?, connLinkInvitation: CreatedConnLink, contactConnection: MutableState<PendingContactConnection?>) {
val showShortLink = remember { mutableStateOf(true) }
Column(modifier = Modifier.fillMaxWidth()) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.TopCenter
) {
Image(
painterResource(MR.images.ic_invitation_one_time_link),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.size(100.dp)
.align(Alignment.CenterHorizontally)
.size(160.dp)
)
}
Spacer(Modifier.height(DEFAULT_PADDING))
SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) {
LinkTextView(connLinkInvitation.simplexChatUri(short = showShortLink.value), true)
SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 8.dp) {
Surface(
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colors.background,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING)
) {
LinkTextView(connLinkInvitation.simplexChatUri(short = showShortLink.value), true)
}
}
Spacer(Modifier.height(DEFAULT_PADDING))
SectionViewWithButton(
stringResource(MR.strings.or_show_this_qr_code).uppercase(),
titleButton = if (connLinkInvitation.connShortLink != null) {{ ToggleShortLinkButton(showShortLink) }} else null
headerBottomPadding = 8.dp,
titleButton = if (connLinkInvitation.connShortLink != null) {
{ ToggleShortLinkButton(showShortLink) }
} else null
) {
SimpleXCreatedLinkQRCode(connLinkInvitation, short = showShortLink.value, onShare = { chatModel.markShowingInvitationUsed() })
Surface(
shape = RoundedCornerShape(18.dp),
color = MaterialTheme.colors.background,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING)
) {
SimpleXCreatedLinkQRCode(
connLink = connLinkInvitation,
short = showShortLink.value,
padding = PaddingValues(vertical = DEFAULT_PADDING),
onShare = { chatModel.markShowingInvitationUsed() }
)
}
}
Spacer(Modifier.height(DEFAULT_PADDING))
val incognito by remember(chatModel.showingInvitation.value?.conn?.incognito, controller.appPrefs.incognito.get()) {
derivedStateOf {
@@ -490,56 +635,64 @@ private fun InviteView(rhId: Long?, connLinkInvitation: CreatedConnLink, contact
val currentUser = remember { chatModel.currentUser }.value
if (currentUser != null) {
SectionView(stringResource(MR.strings.new_chat_share_profile).uppercase(), headerBottomPadding = 5.dp) {
SectionItemView(
padding = PaddingValues(
top = 0.dp,
bottom = 0.dp,
start = 16.dp,
end = 16.dp
),
click = {
ModalManager.start.showCustomModal(keyboardCoversBar = false) { close ->
val search = rememberSaveable { mutableStateOf("") }
ModalView(
{ close() },
showSearch = true,
searchAlwaysVisible = true,
onSearchValueChanged = { search.value = it },
content = {
ActiveProfilePicker(
search = search,
close = close,
rhId = rhId,
contactConnection = contactConnection.value
)
})
}
}
SectionView(stringResource(MR.strings.new_chat_share_profile).uppercase(), headerBottomPadding = 8.dp) {
Surface(
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colors.background,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING)
) {
if (incognito) {
Spacer(Modifier.width(8.dp))
Icon(
painterResource(MR.images.ic_theater_comedy_filled),
contentDescription = stringResource(MR.strings.incognito),
tint = Indigo,
modifier = Modifier.size(32.dp)
)
Spacer(Modifier.width(2.dp))
} else {
ProfileImage(size = 42.dp, image = currentUser.image)
}
TextIconSpaced(false)
Text(
text = if (incognito) stringResource(MR.strings.incognito) else currentUser.chatViewName,
color = MaterialTheme.colors.onBackground
)
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.End) {
Icon(
painter = painterResource(MR.images.ic_arrow_forward_ios),
contentDescription = stringResource(MR.strings.new_chat_share_profile),
tint = MaterialTheme.colors.secondary,
SectionItemView(
padding = PaddingValues(
top = 0.dp,
bottom = 0.dp,
start = 16.dp,
end = 16.dp
),
click = {
ModalManager.start.showCustomModal(keyboardCoversBar = false) { close ->
val search = rememberSaveable { mutableStateOf("") }
ModalView(
{ close() },
showSearch = true,
searchAlwaysVisible = true,
onSearchValueChanged = { search.value = it },
content = {
ActiveProfilePicker(
search = search,
close = close,
rhId = rhId,
contactConnection = contactConnection.value
)
})
}
}
) {
if (incognito) {
Spacer(Modifier.width(8.dp))
Icon(
painterResource(MR.images.ic_theater_comedy_filled),
contentDescription = stringResource(MR.strings.incognito),
tint = Indigo,
modifier = Modifier.size(32.dp)
)
Spacer(Modifier.width(2.dp))
} else {
ProfileImage(size = 42.dp, image = currentUser.image)
}
TextIconSpaced(false)
Text(
text = if (incognito) stringResource(MR.strings.incognito) else currentUser.chatViewName,
color = MaterialTheme.colors.onBackground
)
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.End) {
Icon(
painter = painterResource(MR.images.ic_arrow_forward_ios),
contentDescription = stringResource(MR.strings.new_chat_share_profile),
tint = MaterialTheme.colors.secondary,
)
}
}
}
}
@@ -599,30 +752,39 @@ private fun ConnectView(rhId: Long?, showQRCodeScanner: MutableState<Boolean>, p
connectProgressManager.cancelConnectProgress()
}
}
Column(modifier = Modifier.fillMaxWidth()) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.TopCenter
) {
Image(
painterResource(MR.images.ic_invitation_connect_link),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.size(100.dp)
.padding(horizontal = DEFAULT_PADDING)
.align(Alignment.CenterHorizontally)
.size(160.dp)
)
}
Spacer(Modifier.height(DEFAULT_PADDING))
SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase(), headerBottomPadding = 5.dp) {
PasteLinkView(rhId, pastedLink, showQRCodeScanner, close)
SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase(), headerBottomPadding = 8.dp) {
Surface(
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colors.background,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING)
) {
PasteLinkView(rhId, pastedLink, showQRCodeScanner, close)
}
}
if (appPlatform.isAndroid) {
Spacer(Modifier.height(10.dp))
Spacer(Modifier.height(DEFAULT_PADDING))
SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase(), headerBottomPadding = 5.dp) {
QRCodeScanner(showQRCodeScanner) { text ->
SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase(), headerBottomPadding = 8.dp) {
QRCodeScanner(
showQRCodeScanner = showQRCodeScanner,
clipShape = RoundedCornerShape(12.dp),
padding = PaddingValues(horizontal = DEFAULT_PADDING)
) { text ->
val linkVerified = verifyOnly(text)
if (!linkVerified) {
AlertManager.shared.showAlertMsg(
@@ -655,7 +817,10 @@ private fun PasteLinkView(rhId: Long?, pastedLink: MutableState<String>, showQRC
}
}) {
Box(Modifier.weight(1f)) {
Text(stringResource(MR.strings.tap_to_paste_link))
Text(
text = stringResource(MR.strings.tap_to_paste_link),
color = Color.LightGray,
)
}
if (connectProgressManager.showConnectProgress != null) {
CIFileViewScope.progressIndicator(sizeMultiplier = 0.6f)
@@ -686,7 +851,7 @@ fun LinkTextView(link: String, share: Boolean) {
}) {
BasicTextField(
value = link,
onValueChange = { },
onValueChange = { },
enabled = false,
textStyle = TextStyle(fontSize = 16.sp, color = MaterialTheme.colors.onBackground),
singleLine = true,