From 8b80efd5371610ced5f4c02950a8ba1b5feb8888 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 3 May 2023 11:42:43 +0300 Subject: [PATCH] android: contact address UX (#2363) * android: contact address UX * unneeded block * review changes --- .../java/chat/simplex/app/model/ChatModel.kt | 12 +- .../java/chat/simplex/app/model/SimpleXAPI.kt | 20 +- .../simplex/app/views/chat/ChatInfoView.kt | 27 +- .../app/views/chat/group/GroupChatInfoView.kt | 2 + .../views/chat/group/GroupMemberInfoView.kt | 16 +- .../views/chat/group/WelcomeMessageView.kt | 82 +++- .../app/views/chatlist/ChatHelpView.kt | 14 +- .../simplex/app/views/helpers/TextEditor.kt | 156 +++++-- .../app/views/newchat/AddContactLearnMore.kt | 23 + .../app/views/newchat/AddContactView.kt | 137 +++--- .../newchat/ContactConnectionInfoView.kt | 49 +- .../app/views/newchat/CreateLinkView.kt | 7 +- .../app/views/newchat/PasteToConnect.kt | 2 +- .../app/views/onboarding/HowItWorks.kt | 48 +- .../views/usersettings/AcceptRequestsView.kt | 160 ------- .../views/usersettings/ProtocolServerView.kt | 20 +- .../app/views/usersettings/RTCServers.kt | 2 +- .../app/views/usersettings/SettingsView.kt | 2 +- .../usersettings/UserAddressLearnMore.kt | 24 + .../app/views/usersettings/UserAddressView.kt | 437 +++++++++++++++--- .../app/src/main/res/drawable/ic_person.xml | 9 + .../app/src/main/res/values-ar/strings.xml | 1 - .../app/src/main/res/values-cs/strings.xml | 7 - .../app/src/main/res/values-de/strings.xml | 8 - .../app/src/main/res/values-es/strings.xml | 7 - .../app/src/main/res/values-fr/strings.xml | 7 - .../app/src/main/res/values-hi/strings.xml | 4 - .../app/src/main/res/values-it/strings.xml | 7 - .../app/src/main/res/values-ja/strings.xml | 7 - .../app/src/main/res/values-ko/strings.xml | 5 - .../app/src/main/res/values-lt/strings.xml | 1 - .../app/src/main/res/values-nl/strings.xml | 7 - .../app/src/main/res/values-pl/strings.xml | 7 - .../src/main/res/values-pt-rBR/strings.xml | 7 - .../app/src/main/res/values-ru/strings.xml | 10 +- .../src/main/res/values-zh-rCN/strings.xml | 7 - .../src/main/res/values-zh-rTW/strings.xml | 7 - .../app/src/main/res/values/strings.xml | 48 +- 38 files changed, 884 insertions(+), 512 deletions(-) create mode 100644 apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactLearnMore.kt delete mode 100644 apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AcceptRequestsView.kt create mode 100644 apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressLearnMore.kt create mode 100644 apps/android/app/src/main/res/drawable/ic_person.xml diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 8947c782fd..c6fb1ae2c2 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -461,6 +461,8 @@ data class User( val showNotifications: Boolean = activeUser || showNtfs + val addressShared: Boolean = profile.contactLink != null + companion object { val sampleData = User( userId = 1, @@ -734,6 +736,7 @@ data class Contact( override val displayName get() = localAlias.ifEmpty { profile.displayName } override val fullName get() = profile.fullName override val image get() = profile.image + val contactLink: String? = profile.contactLink override val localAlias get() = profile.localAlias val verified get() = activeConn.connectionCode != null @@ -814,6 +817,7 @@ data class Profile( override val fullName: String, override val image: String? = null, override val localAlias : String = "", + val contactLink: String? = null, val preferences: ChatPreferences? = null ): NamedChat { val profileViewName: String @@ -821,7 +825,7 @@ data class Profile( return if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)" } - fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, image, localAlias, preferences) + fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, image, localAlias, contactLink, preferences) companion object { val sampleData = Profile( @@ -832,17 +836,18 @@ data class Profile( } @Serializable -class LocalProfile( +data class LocalProfile( val profileId: Long, override val displayName: String, override val fullName: String, override val image: String? = null, override val localAlias: String, + val contactLink: String? = null, val preferences: ChatPreferences? = null ): NamedChat { val profileViewName: String = localAlias.ifEmpty { if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)" } - fun toProfile(): Profile = Profile(displayName, fullName, image, localAlias, preferences) + fun toProfile(): Profile = Profile(displayName, fullName, image, localAlias, contactLink, preferences) companion object { val sampleData = LocalProfile( @@ -952,6 +957,7 @@ data class GroupMember ( val displayName: String get() = memberProfile.localAlias.ifEmpty { memberProfile.displayName } val fullName: String get() = memberProfile.fullName val image: String? get() = memberProfile.image + val contactLink: String? = memberProfile.contactLink val verified get() = activeConn?.connectionCode != null val chatViewName: String diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index 662867f368..8542bfcadd 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -855,6 +855,15 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a return null } + suspend fun apiSetProfileAddress(on: Boolean): User? { + val userId = try { currentUserId("apiSetProfileAddress") } catch (e: Exception) { return null } + return when (val r = sendCmd(CC.ApiSetProfileAddress(userId, on))) { + is CR.UserProfileNoChange -> null + is CR.UserProfileUpdated -> r.user + else -> throw Exception("failed to set profile address: ${r.responseType} ${r.details}") + } + } + suspend fun apiSetContactPrefs(contactId: Long, prefs: ChatPreferences): Contact? { val r = sendCmd(CC.ApiSetContactPrefs(contactId, prefs)) if (r is CR.ContactPrefsUpdated) return r.toContact @@ -890,12 +899,12 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } } - suspend fun apiDeleteUserAddress(): Boolean { - val userId = kotlin.runCatching { currentUserId("apiDeleteUserAddress") }.getOrElse { return false } + suspend fun apiDeleteUserAddress(): User? { + val userId = try { currentUserId("apiDeleteUserAddress") } catch (e: Exception) { return null } val r = sendCmd(CC.ApiDeleteMyAddress(userId)) - if (r is CR.UserContactLinkDeleted) return true + if (r is CR.UserContactLinkDeleted) return r.user Log.e(TAG, "apiDeleteUserAddress bad response: ${r.responseType} ${r.details}") - return false + return null } private suspend fun apiGetUserAddress(): UserContactLinkRec? { @@ -1905,6 +1914,7 @@ sealed class CC { class ApiCreateMyAddress(val userId: Long): CC() class ApiDeleteMyAddress(val userId: Long): CC() class ApiShowMyAddress(val userId: Long): CC() + class ApiSetProfileAddress(val userId: Long, val on: Boolean): CC() class ApiAddressAutoAccept(val userId: Long, val autoAccept: AutoAccept?): CC() class ApiSendCallInvitation(val contact: Contact, val callType: CallType): CC() class ApiRejectCall(val contact: Contact): CC() @@ -1989,6 +1999,7 @@ sealed class CC { is ApiCreateMyAddress -> "/_address $userId" is ApiDeleteMyAddress -> "/_delete_address $userId" is ApiShowMyAddress -> "/_show_address $userId" + is ApiSetProfileAddress -> "/_profile_address $userId ${onOff(on)}" is ApiAddressAutoAccept -> "/_auto_accept $userId ${AutoAccept.cmdString(autoAccept)}" is ApiAcceptContact -> "/_accept $contactReqId" is ApiRejectContact -> "/_reject $contactReqId" @@ -2074,6 +2085,7 @@ sealed class CC { is ApiCreateMyAddress -> "apiCreateMyAddress" is ApiDeleteMyAddress -> "apiDeleteMyAddress" is ApiShowMyAddress -> "apiShowMyAddress" + is ApiSetProfileAddress -> "apiSetProfileAddress" is ApiAddressAutoAccept -> "apiAddressAutoAccept" is ApiAcceptContact -> "apiAcceptContact" is ApiRejectContact -> "apiRejectContact" diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt index 0474ff71cd..a97d591388 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt @@ -5,8 +5,11 @@ import InfoRowEllipsis import SectionBottomSpacer import SectionDividerSpaced import SectionItemView +import SectionItemViewWithIcon import SectionSpacer +import SectionTextFooter import SectionView +import TextIconSpaced import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.compose.foundation.* @@ -18,8 +21,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.ClipboardManager -import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.* import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -34,6 +36,7 @@ import chat.simplex.app.SimplexApp import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.newchat.QRCode import chat.simplex.app.views.usersettings.* import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* @@ -195,6 +198,15 @@ fun ChatInfoLayout( } SectionDividerSpaced() + if (contact.contactLink != null) { + val context = LocalContext.current + SectionView(stringResource(R.string.address_section_title).uppercase()) { + QRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) + ShareAddressButton { shareText(context, contact.contactLink) } + SectionTextFooter(stringResource(R.string.you_can_share_this_address_with_your_contacts).format(contact.displayName)) + } + SectionDividerSpaced() + } SectionView(title = stringResource(R.string.conn_stats_section_title_servers)) { SwitchAddressButton(switchContactAddress) @@ -416,6 +428,17 @@ private fun DeleteContactButton(onClick: () -> Unit) { ) } +@Composable +fun ShareAddressButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(R.drawable.ic_share_filled), + stringResource(R.string.share_address), + onClick, + iconColor = MaterialTheme.colors.primary, + textColor = MaterialTheme.colors.primary, + ) +} + private fun setContactAlias(contactApiId: Long, localAlias: String, chatModel: ChatModel) = withApi { chatModel.controller.apiSetContactAlias(contactApiId, localAlias)?.let { chatModel.updateContact(it) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt index 728056f1d9..4722820938 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt @@ -175,6 +175,8 @@ fun GroupChatInfoLayout( SectionView { if (groupInfo.canEdit) { EditGroupProfileButton(editGroupProfile) + } + if (groupInfo.groupProfile.description != null || groupInfo.canEdit) { AddOrEditWelcomeMessage(groupInfo.groupProfile.description, addOrEditWelcomeMessage) } GroupPreferencesButton(openPreferences) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt index 0ba92cc3fc..572b46dff4 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt @@ -4,6 +4,7 @@ import InfoRow import SectionBottomSpacer import SectionDividerSpaced import SectionSpacer +import SectionTextFooter import SectionView import androidx.activity.compose.BackHandler import androidx.compose.foundation.* @@ -13,6 +14,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -20,10 +22,12 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.simplex.app.R +import chat.simplex.app.SimplexApp import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.chat.* import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.newchat.QRCode import chat.simplex.app.views.usersettings.SettingsActionItem import kotlinx.datetime.Clock @@ -173,10 +177,20 @@ fun GroupMemberInfoLayout( VerifyCodeButton(member.verified, verifyClicked) } } - SectionSpacer() + SectionDividerSpaced() } } + if (member.contactLink != null) { + val context = LocalContext.current + SectionView(stringResource(R.string.address_section_title).uppercase()) { + QRCode(member.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) + ShareAddressButton { shareText(context, member.contactLink) } + SectionTextFooter(stringResource(R.string.you_can_share_this_address_with_your_contacts).format(member.displayName)) + } + SectionDividerSpaced() + } + SectionView(title = stringResource(R.string.member_info_section_title_member)) { InfoRow(stringResource(R.string.info_row_group), groupInfo.displayName) val roles = remember { member.canChangeRoleTo(groupInfo) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt index 9e9408e993..eb1cf95d7d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt @@ -1,22 +1,30 @@ package chat.simplex.app.views.chat.group import SectionBottomSpacer +import SectionDividerSpaced import SectionItemView import SectionSpacer import SectionView +import TextIconSpaced +import android.util.Log import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import chat.simplex.app.* import chat.simplex.app.R import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* +import kotlinx.coroutines.delay +import kotlinx.serialization.Serializable +import java.lang.Exception @Composable fun GroupWelcomeView(m: ChatModel, groupInfo: GroupInfo, close: () -> Unit) { @@ -49,6 +57,7 @@ fun GroupWelcomeView(m: ChatModel, groupInfo: GroupInfo, close: () -> Unit) { GroupWelcomeLayout( welcomeText, groupInfo, + m.controller.appPrefs.simplexLinkMode.get(), save = ::save ) } @@ -58,19 +67,43 @@ fun GroupWelcomeView(m: ChatModel, groupInfo: GroupInfo, close: () -> Unit) { private fun GroupWelcomeLayout( welcomeText: MutableState, groupInfo: GroupInfo, + linkMode: SimplexLinkMode, save: () -> Unit, ) { Column( Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), ) { + val editMode = remember { mutableStateOf(true) } AppBarTitle(stringResource(R.string.group_welcome_title)) - val welcomeText = remember { welcomeText } - TextEditor(Modifier.padding(horizontal = DEFAULT_PADDING).height(160.dp), text = welcomeText) - SectionSpacer() - SaveButton( - save = save, - disabled = welcomeText.value == groupInfo.groupProfile.description || (welcomeText.value == "" && groupInfo.groupProfile.description == null) - ) + val welcomeText = rememberSaveable { welcomeText } + if (groupInfo.canEdit) { + if (editMode.value) { + val focusRequester = remember { FocusRequester() } + TextEditor(welcomeText, Modifier.heightIn(min = 100.dp), stringResource(R.string.enter_welcome_message), focusRequester = focusRequester) + LaunchedEffect(Unit) { + delay(300) + focusRequester.requestFocus() + } + } else { + TextEditorPreview(welcomeText.value, linkMode) + } + ChangeModeButton( + editMode.value, + click = { + editMode.value = !editMode.value + }, + welcomeText.value.isEmpty() + ) + CopyTextButton { copyText(SimplexApp.context, welcomeText.value) } + SectionDividerSpaced(maxBottomPadding = false) + SaveButton( + save = save, + disabled = welcomeText.value == groupInfo.groupProfile.description || (welcomeText.value == "" && groupInfo.groupProfile.description == null) + ) + } else { + TextEditorPreview(welcomeText.value, linkMode) + CopyTextButton { copyText(SimplexApp.context, welcomeText.value) } + } SectionBottomSpacer() } } @@ -84,6 +117,35 @@ private fun SaveButton(save: () -> Unit, disabled: Boolean) { } } +@Composable +private fun ChangeModeButton(editMode: Boolean, click: () -> Unit, disabled: Boolean) { + SectionItemView(click, disabled = disabled) { + Icon( + painterResource(if (editMode) R.drawable.ic_visibility else R.drawable.ic_edit), + contentDescription = generalGetString(R.string.edit_verb), + tint = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + ) + TextIconSpaced() + Text( + stringResource(if (editMode) R.string.group_welcome_preview else R.string.edit_verb), + color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + ) + } +} + +@Composable +private fun CopyTextButton(click: () -> Unit) { + SectionItemView(click) { + Icon( + painterResource(R.drawable.ic_content_copy), + contentDescription = generalGetString(R.string.copy_verb), + tint = MaterialTheme.colors.primary, + ) + TextIconSpaced() + Text(stringResource(R.string.copy_verb), color = MaterialTheme.colors.primary) + } +} + private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { AlertManager.shared.showAlertDialogStacked( title = generalGetString(R.string.save_welcome_message_question), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt index 6fd7212b8e..7e933413a4 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt @@ -8,7 +8,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.FontWeight @@ -18,7 +17,7 @@ import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.helpers.annotatedStringResource -import chat.simplex.app.views.helpers.openUriCatching +import chat.simplex.app.views.onboarding.ReadableTextWithLink import chat.simplex.app.views.usersettings.MarkdownHelpView import chat.simplex.app.views.usersettings.simplexTeamUri @@ -29,17 +28,8 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) { Column( verticalArrangement = Arrangement.spacedBy(10.dp) ) { - val uriHandler = LocalUriHandler.current - Text(stringResource(R.string.thank_you_for_installing_simplex), lineHeight = 22.sp) - Text( - annotatedStringResource(R.string.you_can_connect_to_simplex_chat_founder), - modifier = Modifier.clickable(onClick = { - uriHandler.openUriCatching(simplexTeamUri) - }), - lineHeight = 22.sp - ) - + ReadableTextWithLink(R.string.you_can_connect_to_simplex_chat_founder, simplexTeamUri) Column( Modifier.padding(top = 24.dp), verticalArrangement = Arrangement.spacedBy(10.dp) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt index e00f9ba454..d1fe5ebbde 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt @@ -1,64 +1,130 @@ package chat.simplex.app.views.helpers -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background +import android.util.Log +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.* import androidx.compose.ui.graphics.* import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.* -import chat.simplex.app.ui.theme.DEFAULT_PADDING +import chat.simplex.app.TAG +import chat.simplex.app.chatParseMarkdown +import chat.simplex.app.model.* +import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.chat.item.MarkdownText +import com.google.accompanist.insets.navigationBarsWithImePadding +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.serialization.Serializable +import java.lang.Exception @Composable fun TextEditor( + value: MutableState, modifier: Modifier, - text: MutableState, - border: Boolean = true, - fontSize: TextUnit = 14.sp, - background: Color = MaterialTheme.colors.background, - onChange: ((String) -> Unit)? = null + placeholder: String? = null, + contentPadding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING), + isValid: (String) -> Boolean = { true }, + focusRequester: FocusRequester? = null ) { - BasicTextField( - value = text.value, - onValueChange = { text.value = it; onChange?.invoke(it) }, - textStyle = TextStyle( - fontFamily = FontFamily.Monospace, fontSize = fontSize, - color = MaterialTheme.colors.onBackground - ), - keyboardOptions = KeyboardOptions.Default.copy( - capitalization = KeyboardCapitalization.None, - autoCorrect = false - ), - modifier = modifier, - cursorBrush = SolidColor(MaterialTheme.colors.secondary), - decorationBox = { innerTextField -> - Surface( - shape = if (border) RoundedCornerShape(10.dp) else RectangleShape, - border = if (border) BorderStroke(1.dp, MaterialTheme.colors.secondaryVariant) else null - ) { - Row( - Modifier.background(background), - verticalAlignment = Alignment.Top - ) { - Box( - Modifier - .weight(1f) - .padding(vertical = 5.dp, horizontal = if (border) 7.dp else DEFAULT_PADDING) - ) { - innerTextField() - } + var valid by rememberSaveable { mutableStateOf(true) } + var focused by rememberSaveable { mutableStateOf(false) } + val strokeColor by remember { + derivedStateOf { + if (valid) { + if (focused) { + CurrentColors.value.colors.secondary.copy(alpha = 0.6f) + } else { + CurrentColors.value.colors.secondary.copy(alpha = 0.3f) } - } + } else Color.Red } - ) + } + Box( + Modifier + .fillMaxWidth() + .padding(contentPadding) + .heightIn(min = 52.dp), +// .border(border = BorderStroke(1.dp, strokeColor), shape = RoundedCornerShape(26.dp)), + contentAlignment = Alignment.Center, + ) { + val modifier = modifier + .fillMaxWidth() + .navigationBarsWithImePadding() + .onFocusChanged { focused = it.isFocused } + + BasicTextField( + value = value.value, + onValueChange = { value.value = it }, + modifier = if (focusRequester == null) modifier else modifier.focusRequester(focusRequester), + textStyle = TextStyle(fontSize = 18.sp, color = MaterialTheme.colors.onBackground), + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.None, + autoCorrect = false + ), + singleLine = false, + maxLines = 5, + cursorBrush = SolidColor(MaterialTheme.colors.secondary), + decorationBox = @Composable { innerTextField -> + TextFieldDefaults.TextFieldDecorationBox( + value = value.value, + innerTextField = innerTextField, + placeholder = if (placeholder != null) {{ Text(placeholder, fontSize = 18.sp, color = MaterialTheme.colors.secondary) }} else null, + contentPadding = PaddingValues(), + label = null, + visualTransformation = VisualTransformation.None, + leadingIcon = null, + trailingIcon = null, + singleLine = false, + enabled = true, + isError = false, + interactionSource = remember { MutableInteractionSource() }, + ) + } + ) + } + LaunchedEffect(Unit) { + snapshotFlow { value.value } + .distinctUntilChanged() + .collect { + valid = isValid(it) + } + } +} + +@Composable +fun TextEditorPreview(text: String, linkMode: SimplexLinkMode, markdown: Boolean = true) { + SelectionContainer { + MarkdownText( + text, + formattedText = if (markdown) remember(text) { parseToMarkdown(text) } else null, + modifier = Modifier.heightIn(min = 100.dp).padding(horizontal = DEFAULT_PADDING), + linkMode = linkMode, + style = TextStyle(fontSize = 18.sp, color = MaterialTheme.colors.onBackground) + ) + } +} + +@Serializable +data class ParsedFormattedText( + val formattedText: List? = null +) + +fun parseToMarkdown(text: String): List? { + val formatted = chatParseMarkdown(text) + return try { + json.decodeFromString(ParsedFormattedText.serializer(), formatted).formattedText + } catch (e: Exception) { + Log.e(TAG, "Failed to parse into markdown: " + e.stackTraceToString()) + null + } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactLearnMore.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactLearnMore.kt new file mode 100644 index 0000000000..44e576198e --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactLearnMore.kt @@ -0,0 +1,23 @@ +package chat.simplex.app.views.newchat + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import chat.simplex.app.R +import chat.simplex.app.views.helpers.AppBarTitle +import chat.simplex.app.views.onboarding.ReadableText +import chat.simplex.app.views.onboarding.ReadableTextWithLink + +@Composable +fun AddContactLearnMore() { + Column( + Modifier.verticalScroll(rememberScrollState()), + ) { + AppBarTitle(stringResource(R.string.one_time_link)) + ReadableText(R.string.scan_qr_to_connect_to_contact) + ReadableText(R.string.if_you_cant_meet_in_person) + ReadableTextWithLink(R.string.read_more_in_user_guide_with_link, "https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/README.md#connect-to-friends") + } +} \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt index 5ad769adb6..99eab4584d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt @@ -1,6 +1,8 @@ package chat.simplex.app.views.newchat import SectionBottomSpacer +import SectionSpacer +import SectionView import android.content.res.Configuration import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -15,10 +17,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.usersettings.SettingsActionItem @Composable fun AddContactView(connReqInvitation: String, connIncognito: Boolean) { @@ -26,64 +28,94 @@ fun AddContactView(connReqInvitation: String, connIncognito: Boolean) { AddContactLayout( connReq = connReqInvitation, connIncognito = connIncognito, - share = { shareText(cxt, connReqInvitation) } + share = { shareText(cxt, connReqInvitation) }, + learnMore = { + ModalManager.shared.showModal { + Column( + Modifier + .fillMaxHeight() + .padding(horizontal = DEFAULT_PADDING), + verticalArrangement = Arrangement.SpaceBetween + ) { + AddContactLearnMore() + } + } + } ) } @Composable -fun AddContactLayout(connReq: String, connIncognito: Boolean, share: () -> Unit) { - BoxWithConstraints { - val screenHeight = maxHeight - Column( - Modifier - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.SpaceBetween, - ) { - AppBarTitle(stringResource(R.string.add_contact), false) - Text( - stringResource(R.string.show_QR_code_for_your_contact_to_scan_from_the_app__multiline), - ) - Row { - InfoAboutIncognito( - connIncognito, - true, - generalGetString(R.string.incognito_random_profile_description), - generalGetString(R.string.your_profile_will_be_sent) - ) - } - if (connReq.isNotEmpty()) { - QRCode( - connReq, Modifier - .aspectRatio(1f) - .padding(vertical = 3.dp) - ) - } else { - CircularProgressIndicator( - Modifier - .size(36.dp) - .padding(4.dp) - .align(Alignment.CenterHorizontally), - color = MaterialTheme.colors.secondary, - strokeWidth = 3.dp - ) - } - Text( - annotatedStringResource(R.string.if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel), - lineHeight = 22.sp, - modifier = Modifier - .padding(top = DEFAULT_PADDING, bottom = if (screenHeight > 600.dp) DEFAULT_PADDING else 0.dp) - ) - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center - ) { - SimpleButton(stringResource(R.string.share_invitation_link), icon = painterResource(R.drawable.ic_share), click = share) - } - SectionBottomSpacer() +fun AddContactLayout(connReq: String, connIncognito: Boolean, share: () -> Unit, learnMore: () -> Unit) { + Column( + Modifier + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.SpaceBetween, + ) { + AppBarTitle(stringResource(R.string.add_contact)) + OneTimeLinkProfileText(connIncognito) + + SectionSpacer() + SectionView(stringResource(R.string.one_time_link_short).uppercase()) { + OneTimeLinkSection(connReq, share, learnMore) } + SectionBottomSpacer() } } +@Composable +fun OneTimeLinkProfileText(connIncognito: Boolean) { + Row(Modifier.padding(horizontal = DEFAULT_PADDING)) { + InfoAboutIncognito( + connIncognito, + true, + generalGetString(R.string.incognito_random_profile_description), + generalGetString(R.string.your_profile_will_be_sent) + ) + } +} + +@Composable +fun ColumnScope.OneTimeLinkSection(connReq: String, share: () -> Unit, learnMore: () -> Unit) { + if (connReq.isNotEmpty()) { + QRCode( + connReq, Modifier + .padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF) + .aspectRatio(1f) + ) + } else { + CircularProgressIndicator( + Modifier + .size(36.dp) + .padding(4.dp) + .align(Alignment.CenterHorizontally), + color = MaterialTheme.colors.secondary, + strokeWidth = 3.dp + ) + } + ShareLinkButton(share) + OneTimeLinkLearnMoreButton(learnMore) +} + +@Composable +fun ShareLinkButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(R.drawable.ic_share), + stringResource(R.string.share_invitation_link), + onClick, + iconColor = MaterialTheme.colors.primary, + textColor = MaterialTheme.colors.primary, + ) +} + +@Composable +fun OneTimeLinkLearnMoreButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(R.drawable.ic_info), + stringResource(R.string.learn_more), + onClick, + ) +} + @Composable fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean = true, onText: String, offText: String, centered: Boolean = false) { if (chatModelIncognito) { @@ -133,7 +165,8 @@ fun PreviewAddContactView() { AddContactLayout( connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connIncognito = false, - share = {} + share = {}, + learnMore = {}, ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt index 12aa97cc12..fa7ec6f01f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt @@ -1,6 +1,7 @@ package chat.simplex.app.views.newchat import SectionBottomSpacer +import SectionDividerSpaced import SectionView import android.content.res.Configuration import androidx.compose.foundation.layout.* @@ -10,6 +11,7 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -43,13 +45,16 @@ fun ContactConnectionInfoView( } } } + val context = LocalContext.current ContactConnectionInfoLayout( connReq = connReqInvitation, contactConnection, + connIncognito = contactConnection.incognito, focusAlias, deleteConnection = { deleteContactConnectionAlert(contactConnection, chatModel, close) }, onLocalAliasChanged = { setContactAlias(contactConnection, it, chatModel) }, - showQr = { + share = { if (connReqInvitation != null) shareText(context, connReqInvitation) }, + learnMore = { ModalManager.shared.showModal { Column( Modifier @@ -57,7 +62,7 @@ fun ContactConnectionInfoView( .padding(horizontal = DEFAULT_PADDING), verticalArrangement = Arrangement.SpaceBetween ) { - AddContactView(connReqInvitation ?: return@showModal, contactConnection.incognito) + AddContactLearnMore() } } } @@ -68,10 +73,12 @@ fun ContactConnectionInfoView( private fun ContactConnectionInfoLayout( connReq: String?, contactConnection: PendingContactConnection, + connIncognito: Boolean, focusAlias: Boolean, deleteConnection: () -> Unit, onLocalAliasChanged: (String) -> Unit, - showQr: () -> Unit, + share: () -> Unit, + learnMore: () -> Unit, ) { Column( Modifier @@ -83,11 +90,6 @@ private fun ContactConnectionInfoLayout( else R.string.you_accepted_connection ) ) - if (contactConnection.groupLinkId == null) { - Row(Modifier.padding(bottom = DEFAULT_PADDING)) { - LocalAliasEditor(contactConnection.localAlias, center = false, leadingIcon = true, focus = focusAlias, updateValue = onLocalAliasChanged) - } - } Text( stringResource( if (contactConnection.viaContactUri) @@ -97,27 +99,28 @@ private fun ContactConnectionInfoLayout( ), Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING) ) + OneTimeLinkProfileText(connIncognito) + + if (contactConnection.groupLinkId == null) { + LocalAliasEditor(contactConnection.localAlias, center = false, leadingIcon = true, focus = focusAlias, updateValue = onLocalAliasChanged) + } + SectionView { if (!connReq.isNullOrEmpty() && contactConnection.initiated) { - ShowQrButton(contactConnection.incognito, showQr) + OneTimeLinkSection(connReq, share, learnMore) + } else { + OneTimeLinkLearnMoreButton(learnMore) } - DeleteButton(deleteConnection) } + + SectionDividerSpaced(maxBottomPadding = false) + + DeleteButton(deleteConnection) + SectionBottomSpacer() } } -@Composable -fun ShowQrButton(incognito: Boolean, onClick: () -> Unit) { - SettingsActionItem( - painterResource(R.drawable.ic_qr_code), - stringResource(R.string.show_QR_code), - click = onClick, - textColor = if (incognito) Indigo else MaterialTheme.colors.primary, - iconColor = if (incognito) Indigo else MaterialTheme.colors.primary, - ) -} - @Composable fun DeleteButton(onClick: () -> Unit) { SettingsActionItem( @@ -147,10 +150,12 @@ private fun PreviewContactConnectionInfoView() { ContactConnectionInfoLayout( connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", PendingContactConnection.getSampleData(), + connIncognito = false, focusAlias = false, deleteConnection = {}, onLocalAliasChanged = {}, - showQr = {}, + share = {}, + learnMore = {} ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt index 90e58a6986..61a236936b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt @@ -45,14 +45,13 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) { when { it == CreateLinkTab.ONE_TIME && connReqInvitation.value.isNullOrEmpty() -> stringResource(R.string.create_one_time_link) it == CreateLinkTab.ONE_TIME -> stringResource(R.string.one_time_link) - it == CreateLinkTab.LONG_TERM -> stringResource(R.string.your_contact_address) + it == CreateLinkTab.LONG_TERM -> stringResource(R.string.your_simplex_contact_address) else -> "" } } Column( Modifier - .fillMaxHeight() - .padding(horizontal = DEFAULT_PADDING), + .fillMaxHeight(), verticalArrangement = Arrangement.SpaceBetween ) { Column(Modifier.weight(1f)) { @@ -61,7 +60,7 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) { AddContactView(connReqInvitation.value ?: "", m.incognito.value) } CreateLinkTab.LONG_TERM -> { - UserAddressView(m) + UserAddressView(m, viaCreateLinkView = true, close = {}) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt index 19a35a0f6d..54c24999b6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt @@ -85,7 +85,7 @@ fun PasteToConnectLayout( ) Box(Modifier.padding(top = DEFAULT_PADDING, bottom = 6.dp)) { - TextEditor(Modifier.height(180.dp), text = connectionLink) + TextEditor(connectionLink, Modifier.height(180.dp), contentPadding = PaddingValues()) } Row( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt index 5e76c8b095..ad7465fd38 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt @@ -4,21 +4,23 @@ import android.content.res.Configuration import androidx.annotation.StringRes import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.* import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.R +import chat.simplex.app.SimplexApp import chat.simplex.app.model.User -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.chat.item.MarkdownText import chat.simplex.app.views.helpers.* @Composable @@ -33,12 +35,7 @@ fun HowItWorks(user: User?, onboardingStage: MutableState? = n ReadableText(R.string.you_control_servers_to_receive_your_contacts_to_send) ReadableText(R.string.only_client_devices_store_contacts_groups_e2e_encrypted_messages) if (onboardingStage == null) { - val uriHandler = LocalUriHandler.current - Text( - annotatedStringResource(R.string.read_more_in_github_with_link), - modifier = Modifier.padding(bottom = 12.dp).clickable { uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#readme") }, - lineHeight = 22.sp - ) + ReadableTextWithLink(R.string.read_more_in_github_with_link, "https://github.com/simplex-chat/simplex-chat#readme") } else { ReadableText(R.string.read_more_in_github) } @@ -59,11 +56,42 @@ fun ReadableText(@StringRes stringResId: Int, textAlign: TextAlign = TextAlign.S Text(annotatedStringResource(stringResId), modifier = Modifier.padding(padding), textAlign = textAlign, lineHeight = 22.sp) } +@Composable +fun ReadableTextWithLink(@StringRes stringResId: Int, link: String, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp)) { + val annotated = annotatedStringResource(stringResId) + val primary = MaterialTheme.colors.primary + // This replaces links in text highlighted with specific color, e.g. SimplexBlue + val newStyles = remember(stringResId) { + val newStyles = ArrayList>() + annotated.spanStyles.forEach { + if (it.item.color == SimplexBlue) { + newStyles.add(it.copy(item = it.item.copy(primary))) + } else { + newStyles.add(it) + } + } + newStyles + } + val uriHandler = LocalUriHandler.current + Text(AnnotatedString(annotated.text, newStyles), modifier = Modifier.padding(padding).clickable { uriHandler.openUriCatching(link) }, textAlign = textAlign, lineHeight = 22.sp) +} + @Composable fun ReadableText(text: String, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp)) { Text(text, modifier = Modifier.padding(padding), textAlign = textAlign, lineHeight = 22.sp) } +@Composable +fun ReadableMarkdownText(text: String, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp)) { + MarkdownText( + text, + formattedText = remember(text) { parseToMarkdown(text) }, + modifier = Modifier.padding(padding), + style = TextStyle(textAlign = textAlign, lineHeight = 22.sp, fontSize = 16.sp), + linkMode = SimplexApp.context.chatModel.controller.appPrefs.simplexLinkMode.get(), + ) +} + @Preview(showBackground = true) @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AcceptRequestsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AcceptRequestsView.kt deleted file mode 100644 index c383370042..0000000000 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AcceptRequestsView.kt +++ /dev/null @@ -1,160 +0,0 @@ -package chat.simplex.app.views.usersettings - -import SectionBottomSpacer -import SectionCustomFooter -import SectionView -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.ui.res.painterResource -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* - -@Composable -fun AcceptRequestsView(m: ChatModel, contactLink: UserContactLinkRec) { - var contactLink by remember { mutableStateOf(contactLink) } - AcceptRequestsLayout( - contactLink, - saveState = { new: MutableState, old: MutableState -> - withApi { - val link = m.controller.userAddressAutoAccept(new.value.autoAccept) - if (link != null) { - contactLink = link - m.userAddress.value = link - old.value = new.value - } - } - } - ) -} - -@Composable -private fun AcceptRequestsLayout( - contactLink: UserContactLinkRec, - saveState: (new: MutableState, old: MutableState) -> Unit, -) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), - ) { - AppBarTitle(stringResource(R.string.contact_requests)) - val autoAcceptState = remember { mutableStateOf(AutoAcceptState(contactLink)) } - val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) } - SectionView(stringResource(R.string.accept_requests).uppercase()) { - PreferenceToggleWithIcon(stringResource(R.string.accept_automatically), painterResource(R.drawable.ic_check), checked = autoAcceptState.value.enable) { - autoAcceptState.value = if (!it) - AutoAcceptState() - else - AutoAcceptState(it, autoAcceptState.value.incognito, autoAcceptState.value.welcomeText) - } - if (autoAcceptState.value.enable) { - PreferenceToggleWithIcon( - stringResource(R.string.incognito), - if (autoAcceptState.value.incognito) painterResource(R.drawable.ic_theater_comedy_filled) else painterResource(R.drawable.ic_theater_comedy), - if (autoAcceptState.value.incognito) Indigo else MaterialTheme.colors.secondary, - autoAcceptState.value.incognito, - ) { - autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, it, autoAcceptState.value.welcomeText) - } - } - } - val welcomeText = remember { mutableStateOf(autoAcceptState.value.welcomeText) } - SectionCustomFooter(PaddingValues(horizontal = DEFAULT_PADDING)) { - ButtonsFooter( - cancel = { - autoAcceptState.value = autoAcceptStateSaved.value - welcomeText.value = autoAcceptStateSaved.value.welcomeText - }, - save = { saveState(autoAcceptState, autoAcceptStateSaved) }, - disabled = autoAcceptState.value == autoAcceptStateSaved.value - ) - } - Spacer(Modifier.height(DEFAULT_PADDING)) - if (autoAcceptState.value.enable) { - Text( - stringResource(R.string.section_title_welcome_message), color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, - modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), fontSize = 12.sp - ) - TextEditor(Modifier.padding(horizontal = DEFAULT_PADDING).height(160.dp), text = welcomeText) - LaunchedEffect(welcomeText.value) { - if (welcomeText.value != autoAcceptState.value.welcomeText) { - autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, autoAcceptState.value.incognito, welcomeText.value) - } - } - } - SectionBottomSpacer() - } -} - -@Composable -private fun ButtonsFooter(cancel: () -> Unit, save: () -> Unit, disabled: Boolean) { - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - FooterButton(painterResource(R.drawable.ic_replay), stringResource(R.string.cancel_verb), cancel, disabled) - FooterButton(painterResource(R.drawable.ic_check), stringResource(R.string.save_verb), save, disabled) - } -} - -private class AutoAcceptState { - var enable: Boolean = false - private set - var incognito: Boolean = false - private set - var welcomeText: String = "" - private set - - constructor(enable: Boolean = false, incognito: Boolean = false, welcomeText: String = "") { - this.enable = enable - this.incognito = incognito - this.welcomeText = welcomeText - } - - constructor(contactLink: UserContactLinkRec) { - contactLink.autoAccept?.let { aa -> - enable = true - incognito = aa.acceptIncognito - aa.autoReply?.let { msg -> - welcomeText = msg.text - } ?: run { - welcomeText = "" - } - } - } - - val autoAccept: AutoAccept? - get() { - if (enable) { - var autoReply: MsgContent? = null - val s = welcomeText.trim() - if (s != "") { - autoReply = MsgContent.MCText(s) - } - return AutoAccept(incognito, autoReply) - } - return null - } - - override fun equals(other: Any?): Boolean { - if (other !is AutoAcceptState) return false - return this.enable == other.enable && this.incognito == other.incognito && this.welcomeText == other.welcomeText - } - - override fun hashCode(): Int { - var result = enable.hashCode() - result = 31 * result + incognito.hashCode() - result = 31 * result + welcomeText.hashCode() - return result - } -} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt index dc960dfd8b..86a2cafad2 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt @@ -27,6 +27,8 @@ import chat.simplex.app.model.ServerAddress.Companion.parseServerAddress import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.app.views.newchat.QRCode +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -140,14 +142,16 @@ private fun CustomServer( ) { val testedPreviously = remember { mutableMapOf() } TextEditor( - Modifier.height(144.dp), - text = serverAddress, - border = false, - fontSize = 16.sp, - background = if (isInDarkTheme()) GroupDark else MaterialTheme.colors.background - ) { - testedPreviously[server.server] = server.tested - onUpdate(server.copy(server = it, tested = testedPreviously[serverAddress.value])) + serverAddress, + Modifier.height(144.dp) + ) + LaunchedEffect(Unit) { + snapshotFlow { serverAddress.value } + .distinctUntilChanged() + .collect { + testedPreviously[server.server] = server.tested + onUpdate(server.copy(server = it, tested = testedPreviously[serverAddress.value])) + } } } SectionDividerSpaced() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt index bfecd10027..452a084910 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt @@ -120,7 +120,7 @@ fun RTCServersLayout( } else { Text(stringResource(R.string.enter_one_ICE_server_per_line)) if (editRTCServers) { - TextEditor(Modifier.height(160.dp), text = userRTCServersStr) + TextEditor(userRTCServersStr, Modifier.height(160.dp), contentPadding = PaddingValues()) Row( Modifier.fillMaxWidth(), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index 1b1b272923..65482116a0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -150,7 +150,7 @@ fun SettingsLayout( val profileHidden = rememberSaveable { mutableStateOf(false) } SettingsActionItem(painterResource(R.drawable.ic_manage_accounts), stringResource(R.string.your_chat_profiles), { withAuth(generalGetString(R.string.auth_open_chat_profiles), generalGetString(R.string.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden) } } }, disabled = stopped, extraPadding = true) SettingsIncognitoActionItem(incognitoPref, incognito, stopped) { showModal { IncognitoView() }() } - SettingsActionItem(painterResource(R.drawable.ic_qr_code), stringResource(R.string.your_simplex_contact_address), showModal { CreateLinkView(it, CreateLinkTab.LONG_TERM) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(R.drawable.ic_qr_code), stringResource(R.string.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true) ChatPreferencesItem(showCustomModal, stopped = stopped) } SectionDividerSpaced() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressLearnMore.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressLearnMore.kt new file mode 100644 index 0000000000..7a70fcc516 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressLearnMore.kt @@ -0,0 +1,24 @@ +package chat.simplex.app.views.usersettings + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import chat.simplex.app.R +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.onboarding.ReadableText +import chat.simplex.app.views.onboarding.ReadableTextWithLink + +@Composable +fun UserAddressLearnMore() { + Column( + Modifier.verticalScroll(rememberScrollState()), + ) { + AppBarTitle(stringResource(R.string.simplex_address)) + ReadableText(R.string.you_can_share_your_address) + ReadableText(R.string.you_wont_lose_your_contacts_if_delete_address) + ReadableText(R.string.you_can_accept_or_reject_connection) + ReadableTextWithLink(R.string.read_more_in_user_guide_with_link, "https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address") + } +} \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt index 6a2cad337e..c2dd05a296 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt @@ -1,13 +1,19 @@ package chat.simplex.app.views.usersettings import SectionBottomSpacer +import SectionDividerSpaced +import SectionItemView +import SectionTextFooter +import SectionView import android.content.res.Configuration +import android.util.Log 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.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -16,103 +22,382 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.UserContactLinkRec +import chat.simplex.app.TAG +import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.chat.ShareAddressButton import chat.simplex.app.views.helpers.* import chat.simplex.app.views.newchat.QRCode @Composable -fun UserAddressView(chatModel: ChatModel) { +fun UserAddressView( + chatModel: ChatModel, + viaCreateLinkView: Boolean = false, + shareViaProfile: Boolean = false, + close: () -> Unit +) { val cxt = LocalContext.current - UserAddressLayout( - userAddress = remember { chatModel.userAddress }.value, - createAddress = { - withApi { - val connReqContact = chatModel.controller.apiCreateUserAddress() - if (connReqContact != null) { - chatModel.userAddress.value = UserContactLinkRec(connReqContact) + val shareViaProfile = remember { mutableStateOf(shareViaProfile) } + var progressIndicator by remember { mutableStateOf(false) } + val onCloseHandler: MutableState<(close: () -> Unit) -> Unit> = remember { mutableStateOf({ _ -> }) } + + fun setProfileAddress(on: Boolean) { + progressIndicator = true + withBGApi { + try { + val u = chatModel.controller.apiSetProfileAddress(on) + if (u != null) { + chatModel.updateUser(u) } + } catch (e: Exception) { + Log.e(TAG, "UserAddressView apiSetProfileAddress: ${e.stackTraceToString()}") + } finally { + progressIndicator = false } - }, - share = { userAddress: String -> shareText(cxt, userAddress) }, - acceptRequests = { - chatModel.userAddress.value?.let { address -> - ModalManager.shared.showModal(settings = true) { AcceptRequestsView(chatModel, address) } - } - }, - deleteAddress = { - AlertManager.shared.showAlertDialog( - title = generalGetString(R.string.delete_address__question), - text = generalGetString(R.string.all_your_contacts_will_remain_connected), - confirmText = generalGetString(R.string.delete_verb), - onConfirm = { - withApi { - chatModel.controller.apiDeleteUserAddress() - chatModel.userAddress.value = null + } + } + val userAddress = remember { chatModel.userAddress } + val showLayout = @Composable { + UserAddressLayout( + userAddress = userAddress.value, + shareViaProfile, + onCloseHandler, + createAddress = { + withApi { + progressIndicator = true + val connReqContact = chatModel.controller.apiCreateUserAddress() + if (connReqContact != null) { + chatModel.userAddress.value = UserContactLinkRec(connReqContact) + + AlertManager.shared.showAlertDialog( + title = generalGetString(R.string.delete_address_with_contacts_question), + text = generalGetString(R.string.add_address_to_your_profile), + confirmText = generalGetString(R.string.share_verb), + onConfirm = { + setProfileAddress(true) + shareViaProfile.value = true + } + ) } - }, - destructive = true, + progressIndicator = false + } + }, + learnMore = { + ModalManager.shared.showModal { + Column( + Modifier + .fillMaxHeight() + .padding(horizontal = DEFAULT_PADDING), + verticalArrangement = Arrangement.SpaceBetween + ) { + UserAddressLearnMore() + } + } + }, + share = { userAddress: String -> shareText(cxt, userAddress) }, + setProfileAddress = ::setProfileAddress, + deleteAddress = { + AlertManager.shared.showAlertDialog( + title = generalGetString(R.string.delete_address__question), + text = if (shareViaProfile.value) generalGetString(R.string.all_your_contacts_will_remain_connected_update_sent) else generalGetString(R.string.all_your_contacts_will_remain_connected), + confirmText = generalGetString(R.string.delete_verb), + onConfirm = { + progressIndicator = true + withApi { + val u = chatModel.controller.apiDeleteUserAddress() + if (u != null) { + chatModel.userAddress.value = null + chatModel.updateUser(u) + shareViaProfile.value = false + progressIndicator = false + } + } + }, + destructive = true, + ) + }, + saveAas = { aas: AutoAcceptState, savedAAS: MutableState -> + withBGApi { + val address = chatModel.controller.userAddressAutoAccept(aas.autoAccept) + if (address != null) { + chatModel.userAddress.value = address + savedAAS.value = aas + } + } + }) + } + + if (viaCreateLinkView) { + showLayout() + } else { + ModalView(close = { onCloseHandler.value(close) }) { + showLayout() + } + } + + if (progressIndicator) { + Box( + Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + if (userAddress.value != null) { + Surface(Modifier.size(50.dp), color = MaterialTheme.colors.background.copy(0.9f), shape = RoundedCornerShape(50)){} + } + CircularProgressIndicator( + Modifier + .padding(horizontal = 2.dp) + .size(30.dp), + color = MaterialTheme.colors.secondary, + strokeWidth = 3.dp ) } - ) + } } @Composable -fun UserAddressLayout( +private fun UserAddressLayout( userAddress: UserContactLinkRec?, + shareViaProfile: MutableState, + onCloseHandler: MutableState<(close: () -> Unit) -> Unit>, createAddress: () -> Unit, + learnMore: () -> Unit, share: (String) -> Unit, - acceptRequests: () -> Unit, - deleteAddress: () -> Unit + setProfileAddress: (Boolean) -> Unit, + deleteAddress: () -> Unit, + saveAas: (AutoAcceptState, MutableState) -> Unit, ) { Column( Modifier.verticalScroll(rememberScrollState()), ) { - AppBarTitle(stringResource(R.string.your_contact_address), false) - Text( - stringResource(R.string.you_can_share_your_address_anybody_will_be_able_to_connect), - Modifier.padding(bottom = 12.dp), - lineHeight = 22.sp - ) + AppBarTitle(stringResource(R.string.simplex_address), false) Column( Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.SpaceEvenly ) { if (userAddress == null) { - SimpleButton(stringResource(R.string.create_address), icon = painterResource(R.drawable.ic_qr_code), click = createAddress) - } else { - QRCode(userAddress.connReqContact, Modifier.aspectRatio(1f)) - Row( - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(vertical = DEFAULT_PADDING) - ) { - SimpleButton( - stringResource(R.string.share_link), - icon = painterResource(R.drawable.ic_share), - click = { share(userAddress.connReqContact) }) - SimpleButtonIconEnded( - stringResource(R.string.contact_requests), - icon = painterResource(R.drawable.ic_chevron_right), - click = acceptRequests - ) + SectionView { + CreateAddressButton(createAddress) + SectionTextFooter(stringResource(R.string.create_address_and_let_people_connect)) + } + SectionDividerSpaced(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(R.string.address_section_title).uppercase()) { + QRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) + ShareAddressButton { share(userAddress.connReqContact) } + ShareWithContactsButton(shareViaProfile, setProfileAddress) + AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) } + LearnMoreButton(learnMore) + } + if (autoAcceptState.value.enable) { + SectionDividerSpaced() + AutoAcceptSection(autoAcceptState, autoAcceptStateSaved, saveAas) + } + + SectionDividerSpaced(maxBottomPadding = false) + + SectionView { + DeleteAddressButton(deleteAddress) + SectionTextFooter(stringResource(R.string.your_contacts_will_remain_connected)) + } + LaunchedEffect(Unit) { + onCloseHandler.value = { close -> + if (autoAcceptState.value == autoAcceptStateSaved.value) close() + else showUnsavedChangesAlert({ saveAas(autoAcceptState.value, autoAcceptStateSaved); close() }, close) + } } - SimpleButton( - stringResource(R.string.delete_address), - icon = painterResource(R.drawable.ic_delete), - color = Color.Red, - click = deleteAddress - ) } } SectionBottomSpacer() } } +@Composable +private fun CreateAddressButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(R.drawable.ic_qr_code), + stringResource(R.string.create_simplex_address), + onClick, + iconColor = MaterialTheme.colors.primary, + textColor = MaterialTheme.colors.primary, + ) +} + +@Composable +private fun LearnMoreButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(R.drawable.ic_info), + stringResource(R.string.learn_more_about_address), + onClick, + ) +} + +@Composable +fun ShareWithContactsButton(shareViaProfile: MutableState, setProfileAddress: (Boolean) -> Unit) { + PreferenceToggleWithIcon( + stringResource(R.string.share_with_contacts), + painterResource(R.drawable.ic_person), + checked = shareViaProfile.value, + ) { on -> + shareViaProfile.value = on + if (on) { + AlertManager.shared.showAlertDialog( + title = generalGetString(R.string.share_address_with_contacts_question), + text = generalGetString(R.string.profile_update_will_be_sent_to_contacts), + confirmText = generalGetString(R.string.share_verb), + onConfirm = { + setProfileAddress(on) + }, + onDismiss = { + shareViaProfile.value = !on + }, + onDismissRequest = { + shareViaProfile.value = !on + }) + } else { + AlertManager.shared.showAlertDialog( + title = generalGetString(R.string.stop_sharing_address), + text = generalGetString(R.string.profile_update_will_be_sent_to_contacts), + confirmText = generalGetString(R.string.stop_sharing), + onConfirm = { + setProfileAddress(on) + }, + onDismiss = { + shareViaProfile.value = !on + }, + onDismissRequest = { + shareViaProfile.value = !on + }) + } + } +} + +@Composable +private fun AutoAcceptToggle(autoAcceptState: MutableState, saveAas: (AutoAcceptState) -> Unit) { + PreferenceToggleWithIcon(stringResource(R.string.auto_accept_contact), painterResource(R.drawable.ic_check), checked = autoAcceptState.value.enable) { + autoAcceptState.value = if (!it) + AutoAcceptState() + else + AutoAcceptState(it, autoAcceptState.value.incognito, autoAcceptState.value.welcomeText) + saveAas(autoAcceptState.value) + } +} + +@Composable +private fun DeleteAddressButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(R.drawable.ic_delete), + stringResource(R.string.delete_address), + onClick, + iconColor = MaterialTheme.colors.error, + textColor = MaterialTheme.colors.error, + ) +} + +private class AutoAcceptState { + var enable: Boolean = false + private set + var incognito: Boolean = false + private set + var welcomeText: String = "" + private set + + constructor(enable: Boolean = false, incognito: Boolean = false, welcomeText: String = "") { + this.enable = enable + this.incognito = incognito + this.welcomeText = welcomeText + } + + constructor(contactLink: UserContactLinkRec) { + contactLink.autoAccept?.let { aa -> + enable = true + incognito = aa.acceptIncognito + aa.autoReply?.let { msg -> + welcomeText = msg.text + } ?: run { + welcomeText = "" + } + } + } + + val autoAccept: AutoAccept? + get() { + if (enable) { + var autoReply: MsgContent? = null + val s = welcomeText.trim() + if (s != "") { + autoReply = MsgContent.MCText(s) + } + return AutoAccept(incognito, autoReply) + } + return null + } + + override fun equals(other: Any?): Boolean { + if (other !is AutoAcceptState) return false + return this.enable == other.enable && this.incognito == other.incognito && this.welcomeText == other.welcomeText + } + + override fun hashCode(): Int { + var result = enable.hashCode() + result = 31 * result + incognito.hashCode() + result = 31 * result + welcomeText.hashCode() + return result + } +} + +@Composable +private fun AutoAcceptSection( + autoAcceptState: MutableState, + savedAutoAcceptState: MutableState, + saveAas: (AutoAcceptState, MutableState) -> Unit +) { + SectionView(stringResource(R.string.auto_accept_contact).uppercase()) { + AcceptIncognitoToggle(autoAcceptState) + WelcomeMessageEditor(autoAcceptState) + SaveAASButton(autoAcceptState.value == savedAutoAcceptState.value) { saveAas(autoAcceptState.value, savedAutoAcceptState) } + } +} + +@Composable +private fun AcceptIncognitoToggle(autoAcceptState: MutableState) { + PreferenceToggleWithIcon( + stringResource(R.string.accept_contact_incognito_button), + if (autoAcceptState.value.incognito) painterResource(R.drawable.ic_theater_comedy_filled) else painterResource(R.drawable.ic_theater_comedy), + if (autoAcceptState.value.incognito) Indigo else MaterialTheme.colors.secondary, + autoAcceptState.value.incognito, + ) { + autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, it, autoAcceptState.value.welcomeText) + } +} + +@Composable +private fun WelcomeMessageEditor(autoAcceptState: MutableState) { + val welcomeText = rememberSaveable { mutableStateOf(autoAcceptState.value.welcomeText) } + TextEditor(welcomeText, Modifier.height(100.dp), placeholder = stringResource(R.string.enter_welcome_message_optional)) + LaunchedEffect(welcomeText.value) { + if (welcomeText.value != autoAcceptState.value.welcomeText) { + autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, autoAcceptState.value.incognito, welcomeText.value) + } + } +} + +@Composable +private fun SaveAASButton(disabled: Boolean, onClick: () -> Unit) { + SectionItemView(onClick, disabled = disabled) { + Text(stringResource(R.string.save_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + } +} + @Preview(showBackground = true) @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, @@ -126,12 +411,26 @@ fun PreviewUserAddressLayoutNoAddress() { userAddress = null, createAddress = {}, share = { _ -> }, - acceptRequests = {}, deleteAddress = {}, + saveAas = { _, _ -> }, + setProfileAddress = { _ -> }, + learnMore = {}, + shareViaProfile = remember { mutableStateOf(false) }, + onCloseHandler = remember { mutableStateOf({}) } ) } } +private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { + AlertManager.shared.showAlertDialogStacked( + title = generalGetString(R.string.save_settings_question), + confirmText = generalGetString(R.string.save_auto_accept_settings), + dismissText = generalGetString(R.string.exit_without_saving), + onConfirm = save, + onDismiss = revert, + ) +} + @Preview(showBackground = true) @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, @@ -145,8 +444,12 @@ fun PreviewUserAddressLayoutAddressCreated() { userAddress = UserContactLinkRec("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"), createAddress = {}, share = { _ -> }, - acceptRequests = {}, deleteAddress = {}, + saveAas = { _, _ -> }, + setProfileAddress = { _ -> }, + learnMore = {}, + shareViaProfile = remember { mutableStateOf(false) }, + onCloseHandler = remember { mutableStateOf({}) } ) } } diff --git a/apps/android/app/src/main/res/drawable/ic_person.xml b/apps/android/app/src/main/res/drawable/ic_person.xml new file mode 100644 index 0000000000..12ca71b4f9 --- /dev/null +++ b/apps/android/app/src/main/res/drawable/ic_person.xml @@ -0,0 +1,9 @@ + + + diff --git a/apps/android/app/src/main/res/values-ar/strings.xml b/apps/android/app/src/main/res/values-ar/strings.xml index d4c79ee9fd..deb0223248 100644 --- a/apps/android/app/src/main/res/values-ar/strings.xml +++ b/apps/android/app/src/main/res/values-ar/strings.xml @@ -31,7 +31,6 @@ أضف إلى جهاز آخر سيتم حذف جميع الدردشات والرسائل - لا يمكن التراجع عن هذا! الوصول إلى الخوادم عبر بروكسي SOCKS على المنفذ 9050؟ يجب بدء تشغيل الوكيل قبل تمكين هذا الخيار. - قبول طلبات إضافة خادم … إعدادات الشبكة المتقدمة سيبقى جميع أعضاء المجموعة على اتصال. diff --git a/apps/android/app/src/main/res/values-cs/strings.xml b/apps/android/app/src/main/res/values-cs/strings.xml index 6a4f0eed48..ea1f4c5d60 100644 --- a/apps/android/app/src/main/res/values-cs/strings.xml +++ b/apps/android/app/src/main/res/values-cs/strings.xml @@ -14,7 +14,6 @@ Povolte svým kontaktům odesílat mizící zprávy. O SimpleX Chat Přidat do jiného zařízení - Přijímat žádosti Povolit Povolit hlasové zprávy\? O SimpleX @@ -241,8 +240,6 @@ Připojení simplexmq: v%s (%2s) Vytvořit adresu - Automaticky - UVÍTACÍ ZPRÁVA Uložit a upozornit členy skupiny Ukončit bez uložení Platforma pro zasílání zpráv a aplikace chránící vaše soukromí a bezpečnost. @@ -378,7 +375,6 @@ Smazat čekající připojení\? Nastavení QR kód - Váš kontakt může z aplikace naskenovat QR kód. Pokud se nemůžete setkat osobně, ukažte ve videohovoru QR kód nebo sdílejte odkaz. Skenovat kód Nesprávný bezpečnostní kód! @@ -396,7 +392,6 @@ Pro připojení budou vyžadováni Onion hostitelé. Aktualizovat režim dopravní izolace\? Sestavení aplikace: %s - Můžete sdílet svou adresu jako odkaz nebo jako QR kód - kdokoli se k vám bude moci připojit. O své kontakty nepřijdete, pokud ji později smažete. Sdílet odkaz Smazat adresu Celé jméno: @@ -659,7 +654,6 @@ Verze jádra: v%s Smazat adresu\? Všechny vaše kontakty zůstanou připojeny. - Žádosti o kontakt Zobrazované jméno: Váš profil je uložen v zařízení a je sdílen pouze s vašimi kontakty. SimpleX servery váš profil vidět nemohou. Uložit předvolby\? @@ -957,7 +951,6 @@ Italské rozhraní Díky uživatelům - překládejte prostřednictvím Weblate! Budete připojeni, jakmile bude zařízení vašeho kontaktu online, vyčkejte prosím nebo se podívejte později! - Vaše adresa Váš chat profil bude odeslán \nvašemu kontaktu Vaše konverzace diff --git a/apps/android/app/src/main/res/values-de/strings.xml b/apps/android/app/src/main/res/values-de/strings.xml index 34db0193f0..c705c14aeb 100644 --- a/apps/android/app/src/main/res/values-de/strings.xml +++ b/apps/android/app/src/main/res/values-de/strings.xml @@ -328,7 +328,6 @@ Sie werden mit der Gruppe verbunden, sobald das Endgerät des Gruppen-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach! Sie werden verbunden, sobald Ihre Verbindungsanfrage akzeptiert wird. Bitte warten oder schauen Sie später nochmal nach! Sie werden verbunden, sobald das Endgerät Ihres Kontakts online ist. Bitte warten oder schauen Sie später nochmal nach! - Zeigen Sie Ihrem Kontakt den QR-Code aus der App zum Scannen. Wenn Sie sich nicht persönlich treffen können, können Sie den QR-Code während eines Videoanrufs anzeigen oder einen Einladungslink über einen anderen Kanal mit Ihrem Kontakt teilen. Ihr Chat-Profil wird \nan Ihren Kontakt gesendet @@ -345,7 +344,6 @@ Link / QR-Code erstellen Einmaliger Einladungs-Link - Meine Kontaktadresse Meine Einstellungen Meine SimpleX Kontaktadresse @@ -418,14 +416,8 @@ Adresse erstellen Adresse löschen? Alle Ihre Kontakte bleiben verbunden. - Sie können Ihre Adresse als Link oder als QR-Code teilen – Jede Person kann sich darüber mit Ihnen verbinden. Sie werden Ihre mit dieser Adresse verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen. Link teilen Adresse löschen - - Kontaktanfragen - Anfragen annehmen - Automatisch - Begrüßungsmeldung Angezeigter Name: "Vollständiger Name: diff --git a/apps/android/app/src/main/res/values-es/strings.xml b/apps/android/app/src/main/res/values-es/strings.xml index a2d32f0ff1..6a503d1ba6 100644 --- a/apps/android/app/src/main/res/values-es/strings.xml +++ b/apps/android/app/src/main/res/values-es/strings.xml @@ -75,8 +75,6 @@ Sobre SimpleX Chat Añadir a otro dispositivo Versión de la aplicación: v%s - Aceptar solicitudes - Automáticamente Solicita recibir la imagen Ten en cuenta: NO podrás recuperar o cambiar la contraseña si la pierdes. Tanto tú como tu contacto podéis enviar mensajes de voz. @@ -254,7 +252,6 @@ \nContraseña Contribuye Core versión: v%s - Solicitud del contacto Eliminar imagen Editar imagen CHATS @@ -858,7 +855,6 @@ mediante enlace de un uso Tus chats Mensaje de voz… - Mi dirección de contacto Desactivar vídeo Activar vídeo Contraseña de base de datos incorrecta @@ -888,7 +884,6 @@ Tienes un perfil de chat con el mismo nombre mostrado. Debes elegir otro nombre. También puedes conectarte haciendo clic en el enlace. Si se abre en el navegador, haz clic en Abrir en aplicación móvil. Puedes ponerte en contacto con los desarrolladores de SimpleX Chat para consultas y para recibir actualizaciones. - Puedes compartir tu dirección como enlace o como código QR: cualquiera podrá conectarse contigo. Si lo eliminas más tarde tus contactos no se perderán. ¡No puedes enviar mensajes! Puedes usar la sintaxis markdown para dar formato a los mensajes: Debes usar la versión más reciente de tu base de datos ÚNICAMENTE en un dispositivo, de lo contrario podrías dejar de recibir mensajes de algunos contactos. @@ -923,7 +918,6 @@ Has aceptado la conexión Has invitado a tu contacto Te conectarás al grupo cuando el dispositivo del anfitrión esté en línea, por favor espera o compruébalo más tarde. - Tu contacto puede escanear el código QR desde la aplicación. Mi configuración Tus servidores SMP ¡Tú controlas tu chat! @@ -972,7 +966,6 @@ Mi dirección de contacto SimpleX Tu servidor Dirección de tu servidor - MENSAJE DE BIENVENIDA Tu perfil actual Tu perfil se almacena en tu dispositivo y sólo se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. Sistema diff --git a/apps/android/app/src/main/res/values-fr/strings.xml b/apps/android/app/src/main/res/values-fr/strings.xml index 3ccb23d11d..f15988b78e 100644 --- a/apps/android/app/src/main/res/values-fr/strings.xml +++ b/apps/android/app/src/main/res/values-fr/strings.xml @@ -240,7 +240,6 @@ Ce code QR n\'est pas un lien ! Vous serez connecté·e lorsque votre demande de connexion sera acceptée, veuillez attendre ou vérifier plus tard ! Vous serez connecté·e lorsque l\'appareil de votre contact sera en ligne, veuillez attendre ou vérifier plus tard ! - Votre contact peut scanner le code QR depuis l\'app. Votre profil de chat sera envoyé \nà votre contact Partager le lien d\'invitation @@ -260,7 +259,6 @@ Se connecter via un lien Retirer la vérification Lien d\'invitation unique - Votre adresse de contact Scanner le code Code de sécurité incorrect ! Code de sécurité @@ -361,7 +359,6 @@ Les hôtes .onion seront utilisés lorsqu\'ils sont disponibles. Apparence Créer une adresse - Vous pouvez partager votre adresse sous forme de lien ou de code QR - n\'importe qui pourra se connecter à vous. Vous ne perdrez pas vos contacts si vous la supprimez par la suite. Votre profil de chat Modifier l\'image Sauvegarder et notifier les contacts @@ -433,10 +430,6 @@ Tous vos contacts resteront connectés. Partager le lien Supprimer l\'adresse - Demandes de contact - Accepter les demandes - Automatiquement - MESSAGE DE BIENVENUE Nom affiché : Nom complet : Votre profil est stocké sur votre appareil et partagé uniquement avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil. diff --git a/apps/android/app/src/main/res/values-hi/strings.xml b/apps/android/app/src/main/res/values-hi/strings.xml index f75ce402b7..ac5cfc339d 100644 --- a/apps/android/app/src/main/res/values-hi/strings.xml +++ b/apps/android/app/src/main/res/values-hi/strings.xml @@ -10,7 +10,6 @@ ऊपर,तब: स्वीकार करना जुडिये - आपका संपर्क पता दूसरे उपकरण में जोड़ें निडर कॉल का उत्तर दें @@ -35,7 +34,6 @@ संबंध अनुरोध स्वीकार करें\? स्वीकृत कॉल गुप्त स्वीकार करें - निवेदन स्वीकार करो पूर्वनिर्धारित सर्वर जोड़ें प्रोफ़ाइल जोड़ें सर्वर जोड़े… @@ -67,7 +65,6 @@ आप आज्ञा दें स्वागत! चालू करो - स्वागत संदेश अज्ञात संदेश प्रारूप स्वागत %1$s! शुरुआत @@ -235,7 +232,6 @@ चैट कंसोल आपके सभी संपर्क जुड़े रहेंगे। चैट प्रोफ़ाइल - संपर्क अनुरोध बनाएं कॉल त्रुटि कॉल चल रहा है diff --git a/apps/android/app/src/main/res/values-it/strings.xml b/apps/android/app/src/main/res/values-it/strings.xml index 3c1c8728c5..0186f9e056 100644 --- a/apps/android/app/src/main/res/values-it/strings.xml +++ b/apps/android/app/src/main/res/values-it/strings.xml @@ -239,7 +239,6 @@ Consenti i messaggi a tempo solo se il tuo contatto li consente. Permetti di eliminare irreversibilmente i messaggi inviati. Permetti ai tuoi contatti di inviare messaggi a tempo. - Accetta le richieste Accedere ai server via proxy SOCKS sulla porta 9050\? Il proxy deve essere avviato prima di attivare questa opzione. Aggiungi server scansionando codici QR. Tutti i membri del gruppo resteranno connessi. @@ -284,7 +283,6 @@ cambio indirizzo… Chat fermata connessione (presentato) - Richieste del contatto Richiesta di connessione inviata! Eliminare il link\? Elimina link @@ -339,7 +337,6 @@ Come si fa Come usare i tuoi server Server ICE (uno per riga) - Automaticamente grassetto chiamata terminata %1$s errore di chiamata @@ -575,7 +572,6 @@ Hai invitato il contatto Il tuo profilo di chat verrà inviato \nal tuo contatto - Il tuo contatto può scansionare il codice QR dall\'app. Il tuo contatto deve essere in linea per completare la connessione. \nPuoi annullare questa connessione e rimuovere il contatto (e riprovare più tardi con un link nuovo). Verrai connesso/a quando la tua richiesta di connessione verrà accettata, attendi o controlla più tardi! @@ -612,7 +608,6 @@ Usa il server Puoi anche connetterti cliccando il link. Se si apre nel browser, clicca il pulsante Apri nell\'app mobile. Il tuo profilo di chat verrà inviato al tuo contatto - Il tuo indirizzo di contatto Il tuo server L\'indirizzo del tuo server Il tuo indirizzo di contatto di SimpleX @@ -642,7 +637,6 @@ Usare i server di SimpleX Chat\? Stai usando i server di SimpleX Chat. Quando disponibili - Puoi condividere il tuo indirizzo come link o come codice QR: chiunque potrà connettersi a te. Non perderai i tuoi contatti se in seguito lo elimini. I tuoi server ICE I tuoi server SMP corsivo @@ -662,7 +656,6 @@ in attesa di risposta… in attesa di conferma… Non memorizziamo nessuno dei tuoi contatti o messaggi (una volta recapitati) sui server. - MESSAGGIO DI BENVENUTO Puoi usare il markdown per formattare i messaggi: Sei tu a controllare la tua chat! Il tuo profilo attuale diff --git a/apps/android/app/src/main/res/values-ja/strings.xml b/apps/android/app/src/main/res/values-ja/strings.xml index 7d3e9590c6..fc9016a462 100644 --- a/apps/android/app/src/main/res/values-ja/strings.xml +++ b/apps/android/app/src/main/res/values-ja/strings.xml @@ -31,8 +31,6 @@ 添付する アプリ・ビルド番号: %s あなたの連絡先が繋がったまま継続します。 - リクエストを承諾 - 自動的に 音声オン メッセージのハッシュ値問題 メッセージIDの問題 @@ -227,7 +225,6 @@ 連絡先の設定 連絡先はエンドツーエンド暗号化がありません。 連絡先がまだ繋がってません! - 連絡先のリクエスト 追加情報アイコン オニオンのホストが利用可能時に使われます。 オニオンのホストが使われません。 @@ -729,7 +726,6 @@ このリンクは有効な接続リンクではありません! このQRコードはリンクではありません! グループのホスト端末がオンラインになったら、接続されます。後でチェックするか、しばらくお待ちください。 - 連絡相手がアプリからQRコードを読み込めます。 連絡先がオンラインになったら、接続されます。後でチェックするか、しばらくお待ちください。 あなたのチャットプロフィールが \n連絡相手に送られます。 @@ -739,7 +735,6 @@ 連絡相手のアプリからセキュリティコードを読み込む SimpleXロック %s は未認証 - あなたのチャットアドレス あなたの設定 テストサーバ サーバを保存 @@ -890,8 +885,6 @@ SimpleX Chatを使っています。 リンクを送る simplexmq: バージョン%s (%2s) - あなたと繋がるリンク、またはQRコードを共有できます。誰でも接続できます。後で削除しても、連絡先がそのままのこります。 - 歓迎メッセージ 応答待ち… 確認待ち… 世界初のユーザーIDのないプラットフォーム|設計も元からプライベート diff --git a/apps/android/app/src/main/res/values-ko/strings.xml b/apps/android/app/src/main/res/values-ko/strings.xml index 62e3ca0fe2..d0f6d57894 100644 --- a/apps/android/app/src/main/res/values-ko/strings.xml +++ b/apps/android/app/src/main/res/values-ko/strings.xml @@ -52,13 +52,10 @@ 고급 네트워크 설정 채팅 프로필 연결 - 요청 수락 앱 빌드 : %s 외관 앱 버전 앱 버전 : v%s - 자동 - 대화 상대의 요청 코어 버전 : v%s 전화 받음 굵게 @@ -828,12 +825,10 @@ 라이브 메시지 보내기 - 입력 과정을 실시간으로 상대에게 보여줘요. 보내기 초대 링크 공유 - 연락하고자 하는 사람이 앱에서 QR 코드를 스캔할 수 있어요. 보안 코드 서버 QR코드 스캔 일부 서버가 테스트에 통과하지 못했습니다 : 서버 테스트 실패! - 환영 메시지 링크 공유 표시하기 연락처 선택 diff --git a/apps/android/app/src/main/res/values-lt/strings.xml b/apps/android/app/src/main/res/values-lt/strings.xml index 4476facc13..d1a4f0b9df 100644 --- a/apps/android/app/src/main/res/values-lt/strings.xml +++ b/apps/android/app/src/main/res/values-lt/strings.xml @@ -12,7 +12,6 @@ Programėlės versija Programėlės versija: v%s Programėlės darinys: %s - Automatiškai skambinama… skambučio klaida Skambutis jau baigtas! diff --git a/apps/android/app/src/main/res/values-nl/strings.xml b/apps/android/app/src/main/res/values-nl/strings.xml index 1439d7ae68..bd80a475d0 100644 --- a/apps/android/app/src/main/res/values-nl/strings.xml +++ b/apps/android/app/src/main/res/values-nl/strings.xml @@ -75,7 +75,6 @@ Over SimpleX Over SimpleX Chat hier boven, dan: - Verzoeken accepteren Alle gesprekken en berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! Alle berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! De berichten worden ALLEEN voor jou verwijderd. Sta verdwijnende berichten alleen toe als uw contactpersoon dit toestaat. @@ -96,7 +95,6 @@ App versie: v%s Er wordt een aparte TCP-verbinding (en SOCKS-referentie) gebruikt voor elk chat profiel dat je in de app hebt . audio oproep (niet e2e versleuteld) - Automatisch Achtergrondservice is altijd actief, meldingen worden weergegeven zodra de berichten beschikbaar zijn. Nieuw contact toevoegen: om uw eenmalige QR-code voor uw contact te maken. Oproep beëindigd @@ -210,7 +208,6 @@ Maak een eenmalige uitnodiging link gekleurd Oproep verbinden… - Contact verzoeken Maak Maak een profiel aan Adres verwijderen @@ -625,10 +622,8 @@ SimpleX-Team Deze QR-code is geen link! Deze link is geen geldige link! - Uw contactpersoon kan de QR-code vanuit de app scannen. Uitnodiging link delen Code scannen - Uw contact adres Uw instellingen Deel link Jij beheert je gesprek! @@ -728,7 +723,6 @@ SOCKS-proxy gebruiken (poort 9050) Voorkeuren opslaan\? Opslaan en Contact melden - WELKOMST BERICHT Je huidige profiel Opslaan en Contacten melden Opslaan en Groepsleden melden @@ -927,7 +921,6 @@ SimpleX Chat servers gebruiken\? Spraak berichten zijn verboden in deze groep. Welkom %1$s! - U kunt uw adres delen als een link of als een QR-code. Iedereen kan verbinding met u maken. U verliest uw contacten niet als u deze later verwijdert. U kunt de chat starten via app Instellingen / Database of door de app opnieuw op te starten. je hebt het adres gewijzigd voor %s je hebt %1$s verwijderd diff --git a/apps/android/app/src/main/res/values-pl/strings.xml b/apps/android/app/src/main/res/values-pl/strings.xml index 6924ef1724..fed9fae6fe 100644 --- a/apps/android/app/src/main/res/values-pl/strings.xml +++ b/apps/android/app/src/main/res/values-pl/strings.xml @@ -290,7 +290,6 @@ Twój profil czatu zostanie wysłany \ndo Twojego kontaktu Twój profil czatu zostanie wysłany do Twojego kontaktu - Twój adres kontaktowy Zostaniesz połączony do grupy, gdy urządzenie gospodarza grupy będzie online, proszę czekać lub sprawdzić później! Zostaniesz połączony, gdy urządzenie Twojego kontaktu będzie online, proszę czekać lub sprawdzić później! Dodaj gotowe serwery @@ -364,10 +363,7 @@ Użyj hostów .onion Użyć proxy SOCKS\? Użyj proxy SOCKS (port 9050) - Akceptuj prośby Wszystkie Twoje kontakty pozostaną połączone. - Automatycznie - Prośby kontaktu Utwórz adres ID bazy danych i opcja izolacji transportu. Usuń adres @@ -387,8 +383,6 @@ Pokaż: Pokaż opcje dewelopera simplexmq: v%s (%2s) - WIADOMOŚĆ POWITALNA - Możesz udostępnić swój adres jako link lub jako kod QR - każdy będzie mógł się z Tobą połączyć. Nie stracisz swoich kontaktów, jeśli później go usuniesz. Twój obecny profil Potwierdź hasło Utwórz profil @@ -1041,7 +1035,6 @@ Musisz używać najnowszej wersji bazy danych czatu TYLKO na jednym urządzeniu, w przeciwnym razie możesz przestać otrzymywać wiadomości od niektórych kontaktów. Możesz rozpocząć czat poprzez Ustawienia aplikacji / Bazę danych lub poprzez ponowne uruchomienie aplikacji. Twoje ustawienia - Twój kontakt może zeskanować kod QR z aplikacji. Twoja obecna baza danych czatu zostanie USUNIĘTA i ZASTĄPIONA zaimportowaną. \nTej czynności nie można cofnąć - Twój profil, kontakty, wiadomości i pliki zostaną nieodwracalnie utracone. Twój kontakt wysłał plik, który jest większy niż obecnie obsługiwany maksymalny rozmiar (%1$s). diff --git a/apps/android/app/src/main/res/values-pt-rBR/strings.xml b/apps/android/app/src/main/res/values-pt-rBR/strings.xml index 4adb03c202..10b1a1ab2f 100644 --- a/apps/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/apps/android/app/src/main/res/values-pt-rBR/strings.xml @@ -50,7 +50,6 @@ Botão Fechar Limpar verificação Versão do App - Automaticamente negrito erro de chamada Chamadas de áudio e vídeo @@ -67,7 +66,6 @@ Um perfil aleatório será enviado para o seu contato Preferências de chat perfil de chat - Aceitar solicitações Áudio desligado Aceitar imagens automaticamente Banco de dados de chat excluído @@ -123,7 +121,6 @@ Copiado para a área de transferência Aceitar solicitação de conexão\? Configurações de rede avançadas - Solicitações de contato Criar endereço Todos os seus contatos permanecerão conectados. chamada aceita @@ -462,7 +459,6 @@ Rejeitar ofereceu %s Endereço SimpleX - Seu contato pode escanear o código QR do aplicativo. ofereceu %s: %2s Cole o link que você recebeu na caixa abaixo para conectar com o seu contato. Novo em %s @@ -657,7 +653,6 @@ Esse código QR não é um link! Seu perfil de chat será enviado para seu \ncontato - Seu endereço de contato Você será conectado quando o dispositivo do seu contato estiver online, aguarde ou verifique mais tarde! Como Teste do servidor falhou! @@ -784,7 +779,6 @@ Hosts Onion não serão usados. Os hosts Onion serão usados quando disponíveis. Os hosts Onion serão usados quando disponíveis. - Você pode compartilhar seu endereço como um link ou como um código QR - qualquer pessoa poderá se conectar a você. Você não perderá seus contatos se excluí-los posteriormente. Seu perfil atual Privacidade redefinida Notificações privadas @@ -921,7 +915,6 @@ você é um observador Mensagem de voz (%1$s) Compartilhar link - MENSAGEM DE BOAS-VINDAS Para proteger a privacidade, em vez dos IDs de usuário usados por todas as outras plataformas, SimpleX tem identificadores para filas de mensagens, separados para cada um de seus contatos. chamada de vídeo Mostrar diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index a4df902c8a..ec80a7d96f 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -330,7 +330,6 @@ Соединение с группой будет установлено, когда хост группы будет онлайн. Пожалуйста, подождите или проверьте позже! Соединение будет установлено, когда Ваш запрос будет принят. Пожалуйста, подождите или проверьте позже! Соединение будет установлено, когда Ваш контакт будет онлайн. Пожалуйста, подождите или проверьте позже! - Ваш контакт может сосканировать QR код в приложении. Если Вы не можете встретиться лично, Вы можете показать QR код во время видеозвонка или поделиться ссылкой. Ваш профиль будет отправлен \nВашему контакту @@ -344,7 +343,6 @@ Создать одноразовую ссылку Одноразовая ссылка - Ваш SimpleX адрес Настройки Ваш SimpleX адрес @@ -417,14 +415,8 @@ Создать адрес Удалить адрес? Все контакты, которые соединились через этот адрес, сохранятся. - Вы можете использовать Ваш адрес как ссылку или как QR код - кто угодно сможет соединиться с Вами. Вы сможете удалить адрес, сохранив контакты, которые через него соединились. Поделиться\nссылкой - Удалить\nадрес - - Запросы контактов - Принимать запросы - Автоматически - ПРИВЕТСТВЕННОЕ СООБЩЕНИЕ + Удалить адрес Имя профиля: "Полное имя: diff --git a/apps/android/app/src/main/res/values-zh-rCN/strings.xml b/apps/android/app/src/main/res/values-zh-rCN/strings.xml index 2ebcaddde6..759422a3a6 100644 --- a/apps/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/apps/android/app/src/main/res/values-zh-rCN/strings.xml @@ -23,7 +23,6 @@ 接受连接请求? 接受隐身聊天 管理员可以创建链接以加入群组。 - 接受请求 添加预设服务器 通过链接连接 已建立连接 @@ -112,7 +111,6 @@ 错误消息散列 错误消息 ID 语音和视频通话 - 自动 激活电池优化,关闭了后台服务和新消息的定期请求。您可以通过设置重新启用它们。 后台服务一直在运行——一旦有消息,就会显示通知。 关闭音频 @@ -328,7 +326,6 @@ 联系人和所有的消息都将被删除——这是不可逆回的! 联系人姓名 连接(介绍邀请) - 联系人请求 连接中…… 联系人可以将信息标记为删除;您将可以查看这些信息。 贡献 @@ -516,7 +513,6 @@ 您只能在一台设备上使用最新版本的聊天数据库,否则您可能会停止接收来自某些联系人的消息。 新密码…… 该角色将更改为“%s”。群组中每个人都会收到通知。 - 您的联系人地址 SimpleX 锁定 定期通知 定期启动 @@ -891,9 +887,7 @@ SimpleX 团队 %1$s 成员 是 - 您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。 如果您以后删除它,您不会丢失您的联系人。 您可以控制通过哪些服务器接收消息,您的联系人 - 您用来向他们发送消息的服务器。 - 您的联系人可以从应用程序中扫描二维码。 您将在组主设备上线时连接到该群组,请稍等或稍后再检查! 当您启动应用或在应用程序驻留后台超过30 秒后,您将需要进行身份验证。 创建于 %1$s @@ -942,7 +936,6 @@ 使用 .onion 主机 您的 ICE 服务器 simplexmq: v%s (%2s) - 欢迎消息 您的聊天由您掌控! 您可以使用 markdown 来编排消息格式: %dh diff --git a/apps/android/app/src/main/res/values-zh-rTW/strings.xml b/apps/android/app/src/main/res/values-zh-rTW/strings.xml index c4e8727a53..2f8117f0df 100644 --- a/apps/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/apps/android/app/src/main/res/values-zh-rTW/strings.xml @@ -1,7 +1,6 @@ 1 個星期 - 接受請求 a + b 關於 SimpleX 接受 @@ -70,7 +69,6 @@ 程式建構:%s 應用程式版本:v%s 分享連結 - 自動接受所有請求 你目前的個人檔案 顯示的名稱字與字中間不能有空白。 儲存設定? @@ -434,8 +432,6 @@ 核心版本:v%s simplexmq: v%s (%2s) 建立地址 - 聯絡人請求 - 歡迎訊息 編輯圖片 斜體 已拒絕通話 @@ -487,7 +483,6 @@ 這些字串不是連接連結! 你也可以點擊連結連接。如果在瀏覧器中開啟,點擊 程式內的開啟 按扭。 一次性邀請連結 - 你的聯絡人地址 掃描二碼碼 錯誤的安全碼! 在你聯絡人的程式內掃描安全碼 @@ -511,7 +506,6 @@ Onion 主機會當有的時侯啟用 連接時將需要 Onion 主機 刪除地址? - 你可以使用連結或二維碼分享你的地址 - 任何人也可以和你連線。如果你不主動刪除你的聯絡人,你並不會遺失你的聯絡人 你的個人檔案只會儲存於你的裝置和只會分享給你的聯絡人。 SimpleX 伺服器並不會看到你的個人檔案。 儲存並通知你的聯絡人 儲存並通知你的多個聯絡人 @@ -525,7 +519,6 @@ 收到回應 … 連接中 … 通話完結 - 你的聯絡人可以在程式內使用二維碼 連接 網路 & 伺服器 網路設定 diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index ea69b48417..e55407aadb 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -103,6 +103,7 @@ Server requires authorization to create queues, check password Server requires authorization to upload, check password Possibly, certificate fingerprint in server address is incorrect + Error setting address Connect Disconnect Create queue @@ -446,13 +447,24 @@ You will be connected to group when the group host\'s device is online, please wait or check later! You will be connected when your connection request is accepted, please wait or check later! You will be connected when your contact\'s device is online, please wait or check later! - Your contact can scan QR code from the app. If you can\'t meet in person, show QR code in the video call, or share the link. Your chat profile will be sent\nto your contact If you cannot meet in person, you can scan QR code in the video call, or your contact can share an invitation link. - Share invitation link + Share 1-time link Paste the link you received into the box below to connect with your contact. Your chat profile will be sent to your contact + Learn more + About SimpleX address + + + To connect, your contact can scan QR code or use the link in the app. + If you can\'t meet in person, show QR code in a video call, or share the link. + + + You can share your address as a link or QR code - anybody can connect to you. + You won\'t lose your contacts if you later delete your address. + When people request to connect, you can accept or reject it. + Read more in User Guide. Connect via link @@ -464,7 +476,8 @@ Create one-time invitation link One-time invitation link - Your contact address + 1-time link + SimpleX address Scan code @@ -481,7 +494,7 @@ Your settings - Your SimpleX contact address + Your SimpleX address Your chat profiles Database passphrase & export About SimpleX Chat @@ -579,17 +592,25 @@ Create address Delete address? + Your contacts will remain connected. All your contacts will remain connected. - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won\'t lose your contacts if you later delete it. + All your contacts will remain connected. Profile update will be sent to your contacts. Share link + Share address with contacts? + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. + Create an address to let people connect with you. + Create SimpleX address + Share with contacts + Share address with contacts? + Profile update will be sent to your contacts. + Stop sharing address? + Stop sharing + Auto-accept + Enter welcome message… (optional) + Save settings? + Save auto-accept settings Delete address - - Contact requests - Accept requests - Automatically - WELCOME MESSAGE - Display name: "Full name: @@ -1050,6 +1071,9 @@ Error updating group link Error deleting group link Only group owners can change group preferences. + Address + Share address + You can share this address with your contacts to let them connect with %s. FOR CONSOLE @@ -1080,6 +1104,8 @@ Welcome message Save welcome message? Save and update group profile + Preview + Enter welcome message… SERVERS