feat: NIP-72 compliance fixes + standalone Communities screen

Quartz (NIP-72 compliance)
- Fix CommunityDefinition image() builder to use the NIP-72 ImageTag
  (was wrongly pointing at the NIP-23 ImageTag, which prevented the
  builder from attaching `<W>x<H>` dimensions).
- Fix CommunityPostApproval notifyAuthor() to emit a `p` tag for the
  post author as required by NIP-72 (was duplicating the `e` tag).
- Add RelayTag.MARKER_AUTHOR/REQUESTS/APPROVALS constants.

Amethyst - Communities screen
- New top-level Communities screen with FeedFilterSpinner TopNav that
  supports the Mine option (filters to communities authored by the
  logged-in user).
- New Route.Communities + drawer entry.
- New CommunitiesFeedFilter (subclass of DiscoverCommunityFeedFilter
  using defaultCommunitiesFollowList + Mine handling).
- New CommunitiesListFilterAssembler/SubAssembler with dedicated
  filterCommunitiesMine relay query.
- Account.liveCommunitiesFollowLists + settings.defaultCommunitiesFollowList.
- TopNavFilterState.communityRoutes (includes Mine).

Amethyst - Create Community
- Route.NewCommunity + FAB on the Communities screen.
- NewCommunityModel + Material3 form: name, description, image URL,
  rules, moderator pubkeys, relay requests/approvals URLs.
- Signs a kind 34550 CommunityDefinitionEvent and auto-follows it so
  it appears under "Mine".
- Account.sendCommunityDefinition helper.

Misc
- Extracted parseTopFilterOrDefault helper in LocalPreferences to keep
  the existing inner load method under the JVM method-size limit.
This commit is contained in:
Claude
2026-04-20 17:56:18 +00:00
parent f76e4f4c49
commit 30a78c5922
24 changed files with 1154 additions and 15 deletions
@@ -26,6 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.tags.aTag.toATag
import com.vitorpamplona.quartz.nip01Core.tags.events.toETagArray
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
fun TagArrayBuilder<CommunityPostApprovalEvent>.community(event: EventHintBundle<CommunityDefinitionEvent>) = add(event.toATag().toATagArray())
@@ -37,6 +38,4 @@ fun TagArrayBuilder<CommunityPostApprovalEvent>.approved(event: EventHintBundle<
}
}
fun TagArrayBuilder<CommunityPostApprovalEvent>.notifyAuthor(event: EventHintBundle<Event>) {
add(event.toETagArray())
}
fun TagArrayBuilder<CommunityPostApprovalEvent>.notifyAuthor(event: EventHintBundle<Event>) = add(PTag.assemble(event.event.pubKey, event.authorHomeRelay))
@@ -21,18 +21,22 @@
package com.vitorpamplona.quartz.nip72ModCommunities.definition
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip23LongContent.tags.ImageTag
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.DescriptionTag
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.ImageTag
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.ModeratorTag
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.NameTag
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.RelayTag
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.RulesTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
fun TagArrayBuilder<CommunityDefinitionEvent>.name(name: String) = addUnique(NameTag.assemble(name))
fun TagArrayBuilder<CommunityDefinitionEvent>.description(description: String) = addUnique(DescriptionTag.assemble(description))
fun TagArrayBuilder<CommunityDefinitionEvent>.image(webUrl: String) = addUnique(ImageTag.assemble(webUrl))
fun TagArrayBuilder<CommunityDefinitionEvent>.image(
webUrl: String,
dimensions: DimensionTag? = null,
) = addUnique(ImageTag.assemble(webUrl, dimensions))
fun TagArrayBuilder<CommunityDefinitionEvent>.rules(rules: String) = addUnique(RulesTag.assemble(rules))
@@ -35,6 +35,10 @@ class RelayTag(
companion object {
const val TAG_NAME = "relay"
const val MARKER_AUTHOR = "author"
const val MARKER_REQUESTS = "requests"
const val MARKER_APPROVALS = "approvals"
fun parse(tag: Array<String>): RelayTag? {
ensure(tag.has(1)) { return null }
ensure(tag[0] == TAG_NAME) { return null }