mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-11 20:38:55 +00:00
android, desktop, ios: connect via SimpleX name
This commit is contained in:
@@ -1083,6 +1083,21 @@ private func apiConnectResponseAlert<R>(_ r: APIResult<R>) -> Alert {
|
||||
title: "Unsupported connection link",
|
||||
message: "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link."
|
||||
)
|
||||
case let .error(.simplexNameNotFound(name)):
|
||||
mkAlert(
|
||||
title: name.nameType == .contact ? "Contact name not found" : "Channel name not found",
|
||||
message: "There is no contact or group registered with this SimpleX name."
|
||||
)
|
||||
case .error(.simplexNameUnprepared):
|
||||
mkAlert(
|
||||
title: "Cannot reconnect via name",
|
||||
message: "This SimpleX name is known but has no saved link to reconnect via."
|
||||
)
|
||||
case .error(.simplexNameResolverUnavailable):
|
||||
mkAlert(
|
||||
title: "Name resolution unavailable",
|
||||
message: "None of your SMP servers support resolving SimpleX names. Add a server that does, or use a connection link."
|
||||
)
|
||||
case .errorAgent(.SMP(_, .AUTH)):
|
||||
mkAlert(
|
||||
title: "Connection error (AUTH)",
|
||||
|
||||
@@ -214,8 +214,10 @@ private func handleTextTaps(
|
||||
var simplex: Bool = false
|
||||
s.enumerateAttributes(in: NSRange(location: 0, length: s.length)) { attrs, range, stop in
|
||||
if index >= range.location && index < range.location + range.length {
|
||||
if let nameInfo = attrs[nameAttrKey] as? SimplexNameInfo {
|
||||
showUnsupportedNameAlert(nameInfo)
|
||||
if attrs[nameAttrKey] is SimplexNameInfo {
|
||||
// Route the tapped name through the same connect flow as a link;
|
||||
// planAndConnect resolves it on the core (name target).
|
||||
planAndConnect(s.attributedSubstring(from: range).string, theme: theme, dismiss: false)
|
||||
} else if let url = attrs[linkAttrKey] as? String {
|
||||
linkURL = url
|
||||
browser = attrs[webLinkAttrKey] != nil
|
||||
|
||||
@@ -683,8 +683,9 @@ struct ChatListSearchBar: View {
|
||||
searchShowingSimplexLink = true
|
||||
searchChatFilteredBySimplexLink = nil
|
||||
connect(text)
|
||||
case let .name(nameInfo):
|
||||
showUnsupportedNameAlert(nameInfo)
|
||||
case let .name(text, _):
|
||||
searchFocussed = false
|
||||
connect(text)
|
||||
case .none:
|
||||
if t != "" {
|
||||
searchFocussed = true
|
||||
|
||||
@@ -389,8 +389,9 @@ struct ContactsListSearchBar: View {
|
||||
searchShowingSimplexLink = true
|
||||
searchChatFilteredBySimplexLink = nil
|
||||
connect(text)
|
||||
case let .name(nameInfo):
|
||||
showUnsupportedNameAlert(nameInfo)
|
||||
case let .name(text, _):
|
||||
searchFocussed = false
|
||||
connect(text)
|
||||
case .none:
|
||||
if t != "" {
|
||||
searchFocussed = true
|
||||
|
||||
@@ -669,8 +669,9 @@ private struct ConnectView: View {
|
||||
case let .link(text, _, _):
|
||||
pastedLink = text
|
||||
connect(pastedLink)
|
||||
case let .name(nameInfo):
|
||||
showUnsupportedNameAlert(nameInfo)
|
||||
case let .name(text, _):
|
||||
pastedLink = text
|
||||
connect(pastedLink)
|
||||
case .none:
|
||||
alert = .newChatSomeAlert(alert: SomeAlert(
|
||||
alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."),
|
||||
@@ -869,7 +870,7 @@ func strIsSimplexLink(_ str: String) -> Bool {
|
||||
|
||||
enum ConnectTarget {
|
||||
case link(text: String, linkType: SimplexLinkType, linkText: String)
|
||||
case name(SimplexNameInfo)
|
||||
case name(text: String, nameInfo: SimplexNameInfo)
|
||||
}
|
||||
|
||||
func strConnectTarget(_ str: String) -> ConnectTarget? {
|
||||
@@ -878,28 +879,14 @@ func strConnectTarget(_ str: String) -> ConnectTarget? {
|
||||
return if links.count == 1, case let .simplexLink(_, linkType, _, smpHosts) = links[0].format {
|
||||
.link(text: links[0].text, linkType: linkType, linkText: simplexLinkText(linkType, smpHosts))
|
||||
} else if links.isEmpty,
|
||||
case let .simplexName(nameInfo) = parsedMd?.first(where: { if case .simplexName = $0.format { true } else { false } })?.format {
|
||||
.name(nameInfo)
|
||||
let nameFt = parsedMd?.first(where: { if case .simplexName = $0.format { true } else { false } }),
|
||||
case let .simplexName(nameInfo) = nameFt.format {
|
||||
.name(text: nameFt.text, nameInfo: nameInfo)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
func showUnsupportedNameAlert(_ nameInfo: SimplexNameInfo) {
|
||||
let upgrade = " " + NSLocalizedString("Please upgrade the app.", comment: "alert message")
|
||||
if nameInfo.nameType == .contact {
|
||||
showAlert(
|
||||
NSLocalizedString("Unsupported contact name", comment: "alert title"),
|
||||
message: NSLocalizedString("Connecting via contact name requires a newer app version.", comment: "alert message") + upgrade
|
||||
)
|
||||
} else {
|
||||
showAlert(
|
||||
NSLocalizedString("Unsupported channel name", comment: "alert title"),
|
||||
message: NSLocalizedString("Connecting via channel name requires a newer app version.", comment: "alert message") + upgrade
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct IncognitoToggle: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Binding var incognitoEnabled: Bool
|
||||
@@ -1319,10 +1306,6 @@ func planAndConnect(
|
||||
filterKnownGroup: ((GroupInfo) -> Void)? = nil
|
||||
) {
|
||||
switch strConnectTarget(shortOrFullLink) {
|
||||
case let .name(nameInfo):
|
||||
showUnsupportedNameAlert(nameInfo)
|
||||
cleanup?()
|
||||
return
|
||||
case let .link(_, linkType, _):
|
||||
if linkType == .relay {
|
||||
showAlert(
|
||||
@@ -1332,7 +1315,9 @@ func planAndConnect(
|
||||
cleanup?()
|
||||
return
|
||||
}
|
||||
case .none: break
|
||||
// A SimplexName falls through to apiConnectPlan, which resolves it on the
|
||||
// core (the /_connect plan command accepts a name target, not only a link).
|
||||
case .name, .none: break
|
||||
}
|
||||
ConnectProgressManager.shared.cancelConnectProgress()
|
||||
let inProgress = BoxedValue(true)
|
||||
|
||||
@@ -741,6 +741,9 @@ public enum ChatErrorType: Decodable, Hashable {
|
||||
case chatNotStopped
|
||||
case chatStoreChanged
|
||||
case invalidConnReq
|
||||
case simplexNameNotFound(simplexName: SimplexNameInfo)
|
||||
case simplexNameUnprepared(simplexName: SimplexNameInfo)
|
||||
case simplexNameResolverUnavailable(simplexName: SimplexNameInfo)
|
||||
case unsupportedConnReq
|
||||
case invalidChatMessage(connection: Connection, message: String)
|
||||
case connReqMessageProhibited
|
||||
|
||||
+33
@@ -1555,6 +1555,27 @@ object ChatController {
|
||||
generalGetString(MR.strings.link_requires_newer_app_version_please_upgrade)
|
||||
)
|
||||
}
|
||||
r is API.Error && r.err is ChatError.ChatErrorChat
|
||||
&& r.err.errorType is ChatErrorType.SimplexNameNotFound -> {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.simplex_name_not_found),
|
||||
generalGetString(MR.strings.simplex_name_not_found_desc)
|
||||
)
|
||||
}
|
||||
r is API.Error && r.err is ChatError.ChatErrorChat
|
||||
&& r.err.errorType is ChatErrorType.SimplexNameUnprepared -> {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.cannot_reconnect_via_simplex_name),
|
||||
generalGetString(MR.strings.simplex_name_unprepared_desc)
|
||||
)
|
||||
}
|
||||
r is API.Error && r.err is ChatError.ChatErrorChat
|
||||
&& r.err.errorType is ChatErrorType.SimplexNameResolverUnavailable -> {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.simplex_name_resolution_unavailable),
|
||||
generalGetString(MR.strings.simplex_name_resolver_unavailable_desc)
|
||||
)
|
||||
}
|
||||
r is API.Error && r.err is ChatError.ChatErrorAgent
|
||||
&& r.err.agentError is AgentErrorType.SMP
|
||||
&& r.err.agentError.smpErr is SMPErrorType.AUTH -> {
|
||||
@@ -1592,6 +1613,12 @@ object ChatController {
|
||||
generalGetString(MR.strings.invalid_connection_link)
|
||||
e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.UnsupportedConnReq ->
|
||||
generalGetString(MR.strings.unsupported_connection_link)
|
||||
e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.SimplexNameNotFound ->
|
||||
generalGetString(MR.strings.simplex_name_not_found)
|
||||
e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.SimplexNameUnprepared ->
|
||||
generalGetString(MR.strings.cannot_reconnect_via_simplex_name)
|
||||
e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.SimplexNameResolverUnavailable ->
|
||||
generalGetString(MR.strings.simplex_name_resolution_unavailable)
|
||||
e is ChatError.ChatErrorAgent && e.agentError is AgentErrorType.SMP && e.agentError.smpErr is SMPErrorType.AUTH ->
|
||||
generalGetString(MR.strings.connection_error_auth)
|
||||
e is ChatError.ChatErrorAgent && e.agentError is AgentErrorType.SMP && e.agentError.smpErr is SMPErrorType.BLOCKED ->
|
||||
@@ -7297,6 +7324,9 @@ sealed class ChatErrorType {
|
||||
is ChatStoreChanged -> "chatStoreChanged"
|
||||
is ConnectionPlanChatError -> "connectionPlan"
|
||||
is InvalidConnReq -> "invalidConnReq"
|
||||
is SimplexNameNotFound -> "simplexNameNotFound"
|
||||
is SimplexNameUnprepared -> "simplexNameUnprepared"
|
||||
is SimplexNameResolverUnavailable -> "simplexNameResolverUnavailable"
|
||||
is UnsupportedConnReq -> "unsupportedConnReq"
|
||||
is InvalidChatMessage -> "invalidChatMessage"
|
||||
is ConnReqMessageProhibited -> "connReqMessageProhibited"
|
||||
@@ -7379,6 +7409,9 @@ sealed class ChatErrorType {
|
||||
@Serializable @SerialName("chatStoreChanged") object ChatStoreChanged: ChatErrorType()
|
||||
@Serializable @SerialName("connectionPlan") class ConnectionPlanChatError(val connectionPlan: ConnectionPlan): ChatErrorType()
|
||||
@Serializable @SerialName("invalidConnReq") object InvalidConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("simplexNameNotFound") class SimplexNameNotFound(val simplexName: SimplexNameInfo): ChatErrorType()
|
||||
@Serializable @SerialName("simplexNameUnprepared") class SimplexNameUnprepared(val simplexName: SimplexNameInfo): ChatErrorType()
|
||||
@Serializable @SerialName("simplexNameResolverUnavailable") class SimplexNameResolverUnavailable(val simplexName: SimplexNameInfo): ChatErrorType()
|
||||
@Serializable @SerialName("unsupportedConnReq") object UnsupportedConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("connReqMessageProhibited") object ConnReqMessageProhibited: ChatErrorType()
|
||||
|
||||
+4
-7
@@ -338,13 +338,10 @@ fun MarkdownText (
|
||||
withAnnotation("SIMPLEX_URL") { a -> uriHandler.openVerifiedSimplexUri(a.item) }
|
||||
withAnnotation("SIMPLEX_NAME") { a ->
|
||||
val idx = a.item.toIntOrNull()
|
||||
val nameInfo = (idx?.let { formattedText.getOrNull(it) }?.format as? Format.SimplexName)?.nameInfo
|
||||
val (title, msg) = if (nameInfo?.nameType == SimplexNameType.contact) {
|
||||
generalGetString(MR.strings.unsupported_contact_name) to generalGetString(MR.strings.contact_name_requires_newer_app_version)
|
||||
} else {
|
||||
generalGetString(MR.strings.unsupported_channel_name) to generalGetString(MR.strings.channel_name_requires_newer_app_version)
|
||||
}
|
||||
AlertManager.shared.showAlertMsg(title, "$msg ${generalGetString(MR.strings.please_upgrade_the_app)}")
|
||||
val nameText = idx?.let { formattedText.getOrNull(it) }?.text
|
||||
// The name string is routed through the same connect path as a
|
||||
// link; planAndConnect resolves it on the core (name target).
|
||||
if (nameText != null) uriHandler.openVerifiedSimplexUri(nameText)
|
||||
}
|
||||
}
|
||||
if (hasSecrets) {
|
||||
|
||||
+4
-1
@@ -800,7 +800,10 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState
|
||||
searchChatFilteredBySimplexLink.value = null
|
||||
connect(target.text, searchChatFilteredBySimplexLink) { searchText.value = TextFieldValue() }
|
||||
}
|
||||
is ConnectTarget.Name -> showUnsupportedNameAlert(target.nameInfo)
|
||||
is ConnectTarget.Name -> {
|
||||
hideKeyboard(view)
|
||||
connect(target.text, searchChatFilteredBySimplexLink) { searchText.value = TextFieldValue() }
|
||||
}
|
||||
null -> if (!searchShowingSimplexLink.value || it.isEmpty()) {
|
||||
if (it.isNotEmpty()) {
|
||||
focusRequester.requestFocus()
|
||||
|
||||
+3
-6
@@ -31,11 +31,6 @@ suspend fun planAndConnect(
|
||||
filterKnownGroup: ((GroupInfo) -> Unit)? = null,
|
||||
): CompletableDeferred<Boolean> {
|
||||
when (val target = strConnectTarget(shortOrFullLink.trim())) {
|
||||
is ConnectTarget.Name -> {
|
||||
showUnsupportedNameAlert(target.nameInfo)
|
||||
cleanup?.invoke()
|
||||
return CompletableDeferred(false)
|
||||
}
|
||||
is ConnectTarget.Link -> {
|
||||
if (target.linkType == SimplexLinkType.relay) {
|
||||
AlertManager.privacySensitive.showAlertMsg(
|
||||
@@ -46,7 +41,9 @@ suspend fun planAndConnect(
|
||||
return CompletableDeferred(false)
|
||||
}
|
||||
}
|
||||
null -> {}
|
||||
// A SimplexName falls through to apiConnectPlan, which resolves it on the
|
||||
// core (the /_connect plan command accepts a name target, not only a link).
|
||||
is ConnectTarget.Name, null -> {}
|
||||
}
|
||||
connectProgressManager.cancelConnectProgress()
|
||||
val inProgress = mutableStateOf(true)
|
||||
|
||||
+9
-1
@@ -536,7 +536,15 @@ private fun ContactsSearchBar(
|
||||
cleanup = { searchText.value = TextFieldValue() }
|
||||
)
|
||||
}
|
||||
is ConnectTarget.Name -> showUnsupportedNameAlert(target.nameInfo)
|
||||
is ConnectTarget.Name -> {
|
||||
hideKeyboard(view)
|
||||
connect(
|
||||
link = target.text,
|
||||
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
|
||||
close = close,
|
||||
cleanup = { searchText.value = TextFieldValue() }
|
||||
)
|
||||
}
|
||||
null -> if (!searchShowingSimplexLink.value || it.isEmpty()) {
|
||||
if (it.isNotEmpty()) {
|
||||
focusRequester.requestFocus()
|
||||
|
||||
+9
-13
@@ -679,7 +679,11 @@ private fun PasteLinkView(rhId: Long?, pastedLink: MutableState<String>, showQRC
|
||||
showQRCodeScanner.value = false
|
||||
withBGApi { connect(rhId, target.text, close) { pastedLink.value = "" } }
|
||||
}
|
||||
is ConnectTarget.Name -> showUnsupportedNameAlert(target.nameInfo)
|
||||
is ConnectTarget.Name -> {
|
||||
pastedLink.value = target.text
|
||||
showQRCodeScanner.value = false
|
||||
withBGApi { connect(rhId, target.text, close) { pastedLink.value = "" } }
|
||||
}
|
||||
null -> AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_contact_link),
|
||||
text = generalGetString(MR.strings.the_text_you_pasted_is_not_a_link)
|
||||
@@ -824,7 +828,7 @@ fun strIsSimplexLink(str: String): Boolean {
|
||||
|
||||
sealed class ConnectTarget {
|
||||
class Link(val text: String, val linkType: SimplexLinkType, val linkText: String) : ConnectTarget()
|
||||
class Name(val nameInfo: SimplexNameInfo) : ConnectTarget()
|
||||
class Name(val text: String, val nameInfo: SimplexNameInfo) : ConnectTarget()
|
||||
}
|
||||
|
||||
fun strConnectTarget(str: String): ConnectTarget? {
|
||||
@@ -835,21 +839,13 @@ fun strConnectTarget(str: String): ConnectTarget? {
|
||||
return ConnectTarget.Link(links[0].text, fmt.linkType, fmt.simplexLinkText)
|
||||
}
|
||||
if (links.isEmpty()) {
|
||||
val nameInfo = parsedMd.firstNotNullOfOrNull { (it.format as? Format.SimplexName)?.nameInfo }
|
||||
if (nameInfo != null) return ConnectTarget.Name(nameInfo)
|
||||
val nameFt = parsedMd.firstOrNull { it.format is Format.SimplexName }
|
||||
val nameInfo = (nameFt?.format as? Format.SimplexName)?.nameInfo
|
||||
if (nameFt != null && nameInfo != null) return ConnectTarget.Name(nameFt.text, nameInfo)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun showUnsupportedNameAlert(nameInfo: SimplexNameInfo) {
|
||||
val (title, msg) = if (nameInfo.nameType == SimplexNameType.contact) {
|
||||
generalGetString(MR.strings.unsupported_contact_name) to generalGetString(MR.strings.contact_name_requires_newer_app_version)
|
||||
} else {
|
||||
generalGetString(MR.strings.unsupported_channel_name) to generalGetString(MR.strings.channel_name_requires_newer_app_version)
|
||||
}
|
||||
AlertManager.shared.showAlertMsg(title, "$msg ${generalGetString(MR.strings.please_upgrade_the_app)}")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun IncognitoToggle(
|
||||
incognitoPref: SharedPreference<Boolean>,
|
||||
|
||||
@@ -199,6 +199,12 @@
|
||||
<string name="channel_name_requires_newer_app_version">Connecting via channel name requires a newer app version.</string>
|
||||
<string name="contact_name_requires_newer_app_version">Connecting via contact name requires a newer app version.</string>
|
||||
<string name="please_upgrade_the_app">Please upgrade the app.</string>
|
||||
<string name="simplex_name_not_found">SimpleX name not found</string>
|
||||
<string name="simplex_name_not_found_desc">There is no contact or group registered with this SimpleX name.</string>
|
||||
<string name="cannot_reconnect_via_simplex_name">Cannot reconnect via name</string>
|
||||
<string name="simplex_name_unprepared_desc">This SimpleX name is known but has no saved link to reconnect via.</string>
|
||||
<string name="simplex_name_resolution_unavailable">Name resolution unavailable</string>
|
||||
<string name="simplex_name_resolver_unavailable_desc">None of your SMP servers support resolving SimpleX names. Add a server that does, or use a connection link.</string>
|
||||
<string name="channel_temporarily_unavailable">Channel temporarily unavailable</string>
|
||||
<string name="channel_no_active_relays_try_later">Channel has no active relays. Please try to join later.</string>
|
||||
<string name="app_update_required">App update required</string>
|
||||
|
||||
Reference in New Issue
Block a user