mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 15:07:52 +01:00
Adds a All User Follows to fix: https://github.com/vitorpamplona/amethyst/issues/1431
This commit is contained in:
@@ -97,6 +97,9 @@ val GLOBAL_FOLLOWS = " Global "
|
|||||||
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
|
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
|
||||||
val ALL_FOLLOWS = " All Follows "
|
val ALL_FOLLOWS = " All Follows "
|
||||||
|
|
||||||
|
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
|
||||||
|
val ALL_USER_FOLLOWS = " All User Follows "
|
||||||
|
|
||||||
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
|
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
|
||||||
val AROUND_ME = " Around Me "
|
val AROUND_ME = " Around Me "
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,13 @@
|
|||||||
package com.vitorpamplona.amethyst.model.topNavFeeds
|
package com.vitorpamplona.amethyst.model.topNavFeeds
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.ALL_FOLLOWS
|
import com.vitorpamplona.amethyst.model.ALL_FOLLOWS
|
||||||
|
import com.vitorpamplona.amethyst.model.ALL_USER_FOLLOWS
|
||||||
import com.vitorpamplona.amethyst.model.AROUND_ME
|
import com.vitorpamplona.amethyst.model.AROUND_ME
|
||||||
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState
|
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState
|
||||||
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsFeedFlow
|
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsFeedFlow
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows.AllUserFollowsFeedFlow
|
||||||
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.AroundMeFeedFlow
|
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.AroundMeFeedFlow
|
||||||
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalFeedFlow
|
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalFeedFlow
|
||||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.NoteFeedFlow
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.NoteFeedFlow
|
||||||
@@ -60,6 +62,7 @@ class FeedTopNavFilterState(
|
|||||||
when (listName) {
|
when (listName) {
|
||||||
GLOBAL_FOLLOWS -> GlobalFeedFlow(followsRelays, proxyRelays)
|
GLOBAL_FOLLOWS -> GlobalFeedFlow(followsRelays, proxyRelays)
|
||||||
ALL_FOLLOWS -> AllFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays)
|
ALL_FOLLOWS -> AllFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays)
|
||||||
|
ALL_USER_FOLLOWS -> AllUserFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays)
|
||||||
AROUND_ME -> AroundMeFeedFlow(locationFlow, followsRelays, proxyRelays)
|
AROUND_ME -> AroundMeFeedFlow(locationFlow, followsRelays, proxyRelays)
|
||||||
else -> {
|
else -> {
|
||||||
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
|
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 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.model.topNavFeeds.allUserFollows
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxRelayLoader
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilter
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||||
|
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a big OR filter on all fields.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class AllUserFollowsByOutboxTopNavFilter(
|
||||||
|
val authors: Set<String>,
|
||||||
|
val defaultRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||||
|
val blockedRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||||
|
) : IFeedTopNavFilter {
|
||||||
|
override fun matchAuthor(pubkey: HexKey): Boolean = pubkey in authors
|
||||||
|
|
||||||
|
override fun match(noteEvent: Event): Boolean =
|
||||||
|
when (noteEvent) {
|
||||||
|
is LiveActivitiesEvent -> {
|
||||||
|
noteEvent.participantsIntersect(authors)
|
||||||
|
}
|
||||||
|
|
||||||
|
is CommentEvent -> {
|
||||||
|
// ignore follows and checks only the root scope
|
||||||
|
noteEvent.pubKey in authors
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
noteEvent.pubKey in authors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toPerRelayFlow(cache: LocalCache): Flow<AuthorsTopNavPerRelayFilterSet> {
|
||||||
|
val authorsPerRelay = OutboxRelayLoader().toAuthorsPerRelayFlow(authors, cache) { it }
|
||||||
|
|
||||||
|
return combine(authorsPerRelay, defaultRelays, blockedRelays) { perRelayAuthors, default, blockedRelays ->
|
||||||
|
val allRelays = perRelayAuthors.keys.filter { it !in blockedRelays }.ifEmpty { default }
|
||||||
|
|
||||||
|
AuthorsTopNavPerRelayFilterSet(
|
||||||
|
allRelays.associateWith {
|
||||||
|
AuthorsTopNavPerRelayFilter(
|
||||||
|
authors = perRelayAuthors[it] ?: emptySet(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startValue(cache: LocalCache): AuthorsTopNavPerRelayFilterSet {
|
||||||
|
val authorsPerRelay = OutboxRelayLoader().authorsPerRelaySnapshot(authors, cache) { it }
|
||||||
|
|
||||||
|
val allRelays = authorsPerRelay.keys.filter { it !in blockedRelays.value }.ifEmpty { defaultRelays.value }
|
||||||
|
|
||||||
|
return AuthorsTopNavPerRelayFilterSet(
|
||||||
|
allRelays.associateWith {
|
||||||
|
AuthorsTopNavPerRelayFilter(
|
||||||
|
authors = authorsPerRelay[it] ?: emptySet(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 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.model.topNavFeeds.allUserFollows
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilter
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||||
|
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a big OR filter on all fields.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class AllUserFollowsByProxyTopNavFilter(
|
||||||
|
val authors: Set<String>,
|
||||||
|
val proxyRelays: Set<NormalizedRelayUrl>,
|
||||||
|
) : IFeedTopNavFilter {
|
||||||
|
override fun matchAuthor(pubkey: HexKey): Boolean = pubkey in authors
|
||||||
|
|
||||||
|
override fun match(noteEvent: Event): Boolean =
|
||||||
|
when (noteEvent) {
|
||||||
|
is LiveActivitiesEvent -> {
|
||||||
|
noteEvent.participantsIntersect(authors)
|
||||||
|
}
|
||||||
|
|
||||||
|
is CommentEvent -> {
|
||||||
|
// ignore follows and checks only the root scope
|
||||||
|
noteEvent.pubKey in authors
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
noteEvent.pubKey in authors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// forces the use of the Proxy on all connections, replacing the outbox model.
|
||||||
|
override fun toPerRelayFlow(cache: LocalCache): Flow<AuthorsTopNavPerRelayFilterSet> =
|
||||||
|
MutableStateFlow(
|
||||||
|
AuthorsTopNavPerRelayFilterSet(
|
||||||
|
proxyRelays.associateWith {
|
||||||
|
AuthorsTopNavPerRelayFilter(
|
||||||
|
authors = authors,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun startValue(cache: LocalCache): AuthorsTopNavPerRelayFilterSet {
|
||||||
|
// forces the use of the Proxy on all connections, replacing the outbox model.
|
||||||
|
return AuthorsTopNavPerRelayFilterSet(
|
||||||
|
proxyRelays.associateWith {
|
||||||
|
AuthorsTopNavPerRelayFilter(
|
||||||
|
authors = authors,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 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.model.topNavFeeds.allUserFollows
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedFlowsType
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
||||||
|
class AllUserFollowsFeedFlow(
|
||||||
|
val allFollows: StateFlow<FollowListState.Kind3Follows?>,
|
||||||
|
val followsRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||||
|
val blockedRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||||
|
val proxyRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||||
|
) : IFeedFlowsType {
|
||||||
|
fun convert(
|
||||||
|
kind3: FollowListState.Kind3Follows?,
|
||||||
|
proxyRelays: Set<NormalizedRelayUrl>,
|
||||||
|
): IFeedTopNavFilter =
|
||||||
|
if (kind3 != null) {
|
||||||
|
if (proxyRelays.isEmpty()) {
|
||||||
|
AllUserFollowsByOutboxTopNavFilter(
|
||||||
|
authors = kind3.authors,
|
||||||
|
defaultRelays = followsRelays,
|
||||||
|
blockedRelays = blockedRelays,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AllUserFollowsByProxyTopNavFilter(
|
||||||
|
authors = kind3.authors,
|
||||||
|
proxyRelays = proxyRelays,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AllUserFollowsByOutboxTopNavFilter(
|
||||||
|
authors = emptySet(),
|
||||||
|
defaultRelays = followsRelays,
|
||||||
|
blockedRelays = blockedRelays,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flow() = combine(allFollows, proxyRelays, ::convert)
|
||||||
|
|
||||||
|
override fun startValue(): IFeedTopNavFilter = convert(allFollows.value, proxyRelays.value)
|
||||||
|
|
||||||
|
override suspend fun startValue(collector: FlowCollector<IFeedTopNavFilter>) {
|
||||||
|
collector.emit(startValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ALL_FOLLOWS
|
import com.vitorpamplona.amethyst.model.ALL_FOLLOWS
|
||||||
|
import com.vitorpamplona.amethyst.model.ALL_USER_FOLLOWS
|
||||||
import com.vitorpamplona.amethyst.model.AROUND_ME
|
import com.vitorpamplona.amethyst.model.AROUND_ME
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
@@ -82,6 +83,15 @@ class FollowListState(
|
|||||||
unpackList = listOf(ContactListEvent.blockListFor(account.signer.pubKey)),
|
unpackList = listOf(ContactListEvent.blockListFor(account.signer.pubKey)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val kind3FollowUsers =
|
||||||
|
PeopleListOutBoxFeedDefinition(
|
||||||
|
code = ALL_USER_FOLLOWS,
|
||||||
|
name = ResourceName(R.string.follow_list_kind3follows_users_only),
|
||||||
|
type = CodeNameType.HARDCODED,
|
||||||
|
kinds = DEFAULT_FEED_KINDS,
|
||||||
|
unpackList = listOf(ContactListEvent.blockListFor(account.signer.pubKey)),
|
||||||
|
)
|
||||||
|
|
||||||
val globalFollow =
|
val globalFollow =
|
||||||
GlobalFeedDefinition(
|
GlobalFeedDefinition(
|
||||||
code = GLOBAL_FOLLOWS,
|
code = GLOBAL_FOLLOWS,
|
||||||
@@ -107,7 +117,7 @@ class FollowListState(
|
|||||||
unpackList = listOf(MuteListEvent.blockListFor(account.userProfile().pubkeyHex)),
|
unpackList = listOf(MuteListEvent.blockListFor(account.userProfile().pubkeyHex)),
|
||||||
)
|
)
|
||||||
|
|
||||||
val defaultLists = persistentListOf(kind3Follow, aroundMe, globalFollow, muteListFollow)
|
val defaultLists = persistentListOf(kind3Follow, kind3FollowUsers, aroundMe, globalFollow, muteListFollow)
|
||||||
|
|
||||||
fun getPeopleLists(): List<FeedDefinition> =
|
fun getPeopleLists(): List<FeedDefinition> =
|
||||||
account
|
account
|
||||||
@@ -218,7 +228,7 @@ class FollowListState(
|
|||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
emit(
|
emit(
|
||||||
listOf(
|
listOf(
|
||||||
listOf(kind3Follow, aroundMe, globalFollow),
|
listOf(kind3Follow, kind3FollowUsers, aroundMe, globalFollow),
|
||||||
myLivePeopleListsFlow,
|
myLivePeopleListsFlow,
|
||||||
myLiveKind3FollowsFlow,
|
myLiveKind3FollowsFlow,
|
||||||
listOf(muteListFollow),
|
listOf(muteListFollow),
|
||||||
@@ -234,7 +244,7 @@ class FollowListState(
|
|||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
emit(
|
emit(
|
||||||
listOf(
|
listOf(
|
||||||
listOf(kind3Follow, aroundMe, globalFollow),
|
listOf(kind3Follow, kind3FollowUsers, aroundMe, globalFollow),
|
||||||
myLivePeopleListsFlow,
|
myLivePeopleListsFlow,
|
||||||
listOf(muteListFollow),
|
listOf(muteListFollow),
|
||||||
).flatten().toImmutableList(),
|
).flatten().toImmutableList(),
|
||||||
|
|||||||
@@ -502,6 +502,7 @@
|
|||||||
|
|
||||||
<string name="follow_list_selection">Follow List</string>
|
<string name="follow_list_selection">Follow List</string>
|
||||||
<string name="follow_list_kind3follows">All Follows</string>
|
<string name="follow_list_kind3follows">All Follows</string>
|
||||||
|
<string name="follow_list_kind3follows_users_only">All User Follows</string>
|
||||||
<string name="follow_list_kind3follows_proxy">Follows via Proxy</string>
|
<string name="follow_list_kind3follows_proxy">Follows via Proxy</string>
|
||||||
<string name="follow_list_aroundme">Around Me</string>
|
<string name="follow_list_aroundme">Around Me</string>
|
||||||
<string name="follow_list_global">Global</string>
|
<string name="follow_list_global">Global</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user