Skip to content
Draft

2.0.0 #509

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2e5ff57
feat: Start rework
rteyssandier Mar 7, 2026
12ef144
feat: Make app compile & remove flocon-base
rteyssandier Mar 11, 2026
dd9f914
feat: Deeplinks
rteyssandier Mar 11, 2026
5fc6ab7
feat: Need to fix install method
rteyssandier Mar 11, 2026
fd3457f
feat: Make deeplink works
rteyssandier Mar 12, 2026
6ab9f26
feat: Clean
rteyssandier Mar 12, 2026
6c46951
feat: Create core network modules
rteyssandier Mar 12, 2026
f490aca
feat: Move plugin
rteyssandier Mar 12, 2026
754251f
feat: OkHttp work
rteyssandier Mar 12, 2026
a523461
feat: Clean install
rteyssandier Mar 12, 2026
cad125e
feat: No op
rteyssandier Mar 12, 2026
004c713
feat: Other plugins
rteyssandier Mar 12, 2026
c4125f5
feat: Move database
rteyssandier Mar 12, 2026
f56cb5a
fix: Compile
rteyssandier Mar 12, 2026
43f1969
feat: Database compile
rteyssandier Mar 13, 2026
91fb3f8
feat: Clean a bit
rteyssandier Mar 13, 2026
0e89496
fix: Database
rteyssandier Mar 25, 2026
62c0beb
fix: Remove useless file
rteyssandier Mar 25, 2026
2688a24
feat: Add room3
rteyssandier Mar 25, 2026
e826685
fix: Build
rteyssandier Apr 5, 2026
9bda13e
2.0.0 - Rework gradle (#510)
rteyssandier Apr 5, 2026
dc836f7
fix: Build
rteyssandier May 7, 2026
38d160e
2.0.0 - Table (#512)
rteyssandier May 13, 2026
3ab7cd8
2.0.0 - Analytics (#511)
rteyssandier May 13, 2026
0ad3ca9
feat/2.0.0/wasm
rteyssandier Mar 25, 2026
3f82b4e
feat: Gradle
rteyssandier Apr 8, 2026
246980f
Merge branch 'feat/2.0.0/wasm-fix' into 2.0.0
rteyssandier May 13, 2026
3078a2a
fix: Gradle
rteyssandier May 13, 2026
85f9cbe
feat: Wasm
rteyssandier May 13, 2026
9dd4c30
fix: Clean
rteyssandier May 13, 2026
dd73656
feat: Cleaning
rteyssandier May 13, 2026
b8136e4
fix: Clean
rteyssandier May 13, 2026
1d2399b
fix: Delete duplicate
rteyssandier May 13, 2026
a25e068
feat: Move crashreporter
rteyssandier May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion FloconAndroid/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
"java.configuration.updateBuildConfiguration": "automatic"
}
52 changes: 52 additions & 0 deletions FloconAndroid/AGENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Flocon Project Overview

Flocon is a modular, plugin-based framework built with **Kotlin Multiplatform (KMP)**. It provides a standardized way to integrate common cross-cutting concerns like networking, database access, datastores, and deep linking into applications.

## 🚀 Key Features

- **Modular Architecture**: Separate modules for different functionalities (network, database, etc.).
- **Plugin System**: Easily extensible with "no-op" variants for testing and modularity.
- **KMP Support**: Targets Android, iOS, JVM, and WasmJs.
- **Modern Tech Stack**: Uses Room 3, Ktor, OkHttp, gRPC, and Compose Multiplatform.

## 🛠 Technical Stack

- **Kotlin**: 2.1.0
- **Build System**: Gradle with Version Catalog (`libs.versions.toml`).
- **Dependency Injection**: Manual / Constructor injection (based on current exploration).
- **Asynchronous Programming**: Kotlin Coroutines & Flow.
- **Networking**: Ktor 3.x, OkHttp 4.x, gRPC 1.70.x.
- **Database**: Room 2.x & Room 3.0.0-alpha01.
- **UI**: Compose Multiplatform 1.9.0.

## 📂 Module Structure

- `:flocon`: Core library providing the plugin registration and context management.
- `:database`:
- `:database:core`: Abstractions for database providers.
- `:database:room` / `:database:room3`: Room-based implementations.
- `:network`:
- `:network:core`: Core networking abstractions.
- `:network:okhttp-interceptor` / `:network:ktor-interceptor`: Client-specific interceptors.
- `:grpc`:
- `:grpc-interceptor`: Interceptors for gRPC calls.
- `:datastores`: Modules for Typed DataStore integration.
- `:deeplinks`: Abstractions and implementations for handling deep links.

## 🧩 Core Concepts

### Plugins
Flocon operates on a plugin-based architecture. Modules typically provide a "Core" or implementation module and a "No-Op" module. The No-Op modules allow the app to compile and run without the actual implementation, which is useful for specialized builds or testing.

### Interceptors
For networking and gRPC, Flocon uses an interceptor-based approach to hook into the communication pipeline of standard libraries (OkHttp, Ktor, gRPC).

## 📖 Development Guidelines

- **Multiplatform First**: Always consider KMP compatibility when adding new features or modules.
- **Modularity**: Keep modules focused and avoid circular dependencies.
- **Naming Conventions**: Follow the `io.github.openflocon.flocon` package naming structure.
- **Version Catalog**: All dependency versions must be managed in `gradle/libs.versions.toml`.

---
*Created by Antigravity AI to assist in project understanding.*
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
id("com.vanniktech.maven.publish") version "0.34.0"
alias(libs.plugins.vanniktech.maven.publish)
}

kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "11"
}
}
}

