mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 12:53:18 +00:00
Merge branch 'stable' into stable-android
This commit is contained in:
35 files changed
+1197
-501
No files matched your search
@@ -16,6 +16,7 @@ android/build
|
||||
android/release
|
||||
common/build
|
||||
desktop/build
|
||||
external/nanohttpd/build
|
||||
release
|
||||
|
||||
# Generated SimpleX assets
|
||||
|
||||
@@ -6,6 +6,15 @@ This is a guide to contributing to the develop of the SimpleX android and deskto
|
||||
|
||||
This is the **Kotlin Multiplatform (KMP)** mobile and desktop client for SimpleX Chat, sharing code between Android and Desktop (JVM) platforms using Compose Multiplatform for UI.
|
||||
|
||||
## Setup
|
||||
|
||||
The desktop app builds nanohttpd from a submodule, Android does not use it. Before building
|
||||
the desktop app on a fresh checkout:
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
|
||||
@@ -148,8 +148,7 @@ kotlin {
|
||||
implementation("org.slf4j:slf4j-simple:2.0.12")
|
||||
implementation("uk.co.caprica:vlcj:4.8.3")
|
||||
implementation("net.java.dev.jna:jna:5.14.0")
|
||||
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf")
|
||||
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf")
|
||||
implementation(project(":external:nanohttpd"))
|
||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
}
|
||||
}
|
||||
@@ -213,7 +212,7 @@ afterEvaluate {
|
||||
val fontLtGtRegex = Regex("[^>]*>.*<font[^>]*>.*</font>.*</string>")
|
||||
val unbracketedColorRegex = Regex("color=#[abcdefABCDEF0-9]{3,6}")
|
||||
val correctHtmlRegex = Regex("[^>]*>.*<b>.*</b>.*</string>|[^>]*>.*<i>.*</i>.*</string>|[^>]*>.*<u>.*</u>.*</string>|[^>]*>.*<font[^>]*>.*</font>.*</string>")
|
||||
val possibleFormat = listOf("s", "d", "1\$s", "2\$s", "3\$s", "4\$s", "1\$d", "2\$d", "3\$d", "4\$d", "2s", "f")
|
||||
val possibleFormat = listOf("s", "d", "1\$s", "2\$s", "3\$s", "4\$s", "1\$d", "2\$d", "3\$d", "4\$d", "1\$02d", "2\$02d", "3\$02d", "2s", "f")
|
||||
|
||||
fun String.id(): String = replace("<string name=\"", "").trim().substringBefore("\"")
|
||||
|
||||
@@ -240,7 +239,7 @@ afterEvaluate {
|
||||
if (was.length == substring.length) break
|
||||
}
|
||||
return if (formats.any { it.startsWith("1$") || it.startsWith("2$") || it.startsWith("3$") || it.startsWith("4$") }) {
|
||||
formats.sortedBy { it.trim('s', 'd', 'f', '$').toIntOrNull() ?: throw Exception("Formatting don't have positional arguments: $this \nin $filepath") }
|
||||
formats.sortedBy { it.substringBefore('$').toIntOrNull() ?: throw Exception("Formatting don't have positional arguments: $this \nin $filepath") }
|
||||
} else {
|
||||
formats
|
||||
}
|
||||
|
||||
+1
-1
@@ -944,7 +944,7 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
VersionDescription(
|
||||
// the trailing space differs from the previously released "v7.0", so that What's new is shown again
|
||||
version = if (isInUs()) "v7.0.1" else "v7.0",
|
||||
post = null,
|
||||
post = "https://simplex.chat/blog/20260819-simplex-chat-crowdfunding.html",
|
||||
features = listOf(
|
||||
VersionFeature.FeatureView(
|
||||
icon = null,
|
||||
|
||||
+43
-2
@@ -14,7 +14,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.*
|
||||
@@ -24,10 +25,14 @@ import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.datetime.*
|
||||
|
||||
// Each dot-separated label is ASCII letters/digits with single internal hyphens (mirrors simplexmq SimplexName.hs nameLabelP).
|
||||
private val simplexNameLabelRegex = Regex("[A-Za-z0-9]+(-[A-Za-z0-9]+)*")
|
||||
|
||||
private val simplexNameSaleStart = LocalDateTime(2026, 12, 12, 18, 0).toInstant(TimeZone.UTC)
|
||||
private const val SIMPLEX_DOMAINS_URL = "https://simplex.domains/"
|
||||
|
||||
// Set the user's own (prefix "@") or a channel's (prefix "#") SimpleX name.
|
||||
// The field is prefilled with the full prefixed name; `save` receives the encoded name (or null to
|
||||
// clear) and returns true on success (it shows its own error alert otherwise).
|
||||
@@ -128,6 +133,13 @@ fun SetSimplexDomainView(
|
||||
}
|
||||
}
|
||||
|
||||
fun saleCountdown(msRemaining: Long): String {
|
||||
val total = (msRemaining / 1000).coerceAtLeast(0)
|
||||
val days = total / 86400
|
||||
val dayStr = String.format(generalGetString(if (days == 1L) MR.strings.ttl_day else MR.strings.ttl_days), days)
|
||||
return dayStr + " " + String.format(generalGetString(MR.strings.countdown_hrs_min_sec), total / 3600 % 24, total / 60 % 60, total % 60)
|
||||
}
|
||||
|
||||
ModalView(close = { onClose(close) }, cardScreen = true) {
|
||||
ColumnWithScrollBar {
|
||||
AppBarTitle(title)
|
||||
@@ -163,7 +175,7 @@ fun SetSimplexDomainView(
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_open_in_new),
|
||||
stringResource(MR.strings.register_test_name),
|
||||
{ openBrowserAlert("https://github.com/simplex-chat/simplex-chat/blob/master/docs/guide/register-simplex-name.md", uriHandler) },
|
||||
{ openBrowserAlert("https://simplex.domains/#testing", uriHandler) },
|
||||
textColor = MaterialTheme.colors.primary,
|
||||
iconColor = MaterialTheme.colors.primary
|
||||
)
|
||||
@@ -179,6 +191,35 @@ fun SetSimplexDomainView(
|
||||
}
|
||||
}
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
val msToSaleStart = remember { mutableStateOf(simplexNameSaleStart.toEpochMilliseconds() - System.currentTimeMillis()) }
|
||||
LaunchedEffect(Unit) {
|
||||
while (msToSaleStart.value > 0) {
|
||||
delay(1000)
|
||||
msToSaleStart.value = simplexNameSaleStart.toEpochMilliseconds() - System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
SectionView(stringResource(MR.strings.simplex_name_sales)) {
|
||||
SectionItemView {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(saleCountdown(msToSaleStart.value))
|
||||
Text(
|
||||
stringResource(if (msToSaleStart.value > 0) MR.strings.until_register_simplex_domain else MR.strings.update_app_register_simplex_domain),
|
||||
color = MaterialTheme.colors.secondary,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
SectionTextFooter(buildAnnotatedString {
|
||||
append(generalGetString(MR.strings.simplex_name_sales_footer))
|
||||
append(" ")
|
||||
withLink(LinkAnnotation.Url(SIMPLEX_DOMAINS_URL) { uriHandler.openUriCatching(SIMPLEX_DOMAINS_URL) }) {
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.primary)) {
|
||||
append("simplex.domains")
|
||||
}
|
||||
}
|
||||
})
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +397,11 @@
|
||||
<string name="get_simplex_name_beta">Get SimpleX name (BETA)</string>
|
||||
<string name="channel_simplex_name">Channel SimpleX name</string>
|
||||
<string name="register_test_name">How to register a test name</string>
|
||||
<string name="simplex_name_sales">SimpleX name sale starts in</string>
|
||||
<string name="until_register_simplex_domain">until you can register a SimpleX domain</string>
|
||||
<string name="update_app_register_simplex_domain">Update the app to register a SimpleX domain</string>
|
||||
<string name="countdown_hrs_min_sec">%1$02d hrs %2$02d min %3$02d sec</string>
|
||||
<string name="simplex_name_sales_footer">Crowdfunding investors can reserve names before the sale starts:</string>
|
||||
<string name="remove_name">Remove name</string>
|
||||
<string name="save_verb">Save</string>
|
||||
<string name="edit_verb">Edit</string>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
// Built from the upstream submodule pinned at efb2ebf85a2b06f7c508aba9eaad5377e3a01e81, because
|
||||
// upstream never released the org.nanohttpd packages and JitPack no longer serves or builds that
|
||||
// commit. Only the core and websocket modules are used, the samples are not.
|
||||
group = "org.nanohttpd"
|
||||
version = "efb2ebf"
|
||||
|
||||
val upstream = layout.projectDirectory.dir("upstream")
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
setSrcDirs(listOf(upstream.dir("core/src/main/java"), upstream.dir("websocket/src/main/java")))
|
||||
exclude("org/nanohttpd/samples/**")
|
||||
}
|
||||
resources.setSrcDirs(listOf(upstream.dir("core/src/main/resources")))
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
val jvmVersion = JavaVersion.toVersion(providers.gradleProperty("kotlin.jvm.target").get())
|
||||
sourceCompatibility = jvmVersion
|
||||
targetCompatibility = jvmVersion
|
||||
}
|
||||
|
||||
val upstreamSources = sourceSets.main.get().java.matching { include("org/nanohttpd/**") }
|
||||
|
||||
// compileJava and jar silently succeed without the sources, so the check needs its own task.
|
||||
// It cannot run during configuration, Android builds must work without the submodule.
|
||||
val checkUpstreamSources by tasks.registering {
|
||||
doFirst {
|
||||
if (upstreamSources.isEmpty) {
|
||||
throw GradleException("nanohttpd sources are missing, run: git submodule update --init --recursive")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
dependsOn(checkUpstreamSources)
|
||||
}
|
||||
|
||||
// Without this the jar records the build machine's timestamps, file order and file modes,
|
||||
// which makes the desktop packages unreproducible
|
||||
tasks.jar {
|
||||
isPreserveFileTimestamps = false
|
||||
isReproducibleFileOrder = true
|
||||
filePermissions { unix("644") }
|
||||
dirPermissions { unix("755") }
|
||||
}
|
||||
+1
Submodule apps/multiplatform/external/nanohttpd/upstream added at efb2ebf85a.
@@ -18,4 +18,4 @@ pluginManagement {
|
||||
|
||||
rootProject.name = "app"
|
||||
|
||||
include(":android", ":desktop", ":common")
|
||||
include(":android", ":desktop", ":common", ":external:nanohttpd")
|
||||
Reference in new issue
Block a user