diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml
index 2f3e458a..59e18a72 100644
--- a/app/detekt-baseline.xml
+++ b/app/detekt-baseline.xml
@@ -16,6 +16,7 @@
CyclomaticComplexMethod:MessagesRemoteMediator.kt$MessagesRemoteMediator$override suspend fun load(loadType: LoadType, state: PagingState<Int, DirectMessage>): MediatorResult
CyclomaticComplexMethod:NostrUserText.kt$@Composable fun NostrUserText( displayName: String, internetIdentifier: String?, modifier: Modifier = Modifier, displayNameColor: Color = AppTheme.colorScheme.onSurface, fontSize: TextUnit = TextUnit.Unspecified, style: TextStyle = LocalTextStyle.current, overflow: TextOverflow = TextOverflow.Ellipsis, maxLines: Int = 1, internetIdentifierBadgeSize: Dp = 14.dp, internetIdentifierBadgeAlign: PlaceholderVerticalAlign = PlaceholderVerticalAlign.Center, customBadgeStyle: LegendaryStyle? = null, annotatedStringPrefixBuilder: (AnnotatedString.Builder.() -> Unit)? = null, annotatedStringSuffixBuilder: (AnnotatedString.Builder.() -> Unit)? = null, )
CyclomaticComplexMethod:NoteActionsRow.kt$@Composable fun FeedNoteActionsRow( modifier: Modifier, eventStats: EventStatsUi, isBookmarked: Boolean, highlightedNote: Boolean = false, showBookmark: Boolean = false, showCounts: Boolean = true, onPostAction: ((FeedPostAction) -> Unit)? = null, onPostLongPressAction: ((FeedPostAction) -> Unit)? = null, )
+ CyclomaticComplexMethod:NoteContent.kt$@OptIn(ExperimentalFoundationApi::class) @Composable fun NoteContent( modifier: Modifier = Modifier, data: NoteContentUi, expanded: Boolean, noteCallbacks: NoteCallbacks, maxLines: Int = Int.MAX_VALUE, overflow: TextOverflow = TextOverflow.Clip, enableTweetsMode: Boolean = false, textSelectable: Boolean = false, referencedEventsHaveBorder: Boolean = false, highlightColor: Color = AppTheme.colorScheme.secondary, contentColor: Color = AppTheme.colorScheme.onSurface, referencedEventsContainerColor: Color = AppTheme.extraColorScheme.surfaceVariantAlt1, onClick: ((offset: Offset) -> Unit)? = null, onUrlClick: ((url: String) -> Unit)? = null, )
CyclomaticComplexMethod:NoteFeedLazyColumn.kt$@ExperimentalMaterial3Api @ExperimentalFoundationApi @Composable fun NoteFeedLazyColumn( modifier: Modifier = Modifier, pagingItems: LazyPagingItems<FeedPostUi>, listState: LazyListState, showPaywall: Boolean, noteCallbacks: NoteCallbacks, onGoToWallet: () -> Unit, showTopZaps: Boolean = false, shouldShowLoadingState: Boolean = true, shouldShowNoContentState: Boolean = true, showReplyTo: Boolean = true, noContentVerticalArrangement: Arrangement.Vertical = Arrangement.Center, noContentPaddingValues: PaddingValues = PaddingValues(all = 0.dp), noContentText: String = stringResource(id = R.string.feed_no_content), contentPadding: PaddingValues = PaddingValues(all = 0.dp), header: @Composable (LazyItemScope.() -> Unit)? = null, stickyHeader: @Composable (LazyItemScope.() -> Unit)? = null, onUiError: ((UiError) -> Unit)? = null, )
CyclomaticComplexMethod:NotePublishHandler.kt$NotePublishHandler$@Throws(NostrPublishException::class) suspend fun publishShortTextNote( userId: String, content: String, attachments: List<NoteAttachment> = emptyList(), rootArticleEventId: String? = null, rootArticleId: String? = null, rootArticleAuthorId: String? = null, rootPostId: String? = null, replyToPostId: String? = null, replyToAuthorId: String? = null, ): Boolean
CyclomaticComplexMethod:NotificationEvents.kt$private fun ContentPrimalNotification.parseActionPostId(type: NotificationType): String?
@@ -95,8 +96,9 @@
LongParameterList:ArticleDetailsViewModel.kt$ArticleDetailsViewModel$( savedStateHandle: SavedStateHandle, private val activeAccountStore: ActiveAccountStore, private val articleRepository: ArticleRepository, private val feedRepository: FeedRepository, private val profileRepository: ProfileRepository, private val eventRepository: EventRepository, private val zapHandler: ZapHandler, )
LongParameterList:ContentAppearance.kt$ContentAppearance$( val noteBodyFontSize: TextUnit, val noteBodyLineHeight: TextUnit, val noteUsernameSize: TextUnit, val noteAvatarSize: Dp, val articleTextFontSize: TextUnit, val articleTextLineHeight: TextUnit, val tweetFontSize: TextUnit, val tweetLineHeight: TextUnit, )
LongParameterList:MessagesProcessor.kt$MessagesProcessor$( userId: String, messages: List<NostrEvent>, profileMetadata: List<NostrEvent>, mediaResources: List<PrimalEvent>, primalUserNames: PrimalEvent?, primalLegendProfiles: PrimalEvent?, )
- LongParameterList:NostrResources.kt$( eventId: String, postIdToPostDataMap: Map<String, PostData>, articleIdToArticle: Map<String, ArticleData>, profileIdToProfileDataMap: Map<String, ProfileData>, cdnResources: Map<String, CdnResource>, linkPreviews: Map<String, LinkPreviewData>, videoThumbnails: Map<String, String>, )
- LongParameterList:NostrResources.kt$( postIdToPostDataMap: Map<String, PostData>, articleIdToArticle: Map<String, ArticleData>, profileIdToProfileDataMap: Map<String, ProfileData>, cdnResources: Map<String, CdnResource>, linkPreviews: Map<String, LinkPreviewData>, videoThumbnails: Map<String, String>, )
+ LongParameterList:NostrResources.kt$( eventId: String, eventIdToNostrEvent: Map<String, NostrEvent>, postIdToPostDataMap: Map<String, PostData>, articleIdToArticle: Map<String, ArticleData>, profileIdToProfileDataMap: Map<String, ProfileData>, cdnResources: Map<String, CdnResource>, linkPreviews: Map<String, LinkPreviewData>, videoThumbnails: Map<String, String>, )
+ LongParameterList:NostrResources.kt$( eventIdToNostrEvent: Map<String, NostrEvent>, postIdToPostDataMap: Map<String, PostData>, articleIdToArticle: Map<String, ArticleData>, profileIdToProfileDataMap: Map<String, ProfileData>, cdnResources: Map<String, CdnResource>, linkPreviews: Map<String, LinkPreviewData>, videoThumbnails: Map<String, String>, )
+ LongParameterList:NostrResources.kt$( refNote: PostData?, refPostAuthor: ProfileData?, cdnResources: Map<String, CdnResource>, linkPreviews: Map<String, LinkPreviewData>, videoThumbnails: Map<String, String>, eventIdToNostrEvent: Map<String, NostrEvent>, postIdToPostDataMap: Map<String, PostData>, articleIdToArticle: Map<String, ArticleData>, profileIdToProfileDataMap: Map<String, ProfileData>, )
LongParameterList:NoteEditorViewModel.kt$NoteEditorViewModel$( @Assisted private val args: NoteEditorArgs, private val dispatcherProvider: CoroutineDispatcherProvider, private val fileAnalyser: FileAnalyser, private val activeAccountStore: ActiveAccountStore, private val feedRepository: FeedRepository, private val notePublishHandler: NotePublishHandler, private val attachmentRepository: AttachmentsRepository, private val exploreRepository: ExploreRepository, private val profileRepository: ProfileRepository, private val articleRepository: ArticleRepository, )
LongParameterList:ProfileDetailsViewModel.kt$ProfileDetailsViewModel$( savedStateHandle: SavedStateHandle, private val dispatcherProvider: CoroutineDispatcherProvider, private val activeAccountStore: ActiveAccountStore, private val feedsRepository: FeedsRepository, private val profileRepository: ProfileRepository, private val mutedUserRepository: MutedUserRepository, private val zapHandler: ZapHandler, )
LongParameterList:SubscriptionsManager.kt$SubscriptionsManager$( dispatcherProvider: CoroutineDispatcherProvider, private val activeAccountStore: ActiveAccountStore, private val userRepository: UserRepository, private val nostrNotary: NostrNotary, private val appConfigProvider: AppConfigProvider, @PrimalCacheApiClient private val cacheApiClient: PrimalApiClient, @PrimalWalletApiClient private val walletApiClient: PrimalApiClient, )
diff --git a/app/schemas/net.primal.android.db.PrimalDatabase/46.json b/app/schemas/net.primal.android.db.PrimalDatabase/46.json
new file mode 100644
index 00000000..85b8ae68
--- /dev/null
+++ b/app/schemas/net.primal.android.db.PrimalDatabase/46.json
@@ -0,0 +1,1783 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 46,
+ "identityHash": "0e3be72662ad114f97ee4b849358fcf2",
+ "entities": [
+ {
+ "tableName": "PostData",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`postId` TEXT NOT NULL, `authorId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `uris` TEXT NOT NULL, `hashtags` TEXT NOT NULL, `sig` TEXT NOT NULL, `raw` TEXT NOT NULL, `authorMetadataId` TEXT, `replyToPostId` TEXT, `replyToAuthorId` TEXT, PRIMARY KEY(`postId`))",
+ "fields": [
+ {
+ "fieldPath": "postId",
+ "columnName": "postId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "authorId",
+ "columnName": "authorId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tags",
+ "columnName": "tags",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "uris",
+ "columnName": "uris",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hashtags",
+ "columnName": "hashtags",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sig",
+ "columnName": "sig",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "raw",
+ "columnName": "raw",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "authorMetadataId",
+ "columnName": "authorMetadataId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "replyToPostId",
+ "columnName": "replyToPostId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "replyToAuthorId",
+ "columnName": "replyToAuthorId",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "postId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ProfileData",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`ownerId` TEXT NOT NULL, `eventId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `raw` TEXT NOT NULL, `handle` TEXT, `displayName` TEXT, `internetIdentifier` TEXT, `lightningAddress` TEXT, `lnUrlDecoded` TEXT, `avatarCdnImage` TEXT, `bannerCdnImage` TEXT, `website` TEXT, `about` TEXT, `aboutUris` TEXT NOT NULL, `aboutHashtags` TEXT NOT NULL, `primalName` TEXT, `primalLegendProfile` TEXT, PRIMARY KEY(`ownerId`))",
+ "fields": [
+ {
+ "fieldPath": "ownerId",
+ "columnName": "ownerId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "eventId",
+ "columnName": "eventId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "raw",
+ "columnName": "raw",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "handle",
+ "columnName": "handle",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "displayName",
+ "columnName": "displayName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "internetIdentifier",
+ "columnName": "internetIdentifier",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lightningAddress",
+ "columnName": "lightningAddress",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lnUrlDecoded",
+ "columnName": "lnUrlDecoded",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "avatarCdnImage",
+ "columnName": "avatarCdnImage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "bannerCdnImage",
+ "columnName": "bannerCdnImage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "website",
+ "columnName": "website",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "about",
+ "columnName": "about",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "aboutUris",
+ "columnName": "aboutUris",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "aboutHashtags",
+ "columnName": "aboutHashtags",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "primalName",
+ "columnName": "primalName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "primalLegendProfile",
+ "columnName": "primalLegendProfile",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "ownerId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "RepostData",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repostId` TEXT NOT NULL, `authorId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `tags` TEXT NOT NULL, `postId` TEXT NOT NULL, `postAuthorId` TEXT NOT NULL, `sig` TEXT NOT NULL, PRIMARY KEY(`repostId`))",
+ "fields": [
+ {
+ "fieldPath": "repostId",
+ "columnName": "repostId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "authorId",
+ "columnName": "authorId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tags",
+ "columnName": "tags",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "postId",
+ "columnName": "postId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "postAuthorId",
+ "columnName": "postAuthorId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sig",
+ "columnName": "sig",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "repostId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_RepostData_postId",
+ "unique": false,
+ "columnNames": [
+ "postId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_RepostData_postId` ON `${TABLE_NAME}` (`postId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "EventStats",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`eventId` TEXT NOT NULL, `likes` INTEGER NOT NULL, `replies` INTEGER NOT NULL, `mentions` INTEGER NOT NULL, `reposts` INTEGER NOT NULL, `zaps` INTEGER NOT NULL, `satsZapped` INTEGER NOT NULL, `score` INTEGER NOT NULL, `score24h` INTEGER NOT NULL, PRIMARY KEY(`eventId`))",
+ "fields": [
+ {
+ "fieldPath": "eventId",
+ "columnName": "eventId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "likes",
+ "columnName": "likes",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "replies",
+ "columnName": "replies",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mentions",
+ "columnName": "mentions",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reposts",
+ "columnName": "reposts",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "zaps",
+ "columnName": "zaps",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "satsZapped",
+ "columnName": "satsZapped",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "score",
+ "columnName": "score",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "score24h",
+ "columnName": "score24h",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "eventId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "EventZap",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`eventId` TEXT NOT NULL, `zapSenderId` TEXT NOT NULL, `zapReceiverId` TEXT NOT NULL, `zapRequestAt` INTEGER NOT NULL, `zapReceiptAt` INTEGER NOT NULL, `amountInBtc` REAL NOT NULL, `message` TEXT, `zapSenderDisplayName` TEXT, `zapSenderHandle` TEXT, `zapSenderInternetIdentifier` TEXT, `zapSenderAvatarCdnImage` TEXT, PRIMARY KEY(`zapSenderId`, `eventId`, `zapRequestAt`))",
+ "fields": [
+ {
+ "fieldPath": "eventId",
+ "columnName": "eventId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "zapSenderId",
+ "columnName": "zapSenderId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "zapReceiverId",
+ "columnName": "zapReceiverId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "zapRequestAt",
+ "columnName": "zapRequestAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "zapReceiptAt",
+ "columnName": "zapReceiptAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "amountInBtc",
+ "columnName": "amountInBtc",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "message",
+ "columnName": "message",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "zapSenderDisplayName",
+ "columnName": "zapSenderDisplayName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "zapSenderHandle",
+ "columnName": "zapSenderHandle",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "zapSenderInternetIdentifier",
+ "columnName": "zapSenderInternetIdentifier",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "zapSenderAvatarCdnImage",
+ "columnName": "zapSenderAvatarCdnImage",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "zapSenderId",
+ "eventId",
+ "zapRequestAt"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "EventUserStats",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`eventId` TEXT NOT NULL, `userId` TEXT NOT NULL, `replied` INTEGER NOT NULL, `liked` INTEGER NOT NULL, `reposted` INTEGER NOT NULL, `zapped` INTEGER NOT NULL, PRIMARY KEY(`eventId`, `userId`))",
+ "fields": [
+ {
+ "fieldPath": "eventId",
+ "columnName": "eventId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userId",
+ "columnName": "userId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "replied",
+ "columnName": "replied",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "liked",
+ "columnName": "liked",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reposted",
+ "columnName": "reposted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "zapped",
+ "columnName": "zapped",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "eventId",
+ "userId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_EventUserStats_eventId",
+ "unique": false,
+ "columnNames": [
+ "eventId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_EventUserStats_eventId` ON `${TABLE_NAME}` (`eventId`)"
+ },
+ {
+ "name": "index_EventUserStats_userId",
+ "unique": false,
+ "columnNames": [
+ "userId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_EventUserStats_userId` ON `${TABLE_NAME}` (`userId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "NoteNostrUri",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`noteId` TEXT NOT NULL, `uri` TEXT NOT NULL, `type` TEXT NOT NULL, `refEvent_alt` TEXT, `refHighlight_text` TEXT, `refHighlight_aTag` TEXT, `refHighlight_eventId` TEXT, `refHighlight_authorId` TEXT, `refNote_postId` TEXT, `refNote_createdAt` INTEGER, `refNote_content` TEXT, `refNote_authorId` TEXT, `refNote_authorName` TEXT, `refNote_authorAvatarCdnImage` TEXT, `refNote_authorInternetIdentifier` TEXT, `refNote_authorLightningAddress` TEXT, `refNote_attachments` TEXT, `refNote_nostrUris` TEXT, `refArticle_naddr` TEXT, `refArticle_aTag` TEXT, `refArticle_eventId` TEXT, `refArticle_articleId` TEXT, `refArticle_articleTitle` TEXT, `refArticle_authorId` TEXT, `refArticle_authorName` TEXT, `refArticle_authorAvatarCdnImage` TEXT, `refArticle_createdAt` INTEGER, `refArticle_raw` TEXT, `refArticle_articleImageCdnImage` TEXT, `refArticle_articleReadingTimeInMinutes` INTEGER, `refUser_userId` TEXT, `refUser_handle` TEXT, PRIMARY KEY(`noteId`, `uri`))",
+ "fields": [
+ {
+ "fieldPath": "noteId",
+ "columnName": "noteId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "uri",
+ "columnName": "uri",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "referencedEventAlt",
+ "columnName": "refEvent_alt",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedHighlight.text",
+ "columnName": "refHighlight_text",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedHighlight.aTag",
+ "columnName": "refHighlight_aTag",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedHighlight.eventId",
+ "columnName": "refHighlight_eventId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedHighlight.authorId",
+ "columnName": "refHighlight_authorId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedNote.postId",
+ "columnName": "refNote_postId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedNote.createdAt",
+ "columnName": "refNote_createdAt",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedNote.content",
+ "columnName": "refNote_content",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedNote.authorId",
+ "columnName": "refNote_authorId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedNote.authorName",
+ "columnName": "refNote_authorName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedNote.authorAvatarCdnImage",
+ "columnName": "refNote_authorAvatarCdnImage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedNote.authorInternetIdentifier",
+ "columnName": "refNote_authorInternetIdentifier",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedNote.authorLightningAddress",
+ "columnName": "refNote_authorLightningAddress",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedNote.attachments",
+ "columnName": "refNote_attachments",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedNote.nostrUris",
+ "columnName": "refNote_nostrUris",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.naddr",
+ "columnName": "refArticle_naddr",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.aTag",
+ "columnName": "refArticle_aTag",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.eventId",
+ "columnName": "refArticle_eventId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.articleId",
+ "columnName": "refArticle_articleId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.articleTitle",
+ "columnName": "refArticle_articleTitle",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.authorId",
+ "columnName": "refArticle_authorId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.authorName",
+ "columnName": "refArticle_authorName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.authorAvatarCdnImage",
+ "columnName": "refArticle_authorAvatarCdnImage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.createdAt",
+ "columnName": "refArticle_createdAt",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.raw",
+ "columnName": "refArticle_raw",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.articleImageCdnImage",
+ "columnName": "refArticle_articleImageCdnImage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedArticle.articleReadingTimeInMinutes",
+ "columnName": "refArticle_articleReadingTimeInMinutes",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedUser.userId",
+ "columnName": "refUser_userId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "referencedUser.handle",
+ "columnName": "refUser_handle",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "noteId",
+ "uri"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "NoteAttachment",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`eventId` TEXT NOT NULL, `url` TEXT NOT NULL, `type` TEXT NOT NULL, `mimeType` TEXT, `variants` TEXT, `title` TEXT, `description` TEXT, `thumbnail` TEXT, `authorAvatarUrl` TEXT, PRIMARY KEY(`eventId`, `url`))",
+ "fields": [
+ {
+ "fieldPath": "eventId",
+ "columnName": "eventId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mimeType",
+ "columnName": "mimeType",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "variants",
+ "columnName": "variants",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "thumbnail",
+ "columnName": "thumbnail",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "authorAvatarUrl",
+ "columnName": "authorAvatarUrl",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "eventId",
+ "url"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Feed",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`spec` TEXT NOT NULL, `specKind` TEXT NOT NULL, `feedKind` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `enabled` INTEGER NOT NULL, PRIMARY KEY(`spec`))",
+ "fields": [
+ {
+ "fieldPath": "spec",
+ "columnName": "spec",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "specKind",
+ "columnName": "specKind",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "feedKind",
+ "columnName": "feedKind",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "enabled",
+ "columnName": "enabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "spec"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "FeedPostDataCrossRef",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`feedSpec` TEXT NOT NULL, `eventId` TEXT NOT NULL, `orderIndex` INTEGER NOT NULL, PRIMARY KEY(`feedSpec`, `eventId`))",
+ "fields": [
+ {
+ "fieldPath": "feedSpec",
+ "columnName": "feedSpec",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "eventId",
+ "columnName": "eventId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "orderIndex",
+ "columnName": "orderIndex",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "feedSpec",
+ "eventId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_FeedPostDataCrossRef_feedSpec",
+ "unique": false,
+ "columnNames": [
+ "feedSpec"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_FeedPostDataCrossRef_feedSpec` ON `${TABLE_NAME}` (`feedSpec`)"
+ },
+ {
+ "name": "index_FeedPostDataCrossRef_eventId",
+ "unique": false,
+ "columnNames": [
+ "eventId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_FeedPostDataCrossRef_eventId` ON `${TABLE_NAME}` (`eventId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "FeedPostRemoteKey",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`eventId` TEXT NOT NULL, `directive` TEXT NOT NULL, `sinceId` INTEGER NOT NULL, `untilId` INTEGER NOT NULL, `cachedAt` INTEGER NOT NULL, PRIMARY KEY(`eventId`, `directive`))",
+ "fields": [
+ {
+ "fieldPath": "eventId",
+ "columnName": "eventId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directive",
+ "columnName": "directive",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sinceId",
+ "columnName": "sinceId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "untilId",
+ "columnName": "untilId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "cachedAt",
+ "columnName": "cachedAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "eventId",
+ "directive"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "NoteConversationCrossRef",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`noteId` TEXT NOT NULL, `replyNoteId` TEXT NOT NULL, PRIMARY KEY(`noteId`, `replyNoteId`))",
+ "fields": [
+ {
+ "fieldPath": "noteId",
+ "columnName": "noteId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "replyNoteId",
+ "columnName": "replyNoteId",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "noteId",
+ "replyNoteId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_NoteConversationCrossRef_noteId",
+ "unique": false,
+ "columnNames": [
+ "noteId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_NoteConversationCrossRef_noteId` ON `${TABLE_NAME}` (`noteId`)"
+ },
+ {
+ "name": "index_NoteConversationCrossRef_replyNoteId",
+ "unique": false,
+ "columnNames": [
+ "replyNoteId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_NoteConversationCrossRef_replyNoteId` ON `${TABLE_NAME}` (`replyNoteId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ProfileStats",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` TEXT NOT NULL, `following` INTEGER, `followers` INTEGER, `notesCount` INTEGER, `readsCount` INTEGER, `mediaCount` INTEGER, `repliesCount` INTEGER, `relaysCount` INTEGER, `totalReceivedZaps` INTEGER, `contentZapCount` INTEGER, `totalReceivedSats` INTEGER, `joinedAt` INTEGER, PRIMARY KEY(`profileId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "following",
+ "columnName": "following",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "followers",
+ "columnName": "followers",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "notesCount",
+ "columnName": "notesCount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "readsCount",
+ "columnName": "readsCount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mediaCount",
+ "columnName": "mediaCount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "repliesCount",
+ "columnName": "repliesCount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "relaysCount",
+ "columnName": "relaysCount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "totalReceivedZaps",
+ "columnName": "totalReceivedZaps",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "contentZapCount",
+ "columnName": "contentZapCount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "totalReceivedSats",
+ "columnName": "totalReceivedSats",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "joinedAt",
+ "columnName": "joinedAt",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "profileId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TrendingTopic",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`topic` TEXT NOT NULL, `score` REAL NOT NULL, PRIMARY KEY(`topic`))",
+ "fields": [
+ {
+ "fieldPath": "topic",
+ "columnName": "topic",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "score",
+ "columnName": "score",
+ "affinity": "REAL",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "topic"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "NotificationData",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notificationId` TEXT NOT NULL, `ownerId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `type` TEXT NOT NULL, `seenGloballyAt` INTEGER, `actionUserId` TEXT, `actionPostId` TEXT, `satsZapped` INTEGER, PRIMARY KEY(`notificationId`))",
+ "fields": [
+ {
+ "fieldPath": "notificationId",
+ "columnName": "notificationId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ownerId",
+ "columnName": "ownerId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "seenGloballyAt",
+ "columnName": "seenGloballyAt",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "actionUserId",
+ "columnName": "actionUserId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "actionPostId",
+ "columnName": "actionPostId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "satsZapped",
+ "columnName": "satsZapped",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "notificationId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MutedUserData",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` TEXT NOT NULL, `userMetadataEventId` TEXT, PRIMARY KEY(`userId`))",
+ "fields": [
+ {
+ "fieldPath": "userId",
+ "columnName": "userId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userMetadataEventId",
+ "columnName": "userMetadataEventId",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "userId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "DirectMessageData",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `senderId` TEXT NOT NULL, `receiverId` TEXT NOT NULL, `participantId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `content` TEXT NOT NULL, `uris` TEXT NOT NULL, `hashtags` TEXT NOT NULL, PRIMARY KEY(`messageId`))",
+ "fields": [
+ {
+ "fieldPath": "messageId",
+ "columnName": "messageId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "senderId",
+ "columnName": "senderId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "receiverId",
+ "columnName": "receiverId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "participantId",
+ "columnName": "participantId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "uris",
+ "columnName": "uris",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hashtags",
+ "columnName": "hashtags",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "messageId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "MessageConversationData",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`participantId` TEXT NOT NULL, `lastMessageId` TEXT NOT NULL, `lastMessageAt` INTEGER NOT NULL, `unreadMessagesCount` INTEGER NOT NULL, `relation` TEXT NOT NULL, `participantMetadataId` TEXT, PRIMARY KEY(`participantId`))",
+ "fields": [
+ {
+ "fieldPath": "participantId",
+ "columnName": "participantId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastMessageId",
+ "columnName": "lastMessageId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastMessageAt",
+ "columnName": "lastMessageAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unreadMessagesCount",
+ "columnName": "unreadMessagesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "relation",
+ "columnName": "relation",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "participantMetadataId",
+ "columnName": "participantMetadataId",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "participantId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "WalletTransactionData",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `walletLightningAddress` TEXT NOT NULL, `type` TEXT NOT NULL, `state` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `completedAt` INTEGER, `amountInBtc` REAL NOT NULL, `amountInUsd` REAL, `isZap` INTEGER NOT NULL, `isStorePurchase` INTEGER NOT NULL, `userId` TEXT NOT NULL, `userSubWallet` TEXT NOT NULL, `userLightningAddress` TEXT, `otherUserId` TEXT, `otherLightningAddress` TEXT, `note` TEXT, `invoice` TEXT, `totalFeeInBtc` TEXT, `exchangeRate` TEXT, `onChainAddress` TEXT, `onChainTxId` TEXT, `zapNoteId` TEXT, `zapNoteAuthorId` TEXT, `zappedByUserId` TEXT, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "walletLightningAddress",
+ "columnName": "walletLightningAddress",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "updatedAt",
+ "columnName": "updatedAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "completedAt",
+ "columnName": "completedAt",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "amountInBtc",
+ "columnName": "amountInBtc",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "amountInUsd",
+ "columnName": "amountInUsd",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isZap",
+ "columnName": "isZap",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isStorePurchase",
+ "columnName": "isStorePurchase",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userId",
+ "columnName": "userId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userSubWallet",
+ "columnName": "userSubWallet",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userLightningAddress",
+ "columnName": "userLightningAddress",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "otherUserId",
+ "columnName": "otherUserId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "otherLightningAddress",
+ "columnName": "otherLightningAddress",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "note",
+ "columnName": "note",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "invoice",
+ "columnName": "invoice",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "totalFeeInBtc",
+ "columnName": "totalFeeInBtc",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "exchangeRate",
+ "columnName": "exchangeRate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "onChainAddress",
+ "columnName": "onChainAddress",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "onChainTxId",
+ "columnName": "onChainTxId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "zapNoteId",
+ "columnName": "zapNoteId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "zapNoteAuthorId",
+ "columnName": "zapNoteAuthorId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "zappedByUserId",
+ "columnName": "zappedByUserId",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Relay",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` TEXT NOT NULL, `kind` TEXT NOT NULL, `url` TEXT NOT NULL, `read` INTEGER NOT NULL, `write` INTEGER NOT NULL, PRIMARY KEY(`userId`, `kind`, `url`))",
+ "fields": [
+ {
+ "fieldPath": "userId",
+ "columnName": "userId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "kind",
+ "columnName": "kind",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "read",
+ "columnName": "read",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "write",
+ "columnName": "write",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "userId",
+ "kind",
+ "url"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "EventRelayHints",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`eventId` TEXT NOT NULL, `relays` TEXT NOT NULL, PRIMARY KEY(`eventId`))",
+ "fields": [
+ {
+ "fieldPath": "eventId",
+ "columnName": "eventId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "relays",
+ "columnName": "relays",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "eventId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "PublicBookmark",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tagValue` TEXT NOT NULL, `tagType` TEXT NOT NULL, `bookmarkType` TEXT NOT NULL, `ownerId` TEXT NOT NULL, PRIMARY KEY(`tagValue`))",
+ "fields": [
+ {
+ "fieldPath": "tagValue",
+ "columnName": "tagValue",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tagType",
+ "columnName": "tagType",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "bookmarkType",
+ "columnName": "bookmarkType",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ownerId",
+ "columnName": "ownerId",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "tagValue"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ProfileInteraction",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` TEXT NOT NULL, `lastInteractionAt` INTEGER NOT NULL, PRIMARY KEY(`profileId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastInteractionAt",
+ "columnName": "lastInteractionAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "profileId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ArticleData",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`aTag` TEXT NOT NULL, `eventId` TEXT NOT NULL, `articleId` TEXT NOT NULL, `authorId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `content` TEXT NOT NULL, `title` TEXT NOT NULL, `publishedAt` INTEGER NOT NULL, `raw` TEXT NOT NULL, `imageCdnImage` TEXT, `summary` TEXT, `authorMetadataId` TEXT, `wordsCount` INTEGER, `uris` TEXT NOT NULL, `hashtags` TEXT NOT NULL, PRIMARY KEY(`aTag`))",
+ "fields": [
+ {
+ "fieldPath": "aTag",
+ "columnName": "aTag",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "eventId",
+ "columnName": "eventId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "articleId",
+ "columnName": "articleId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "authorId",
+ "columnName": "authorId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "publishedAt",
+ "columnName": "publishedAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "raw",
+ "columnName": "raw",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "imageCdnImage",
+ "columnName": "imageCdnImage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "summary",
+ "columnName": "summary",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "authorMetadataId",
+ "columnName": "authorMetadataId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "wordsCount",
+ "columnName": "wordsCount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "uris",
+ "columnName": "uris",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hashtags",
+ "columnName": "hashtags",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "aTag"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ArticleCommentCrossRef",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`articleId` TEXT NOT NULL, `articleAuthorId` TEXT NOT NULL, `commentNoteId` TEXT NOT NULL, PRIMARY KEY(`articleId`, `articleAuthorId`, `commentNoteId`))",
+ "fields": [
+ {
+ "fieldPath": "articleId",
+ "columnName": "articleId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "articleAuthorId",
+ "columnName": "articleAuthorId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "commentNoteId",
+ "columnName": "commentNoteId",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "articleId",
+ "articleAuthorId",
+ "commentNoteId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ArticleFeedCrossRef",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`spec` TEXT NOT NULL, `articleId` TEXT NOT NULL, `articleAuthorId` TEXT NOT NULL, PRIMARY KEY(`spec`, `articleId`, `articleAuthorId`))",
+ "fields": [
+ {
+ "fieldPath": "spec",
+ "columnName": "spec",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "articleId",
+ "columnName": "articleId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "articleAuthorId",
+ "columnName": "articleAuthorId",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "spec",
+ "articleId",
+ "articleAuthorId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_ArticleFeedCrossRef_spec",
+ "unique": false,
+ "columnNames": [
+ "spec"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_ArticleFeedCrossRef_spec` ON `${TABLE_NAME}` (`spec`)"
+ },
+ {
+ "name": "index_ArticleFeedCrossRef_articleId",
+ "unique": false,
+ "columnNames": [
+ "articleId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_ArticleFeedCrossRef_articleId` ON `${TABLE_NAME}` (`articleId`)"
+ },
+ {
+ "name": "index_ArticleFeedCrossRef_articleAuthorId",
+ "unique": false,
+ "columnNames": [
+ "articleAuthorId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_ArticleFeedCrossRef_articleAuthorId` ON `${TABLE_NAME}` (`articleAuthorId`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0e3be72662ad114f97ee4b849358fcf2')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/net/primal/android/attachments/db/NoteNostrUri.kt b/app/src/main/kotlin/net/primal/android/attachments/db/NoteNostrUri.kt
index 758c0024..49f091f8 100644
--- a/app/src/main/kotlin/net/primal/android/attachments/db/NoteNostrUri.kt
+++ b/app/src/main/kotlin/net/primal/android/attachments/db/NoteNostrUri.kt
@@ -1,9 +1,12 @@
package net.primal.android.attachments.db
+import androidx.room.ColumnInfo
import androidx.room.Embedded
import androidx.room.Entity
import kotlinx.serialization.Serializable
+import net.primal.android.attachments.domain.NostrUriType
import net.primal.android.notes.db.ReferencedArticle
+import net.primal.android.notes.db.ReferencedHighlight
import net.primal.android.notes.db.ReferencedNote
import net.primal.android.notes.db.ReferencedUser
@@ -14,6 +17,9 @@ import net.primal.android.notes.db.ReferencedUser
data class NoteNostrUri(
val noteId: String,
val uri: String,
+ val type: NostrUriType,
+ @ColumnInfo("refEvent_alt") val referencedEventAlt: String? = null,
+ @Embedded(prefix = "refHighlight_") val referencedHighlight: ReferencedHighlight? = null,
@Embedded(prefix = "refNote_") val referencedNote: ReferencedNote? = null,
@Embedded(prefix = "refArticle_") val referencedArticle: ReferencedArticle? = null,
@Embedded(prefix = "refUser_") val referencedUser: ReferencedUser? = null,
diff --git a/app/src/main/kotlin/net/primal/android/attachments/domain/NostrUriType.kt b/app/src/main/kotlin/net/primal/android/attachments/domain/NostrUriType.kt
new file mode 100644
index 00000000..17ba0351
--- /dev/null
+++ b/app/src/main/kotlin/net/primal/android/attachments/domain/NostrUriType.kt
@@ -0,0 +1,9 @@
+package net.primal.android.attachments.domain
+
+enum class NostrUriType {
+ Note,
+ Profile,
+ Article,
+ Highlight,
+ Unsupported,
+}
diff --git a/app/src/main/kotlin/net/primal/android/core/compose/icons/__PrimalIcons.kt b/app/src/main/kotlin/net/primal/android/core/compose/icons/__PrimalIcons.kt
index a38aa531..9ad69116 100644
--- a/app/src/main/kotlin/net/primal/android/core/compose/icons/__PrimalIcons.kt
+++ b/app/src/main/kotlin/net/primal/android/core/compose/icons/__PrimalIcons.kt
@@ -30,6 +30,7 @@ import net.primal.android.core.compose.icons.primaliconpack.DarkMode
import net.primal.android.core.compose.icons.primaliconpack.Delete
import net.primal.android.core.compose.icons.primaliconpack.Directory
import net.primal.android.core.compose.icons.primaliconpack.Discuss
+import net.primal.android.core.compose.icons.primaliconpack.Document
import net.primal.android.core.compose.icons.primaliconpack.Download
import net.primal.android.core.compose.icons.primaliconpack.Downloads
import net.primal.android.core.compose.icons.primaliconpack.DownloadsFilled
@@ -283,6 +284,7 @@ val PrimalIcons.PrimalIcons: ____KtList
DrawerSettings,
DrawerSignOut,
OnboardingZapsExplained,
+ Document,
)
return __PrimalIcons!!
}
diff --git a/app/src/main/kotlin/net/primal/android/core/compose/icons/primaliconpack/Document.kt b/app/src/main/kotlin/net/primal/android/core/compose/icons/primaliconpack/Document.kt
new file mode 100644
index 00000000..00539dd2
--- /dev/null
+++ b/app/src/main/kotlin/net/primal/android/core/compose/icons/primaliconpack/Document.kt
@@ -0,0 +1,57 @@
+package net.primal.android.core.compose.icons.primaliconpack
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.path
+import androidx.compose.ui.unit.dp
+import net.primal.android.core.compose.icons.PrimalIcons
+
+val PrimalIcons.Document: ImageVector
+ get() {
+ if (_Document != null) {
+ return _Document!!
+ }
+ _Document = ImageVector.Builder(
+ name = "Document",
+ defaultWidth = 20.dp,
+ defaultHeight = 23.dp,
+ viewportWidth = 20f,
+ viewportHeight = 23f
+ ).apply {
+ path(
+ stroke = SolidColor(Color(0xFF757575)),
+ strokeLineWidth = 1.5f
+ ) {
+ moveTo(2.75f, 4f)
+ curveTo(2.75f, 2.757f, 3.757f, 1.75f, 5f, 1.75f)
+ horizontalLineTo(11.757f)
+ curveTo(12.354f, 1.75f, 12.926f, 1.987f, 13.348f, 2.409f)
+ lineTo(13.879f, 1.879f)
+ lineTo(13.348f, 2.409f)
+ lineTo(15.47f, 4.53f)
+ lineTo(17.591f, 6.652f)
+ curveTo(18.013f, 7.074f, 18.25f, 7.646f, 18.25f, 8.243f)
+ verticalLineTo(18f)
+ curveTo(18.25f, 19.243f, 17.243f, 20.25f, 16f, 20.25f)
+ horizontalLineTo(5f)
+ curveTo(3.757f, 20.25f, 2.75f, 19.243f, 2.75f, 18f)
+ verticalLineTo(4f)
+ close()
+ }
+ path(
+ stroke = SolidColor(Color(0xFF757575)),
+ strokeLineWidth = 1.5f
+ ) {
+ moveTo(11.5f, 2f)
+ verticalLineTo(7.5f)
+ curveTo(11.5f, 8.052f, 11.948f, 8.5f, 12.5f, 8.5f)
+ horizontalLineTo(18f)
+ }
+ }.build()
+
+ return _Document!!
+ }
+
+@Suppress("ObjectPropertyName")
+private var _Document: ImageVector? = null
diff --git a/app/src/main/kotlin/net/primal/android/core/serialization/room/JsonTypeConverters.kt b/app/src/main/kotlin/net/primal/android/core/serialization/room/JsonTypeConverters.kt
new file mode 100644
index 00000000..10799c74
--- /dev/null
+++ b/app/src/main/kotlin/net/primal/android/core/serialization/room/JsonTypeConverters.kt
@@ -0,0 +1,23 @@
+package net.primal.android.core.serialization.room
+
+import androidx.room.TypeConverter
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.JsonArray
+import net.primal.android.core.serialization.json.NostrJson
+import net.primal.android.core.serialization.json.decodeFromStringOrNull
+
+class JsonTypeConverters {
+
+ @TypeConverter
+ fun stringToJsonArray(value: String?): JsonArray? {
+ return NostrJson.decodeFromStringOrNull(value)
+ }
+
+ @TypeConverter
+ fun jsonArrayToString(jsonArray: JsonArray?): String? {
+ return when (jsonArray) {
+ null -> null
+ else -> NostrJson.encodeToString(jsonArray)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/net/primal/android/db/PrimalDatabase.kt b/app/src/main/kotlin/net/primal/android/db/PrimalDatabase.kt
index 6f46b5ef..4eec9ed5 100644
--- a/app/src/main/kotlin/net/primal/android/db/PrimalDatabase.kt
+++ b/app/src/main/kotlin/net/primal/android/db/PrimalDatabase.kt
@@ -15,6 +15,7 @@ import net.primal.android.attachments.db.NoteNostrUri
import net.primal.android.attachments.db.serialization.AttachmentTypeConverters
import net.primal.android.bookmarks.db.PublicBookmark
import net.primal.android.bookmarks.db.PublicBookmarkDao
+import net.primal.android.core.serialization.room.JsonTypeConverters
import net.primal.android.core.serialization.room.ListsTypeConverters
import net.primal.android.explore.db.TrendingTopic
import net.primal.android.explore.db.TrendingTopicDao
@@ -89,11 +90,12 @@ import net.primal.android.wallet.db.WalletTransactionData
ArticleCommentCrossRef::class,
ArticleFeedCrossRef::class,
],
- version = 45,
+ version = 46,
exportSchema = true,
)
@TypeConverters(
ListsTypeConverters::class,
+ JsonTypeConverters::class,
AttachmentTypeConverters::class,
ProfileTypeConverters::class,
)
diff --git a/app/src/main/kotlin/net/primal/android/editor/NoteEditorViewModel.kt b/app/src/main/kotlin/net/primal/android/editor/NoteEditorViewModel.kt
index 6f13d921..0a8429d6 100644
--- a/app/src/main/kotlin/net/primal/android/editor/NoteEditorViewModel.kt
+++ b/app/src/main/kotlin/net/primal/android/editor/NoteEditorViewModel.kt
@@ -70,7 +70,7 @@ class NoteEditorViewModel @AssistedInject constructor(
) : ViewModel() {
private val replyToNoteId = args.replyToNoteId
- private val replyToArticleNaddr = args.replyToArticleNaddr?.let(Nip19TLV::parseAsNaddr)
+ private val replyToArticleNaddr = args.replyToArticleNaddr?.let(Nip19TLV::parseUriAsNaddrOrNull)
private val _state = MutableStateFlow(UiState())
val state = _state.asStateFlow()
diff --git a/app/src/main/kotlin/net/primal/android/explore/repository/ExploreRepository.kt b/app/src/main/kotlin/net/primal/android/explore/repository/ExploreRepository.kt
index 32088df1..a11f891f 100644
--- a/app/src/main/kotlin/net/primal/android/explore/repository/ExploreRepository.kt
+++ b/app/src/main/kotlin/net/primal/android/explore/repository/ExploreRepository.kt
@@ -65,6 +65,7 @@ class ExploreRepository @Inject constructor(
val notes = response.noteEvents.mapAsPostDataPO(referencedPosts = emptyList())
val nostrUris = notes.flatMapPostsAsNoteNostrUriPO(
+ eventIdToNostrEvent = emptyMap(),
postIdToPostDataMap = emptyMap(),
articleIdToArticle = emptyMap(),
profileIdToProfileDataMap = profilesMap,
diff --git a/app/src/main/kotlin/net/primal/android/messages/api/mediator/MessagesProcessor.kt b/app/src/main/kotlin/net/primal/android/messages/api/mediator/MessagesProcessor.kt
index a68da32f..cf198ecd 100644
--- a/app/src/main/kotlin/net/primal/android/messages/api/mediator/MessagesProcessor.kt
+++ b/app/src/main/kotlin/net/primal/android/messages/api/mediator/MessagesProcessor.kt
@@ -128,6 +128,7 @@ class MessagesProcessor @Inject constructor(
database.attachments().upsertAllNostrUris(
data = messageDataList.flatMapMessagesAsNostrResourcePO(
+ eventIdToNostrEvent = emptyMap(),
postIdToPostDataMap = referencedNotesMap,
articleIdToArticle = emptyMap(),
profileIdToProfileDataMap = referencedProfilesMap,
diff --git a/app/src/main/kotlin/net/primal/android/nostr/ext/NostrResources.kt b/app/src/main/kotlin/net/primal/android/nostr/ext/NostrResources.kt
index c874f759..6a323578 100644
--- a/app/src/main/kotlin/net/primal/android/nostr/ext/NostrResources.kt
+++ b/app/src/main/kotlin/net/primal/android/nostr/ext/NostrResources.kt
@@ -1,11 +1,13 @@
package net.primal.android.nostr.ext
import java.util.regex.Pattern
+import kotlinx.serialization.json.JsonArray
import net.primal.android.articles.db.ArticleData
import net.primal.android.articles.feed.ui.wordsCountToReadingTime
import net.primal.android.attachments.db.NoteNostrUri
import net.primal.android.attachments.domain.CdnResource
import net.primal.android.attachments.domain.LinkPreviewData
+import net.primal.android.attachments.domain.NostrUriType
import net.primal.android.attachments.ext.flatMapPostsAsNoteAttachmentPO
import net.primal.android.core.utils.asEllipsizedNpub
import net.primal.android.core.utils.authorNameUiFriendly
@@ -14,11 +16,14 @@ import net.primal.android.crypto.bech32ToHexOrThrow
import net.primal.android.crypto.bechToBytesOrThrow
import net.primal.android.crypto.toHex
import net.primal.android.messages.db.DirectMessageData
+import net.primal.android.nostr.model.NostrEvent
import net.primal.android.nostr.model.NostrEventKind
+import net.primal.android.nostr.utils.Naddr
import net.primal.android.nostr.utils.Nip19TLV
import net.primal.android.nostr.utils.Nip19TLV.toNaddrString
import net.primal.android.notes.db.PostData
import net.primal.android.notes.db.ReferencedArticle
+import net.primal.android.notes.db.ReferencedHighlight
import net.primal.android.notes.db.ReferencedNote
import net.primal.android.notes.db.ReferencedUser
import net.primal.android.profile.db.ProfileData
@@ -136,6 +141,19 @@ fun String.extractNoteId(): String? {
}
}
+fun String.extractEventId(): String? {
+ return extract { bechPrefix: String?, key: String? ->
+ when (bechPrefix?.lowercase()) {
+ NEVENT -> {
+ val tlv = Nip19TLV.parse((bechPrefix + key).bechToBytesOrThrow())
+ tlv[Nip19TLV.Type.SPECIAL.id]?.first()?.toHex()
+ }
+
+ else -> (bechPrefix + key).bechToBytesOrThrow().toHex()
+ }
+ }
+}
+
private fun String.extract(parser: (bechPrefix: String?, key: String?) -> String?): String? {
val matcher = nostrUriRegexPattern.matcher(this)
if (!matcher.find()) return null
@@ -174,7 +192,7 @@ fun String.takeAsProfileHexIdOrNull(): String? {
fun String.takeAsNaddrOrNull(): String? {
return if (isNAddr() || isNAddrUri()) {
val result = runCatching {
- Nip19TLV.parseAsNaddr(this)
+ Nip19TLV.parseUriAsNaddrOrNull(this)
}
if (result.getOrNull() != null) {
this
@@ -187,6 +205,7 @@ fun String.takeAsNaddrOrNull(): String? {
}
fun List.flatMapPostsAsNoteNostrUriPO(
+ eventIdToNostrEvent: Map,
postIdToPostDataMap: Map,
articleIdToArticle: Map,
profileIdToProfileDataMap: Map,
@@ -197,6 +216,7 @@ fun List.flatMapPostsAsNoteNostrUriPO(
flatMap { postData ->
postData.uris.mapAsNoteNostrUriPO(
eventId = postData.postId,
+ eventIdToNostrEvent = eventIdToNostrEvent,
postIdToPostDataMap = postIdToPostDataMap,
articleIdToArticle = articleIdToArticle,
profileIdToProfileDataMap = profileIdToProfileDataMap,
@@ -207,6 +227,7 @@ fun List.flatMapPostsAsNoteNostrUriPO(
}
fun List.flatMapMessagesAsNostrResourcePO(
+ eventIdToNostrEvent: Map,
postIdToPostDataMap: Map,
articleIdToArticle: Map,
profileIdToProfileDataMap: Map,
@@ -216,6 +237,7 @@ fun List.flatMapMessagesAsNostrResourcePO(
) = flatMap { messageData ->
messageData.uris.mapAsNoteNostrUriPO(
eventId = messageData.messageId,
+ eventIdToNostrEvent = eventIdToNostrEvent,
postIdToPostDataMap = postIdToPostDataMap,
articleIdToArticle = articleIdToArticle,
profileIdToProfileDataMap = profileIdToProfileDataMap,
@@ -227,6 +249,7 @@ fun List.flatMapMessagesAsNostrResourcePO(
fun List.mapAsNoteNostrUriPO(
eventId: String,
+ eventIdToNostrEvent: Map,
postIdToPostDataMap: Map,
articleIdToArticle: Map,
profileIdToProfileDataMap: Map,
@@ -240,76 +263,150 @@ fun List.mapAsNoteNostrUriPO(
val refNote = postIdToPostDataMap[refNoteId]
val refPostAuthor = profileIdToProfileDataMap[refNote?.authorId]
- val refNaddr = Nip19TLV.parseAsNaddr(
- naddr = if (link.startsWith("nostr:")) {
- link.removePrefix("nostr:")
- } else {
- link
- },
- )
+ val refNaddr = Nip19TLV.parseUriAsNaddrOrNull(link)
val refArticle = articleIdToArticle[refNaddr?.identifier]
val refArticleAuthor = profileIdToProfileDataMap[refNaddr?.userId]
+ val referencedNostrEvent: NostrEvent? = eventIdToNostrEvent[link.extractEventId()]
+
+ val refHighlightText = referencedNostrEvent?.content
+ val refHighlightATag = referencedNostrEvent?.tags?.firstOrNull { it.isATag() }
+
+ val type = if (refUserProfileId != null) {
+ NostrUriType.Profile
+ } else if (refNote != null && refPostAuthor != null) {
+ NostrUriType.Note
+ } else if (refNaddr?.kind == NostrEventKind.LongFormContent.value &&
+ refArticle != null && refArticleAuthor != null
+ ) {
+ NostrUriType.Article
+ } else if (referencedNostrEvent?.kind == NostrEventKind.Highlight.value &&
+ refHighlightText?.isNotEmpty() == true && refHighlightATag != null
+ ) {
+ NostrUriType.Highlight
+ } else {
+ NostrUriType.Unsupported
+ }
+
NoteNostrUri(
noteId = eventId,
uri = link,
- referencedUser = if (refUserProfileId != null) {
- ReferencedUser(
- userId = refUserProfileId,
- handle = profileIdToProfileDataMap[refUserProfileId]?.usernameUiFriendly()
- ?: refUserProfileId.asEllipsizedNpub(),
- )
- } else {
- null
- },
- referencedNote = if (refNote != null && refPostAuthor != null) {
- ReferencedNote(
- postId = refNote.postId,
- createdAt = refNote.createdAt,
- content = refNote.content,
- authorId = refNote.authorId,
- authorName = refPostAuthor.authorNameUiFriendly(),
- authorAvatarCdnImage = refPostAuthor.avatarCdnImage,
- authorInternetIdentifier = refPostAuthor.internetIdentifier,
- authorLightningAddress = refPostAuthor.lightningAddress,
- attachments = listOf(refNote).flatMapPostsAsNoteAttachmentPO(
- cdnResources = cdnResources,
- linkPreviews = linkPreviews,
- videoThumbnails = videoThumbnails,
- ),
- nostrUris = listOf(refNote).flatMapPostsAsNoteNostrUriPO(
- postIdToPostDataMap = postIdToPostDataMap,
- articleIdToArticle = articleIdToArticle,
- profileIdToProfileDataMap = profileIdToProfileDataMap,
- cdnResources = cdnResources,
- linkPreviews = linkPreviews,
- videoThumbnails = videoThumbnails,
- ),
- )
- } else {
- null
- },
- referencedArticle = if (
- refNaddr?.kind == NostrEventKind.LongFormContent.value &&
- refArticle != null &&
- refArticleAuthor != null
- ) {
- ReferencedArticle(
- naddr = refNaddr.toNaddrString(),
- aTag = refArticle.aTag,
- eventId = refArticle.eventId,
- articleId = refArticle.articleId,
- articleTitle = refArticle.title,
- authorId = refArticle.authorId,
- authorName = refArticleAuthor.authorNameUiFriendly(),
- authorAvatarCdnImage = refArticleAuthor.avatarCdnImage,
- createdAt = refArticle.createdAt,
- raw = refArticle.raw,
- articleImageCdnImage = refArticle.imageCdnImage,
- articleReadingTimeInMinutes = refArticle.wordsCount.wordsCountToReadingTime(),
- )
- } else {
- null
- },
+ type = type,
+ referencedEventAlt = referencedNostrEvent?.tags?.findFirstAltDescription(),
+ referencedUser = takeAsReferencedUserOrNull(refUserProfileId, profileIdToProfileDataMap),
+ referencedNote = takeAsReferencedNoteOrNull(
+ refNote = refNote,
+ refPostAuthor = refPostAuthor,
+ cdnResources = cdnResources,
+ linkPreviews = linkPreviews,
+ videoThumbnails = videoThumbnails,
+ eventIdToNostrEvent = eventIdToNostrEvent,
+ postIdToPostDataMap = postIdToPostDataMap,
+ articleIdToArticle = articleIdToArticle,
+ profileIdToProfileDataMap = profileIdToProfileDataMap,
+ ),
+ referencedArticle = takeAsReferencedArticleOrNull(refNaddr, refArticle, refArticleAuthor),
+ referencedHighlight = takeAsReferencedHighlightOrNull(
+ uri = link,
+ highlight = refHighlightText,
+ aTag = refHighlightATag,
+ authorId = referencedNostrEvent?.tags?.findFirstProfileId(),
+ ),
+ )
+}
+
+private fun takeAsReferencedNoteOrNull(
+ refNote: PostData?,
+ refPostAuthor: ProfileData?,
+ cdnResources: Map,
+ linkPreviews: Map,
+ videoThumbnails: Map,
+ eventIdToNostrEvent: Map,
+ postIdToPostDataMap: Map,
+ articleIdToArticle: Map,
+ profileIdToProfileDataMap: Map,
+) = if (refNote != null && refPostAuthor != null) {
+ ReferencedNote(
+ postId = refNote.postId,
+ createdAt = refNote.createdAt,
+ content = refNote.content,
+ authorId = refNote.authorId,
+ authorName = refPostAuthor.authorNameUiFriendly(),
+ authorAvatarCdnImage = refPostAuthor.avatarCdnImage,
+ authorInternetIdentifier = refPostAuthor.internetIdentifier,
+ authorLightningAddress = refPostAuthor.lightningAddress,
+ attachments = listOf(refNote).flatMapPostsAsNoteAttachmentPO(
+ cdnResources = cdnResources,
+ linkPreviews = linkPreviews,
+ videoThumbnails = videoThumbnails,
+ ),
+ nostrUris = listOf(refNote).flatMapPostsAsNoteNostrUriPO(
+ eventIdToNostrEvent = eventIdToNostrEvent,
+ postIdToPostDataMap = postIdToPostDataMap,
+ articleIdToArticle = articleIdToArticle,
+ profileIdToProfileDataMap = profileIdToProfileDataMap,
+ cdnResources = cdnResources,
+ linkPreviews = linkPreviews,
+ videoThumbnails = videoThumbnails,
+ ),
+ )
+} else {
+ null
+}
+
+private fun takeAsReferencedUserOrNull(
+ refUserProfileId: String?,
+ profileIdToProfileDataMap: Map,
+) = if (refUserProfileId != null) {
+ ReferencedUser(
+ userId = refUserProfileId,
+ handle = profileIdToProfileDataMap[refUserProfileId]?.usernameUiFriendly()
+ ?: refUserProfileId.asEllipsizedNpub(),
+ )
+} else {
+ null
+}
+
+private fun takeAsReferencedArticleOrNull(
+ refNaddr: Naddr?,
+ refArticle: ArticleData?,
+ refArticleAuthor: ProfileData?,
+) = if (
+ refNaddr?.kind == NostrEventKind.LongFormContent.value &&
+ refArticle != null &&
+ refArticleAuthor != null
+) {
+ ReferencedArticle(
+ naddr = refNaddr.toNaddrString(),
+ aTag = refArticle.aTag,
+ eventId = refArticle.eventId,
+ articleId = refArticle.articleId,
+ articleTitle = refArticle.title,
+ authorId = refArticle.authorId,
+ authorName = refArticleAuthor.authorNameUiFriendly(),
+ authorAvatarCdnImage = refArticleAuthor.avatarCdnImage,
+ createdAt = refArticle.createdAt,
+ raw = refArticle.raw,
+ articleImageCdnImage = refArticle.imageCdnImage,
+ articleReadingTimeInMinutes = refArticle.wordsCount.wordsCountToReadingTime(),
+ )
+} else {
+ null
+}
+
+private fun takeAsReferencedHighlightOrNull(
+ uri: String,
+ highlight: String?,
+ aTag: JsonArray?,
+ authorId: String?,
+) = if (highlight?.isNotEmpty() == true && aTag != null) {
+ val nevent = Nip19TLV.parseUriAsNeventOrNull(neventUri = uri)
+ ReferencedHighlight(
+ text = highlight,
+ aTag = aTag,
+ eventId = nevent?.eventId,
+ authorId = authorId,
)
+} else {
+ null
}
diff --git a/app/src/main/kotlin/net/primal/android/nostr/ext/Tags.kt b/app/src/main/kotlin/net/primal/android/nostr/ext/Tags.kt
index 22557a13..c7da7b3c 100644
--- a/app/src/main/kotlin/net/primal/android/nostr/ext/Tags.kt
+++ b/app/src/main/kotlin/net/primal/android/nostr/ext/Tags.kt
@@ -8,30 +8,15 @@ import kotlinx.serialization.json.jsonPrimitive
import net.primal.android.core.utils.parseHashtags
import net.primal.android.editor.domain.NoteAttachment
-fun List.findFirstEventId(): String? {
- val postTag = firstOrNull { it.isEventIdTag() }
- return postTag?.getTagValueOrNull()
-}
+fun List.findFirstEventId() = firstOrNull { it.isEventIdTag() }?.getTagValueOrNull()
-fun List.findFirstProfileId(): String? {
- val postAuthorTag = firstOrNull { it.isPubKeyTag() }
- return postAuthorTag?.getTagValueOrNull()
-}
+fun List.findFirstProfileId() = firstOrNull { it.isPubKeyTag() }?.getTagValueOrNull()
-fun List.findFirstZapRequest(): String? {
- val zapRequestTag = firstOrNull { it.isDescriptionTag() }
- return zapRequestTag?.getTagValueOrNull()
-}
+fun List.findFirstZapRequest() = firstOrNull { it.isDescriptionTag() }?.getTagValueOrNull()
-fun List.findFirstZapAmount(): String? {
- val zapRequestTag = firstOrNull { it.isAmountTag() }
- return zapRequestTag?.getTagValueOrNull()
-}
+fun List.findFirstZapAmount() = firstOrNull { it.isAmountTag() }?.getTagValueOrNull()
-fun List.findFirstBolt11(): String? {
- val zapRequestTag = firstOrNull { it.isBolt11Tag() }
- return zapRequestTag?.getTagValueOrNull()
-}
+fun List.findFirstBolt11() = firstOrNull { it.isBolt11Tag() }?.getTagValueOrNull()
fun List.findFirstTitle() = firstOrNull { it.isTitleTag() }?.getTagValueOrNull()
@@ -43,6 +28,8 @@ fun List.findFirstPublishedAt() = firstOrNull { it.isPublishedAtTag()
fun List.findFirstIdentifier() = firstOrNull { it.isIdentifierTag() }?.getTagValueOrNull()
+fun List.findFirstAltDescription() = firstOrNull { it.isAltTag() }?.getTagValueOrNull()
+
fun JsonArray.isBolt11Tag() = getOrNull(0)?.jsonPrimitive?.content == "bolt11"
fun JsonArray.isDescriptionTag() = getOrNull(0)?.jsonPrimitive?.content == "description"
@@ -57,12 +44,16 @@ fun JsonArray.isHashtagTag() = getOrNull(0)?.jsonPrimitive?.content == "t"
fun JsonArray.isIdentifierTag() = getOrNull(0)?.jsonPrimitive?.content == "d"
+fun JsonArray.isATag() = getOrNull(0)?.jsonPrimitive?.content == "a"
+
fun JsonArray.isTitleTag() = getOrNull(0)?.jsonPrimitive?.content == "title"
fun JsonArray.isSummaryTag() = getOrNull(0)?.jsonPrimitive?.content == "summary"
fun JsonArray.isImageTag() = getOrNull(0)?.jsonPrimitive?.content == "image"
+fun JsonArray.isAltTag() = getOrNull(0)?.jsonPrimitive?.content == "alt"
+
fun JsonArray.isPublishedAtTag() = getOrNull(0)?.jsonPrimitive?.content == "published_at"
fun JsonArray.getTagValueOrNull() = getOrNull(1)?.jsonPrimitive?.content
diff --git a/app/src/main/kotlin/net/primal/android/nostr/model/NostrEventKind.kt b/app/src/main/kotlin/net/primal/android/nostr/model/NostrEventKind.kt
index e4e7de59..c1e8c168 100644
--- a/app/src/main/kotlin/net/primal/android/nostr/model/NostrEventKind.kt
+++ b/app/src/main/kotlin/net/primal/android/nostr/model/NostrEventKind.kt
@@ -23,6 +23,7 @@ enum class NostrEventKind(val value: Int) {
Reporting(value = 1984),
ZapRequest(value = 9734),
Zap(value = 9735),
+ Highlight(value = 9802),
MuteList(value = 10_000),
PinList(value = 10_001),
RelayListMetadata(value = 10_002),
diff --git a/app/src/main/kotlin/net/primal/android/nostr/utils/Naddr.kt b/app/src/main/kotlin/net/primal/android/nostr/utils/Naddr.kt
index 786a0839..19b164bb 100644
--- a/app/src/main/kotlin/net/primal/android/nostr/utils/Naddr.kt
+++ b/app/src/main/kotlin/net/primal/android/nostr/utils/Naddr.kt
@@ -1,8 +1,36 @@
package net.primal.android.nostr.utils
+import kotlinx.serialization.json.JsonArray
+import kotlinx.serialization.json.jsonPrimitive
+import net.primal.android.nostr.ext.isATag
+
data class Naddr(
+ val kind: Int,
+ val userId: String,
val identifier: String,
val relays: List = emptyList(),
- val userId: String,
- val kind: Int,
)
+
+@Suppress("MagicNumber")
+fun JsonArray.aTagToNaddr(): Naddr? {
+ return if (this.isATag()) {
+ val value = getOrNull(1)?.jsonPrimitive?.content
+ val chunks = value?.split(":")
+ val kind = chunks?.getOrNull(0)?.toIntOrNull()
+ if (chunks?.size == 3 && kind != null) {
+ val userId = chunks[1]
+ val identifier = chunks[2]
+ val relay = getOrNull(2)?.jsonPrimitive?.content
+ Naddr(
+ kind = kind,
+ userId = userId,
+ identifier = identifier,
+ relays = if (relay?.isNotEmpty() == true) listOf(relay) else emptyList(),
+ )
+ } else {
+ null
+ }
+ } else {
+ null
+ }
+}
diff --git a/app/src/main/kotlin/net/primal/android/nostr/utils/Nevent.kt b/app/src/main/kotlin/net/primal/android/nostr/utils/Nevent.kt
new file mode 100644
index 00000000..69c01d49
--- /dev/null
+++ b/app/src/main/kotlin/net/primal/android/nostr/utils/Nevent.kt
@@ -0,0 +1,8 @@
+package net.primal.android.nostr.utils
+
+data class Nevent(
+ val kind: Int,
+ val userId: String,
+ val eventId: String,
+ val relays: List = emptyList(),
+)
diff --git a/app/src/main/kotlin/net/primal/android/nostr/utils/Nip19TLV.kt b/app/src/main/kotlin/net/primal/android/nostr/utils/Nip19TLV.kt
index a68d9b76..054a5b6d 100644
--- a/app/src/main/kotlin/net/primal/android/nostr/utils/Nip19TLV.kt
+++ b/app/src/main/kotlin/net/primal/android/nostr/utils/Nip19TLV.kt
@@ -40,32 +40,74 @@ object Nip19TLV {
return result
}
- fun parseAsNaddr(naddr: String): Naddr? =
+ private fun String.cleanNostrScheme(): String {
+ return if (this.startsWith("nostr:")) {
+ this.removePrefix("nostr:")
+ } else {
+ this
+ }
+ }
+
+ fun parseUriAsNeventOrNull(neventUri: String): Nevent? =
runCatching {
- val tlv = parse(naddr)
- val identifier = tlv[Type.SPECIAL.id]?.first()?.let {
- String(bytes = it, charset = Charsets.US_ASCII)
- }
- val relays = tlv[Type.RELAY.id]?.first()?.let {
- String(bytes = it, charset = Charsets.US_ASCII)
- }
- val profileId = tlv[Type.AUTHOR.id]?.first()?.toHex()
+ parseAsNevent(nevent = neventUri.cleanNostrScheme())
+ }.getOrNull()
- val kind = tlv[Type.KIND.id]?.first()?.let {
- toInt32(it)
- }
- if (identifier != null && profileId != null && kind != null) {
- Naddr(
- identifier = identifier,
- relays = relays?.split(",") ?: emptyList(),
- userId = profileId,
- kind = kind,
- )
- } else {
- null
- }
+ private fun parseAsNevent(nevent: String): Nevent? {
+ val tlv = parse(nevent)
+ val eventId = tlv[Type.SPECIAL.id]?.first()?.toHex()
+
+ val relays = tlv[Type.RELAY.id]?.first()?.let {
+ String(bytes = it, charset = Charsets.US_ASCII)
+ }
+ val profileId = tlv[Type.AUTHOR.id]?.first()?.toHex()
+
+ val kind = tlv[Type.KIND.id]?.first()?.let {
+ toInt32(it)
+ }
+ return if (eventId != null && profileId != null && kind != null) {
+ Nevent(
+ kind = kind,
+ eventId = eventId,
+ userId = profileId,
+ relays = relays?.split(",") ?: emptyList(),
+ )
+ } else {
+ null
+ }
+ }
+
+ fun parseUriAsNaddrOrNull(naddrUri: String) =
+ runCatching {
+ parseAsNaddrOrNull(naddr = naddrUri.cleanNostrScheme())
}.getOrNull()
+ private fun parseAsNaddrOrNull(naddr: String): Naddr? {
+ val tlv = parse(naddr)
+ val identifier = tlv[Type.SPECIAL.id]?.first()?.let {
+ String(bytes = it, charset = Charsets.US_ASCII)
+ }
+ val relays = tlv[Type.RELAY.id]?.first()?.let {
+ String(bytes = it, charset = Charsets.US_ASCII)
+ }
+ val profileId = tlv[Type.AUTHOR.id]?.first()?.toHex()
+
+ val kind = tlv[Type.KIND.id]?.first()?.let {
+ toInt32(it)
+ }
+
+ return if (identifier != null && profileId != null && kind != null) {
+ Naddr(
+ identifier = identifier,
+ relays = relays?.split(",") ?: emptyList(),
+ userId = profileId,
+ kind = kind,
+ )
+ } else {
+ null
+ }
+ }
+
fun Naddr.toNaddrString(): String {
val tlv = mutableListOf()
diff --git a/app/src/main/kotlin/net/primal/android/notes/db/ReferencedHighlight.kt b/app/src/main/kotlin/net/primal/android/notes/db/ReferencedHighlight.kt
new file mode 100644
index 00000000..46cb3cbc
--- /dev/null
+++ b/app/src/main/kotlin/net/primal/android/notes/db/ReferencedHighlight.kt
@@ -0,0 +1,12 @@
+package net.primal.android.notes.db
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonArray
+
+@Serializable
+data class ReferencedHighlight(
+ val text: String,
+ val eventId: String?,
+ val authorId: String?,
+ val aTag: JsonArray,
+)
diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/model/NoteNostrUriUi.kt b/app/src/main/kotlin/net/primal/android/notes/feed/model/NoteNostrUriUi.kt
index 6ea09d4e..8629ae2d 100644
--- a/app/src/main/kotlin/net/primal/android/notes/feed/model/NoteNostrUriUi.kt
+++ b/app/src/main/kotlin/net/primal/android/notes/feed/model/NoteNostrUriUi.kt
@@ -1,12 +1,17 @@
package net.primal.android.notes.feed.model
import net.primal.android.attachments.db.NoteNostrUri
+import net.primal.android.attachments.domain.NostrUriType
import net.primal.android.notes.db.ReferencedArticle
+import net.primal.android.notes.db.ReferencedHighlight
import net.primal.android.notes.db.ReferencedNote
import net.primal.android.notes.db.ReferencedUser
data class NoteNostrUriUi(
val uri: String,
+ val type: NostrUriType,
+ val referencedEventAlt: String?,
+ val referencedHighlight: ReferencedHighlight?,
val referencedNote: ReferencedNote?,
val referencedArticle: ReferencedArticle?,
val referencedUser: ReferencedUser?,
@@ -15,6 +20,9 @@ data class NoteNostrUriUi(
fun NoteNostrUri.asNoteNostrUriUi() =
NoteNostrUriUi(
uri = this.uri,
+ type = this.type,
+ referencedEventAlt = this.referencedEventAlt,
+ referencedHighlight = this.referencedHighlight,
referencedNote = this.referencedNote,
referencedArticle = this.referencedArticle,
referencedUser = this.referencedUser,
diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteContent.kt b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteContent.kt
index 506cd5a1..087d2228 100644
--- a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteContent.kt
+++ b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteContent.kt
@@ -2,14 +2,20 @@ package net.primal.android.notes.feed.note.ui
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
@@ -19,8 +25,11 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.primal.android.LocalContentDisplaySettings
import net.primal.android.R
+import net.primal.android.attachments.domain.NostrUriType
import net.primal.android.core.compose.PrimalClickableText
import net.primal.android.core.compose.attachment.model.isMediaAttachment
+import net.primal.android.core.compose.icons.PrimalIcons
+import net.primal.android.core.compose.icons.primaliconpack.Document
import net.primal.android.core.compose.preview.PrimalPreview
import net.primal.android.core.utils.HashtagMatch
import net.primal.android.core.utils.HashtagMatcher
@@ -41,11 +50,7 @@ private const val NOTE_ANNOTATION_TAG = "note"
private const val HASHTAG_ANNOTATION_TAG = "hashtag"
private const val NOSTR_ADDRESS_ANNOTATION_TAG = "naddr"
-private fun List.filterMentionedNotes() = filter { it.referencedNote != null }
-
-private fun List.filterMentionedArticles() = filter { it.referencedArticle != null }
-
-private fun List.filterMentionedUsers() = filter { it.referencedUser != null }
+private fun List.filter(type: NostrUriType) = filter { it.type == type }
private fun List.filterUnhandledNostrAddressUris() =
filter {
@@ -201,6 +206,22 @@ fun NoteContent(
)
}
+ val referencedHighlights = data.nostrUris.filter(type = NostrUriType.Highlight)
+ if (referencedHighlights.isNotEmpty()) {
+ referencedHighlights
+ .mapNotNull { it.referencedHighlight }
+ .forEachIndexed { index, highlight ->
+ ReferencedHighlight(
+ highlight = highlight,
+ onClick = { naddr -> noteCallbacks.onArticleClick?.invoke(naddr) },
+ )
+
+ if (index < referencedHighlights.size - 1) {
+ Spacer(modifier = Modifier.height(4.dp))
+ }
+ }
+ }
+
if (data.invoices.isNotEmpty()) {
NoteLightningInvoice(
modifier = Modifier.padding(top = if (contentText.isEmpty()) 4.dp else 6.dp),
@@ -220,7 +241,7 @@ fun NoteContent(
)
}
- val referencedPostResources = data.nostrUris.filterMentionedNotes()
+ val referencedPostResources = data.nostrUris.filter(type = NostrUriType.Note)
if (referencedPostResources.isNotEmpty()) {
ReferencedNotesColumn(
modifier = Modifier.padding(top = 4.dp),
@@ -232,7 +253,7 @@ fun NoteContent(
)
}
- val referencedArticleResources = data.nostrUris.filterMentionedArticles()
+ val referencedArticleResources = data.nostrUris.filter(type = NostrUriType.Article)
if (referencedArticleResources.isNotEmpty()) {
ReferencedArticlesColumn(
modifier = Modifier.padding(top = 4.dp),
@@ -243,6 +264,40 @@ fun NoteContent(
hasBorder = referencedEventsHaveBorder,
)
}
+
+ val genericEvents = data.nostrUris.filter(type = NostrUriType.Unsupported)
+ if (genericEvents.isNotEmpty()) {
+ genericEvents.forEachIndexed { index, nostrUriUi ->
+ NoteUnknownEvent(
+ modifier = Modifier.fillMaxWidth(),
+ icon = nostrUriUi.uri.nostrUriToMissingEventIcon(),
+ altDescription = nostrUriUi.referencedEventAlt
+ ?: nostrUriUi.uri.nostrUriToMissingEventAltDescription(),
+ )
+
+ if (index < genericEvents.size - 1) {
+ Spacer(modifier = Modifier.height(4.dp))
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun String.nostrUriToMissingEventAltDescription(): String {
+ return if (contains("note1")) {
+ stringResource(R.string.feed_missing_event_alt_description_note)
+ } else {
+ stringResource(R.string.feed_missing_event_alt_description_event)
+ }
+}
+
+@Composable
+private fun String.nostrUriToMissingEventIcon(): ImageVector {
+ return if (contains("note1")) {
+ Icons.Outlined.ErrorOutline
+ } else {
+ PrimalIcons.Document
}
}
@@ -275,8 +330,7 @@ fun renderContentAsAnnotatedString(
): AnnotatedString {
val mediaAttachments = data.attachments.filter { it.isMediaAttachment() }
val linkAttachments = data.attachments.filterNot { it.isMediaAttachment() }
- val mentionedEvents = data.nostrUris.filterMentionedNotes() + data.nostrUris.filterMentionedArticles()
- val mentionedUsers = data.nostrUris.filterMentionedUsers()
+ val mentionedUsers = data.nostrUris.filter(type = NostrUriType.Profile)
val unhandledNostrAddressUris = data.nostrUris.filterUnhandledNostrAddressUris()
val shouldDeleteLinks = mediaAttachments.isEmpty() && linkAttachments.size == 1 &&
@@ -288,7 +342,7 @@ fun renderContentAsAnnotatedString(
val refinedContent = data.content
.cleanNostrUris()
.remove(texts = mediaAttachments.map { it.url })
- .remove(texts = if (!shouldKeepNostrNoteUris) mentionedEvents.map { it.uri } else emptyList())
+ .remove(texts = if (!shouldKeepNostrNoteUris) data.nostrUris.map { it.uri } else emptyList())
.remove(texts = if (shouldDeleteLinks) linkAttachments.map { it.url } else emptyList())
.remove(texts = data.invoices)
.replaceNostrProfileUrisWithHandles(resources = mentionedUsers)
@@ -444,12 +498,81 @@ fun PreviewPostContent() {
nostrUris = listOf(
NoteNostrUriUi(
uri = "nostr:referencedUser",
+ type = NostrUriType.Profile,
+ referencedEventAlt = null,
referencedNote = null,
referencedUser = ReferencedUser(
userId = "nostr:referencedUser",
handle = "alex",
),
referencedArticle = null,
+ referencedHighlight = null,
+ ),
+ ),
+ hashtags = listOf("#nostr"),
+ ),
+ expanded = false,
+ enableTweetsMode = false,
+ onClick = {},
+ onUrlClick = {},
+ noteCallbacks = NoteCallbacks(),
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+fun PreviewPostUnknownReferencedEventWithAlt() {
+ PrimalPreview(primalTheme = PrimalTheme.Sunset) {
+ Surface(modifier = Modifier.fillMaxWidth()) {
+ NoteContent(
+ data = NoteContentUi(
+ noteId = "",
+ content = "This is amazing! nostr:nevent124124124214123412",
+ attachments = emptyList(),
+ nostrUris = listOf(
+ NoteNostrUriUi(
+ uri = "nostr:nevent124124124214123412",
+ type = NostrUriType.Unsupported,
+ referencedEventAlt = "This is a music song.",
+ referencedNote = null,
+ referencedUser = null,
+ referencedArticle = null,
+ referencedHighlight = null,
+ ),
+ ),
+ hashtags = listOf("#nostr"),
+ ),
+ expanded = false,
+ enableTweetsMode = false,
+ onClick = {},
+ onUrlClick = {},
+ noteCallbacks = NoteCallbacks(),
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+fun PreviewPostUnknownReferencedEventWithoutAlt() {
+ PrimalPreview(primalTheme = PrimalTheme.Sunset) {
+ Surface(modifier = Modifier.fillMaxWidth()) {
+ NoteContent(
+ data = NoteContentUi(
+ noteId = "",
+ content = "This is amazing! nostr:note111",
+ attachments = emptyList(),
+ nostrUris = listOf(
+ NoteNostrUriUi(
+ uri = "nostr:note111",
+ type = NostrUriType.Unsupported,
+ referencedEventAlt = null,
+ referencedNote = null,
+ referencedUser = null,
+ referencedArticle = null,
+ referencedHighlight = null,
),
),
hashtags = listOf("#nostr"),
@@ -485,6 +608,7 @@ fun PreviewPostContentWithReferencedPost() {
nostrUris = listOf(
NoteNostrUriUi(
uri = "nostr:referencedPost",
+ type = NostrUriType.Note,
referencedNote = ReferencedNote(
postId = "postId",
createdAt = 0,
@@ -499,9 +623,12 @@ fun PreviewPostContentWithReferencedPost() {
),
referencedUser = null,
referencedArticle = null,
+ referencedEventAlt = null,
+ referencedHighlight = null,
),
NoteNostrUriUi(
uri = "nostr:referenced2Post",
+ type = NostrUriType.Note,
referencedNote = ReferencedNote(
postId = "postId",
createdAt = 0,
@@ -516,6 +643,8 @@ fun PreviewPostContentWithReferencedPost() {
),
referencedUser = null,
referencedArticle = null,
+ referencedEventAlt = null,
+ referencedHighlight = null,
),
),
hashtags = listOf("#nostr"),
@@ -541,7 +670,7 @@ fun PreviewPostContentWithReferencedPost() {
@Composable
fun PreviewPostContentWithTweet() {
PrimalPreview(primalTheme = PrimalTheme.Sunset) {
- Surface {
+ Surface(modifier = Modifier.fillMaxWidth()) {
NoteContent(
data = NoteContentUi(
noteId = "",
diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteUnknownEvent.kt b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteUnknownEvent.kt
new file mode 100644
index 00000000..a8ebfc0d
--- /dev/null
+++ b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteUnknownEvent.kt
@@ -0,0 +1,78 @@
+package net.primal.android.notes.feed.note.ui
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Description
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import net.primal.android.core.compose.preview.PrimalPreview
+import net.primal.android.theme.AppTheme
+
+@Composable
+fun NoteUnknownEvent(
+ modifier: Modifier = Modifier,
+ altDescription: String,
+ icon: ImageVector = Icons.Outlined.Description,
+ onClick: (() -> Unit)? = null,
+) {
+ Row(
+ modifier = modifier
+ .background(
+ shape = AppTheme.shapes.medium,
+ color = AppTheme.extraColorScheme.surfaceVariantAlt1,
+ )
+ .padding(all = 16.dp)
+ .clickable(enabled = onClick != null, onClick = { onClick?.invoke() }),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ imageVector = icon,
+ contentDescription = null,
+ )
+
+ Text(
+ modifier = Modifier.padding(start = 12.dp, top = 2.dp),
+ text = altDescription,
+ style = AppTheme.typography.bodyMedium,
+ maxLines = 7,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewLightNoteInvoice() {
+ PrimalPreview(primalTheme = net.primal.android.theme.domain.PrimalTheme.Sunrise) {
+ Surface(modifier = Modifier.fillMaxWidth()) {
+ NoteUnknownEvent(
+ altDescription = "This is unknown event.",
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewDarkNoteInvoice() {
+ PrimalPreview(primalTheme = net.primal.android.theme.domain.PrimalTheme.Sunset) {
+ Surface(modifier = Modifier.fillMaxWidth()) {
+ NoteUnknownEvent(
+ altDescription = "This is unknown event with some very long alt description " +
+ "which is going to break into multiple lines for sure.",
+ )
+ }
+ }
+}
diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedHighlight.kt b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedHighlight.kt
new file mode 100644
index 00000000..3d693248
--- /dev/null
+++ b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedHighlight.kt
@@ -0,0 +1,46 @@
+package net.primal.android.notes.feed.note.ui
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.PlatformTextStyle
+import androidx.compose.ui.text.style.LineBreak
+import androidx.compose.ui.text.style.LineHeightStyle
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
+import net.primal.android.nostr.utils.Nip19TLV.toNaddrString
+import net.primal.android.nostr.utils.aTagToNaddr
+import net.primal.android.notes.db.ReferencedHighlight
+import net.primal.android.theme.AppTheme
+
+private val HighlightBackground = Color(0xFF2E3726)
+
+@Composable
+fun ReferencedHighlight(highlight: ReferencedHighlight, onClick: (naddr: String) -> Unit) {
+ val naddr = highlight.aTag.aTagToNaddr()?.toNaddrString()
+ Text(
+ modifier = Modifier
+ .padding(top = 2.dp)
+ .clickable(
+ enabled = naddr != null,
+ onClick = { naddr?.let(onClick) },
+ ),
+ text = highlight.text,
+ style = AppTheme.typography.bodyMedium.merge(
+ background = HighlightBackground,
+ color = Color.White,
+ fontSize = 16.sp,
+ lineBreak = LineBreak.Paragraph,
+ platformStyle = PlatformTextStyle(includeFontPadding = false),
+ lineHeight = 2.em,
+ lineHeightStyle = LineHeightStyle(
+ alignment = LineHeightStyle.Alignment.Center,
+ trim = LineHeightStyle.Trim.None,
+ ),
+ ),
+ )
+}
diff --git a/app/src/main/kotlin/net/primal/android/notes/repository/FeedResponseProcessor.kt b/app/src/main/kotlin/net/primal/android/notes/repository/FeedResponseProcessor.kt
index 1ff9b51c..20399dbb 100644
--- a/app/src/main/kotlin/net/primal/android/notes/repository/FeedResponseProcessor.kt
+++ b/app/src/main/kotlin/net/primal/android/notes/repository/FeedResponseProcessor.kt
@@ -3,6 +3,8 @@ package net.primal.android.notes.repository
import androidx.room.withTransaction
import net.primal.android.attachments.ext.flatMapPostsAsNoteAttachmentPO
import net.primal.android.core.ext.asMapByKey
+import net.primal.android.core.serialization.json.NostrJson
+import net.primal.android.core.serialization.json.decodeFromStringOrNull
import net.primal.android.db.PrimalDatabase
import net.primal.android.nostr.db.eventRelayHintsUpserter
import net.primal.android.nostr.ext.flatMapAsEventHintsPO
@@ -21,6 +23,7 @@ import net.primal.android.nostr.ext.mapNotNullAsRepostDataPO
import net.primal.android.nostr.ext.mapReferencedEventsAsArticleDataPO
import net.primal.android.nostr.ext.parseAndMapPrimalLegendProfiles
import net.primal.android.nostr.ext.parseAndMapPrimalUserNames
+import net.primal.android.nostr.model.NostrEvent
import net.primal.android.notes.api.model.FeedResponse
import net.primal.android.thread.db.ArticleCommentCrossRef
import net.primal.android.thread.db.NoteConversationCrossRef
@@ -62,7 +65,10 @@ suspend fun FeedResponse.persistToDatabaseAsTransaction(userId: String, database
videoThumbnails = videoThumbnails,
)
+ val refEvents = referencedEvents.mapNotNull { NostrJson.decodeFromStringOrNull(it.content) }
+
val noteNostrUris = allPosts.flatMapPostsAsNoteNostrUriPO(
+ eventIdToNostrEvent = refEvents.associateBy { it.id },
postIdToPostDataMap = allPosts.groupBy { it.postId }.mapValues { it.value.first() },
articleIdToArticle = allArticles.groupBy { it.articleId }.mapValues { it.value.first() },
profileIdToProfileDataMap = profileIdToProfileDataMap,
diff --git a/app/src/main/kotlin/net/primal/android/thread/articles/details/ArticleDetailsViewModel.kt b/app/src/main/kotlin/net/primal/android/thread/articles/details/ArticleDetailsViewModel.kt
index c221197d..cc8c03f7 100644
--- a/app/src/main/kotlin/net/primal/android/thread/articles/details/ArticleDetailsViewModel.kt
+++ b/app/src/main/kotlin/net/primal/android/thread/articles/details/ArticleDetailsViewModel.kt
@@ -58,7 +58,7 @@ class ArticleDetailsViewModel @Inject constructor(
private val zapHandler: ZapHandler,
) : ViewModel() {
- private val naddr = Nip19TLV.parseAsNaddr(savedStateHandle.naddrOrThrow)
+ private val naddr = Nip19TLV.parseUriAsNaddrOrNull(savedStateHandle.naddrOrThrow)
private val _state = MutableStateFlow(UiState(naddr = naddr))
val state = _state.asStateFlow()
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 750cb1e0..eb40710f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -439,6 +439,9 @@
Expires: %1$s
This invoice has expired
+ Mentioned note not found.
+ Mentioned event not found.
+
This is a Primal Premium feed.
Buy a Subscription to become a Nostr power user and support our work:
Get Primal Premium
diff --git a/app/src/test/kotlin/net/primal/android/nostr/utils/Nip19TLVTest.kt b/app/src/test/kotlin/net/primal/android/nostr/utils/Nip19TLVTest.kt
index ba275621..f0ac895b 100644
--- a/app/src/test/kotlin/net/primal/android/nostr/utils/Nip19TLVTest.kt
+++ b/app/src/test/kotlin/net/primal/android/nostr/utils/Nip19TLVTest.kt
@@ -67,7 +67,26 @@ class Nip19TLVTest {
}
@Test
- fun parseAsNaddr_returnsProperValuesForNaddr1() {
+ fun parseUriAsNaddrOrNull_returnsProperValuesForNaddr1Uri() {
+ val naddr = "nostr:naddr1qqw9x6rfwpcxjmn894fks6ts09shyepdg3ty6tthv4unxmf5qy28wumn8ghj7un9d3shjtnyv" +
+ "9kh2uewd9hsyg86np9a0kajstc8u9h846rmy6320wdepdeydfz8w8cv7kh9sqv02gpsgqqqw4rsgwawdk"
+
+ val expectedIdentifier = "Shipping-Shipyard-DVM-wey3m4"
+ val expectedRelays = listOf("wss://relay.damus.io")
+ val expectedProfileId = "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52"
+ val expectedKind = 30023
+
+ val result = Nip19TLV.parseUriAsNaddrOrNull(naddr)
+ result.shouldNotBeNull()
+
+ result.identifier shouldBe expectedIdentifier
+ result.relays shouldBe expectedRelays
+ result.userId shouldBe expectedProfileId
+ result.kind shouldBe expectedKind
+ }
+
+ @Test
+ fun parseUriAsNaddrOrNull_returnsProperValuesForNaddr1WithoutNostrScheme() {
val naddr = "naddr1qqw9x6rfwpcxjmn894fks6ts09shyepdg3ty6tthv4unxmf5qy28wumn8ghj7un9d3shjtnyv" +
"9kh2uewd9hsyg86np9a0kajstc8u9h846rmy6320wdepdeydfz8w8cv7kh9sqv02gpsgqqqw4rsgwawdk"
@@ -76,7 +95,7 @@ class Nip19TLVTest {
val expectedProfileId = "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52"
val expectedKind = 30023
- val result = Nip19TLV.parseAsNaddr(naddr)
+ val result = Nip19TLV.parseUriAsNaddrOrNull(naddr)
result.shouldNotBeNull()
result.identifier shouldBe expectedIdentifier
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 805b7cbe..1cdfbf38 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -22,7 +22,7 @@ detekt = "1.23.6"
emoji2-emojipicker = "1.5.0"
espresso-core = "3.6.1"
flippable = "1.5.4"
-guava = "33.0.0-android"
+guava = "33.0.0-android" # do not upgrade (added with zap settings, should be removed)
hilt = "1.2.0"
jetbrains-markdown = "0.7.3"
junit = "4.13.2"
@@ -36,7 +36,7 @@ ksp = "2.0.21-1.0.25"
ktlint_compose_rules = "0.3.17"
lifecycle = "2.8.7"
material-icons = "1.7.5"
-media3-exoplayer = "1.4.1"
+media3-exoplayer = "1.5.0"
mockk = "1.13.12"
okhttp = "4.12.0"
paging = "3.3.4"