Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,10 @@ tasks.named("preBuild") {



// When the vendored :lui module is used (no ../lui/platform/android
// composite build), resolve the dev.lui:lui coordinate to it.
if (rootProject.findProject(":lui") != null) {
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute(module("dev.lui:lui")).using(project(":lui"))
}
}
}

dependencies {
// Resolved to ../lui/platform/android when the Kotlin LUI backend
// checkout is present (composite build substitution), otherwise to
// the vendored :lui module — see settings.gradle.kts.
implementation("dev.lui:lui")
// The real Kotlin LUI backend, included from
// ../lui/platform/android/lui — see settings.gradle.kts.
implementation(project(":lui"))

implementation(platform("androidx.compose:compose-bom:2025.09.00"))
implementation("androidx.compose.ui:ui")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@ internal class LogseqChatNativeBridge(
if (patch.isNotEmpty()) onPatch(patch)
}

// platform 3 = AndroidOS, host 3 = the Android host slot the core
// renders for (the legacy Android host kind in
// shared/src/logseq_chat/native_bridge.ml; the Kotlin backend takes
// over that host profile).
// platform 3 = AndroidOS, host 4 = KotlinHost (see the host-code table
// in shared/src/logseq_chat/native_bridge.ml, matching lui's
// components_bridge.ml).
fun initialize(
platformCode: Int = 3,
hostCode: Int = 3,
hostCode: Int = 4,
authenticationCode: Int = 1,
): Long {
apply(decode(NativeCore.luiInitialize(platformCode, hostCode, authenticationCode)))
Expand Down
14 changes: 14 additions & 0 deletions android/app/src/main/kotlin/com/logseq/chat/runtime/LuiDispatch.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ internal fun dispatchLuiEvent(event: LuiEvent, native: LogseqChatNativeBridge) {
extensionText(event.values),
extensionValue(event.values),
)
// The chat core ABI exports no pointer-level, context-menu,
// press-modifier, or load entries (see
// shared/native/logseq_chat_core_ffi.h), so these events are
// dropped here — matching the upstream dispatchToBridge's
// treatment of events with no mobile export.
is LuiEvent.PressModifiers,
is LuiEvent.PressDetail,
is LuiEvent.PointerDown,
is LuiEvent.PointerUp,
is LuiEvent.PointerEnter,
is LuiEvent.PointerLeave,
is LuiEvent.ContextMenuPress,
is LuiEvent.Load,
-> Unit
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import dev.lui.LuiExtensionContext
import dev.lui.LuiExtensionEventSchema
import dev.lui.LuiExtensionProperty
import dev.lui.LuiExtensionRegistry
import dev.lui.LuiExtensionValueKind

// Port of the former logseq_chat_extensions.dart — the chat-specific
// extension hosts. Fingerprints are generated by the OCaml core and must
// match byte-for-byte.
private const val EDITOR_FINGERPRINT =
"lui-extension-v1|15:outliner-editor|profiles:android/flutter," +
"lui-extension-v1|15:outliner-editor|profiles:android/kotlin," +
"ios/swiftui|standard-children:0|children:|" +
"properties:18:caret-utf16-offset:int:required:none,5:title:string:" +
"required:none,8:block-id:string:required:none|events:11:text-change[" +
Expand All @@ -41,7 +44,7 @@ private const val EDITOR_FINGERPRINT =
"required,5:title:string:required],9:backspace[16:selection-length:int:" +
"required,5:title:string:required]"
private const val RICH_FINGERPRINT =
"lui-extension-v1|22:outliner-block-content|profiles:android/flutter," +
"lui-extension-v1|22:outliner-block-content|profiles:android/kotlin," +
"ios/swiftui|standard-children:0|children:|" +
"properties:10:asset-type:string:required:none,10:local-path:string:" +
"required:none,11:markup-json:string:required:none,12:is-completed:bool:" +
Expand All @@ -51,14 +54,14 @@ private const val RICH_FINGERPRINT =
"9:placement:string:required],4:edit[4:uuid:string:required],9:open-node[" +
"4:uuid:string:required]"
private const val NAVIGATION_FINGERPRINT =
"lui-extension-v1|23:native-navigation-stack|profiles:android/flutter," +
"lui-extension-v1|23:native-navigation-stack|profiles:android/kotlin," +
"ios/swiftui|standard-children:1|children:|" +
"properties:26:composer-dismissal-enabled:bool:required:none," +
"28:bottom-occupies-layout-space:bool:required:none,5:depth:int:required:none," +
"5:title:string:required:none|events:16:dismiss-composer[],4:back[" +
"5:count:int:required]"
private const val SEARCH_FINGERPRINT =
"lui-extension-v1|26:native-search-presentation|profiles:android/flutter," +
"lui-extension-v1|26:native-search-presentation|profiles:android/kotlin," +
"ios/swiftui|standard-children:1|children:|" +
"properties:5:depth:int:required:none,5:query:string:required:none," +
"5:title:string:required:none,9:presented:bool:required:none|events:" +
Expand All @@ -68,17 +71,127 @@ private const val SEARCH_FINGERPRINT =
fun logseqChatExtensionRegistry(
resolveAssetPath: (String) -> String = { it },
): LuiExtensionRegistry = LuiExtensionRegistry().apply {
register("outliner-editor", EDITOR_FINGERPRINT) { context ->
OutlinerEditor(context)
register(
"outliner-editor",
EDITOR_FINGERPRINT,
properties = listOf(
LuiExtensionProperty("caret-utf16-offset", LuiExtensionValueKind.INTEGER),
LuiExtensionProperty("title", LuiExtensionValueKind.STRING),
LuiExtensionProperty("block-id", LuiExtensionValueKind.STRING),
),
events = listOf(
LuiExtensionEventSchema(
"text-change",
listOf(
LuiExtensionProperty("caret-utf16-offset", LuiExtensionValueKind.INTEGER, required = true),
LuiExtensionProperty("title", LuiExtensionValueKind.STRING, required = true),
),
),
LuiExtensionEventSchema(
"caret-change",
listOf(
LuiExtensionProperty("caret-utf16-offset", LuiExtensionValueKind.INTEGER, required = true),
),
),
LuiExtensionEventSchema(
"return",
listOf(
LuiExtensionProperty("caret-utf16-offset", LuiExtensionValueKind.INTEGER, required = true),
LuiExtensionProperty("title", LuiExtensionValueKind.STRING, required = true),
),
),
LuiExtensionEventSchema(
"backspace",
listOf(
LuiExtensionProperty("selection-length", LuiExtensionValueKind.INTEGER, required = true),
LuiExtensionProperty("caret-utf16-offset", LuiExtensionValueKind.INTEGER, required = true),
LuiExtensionProperty("title", LuiExtensionValueKind.STRING, required = true),
),
),
),
) {
OutlinerEditor(this)
}
register("outliner-block-content", RICH_FINGERPRINT) { context ->
OutlinerBlockContent(context, resolveAssetPath)
register(
"outliner-block-content",
RICH_FINGERPRINT,
properties = listOf(
LuiExtensionProperty("asset-type", LuiExtensionValueKind.STRING),
LuiExtensionProperty("local-path", LuiExtensionValueKind.STRING),
LuiExtensionProperty("markup-json", LuiExtensionValueKind.STRING),
LuiExtensionProperty("is-completed", LuiExtensionValueKind.BOOLEAN),
LuiExtensionProperty("youtube-target-url", LuiExtensionValueKind.STRING),
LuiExtensionProperty("title", LuiExtensionValueKind.STRING),
LuiExtensionProperty("block-id", LuiExtensionValueKind.STRING),
LuiExtensionProperty("is-asset", LuiExtensionValueKind.BOOLEAN),
),
events = listOf(
LuiExtensionEventSchema(
"drag-start",
listOf(LuiExtensionProperty("uuid", LuiExtensionValueKind.STRING, required = true)),
),
LuiExtensionEventSchema(
"drop",
listOf(
LuiExtensionProperty("uuid", LuiExtensionValueKind.STRING, required = true),
LuiExtensionProperty("placement", LuiExtensionValueKind.STRING, required = true),
),
),
LuiExtensionEventSchema(
"edit",
listOf(LuiExtensionProperty("uuid", LuiExtensionValueKind.STRING, required = true)),
),
LuiExtensionEventSchema(
"open-node",
listOf(LuiExtensionProperty("uuid", LuiExtensionValueKind.STRING, required = true)),
),
),
) {
OutlinerBlockContent(this, resolveAssetPath)
}
register("native-navigation-stack", NAVIGATION_FINGERPRINT) { context ->
NavigationBoundary(context)
register(
"native-navigation-stack",
NAVIGATION_FINGERPRINT,
acceptsStandardChildren = true,
properties = listOf(
LuiExtensionProperty("composer-dismissal-enabled", LuiExtensionValueKind.BOOLEAN),
LuiExtensionProperty("bottom-occupies-layout-space", LuiExtensionValueKind.BOOLEAN),
LuiExtensionProperty("depth", LuiExtensionValueKind.INTEGER),
LuiExtensionProperty("title", LuiExtensionValueKind.STRING),
),
events = listOf(
LuiExtensionEventSchema("dismiss-composer"),
LuiExtensionEventSchema(
"back",
listOf(LuiExtensionProperty("count", LuiExtensionValueKind.INTEGER, required = true)),
),
),
) {
NavigationBoundary(this)
}
register("native-search-presentation", SEARCH_FINGERPRINT) { context ->
SearchPresentation(context)
register(
"native-search-presentation",
SEARCH_FINGERPRINT,
acceptsStandardChildren = true,
properties = listOf(
LuiExtensionProperty("depth", LuiExtensionValueKind.INTEGER),
LuiExtensionProperty("query", LuiExtensionValueKind.STRING),
LuiExtensionProperty("title", LuiExtensionValueKind.STRING),
LuiExtensionProperty("presented", LuiExtensionValueKind.BOOLEAN),
),
events = listOf(
LuiExtensionEventSchema(
"query-changed",
listOf(LuiExtensionProperty("query", LuiExtensionValueKind.STRING, required = true)),
),
LuiExtensionEventSchema(
"back",
listOf(LuiExtensionProperty("count", LuiExtensionValueKind.INTEGER, required = true)),
),
LuiExtensionEventSchema("dismiss"),
),
) {
SearchPresentation(this)
}
}

Expand Down
8 changes: 8 additions & 0 deletions android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ plugins {
id("com.android.library") version "9.1.0" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.4.0" apply false
}

// :lui is the real LUI Kotlin backend included from ../lui/platform/android.
// Its own test suite (paparazzi screenshot tests) needs the standalone
// platform/android build — disable it here so ./gradlew test only runs
// the app's tests.
project(":lui").tasks.withType<Test>().configureEach {
enabled = false
}
47 changes: 0 additions & 47 deletions android/lui/build.gradle.kts

This file was deleted.

Loading