Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Kind 20 support for picture notes posted from Olas #1753

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
668532c
updating build settings
rabble Feb 2, 2025
fe9c2dc
feat: Add NIP-68 picture-first event support to JSONEvent and NoteCard
rabble Feb 2, 2025
a0185af
feat: Add picturePost event kind support for NIP-68
rabble Feb 2, 2025
1ea55de
fix: Add pictureFirst case to CardStyle and update NoteCard comparison
rabble Feb 2, 2025
bc29168
fix: Handle pictureFirst case in CardButtonStyle cornerRadius switch
rabble Feb 2, 2025
171b28d
fix: Add missing .pictureFirst case and correct event kind comparison
rabble Feb 2, 2025
77869d4
refactor: Update note kind comparison to use EventKind.picturePost.ra…
rabble Feb 2, 2025
a3fc16f
refactor: Extract picture-first note card to separate component
rabble Feb 2, 2025
7190e29
refactor: Remove unnecessary blank lines in PictureFirstNoteCard view
rabble Feb 2, 2025
1a7c100
fix: Handle optional tags and type casting in PictureFirstNoteCard
rabble Feb 2, 2025
3a576fa
fix: Correct nil content check in PictureFirstNoteCard view
rabble Feb 2, 2025
9e1da15
refactor: Improve optional content handling in PictureFirstNoteCard
rabble Feb 2, 2025
f608889
feat: Automatically include kind 20 picture-first events when request…
rabble Feb 2, 2025
7d3c9c8
feat: Add logging for kind 20 events in RelayService
rabble Feb 2, 2025
06fab06
fix: Correct log statement syntax in RelayService
rabble Feb 2, 2025
25dd772
first working version supporting kind 20 events
rabble Feb 3, 2025
d38b92f
fixing the layout a bit
rabble Feb 3, 2025
8256714
feat: Add support for NIP-71 video events (kinds 21 and 22)
rabble Feb 3, 2025
85d69ba
adding support for displaying kind 20 events, kind 21, and 22 aren't …
rabble Feb 4, 2025
4d012f2
adding change log
rabble Feb 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,4 @@ fastlane/test_output

.idea
.vscode
.aider*
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
- Added support for displaying kind 20 picture notes generated by Olas.

