diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml
index a25745c..9468b3a 100644
--- a/composeApp/src/androidMain/AndroidManifest.xml
+++ b/composeApp/src/androidMain/AndroidManifest.xml
@@ -7,6 +7,8 @@
android:required="false" />
+
+
diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/AndroidConnectivityMonitor.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/AndroidConnectivityMonitor.kt
new file mode 100644
index 0000000..20984a9
--- /dev/null
+++ b/composeApp/src/androidMain/kotlin/su/reya/coop/AndroidConnectivityMonitor.kt
@@ -0,0 +1,49 @@
+package su.reya.coop
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class AndroidConnectivityMonitor(context: Context) : ConnectivityMonitor {
+ private val manager =
+ context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+
+ private val _isMobileData = MutableStateFlow(checkIsMobileData())
+ override val isMobileData: StateFlow = _isMobileData.asStateFlow()
+
+ init {
+ val networkRequest = NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build()
+
+ manager.registerNetworkCallback(
+ networkRequest,
+ object : ConnectivityManager.NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ _isMobileData.value = checkIsMobileData()
+ }
+
+ override fun onLost(network: Network) {
+ _isMobileData.value = checkIsMobileData()
+ }
+
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ _isMobileData.value = checkIsMobileData()
+ }
+ })
+ }
+
+ private fun checkIsMobileData(): Boolean {
+ val activeNetwork = manager.activeNetwork ?: return false
+ val capabilities = manager.getNetworkCapabilities(activeNetwork) ?: return false
+ return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ }
+}
diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt
index 1515407..3570b27 100644
--- a/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt
+++ b/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt
@@ -67,6 +67,10 @@ val LocalSettings = staticCompositionLocalOf {
error("No Settings provided")
}
+val LocalConnectivity = staticCompositionLocalOf {
+ false
+}
+
val LocalSnackbarHostState = staticCompositionLocalOf {
error("No SnackbarHostState provided")
}
@@ -86,6 +90,7 @@ fun App(
accountRepository: AccountRepository,
chatRepository: ChatRepository,
settingsRepository: SettingsRepository,
+ connectivityMonitor: ConnectivityMonitor,
) {
val viewModelFactory = remember {
object : ViewModelProvider.Factory {
@@ -128,6 +133,9 @@ fun App(
// Get the settings
val settings by settingsViewModel.settings.collectAsStateWithLifecycle()
+ // Get connectivity status
+ val isMobileData by connectivityMonitor.isMobileData.collectAsStateWithLifecycle()
+
// Snackbar
val snackbarHostState = remember { SnackbarHostState() }
@@ -208,6 +216,7 @@ fun App(
CompositionLocalProvider(
LocalProfileCache provides profileCache,
LocalSettings provides settings,
+ LocalConnectivity provides isMobileData,
LocalSnackbarHostState provides snackbarHostState,
LocalNavigator provides navigator,
LocalScanResult provides qrScanResult,
diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt
index a1745ef..86b9171 100644
--- a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt
+++ b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt
@@ -26,6 +26,8 @@ class MainActivity : ComponentActivity() {
private val profileCache by lazy { ProfileCache(NostrManager.instance) }
private val scope = MainScope()
+ private val connectivityMonitor by lazy { AndroidConnectivityMonitor(this@MainActivity) }
+
private val settingsRepository by lazy {
val storage = AppStore(this@MainActivity)
SettingsRepository(storage, scope)
@@ -96,6 +98,7 @@ class MainActivity : ComponentActivity() {
accountRepository = accountRepository,
chatRepository = chatRepository,
settingsRepository = settingsRepository,
+ connectivityMonitor = connectivityMonitor,
)
}
}
diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/chat/ChatMessage.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/chat/ChatMessage.kt
index 213c9b9..a01ff76 100644
--- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/chat/ChatMessage.kt
+++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/chat/ChatMessage.kt
@@ -38,6 +38,9 @@ import coil3.compose.AsyncImage
import rust.nostr.sdk.EventId
import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.UnsignedEvent
+import su.reya.coop.LocalConnectivity
+import su.reya.coop.LocalSettings
+import su.reya.coop.MediaConfig
import su.reya.coop.URL_REGEX
import su.reya.coop.formatAsTime
import su.reya.coop.isImageUrl
@@ -56,14 +59,27 @@ data class MessageModel(
@Composable
fun rememberMessageModel(event: UnsignedEvent, currentUser: PublicKey? = null): MessageModel {
- return remember(event, currentUser) {
+ val settings = LocalSettings.current
+ val isMobileData = LocalConnectivity.current
+
+ return remember(event, currentUser, settings, isMobileData) {
val id = event.ensureId().id()!!
val isMine = currentUser == event.author()
val content = event.content()
val replyEventIds = event.tags().eventIds()
- val images = URL_REGEX.findAll(content).map { it.value }.filter { it.isImageUrl() }.toList()
- val cleanedContent = content.removeImageUrls()
+ val showMedia = when (settings.media) {
+ MediaConfig.AlwaysEnabled -> true
+ MediaConfig.Disabled -> false
+ MediaConfig.DisabledForMobileData -> !isMobileData
+ }
+
+ val images = if (showMedia) {
+ URL_REGEX.findAll(content).map { it.value }.filter { it.isImageUrl() }.toList()
+ } else {
+ emptyList()
+ }
+ val cleanedContent = if (showMedia) content.removeImageUrls() else content
val annotatedString = buildAnnotatedString {
var lastIndex = 0
diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/chat/ChatScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/chat/ChatScreen.kt
index bda1743..9f2eb67 100644
--- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/chat/ChatScreen.kt
+++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/chat/ChatScreen.kt
@@ -93,6 +93,7 @@ import org.jetbrains.compose.resources.painterResource
import rust.nostr.sdk.UnsignedEvent
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalProfileCache
+import su.reya.coop.LocalSettings
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Room
import su.reya.coop.RoomUiState
@@ -115,6 +116,7 @@ fun ChatScreen(
val clipboardManager = LocalClipboard.current
val navigator = LocalNavigator.current
val profileCache = LocalProfileCache.current
+ val settings = LocalSettings.current
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
@@ -265,7 +267,7 @@ fun ChatScreen(
.fillMaxSize()
.padding(bottom = innerPadding.calculateBottomPadding())
) {
- if (requireScreening) {
+ if (requireScreening && settings.screening) {
room?.let { ScreenerCard(accountViewModel, it) }
}
@@ -349,7 +351,7 @@ fun ChatScreen(
}
}
- when (requireScreening) {
+ when (requireScreening && settings.screening) {
true -> {
Row(
modifier = Modifier
diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/shared/Avatar.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/shared/Avatar.kt
index 023f871..6c8fc46 100644
--- a/composeApp/src/androidMain/kotlin/su/reya/coop/shared/Avatar.kt
+++ b/composeApp/src/androidMain/kotlin/su/reya/coop/shared/Avatar.kt
@@ -1,5 +1,6 @@
package su.reya.coop.shared
+import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
@@ -13,6 +14,9 @@ import coil3.compose.AsyncImage
import coop.composeapp.generated.resources.Res
import coop.composeapp.generated.resources.avatar
import org.jetbrains.compose.resources.painterResource
+import su.reya.coop.LocalConnectivity
+import su.reya.coop.LocalSettings
+import su.reya.coop.MediaConfig
@Composable
fun Avatar(
@@ -22,17 +26,36 @@ fun Avatar(
size: Dp = 48.dp,
shape: Shape = CircleShape
) {
+ val settings = LocalSettings.current
+ val isMobileData = LocalConnectivity.current
val placeholder = painterResource(Res.drawable.avatar)
- AsyncImage(
- model = picture,
- contentDescription = description,
- modifier = modifier
- .size(size)
- .clip(shape),
- contentScale = ContentScale.Crop,
- fallback = placeholder,
- error = placeholder,
- placeholder = placeholder
- )
+ val showMedia = when (settings.media) {
+ MediaConfig.AlwaysEnabled -> true
+ MediaConfig.Disabled -> false
+ MediaConfig.DisabledForMobileData -> !isMobileData
+ }
+
+ if (showMedia) {
+ AsyncImage(
+ model = picture,
+ contentDescription = description,
+ modifier = modifier
+ .size(size)
+ .clip(shape),
+ contentScale = ContentScale.Crop,
+ fallback = placeholder,
+ error = placeholder,
+ placeholder = placeholder
+ )
+ } else {
+ Image(
+ painter = placeholder,
+ contentDescription = description,
+ modifier = modifier
+ .size(size)
+ .clip(shape),
+ contentScale = ContentScale.Crop
+ )
+ }
}
diff --git a/shared/src/commonMain/kotlin/su/reya/coop/ConnectivityMonitor.kt b/shared/src/commonMain/kotlin/su/reya/coop/ConnectivityMonitor.kt
new file mode 100644
index 0000000..8d7c49d
--- /dev/null
+++ b/shared/src/commonMain/kotlin/su/reya/coop/ConnectivityMonitor.kt
@@ -0,0 +1,7 @@
+package su.reya.coop
+
+import kotlinx.coroutines.flow.StateFlow
+
+interface ConnectivityMonitor {
+ val isMobileData: StateFlow
+}
diff --git a/shared/src/commonMain/kotlin/su/reya/coop/Settings.kt b/shared/src/commonMain/kotlin/su/reya/coop/Settings.kt
index c427767..53134b8 100644
--- a/shared/src/commonMain/kotlin/su/reya/coop/Settings.kt
+++ b/shared/src/commonMain/kotlin/su/reya/coop/Settings.kt
@@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable
data class Settings(
val theme: Theme = Theme.System,
val dynamicColor: Boolean = true,
- val media: Media = Media.AlwaysEnabled,
+ val media: MediaConfig = MediaConfig.AlwaysEnabled,
val screening: Boolean = true,
val blossomServer: String? = "https://blossom.band",
)
@@ -17,6 +17,6 @@ enum class Theme {
}
@Serializable
-enum class Media {
+enum class MediaConfig {
Disabled, DisabledForMobileData, AlwaysEnabled
}