use settings
This commit is contained in:
@@ -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" />
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,17 +26,36 @@ 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)
|
||||||
|
|
||||||
AsyncImage(
|
val showMedia = when (settings.media) {
|
||||||
model = picture,
|
MediaConfig.AlwaysEnabled -> true
|
||||||
contentDescription = description,
|
MediaConfig.Disabled -> false
|
||||||
modifier = modifier
|
MediaConfig.DisabledForMobileData -> !isMobileData
|
||||||
.size(size)
|
}
|
||||||
.clip(shape),
|
|
||||||
contentScale = ContentScale.Crop,
|
if (showMedia) {
|
||||||
fallback = placeholder,
|
AsyncImage(
|
||||||
error = placeholder,
|
model = picture,
|
||||||
placeholder = placeholder
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package su.reya.coop
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
interface ConnectivityMonitor {
|
||||||
|
val isMobileData: StateFlow<Boolean>
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user