### Release Notes
- Fixed: adding/removing relays not reflected on feed filter. [#119](https://github.com/verse-pbc/issues/issues/119)
Expand Down
32 changes: 18 additions & 14 deletions Nos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@
04F16AA72CBDBD91003AD693 /* DeleteConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F16AA62CBDBD91003AD693 /* DeleteConfirmationView.swift */; };
2D06BB9D2AE249D70085F509 /* ThreadRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D06BB9C2AE249D70085F509 /* ThreadRootView.swift */; };
2D4010A22AD87DF300F93AD4 /* KnownFollowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4010A12AD87DF300F93AD4 /* KnownFollowersView.swift */; };
2DC910682D50AAFF0065C468 /* PictureNoteCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC910672D50AAFF0065C468 /* PictureNoteCard.swift */; };
2DC9106C2D50AB300065C468 /* VideoNoteCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC9106B2D50AB2B0065C468 /* VideoNoteCard.swift */; };
3A1C296F2B2A537C0020B753 /* Moderation.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 3A1C296E2B2A537C0020B753 /* Moderation.xcstrings */; };
3A67449C2B294712002B8DE0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 3A67449B2B294712002B8DE0 /* Localizable.xcstrings */; };
3AAB61B52B24CD0000717A07 /* Date+ElapsedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB61B42B24CD0000717A07 /* Date+ElapsedTests.swift */; };
Expand Down Expand Up @@ -759,6 +761,8 @@
2D06BB9C2AE249D70085F509 /* ThreadRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadRootView.swift; sourceTree = "<group>"; };
2D3C71A52CEE6F7100625BCB /* Nos 20.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Nos 20.xcdatamodel"; sourceTree = "<group>"; };
2D4010A12AD87DF300F93AD4 /* KnownFollowersView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KnownFollowersView.swift; sourceTree = "<group>"; };
2DC910672D50AAFF0065C468 /* PictureNoteCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PictureNoteCard.swift; sourceTree = "<group>"; };
2DC9106B2D50AB2B0065C468 /* VideoNoteCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoNoteCard.swift; sourceTree = "<group>"; };
3A1C296E2B2A537C0020B753 /* Moderation.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Moderation.xcstrings; sourceTree = "<group>"; };
3A67449B2B294712002B8DE0 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
3AAB61B42B24CD0000717A07 /* Date+ElapsedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+ElapsedTests.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1434,6 +1438,8 @@
03618C6D2C8267E600BCBC55 /* Note */ = {
isa = PBXGroup;
children = (
2DC910672D50AAFF0065C468 /* PictureNoteCard.swift */,
2DC9106B2D50AB2B0065C468 /* VideoNoteCard.swift */,
C9DFA96A299BEE2C006929C1 /* CompactNoteView.swift */,
C9DFA964299BEB96006929C1 /* NoteCard.swift */,
C974652D2A3B86600031226F /* NoteCardHeader.swift */,
Expand Down Expand Up @@ -2686,6 +2692,7 @@
0326346D2C10C2FD00E489B5 /* FileStorageServerInfoResponseJSON.swift in Sources */,
5B6136462C348A5100ADD9C3 /* RepliesDisplayType.swift in Sources */,
5BE281C72AE2CCD800880466 /* ReplyButton.swift in Sources */,
2DC9106C2D50AB300065C468 /* VideoNoteCard.swift in Sources */,
C9DFA966299BEB96006929C1 /* NoteCard.swift in Sources */,
C98651102B0BD49200597B68 /* PagedNoteListView.swift in Sources */,
C9E37E152A1E8143003D4B0A /* ReportTarget.swift in Sources */,
Expand All @@ -2694,6 +2701,7 @@
045EDD052CAC025700B67964 /* ScrollViewProxy+Animate.swift in Sources */,
CD09A74629A50F750063464F /* SideMenuContent.swift in Sources */,
030E56F32CC2836D00A4A51E /* CopyKeyView.swift in Sources */,
2DC910682D50AAFF0065C468 /* PictureNoteCard.swift in Sources */,
C9DFA971299BF8CD006929C1 /* NoteView.swift in Sources */,
033E940B2D08F14900C6FB03 /* AuthorList+CoreDataClass.swift in Sources */,
037071272C90C5FA00BEAEC4 /* OpenGraphService.swift in Sources */,
Expand Down Expand Up @@ -3034,12 +3042,11 @@
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = accent;
CODE_SIGN_ENTITLEMENTS = Nos/NosStaging.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: Verse Communications, Inc. (GZCZBKH7MY)";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 224;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = GZCZBKH7MY;
DEVELOPMENT_TEAM = GZCZBKH7MY;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
Expand Down Expand Up @@ -3072,7 +3079,6 @@
PRODUCT_MODULE_NAME = Nos;
PRODUCT_NAME = "$(TARGET_NAME) Staging";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.verse.Nos-staging";
SCHEME_PREFIX = "nos-staging";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand Down Expand Up @@ -3489,11 +3495,11 @@
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = accent;
CODE_SIGN_ENTITLEMENTS = Nos/Nos.entitlements;
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 224;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = GZCZBKH7MY;
DEVELOPMENT_TEAM = GZCZBKH7MY;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
Expand Down Expand Up @@ -3525,7 +3531,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.verse.Nos;
PRODUCT_MODULE_NAME = Nos;
PRODUCT_NAME = "$(TARGET_NAME)";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.verse.Nos";
PROVISIONING_PROFILE_SPECIFIER = "";
SCHEME_PREFIX = nos;
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand All @@ -3546,12 +3552,11 @@
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = accent;
CODE_SIGN_ENTITLEMENTS = Nos/Nos.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: Verse Communications, Inc. (GZCZBKH7MY)";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 224;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = GZCZBKH7MY;
DEVELOPMENT_TEAM = GZCZBKH7MY;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
Expand Down Expand Up @@ -3583,7 +3588,6 @@
PRODUCT_MODULE_NAME = Nos;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.verse.Nos";
SCHEME_PREFIX = nos;
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand Down
1 change: 1 addition & 0 deletions Nos.xcodeproj/xcshareddata/xcschemes/Nos.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
buildConfiguration = "Dev"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableThreadSanitizer = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
2 changes: 1 addition & 1 deletion Nos/Models/CoreData/Author+CoreDataClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ import Logger
}

@nonobjc func postsPredicate(before: Date) -> NSPredicate {
let onlyRootPostsClause = "(kind = 1 AND SUBQUERY(" +
let onlyRootPostsClause = "(kind = 1 or kind = 20 AND SUBQUERY(" +
"eventReferences, " +
"$reference, " +
"$reference.marker = 'root' OR $reference.marker = 'reply' OR $reference.marker = nil" +
Expand Down
6 changes: 6 additions & 0 deletions Nos/Models/CoreData/Event+Fetching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -354,11 +354,17 @@ extension Event {
)
])
let kind6Predicate = NSPredicate(format: "kind = 6")
let kind20Predicate = NSPredicate(format: "kind = 20")
let kind21Predicate = NSPredicate(format: "kind = 21")
let kind22Predicate = NSPredicate(format: "kind = 22")
let kind30023Predicate = NSPredicate(format: "kind = 30023")

let kindsPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [
kind1Predicate,
kind6Predicate,
kind20Predicate,
kind21Predicate,
kind22Predicate,
kind30023Predicate
])

Expand Down
13 changes: 11 additions & 2 deletions Nos/Models/EventKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,16 @@ public enum EventKind: Int64, CaseIterable, Hashable {

/// Direct Message
case directMessageRumor = 14


/// Picture Post (NIP-68)
case picturePost = 20

/// Regular Video Post (NIP-71)
case video = 21

/// Short-form Video Post (NIP-71)
case shortVideo = 22

/// Channel Message
case channelMessage = 42

Expand Down Expand Up @@ -70,6 +79,6 @@ public enum EventKind: Int64, CaseIterable, Hashable {

/// Long-form Content
case longFormContent = 30023

// swiftlint:enable number_separator
}
138 changes: 138 additions & 0 deletions Nos/Models/JSONEvent+Kinds.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,90 @@ extension JSONEvent {
)
}

/// An event that represents a picture-first post (NIP-68).
/// - Parameters:
/// - pubKey: The public key of the event creator.
/// - title: The title of the post.
/// - description: The description/content of the post.
/// - imageMetadata: Array of image metadata including URLs and attributes.
/// - tags: Additional tags like content warnings, location, etc.
/// - Returns: The ``JSONEvent`` representing the picture post.
static func picturePost(
pubKey: String,
title: String,
description: String,
imageMetadata: [[String]],
tags: [[String]] = []
) -> JSONEvent {
var allTags = [["title", title]]
allTags.append(contentsOf: imageMetadata)
allTags.append(contentsOf: tags)

return JSONEvent(
pubKey: pubKey,
kind: .picturePost,
tags: allTags,
content: description
)
}

