This commit is contained in:
nrobi144
2025-12-30 06:56:58 +02:00
parent 06accf5831
commit 63fc5e86f6
12 changed files with 256 additions and 3 deletions
@@ -168,6 +168,11 @@ import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.nip64Chess.ChessGameEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameAcceptEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameChallengeEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameEndEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessMoveEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
@@ -2930,6 +2935,11 @@ object LocalCache : ILocalCache, ICacheProvider {
is CalendarDateSlotEvent -> consume(event, relay, wasVerified)
is CalendarTimeSlotEvent -> consume(event, relay, wasVerified)
is CalendarRSVPEvent -> consume(event, relay, wasVerified)
is ChessGameEvent -> consume(event, relay, wasVerified)
is LiveChessGameChallengeEvent -> consume(event, relay, wasVerified)
is LiveChessGameAcceptEvent -> consume(event, relay, wasVerified)
is LiveChessMoveEvent -> consume(event, relay, wasVerified)
is LiveChessGameEndEvent -> consume(event, relay, wasVerified)
is ChannelCreateEvent -> consume(event, relay, wasVerified)
is ChannelListEvent -> consume(event, relay, wasVerified)
is ChannelHideMessageEvent -> consume(event, relay, wasVerified)
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.model.topNavFeeds
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.CHESS
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
@@ -32,6 +33,7 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsFeedFlo
import com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows.AllUserFollowsFeedFlow
import com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows.Kind3UserFollowsFeedFlow
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.AroundMeFeedFlow
import com.vitorpamplona.amethyst.model.topNavFeeds.chess.ChessFeedFlow
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalFeedFlow
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.NoteFeedFlow
import com.vitorpamplona.amethyst.model.topNavFeeds.unknown.UnknownFeedFlow
@@ -84,6 +86,9 @@ class FeedTopNavFilterState(
AroundMeFeedFlow(locationFlow, followsRelays, proxyRelays)
}
CHESS -> {
ChessFeedFlow(followsRelays, proxyRelays)
}
else -> {
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
if (note != null) {
@@ -0,0 +1,43 @@
/**
* 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.chess
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.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class ChessFeedFlow(
val outboxRelays: StateFlow<Set<NormalizedRelayUrl>>,
val proxyRelays: StateFlow<Set<NormalizedRelayUrl>>,
) : IFeedFlowsType {
val default = ChessTopNavFilter(outboxRelays, proxyRelays)
override fun flow() = MutableStateFlow(default)
override fun startValue(): ChessTopNavFilter = default
override suspend fun startValue(collector: FlowCollector<IFeedTopNavFilter>) {
collector.emit(startValue())
}
}
@@ -0,0 +1,63 @@
/**
* 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.chess
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
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.nip64Chess.ChessGameEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameChallengeEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameEndEvent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@Immutable
class ChessTopNavFilter(
val outboxRelays: StateFlow<Set<NormalizedRelayUrl>>,
val proxyRelays: StateFlow<Set<NormalizedRelayUrl>>,
) : IFeedTopNavFilter {
override fun matchAuthor(pubkey: HexKey): Boolean = true
override fun match(noteEvent: Event) =
noteEvent is ChessGameEvent ||
noteEvent is LiveChessGameChallengeEvent ||
noteEvent is LiveChessGameEndEvent
override fun toPerRelayFlow(cache: LocalCache): Flow<ChessTopNavPerRelayFilterSet> =
combine(outboxRelays, proxyRelays) { outboxRelays, proxyRelays ->
if (proxyRelays.isNotEmpty()) {
ChessTopNavPerRelayFilterSet(proxyRelays.associateWith { ChessTopNavPerRelayFilter })
} else {
ChessTopNavPerRelayFilterSet(outboxRelays.associateWith { ChessTopNavPerRelayFilter })
}
}
override fun startValue(cache: LocalCache): ChessTopNavPerRelayFilterSet =
if (proxyRelays.value.isNotEmpty()) {
ChessTopNavPerRelayFilterSet(proxyRelays.value.associateWith { ChessTopNavPerRelayFilter })
} else {
ChessTopNavPerRelayFilterSet(outboxRelays.value.associateWith { ChessTopNavPerRelayFilter })
}
}
@@ -0,0 +1,27 @@
/**
* 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.chess
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilter
@Immutable
object ChessTopNavPerRelayFilter : IFeedTopNavPerRelayFilter
@@ -0,0 +1,28 @@
/**
* 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.chess
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
class ChessTopNavPerRelayFilterSet(
val set: Map<NormalizedRelayUrl, ChessTopNavPerRelayFilter>,
) : IFeedTopNavPerRelayFilterSet
@@ -414,6 +414,8 @@ val DEFAULT_FEED_KINDS =
NipTextEvent.KIND,
InteractiveStoryPrologueEvent.KIND,
ChessGameEvent.KIND,
LiveChessGameChallengeEvent.KIND,
LiveChessGameEndEvent.KIND,
)
val DEFAULT_COMMUNITY_FEEDS =
@@ -95,10 +95,10 @@ class ChessViewModel(
// Send to relays
account.client.send(challengeEvent, account.outboxRelays.flow.value)
// Cache locally
// Cache locally - this adds event to LocalCache and should trigger feed updates
account.cache.justConsumeMyOwnEvent(challengeEvent)
Log.d("Chess", "Challenge created: $gameId")
Log.d("Chess", "Challenge created: gameId=$gameId, eventId=${challengeEvent.id}, kind=${challengeEvent.kind}, pubkey=${challengeEvent.pubKey}")
} catch (e: Exception) {
Log.e("Chess", "Failed to create challenge", e)
}
@@ -39,6 +39,9 @@ import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip64Chess.ChessGameEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameChallengeEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameEndEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent
@@ -54,6 +57,8 @@ class HomeNewThreadFeedFilter(
WikiNoteEvent.KIND,
ClassifiedsEvent.KIND,
LongTextNoteEvent.KIND,
LiveChessGameChallengeEvent.KIND,
LiveChessGameEndEvent.KIND,
)
}
@@ -116,7 +121,10 @@ class HomeNewThreadFeedFilter(
noteEvent is CommentEvent ||
noteEvent is AudioTrackEvent ||
noteEvent is VoiceEvent ||
noteEvent is AudioHeaderEvent
noteEvent is AudioHeaderEvent ||
noteEvent is ChessGameEvent ||
noteEvent is LiveChessGameChallengeEvent ||
noteEvent is LiveChessGameEndEvent
) &&
filterParams.match(noteEvent, it.relays) &&
it.isNewThread()
@@ -0,0 +1,58 @@
/**
* 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.ui.screen.loggedIn.home.datasource.nip64Chess
import com.vitorpamplona.amethyst.model.topNavFeeds.chess.ChessTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip64Chess.ChessGameEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameChallengeEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameEndEvent
val ChessPostsKinds =
listOf(
ChessGameEvent.KIND, // Completed games (Kind 64)
LiveChessGameChallengeEvent.KIND, // Challenges (Kind 30064)
LiveChessGameEndEvent.KIND, // Game endings (Kind 30067)
)
fun filterHomePostsByChess(
relays: ChessTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
newThreadSince: Long?,
): List<RelayBasedFilter> {
if (relays.set.isEmpty()) return emptyList()
return relays.set.map {
val since = since?.get(it.key)?.time
val relayUrl = it.key
RelayBasedFilter(
relay = relayUrl,
filter =
Filter(
kinds = ChessPostsKinds,
limit = 50,
since = since ?: newThreadSince,
),
)
}
}
@@ -39,6 +39,9 @@ import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip64Chess.ChessGameEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameChallengeEvent
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameEndEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent
@@ -57,6 +60,9 @@ val HomePostsNewThreadKinds =
NipTextEvent.KIND,
PollNoteEvent.KIND,
InteractiveStoryPrologueEvent.KIND,
ChessGameEvent.KIND,
LiveChessGameChallengeEvent.KIND,
LiveChessGameEndEvent.KIND,
)
val HomePostsConversationKinds =
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip65Follo
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.chess.ChessTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
@@ -35,6 +36,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeQuerySt
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip01Core.filterHomePostsByGeohashes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip01Core.filterHomePostsByGlobal
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip01Core.filterHomePostsByHashtags
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip64Chess.filterHomePostsByChess
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip72Communities.filterHomePostsByAllCommunities
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip72Communities.filterHomePostsByCommunity
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
@@ -62,6 +64,7 @@ class HomeOutboxEventsEoseManager(
is AllCommunitiesTopNavPerRelayFilterSet -> filterHomePostsByAllCommunities(feedSettings, since, newThreadSince)
is AllFollowsTopNavPerRelayFilterSet -> filterHomePostsByAllFollows(feedSettings, since, newThreadSince, repliesSince)
is AuthorsTopNavPerRelayFilterSet -> filterHomePostsByAuthors(feedSettings, since, newThreadSince, repliesSince)
is ChessTopNavPerRelayFilterSet -> filterHomePostsByChess(feedSettings, since, newThreadSince)
is GlobalTopNavPerRelayFilterSet -> filterHomePostsByGlobal(feedSettings, since, newThreadSince, repliesSince)
is HashtagTopNavPerRelayFilterSet -> filterHomePostsByHashtags(feedSettings, since, newThreadSince)
is LocationTopNavPerRelayFilterSet -> filterHomePostsByGeohashes(feedSettings, since, newThreadSince)