diff --git a/.gitignore b/.gitignore
index 7f75bc93..8033a71f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,7 @@ revoltbuild.properties
sentry.properties
/.kotlin/sessions
app/src/main/assets/embedded
+/app/src/main/jniLibs/arm64-v8a/
+/app/src/main/jniLibs/armeabi-v7a/
+/app/src/main/jniLibs/x86/
+/app/src/main/jniLibs/x86_64/
diff --git a/README.md b/README.md
index 7dd0e77a..6929b8ec 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,20 @@
-# Revolt on Android
+
+
Revolt for Android
+
Official Revolt Android app.
+
+
+

+
+
+
Google Play is a trademark of Google LLC.
+
+
## Description
-This is the official Android app for the [Revolt](https://revolt.chat) chat platform.
The codebase includes the app itself, as well as an internal library for interacting with the Revolt
-API.
-
-| Module | Package | Description |
-|--------|---------------|----------------------|
-| `:app` | `chat.revolt` | The main app module. |
-
-The API library is part of the `app` module, and is not intended to be used as a standalone library,
-as it makes liberal use of Android-specific APIs for reactivity.
-
-The app is written in Kotlin, and uses
-the [Jetpack Compose](https://developer.android.com/jetpack/compose) UI toolkit, the current state
-of the art for Android UI development.
+API. The app is written in Kotlin, and wholly
+uses [Jetpack Compose](https://developer.android.com/jetpack/compose).
## Stack
@@ -29,9 +28,10 @@ of the art for Android UI development.
## Resources
-### Revolt on Android
+### Revolt for Android
-- [Revolt on Android Technical Documentation](https://revoltchat.github.io/android/)
+- [Roadmap](https://op.revolt.wtf/projects/revolt-for-android/work_packages)
+- [Revolt for Android Technical Documentation](https://revoltchat.github.io/android/)
- [Android-specific Contribution Guide](https://revoltchat.github.io/android/contributing/guidelines/)
—**read carefully before contributing!**
@@ -39,8 +39,9 @@ of the art for Android UI development.
- [Revolt Project Board](https://github.com/revoltchat/revolt/discussions) (Submit feature requests
here)
-- [Revolt Testers Server](https://app.revolt.chat/invite/Testers)
-- [General Revolt Contribution Guide](https://developers.revolt.chat/contributing)
+- [Revolt Development Server](https://app.revolt.chat/invite/API)
+- [Revolt Server](https://app.revolt.chat/invite/Testers)
+- [General Revolt Contribution Guide](https://developers.revolt.chat/contrib.html)
## Quick Start
@@ -48,4 +49,4 @@ Open the project in Android Studio. You can then run the app on an emulator or a
running the `app` module.
In-depth setup instructions can be found
-at [Setting up your Development Environment](https://revoltchat.github.io/android/contributing/setup/)
\ No newline at end of file
+at [Setting up your Development Environment](https://revoltchat.github.io/android/contributing/setup/)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a4ca9686..a807dbcf 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -73,12 +73,12 @@ fun buildproperty(propertyName: String, fallbackEnv: String? = null): String? {
}
android {
- compileSdk = 35
+ compileSdk = 36
defaultConfig {
applicationId = "chat.revolt"
minSdk = 24
- targetSdk = 35
+ targetSdk = 36
versionCode = Integer.parseInt("001_003_206".replace("_", ""), 10)
versionName = "1.3.6b"
diff --git a/app/src/main/java/chat/revolt/RevoltApplication.kt b/app/src/main/java/chat/revolt/RevoltApplication.kt
index e0999f8e..a9936361 100644
--- a/app/src/main/java/chat/revolt/RevoltApplication.kt
+++ b/app/src/main/java/chat/revolt/RevoltApplication.kt
@@ -1,6 +1,7 @@
package chat.revolt
import android.app.Application
+import android.os.StrictMode
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp
import logcat.AndroidLogcatLogger
@@ -15,6 +16,21 @@ class RevoltApplication : Application() {
override fun onCreate() {
super.onCreate()
AndroidLogcatLogger.installOnDebuggableApp(this, minPriority = LogPriority.VERBOSE)
+
+ if (BuildConfig.DEBUG) {
+ // Enable strict mode primarily to catch non-API usage, although we detect all
+ // violations for our reference.
+ // https://developer.android.com/reference/android/os/StrictMode
+ StrictMode.setVmPolicy(
+ StrictMode.VmPolicy
+ .Builder()
+ .apply {
+ detectAll()
+ penaltyLog()
+ }
+ .build()
+ )
+ }
}
init {
diff --git a/app/src/main/java/chat/revolt/api/settings/Experiments.kt b/app/src/main/java/chat/revolt/api/settings/Experiments.kt
index 818d997e..41edc140 100644
--- a/app/src/main/java/chat/revolt/api/settings/Experiments.kt
+++ b/app/src/main/java/chat/revolt/api/settings/Experiments.kt
@@ -29,6 +29,7 @@ object Experiments {
val useKotlinBasedMarkdownRenderer = ExperimentInstance(false)
val usePolar = ExperimentInstance(false)
val enableServerIdentityOptions = ExperimentInstance(false)
+ val useFinalMarkdownRenderer = ExperimentInstance(false)
suspend fun hydrateWithKv() {
val kvStorage = KVStorage(RevoltApplication.instance)
@@ -48,5 +49,14 @@ object Experiments {
enableServerIdentityOptions.setEnabled(
kvStorage.getBoolean("exp/enableServerIdentityOptions") == true
)
+ useFinalMarkdownRenderer.setEnabled(
+ kvStorage.getBoolean("exp/useFinalMarkdownRenderer") == true
+ )
+
+ if (useFinalMarkdownRenderer.isEnabled && useKotlinBasedMarkdownRenderer.isEnabled) {
+ // if jbm and fm are enabled, fm takes precedence. this should not be possible in practice
+ useKotlinBasedMarkdownRenderer.setEnabled(false)
+ kvStorage.set("exp/useKotlinBasedMarkdownRenderer", false)
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/chat/revolt/api/settings/FeatureFlags.kt b/app/src/main/java/chat/revolt/api/settings/FeatureFlags.kt
index f7dee8c9..28dfac83 100644
--- a/app/src/main/java/chat/revolt/api/settings/FeatureFlags.kt
+++ b/app/src/main/java/chat/revolt/api/settings/FeatureFlags.kt
@@ -56,6 +56,19 @@ sealed class VoiceChannels2_0Variates {
object Disabled : VoiceChannels2_0Variates()
}
+@FeatureFlag("FinalMarkdown")
+sealed class FinalMarkdownVariates {
+ @Treatment(
+ "Enable the new FinalMarkdown library for all users"
+ )
+ object Enabled : FinalMarkdownVariates()
+
+ @Treatment(
+ "Disable the new FinalMarkdown library for all users"
+ )
+ object Disabled : FinalMarkdownVariates()
+}
+
object FeatureFlags {
@FeatureFlag("LabsAccessControl")
var labsAccessControl by mutableStateOf(
@@ -101,4 +114,14 @@ object FeatureFlags {
is VoiceChannels2_0Variates.Enabled -> true
is VoiceChannels2_0Variates.Disabled -> false
}
+
+ @FeatureFlag("FinalMarkdown")
+ var finalMarkdown by mutableStateOf(
+ FinalMarkdownVariates.Disabled
+ )
+ val finalMarkdownGranted: Boolean
+ get() = when (finalMarkdown) {
+ is FinalMarkdownVariates.Enabled -> true
+ is FinalMarkdownVariates.Disabled -> false
+ }
}
diff --git a/app/src/main/java/chat/revolt/ndk/FinalMarkdown.kt b/app/src/main/java/chat/revolt/ndk/FinalMarkdown.kt
new file mode 100644
index 00000000..e80247dc
--- /dev/null
+++ b/app/src/main/java/chat/revolt/ndk/FinalMarkdown.kt
@@ -0,0 +1,14 @@
+package chat.revolt.ndk
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class FinalMarkdownNodeTest(
+ val test: Int,
+)
+
+@Suppress("KotlinJniMissingFunction")
+object FinalMarkdown {
+ external fun init()
+ external fun process(input: String): FinalMarkdownNodeTest
+}
\ No newline at end of file
diff --git a/app/src/main/java/chat/revolt/ndk/NativeLibraries.kt b/app/src/main/java/chat/revolt/ndk/NativeLibraries.kt
index 10af43e7..e8bc19dd 100644
--- a/app/src/main/java/chat/revolt/ndk/NativeLibraries.kt
+++ b/app/src/main/java/chat/revolt/ndk/NativeLibraries.kt
@@ -3,12 +3,15 @@ package chat.revolt.ndk
annotation class NativeLibrary(val name: String) {
companion object {
const val LIB_NAME_NATIVE_MARKDOWN = "stendal"
+ const val LIB_NAME_NATIVE_MARKDOWN_V2 = "finalmarkdown"
}
}
object NativeLibraries {
fun init() {
System.loadLibrary(NativeLibrary.LIB_NAME_NATIVE_MARKDOWN)
+ System.loadLibrary(NativeLibrary.LIB_NAME_NATIVE_MARKDOWN_V2)
Stendal.init()
+ FinalMarkdown.init()
}
}
diff --git a/app/src/main/java/chat/revolt/screens/settings/ExperimentsSettingsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/ExperimentsSettingsScreen.kt
index 5b931589..fce5374f 100644
--- a/app/src/main/java/chat/revolt/screens/settings/ExperimentsSettingsScreen.kt
+++ b/app/src/main/java/chat/revolt/screens/settings/ExperimentsSettingsScreen.kt
@@ -2,21 +2,36 @@ package chat.revolt.screens.settings
import android.content.Context
import android.content.Intent
+import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonGroupDefaults
import androidx.compose.material3.ElevatedButton
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.ListItem
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
+import androidx.compose.material3.ToggleButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -24,18 +39,35 @@ import androidx.navigation.NavController
import chat.revolt.BuildConfig
import chat.revolt.RevoltApplication
import chat.revolt.api.settings.Experiments
+import chat.revolt.api.settings.FeatureFlags
import chat.revolt.api.settings.LoadedSettings
import chat.revolt.persistence.KVStorage
import chat.revolt.settings.dsl.SettingsPage
import chat.revolt.settings.dsl.SubcategoryContentInsets
import kotlinx.coroutines.launch
+enum class MarkdownRenderer {
+ Stendal, JetBrains, FinalMarkdown
+}
+
class ExperimentsSettingsScreenViewModel : ViewModel() {
private val kv = KVStorage(RevoltApplication.instance)
fun init() {
viewModelScope.launch {
- useKotlinMdRendererChecked.value = Experiments.useKotlinBasedMarkdownRenderer.isEnabled
+ when {
+ Experiments.useKotlinBasedMarkdownRenderer.isEnabled -> {
+ mdRenderer.value = MarkdownRenderer.JetBrains
+ }
+
+ Experiments.useFinalMarkdownRenderer.isEnabled -> {
+ mdRenderer.value = MarkdownRenderer.FinalMarkdown
+ }
+
+ else -> {
+ mdRenderer.value = MarkdownRenderer.Stendal
+ }
+ }
usePolarChecked.value = Experiments.usePolar.isEnabled
enableServerIdentityOptionsChecked.value =
Experiments.enableServerIdentityOptions.isEnabled
@@ -66,13 +98,33 @@ class ExperimentsSettingsScreenViewModel : ViewModel() {
}
}
- val useKotlinMdRendererChecked = mutableStateOf(false)
+ val mdRenderer = mutableStateOf(MarkdownRenderer.Stendal)
- fun setUseKotlinMdRendererChecked(value: Boolean) {
+ fun setMdRenderer(value: MarkdownRenderer) {
viewModelScope.launch {
- kv.set("exp/useKotlinBasedMarkdownRenderer", value)
- Experiments.useKotlinBasedMarkdownRenderer.setEnabled(value)
- useKotlinMdRendererChecked.value = value
+ when (value) {
+ MarkdownRenderer.Stendal -> {
+ kv.set("exp/useKotlinBasedMarkdownRenderer", false)
+ Experiments.useKotlinBasedMarkdownRenderer.setEnabled(false)
+ kv.set("exp/useFinalMarkdownRenderer", false)
+ Experiments.useFinalMarkdownRenderer.setEnabled(false)
+ }
+
+ MarkdownRenderer.JetBrains -> {
+ kv.set("exp/useKotlinBasedMarkdownRenderer", true)
+ Experiments.useKotlinBasedMarkdownRenderer.setEnabled(true)
+ kv.set("exp/useFinalMarkdownRenderer", false)
+ Experiments.useFinalMarkdownRenderer.setEnabled(false)
+ }
+
+ MarkdownRenderer.FinalMarkdown -> {
+ kv.set("exp/useKotlinBasedMarkdownRenderer", false)
+ Experiments.useKotlinBasedMarkdownRenderer.setEnabled(false)
+ kv.set("exp/useFinalMarkdownRenderer", true)
+ Experiments.useFinalMarkdownRenderer.setEnabled(true)
+ }
+ }
+ mdRenderer.value = value
}
}
@@ -98,6 +150,7 @@ class ExperimentsSettingsScreenViewModel : ViewModel() {
}
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ExperimentsSettingsScreen(
navController: NavController,
@@ -147,21 +200,63 @@ fun ExperimentsSettingsScreen(
Text("Experiments", maxLines = 1, overflow = TextOverflow.Ellipsis)
}
) {
- ListItem(
- headlineContent = {
- Text("New Message Markdown Renderer")
- },
- supportingContent = {
- Text("Use a Kotlin-based Markdown renderer for messages rather than the C++ one. Missing features may be present.")
- },
- trailingContent = {
- Switch(
- checked = viewModel.useKotlinMdRendererChecked.value,
- onCheckedChange = null
- )
- },
- modifier = Modifier.clickable { viewModel.setUseKotlinMdRendererChecked(!viewModel.useKotlinMdRendererChecked.value) }
- )
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ ListItem(
+ headlineContent = {
+ Text("Markdown Renderer")
+ },
+ supportingContent = {
+ when (viewModel.mdRenderer.value) {
+ MarkdownRenderer.Stendal -> Text("Use the original C++ Markdown renderer for messages.")
+ MarkdownRenderer.JetBrains -> Text("Use the Kotlin-based JetBrains Markdown renderer for messages. This renderer is more feature-complete and has better results.")
+ MarkdownRenderer.FinalMarkdown -> Text("Use a new. blazingly fast markdown renderer for messages. This renderer is experimental and may have missing features.")
+ }
+ },
+ modifier = Modifier
+ .animateContentSize()
+ .weight(1f)
+ )
+ Column(
+ modifier = Modifier
+ .width(IntrinsicSize.Max)
+ .padding(end = 8.dp),
+ verticalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
+ ) {
+ ToggleButton(
+ checked = viewModel.mdRenderer.value == MarkdownRenderer.Stendal,
+ onCheckedChange = { viewModel.setMdRenderer(MarkdownRenderer.Stendal) },
+ modifier = Modifier
+ .fillMaxWidth()
+ .semantics { role = Role.RadioButton }
+ ) {
+ Text("Default")
+ }
+ ToggleButton(
+ checked = viewModel.mdRenderer.value == MarkdownRenderer.JetBrains,
+ onCheckedChange = { viewModel.setMdRenderer(MarkdownRenderer.JetBrains) },
+ modifier = Modifier
+ .fillMaxWidth()
+ .semantics { role = Role.RadioButton }
+ ) {
+ Text("Kotlin")
+ }
+ if (FeatureFlags.finalMarkdownGranted || viewModel.mdRenderer.value == MarkdownRenderer.FinalMarkdown) {
+ ToggleButton(
+ checked = viewModel.mdRenderer.value == MarkdownRenderer.FinalMarkdown,
+ onCheckedChange = { viewModel.setMdRenderer(MarkdownRenderer.FinalMarkdown) },
+ enabled = FeatureFlags.finalMarkdownGranted,
+ modifier = Modifier
+ .fillMaxWidth()
+ .semantics { role = Role.RadioButton }
+ ) {
+ Text("Final")
+ }
+ }
+ }
+ }
ListItem(
headlineContent = {
diff --git a/app/src/main/jniLibs/.gitkeep b/app/src/main/jniLibs/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7695422e..4aff6a52 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -85,7 +85,7 @@
Enter your password to continue.
About
- Revolt on Android
+ Revolt for Android
Brought to you with ❤️ by the Revolt team.
Version
Details
diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index 9d925410..1f6d2134 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -26,20 +26,7 @@ export default defineConfig({
link: "/contributing/setup",
}
],
- },
- {
- label: "Beta Test",
- items: [
- {
- label: "Geographic Availability",
- link: "/beta/availability-regions",
- },
- {
- label: "Join the Beta Test",
- link: "/beta/joining-the-public-beta-test",
- }
- ],
- },
+ }
/* {
label: "Reference",
autogenerate: { directory: "reference" },
diff --git a/docs/src/content/docs/beta/availability-regions.md b/docs/src/content/docs/beta/availability-regions.md
deleted file mode 100644
index 11b658b4..00000000
--- a/docs/src/content/docs/beta/availability-regions.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-title: Geographic Availability of the Beta Test
-description: Find out where you can join the beta test for Revolt on Android.
-template: doc
----
-
-The beta test for Revolt on Android is available for testers in the following jurisdictions:
-
-- Armenia
-- Australia
-- Austria
-- Azerbaijan
-- Belgium
-- Brazil
-- Bulgaria
-- Canada
-- Croatia
-- Cyprus
-- Czechia
-- Denmark (Kingdom of)
-- Dominican Republic
-- Estonia
-- Finland (including Åland)
-- France (including overseas départements and other outre-mer territories)
-- Georgia
-- Germany
-- Gibraltar
-- Greece
-- Hungary
-- Iceland
-- Ireland
-- Israel
-- Italy
-- Japan
-- Latvia
-- Liechtenstein
-- Lithuania
-- Luxembourg
-- Malaysia
-- Malta
-- Moldova
-- Monaco
-- Netherlands
-- New Zealand
-- Norway (Kingdom of)
-- Poland
-- Portugal
-- Romania
-- San Marino
-- Slovakia
-- Slovenia
-- South Africa
-- South Korea
-- Spain
-- Sweden
-- Switzerland
-- Türkiye
-- Ukraine
-- United Kingdom
-- United States (including Territories of the U.S.)
-- Vatican City
-
-## My region isn't listed!
-
-While we don't offer the beta test in *all* regions of the world, we may and most likely will add regions that are not listed upon request. Please request the addition on [Jenvolt](https://rvlt.gg/jen) (if you beta test, you are probably already in the server!).
\ No newline at end of file
diff --git a/docs/src/content/docs/beta/joining-the-public-beta-test.md b/docs/src/content/docs/beta/joining-the-public-beta-test.md
deleted file mode 100644
index 75754a0b..00000000
--- a/docs/src/content/docs/beta/joining-the-public-beta-test.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: Joining the Public Beta Test
-description: Learn how to join the public beta test for Revolt for Android.
-template: doc
----
-
-The public beta test for Revolt for Android is available to anyone with an Android device
-in [those jurisdictions](https://revoltchat.github.io/android/beta/availability-regions/) where the
-beta test is available. The beta test is open to everyone, and you can join the beta test by
-following these steps:
-
-1. [Join the Revolt for Android Alpha Track on Google Groups](https://groups.google.com/g/revolt-android-alpha-track).
-2. [Opt-in to the Revolt for Android Beta Test on Google Play](https://play.google.com/apps/testing/chat.revolt).
-3. [Download Revolt for Android from Google Play](https://play.google.com/store/apps/details?id=chat.revolt).
-
-You're all set! You can now use Revolt on Android and help us test new features and improvements.
-
-If you encounter any issues or have any feedback, please let us know
-by [reporting an issue](https://androidtasks.starlightjen.com/)
-or [joining the Revolt Developers server](https://rvlt.gg/api).
\ No newline at end of file
diff --git a/docs/src/content/docs/contributing/setup.mdx b/docs/src/content/docs/contributing/setup.mdx
index 6bfb440e..741c0737 100644
--- a/docs/src/content/docs/contributing/setup.mdx
+++ b/docs/src/content/docs/contributing/setup.mdx
@@ -107,6 +107,18 @@ import { Tabs, TabItem, Steps } from "@astrojs/starlight/components"
deno run -A scripts/download_deps.ts
```
+ :::tip
+ You can run this script with the `-y` flag to skip confirmation prompts.
+ :::
+
+ :::note
+ This script will query the Revolt version control server for the latest version of the
+ final-markdown native library and download it. If you do not wish to use pre-built libraries,
+ you can build final-markdown yourself from the source code at https://git.revolt.chat/android/final-markdown,
+ and copy the files to the `app/src/main/jniLibs` folder. Note the files that are downloaded are
+ exactly the same as the ones we use for the app on Google Play, so you can be sure they are safe to use.
+ :::
+
8. Copy the `revoltbuild.properties.example` file to `revoltbuild.properties` and fill in the required values.
```sh
diff --git a/scripts/download_deps.ts b/scripts/download_deps.ts
index c9d252ab..618b8233 100644
--- a/scripts/download_deps.ts
+++ b/scripts/download_deps.ts
@@ -1,5 +1,6 @@
import { resolve } from "jsr:@std/path"
import { parseArgs } from "jsr:@std/cli/parse-args"
+import { ZipReader, Uint8ArrayReader, Uint8ArrayWriter } from "jsr:@zip-js/zip-js"
const args = parseArgs(Deno.args, {
boolean: ["yes", "y", "auto-accept"],
@@ -11,9 +12,11 @@ const args = parseArgs(Deno.args, {
const autoAccept = args.yes || args["auto-accept"]
const outputFolderParent = resolve(Deno.cwd(), "app", "src", "main", "assets")
+const jniLibsFolder = resolve(Deno.cwd(), "app", "src", "main", "jniLibs")
try {
Deno.statSync(outputFolderParent)
+ Deno.statSync(jniLibsFolder)
} catch (_) {
console.error(
"\x1b[31m" + // red
@@ -330,4 +333,80 @@ for (const dep of deps) {
console.log(`Downloaded ${dep.file} to ${file}`)
}
-console.log("Done.")
+const libsQuery = "https://git.revolt.chat/api/v1/repos/android/final-markdown/releases/latest"
+
+console.log("The script will now query the Revolt version control server for the latest version"
+ + " of the final-markdown native library and download it. If you do not wish to use pre-built"
+ + " libraries, you can build final-markdown yourself from the source code at"
+ + " https://git.revolt.chat/android/final-markdown, and copy the files to the"
+ + " app/src/main/jniLibs folder. Note the files that are downloaded are exactly the same as"
+ + " the ones we use for the app on Google Play, so you can be sure they are safe to use.")
+
+if (!autoAccept) {
+ console.log(`Will now query ${libsQuery} for the latest version of the library and download it.`)
+ if (!confirm("Continue?")) {
+ console.log("Aborted.")
+ Deno.exit(0)
+ }
+} else {
+ console.log(`Will now query ${libsQuery} for the latest version of the library and download it. (auto-accepted)`)
+}
+
+const queryLibsRes = await fetch(libsQuery)
+if (!queryLibsRes.ok) {
+ console.error(`Failed to fetch the latest library version: ${queryLibsRes.status} ${queryLibsRes.statusText}`)
+ Deno.exit(1)
+}
+
+const libsJson = await queryLibsRes.json()
+const zipUrl = libsJson
+ .assets
+ .find((asset: { name: string }) => asset.name === "jniLibs.zip")?.browser_download_url
+
+if (!zipUrl) {
+ console.error("No jniLibs.zip found in the latest release.")
+ Deno.exit(1)
+}
+
+const libsRes = await fetch(zipUrl)
+if (!libsRes.ok) {
+ console.error(`Failed to fetch the jniLibs.zip: ${libsRes.status} ${libsRes.statusText}`)
+ Deno.exit(1)
+}
+const libsData = await libsRes.arrayBuffer()
+
+const libArchitectures = ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"]
+
+const zipFileBytes = new Uint8Array(libsData)
+
+const zipReader = new ZipReader(new Uint8ArrayReader(zipFileBytes))
+
+const entries = await zipReader.getEntries()
+
+const createDirPromises = libArchitectures.map(arch => {
+ const dirPath = resolve(jniLibsFolder, arch)
+ return Deno.mkdir(dirPath, { recursive: true })
+})
+await Promise.all(createDirPromises)
+
+const writeFilePromises = libArchitectures.map(async arch => {
+ const filePathInZip = `${arch}/libfinalmarkdown.so`
+
+ const entry = entries.find(e => e.filename === filePathInZip)
+
+ if (!entry) {
+ throw new Error(`Expected file not found in zip: ${filePathInZip}`)
+ }
+
+ const writer = new Uint8ArrayWriter()
+ const fileData = await entry.getData!(writer)
+
+ const destinationPath = resolve(jniLibsFolder, filePathInZip)
+ return Deno.writeFile(destinationPath, fileData)
+})
+
+await Promise.all(writeFilePromises)
+
+// Close the zip reader
+await zipReader.close()
+