diff --git a/apps/multiplatform/.idea/codeStyles/Project.xml b/apps/multiplatform/.idea/codeStyles/Project.xml index a847d12689..78bc0449db 100644 --- a/apps/multiplatform/.idea/codeStyles/Project.xml +++ b/apps/multiplatform/.idea/codeStyles/Project.xml @@ -148,4 +148,4 @@ - \ No newline at end of file + diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 875b74680c..3a10ab4d09 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -151,45 +151,120 @@ fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow 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, listState: LazyListState, onConnectClick: () -> Unit) { +private fun BoxScope.ChatListWithLoadingScreen(searchText: MutableState, 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, listState: LazyListState) { +private fun BoxScope.ChatList(searchText: MutableState, 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, 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) { } @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() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/ConnectBannerCard.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/ConnectBannerCard.kt index 3bef4e034d..c8307d22ce 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/ConnectBannerCard.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/ConnectBannerCard.kt @@ -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 = {}) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/InviteSomeoneWithPicturesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/InviteSomeoneWithPicturesView.kt index da24045654..aa043b945d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/InviteSomeoneWithPicturesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/InviteSomeoneWithPicturesView.kt @@ -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() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/NowYouCanView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/NowYouCanView.kt index 7b75c4ecb3..75fbc58a95 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/NowYouCanView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/NowYouCanView.kt @@ -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 = {}) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/OneTimeLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/OneTimeLinkView.kt index ca5d7b80a8..6cf2cfb492 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/OneTimeLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/invitation_redesign/OneTimeLinkView.kt @@ -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 = 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) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index 5bc7a47af2..9cb13fd8eb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -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, + 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, + profileImage: MutableState, + 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() { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index ef6e426141..98b75c6267 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -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(null) } + val addGroupProfileImage = rememberSaveable { mutableStateOf(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 { chat1, chat2 -> when { chat1Type.ordinal < chat2Type.ordinal -> -1 chat1Type.ordinal > chat2Type.ordinal -> 1 - else -> chat2.chatInfo.chatTs.compareTo(chat1.chatInfo.chatTs) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 82a1bee146..a7e5f60644 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -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 = 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, connLinkInvitation: CreatedConnLink, creatingConnReq: MutableState) { 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) { 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, 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, 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,