Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List/Set management: Part 1 - Profiles. #1169

Draft
wants to merge 40 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
c5be20b
Add icon for Lists and create Drawer/Route entry.
KotlinGeekDev Oct 1, 2024
3c22129
Merge remote-tracking branch 'origin/profiles-list-management' into p…
KotlinGeekDev Oct 5, 2024
34a6be6
Merge remote-tracking branch 'origin/profiles-list-management' into p…
KotlinGeekDev Oct 9, 2024
3d11845
Merge remote-tracking branch 'origin/profiles-list-management' into p…
KotlinGeekDev Oct 16, 2024
bbeec18
Merge remote-tracking branch 'origin/profiles-list-management' into p…
KotlinGeekDev Oct 16, 2024
9bca918
Merge remote-tracking branch 'origin/profiles-list-management' into p…
KotlinGeekDev Oct 17, 2024
1076e00
Merge remote-tracking branch 'origin/profiles-list-management' into p…
KotlinGeekDev Oct 17, 2024
bcd2201
Merge remote-tracking branch 'origin/profiles-list-management' into p…
KotlinGeekDev Oct 18, 2024
27d9b63
Merge remote-tracking branch 'origin/profiles-list-management' into p…
KotlinGeekDev Oct 20, 2024
0c9bea3
Add function to get all follow sets from cache.
KotlinGeekDev Oct 23, 2024
5a38267
Merge remote-tracking branch 'origin/profiles-list-management' into p…
KotlinGeekDev Oct 23, 2024
c79157f
Add description() for list events.
KotlinGeekDev Oct 24, 2024
afca104
Add FollowSet class, and property in User.
KotlinGeekDev Oct 26, 2024
aa1175c
Implement method for retrieving follow sets.
KotlinGeekDev Oct 26, 2024
c86ce1b
Load and populate follow sets in Account(since Account is initialized…
KotlinGeekDev Oct 26, 2024
caad506
Refactor followSetNotesFlow() and introduce FollowSetFeedFilter.
KotlinGeekDev Oct 27, 2024
7dec46f
Create Viewmodel for Follow sets. Refactor User.followSets to be a co…
KotlinGeekDev Oct 27, 2024
bc7d9ee
Make FollowSet stable. The class may be moved to another location.
KotlinGeekDev Oct 30, 2024
0b014c5
Move mapping function from FollowSetFeedFilter to FollowSet.
KotlinGeekDev Oct 30, 2024
d348e7a
Introduce ListsScreen. Current implementation is focused on follows.
KotlinGeekDev Oct 30, 2024
5b51e97
Replace TODOs with provided feed state implementations.
KotlinGeekDev Oct 30, 2024
ddbaca3
Navigation: Add custom lists to main app navigation control helper.
KotlinGeekDev Oct 30, 2024
3d4483e
Move FollowSet to it's own class.
KotlinGeekDev Oct 31, 2024
0e9cc54
Refactor FollowSetFeedFilter and Account.
KotlinGeekDev Oct 31, 2024
f3edc2d
Introduce FollowSetFeedViewModel and FollowSetState.
KotlinGeekDev Oct 31, 2024
63654b7
Refactor NostrUserFollowSetFeedViewModel to use the FollowSetViewMode…
KotlinGeekDev Oct 31, 2024
ee8148a
Just use NavigationRow instead of IconRow.
KotlinGeekDev Oct 31, 2024
00b7ace
Change followSets signature again. Refactor followSetNotesFLow().
KotlinGeekDev Oct 31, 2024
a39834d
Use FollowSetState in in CustomListsScreen. Make other fixes.
KotlinGeekDev Oct 31, 2024
8f798d1
Add some error handling in the feed filter.
KotlinGeekDev Oct 31, 2024
f2640de
Minor refactor.
KotlinGeekDev Oct 31, 2024
2366f26
Merge branch 'main' of https://github.com/vitorpamplona/amethyst into…
KotlinGeekDev Oct 31, 2024
3ad26f0
Merge branch 'vitorpamplona-main' into profiles-list-management
KotlinGeekDev Oct 31, 2024
4b76f56
Merge branch 'vitorpamplona:main' into profiles-list-management
KotlinGeekDev Oct 31, 2024
2fcd6af
Merge branch 'vitorpamplona:main' into profiles-list-management
KotlinGeekDev Nov 1, 2024
d763f43
Restore function for getting follow sets(due to merge of upstream).
KotlinGeekDev Nov 1, 2024
aed914d
Merge remote-tracking branch 'origin/profiles-list-management' into p…
KotlinGeekDev Nov 1, 2024
aa64e42
Introduce FollowSetsActionMenu.
KotlinGeekDev Nov 5, 2024
883f7a9
Experiment with Icon as a button. Refactor FollowButton and UnfollowB…
KotlinGeekDev Nov 6, 2024
c485f75
Make custom button border naming clearer, and fix list dropdown butto…
KotlinGeekDev Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.service.FileHeader
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSet
import com.vitorpamplona.amethyst.ui.tor.TorType
import com.vitorpamplona.ammolite.relays.Client
import com.vitorpamplona.ammolite.relays.Constants
Expand Down Expand Up @@ -130,6 +131,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import java.math.BigDecimal
import java.util.Locale
Expand Down Expand Up @@ -2891,6 +2893,22 @@ class Account(
return LocalCache.getOrCreateAddressableNote(aTag)
}

suspend fun getFollowSetNotes() =
withContext(Dispatchers.Default) {
val followSetNotes = LocalCache.getFollowSetsFor(userProfile())
userProfile().followSets = followSetNotes
println("Number of follow sets: ${followSetNotes.size}")
}

fun mapNoteToFollowSet(note: Note): FollowSet =
FollowSet
.mapEventToSet(
event = note.event as PeopleListEvent,
signer,
)

fun followSetNotesFlow() = MutableStateFlow(userProfile().followSets)

fun getMuteListFlow(): StateFlow<NoteState> = getMuteListNote().flow().metadata.stateFlow

fun getBlockList(): PeopleListEvent? = getBlockListNote().event as? PeopleListEvent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2163,6 +2163,19 @@ object LocalCache {
}
}

