use settings

This commit is contained in:
2026-07-20 09:21:06 +07:00
parent 6bb4ef2805
commit d243c62157
9 changed files with 129 additions and 18 deletions

View File

@@ -7,6 +7,8 @@
android:required="false" /> android:required="false" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

View File

@@ -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<Boolean> = _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)
}
}

View File

@@ -67,6 +67,10 @@ val LocalSettings = staticCompositionLocalOf<Settings> {
error("No Settings provided") error("No Settings provided")
} }
val LocalConnectivity = staticCompositionLocalOf<Boolean> {
false
}
val LocalSnackbarHostState = staticCompositionLocalOf<SnackbarHostState> { val LocalSnackbarHostState = staticCompositionLocalOf<SnackbarHostState> {
error("No SnackbarHostState provided") error("No SnackbarHostState provided")
} }
@@ -86,6 +90,7 @@ fun App(
accountRepository: AccountRepository, accountRepository: AccountRepository,
chatRepository: ChatRepository, chatRepository: ChatRepository,
settingsRepository: SettingsRepository, settingsRepository: SettingsRepository,
connectivityMonitor: ConnectivityMonitor,
) { ) {
val viewModelFactory = remember { val viewModelFactory = remember {
object : ViewModelProvider.Factory { object : ViewModelProvider.Factory {
@@ -128,6 +133,9 @@ fun App(
// Get the settings // Get the settings
val settings by settingsViewModel.settings.collectAsStateWithLifecycle() val settings by settingsViewModel.settings.collectAsStateWithLifecycle()
// Get connectivity status
val isMobileData by connectivityMonitor.isMobileData.collectAsStateWithLifecycle()
// Snackbar // Snackbar
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
@@ -208,6 +216,7 @@ fun App(
CompositionLocalProvider( CompositionLocalProvider(
LocalProfileCache provides profileCache, LocalProfileCache provides profileCache,
LocalSettings provides settings, LocalSettings provides settings,
LocalConnectivity provides isMobileData,
LocalSnackbarHostState provides snackbarHostState, LocalSnackbarHostState provides snackbarHostState,
LocalNavigator provides navigator, LocalNavigator provides navigator,
LocalScanResult provides qrScanResult, LocalScanResult provides qrScanResult,

View File

@@ -26,6 +26,8 @@ class MainActivity : ComponentActivity() {
private val profileCache by lazy { ProfileCache(NostrManager.instance) } private val profileCache by lazy { ProfileCache(NostrManager.instance) }
private val scope = MainScope() private val scope = MainScope()
private val connectivityMonitor by lazy { AndroidConnectivityMonitor(this@MainActivity) }
private val settingsRepository by lazy { private val settingsRepository by lazy {
val storage = AppStore(this@MainActivity) val storage = AppStore(this@MainActivity)
SettingsRepository(storage, scope) SettingsRepository(storage, scope)
@@ -96,6 +98,7 @@ class MainActivity : ComponentActivity() {
accountRepository = accountRepository, accountRepository = accountRepository,
chatRepository = chatRepository, chatRepository = chatRepository,
settingsRepository = settingsRepository, settingsRepository = settingsRepository,
connectivityMonitor = connectivityMonitor,
) )
} }
} }

View File

@@ -38,6 +38,9 @@ import coil3.compose.AsyncImage
import rust.nostr.sdk.EventId import rust.nostr.sdk.EventId
import rust.nostr.sdk.PublicKey import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.UnsignedEvent 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.URL_REGEX
import su.reya.coop.formatAsTime import su.reya.coop.formatAsTime
import su.reya.coop.isImageUrl import su.reya.coop.isImageUrl
@@ -56,14 +59,27 @@ data class MessageModel(
@Composable @Composable
fun rememberMessageModel(event: UnsignedEvent, currentUser: PublicKey? = null): MessageModel { 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 id = event.ensureId().id()!!
val isMine = currentUser == event.author() val isMine = currentUser == event.author()
val content = event.content() val content = event.content()
val replyEventIds = event.tags().eventIds() val replyEventIds = event.tags().eventIds()
val images = URL_REGEX.findAll(content).map { it.value }.filter { it.isImageUrl() }.toList() val showMedia = when (settings.media) {
val cleanedContent = content.removeImageUrls() 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 { val annotatedString = buildAnnotatedString {
var lastIndex = 0 var lastIndex = 0

View File

@@ -93,6 +93,7 @@ import org.jetbrains.compose.resources.painterResource
import rust.nostr.sdk.UnsignedEvent import rust.nostr.sdk.UnsignedEvent
import su.reya.coop.LocalNavigator import su.reya.coop.LocalNavigator
import su.reya.coop.LocalProfileCache import su.reya.coop.LocalProfileCache
import su.reya.coop.LocalSettings
import su.reya.coop.LocalSnackbarHostState import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Room import su.reya.coop.Room
import su.reya.coop.RoomUiState import su.reya.coop.RoomUiState
@@ -115,6 +116,7 @@ fun ChatScreen(
val clipboardManager = LocalClipboard.current val clipboardManager = LocalClipboard.current
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val profileCache = LocalProfileCache.current val profileCache = LocalProfileCache.current
val settings = LocalSettings.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val listState = rememberLazyListState() val listState = rememberLazyListState()
@@ -265,7 +267,7 @@ fun ChatScreen(
.fillMaxSize() .fillMaxSize()
.padding(bottom = innerPadding.calculateBottomPadding()) .padding(bottom = innerPadding.calculateBottomPadding())
) { ) {
if (requireScreening) { if (requireScreening && settings.screening) {
room?.let { ScreenerCard(accountViewModel, it) } room?.let { ScreenerCard(accountViewModel, it) }
} }
@@ -349,7 +351,7 @@ fun ChatScreen(
} }
} }
when (requireScreening) { when (requireScreening && settings.screening) {
true -> { true -> {
Row( Row(
modifier = Modifier modifier = Modifier

View File

@@ -1,5 +1,6 @@
package su.reya.coop.shared package su.reya.coop.shared
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -13,6 +14,9 @@ import coil3.compose.AsyncImage
import coop.composeapp.generated.resources.Res import coop.composeapp.generated.resources.Res
import coop.composeapp.generated.resources.avatar import coop.composeapp.generated.resources.avatar
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import su.reya.coop.LocalConnectivity
import su.reya.coop.LocalSettings
import su.reya.coop.MediaConfig
@Composable @Composable
fun Avatar( fun Avatar(
@@ -22,8 +26,17 @@ fun Avatar(
size: Dp = 48.dp, size: Dp = 48.dp,
shape: Shape = CircleShape shape: Shape = CircleShape
) { ) {
val settings = LocalSettings.current
val isMobileData = LocalConnectivity.current
val placeholder = painterResource(Res.drawable.avatar) val placeholder = painterResource(Res.drawable.avatar)
val showMedia = when (settings.media) {
MediaConfig.AlwaysEnabled -> true
MediaConfig.Disabled -> false
MediaConfig.DisabledForMobileData -> !isMobileData
}
if (showMedia) {
AsyncImage( AsyncImage(
model = picture, model = picture,
contentDescription = description, contentDescription = description,
@@ -35,4 +48,14 @@ fun Avatar(
error = placeholder, error = placeholder,
placeholder = placeholder placeholder = placeholder
) )
} else {
Image(
painter = placeholder,
contentDescription = description,
modifier = modifier
.size(size)
.clip(shape),
contentScale = ContentScale.Crop
)
}
} }

View File

@@ -0,0 +1,7 @@
package su.reya.coop
import kotlinx.coroutines.flow.StateFlow
interface ConnectivityMonitor {
val isMobileData: StateFlow<Boolean>
}

View File

@@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable
data class Settings( data class Settings(
val theme: Theme = Theme.System, val theme: Theme = Theme.System,
val dynamicColor: Boolean = true, val dynamicColor: Boolean = true,
val media: Media = Media.AlwaysEnabled, val media: MediaConfig = MediaConfig.AlwaysEnabled,
val screening: Boolean = true, val screening: Boolean = true,
val blossomServer: String? = "https://blossom.band", val blossomServer: String? = "https://blossom.band",
) )
@@ -17,6 +17,6 @@ enum class Theme {
} }
@Serializable @Serializable
enum class Media { enum class MediaConfig {
Disabled, DisabledForMobileData, AlwaysEnabled Disabled, DisabledForMobileData, AlwaysEnabled
} }