/// An event that represents a video post (NIP-71).
/// - Parameters:
/// - pubKey: The public key of the event creator.
/// - title: The title of the video.
/// - description: A summary or description of the video content.
/// - isShortForm: If true, creates a short video event (kind 22), otherwise a normal video event (kind 21).
/// - publishedAt: Optional Unix timestamp (in seconds) when the video was first published.
/// - duration: Optional duration of the video in seconds.
/// - videoMetadata: An array of "imeta" tags describing the video sources, dimensions, preview images, etc.
/// - contentWarning: Optional warning regarding the video content.
/// - altText: Optional accessibility description for the video.
/// - tags: Additional tags (e.g. text-track, segment, hashtags, participants, reference links) to include.
/// - Returns: A JSONEvent representing the video post.
static func videoPost(
pubKey: String,
title: String,
description: String,
isShortForm: Bool = false,
publishedAt: Int? = nil,
duration: Int? = nil,
videoMetadata: [[String]],
contentWarning: String? = nil,
altText: String? = nil,
tags: [[String]] = []
) -> JSONEvent {
var allTags = [["title", title]]

if let publishedAt = publishedAt {
allTags.append(["published_at", String(publishedAt)])
}

if let duration = duration {
allTags.append(["duration", String(duration)])
}

// Append the video-specific metadata (imeta tags) – these carry the URL, dimensions, preview images, etc.
allTags.append(contentsOf: videoMetadata)

if let contentWarning = contentWarning {
allTags.append(["content-warning", contentWarning])
}

if let altText = altText {
allTags.append(["alt", altText])
}

// Append any additional tags provided.
allTags.append(contentsOf: tags)

return JSONEvent(
pubKey: pubKey,
kind: isShortForm ? .shortVideo : .video,
tags: allTags,
content: description
)
}

/// An event that represents a list of authors.
/// - Parameters:
/// - pubKey: The public key of the user making the request.
Expand Down Expand Up @@ -82,4 +166,58 @@ extension JSONEvent {
content: ""
)
}

/// Creates a video event (NIP-71)
/// - Parameters:
/// - pubKey: The public key of the event creator
/// - title: Title of the video
/// - description: Summary or description of the video content
/// - isShortForm: If true, creates a kind 22 (short-form) video event, otherwise kind 21
/// - publishedAt: Optional timestamp when video was first published
/// - duration: Optional duration in seconds
/// - imageMetadata: Array of video metadata including URLs and attributes
/// - contentWarning: Optional content warning
/// - altText: Optional accessibility description
/// - tags: Additional tags like hashtags, participants etc
static func videoPost(
pubKey: String,
title: String,
description: String,
isShortForm: Bool = false,
publishedAt: Int? = nil,
duration: Int? = nil,
imageMetadata: [[String]],
contentWarning: String? = nil,
altText: String? = nil,
tags: [[String]] = []
) -> JSONEvent {
var allTags = [["title", title]]

if let publishedAt {
allTags.append(["published_at", String(publishedAt)])
}

if let duration {
allTags.append(["duration", String(duration)])
}

allTags.append(contentsOf: imageMetadata)

if let contentWarning {
allTags.append(["content-warning", contentWarning])
}

if let altText {
allTags.append(["alt", altText])
}

allTags.append(contentsOf: tags)

return JSONEvent(
pubKey: pubKey,
kind: isShortForm ? .shortVideo : .video,
tags: allTags,
content: description
)
}
}
6 changes: 1 addition & 5 deletions Nos/Service/Relay/Filter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,7 @@ struct Filter: Hashable, Identifiable {
if !eventIDs.isEmpty {
filterDict["ids"] = eventIDs
}

if !kinds.isEmpty {
filterDict["kinds"] = kinds.map({ $0.rawValue })
}


if !dTags.isEmpty {
filterDict["#d"] = dTags
}
Expand Down
5 changes: 2 additions & 3 deletions Nos/Views/Components/Button/NoteButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,11 @@ struct NoteButton: View {
}
.buttonStyle(CardButtonStyle(style: style))

let buttonOrLabel = Group {
let buttonOrLabel: some View = SwiftUI.Group {
if isTapEnabled {
button
} else {
buttonLabel
.mimicCardButtonStyle()
buttonLabel.mimicCardButtonStyle()
}
}

Expand Down
Loading