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.

+

+
+ Get it on Google Play +
+
+ 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() +