fun getFollowSetsFor(user: User): List<AddressableNote> {
checkNotInMainThread()

return addressables
.filter { _, note ->
val listEvent = note.event
(
listEvent is PeopleListEvent &&
user.pubkeyHex == listEvent.pubKey
)
}
}

suspend fun findStatusesForUser(user: User): ImmutableList<AddressableNote> {
checkNotInMainThread()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class User(
var latestMetadataRelay: String? = null
var latestContactList: ContactListEvent? = null
var latestBookmarkList: BookmarkListEvent? = null
var followSets: List<AddressableNote> = listOf()

var reports = mapOf<User, Set<Note>>()
private set
Expand Down Expand Up @@ -470,16 +471,19 @@ class UserFlowSet(
val metadata = UserBundledRefresherFlow(u)
val follows = UserBundledRefresherFlow(u)
val relays = UserBundledRefresherFlow(u)
val followSets = UserBundledRefresherFlow(u)

fun isInUse(): Boolean =
metadata.stateFlow.subscriptionCount.value > 0 ||
relays.stateFlow.subscriptionCount.value > 0 ||
follows.stateFlow.subscriptionCount.value > 0
follows.stateFlow.subscriptionCount.value > 0 ||
followSets.stateFlow.subscriptionCount.value > 0

fun destroy() {
metadata.destroy()
relays.destroy()
follows.destroy()
followSets.destroy()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.dal

import android.util.Log
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSet
import kotlinx.coroutines.launch
import kotlin.coroutines.cancellation.CancellationException

class FollowSetFeedFilter(
val account: Account,
) : FeedFilter<FollowSet>() {
override fun feedKey(): String = account.userProfile().pubkeyHex

override fun feed(): List<FollowSet> {
if (account.userProfile().followSets.isEmpty()) {
account.scope.launch {
try {
account.getFollowSetNotes()
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e("HiddenAccountsFeedFilter", "Failed to load follow lists: ${e.message}")
null
}
}
}
return account.followSetNotesFlow().value.map { account.mapNoteToFollowSet(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.DvmContentDiscoveryScr
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.GeoHashScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.HashtagScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.HomeScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.ListsScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen
Expand Down Expand Up @@ -130,6 +131,14 @@ fun AppNavigation(
popExitTransition = { slideOutHorizontallyToEnd },
) { BookmarkListScreen(accountViewModel, nav) }

composable(
Route.Lists.route,
enterTransition = { slideInHorizontallyFromEnd },
exitTransition = { scaleOut },
popEnterTransition = { scaleIn },
popExitTransition = { slideOutHorizontallyToEnd },
) { ListsScreen(accountViewModel, nav) }

composable(
Route.Drafts.route,
enterTransition = { slideInHorizontallyFromEnd },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,14 @@ fun ListContent(
route = route,
)

NavigationRow(
title = stringRes(Route.Lists.contentDescriptor),
icon = Route.Lists.icon,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Lists.route,
)

NavigationRow(
title = stringRes(R.string.bookmarks),
icon = Route.Bookmarks.icon,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ sealed class Route(
contentDescriptor = R.string.route_home,
)

object Lists :
Route(
route = "Lists",
icon = R.drawable.format_list_bulleted_type,
contentDescriptor = R.string.my_lists,
)

object ContentDiscovery :
Route(
icon = R.drawable.ic_bookmarks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import com.vitorpamplona.amethyst.ui.dal.ChatroomFeedFilter
import com.vitorpamplona.amethyst.ui.dal.CommunityFeedFilter
import com.vitorpamplona.amethyst.ui.dal.DraftEventsFeedFilter
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
import com.vitorpamplona.amethyst.ui.dal.FollowSetFeedFilter
import com.vitorpamplona.amethyst.ui.dal.GeoHashFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
import com.vitorpamplona.amethyst.ui.dal.NIP90ContentDiscoveryResponseFilter
Expand All @@ -56,6 +57,7 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
import com.vitorpamplona.amethyst.ui.feeds.FeedState
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSetFeedViewModel
import com.vitorpamplona.quartz.events.ChatroomKey
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -231,6 +233,16 @@ class NostrBookmarkPrivateFeedViewModel(
}
}

class NostrUserFollowSetFeedViewModel(
val account: Account,
) : FollowSetFeedViewModel(FollowSetFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrUserFollowSetFeedViewModel : ViewModel> create(modelClass: Class<NostrUserFollowSetFeedViewModel>): NostrUserFollowSetFeedViewModel = NostrUserFollowSetFeedViewModel(account) as NostrUserFollowSetFeedViewModel
}
}

@Stable
class NostrNIP90ContentDiscoveryFeedViewModel(
val account: Account,
Expand Down
Loading