Simplifying observables

This commit is contained in:
Vitor Pamplona
2023-06-01 16:01:35 -04:00
parent 92c61f317b
commit 99c30816c6
9 changed files with 175 additions and 144 deletions
@@ -449,10 +449,12 @@ private fun ObserveNIP19Event(
var baseNote by remember(it) { mutableStateOf<Note?>(null) }
LaunchedEffect(key1 = it.hex) {
launch(Dispatchers.IO) {
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
baseNote = note
if (baseNote == null) {
launch(Dispatchers.IO) {
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
baseNote = note
}
}
}
}
@@ -479,10 +481,12 @@ private fun ObserveNIP19User(
var baseUser by remember(it) { mutableStateOf<User?>(null) }
LaunchedEffect(key1 = it.hex) {
launch(Dispatchers.IO) {
if (it.type == Nip19.Type.USER) {
LocalCache.checkGetOrCreateUser(it.hex)?.let { user ->
baseUser = user
if (baseUser == null) {
launch(Dispatchers.IO) {
if (it.type == Nip19.Type.USER) {
LocalCache.checkGetOrCreateUser(it.hex)?.let { user ->
baseUser = user
}
}
}
}
@@ -782,27 +786,29 @@ fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgro
var baseNotePair by remember { mutableStateOf<Pair<Note, String?>?>(null) }
LaunchedEffect(key1 = word) {
launch(Dispatchers.IO) {
val matcher = tagIndex.matcher(word)
val (index, suffix) = try {
matcher.find()
Pair(matcher.group(1)?.toInt(), matcher.group(2) ?: "")
} catch (e: Exception) {
Log.w("Tag Parser", "Couldn't link tag $word", e)
Pair(null, null)
}
if (baseUserPair == null && baseNotePair == null) {
launch(Dispatchers.IO) {
val matcher = tagIndex.matcher(word)
val (index, suffix) = try {
matcher.find()
Pair(matcher.group(1)?.toInt(), matcher.group(2) ?: "")
} catch (e: Exception) {
Log.w("Tag Parser", "Couldn't link tag $word", e)
Pair(null, null)
}
if (index != null && index >= 0 && index < tags.size) {
val tag = tags[index]
if (index != null && index >= 0 && index < tags.size) {
val tag = tags[index]
if (tag.size > 1) {
if (tag[0] == "p") {
LocalCache.checkGetOrCreateUser(tag[1])?.let {
baseUserPair = Pair(it, suffix)
}
} else if (tag[0] == "e" || tag[0] == "a") {
LocalCache.checkGetOrCreateNote(tag[1])?.let {
baseNotePair = Pair(it, suffix)
if (tag.size > 1) {
if (tag[0] == "p") {
LocalCache.checkGetOrCreateUser(tag[1])?.let {
baseUserPair = Pair(it, suffix)
}
} else if (tag[0] == "e" || tag[0] == "a") {
LocalCache.checkGetOrCreateNote(tag[1])?.let {
baseNotePair = Pair(it, suffix)
}
}
}
}
@@ -110,31 +110,11 @@ private fun RowScope.HasNewItemsIcon(
accountViewModel: AccountViewModel,
navController: NavHostController
) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return
var hasNewItems by remember { mutableStateOf(false) }
val notifState by NotificationCache.live.observeAsState()
val notif = remember(notifState) { notifState?.cache } ?: return
var hasNewItems by remember { mutableStateOf<Boolean>(false) }
LaunchedEffect(key1 = notifState, key2 = accountState) {
launch(Dispatchers.IO) {
val newHasNewItems = route.hasNewItems(account, notif, emptySet())
if (newHasNewItems != hasNewItems) {
hasNewItems = newHasNewItems
}
}
}
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect {
val newHasNewItems = route.hasNewItems(account, notif, it)
if (newHasNewItems != hasNewItems) {
hasNewItems = newHasNewItems
}
}
WatchPossibleNotificationChanges(route, accountViewModel) {
if (it != hasNewItems) {
hasNewItems = it
}
}
@@ -167,6 +147,33 @@ private fun RowScope.HasNewItemsIcon(
}
}
@Composable
fun WatchPossibleNotificationChanges(
route: Route,
accountViewModel: AccountViewModel,
onChange: (Boolean) -> Unit
) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return
val notifState by NotificationCache.live.observeAsState()
val notif = remember(notifState) { notifState?.cache } ?: return
LaunchedEffect(key1 = notifState, key2 = accountState) {
launch(Dispatchers.IO) {
onChange(route.hasNewItems(account, notif, emptySet()))
}
}
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect {
onChange(route.hasNewItems(account, notif, it))
}
}
}
}
@Composable
private fun RowScope.BottomIcon(
icon: Int,
@@ -54,7 +54,6 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
@@ -103,16 +102,20 @@ fun AppTopBar(followLists: FollowListViewModel, navController: NavHostController
fun StoriesTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
GenericTopBar(scaffoldState, accountViewModel) { accountViewModel ->
val accountState by accountViewModel.accountLiveData.observeAsState()
accountState?.account?.let { account ->
FollowList(
followLists,
account.defaultStoriesFollowList,
account.userProfile(),
true
) { listName ->
account.changeDefaultStoriesFollowList(listName)
val list by remember(accountState) {
derivedStateOf {
accountState?.account?.defaultStoriesFollowList ?: GLOBAL_FOLLOWS
}
}
FollowList(
followLists,
list,
true
) { listName ->
accountViewModel.account.changeDefaultStoriesFollowList(listName)
}
}
}
@@ -120,16 +123,20 @@ fun StoriesTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState
fun HomeTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
GenericTopBar(scaffoldState, accountViewModel) { accountViewModel ->
val accountState by accountViewModel.accountLiveData.observeAsState()
accountState?.account?.let { account ->
FollowList(
followLists,
account.defaultHomeFollowList,
account.userProfile(),
false
) { listName ->
account.changeDefaultHomeFollowList(listName)
val list by remember(accountState) {
derivedStateOf {
accountState?.account?.defaultHomeFollowList ?: GLOBAL_FOLLOWS
}
}
FollowList(
followLists,
list,
false
) { listName ->
accountViewModel.account.changeDefaultHomeFollowList(listName)
}
}
}
@@ -137,16 +144,20 @@ fun HomeTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, a
fun NotificationTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
GenericTopBar(scaffoldState, accountViewModel) { accountViewModel ->
val accountState by accountViewModel.accountLiveData.observeAsState()
accountState?.account?.let { account ->
FollowList(
followLists,
account.defaultNotificationFollowList,
account.userProfile(),
true
) { listName ->
account.changeDefaultNotificationFollowList(listName)
val list by remember(accountState) {
derivedStateOf {
accountState?.account?.defaultNotificationFollowList ?: GLOBAL_FOLLOWS
}
}
FollowList(
followLists,
list,
true
) { listName ->
accountViewModel.account.changeDefaultNotificationFollowList(listName)
}
}
}
@@ -296,7 +307,7 @@ private fun LoggedInUserPictureDrawer(
}
@Composable
fun FollowList(followListsModel: FollowListViewModel, listName: String, loggedIn: User, withGlobal: Boolean, onChange: (String) -> Unit) {
fun FollowList(followListsModel: FollowListViewModel, listName: String, withGlobal: Boolean, onChange: (String) -> Unit) {
val kind3Follow = Pair(KIND3_FOLLOWS, stringResource(id = R.string.follow_list_kind3follows))
val globalFollow = Pair(GLOBAL_FOLLOWS, stringResource(id = R.string.follow_list_global))
@@ -339,7 +350,10 @@ class FollowListViewModel(val account: Account) : ViewModel() {
val newFollowLists = LocalCache.addressables.mapNotNull {
val event = (it.value.event as? PeopleListEvent)
// Has to have an list
if (event != null && event.pubKey == account.userProfile().pubkeyHex && (event.tags.size > 1 || event.content.length > 50)) {
if (event != null &&
event.pubKey == account.userProfile().pubkeyHex &&
(event.tags.size > 1 || event.content.length > 50)
) {
Pair(event.dTag(), event.dTag())
} else {
null
@@ -76,16 +76,13 @@ fun DrawerContent(
sheetState: ModalBottomSheetState,
accountViewModel: AccountViewModel
) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
Surface(
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colors.background
) {
Column() {
ProfileContent(
account.userProfile(),
accountViewModel.account.userProfile(),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 25.dp)
@@ -98,17 +95,16 @@ fun DrawerContent(
modifier = Modifier.padding(top = 20.dp)
)
ListContent(
account.userProfile().pubkeyHex,
nav,
scaffoldState,
sheetState,
modifier = Modifier
.fillMaxWidth()
.weight(1f),
account
accountViewModel
)
BottomContent(account.userProfile(), scaffoldState, nav)
BottomContent(accountViewModel.account.userProfile(), scaffoldState, nav)
}
}
}
@@ -265,13 +261,15 @@ private fun FollowingAndFollowerCounts(baseAccountUser: User) {
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ListContent(
accountUserPubKey: String?,
nav: (String) -> Unit,
scaffoldState: ScaffoldState,
sheetState: ModalBottomSheetState,
modifier: Modifier,
account: Account
accountViewModel: AccountViewModel
) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return
val coroutineScope = rememberCoroutineScope()
var backupDialogOpen by remember { mutableStateOf(false) }
var checked by remember { mutableStateOf(account.proxy != null) }
@@ -284,25 +282,23 @@ fun ListContent(
.fillMaxHeight()
.verticalScroll(rememberScrollState())
) {
if (accountUserPubKey != null) {
NavigationRow(
title = stringResource(R.string.profile),
icon = Route.Profile.icon,
tint = MaterialTheme.colors.primary,
nav = nav,
scaffoldState = scaffoldState,
route = "User/$accountUserPubKey"
)
NavigationRow(
title = stringResource(R.string.profile),
icon = Route.Profile.icon,
tint = MaterialTheme.colors.primary,
nav = nav,
scaffoldState = scaffoldState,
route = "User/${account.userProfile().pubkeyHex}"
)
NavigationRow(
title = stringResource(R.string.bookmarks),
icon = Route.Bookmarks.icon,
tint = MaterialTheme.colors.onBackground,
nav = nav,
scaffoldState = scaffoldState,
route = Route.Bookmarks.route
)
}
NavigationRow(
title = stringResource(R.string.bookmarks),
icon = Route.Bookmarks.icon,
tint = MaterialTheme.colors.onBackground,
nav = nav,
scaffoldState = scaffoldState,
route = Route.Bookmarks.route
)
NavigationRow(
title = stringResource(R.string.security_filters),
@@ -65,9 +65,6 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
val noteState by baseNote.live().metadata.observeAsState()
val note = remember(noteState) { noteState?.note } ?: return
val accountState by accountViewModel.accountLiveData.observeAsState()
val loggedIn = remember(accountState) { accountState?.account?.userProfile() } ?: return
var popupExpanded by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
@@ -108,7 +105,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
.combinedClickable(
onClick = {
scope.launch {
routeFor(baseNote, loggedIn)?.let { nav(it) }
routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) }
}
},
onLongClick = { popupExpanded = true }
@@ -15,6 +15,7 @@ import androidx.compose.material.icons.filled.Report
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
@@ -97,11 +98,14 @@ fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): State<Bool
@Composable
fun ObserveDisplayNip05Status(baseNote: Note, columnModifier: Modifier = Modifier) {
val noteState by baseNote.live().metadata.observeAsState()
val note = noteState?.note ?: return
val author by remember(noteState) {
derivedStateOf {
noteState?.note?.author
}
}
val author = note.author
if (author != null) {
ObserveDisplayNip05Status(author, columnModifier)
author?.let {
ObserveDisplayNip05Status(it, columnModifier)
}
}
@@ -269,19 +269,18 @@ fun ZapVote(
nonClickablePrepend: @Composable () -> Unit,
clickablePrepend: @Composable () -> Unit
) {
val zapsState by baseNote.live().zaps.observeAsState()
val zappedNote = zapsState?.note ?: return
val isLoggedUser by remember {
derivedStateOf {
accountViewModel.isLoggedUser(baseNote.author)
}
}
var wantsToZap by remember { mutableStateOf(false) }
var zappingProgress by remember { mutableStateOf(0f) }
val context = LocalContext.current
val scope = rememberCoroutineScope()
var zappingProgress by remember { mutableStateOf(0f) }
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
nonClickablePrepend()
Row(
@@ -311,7 +310,7 @@ fun ZapVote(
)
.show()
}
} else if (accountViewModel.isLoggedUser(zappedNote.author)) {
} else if (isLoggedUser) {
scope.launch {
Toast
.makeText(
@@ -333,11 +332,13 @@ fun ZapVote(
.show()
}
return@combinedClickable
} else if (account.zapAmountChoices.size == 1 && pollViewModel.isValidInputVoteAmount(account.zapAmountChoices.first())) {
} else if (accountViewModel.account.zapAmountChoices.size == 1 &&
pollViewModel.isValidInputVoteAmount(accountViewModel.account.zapAmountChoices.first())
) {
scope.launch(Dispatchers.IO) {
accountViewModel.zap(
baseNote,
account.zapAmountChoices.first() * 1000,
accountViewModel.account.zapAmountChoices.first() * 1000,
poolOption.option,
"",
context,
@@ -354,7 +355,7 @@ fun ZapVote(
zappingProgress = it
}
},
zapType = account.defaultZapType
zapType = accountViewModel.account.defaultZapType
)
}
} else {
@@ -519,10 +519,8 @@ private fun ZapIcon(
var wasZappedByLoggedInUser by remember { mutableStateOf(false) }
val zapsState by baseNote.live().zaps.observeAsState()
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = zapsState) {
scope.launch(Dispatchers.IO) {
launch(Dispatchers.IO) {
zapsState?.note?.let {
if (!wasZappedByLoggedInUser) {
val newWasZapped = accountViewModel.calculateIfNoteWasZappedByAccount(it)
@@ -559,17 +557,16 @@ private fun ZapAmountText(
accountViewModel: AccountViewModel
) {
val zapsState by baseNote.live().zaps.observeAsState()
val zappedNote = remember(zapsState) { zapsState?.note } ?: return
val scope = rememberCoroutineScope()
var zapAmountTxt by remember { mutableStateOf("") }
LaunchedEffect(key1 = zapsState) {
scope.launch(Dispatchers.IO) {
val newZapAmount = showAmount(accountViewModel.calculateZapAmount(zappedNote))
if (newZapAmount != zapAmountTxt) {
zapAmountTxt = newZapAmount
launch(Dispatchers.IO) {
zapsState?.note?.let {
val newZapAmount = showAmount(accountViewModel.calculateZapAmount(it))
if (newZapAmount != zapAmountTxt) {
zapAmountTxt = newZapAmount
}
}
}
}
@@ -307,11 +307,13 @@ private fun ReportsTabHeader(baseUser: User) {
var userReports by remember { mutableStateOf(0) }
LaunchedEffect(key1 = userState) {
UserProfileReportsFeedFilter.user = baseUser
val newSize = UserProfileReportsFeedFilter.feed().size
launch(Dispatchers.IO) {
UserProfileReportsFeedFilter.user = baseUser
val newSize = UserProfileReportsFeedFilter.feed().size
if (newSize != userReports) {
userReports = newSize
if (newSize != userReports) {
userReports = newSize
}
}
}
@@ -321,12 +323,19 @@ private fun ReportsTabHeader(baseUser: User) {
@Composable
private fun BookmarkTabHeader(baseUser: User) {
val userState by baseUser.live().bookmarks.observeAsState()
val userBookmarks = remember(userState) {
val bookmarkList = userState?.user?.latestBookmarkList
(bookmarkList?.taggedEvents()?.count() ?: 0) + (
bookmarkList?.taggedAddresses()?.count()
?: 0
)
var userBookmarks by remember { mutableStateOf(0) }
LaunchedEffect(key1 = userState) {
launch(Dispatchers.IO) {
val bookmarkList = userState?.user?.latestBookmarkList
val newBookmarks = (bookmarkList?.taggedEvents()?.count() ?: 0) + (bookmarkList?.taggedAddresses()?.count() ?: 0)
if (newBookmarks != userBookmarks) {
userBookmarks = newBookmarks
}
}
}
Text(text = "$userBookmarks ${stringResource(R.string.bookmarks)}")