android, desktop: offer to create 1-time link on address views (#5253)

This commit is contained in:
spaced4ndy
2024-11-27 11:16:22 +04:00
committed by GitHub
parent 8c1abcccfb
commit 15fae29e5b
5 changed files with 187 additions and 46 deletions
@@ -174,7 +174,7 @@ private fun ModalData.NewChatSheetLayout(
val actionButtonsOriginal = listOf(
Triple(
painterResource(MR.images.ic_add_link),
stringResource(MR.strings.add_contact_tab),
stringResource(MR.strings.create_1_time_link),
addContact,
),
Triple(
@@ -98,7 +98,7 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC
val tabTitles = NewChatOption.values().map {
when(it) {
NewChatOption.INVITE ->
stringResource(MR.strings.add_contact_tab)
stringResource(MR.strings.one_time_link_short)
NewChatOption.CONNECT ->
stringResource(MR.strings.connect_via_link)
}
@@ -3,29 +3,57 @@ package chat.simplex.common.views.usersettings
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.platform.ColumnWithScrollBar
import chat.simplex.common.platform.chatModel
import chat.simplex.common.ui.theme.DEFAULT_PADDING
import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF
import dev.icerock.moko.resources.compose.stringResource
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.onboarding.ReadableText
import chat.simplex.common.views.onboarding.ReadableTextWithLink
import chat.simplex.common.views.newchat.*
import chat.simplex.common.views.onboarding.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.painterResource
@Composable
fun UserAddressLearnMore(showCreateAddressButton: Boolean = false) {
ColumnWithScrollBar(Modifier .padding(horizontal = DEFAULT_PADDING)) {
AppBarTitle(stringResource(MR.strings.simplex_address), withPadding = false)
ReadableText(MR.strings.you_can_share_your_address)
AppBarTitle(stringResource(MR.strings.address_or_1_time_link), withPadding = false)
Row {
Icon(painterResource(MR.images.ic_mail), null, tint = MaterialTheme.colors.secondary)
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
ReadableText(MR.strings.share_address_publicly, style = MaterialTheme.typography.h3.copy(fontWeight = FontWeight.Bold))
}
ReadableText(MR.strings.share_simplex_address_on_social_media)
ReadableText(MR.strings.you_wont_lose_your_contacts_if_delete_address)
ReadableText(MR.strings.you_can_accept_or_reject_connection)
ReadableTextWithLink(MR.strings.read_more_in_user_guide_with_link, "https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address")
Row(Modifier.padding(top = DEFAULT_PADDING_HALF)) {
Icon(painterResource(MR.images.ic_add_link), null, tint = MaterialTheme.colors.secondary)
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
ReadableText(MR.strings.share_1_time_link_with_a_friend, style = MaterialTheme.typography.h3.copy(fontWeight = FontWeight.Bold))
}
ReadableText(MR.strings.one_time_link_can_be_used_with_one_contact_only)
ReadableText(MR.strings.you_can_set_connection_name_to_remember)
if (!showCreateAddressButton) {
Row(Modifier.padding(top = DEFAULT_PADDING_HALF)) {
Icon(painterResource(MR.images.ic_shield), null, tint = MaterialTheme.colors.secondary)
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
ReadableText(MR.strings.connection_security, style = MaterialTheme.typography.h3.copy(fontWeight = FontWeight.Bold))
}
ReadableText(MR.strings.simplex_address_and_1_time_links_are_safe_to_share)
ReadableText(MR.strings.to_protect_against_your_link_replaced_compare_codes)
ReadableTextWithLink(MR.strings.read_more_in_user_guide_with_link, "https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses")
}
if (showCreateAddressButton) {
Spacer(Modifier.weight(1f))
@@ -33,7 +61,7 @@ fun UserAddressLearnMore(showCreateAddressButton: Boolean = false) {
Button(
onClick = {
ModalManager.start.showModalCloseable { close ->
UserAddressView(chatModel = chatModel, shareViaProfile = false, autoCreateAddress = true, close = close)
UserAddressView(chatModel = chatModel, shareViaProfile = false, autoCreateAddress = true, close = { ModalManager.start.closeModals() })
}
},
shape = CircleShape,
@@ -42,6 +70,24 @@ fun UserAddressLearnMore(showCreateAddressButton: Boolean = false) {
) {
Text(stringResource(MR.strings.create_simplex_address), style = MaterialTheme.typography.h2, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Medium)
}
val closeAll = { ModalManager.start.closeModals() }
TextButton(
onClick = {
ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ ->
NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll)
}
},
Modifier.padding(top = DEFAULT_PADDING, bottom = DEFAULT_PADDING * 2).clip(CircleShape)
) {
Text(
stringResource(MR.strings.create_1_time_link),
Modifier.padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING_HALF, bottom = 5.dp),
color = MaterialTheme.colors.primary,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center
)
}
}
}
}
@@ -31,7 +31,6 @@ import chat.simplex.res.MR
@Composable
fun UserAddressView(
chatModel: ChatModel,
viaCreateLinkView: Boolean = false,
shareViaProfile: Boolean = false,
autoCreateAddress: Boolean = false,
close: () -> Unit
@@ -39,7 +38,6 @@ fun UserAddressView(
// TODO close when remote host changes
val shareViaProfile = remember { mutableStateOf(shareViaProfile) }
var progressIndicator by remember { mutableStateOf(false) }
val onCloseHandler: MutableState<(close: () -> Unit) -> Unit> = remember { mutableStateOf({ _ -> }) }
val user = remember { chatModel.currentUser }
KeyChangeEffect(user.value?.remoteHostId, user.value?.userId) {
close()
@@ -82,7 +80,7 @@ fun UserAddressView(
}
LaunchedEffect(autoCreateAddress) {
if (autoCreateAddress) {
if (chatModel.userAddress.value == null && autoCreateAddress) {
createAddress()
}
}
@@ -94,7 +92,6 @@ fun UserAddressView(
user = user.value,
userAddress = userAddress.value,
shareViaProfile,
onCloseHandler,
createAddress = { createAddress() },
learnMore = {
ModalManager.start.showModal {
@@ -105,7 +102,7 @@ fun UserAddressView(
sendEmail = { userAddress ->
uriHandler.sendEmail(
generalGetString(MR.strings.email_invite_subject),
generalGetString(MR.strings.email_invite_body).format(simplexChatLink( userAddress.connReqContact))
generalGetString(MR.strings.email_invite_body).format(simplexChatLink(userAddress.connReqContact))
)
},
setProfileAddress = ::setProfileAddress,
@@ -141,12 +138,8 @@ fun UserAddressView(
)
}
if (viaCreateLinkView) {
ModalView(close = close) {
showLayout()
} else {
ModalView(close = { onCloseHandler.value(close) }) {
showLayout()
}
}
if (progressIndicator) {
@@ -173,7 +166,6 @@ private fun UserAddressLayout(
user: User?,
userAddress: UserContactLinkRec?,
shareViaProfile: MutableState<Boolean>,
onCloseHandler: MutableState<(close: () -> Unit) -> Unit>,
createAddress: () -> Unit,
learnMore: () -> Unit,
share: (String) -> Unit,
@@ -190,45 +182,41 @@ private fun UserAddressLayout(
verticalArrangement = Arrangement.SpaceEvenly
) {
if (userAddress == null) {
SectionView {
SectionView(generalGetString(MR.strings.for_social_media).uppercase()) {
CreateAddressButton(createAddress)
SectionTextFooter(stringResource(MR.strings.create_address_and_let_people_connect))
}
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.or_to_share_privately).uppercase()) {
CreateOneTimeLinkButton()
}
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
SectionView {
LearnMoreButton(learnMore)
}
LaunchedEffect(Unit) {
onCloseHandler.value = { close -> close() }
}
} else {
val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) }
val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) }
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
SectionView(stringResource(MR.strings.for_social_media).uppercase()) {
SimpleXLinkQRCode(userAddress.connReqContact)
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
ShareViaEmailButton { sendEmail(userAddress) }
ShareWithContactsButton(shareViaProfile, setProfileAddress)
AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) }
LearnMoreButton(learnMore)
// ShareViaEmailButton { sendEmail(userAddress) }
AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAas)
}
if (autoAcceptState.value.enable) {
SectionDividerSpaced()
AutoAcceptSection(autoAcceptState, autoAcceptStateSaved, saveAas)
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.or_to_share_privately).uppercase()) {
CreateOneTimeLinkButton()
}
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
LearnMoreButton(learnMore)
}
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
DeleteAddressButton(deleteAddress)
SectionTextFooter(stringResource(MR.strings.your_contacts_will_remain_connected))
}
LaunchedEffect(Unit) {
onCloseHandler.value = { close ->
if (autoAcceptState.value == autoAcceptStateSaved.value) close()
else showUnsavedChangesAlert({ saveAas(autoAcceptState.value, autoAcceptStateSaved); close() }, close)
}
}
}
}
SectionBottomSpacer()
@@ -246,11 +234,27 @@ private fun CreateAddressButton(onClick: () -> Unit) {
)
}
@Composable
private fun CreateOneTimeLinkButton() {
val closeAll = { ModalManager.start.closeModals() }
SettingsActionItem(
painterResource(MR.images.ic_add_link),
stringResource(MR.strings.create_1_time_link),
click = {
ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ ->
NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll)
}
},
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
}
@Composable
private fun LearnMoreButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_info),
stringResource(MR.strings.learn_more_about_address),
stringResource(MR.strings.simplex_address_or_1_time_link),
onClick,
)
}
@@ -266,6 +270,85 @@ fun ShareViaEmailButton(onClick: () -> Unit) {
)
}
@Composable
private fun AddressSettingsButton(
user: User?,
userAddress: UserContactLinkRec,
shareViaProfile: MutableState<Boolean>,
setProfileAddress: (Boolean) -> Unit,
saveAas: (AutoAcceptState, MutableState<AutoAcceptState>) -> Unit,
) {
SettingsActionItem(
painterResource(MR.images.ic_settings),
stringResource(MR.strings.address_settings),
click = {
ModalManager.start.showCustomModal { close ->
UserAddressSettings(user, userAddress, shareViaProfile, setProfileAddress, saveAas, close = close)
}
}
)
}
@Composable
private fun ModalData.UserAddressSettings(
user: User?,
userAddress: UserContactLinkRec,
shareViaProfile: MutableState<Boolean>,
setProfileAddress: (Boolean) -> Unit,
saveAas: (AutoAcceptState, MutableState<AutoAcceptState>) -> Unit,
close: () -> Unit
) {
val autoAcceptState = remember { stateGetOrPut("autoAcceptState") { (AutoAcceptState(userAddress)) } }
val autoAcceptStateSaved = remember { stateGetOrPut("autoAcceptStateSaved") { (autoAcceptState.value) } }
fun onClose(close: () -> Unit): Boolean = if (autoAcceptState.value == autoAcceptStateSaved.value) {
chatModel.centerPanelBackgroundClickHandler = null
close()
false
} else {
showUnsavedChangesAlert(
save = {
saveAas(autoAcceptState.value, autoAcceptStateSaved)
chatModel.centerPanelBackgroundClickHandler = null
close()
},
revert = {
chatModel.centerPanelBackgroundClickHandler = null
close()
}
)
true
}
LaunchedEffect(Unit) {
// Enables unsaved changes alert on this view.
chatModel.centerPanelBackgroundClickHandler = {
onClose(close = { ModalManager.start.closeModals() })
}
}
ModalView(close = { onClose(close) }) {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.address_settings), hostDevice(user?.remoteHostId))
Column(
Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) {
SectionView {
ShareWithContactsButton(shareViaProfile, setProfileAddress)
AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) }
}
if (autoAcceptState.value.enable) {
SectionDividerSpaced()
AutoAcceptSection(autoAcceptState, autoAcceptStateSaved, saveAas)
}
}
}
}
}
@Composable
fun ShareWithContactsButton(shareViaProfile: MutableState<Boolean>, setProfileAddress: (Boolean) -> Unit) {
PreferenceToggleWithIcon(
@@ -441,7 +524,6 @@ fun PreviewUserAddressLayoutNoAddress() {
setProfileAddress = { _ -> },
learnMore = {},
shareViaProfile = remember { mutableStateOf(false) },
onCloseHandler = remember { mutableStateOf({}) },
sendEmail = {},
)
}
@@ -475,7 +557,6 @@ fun PreviewUserAddressLayoutAddressCreated() {
setProfileAddress = { _ -> },
learnMore = {},
shareViaProfile = remember { mutableStateOf(false) },
onCloseHandler = remember { mutableStateOf({}) },
sendEmail = {},
)
}
@@ -675,10 +675,19 @@
<string name="if_you_cant_meet_in_person">If you can\'t meet in person, show QR code in a video call, or share the link.</string>
<!-- User Address Learn More - UserAddressLearnMore.kt -->
<string name="share_address_publicly">Share address publicly</string>
<string name="share_simplex_address_on_social_media">Share SimpleX address on social media.</string>
<string name="you_can_share_your_address">You can share your address as a link or QR code - anybody can connect to you.</string>
<string name="you_wont_lose_your_contacts_if_delete_address">You won\'t lose your contacts if you later delete your address.</string>
<string name="share_1_time_link_with_a_friend">Share 1-time link with a friend</string>
<string name="one_time_link_can_be_used_with_one_contact_only"><![CDATA[1-time link can be used <i>with one contact only</i> - share in person or via any messenger.]]></string>
<string name="you_can_set_connection_name_to_remember">You can set connection name, to remember who the link was shared with.</string>
<string name="connection_security">Connection security</string>
<string name="simplex_address_and_1_time_links_are_safe_to_share">SimpleX address and 1-time links are safe to share via any messenger.</string>
<string name="to_protect_against_your_link_replaced_compare_codes">To protect against your link being replaced, you can compare contact security codes.</string>
<string name="you_can_accept_or_reject_connection">When people request to connect, you can accept or reject it.</string>
<string name="read_more_in_user_guide_with_link"><![CDATA[Read more in <font color="#0088ff">User Guide</font>.]]></string>
<string name="address_or_1_time_link">Address or 1-time link?</string>
<!-- PasteToConnect.kt -->
<string name="connect_via_link">Connect via link</string>
@@ -917,6 +926,11 @@
<string name="invite_friends">Invite friends</string>
<string name="email_invite_subject">Let\'s talk in SimpleX Chat</string>
<string name="email_invite_body">Hi!\nConnect to me via SimpleX Chat: %s</string>
<string name="for_social_media">For social media</string>
<string name="or_to_share_privately">Or to share privately</string>
<string name="simplex_address_or_1_time_link">SimpleX address or 1-time link?</string>
<string name="create_1_time_link">Create 1-time link</string>
<string name="address_settings">Address settings</string>
<!-- CreateSimpleXAddress.kt -->
<string name="continue_to_next_step">Continue</string>