mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-06-04 12:31:46 +00:00
rfc: namespace (#7001)
* rfc: namespace * update rfc * markdown for names * record type, app "upgrade" alerts * update api types * rfc: change namespace syntax - now it is the usual namespace * update bot types * move types to simplexmq * core: refactore markdown * update simplexmq * better names * new names * update nix content hashes * fix * change valid name function * update simplexq, update valid name conditions * fixes Co-authored-by: simplex-chat-agent[bot] <287173099+simplex-chat-agent[bot]@users.noreply.github.com> * update simplexmq * fix localization * simpler * refactor * refactor * fix --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> Co-authored-by: simplex-chat-agent[bot] <287173099+simplex-chat-agent[bot]@users.noreply.github.com>
This commit is contained in:
+23
@@ -4680,6 +4680,7 @@ sealed class Format {
|
||||
val viaHosts: String get() =
|
||||
"(${String.format(generalGetString(MR.strings.simplex_link_connection), smpHosts.firstOrNull() ?: "?")})"
|
||||
}
|
||||
@Serializable @SerialName("simplexName") class SimplexName(val nameInfo: SimplexNameInfo): Format()
|
||||
@Serializable @SerialName("command") class Command(val commandStr: String): Format()
|
||||
@Serializable @SerialName("mention") class Mention(val memberName: String): Format()
|
||||
@Serializable @SerialName("email") class Email: Format()
|
||||
@@ -4697,6 +4698,7 @@ sealed class Format {
|
||||
is Uri -> linkStyle
|
||||
is HyperLink -> linkStyle
|
||||
is SimplexLink -> linkStyle
|
||||
is SimplexName -> linkStyle
|
||||
is Command -> SpanStyle(color = MaterialTheme.colors.primary, fontFamily = FontFamily.Monospace)
|
||||
is Mention -> SpanStyle(fontWeight = FontWeight.Medium)
|
||||
is Email -> linkStyle
|
||||
@@ -4728,6 +4730,27 @@ enum class SimplexLinkType(val linkType: String) {
|
||||
})
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SimplexNameInfo(
|
||||
val nameType: SimplexNameType,
|
||||
val nameTLD: SimplexTLD,
|
||||
val domain: String,
|
||||
val subDomain: List<String>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
enum class SimplexTLD {
|
||||
@SerialName("simplex") simplex,
|
||||
@SerialName("testing") testing,
|
||||
@SerialName("web") web
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class SimplexNameType {
|
||||
@SerialName("publicGroup") publicGroup,
|
||||
@SerialName("contact") contact
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class FormatColor(val color: String) {
|
||||
red("red"),
|
||||
|
||||
+18
-1
@@ -281,6 +281,13 @@ fun MarkdownText (
|
||||
}
|
||||
}
|
||||
}
|
||||
is Format.SimplexName -> {
|
||||
hasLinks = true
|
||||
val ftStyle = Format.linkStyle
|
||||
withAnnotation(tag = "SIMPLEX_NAME", annotation = i.toString()) {
|
||||
withStyle(ftStyle) { append(ft.text) }
|
||||
}
|
||||
}
|
||||
is Format.Email -> {
|
||||
hasLinks = true
|
||||
val ftStyle = Format.linkStyle
|
||||
@@ -329,6 +336,16 @@ fun MarkdownText (
|
||||
withAnnotation("WEB_URL") { a -> openBrowserAlert(a.item, uriHandler) }
|
||||
withAnnotation("OTHER_URL") { a -> safeOpenUri(a.item, uriHandler) }
|
||||
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)}")
|
||||
}
|
||||
}
|
||||
if (hasSecrets) {
|
||||
withAnnotation("SECRET") { a ->
|
||||
@@ -343,7 +360,7 @@ fun MarkdownText (
|
||||
onHover = { offset ->
|
||||
val hasAnnotation: (String) -> Boolean = { tag -> annotatedText.hasStringAnnotations(tag, start = offset, end = offset) }
|
||||
icon.value =
|
||||
if (hasAnnotation("WEB_URL") || hasAnnotation("SIMPLEX_URL") || hasAnnotation("OTHER_URL") || hasAnnotation("SECRET") || hasAnnotation("COMMAND")) {
|
||||
if (hasAnnotation("WEB_URL") || hasAnnotation("SIMPLEX_URL") || hasAnnotation("OTHER_URL") || hasAnnotation("SIMPLEX_NAME") || hasAnnotation("SECRET") || hasAnnotation("COMMAND")) {
|
||||
PointerIcon.Hand
|
||||
} else {
|
||||
PointerIcon.Text
|
||||
|
||||
+20
-22
@@ -791,31 +791,29 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState
|
||||
snapshotFlow { searchText.value.text }
|
||||
.distinctUntilChanged()
|
||||
.collect {
|
||||
val link = strHasSingleSimplexLink(it.trim())
|
||||
if (link != null) {
|
||||
// if SimpleX link is pasted, show connection dialogue
|
||||
hideKeyboard(view)
|
||||
if (link.format is Format.SimplexLink) {
|
||||
val linkText = link.format.simplexLinkText
|
||||
searchText.value = searchText.value.copy(linkText, selection = TextRange.Zero)
|
||||
when (val target = strConnectTarget(it.trim())) {
|
||||
is ConnectTarget.Link -> {
|
||||
hideKeyboard(view)
|
||||
searchText.value = searchText.value.copy(target.linkText, selection = TextRange.Zero)
|
||||
searchShowingSimplexLink.value = true
|
||||
searchChatFilteredBySimplexLink.value = null
|
||||
connect(target.text, searchChatFilteredBySimplexLink) { searchText.value = TextFieldValue() }
|
||||
}
|
||||
searchShowingSimplexLink.value = true
|
||||
searchChatFilteredBySimplexLink.value = null
|
||||
connect(link.text, searchChatFilteredBySimplexLink) { searchText.value = TextFieldValue() }
|
||||
} else if (!searchShowingSimplexLink.value || it.isEmpty()) {
|
||||
if (it.isNotEmpty()) {
|
||||
// if some other text is pasted, enter search mode
|
||||
focusRequester.requestFocus()
|
||||
} else {
|
||||
if (!chatModel.appOpenUrlConnecting.value) {
|
||||
connectProgressManager.cancelConnectProgress()
|
||||
}
|
||||
if (listState.layoutInfo.totalItemsCount > 0) {
|
||||
listState.scrollToItem(0)
|
||||
is ConnectTarget.Name -> showUnsupportedNameAlert(target.nameInfo)
|
||||
null -> if (!searchShowingSimplexLink.value || it.isEmpty()) {
|
||||
if (it.isNotEmpty()) {
|
||||
focusRequester.requestFocus()
|
||||
} else {
|
||||
if (!chatModel.appOpenUrlConnecting.value) {
|
||||
connectProgressManager.cancelConnectProgress()
|
||||
}
|
||||
if (listState.layoutInfo.totalItemsCount > 0) {
|
||||
listState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
searchShowingSimplexLink.value = false
|
||||
searchChatFilteredBySimplexLink.value = null
|
||||
}
|
||||
searchShowingSimplexLink.value = false
|
||||
searchChatFilteredBySimplexLink.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-13
@@ -30,14 +30,23 @@ suspend fun planAndConnect(
|
||||
filterKnownContact: ((Contact) -> Unit)? = null,
|
||||
filterKnownGroup: ((GroupInfo) -> Unit)? = null,
|
||||
): CompletableDeferred<Boolean> {
|
||||
val link = strHasSingleSimplexLink(shortOrFullLink.trim())
|
||||
if (link?.format is Format.SimplexLink && (link.format as Format.SimplexLink).linkType == SimplexLinkType.relay) {
|
||||
AlertManager.privacySensitive.showAlertMsg(
|
||||
generalGetString(MR.strings.relay_address_alert_title),
|
||||
generalGetString(MR.strings.relay_address_alert_message),
|
||||
)
|
||||
cleanup?.invoke()
|
||||
return CompletableDeferred(false)
|
||||
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(
|
||||
generalGetString(MR.strings.relay_address_alert_title),
|
||||
generalGetString(MR.strings.relay_address_alert_message),
|
||||
)
|
||||
cleanup?.invoke()
|
||||
return CompletableDeferred(false)
|
||||
}
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
connectProgressManager.cancelConnectProgress()
|
||||
val inProgress = mutableStateOf(true)
|
||||
@@ -73,11 +82,8 @@ private suspend fun planAndConnectTask(
|
||||
if (!inProgress.value) { return completable }
|
||||
if (result != null) {
|
||||
val (connectionLink, connectionPlan) = result
|
||||
val link = strHasSingleSimplexLink(shortOrFullLink.trim())
|
||||
val linkText = if (link?.format is Format.SimplexLink)
|
||||
"<br><br><u>${link.format.simplexLinkText}</u>"
|
||||
else
|
||||
""
|
||||
val target = strConnectTarget(shortOrFullLink.trim())
|
||||
val linkText = if (target is ConnectTarget.Link) "<br><br><u>${target.linkText}</u>" else ""
|
||||
when (connectionPlan) {
|
||||
is ConnectionPlan.InvitationLink -> when (connectionPlan.invitationLinkPlan) {
|
||||
is InvitationLinkPlan.Ok ->
|
||||
|
||||
+23
-25
@@ -523,34 +523,32 @@ private fun ContactsSearchBar(
|
||||
snapshotFlow { searchText.value.text }
|
||||
.distinctUntilChanged()
|
||||
.collect {
|
||||
val link = strHasSingleSimplexLink(it.trim())
|
||||
if (link != null) {
|
||||
// if SimpleX link is pasted, show connection dialogue
|
||||
hideKeyboard(view)
|
||||
if (link.format is Format.SimplexLink) {
|
||||
val linkText = link.format.simplexLinkText
|
||||
searchText.value = searchText.value.copy(linkText, selection = TextRange.Zero)
|
||||
when (val target = strConnectTarget(it.trim())) {
|
||||
is ConnectTarget.Link -> {
|
||||
hideKeyboard(view)
|
||||
searchText.value = searchText.value.copy(target.linkText, selection = TextRange.Zero)
|
||||
searchShowingSimplexLink.value = true
|
||||
searchChatFilteredBySimplexLink.value = null
|
||||
connect(
|
||||
link = target.text,
|
||||
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
|
||||
close = close,
|
||||
cleanup = { searchText.value = TextFieldValue() }
|
||||
)
|
||||
}
|
||||
searchShowingSimplexLink.value = true
|
||||
searchChatFilteredBySimplexLink.value = null
|
||||
connect(
|
||||
link = link.text,
|
||||
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
|
||||
close = close,
|
||||
cleanup = { searchText.value = TextFieldValue() }
|
||||
)
|
||||
} else if (!searchShowingSimplexLink.value || it.isEmpty()) {
|
||||
if (it.isNotEmpty()) {
|
||||
// if some other text is pasted, enter search mode
|
||||
focusRequester.requestFocus()
|
||||
} else {
|
||||
connectProgressManager.cancelConnectProgress()
|
||||
if (listState.layoutInfo.totalItemsCount > 0) {
|
||||
listState.scrollToItem(0)
|
||||
is ConnectTarget.Name -> showUnsupportedNameAlert(target.nameInfo)
|
||||
null -> if (!searchShowingSimplexLink.value || it.isEmpty()) {
|
||||
if (it.isNotEmpty()) {
|
||||
focusRequester.requestFocus()
|
||||
} else {
|
||||
connectProgressManager.cancelConnectProgress()
|
||||
if (listState.layoutInfo.totalItemsCount > 0) {
|
||||
listState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
searchShowingSimplexLink.value = false
|
||||
searchChatFilteredBySimplexLink.value = null
|
||||
}
|
||||
searchShowingSimplexLink.value = false
|
||||
searchChatFilteredBySimplexLink.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-12
@@ -671,13 +671,14 @@ private fun PasteLinkView(rhId: Long?, pastedLink: MutableState<String>, showQRC
|
||||
val clipboard = LocalClipboardManager.current
|
||||
SectionItemView({
|
||||
val str = clipboard.getText()?.text ?: return@SectionItemView
|
||||
val link = strHasSingleSimplexLink(str.trim())
|
||||
if (link != null) {
|
||||
pastedLink.value = link.text
|
||||
showQRCodeScanner.value = false
|
||||
withBGApi { connect(rhId, link.text, close) { pastedLink.value = "" } }
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
when (val target = strConnectTarget(str.trim())) {
|
||||
is ConnectTarget.Link -> {
|
||||
pastedLink.value = target.text
|
||||
showQRCodeScanner.value = false
|
||||
withBGApi { connect(rhId, target.text, close) { pastedLink.value = "" } }
|
||||
}
|
||||
is ConnectTarget.Name -> showUnsupportedNameAlert(target.nameInfo)
|
||||
null -> AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_contact_link),
|
||||
text = generalGetString(MR.strings.the_text_you_pasted_is_not_a_link)
|
||||
)
|
||||
@@ -819,12 +820,32 @@ fun strIsSimplexLink(str: String): Boolean {
|
||||
return parsedMd != null && parsedMd.size == 1 && parsedMd[0].format is Format.SimplexLink
|
||||
}
|
||||
|
||||
fun strHasSingleSimplexLink(str: String): FormattedText? {
|
||||
val parsedMd = parseToMarkdown(str) ?: return null
|
||||
val parsedLinks = parsedMd.filter { it.format?.isSimplexLink ?: false }
|
||||
if (parsedLinks.size != 1) return null
|
||||
sealed class ConnectTarget {
|
||||
class Link(val text: String, val linkType: SimplexLinkType, val linkText: String) : ConnectTarget()
|
||||
class Name(val nameInfo: SimplexNameInfo) : ConnectTarget()
|
||||
}
|
||||
|
||||
return parsedLinks[0]
|
||||
fun strConnectTarget(str: String): ConnectTarget? {
|
||||
val parsedMd = parseToMarkdown(str) ?: return null
|
||||
val links = parsedMd.filter { it.format?.isSimplexLink ?: false }
|
||||
if (links.size == 1) {
|
||||
val fmt = links[0].format as Format.SimplexLink
|
||||
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)
|
||||
}
|
||||
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
|
||||
|
||||
@@ -194,6 +194,11 @@
|
||||
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Please check that you used the correct link or ask your contact to send you another one.</string>
|
||||
<string name="unsupported_connection_link">Unsupported connection link</string>
|
||||
<string name="link_requires_newer_app_version_please_upgrade">This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link.</string>
|
||||
<string name="unsupported_channel_name">Unsupported channel name</string>
|
||||
<string name="unsupported_contact_name">Unsupported contact name</string>
|
||||
<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="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