mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-06-03 21:21:46 +00:00
Merge branch 'stable'
This commit is contained in:
+23
@@ -4711,6 +4711,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()
|
||||
@@ -4728,6 +4729,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
|
||||
@@ -4759,6 +4761,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"),
|
||||
|
||||
+1
@@ -6994,6 +6994,7 @@ sealed class GroupLinkPlan {
|
||||
@Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val groupInfo_: GroupInfo? = null): GroupLinkPlan()
|
||||
@Serializable @SerialName("known") class Known(val groupInfo: GroupInfo): GroupLinkPlan()
|
||||
@Serializable @SerialName("noRelays") class NoRelays(val groupSLinkData_: GroupShortLinkData? = null): GroupLinkPlan()
|
||||
@Serializable @SerialName("updateRequired") class UpdateRequired(val groupSLinkData_: GroupShortLinkData? = null): GroupLinkPlan()
|
||||
}
|
||||
|
||||
abstract class TerminalItem {
|
||||
|
||||
+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
@@ -792,31 +792,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
-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 ->
|
||||
@@ -316,6 +322,33 @@ private suspend fun planAndConnectTask(
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
is GroupLinkPlan.UpdateRequired -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .UpdateRequired")
|
||||
val groupSLinkData = connectionPlan.groupLinkPlan.groupSLinkData_
|
||||
if (groupSLinkData != null) {
|
||||
AlertManager.privacySensitive.showOpenChatAlert(
|
||||
profileName = groupSLinkData.groupProfile.displayName,
|
||||
profileFullName = groupSLinkData.groupProfile.fullName,
|
||||
profileImage = {
|
||||
ProfileImage(
|
||||
size = alertProfileImageSize,
|
||||
image = groupSLinkData.groupProfile.image,
|
||||
icon = MR.images.ic_supervised_user_circle_filled
|
||||
)
|
||||
},
|
||||
subtitle = generalGetString(MR.strings.group_link_requires_newer_version),
|
||||
confirmText = null,
|
||||
dismissText = generalGetString(MR.strings.ok),
|
||||
onDismiss = { cleanup() }
|
||||
)
|
||||
} else {
|
||||
AlertManager.privacySensitive.showAlertMsg(
|
||||
generalGetString(MR.strings.app_update_required),
|
||||
generalGetString(MR.strings.group_link_requires_newer_version)
|
||||
)
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
is ConnectionPlan.Error -> {
|
||||
Log.d(TAG, "planAndConnect, error ${connectionPlan.chatError}")
|
||||
|
||||
+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,8 +194,15 @@
|
||||
<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>
|
||||
<string name="group_link_requires_newer_version">This group requires a newer version of the app. Please update the app to join.</string>
|
||||
<string name="connection_error_auth">Connection error (AUTH)</string>
|
||||
<string name="connection_error_auth_desc">Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection.</string>
|
||||
<string name="connection_error_blocked">Connection blocked</string>
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ val desktopPlatform = detectDesktopPlatform()
|
||||
|
||||
enum class DesktopPlatform(val libExtension: String, val configPath: String, val dataPath: String, val githubAssetName: String) {
|
||||
LINUX_X86_64("so", unixConfigPath, unixDataPath, "simplex-desktop-x86_64.AppImage"),
|
||||
LINUX_AARCH64("so", unixConfigPath, unixDataPath, " simplex-desktop-aarch64.AppImage"),
|
||||
LINUX_AARCH64("so", unixConfigPath, unixDataPath, "simplex-desktop-aarch64.AppImage"),
|
||||
WINDOWS_X86_64("dll", System.getenv("AppData") + File.separator + "SimpleX", System.getenv("AppData") + File.separator + "SimpleX", "simplex-desktop-windows-x86_64.msi"),
|
||||
MAC_X86_64("dylib", unixConfigPath, unixDataPath, "simplex-desktop-macos-x86_64.dmg"),
|
||||
MAC_AARCH64("dylib", unixConfigPath, unixDataPath, "simplex-desktop-macos-aarch64.dmg");
|
||||
|
||||
+8
-6
@@ -6,6 +6,7 @@ import androidx.compose.ui.graphics.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.*
|
||||
import org.jetbrains.compose.videoplayer.SkiaBitmapVideoSurface
|
||||
import uk.co.caprica.vlcj.media.VideoOrientation
|
||||
import uk.co.caprica.vlcj.player.base.*
|
||||
import uk.co.caprica.vlcj.player.component.CallbackMediaPlayerComponent
|
||||
@@ -214,7 +215,7 @@ actual class VideoPlayer actual constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getBitmapFromVideo(defaultPreview: ImageBitmap?, uri: URI?, withAlertOnException: Boolean = true): VideoPlayerInterface.PreviewAndDuration = withContext(playerThread.asCoroutineDispatcher()) {
|
||||
suspend fun getBitmapFromVideo(defaultPreview: ImageBitmap?, uri: URI?, withAlertOnException: Boolean = true): VideoPlayerInterface.PreviewAndDuration = withContext(previewThread.asCoroutineDispatcher()) {
|
||||
val mediaComponent = getOrCreateHelperPlayer()
|
||||
val player = mediaComponent.mediaPlayer()
|
||||
if (uri == null || !uri.toFile().exists()) {
|
||||
@@ -222,12 +223,12 @@ actual class VideoPlayer actual constructor(
|
||||
|
||||
return@withContext VideoPlayerInterface.PreviewAndDuration(preview = defaultPreview, timestamp = 0L, duration = 0L)
|
||||
}
|
||||
val surface = SkiaBitmapVideoSurface()
|
||||
player.videoSurface().set(surface)
|
||||
player.media().startPaused(uri.toFile().absolutePath)
|
||||
val start = System.currentTimeMillis()
|
||||
var snap: BufferedImage? = null
|
||||
while (snap == null && start + 1500 > System.currentTimeMillis()) {
|
||||
snap = player.snapshots()?.get()
|
||||
delay(50)
|
||||
val snap = withTimeoutOrNull(1500L) {
|
||||
while (surface.bitmap.value == null) delay(50)
|
||||
surface.bitmap.value!!.toAwtImage()
|
||||
}
|
||||
val orientation = player.media().info().videoTracks().firstOrNull()?.orientation()
|
||||
if (orientation == null) {
|
||||
@@ -255,6 +256,7 @@ actual class VideoPlayer actual constructor(
|
||||
}
|
||||
|
||||
val playerThread = Executors.newSingleThreadExecutor()
|
||||
private val previewThread = Executors.newSingleThreadExecutor()
|
||||
private val playersPool: ArrayList<Component> = ArrayList()
|
||||
private val helperPlayersPool: ArrayList<CallbackMediaPlayerComponent> = ArrayList()
|
||||
|
||||
|
||||
+38
-12
@@ -26,6 +26,8 @@ import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import kotlin.math.min
|
||||
|
||||
data class SemVer(
|
||||
@@ -376,7 +378,7 @@ private fun chooseGitHubReleaseAssets(release: GitHubRelease): List<GitHubAsset>
|
||||
val res = if (isRunningFromFlatpak()) {
|
||||
// No need to show download options for Flatpak users
|
||||
emptyList()
|
||||
} else if (!isRunningFromAppImage() && Runtime.getRuntime().exec("which dpkg").onExit().join().exitValue() == 0) {
|
||||
} else if (desktopPlatform.isLinux() && !isRunningFromAppImage() && Runtime.getRuntime().exec("which dpkg").onExit().join().exitValue() == 0) {
|
||||
// Show all available .deb packages and user will choose the one that works on his system (for Debian derivatives)
|
||||
release.assets.filter { it.name.lowercase().endsWith(".deb") }
|
||||
} else {
|
||||
@@ -388,18 +390,42 @@ private fun chooseGitHubReleaseAssets(release: GitHubRelease): List<GitHubAsset>
|
||||
private suspend fun installAppUpdate(file: File) = withContext(Dispatchers.IO) {
|
||||
when {
|
||||
desktopPlatform.isLinux() -> {
|
||||
val process = Runtime.getRuntime().exec("xdg-open ${file.absolutePath}").onExit().join()
|
||||
val startedInstallation = process.exitValue() == 0 && process.children().count() > 0
|
||||
if (!startedInstallation) {
|
||||
Log.e(TAG, "Error starting installation: ${process.inputReader().use { it.readLines().joinToString("\n") }}${process.errorStream.use { String(it.readAllBytes()) }}")
|
||||
// Failed to start installation. show directory with the file for manual installation
|
||||
desktopOpenDir(file.parentFile)
|
||||
val appImagePath = System.getenv("APPIMAGE")
|
||||
if (appImagePath != null) {
|
||||
// Replace the running AppImage crash-safely: copy onto the target's own
|
||||
// filesystem first (an atomic rename only works within one filesystem, and
|
||||
// the download lives in the temp dir which is usually a different one),
|
||||
// then atomically move the staged file onto $APPIMAGE.
|
||||
val target = File(appImagePath)
|
||||
val staging = File(target.parentFile, ".${target.name}.update")
|
||||
try {
|
||||
Files.copy(file.toPath(), staging.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
staging.setExecutable(true, false)
|
||||
Files.move(staging.toPath(), target.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
|
||||
file.delete()
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.app_check_for_updates_installed_successfully_title),
|
||||
text = generalGetString(MR.strings.app_check_for_updates_installed_successfully_desc)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to replace AppImage: ${e.stackTraceToString()}")
|
||||
staging.delete()
|
||||
desktopOpenDir(file.parentFile)
|
||||
}
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.app_check_for_updates_installed_successfully_title),
|
||||
text = generalGetString(MR.strings.app_check_for_updates_installed_successfully_desc)
|
||||
)
|
||||
file.delete()
|
||||
val process = Runtime.getRuntime().exec("xdg-open ${file.absolutePath}").onExit().join()
|
||||
val startedInstallation = process.exitValue() == 0 && process.children().count() > 0
|
||||
if (!startedInstallation) {
|
||||
Log.e(TAG, "Error starting installation: ${process.inputReader().use { it.readLines().joinToString("\n") }}${process.errorStream.use { String(it.readAllBytes()) }}")
|
||||
// Failed to start installation. show directory with the file for manual installation
|
||||
desktopOpenDir(file.parentFile)
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.app_check_for_updates_installed_successfully_title),
|
||||
text = generalGetString(MR.strings.app_check_for_updates_installed_successfully_desc)
|
||||
)
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
desktopPlatform.isWindows() -> {
|
||||
|
||||
Reference in New Issue
Block a user