jvm()

iosX64()
iosArm64()
iosSimulatorArm64()

sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":flocon"))
implementation(libs.kotlinx.coroutines.core)
}
}
}
}

android {
namespace = "io.github.openflocon.flocon.okhttp"
namespace = "io.github.openflocon.flocon.analytics.noop"
compileSdk = 36

defaultConfig {
Expand All @@ -26,24 +49,12 @@ android {
)
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

dependencies {
implementation(platform(libs.okhttp.bom))
implementation(libs.okhttp3.okhttp)
}

kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}

mavenPublishing {
publishToMavenCentral(automaticRelease = true)

Expand All @@ -55,12 +66,12 @@ mavenPublishing {

coordinates(
groupId = project.property("floconGroupId") as String,
artifactId = "flocon-okhttp-interceptor-no-op",
artifactId = "flocon-analytics-no-op",
version = System.getenv("PROJECT_VERSION_NAME") ?: project.property("floconVersion") as String
)

pom {
name = "Flocon OkHttp Interceptor"
name = "Flocon Analytics No-Op"
description = project.property("floconDescription") as String
inceptionYear = "2025"
url = "https://github.com/openflocon/Flocon"
Expand All @@ -84,4 +95,4 @@ mavenPublishing {
developerConnection = "scm:git:ssh://git@github.com/openflocon/Flocon.git"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.github.openflocon.flocon.plugins.analytics

import io.github.openflocon.flocon.FloconConfig
import io.github.openflocon.flocon.FloconContext
import io.github.openflocon.flocon.FloconPlugin
import io.github.openflocon.flocon.FloconPluginConfig
import io.github.openflocon.flocon.FloconPluginFactory
import io.github.openflocon.flocon.Protocol
import io.github.openflocon.flocon.plugins.analytics.model.AnalyticsItem

class FloconAnalyticsConfig : FloconPluginConfig

interface FloconAnalyticsPlugin : FloconPlugin {
fun registerAnalytics(analyticsItems: List<AnalyticsItem>)
}

object FloconAnalytics : FloconPluginFactory<FloconAnalyticsConfig, FloconAnalyticsPlugin> {
override val name: String = "Analytics"
override val pluginId: String = Protocol.ToDevice.Analytics.Plugin
override fun createConfig(context: FloconContext) = FloconAnalyticsConfig()
override fun install(
pluginConfig: FloconAnalyticsConfig,
floconConfig: FloconConfig
): FloconAnalyticsPlugin {
return FloconAnalyticsNoOpImpl()
}
}

internal class FloconAnalyticsNoOpImpl : FloconPlugin, FloconAnalyticsPlugin {
override val key: String
get() = Protocol.ToDevice.Analytics.Plugin

override suspend fun onMessageReceived(
method: String,
body: String,
) {
// no op
}

override suspend fun onConnectedToServer() {
// no op
}

override fun registerAnalytics(analyticsItems: List<AnalyticsItem>) {
// no op
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.github.openflocon.flocon.plugins.analytics.builder

import io.github.openflocon.flocon.plugins.analytics.FloconAnalyticsPlugin
import io.github.openflocon.flocon.plugins.analytics.model.AnalyticsEvent

class AnalyticsBuilder(
val analyticsTableId: String,
private val analyticsPlugin: FloconAnalyticsPlugin?,
) {
fun logEvents(vararg events: AnalyticsEvent) {
// no-op
}

fun logEvents(events: List<AnalyticsEvent>) {
// no-op
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ data class AnalyticsEvent(
eventName: String,
vararg properties: AnalyticsPropertiesConfig,
) : this(eventName, properties.toList())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ data class AnalyticsItem(
val eventName: String,
val createdAt: Long,
val properties: List<AnalyticsPropertiesConfig>,
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ data class AnalyticsPropertiesConfig(
infix fun String.analyticsProperty(value: String) = AnalyticsPropertiesConfig(
name = this,
value = value,
)
)
28 changes: 28 additions & 0 deletions FloconAndroid/analytics/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
plugins {
id("flocon.kotlin.multiplatform")
id("flocon.publish")
}

kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":flocon"))
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}
}
}
}

android {
namespace = "io.github.openflocon.flocon.analytics"
}

mavenPublishing {
coordinates(
groupId = project.property("floconGroupId") as String,
artifactId = "flocon-analytics",
version = System.getenv("PROJECT_VERSION_NAME") ?: project.property("floconVersion") as String
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.github.openflocon.flocon.analytics

import io.github.openflocon.flocon.FloconConfig
import io.github.openflocon.flocon.FloconContext
import io.github.openflocon.flocon.FloconLogger
import io.github.openflocon.flocon.FloconPlugin
import io.github.openflocon.flocon.FloconPluginConfig
import io.github.openflocon.flocon.FloconPluginFactory
import io.github.openflocon.flocon.Protocol
import io.github.openflocon.flocon.core.FloconEncoder
import io.github.openflocon.flocon.core.FloconMessageSender
import io.github.openflocon.flocon.core.encode
import io.github.openflocon.flocon.analytics.model.AnalyticsItem

class FloconAnalyticsConfig : FloconPluginConfig

interface FloconAnalyticsPlugin : FloconPlugin {
fun registerAnalytics(analyticsItems: List<AnalyticsItem>)
}

object FloconAnalytics : FloconPluginFactory<FloconAnalyticsConfig, FloconAnalyticsPlugin> {
override val name: String = "Analytics"
override val pluginId: String = Protocol.ToDevice.Analytics.Plugin
override fun createConfig(context: FloconContext) = FloconAnalyticsConfig()
override fun install(
pluginConfig: FloconAnalyticsConfig,
floconConfig: FloconConfig,
encoder: FloconEncoder
): FloconAnalyticsPlugin {
return FloconAnalyticsPluginImpl(
sender = floconConfig.client as FloconMessageSender,
encoder = encoder
)
}
}

internal class FloconAnalyticsPluginImpl(
private val sender: FloconMessageSender,
private val encoder: FloconEncoder
) : FloconPlugin, FloconAnalyticsPlugin {
override val key: String
get() = Protocol.ToDevice.Analytics.Plugin

override suspend fun onMessageReceived(
method: String,
body: String,
) {
// no op
}

override suspend fun onConnectedToServer() {
// no op
}

override fun registerAnalytics(analyticsItems: List<AnalyticsItem>) {
sendAnalytics(analyticsItems)
}

private fun sendAnalytics(analyticsItems: List<AnalyticsItem>) {
analyticsItems.takeIf { it.isNotEmpty() }?.forEach { toSend ->
try {
sender.send(
plugin = Protocol.FromDevice.Analytics.Plugin,
method = Protocol.FromDevice.Analytics.Method.AddItems,
body = encoder.encode(toSend)
)
} catch (t: Throwable) {
FloconLogger.logError("error on sendAnalytics", t)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
@file:OptIn(ExperimentalUuidApi::class)

package io.github.openflocon.flocon.plugins.analytics.builder
package io.github.openflocon.flocon.analytics.builder

import io.github.openflocon.flocon.plugins.analytics.FloconAnalyticsPlugin
import io.github.openflocon.flocon.plugins.analytics.model.AnalyticsEvent
import io.github.openflocon.flocon.plugins.analytics.model.AnalyticsItem
import io.github.openflocon.flocon.analytics.FloconAnalyticsPlugin
import io.github.openflocon.flocon.analytics.model.AnalyticsEvent
import io.github.openflocon.flocon.analytics.model.AnalyticsItem
import io.github.openflocon.flocon.utils.currentTimeMillis
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
Expand All @@ -29,4 +29,4 @@ class AnalyticsBuilder(
}
analyticsPlugin?.registerAnalytics(analyticsItems)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
package io.github.openflocon.flocon.plugins.analytics.mapper
package io.github.openflocon.flocon.analytics.mapper

import io.github.openflocon.flocon.core.FloconEncoder
import io.github.openflocon.flocon.plugins.analytics.model.AnalyticsItem
import io.github.openflocon.flocon.analytics.model.AnalyticsItem
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString

internal fun analyticsItemsToJson(item: AnalyticsItem): String {
return FloconEncoder.json.encodeToString(
listOf(
item.toSerializable()
)
)
}

@Serializable
internal class AnalyticsItemSerializable(
Expand All @@ -28,7 +18,7 @@ internal class AnalyticsPropertySerializable(
val value: String,
)

internal fun AnalyticsItem.toSerializable(): AnalyticsItemSerializable {
internal fun AnalyticsItem.toRemote(): AnalyticsItemSerializable {
return AnalyticsItemSerializable(
id = id,
analyticsTableId = analyticsTableId,
Expand All @@ -41,4 +31,4 @@ internal fun AnalyticsItem.toSerializable(): AnalyticsItemSerializable {
)
}
)
}
}
Loading
Loading