diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts
index ffb177cb8..fcd9256a8 100644
--- a/composeApp/build.gradle.kts
+++ b/composeApp/build.gradle.kts
@@ -99,6 +99,7 @@ kotlin {
iosMain.dependencies {
implementation(libs.sqldelight.native)
implementation(libs.bundles.mobile)
+ implementation(libs.bundles.ios)
}
val desktopMain by getting {
dependencies {
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/ArticleModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/ArticleModel.kt
index 318e5dbcf..d68f9d9a2 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/ArticleModel.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/ArticleModel.kt
@@ -9,8 +9,9 @@ import org.ooni.probe.shared.today
data class ArticleModel(
val url: Url,
val title: String,
- val description: String?,
val source: Source,
+ val description: String?,
+ val imageUrl: String?,
val time: LocalDateTime,
) {
data class Url(
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/repositories/ArticleRepository.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/repositories/ArticleRepository.kt
index 3e7043380..e1824863f 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/repositories/ArticleRepository.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/repositories/ArticleRepository.kt
@@ -23,8 +23,9 @@ class ArticleRepository(
database.articleQueries.insertOrReplace(
url = model.url.value,
title = model.title,
- description = model.description,
source = model.source.value,
+ description = model.description,
+ image_url = model.imageUrl,
time = model.time.toEpoch(),
)
}
@@ -45,8 +46,9 @@ class ArticleRepository(
ArticleModel(
url = ArticleModel.Url(url),
title = title,
- description = description,
source = ArticleModel.Source.fromValue(source) ?: return@run null,
+ description = description,
+ imageUrl = image_url,
time = time.toLocalDateTime(),
)
}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/articles/GetFindings.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/articles/GetFindings.kt
index 5c30c5e6b..34ee184ad 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/articles/GetFindings.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/articles/GetFindings.kt
@@ -46,6 +46,7 @@ class GetFindings(
title = title ?: return@run null,
source = ArticleModel.Source.Finding,
description = shortDescription,
+ imageUrl = null,
time = createTime?.toLocalDateTime() ?: return@run null,
)
}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/articles/GetRSSFeed.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/articles/GetRSSFeed.kt
index e68857b87..9f5a0bbc1 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/articles/GetRSSFeed.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/articles/GetRSSFeed.kt
@@ -9,8 +9,6 @@ import kotlinx.datetime.format.byUnicodePattern
import kotlinx.datetime.parse
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
-import nl.adaptivity.xmlutil.ExperimentalXmlUtilApi
-import nl.adaptivity.xmlutil.serialization.UnknownChildHandler
import nl.adaptivity.xmlutil.serialization.XML
import nl.adaptivity.xmlutil.serialization.XmlElement
import nl.adaptivity.xmlutil.serialization.XmlSerialName
@@ -56,6 +54,7 @@ class GetRSSFeed(
title = title ?: return@run null,
source = source,
description = description,
+ imageUrl = content?.url,
time = pubDate?.toLocalDateTime() ?: return@run null,
)
}
@@ -80,8 +79,7 @@ class GetRSSFeed(
private val Xml by lazy {
XML {
defaultPolicy {
- @OptIn(ExperimentalXmlUtilApi::class)
- unknownChildHandler = UnknownChildHandler { _, _, _, _, _ -> emptyList() }
+ ignoreUnknownChildren()
}
}
}
@@ -115,6 +113,16 @@ class GetRSSFeed(
@XmlSerialName("pubDate")
@XmlElement
val pubDate: String?,
+ @XmlSerialName("content", namespace = "http://search.yahoo.com/mrss/")
+ @XmlElement
+ val content: MediaContent?,
+ )
+
+ @Serializable
+ data class MediaContent(
+ @XmlSerialName("url")
+ @XmlElement(false)
+ val url: String?,
)
}
}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/articles/ArticleCard.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/articles/ArticleCard.kt
new file mode 100644
index 000000000..77bb949f1
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/articles/ArticleCard.kt
@@ -0,0 +1,168 @@
+package org.ooni.probe.ui.articles
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedCard
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import coil3.compose.AsyncImagePainter
+import coil3.compose.rememberAsyncImagePainter
+import kotlinx.datetime.LocalDate
+import ooniprobe.composeapp.generated.resources.Dashboard_Articles_Blog
+import ooniprobe.composeapp.generated.resources.Dashboard_Articles_Finding
+import ooniprobe.composeapp.generated.resources.Dashboard_Articles_Recent
+import ooniprobe.composeapp.generated.resources.Dashboard_Articles_Report
+import ooniprobe.composeapp.generated.resources.Res
+import ooniprobe.composeapp.generated.resources.ic_cloud_off
+import org.jetbrains.compose.resources.painterResource
+import org.jetbrains.compose.resources.stringResource
+import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.ooni.probe.data.models.ArticleModel
+import org.ooni.probe.shared.toDateTime
+import org.ooni.probe.ui.shared.articleFormat
+import org.ooni.probe.ui.theme.AppTheme
+import org.ooni.probe.ui.theme.LocalCustomColors
+
+@Composable
+fun ArticleCard(
+ article: ArticleModel,
+ onClick: () -> Unit,
+) {
+ OutlinedCard(
+ onClick = onClick,
+ colors = CardDefaults
+ .outlinedCardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerLowest),
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .padding(bottom = 8.dp),
+ ) {
+ Row(
+ Modifier
+ .height(IntrinsicSize.Min)
+ .defaultMinSize(minHeight = 88.dp),
+ ) {
+ Column(Modifier.weight(2f)) {
+ Text(
+ article.title,
+ style = MaterialTheme.typography.titleMedium,
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier
+ .padding(horizontal = 12.dp)
+ .padding(top = 8.dp, bottom = 4.dp),
+ )
+ Text(
+ buildAnnotatedString {
+ withStyle(
+ SpanStyle(
+ color = MaterialTheme.colorScheme.primary,
+ fontWeight = FontWeight.Bold,
+ ),
+ ) {
+ append(
+ stringResource(
+ when (article.source) {
+ ArticleModel.Source.Blog -> Res.string.Dashboard_Articles_Blog
+ ArticleModel.Source.Finding -> Res.string.Dashboard_Articles_Finding
+ ArticleModel.Source.Report -> Res.string.Dashboard_Articles_Report
+ },
+ ),
+ )
+ }
+ append(" • ")
+ append(article.time.articleFormat())
+ if (article.isRecent) {
+ append(" • ")
+ withStyle(SpanStyle(color = LocalCustomColors.current.success)) {
+ append(stringResource(Res.string.Dashboard_Articles_Recent))
+ }
+ }
+ },
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(horizontal = 12.dp).padding(bottom = 8.dp),
+ )
+ }
+ article.imageUrl?.let { imageUrl ->
+ val painter = rememberAsyncImagePainter(imageUrl)
+ val state by painter.state.collectAsStateWithLifecycle()
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ .fillMaxHeight()
+ .height(IntrinsicSize.Min)
+ .clip(MaterialTheme.shapes.medium)
+ .background(MaterialTheme.colorScheme.surfaceVariant)
+ .border(
+ Dp.Hairline,
+ MaterialTheme.colorScheme.onSurface.copy(alpha = 0.25f),
+ MaterialTheme.shapes.medium,
+ ).weight(1f),
+ ) {
+ Image(
+ painter = painter,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.matchParentSize(),
+ )
+ if (state is AsyncImagePainter.State.Loading) {
+ CircularProgressIndicator(
+ Modifier.size(48.dp),
+ )
+ }
+ if (state is AsyncImagePainter.State.Error) {
+ Image(
+ painterResource(Res.drawable.ic_cloud_off),
+ contentDescription = null,
+ modifier = Modifier.alpha(0.5f),
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+@Preview
+fun ArticleCellPreview() {
+ AppTheme {
+ ArticleCard(
+ article = ArticleModel(
+ url = ArticleModel.Url("http://ooni.org"),
+ title = "Join us at the OMG Village at the Global Gathering 2025!",
+ description = "Hello there.",
+ imageUrl = "https://ooni.org/images/logos/OONI-VerticalColor@2x.png",
+ source = ArticleModel.Source.Blog,
+ time = LocalDate(2025, 4, 1).toDateTime(),
+ ),
+ onClick = {},
+ )
+ }
+}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/articles/ArticleCell.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/articles/ArticleCell.kt
deleted file mode 100644
index 26786b373..000000000
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/articles/ArticleCell.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.ooni.probe.ui.articles
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedCard
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import ooniprobe.composeapp.generated.resources.Dashboard_Articles_Blog
-import ooniprobe.composeapp.generated.resources.Dashboard_Articles_Finding
-import ooniprobe.composeapp.generated.resources.Dashboard_Articles_Recent
-import ooniprobe.composeapp.generated.resources.Dashboard_Articles_Report
-import ooniprobe.composeapp.generated.resources.Res
-import org.jetbrains.compose.resources.stringResource
-import org.ooni.probe.data.models.ArticleModel
-import org.ooni.probe.data.models.ArticleModel.Source.Blog
-import org.ooni.probe.data.models.ArticleModel.Source.Finding
-import org.ooni.probe.data.models.ArticleModel.Source.Report
-import org.ooni.probe.ui.shared.articleFormat
-import org.ooni.probe.ui.theme.LocalCustomColors
-
-@Composable
-fun ArticleCell(
- article: ArticleModel,
- onClick: () -> Unit,
-) {
- OutlinedCard(
- onClick = onClick,
- modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 8.dp),
- ) {
- Column(
- Modifier
- .fillMaxWidth()
- .padding(horizontal = 12.dp, vertical = 8.dp),
- ) {
- Text(
- article.title,
- style = MaterialTheme.typography.titleMedium,
- maxLines = 2,
- overflow = TextOverflow.Ellipsis,
- )
- Row(verticalAlignment = Alignment.Bottom) {
- Text(
- stringResource(
- when (article.source) {
- Blog -> Res.string.Dashboard_Articles_Blog
- Finding -> Res.string.Dashboard_Articles_Finding
- Report -> Res.string.Dashboard_Articles_Report
- },
- ),
- style = MaterialTheme.typography.labelLarge
- .copy(fontWeight = FontWeight.Bold),
- color = MaterialTheme.colorScheme.primary,
- modifier = Modifier.padding(top = 4.dp),
- )
-
- Text(
- "•",
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.padding(horizontal = 4.dp),
- )
- Text(
- article.time.articleFormat(),
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.padding(top = 4.dp),
- )
- if (article.isRecent) {
- Text(
- "•",
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.padding(horizontal = 4.dp),
- )
- Text(
- stringResource(Res.string.Dashboard_Articles_Recent),
- style = MaterialTheme.typography.labelLarge,
- color = LocalCustomColors.current.success,
- modifier = Modifier.padding(top = 4.dp),
- )
- }
- }
- }
- }
-}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/articles/ArticlesScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/articles/ArticlesScreen.kt
index 764dd638f..f30054ff7 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/articles/ArticlesScreen.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/articles/ArticlesScreen.kt
@@ -78,7 +78,7 @@ fun ArticlesScreen(
state = lazyListState,
) {
items(state.articles, key = { it.url.value }) { article ->
- ArticleCell(
+ ArticleCard(
article = article,
onClick = { onEvent(ArticlesViewModel.Event.ArticleClicked(article)) },
)
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/DashboardScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/DashboardScreen.kt
index 61fdfbb47..40c63f98d 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/DashboardScreen.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/DashboardScreen.kt
@@ -87,7 +87,7 @@ import org.ooni.probe.data.models.MeasurementStats
import org.ooni.probe.data.models.Run
import org.ooni.probe.data.models.RunBackgroundState
import org.ooni.probe.shared.largeNumberShort
-import org.ooni.probe.ui.articles.ArticleCell
+import org.ooni.probe.ui.articles.ArticleCard
import org.ooni.probe.ui.shared.IgnoreBatteryOptimizationDialog
import org.ooni.probe.ui.shared.TestRunErrorMessages
import org.ooni.probe.ui.shared.VerticalScrollbar
@@ -486,7 +486,7 @@ private fun ArticlesSection(
)
articles.forEach { article ->
- ArticleCell(
+ ArticleCard(
article = article,
onClick = { onEvent(DashboardViewModel.Event.ArticleClicked(article)) },
)
diff --git a/composeApp/src/commonMain/sqldelight/migrations/15.sqm b/composeApp/src/commonMain/sqldelight/migrations/15.sqm
index 881eb05b4..89cb5373f 100644
--- a/composeApp/src/commonMain/sqldelight/migrations/15.sqm
+++ b/composeApp/src/commonMain/sqldelight/migrations/15.sqm
@@ -3,5 +3,6 @@ CREATE TABLE Article(
title TEXT NOT NULL,
source TEXT NOT NULL,
description TEXT,
+ image_url TEXT,
time INTEGER NOT NULL
);
diff --git a/composeApp/src/commonMain/sqldelight/org/ooni/probe/data/Article.sq b/composeApp/src/commonMain/sqldelight/org/ooni/probe/data/Article.sq
index 357b64b91..5b799cf4b 100644
--- a/composeApp/src/commonMain/sqldelight/org/ooni/probe/data/Article.sq
+++ b/composeApp/src/commonMain/sqldelight/org/ooni/probe/data/Article.sq
@@ -3,6 +3,7 @@ CREATE TABLE Article(
title TEXT NOT NULL,
source TEXT NOT NULL,
description TEXT,
+ image_url TEXT,
time INTEGER NOT NULL
);
@@ -10,10 +11,11 @@ insertOrReplace:
INSERT OR REPLACE INTO Article (
url,
title,
- description,
source,
+ description,
+ image_url,
time
-) VALUES (?,?,?,?,?);
+) VALUES (?,?,?,?,?,?);
selectAll:
SELECT * FROM Article ORDER BY time DESC;
diff --git a/composeApp/src/commonTest/kotlin/org/ooni/probe/domain/articles/GetRssFeedTest.kt b/composeApp/src/commonTest/kotlin/org/ooni/probe/domain/articles/GetRssFeedTest.kt
index 498352a2a..011ef8c79 100644
--- a/composeApp/src/commonTest/kotlin/org/ooni/probe/domain/articles/GetRssFeedTest.kt
+++ b/composeApp/src/commonTest/kotlin/org/ooni/probe/domain/articles/GetRssFeedTest.kt
@@ -22,11 +22,49 @@ class GetRssFeedTest {
assertEquals("https://ooni.org/post/2025-gg-omg-village/", url.value)
assertEquals("Join us at the OMG Village at the Global Gathering 2025!", title)
assertEquals(2025, time.year)
+ assertEquals("https://ooni.org/post/2025-gg-omg-village/images/omg-banner.png", imageUrl)
}
}
companion object {
- private const val RSS_FEED =
- "Blog posts on OONI: Open Observatory of Network Interferencehttps://ooni.org/blog/Recent content in Blog posts on OONI: Open Observatory of Network InterferenceHugoen- Join us at the OMG Village at the Global Gathering 2025!https://ooni.org/post/2025-gg-omg-village/Mon, 01 Sep 2025 00:00:00 +0000https://ooni.org/post/2025-gg-omg-village/<p>Are you attending the upcoming <a href=\"https://wiki.digitalrights.community/index.php?title=Global_Gathering_2025\">Global Gathering</a> event in Estoril, Portugal? Are you interested in investigating internet shutdowns and censorship, and curious to learn more about the tools and open datasets that support this work?</p>
"
+ private val RSS_FEED = """
+
+
+
+ Blog posts on OONI: Open Observatory of Network Interference
+ https://ooni.org/blog/
+ Recent content in Blog posts on OONI: Open Observatory of Network Interference
+ Hugo
+ en
+
+ -
+ Join us at the OMG Village at the Global Gathering 2025!
+ https://ooni.org/post/2025-gg-omg-village/
+
+ Mon, 01 Sep 2025 00:00:00 +0000
+ https://ooni.org/post/2025-gg-omg-village/
+
+ <div>
+ <a href="https://ooni.org/post/2025-gg-omg-village/images/omg-banner.png">
+ <img
+ src="https://ooni.org/post/2025-gg-omg-village/images/omg-banner_hu_1e6c0cc0ca63d2d9.png"
+
+
+ srcset="https://ooni.org/post/2025-gg-omg-village/images/omg-banner_hu_79aea09410c8ceb7.png 2x"
+
+
+ title="OMG Village announcement"
+
+ alt="OMG Village announcement"
+
+ />
+ </a>
+
+ </div>
+
+
+
+
+ """.trimIndent()
}
}
diff --git a/composeApp/src/commonTest/kotlin/org/ooni/testing/factories/ArticleModelFactory.kt b/composeApp/src/commonTest/kotlin/org/ooni/testing/factories/ArticleModelFactory.kt
index 5e5d607a0..d1076d593 100644
--- a/composeApp/src/commonTest/kotlin/org/ooni/testing/factories/ArticleModelFactory.kt
+++ b/composeApp/src/commonTest/kotlin/org/ooni/testing/factories/ArticleModelFactory.kt
@@ -11,13 +11,15 @@ object ArticleModelFactory {
fun build(
url: ArticleModel.Url = ArticleModel.Url("https://example.org/${Random.nextInt()}"),
title: String = "Title",
- description: String? = null,
time: LocalDateTime = LocalDate.today().atTime(0, 0),
+ description: String? = null,
+ imageUrl: String? = null,
source: ArticleModel.Source = ArticleModel.Source.Blog,
) = ArticleModel(
url = url,
title = title,
description = description,
+ imageUrl = imageUrl,
time = time,
source = source,
)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e60ad12e3..ad91acfef 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -12,6 +12,8 @@ sqldelight = "2.1.0"
dataStoreVersion = "1.1.4"
junitKtx = "1.3.0"
mokoPermissions = "0.20.1"
+ktor = "3.3.1"
+coil = "3.3.0"
[plugins]
@@ -49,6 +51,11 @@ window-size = { module = "org.jetbrains.compose.material3:material3-window-size-
back-handler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.ref = "compose-plugin" }
material-icons = { module = "org.jetbrains.compose.material:material-icons-core", version = "1.7.3" }
dark-mode-detector = { module = "io.github.kdroidfilter:platformtools.darkmodedetector", version = "0.7.4" }
+coil = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
+coil-network = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
+coil-network-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
+coil-network-ios = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
+coil-network-jvm = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
# Preferences
androidx-datastore-core-okio = { group = "androidx.datastore", name = "datastore-core-okio", version.ref = "dataStoreVersion" }
@@ -134,26 +141,33 @@ tooling = [
"markdown",
"kottie",
"web-view",
+ "coil",
+ "coil-network",
]
android = [
-# "android-oonimkall",
+ # "android-oonimkall",
"android-activity",
"android-fragment",
"android-work",
"sqldelight-android",
"android-appcompat",
+ "coil-network-android",
]
mobile = [
"moko-permissions-compose",
"moko-permissions-notifications",
]
+ios = [
+ "coil-network-ios",
+]
desktop = [
"sqldelight-jvm",
"androidx-datastore-core-jvm",
"directories",
"auto-launch",
"pratanumandal-unique",
- "desktop-oonimkall"
+ "desktop-oonimkall",
+ "coil-network-jvm",
]
full = [
"sentry",