From d243c62157f96f8656a076ec528e1ae91c5221ff Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Mon, 20 Jul 2026 09:21:06 +0700 Subject: [PATCH] use settings --- .../src/androidMain/AndroidManifest.xml | 2 + .../reya/coop/AndroidConnectivityMonitor.kt | 49 +++++++++++++++++++ .../androidMain/kotlin/su/reya/coop/App.kt | 9 ++++ .../kotlin/su/reya/coop/MainActivity.kt | 3 ++ .../su/reya/coop/screens/chat/ChatMessage.kt | 22 +++++++-- .../su/reya/coop/screens/chat/ChatScreen.kt | 6 ++- .../kotlin/su/reya/coop/shared/Avatar.kt | 45 ++++++++++++----- .../su/reya/coop/ConnectivityMonitor.kt | 7 +++ .../kotlin/su/reya/coop/Settings.kt | 4 +- 9 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/su/reya/coop/AndroidConnectivityMonitor.kt create mode 100644 shared/src/commonMain/kotlin/su/reya/coop/ConnectivityMonitor.kt 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 }