chore: clean up codebase #42
@@ -50,10 +50,10 @@ import su.reya.coop.screens.UpdateProfileScreen
|
|||||||
import su.reya.coop.screens.chat.ChatScreen
|
import su.reya.coop.screens.chat.ChatScreen
|
||||||
import su.reya.coop.viewmodel.AccountViewModel
|
import su.reya.coop.viewmodel.AccountViewModel
|
||||||
import su.reya.coop.viewmodel.ChatViewModel
|
import su.reya.coop.viewmodel.ChatViewModel
|
||||||
import su.reya.coop.viewmodel.NostrViewModel
|
import su.reya.coop.viewmodel.ProfileCache
|
||||||
|
|
||||||
val LocalNostrViewModel = staticCompositionLocalOf<NostrViewModel> {
|
val LocalProfileCache = staticCompositionLocalOf<ProfileCache> {
|
||||||
error("No NostrViewModel provided")
|
error("No ProfileCache provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
val LocalChatViewModel = staticCompositionLocalOf<ChatViewModel> {
|
val LocalChatViewModel = staticCompositionLocalOf<ChatViewModel> {
|
||||||
@@ -80,7 +80,7 @@ val LocalScanResult = staticCompositionLocalOf<QrScanResult> {
|
|||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun App(
|
fun App(
|
||||||
nostrViewModel: NostrViewModel,
|
profileCache: ProfileCache,
|
||||||
chatViewModel: ChatViewModel,
|
chatViewModel: ChatViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
@@ -130,7 +130,7 @@ fun App(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
nostrViewModel.errorEvents.collect { message ->
|
profileCache.errorEvents.collect { message ->
|
||||||
snackbarHostState.showSnackbar(message)
|
snackbarHostState.showSnackbar(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ fun App(
|
|||||||
motionScheme = MotionScheme.expressive(),
|
motionScheme = MotionScheme.expressive(),
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalNostrViewModel provides nostrViewModel,
|
LocalProfileCache provides profileCache,
|
||||||
LocalChatViewModel provides chatViewModel,
|
LocalChatViewModel provides chatViewModel,
|
||||||
LocalAccountViewModel provides accountViewModel,
|
LocalAccountViewModel provides accountViewModel,
|
||||||
LocalSnackbarHostState provides snackbarHostState,
|
LocalSnackbarHostState provides snackbarHostState,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import su.reya.coop.nostr.NostrManager
|
|||||||
import su.reya.coop.repository.MediaRepository
|
import su.reya.coop.repository.MediaRepository
|
||||||
import su.reya.coop.viewmodel.AccountViewModel
|
import su.reya.coop.viewmodel.AccountViewModel
|
||||||
import su.reya.coop.viewmodel.ChatViewModel
|
import su.reya.coop.viewmodel.ChatViewModel
|
||||||
import su.reya.coop.viewmodel.NostrViewModel
|
import su.reya.coop.viewmodel.ProfileCache
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@@ -24,11 +24,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
val externalSignerLauncher = ExternalSignerLauncher()
|
val externalSignerLauncher = ExternalSignerLauncher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val profileCache by lazy { ProfileCache(NostrManager.instance) }
|
||||||
|
|
||||||
private val factory by lazy {
|
private val factory by lazy {
|
||||||
object : ViewModelProvider.Factory {
|
object : ViewModelProvider.Factory {
|
||||||
private val storage = AppStore(this@MainActivity)
|
private val storage = AppStore(this@MainActivity)
|
||||||
private val mediaRepository = MediaRepository()
|
private val mediaRepository = MediaRepository()
|
||||||
private val nostrViewModel = NostrViewModel(NostrManager.instance)
|
|
||||||
private val chatViewModel = ChatViewModel(NostrManager.instance, mediaRepository)
|
private val chatViewModel = ChatViewModel(NostrManager.instance, mediaRepository)
|
||||||
private val androidSigner =
|
private val androidSigner =
|
||||||
AndroidExternalSigner(this@MainActivity, externalSignerLauncher)
|
AndroidExternalSigner(this@MainActivity, externalSignerLauncher)
|
||||||
@@ -37,7 +38,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
return when {
|
return when {
|
||||||
modelClass.isAssignableFrom(NostrViewModel::class.java) -> nostrViewModel
|
|
||||||
modelClass.isAssignableFrom(ChatViewModel::class.java) -> chatViewModel
|
modelClass.isAssignableFrom(ChatViewModel::class.java) -> chatViewModel
|
||||||
modelClass.isAssignableFrom(AccountViewModel::class.java) -> accountViewModel
|
modelClass.isAssignableFrom(AccountViewModel::class.java) -> accountViewModel
|
||||||
else -> throw IllegalArgumentException("Unknown ViewModel class")
|
else -> throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
@@ -46,7 +46,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val nostrViewModel: NostrViewModel by viewModels { factory }
|
|
||||||
private val chatViewModel: ChatViewModel by viewModels { factory }
|
private val chatViewModel: ChatViewModel by viewModels { factory }
|
||||||
private val accountViewModel: AccountViewModel by viewModels { factory }
|
private val accountViewModel: AccountViewModel by viewModels { factory }
|
||||||
|
|
||||||
@@ -92,7 +91,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
App(
|
App(
|
||||||
nostrViewModel = nostrViewModel,
|
profileCache = profileCache,
|
||||||
chatViewModel = chatViewModel,
|
chatViewModel = chatViewModel,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ import rust.nostr.sdk.PublicKey
|
|||||||
import su.reya.coop.LocalAccountViewModel
|
import su.reya.coop.LocalAccountViewModel
|
||||||
import su.reya.coop.LocalChatViewModel
|
import su.reya.coop.LocalChatViewModel
|
||||||
import su.reya.coop.LocalNavigator
|
import su.reya.coop.LocalNavigator
|
||||||
import su.reya.coop.LocalNostrViewModel
|
import su.reya.coop.LocalProfileCache
|
||||||
import su.reya.coop.LocalSnackbarHostState
|
import su.reya.coop.LocalSnackbarHostState
|
||||||
import su.reya.coop.Screen
|
import su.reya.coop.Screen
|
||||||
import su.reya.coop.shared.Avatar
|
import su.reya.coop.shared.Avatar
|
||||||
@@ -72,7 +72,7 @@ import su.reya.coop.short
|
|||||||
fun ContactListScreen() {
|
fun ContactListScreen() {
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
val snackbarHostState = LocalSnackbarHostState.current
|
val snackbarHostState = LocalSnackbarHostState.current
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val accountViewModel = LocalAccountViewModel.current
|
val accountViewModel = LocalAccountViewModel.current
|
||||||
val chatViewModel = LocalChatViewModel.current
|
val chatViewModel = LocalChatViewModel.current
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ fun ContactListScreen() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun AddContactDialog(onDismissRequest: () -> Unit) {
|
fun AddContactDialog(onDismissRequest: () -> Unit) {
|
||||||
val snackbarHostState = LocalSnackbarHostState.current
|
val snackbarHostState = LocalSnackbarHostState.current
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val accountViewModel = LocalAccountViewModel.current
|
val accountViewModel = LocalAccountViewModel.current
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
var contact by remember { mutableStateOf("") }
|
var contact by remember { mutableStateOf("") }
|
||||||
@@ -326,8 +326,8 @@ fun ContactListItem(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val profileFlow = remember(pubkey) { nostrViewModel.getMetadata(pubkey) }
|
val profileFlow = remember(pubkey) { profileCache.getMetadata(pubkey) }
|
||||||
val profile by profileFlow.collectAsStateWithLifecycle(initialValue = null)
|
val profile by profileFlow.collectAsStateWithLifecycle(initialValue = null)
|
||||||
|
|
||||||
SegmentedListItem(
|
SegmentedListItem(
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ import rust.nostr.sdk.PublicKey
|
|||||||
import su.reya.coop.LocalAccountViewModel
|
import su.reya.coop.LocalAccountViewModel
|
||||||
import su.reya.coop.LocalChatViewModel
|
import su.reya.coop.LocalChatViewModel
|
||||||
import su.reya.coop.LocalNavigator
|
import su.reya.coop.LocalNavigator
|
||||||
import su.reya.coop.LocalNostrViewModel
|
import su.reya.coop.LocalProfileCache
|
||||||
import su.reya.coop.LocalScanResult
|
import su.reya.coop.LocalScanResult
|
||||||
import su.reya.coop.LocalSnackbarHostState
|
import su.reya.coop.LocalSnackbarHostState
|
||||||
import su.reya.coop.Room
|
import su.reya.coop.Room
|
||||||
@@ -609,15 +609,15 @@ fun HomeScreen() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun NewRequests(requests: List<Room>) {
|
fun NewRequests(requests: List<Room>) {
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
|
|
||||||
val total = requests.size
|
val total = requests.size
|
||||||
val firstRoom = requests.getOrNull(0)
|
val firstRoom = requests.getOrNull(0)
|
||||||
val secondRoom = requests.getOrNull(1)
|
val secondRoom = requests.getOrNull(1)
|
||||||
|
|
||||||
val firstRoomState by (firstRoom as Room).uiStateFlow(nostrViewModel)
|
val firstRoomState by (firstRoom as Room).uiStateFlow(profileCache)
|
||||||
.collectAsStateWithLifecycle(RoomUiState())
|
.collectAsStateWithLifecycle(RoomUiState())
|
||||||
val secondRoomState by (secondRoom ?: firstRoom).uiStateFlow(nostrViewModel)
|
val secondRoomState by (secondRoom ?: firstRoom).uiStateFlow(profileCache)
|
||||||
.collectAsStateWithLifecycle(RoomUiState())
|
.collectAsStateWithLifecycle(RoomUiState())
|
||||||
|
|
||||||
val supportingText = when {
|
val supportingText = when {
|
||||||
@@ -689,8 +689,8 @@ fun NewRequests(requests: List<Room>) {
|
|||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatRoom(room: Room, onClick: () -> Unit) {
|
fun ChatRoom(room: Room, onClick: () -> Unit) {
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val roomState by room.uiStateFlow(nostrViewModel)
|
val roomState by room.uiStateFlow(profileCache)
|
||||||
.collectAsStateWithLifecycle(RoomUiState())
|
.collectAsStateWithLifecycle(RoomUiState())
|
||||||
|
|
||||||
ListItem(
|
ListItem(
|
||||||
@@ -737,7 +737,7 @@ fun BottomMenuList(
|
|||||||
onDismiss: (suspend () -> Unit) -> Unit
|
onDismiss: (suspend () -> Unit) -> Unit
|
||||||
) {
|
) {
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val chatViewModel = LocalChatViewModel.current
|
val chatViewModel = LocalChatViewModel.current
|
||||||
val accountViewModel = LocalAccountViewModel.current
|
val accountViewModel = LocalAccountViewModel.current
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import rust.nostr.sdk.PublicKey
|
|||||||
import su.reya.coop.LocalAccountViewModel
|
import su.reya.coop.LocalAccountViewModel
|
||||||
import su.reya.coop.LocalChatViewModel
|
import su.reya.coop.LocalChatViewModel
|
||||||
import su.reya.coop.LocalNavigator
|
import su.reya.coop.LocalNavigator
|
||||||
import su.reya.coop.LocalNostrViewModel
|
import su.reya.coop.LocalProfileCache
|
||||||
import su.reya.coop.LocalScanResult
|
import su.reya.coop.LocalScanResult
|
||||||
import su.reya.coop.LocalSnackbarHostState
|
import su.reya.coop.LocalSnackbarHostState
|
||||||
import su.reya.coop.Screen
|
import su.reya.coop.Screen
|
||||||
@@ -72,7 +72,7 @@ fun NewChatScreen() {
|
|||||||
val snackbarHostState = LocalSnackbarHostState.current
|
val snackbarHostState = LocalSnackbarHostState.current
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
val qrScanResult = LocalScanResult.current
|
val qrScanResult = LocalScanResult.current
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val accountViewModel = LocalAccountViewModel.current
|
val accountViewModel = LocalAccountViewModel.current
|
||||||
val chatViewModel = LocalChatViewModel.current
|
val chatViewModel = LocalChatViewModel.current
|
||||||
|
|
||||||
@@ -294,8 +294,8 @@ fun ReceiverChip(
|
|||||||
pubkey: PublicKey,
|
pubkey: PublicKey,
|
||||||
onRemove: () -> Unit
|
onRemove: () -> Unit
|
||||||
) {
|
) {
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val profileFlow = remember(pubkey) { nostrViewModel.getMetadata(pubkey) }
|
val profileFlow = remember(pubkey) { profileCache.getMetadata(pubkey) }
|
||||||
val profile by profileFlow.collectAsState(initial = null)
|
val profile by profileFlow.collectAsState(initial = null)
|
||||||
|
|
||||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||||
@@ -376,8 +376,8 @@ fun ContactListItem(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit
|
onLongClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val profileFlow = remember(pubkey) { nostrViewModel.getMetadata(pubkey) }
|
val profileFlow = remember(pubkey) { profileCache.getMetadata(pubkey) }
|
||||||
val profile by profileFlow.collectAsState(initial = null)
|
val profile by profileFlow.collectAsState(initial = null)
|
||||||
|
|
||||||
SegmentedListItem(
|
SegmentedListItem(
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ import org.jetbrains.compose.resources.painterResource
|
|||||||
import rust.nostr.sdk.PublicKey
|
import rust.nostr.sdk.PublicKey
|
||||||
import su.reya.coop.LocalChatViewModel
|
import su.reya.coop.LocalChatViewModel
|
||||||
import su.reya.coop.LocalNavigator
|
import su.reya.coop.LocalNavigator
|
||||||
import su.reya.coop.LocalNostrViewModel
|
import su.reya.coop.LocalProfileCache
|
||||||
import su.reya.coop.LocalSnackbarHostState
|
import su.reya.coop.LocalSnackbarHostState
|
||||||
import su.reya.coop.Screen
|
import su.reya.coop.Screen
|
||||||
import su.reya.coop.shared.Avatar
|
import su.reya.coop.shared.Avatar
|
||||||
@@ -61,11 +61,11 @@ fun ProfileScreen(pubkey: String) {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val snackbarHostState = LocalSnackbarHostState.current
|
val snackbarHostState = LocalSnackbarHostState.current
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val chatViewModel = LocalChatViewModel.current
|
val chatViewModel = LocalChatViewModel.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val profileFlow = remember(pubkey) { nostrViewModel.getMetadata(pubkey) }
|
val profileFlow = remember(pubkey) { profileCache.getMetadata(pubkey) }
|
||||||
val profile by profileFlow.collectAsStateWithLifecycle()
|
val profile by profileFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val metadata = profile?.metadata?.asRecord()
|
val metadata = profile?.metadata?.asRecord()
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ import org.jetbrains.compose.resources.painterResource
|
|||||||
import rust.nostr.sdk.RelayMetadata
|
import rust.nostr.sdk.RelayMetadata
|
||||||
import rust.nostr.sdk.RelayUrl
|
import rust.nostr.sdk.RelayUrl
|
||||||
import su.reya.coop.LocalNavigator
|
import su.reya.coop.LocalNavigator
|
||||||
import su.reya.coop.LocalNostrViewModel
|
import su.reya.coop.LocalProfileCache
|
||||||
import su.reya.coop.LocalAccountViewModel
|
import su.reya.coop.LocalAccountViewModel
|
||||||
import su.reya.coop.LocalSnackbarHostState
|
import su.reya.coop.LocalSnackbarHostState
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ import su.reya.coop.LocalSnackbarHostState
|
|||||||
@Composable
|
@Composable
|
||||||
fun RelayScreen() {
|
fun RelayScreen() {
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val accountViewModel = LocalAccountViewModel.current
|
val accountViewModel = LocalAccountViewModel.current
|
||||||
val snackbarHostState = LocalSnackbarHostState.current
|
val snackbarHostState = LocalSnackbarHostState.current
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import org.jetbrains.compose.resources.painterResource
|
|||||||
import rust.nostr.sdk.PublicKey
|
import rust.nostr.sdk.PublicKey
|
||||||
import rust.nostr.sdk.Timestamp
|
import rust.nostr.sdk.Timestamp
|
||||||
import su.reya.coop.LocalAccountViewModel
|
import su.reya.coop.LocalAccountViewModel
|
||||||
import su.reya.coop.LocalNostrViewModel
|
import su.reya.coop.LocalProfileCache
|
||||||
import su.reya.coop.Room
|
import su.reya.coop.Room
|
||||||
import su.reya.coop.humanReadable
|
import su.reya.coop.humanReadable
|
||||||
import su.reya.coop.shared.Avatar
|
import su.reya.coop.shared.Avatar
|
||||||
@@ -44,14 +44,14 @@ import su.reya.coop.short
|
|||||||
fun ScreenerCard(room: Room) {
|
fun ScreenerCard(room: Room) {
|
||||||
val pubkey = room.members.firstOrNull() ?: return
|
val pubkey = room.members.firstOrNull() ?: return
|
||||||
|
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val accountViewModel = LocalAccountViewModel.current
|
val accountViewModel = LocalAccountViewModel.current
|
||||||
|
|
||||||
var isContact by remember { mutableStateOf(false) }
|
var isContact by remember { mutableStateOf(false) }
|
||||||
var mutualContacts by remember { mutableStateOf<Set<PublicKey>>(emptySet()) }
|
var mutualContacts by remember { mutableStateOf<Set<PublicKey>>(emptySet()) }
|
||||||
var lastActivity by remember { mutableStateOf<Timestamp?>(null) }
|
var lastActivity by remember { mutableStateOf<Timestamp?>(null) }
|
||||||
|
|
||||||
val profileFlow = remember(pubkey) { nostrViewModel.getMetadata(pubkey) }
|
val profileFlow = remember(pubkey) { profileCache.getMetadata(pubkey) }
|
||||||
val profile by profileFlow.collectAsStateWithLifecycle()
|
val profile by profileFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
LaunchedEffect(pubkey) {
|
LaunchedEffect(pubkey) {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ import rust.nostr.sdk.UnsignedEvent
|
|||||||
import su.reya.coop.LocalAccountViewModel
|
import su.reya.coop.LocalAccountViewModel
|
||||||
import su.reya.coop.LocalChatViewModel
|
import su.reya.coop.LocalChatViewModel
|
||||||
import su.reya.coop.LocalNavigator
|
import su.reya.coop.LocalNavigator
|
||||||
import su.reya.coop.LocalNostrViewModel
|
import su.reya.coop.LocalProfileCache
|
||||||
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
|
||||||
@@ -88,7 +88,7 @@ fun ChatScreen(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val snackbarHostState = LocalSnackbarHostState.current
|
val snackbarHostState = LocalSnackbarHostState.current
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
val nostrViewModel = LocalNostrViewModel.current
|
val profileCache = LocalProfileCache.current
|
||||||
val chatViewModel = LocalChatViewModel.current
|
val chatViewModel = LocalChatViewModel.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
@@ -116,7 +116,7 @@ fun ChatScreen(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val roomState by (room as Room).uiStateFlow(nostrViewModel, currentUser?.publicKey)
|
val roomState by (room as Room).uiStateFlow(profileCache, currentUser?.publicKey)
|
||||||
.collectAsStateWithLifecycle(RoomUiState())
|
.collectAsStateWithLifecycle(RoomUiState())
|
||||||
var text by remember { mutableStateOf("") }
|
var text by remember { mutableStateOf("") }
|
||||||
var loading by remember { mutableStateOf(true) }
|
var loading by remember { mutableStateOf(true) }
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import kotlinx.datetime.toLocalDateTime
|
|||||||
import rust.nostr.sdk.PublicKey
|
import rust.nostr.sdk.PublicKey
|
||||||
import rust.nostr.sdk.Timestamp
|
import rust.nostr.sdk.Timestamp
|
||||||
import rust.nostr.sdk.UnsignedEvent
|
import rust.nostr.sdk.UnsignedEvent
|
||||||
import su.reya.coop.viewmodel.NostrViewModel
|
import su.reya.coop.viewmodel.ProfileCache
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ data class RoomUiState(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun Room.uiStateFlow(
|
fun Room.uiStateFlow(
|
||||||
nostrViewModel: NostrViewModel,
|
profileCache: ProfileCache,
|
||||||
currentUser: PublicKey? = null
|
currentUser: PublicKey? = null
|
||||||
): Flow<RoomUiState> {
|
): Flow<RoomUiState> {
|
||||||
val displayMembers = if (isGroup()) members.take(2) else members.take(1)
|
val displayMembers = if (isGroup()) members.take(2) else members.take(1)
|
||||||
@@ -81,7 +81,7 @@ fun Room.uiStateFlow(
|
|||||||
return flowOf(RoomUiState(name = subject.sanitizeName(), isGroup = isGroup()))
|
return flowOf(RoomUiState(name = subject.sanitizeName(), isGroup = isGroup()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return combine(displayMembers.map { nostrViewModel.getMetadata(it) }) { profiles ->
|
return combine(displayMembers.map { profileCache.getMetadata(it) }) { profiles ->
|
||||||
val names = profiles.mapIndexed { i, profile ->
|
val names = profiles.mapIndexed { i, profile ->
|
||||||
profile?.name?.sanitizeName() ?: displayMembers[i].short()
|
profile?.name?.sanitizeName() ?: displayMembers[i].short()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package su.reya.coop.viewmodel
|
package su.reya.coop.viewmodel
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import androidx.lifecycle.viewModelScope
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -16,33 +17,36 @@ import su.reya.coop.Profile
|
|||||||
import su.reya.coop.nostr.Nostr
|
import su.reya.coop.nostr.Nostr
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
class NostrViewModel(private val nostr: Nostr) : ViewModel(), ErrorHost by createErrorHost() {
|
/**
|
||||||
|
* Application-level singleton cache for Nostr user profiles.
|
||||||
|
*
|
||||||
|
* Replaces [NostrViewModel] as a non-ViewModel component with its own lifecycle scope.
|
||||||
|
* This is appropriate because profile caching is not screen-specific — it's a shared
|
||||||
|
* concern used by every screen that displays user metadata.
|
||||||
|
*
|
||||||
|
* Long-running tasks ([runObserver], [runMetadataBatching]) run in a dedicated
|
||||||
|
* [CoroutineScope] that outlives any individual screen, ensuring continuous operation
|
||||||
|
* regardless of navigation.
|
||||||
|
*/
|
||||||
|
class ProfileCache(
|
||||||
|
private val nostr: Nostr,
|
||||||
|
) : ErrorHost by createErrorHost() {
|
||||||
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||||
private val profilesMutex = Mutex()
|
private val profilesMutex = Mutex()
|
||||||
private val profiles = mutableMapOf<PublicKey, MutableStateFlow<Profile?>>()
|
private val profiles = mutableMapOf<PublicKey, MutableStateFlow<Profile?>>()
|
||||||
private val metadataRequestChannel = Channel<PublicKey>(Channel.UNLIMITED)
|
private val metadataRequestChannel = Channel<PublicKey>(Channel.UNLIMITED)
|
||||||
private val seenPublicKeys = mutableSetOf<PublicKey>()
|
private val seenPublicKeys = mutableSetOf<PublicKey>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Launch continuous background observers
|
scope.launch { runObserver() }
|
||||||
viewModelScope.launch { runObserver() }
|
scope.launch { runMetadataBatching() }
|
||||||
viewModelScope.launch { runMetadataBatching() }
|
scope.launch {
|
||||||
|
|
||||||
// Automatically reconnect bootstrap relays
|
|
||||||
reconnect()
|
|
||||||
|
|
||||||
// Get all local stored metadata
|
|
||||||
getCacheMetadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reconnect() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
nostr.waitUntilInitialized()
|
nostr.waitUntilInitialized()
|
||||||
nostr.reconnect()
|
loadCacheMetadata()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun runObserver() = coroutineScope {
|
private suspend fun runObserver() = coroutineScope {
|
||||||
// Observe metadata updates
|
|
||||||
launch {
|
launch {
|
||||||
nostr.profiles.metadataUpdates.collect { (pubkey, metadata) ->
|
nostr.profiles.metadataUpdates.collect { (pubkey, metadata) ->
|
||||||
updateMetadata(pubkey, Profile(pubkey, metadata))
|
updateMetadata(pubkey, Profile(pubkey, metadata))
|
||||||
@@ -57,7 +61,6 @@ class NostrViewModel(private val nostr: Nostr) : ViewModel(), ErrorHost by creat
|
|||||||
val firstKey = metadataRequestChannel.receive()
|
val firstKey = metadataRequestChannel.receive()
|
||||||
val batch = mutableSetOf(firstKey)
|
val batch = mutableSetOf(firstKey)
|
||||||
|
|
||||||
// Collect up to 10 keys that arrive within 500ms
|
|
||||||
while (batch.size < 10) {
|
while (batch.size < 10) {
|
||||||
val nextKey =
|
val nextKey =
|
||||||
withTimeoutOrNull(500.milliseconds) { metadataRequestChannel.receive() }
|
withTimeoutOrNull(500.milliseconds) { metadataRequestChannel.receive() }
|
||||||
@@ -69,10 +72,7 @@ class NostrViewModel(private val nostr: Nostr) : ViewModel(), ErrorHost by creat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCacheMetadata() {
|
private suspend fun loadCacheMetadata() {
|
||||||
viewModelScope.launch {
|
|
||||||
// Wait until the client is ready
|
|
||||||
nostr.waitUntilInitialized()
|
|
||||||
val cache = nostr.profiles.getAllCacheMetadata()
|
val cache = nostr.profiles.getAllCacheMetadata()
|
||||||
|
|
||||||
profilesMutex.withLock {
|
profilesMutex.withLock {
|
||||||
@@ -83,7 +83,6 @@ class NostrViewModel(private val nostr: Nostr) : ViewModel(), ErrorHost by creat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun requestMetadata(pubkey: PublicKey) {
|
private fun requestMetadata(pubkey: PublicKey) {
|
||||||
if (seenPublicKeys.add(pubkey)) {
|
if (seenPublicKeys.add(pubkey)) {
|
||||||
@@ -97,11 +96,13 @@ class NostrViewModel(private val nostr: Nostr) : ViewModel(), ErrorHost by creat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [StateFlow] for the profile of the given [pubkey].
|
||||||
|
* Triggers a metadata fetch if the profile is not yet cached.
|
||||||
|
*/
|
||||||
fun getMetadata(pubkey: PublicKey): StateFlow<Profile?> {
|
fun getMetadata(pubkey: PublicKey): StateFlow<Profile?> {
|
||||||
val flow = profiles.getOrPut(pubkey) { MutableStateFlow(null) }
|
val flow = profiles.getOrPut(pubkey) { MutableStateFlow(null) }
|
||||||
if (flow.value == null) requestMetadata(pubkey)
|
if (flow.value == null) requestMetadata(pubkey)
|
||||||
|
|
||||||
return flow.asStateFlow()
|
return flow.asStateFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user