WIP NIP-A0 voice messages

This commit is contained in:
2025-11-23 13:04:05 -06:00
parent 719a0c8cb0
commit ed749c5879
13 changed files with 159 additions and 16 deletions

View File

@@ -33,6 +33,10 @@
3A515C542DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */; }; 3A515C542DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */; };
3A515C552DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */; }; 3A515C552DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */; };
3A515C562DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */; }; 3A515C562DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */; };
3A7379D42E5D5C4A00DF8B4E /* DSWaveformImageViews in Frameworks */ = {isa = PBXBuildFile; productRef = 3A7379D32E5D5C4A00DF8B4E /* DSWaveformImageViews */; };
3A7379D72E5D5E1900DF8B4E /* DamusAudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7379D62E5D5E1900DF8B4E /* DamusAudioPlayerView.swift */; };
3A7379D82E5D5E1900DF8B4E /* DamusAudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7379D62E5D5E1900DF8B4E /* DamusAudioPlayerView.swift */; };
3A7379D92E5D5E1900DF8B4E /* DamusAudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7379D62E5D5E1900DF8B4E /* DamusAudioPlayerView.swift */; };
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; }; 3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
3A92C0FE2DE16E9800CEEBAC /* FaviconCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */; }; 3A92C0FE2DE16E9800CEEBAC /* FaviconCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */; };
3A92C0FF2DE16E9800CEEBAC /* FaviconCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */; }; 3A92C0FF2DE16E9800CEEBAC /* FaviconCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */; };
@@ -1923,6 +1927,7 @@
3A66D927299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 3A66D927299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3A66D928299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; }; 3A66D928299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
3A66D929299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 3A66D929299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
3A7379D62E5D5E1900DF8B4E /* DamusAudioPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusAudioPlayerView.swift; sourceTree = "<group>"; };
3A821C3E29E819D500B4BCA7 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; }; 3A821C3E29E819D500B4BCA7 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
3A821C3F29E819D500B4BCA7 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 3A821C3F29E819D500B4BCA7 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3A821C4029E819D500B4BCA7 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 3A821C4029E819D500B4BCA7 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@@ -2731,6 +2736,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
3ACF94382DA9A52F00971A4E /* FaviconFinder in Frameworks */, 3ACF94382DA9A52F00971A4E /* FaviconFinder in Frameworks */,
3A7379D42E5D5C4A00DF8B4E /* DSWaveformImageViews in Frameworks */,
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */, 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
D7DB1FE42D5A9AC900CF06DA /* CryptoSwift in Frameworks */, D7DB1FE42D5A9AC900CF06DA /* CryptoSwift in Frameworks */,
3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */, 3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */,
@@ -2824,6 +2830,14 @@
path = Tips; path = Tips;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
3A7379D52E5D5DF000DF8B4E /* Audio */ = {
isa = PBXGroup;
children = (
3A7379D62E5D5E1900DF8B4E /* DamusAudioPlayerView.swift */,
);
path = Audio;
sourceTree = "<group>";
};
3AA24800297E3DAE0090C62D /* Reposts */ = { 3AA24800297E3DAE0090C62D /* Reposts */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -4392,6 +4406,7 @@
children = ( children = (
5C78A79D2E303D2600CF177D /* Models */, 5C78A79D2E303D2600CF177D /* Models */,
4CFF8F6129CC9A80008DB934 /* Images */, 4CFF8F6129CC9A80008DB934 /* Images */,
3A7379D52E5D5DF000DF8B4E /* Audio */,
4C1A9A2829DDF53B00516EAC /* Video */, 4C1A9A2829DDF53B00516EAC /* Video */,
BA3759952ABCCF360018D73B /* Camera */, BA3759952ABCCF360018D73B /* Camera */,
4C198DEA29F88C6B004C165C /* BlurHash */, 4C198DEA29F88C6B004C165C /* BlurHash */,
@@ -5060,6 +5075,7 @@
D7C48C0A2D12DE0C00A3BACF /* SwiftyCrop */, D7C48C0A2D12DE0C00A3BACF /* SwiftyCrop */,
D7DB1FE32D5A9AC900CF06DA /* CryptoSwift */, D7DB1FE32D5A9AC900CF06DA /* CryptoSwift */,
3ACF94372DA9A52F00971A4E /* FaviconFinder */, 3ACF94372DA9A52F00971A4E /* FaviconFinder */,
3A7379D32E5D5C4A00DF8B4E /* DSWaveformImageViews */,
); );
productName = damus; productName = damus;
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */; productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
@@ -5271,6 +5287,7 @@
D7C48C092D12DE0C00A3BACF /* XCRemoteSwiftPackageReference "SwiftyCrop" */, D7C48C092D12DE0C00A3BACF /* XCRemoteSwiftPackageReference "SwiftyCrop" */,
D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */, D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */,
3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */, 3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */,
3A7379D22E5D5C4A00DF8B4E /* XCRemoteSwiftPackageReference "DSWaveformImage" */,
); );
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */; productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
projectDirPath = ""; projectDirPath = "";
@@ -5805,6 +5822,7 @@
E0EE9DD42B8E5FEA00F3002D /* ImageProcessing.swift in Sources */, E0EE9DD42B8E5FEA00F3002D /* ImageProcessing.swift in Sources */,
4CB883B0297705DD00DC99E7 /* NoteZapButton.swift in Sources */, 4CB883B0297705DD00DC99E7 /* NoteZapButton.swift in Sources */,
D7DF58342DFCF18D00E9AD28 /* SendPaymentView.swift in Sources */, D7DF58342DFCF18D00E9AD28 /* SendPaymentView.swift in Sources */,
3A7379D72E5D5E1900DF8B4E /* DamusAudioPlayerView.swift in Sources */,
4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */, 4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */,
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */, 4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
4C32B9502A9AD44700DC3548 /* FlatBufferBuilder.swift in Sources */, 4C32B9502A9AD44700DC3548 /* FlatBufferBuilder.swift in Sources */,
@@ -6095,6 +6113,7 @@
82D6FB092CD99F7900C925F4 /* SearchHeaderView.swift in Sources */, 82D6FB092CD99F7900C925F4 /* SearchHeaderView.swift in Sources */,
82D6FB0A2CD99F7900C925F4 /* DamusGradient.swift in Sources */, 82D6FB0A2CD99F7900C925F4 /* DamusGradient.swift in Sources */,
D7DB93052D66A44100DA1EE5 /* Undistractor.swift in Sources */, D7DB93052D66A44100DA1EE5 /* Undistractor.swift in Sources */,
3A7379D92E5D5E1900DF8B4E /* DamusAudioPlayerView.swift in Sources */,
82D6FB0C2CD99F7900C925F4 /* GoldSupportGradient.swift in Sources */, 82D6FB0C2CD99F7900C925F4 /* GoldSupportGradient.swift in Sources */,
82D6FB0D2CD99F7900C925F4 /* PinkGradient.swift in Sources */, 82D6FB0D2CD99F7900C925F4 /* PinkGradient.swift in Sources */,
82D6FB0E2CD99F7900C925F4 /* GrayGradient.swift in Sources */, 82D6FB0E2CD99F7900C925F4 /* GrayGradient.swift in Sources */,
@@ -6780,6 +6799,7 @@
D73E5F092C6A97F4007EB227 /* ProfilePicView.swift in Sources */, D73E5F092C6A97F4007EB227 /* ProfilePicView.swift in Sources */,
D73E5F0A2C6A97F4007EB227 /* ProfileView.swift in Sources */, D73E5F0A2C6A97F4007EB227 /* ProfileView.swift in Sources */,
D73E5F0B2C6A97F4007EB227 /* ProfileNameView.swift in Sources */, D73E5F0B2C6A97F4007EB227 /* ProfileNameView.swift in Sources */,
3A7379D82E5D5E1900DF8B4E /* DamusAudioPlayerView.swift in Sources */,
D73E5F0C2C6A97F4007EB227 /* MaybeAnonPfpView.swift in Sources */, D73E5F0C2C6A97F4007EB227 /* MaybeAnonPfpView.swift in Sources */,
D73E5F0D2C6A97F4007EB227 /* EventProfileName.swift in Sources */, D73E5F0D2C6A97F4007EB227 /* EventProfileName.swift in Sources */,
D73E5F0E2C6A97F4007EB227 /* FriendIcon.swift in Sources */, D73E5F0E2C6A97F4007EB227 /* FriendIcon.swift in Sources */,
@@ -7922,6 +7942,14 @@
minimumVersion = 0.2.0; minimumVersion = 0.2.0;
}; };
}; };
3A7379D22E5D5C4A00DF8B4E /* XCRemoteSwiftPackageReference "DSWaveformImage" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/dmrschmidt/DSWaveformImage";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 14.2.2;
};
};
3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */ = { 3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/will-lumley/FaviconFinder.git"; repositoryURL = "https://github.com/will-lumley/FaviconFinder.git";
@@ -8010,6 +8038,11 @@
package = 3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */; package = 3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */;
productName = EmojiPicker; productName = EmojiPicker;
}; };
3A7379D32E5D5C4A00DF8B4E /* DSWaveformImageViews */ = {
isa = XCSwiftPackageProductDependency;
package = 3A7379D22E5D5C4A00DF8B4E /* XCRemoteSwiftPackageReference "DSWaveformImage" */;
productName = DSWaveformImageViews;
};
3ACF94372DA9A52F00971A4E /* FaviconFinder */ = { 3ACF94372DA9A52F00971A4E /* FaviconFinder */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */; package = 3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */;

View File

@@ -1,5 +1,5 @@
{ {
"originHash" : "1fc7e0b44329ba72cd285eeb022b5b92582cd01586b920d243cb0485c2e69dcc", "originHash" : "8d71e78d1d7bdc5a85a38932a14f84af755a9e34aeab19f9d540bd11a7b32fbc",
"pins" : [ "pins" : [
{ {
"identity" : "codescanner", "identity" : "codescanner",
@@ -17,6 +17,15 @@
"revision" : "e74bbbfbef939224b242ae7c342a90e60b88b5ce" "revision" : "e74bbbfbef939224b242ae7c342a90e60b88b5ce"
} }
}, },
{
"identity" : "dswaveformimage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/dmrschmidt/DSWaveformImage",
"state" : {
"revision" : "4c56578ee10128ee2b2c04c9c5aa73812de722db",
"version" : "14.2.2"
}
},
{ {
"identity" : "emojikit", "identity" : "emojikit",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@@ -18,6 +18,7 @@ enum NostrKind: UInt32, Codable {
case boost = 6 case boost = 6
case like = 7 case like = 7
case chat = 42 case chat = 42
case voice_message = 1222
case mute_list = 10000 case mute_list = 10000
case relay_list = 10002 case relay_list = 10002
case interest_list = 10015 case interest_list = 10015

View File

@@ -62,7 +62,7 @@ class LoadableNostrEventViewModel: ObservableObject {
guard let ev = await self.loadEvent(noteId: note_id) else { return .not_found } guard let ev = await self.loadEvent(noteId: note_id) else { return .not_found }
guard let known_kind = ev.known_kind else { return .unknown_or_unsupported_kind } guard let known_kind = ev.known_kind else { return .unknown_or_unsupported_kind }
switch known_kind { switch known_kind {
case .text, .highlight: case .text, .highlight, .voice_message:
return .loaded(route: Route.Thread(thread: ThreadModel(event: ev, damus_state: damus_state))) return .loaded(route: Route.Thread(thread: ThreadModel(event: ev, damus_state: damus_state)))
case .dm: case .dm:
let dm_model = damus_state.dms.lookup_or_create(ev.pubkey) let dm_model = damus_state.dms.lookup_or_create(ev.pubkey)

View File

@@ -319,6 +319,8 @@ func classify_url(_ url: URL) -> UrlType {
return .media(.image(url)) return .media(.image(url))
case "mp4", "mov", "m3u8": case "mp4", "mov", "m3u8":
return .media(.video(url)) return .media(.video(url))
case "m4a":
return .media(.audio(url))
default: default:
return .link(url) return .link(url)
} }
@@ -452,6 +454,8 @@ enum UrlType {
return url return url
case .video(let url): case .video(let url):
return url return url
case .audio(let url):
return url
} }
case .link(let url): case .link(let url):
return url return url
@@ -462,7 +466,7 @@ enum UrlType {
switch self { switch self {
case .media(let media_url): case .media(let media_url):
switch media_url { switch media_url {
case .image: case .image, .audio:
return nil return nil
case .video(let url): case .video(let url):
return url return url
@@ -478,14 +482,28 @@ enum UrlType {
switch media_url { switch media_url {
case .image(let url): case .image(let url):
return url return url
case .video: case .video, .audio:
return nil return nil
} }
case .link: case .link:
return nil return nil
} }
} }
var is_audio: URL? {
switch self {
case .media(let media_url):
switch media_url {
case .audio(let url):
return url
case .image, .video:
return nil
}
case .link:
return nil
}
}
var is_link: URL? { var is_link: URL? {
switch self { switch self {
case .media: case .media:
@@ -508,13 +526,16 @@ enum UrlType {
enum MediaUrl { enum MediaUrl {
case image(URL) case image(URL)
case video(URL) case video(URL)
case audio(URL)
var url: URL { var url: URL {
switch self { switch self {
case .image(let url): case .image(let url):
return url return url
case .video(let url): case .video(let url):
return url return url
case .audio(let url):
return url
} }
} }
} }

View File

@@ -253,7 +253,7 @@ struct NoteContentView: View {
Divider() Divider()
.frame(height: 1) .frame(height: 1)
switch artifacts.media[index] { switch artifacts.media[index] {
case .image(let url), .video(let url): case .image(let url), .video(let url), .audio(let url):
Text(abbreviateURL(url)) Text(abbreviateURL(url))
.font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size)) .font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size))
.foregroundStyle(DamusColors.neutral6) .foregroundStyle(DamusColors.neutral6)
@@ -477,7 +477,7 @@ struct BlurOverlayView: View {
let damus_state = damus_state let damus_state = damus_state
{ {
switch artifacts.media[0] { switch artifacts.media[0] {
case .image(let url), .video(let url): case .image(let url), .video(let url), .audio(let url):
Text(abbreviateURL(url, maxLength: 30)) Text(abbreviateURL(url, maxLength: 30))
.font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size * 0.8)) .font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size * 0.8))
.foregroundStyle(.white) .foregroundStyle(.white)

View File

@@ -75,7 +75,7 @@ class ProfileModel: ObservableObject, Equatable {
} }
func subscribe() { func subscribe() {
var text_filter = NostrFilter(kinds: [.text, .longform, .highlight]) var text_filter = NostrFilter(kinds: [.text, .longform, .highlight, .voice_message])
var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost]) var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost])
var relay_list_filter = NostrFilter(kinds: [.relay_list], authors: [pubkey]) var relay_list_filter = NostrFilter(kinds: [.relay_list], authors: [pubkey])
@@ -98,7 +98,7 @@ class ProfileModel: ObservableObject, Equatable {
return return
} }
let conversation_kinds: [NostrKind] = [.text, .longform, .highlight] let conversation_kinds: [NostrKind] = [.text, .longform, .highlight, .voice_message]
let limit: UInt32 = 500 let limit: UInt32 = 500
let conversations_filter_them = NostrFilter(kinds: conversation_kinds, pubkeys: [damus.pubkey], limit: limit, authors: [pubkey]) let conversations_filter_them = NostrFilter(kinds: conversation_kinds, pubkeys: [damus.pubkey], limit: limit, authors: [pubkey])
let conversations_filter_us = NostrFilter(kinds: conversation_kinds, pubkeys: [pubkey], limit: limit, authors: [damus.pubkey]) let conversations_filter_us = NostrFilter(kinds: conversation_kinds, pubkeys: [pubkey], limit: limit, authors: [damus.pubkey])
@@ -122,7 +122,7 @@ class ProfileModel: ObservableObject, Equatable {
} }
private func add_event(_ ev: NostrEvent) { private func add_event(_ ev: NostrEvent) {
if ev.is_textlike || ev.known_kind == .boost { if ev.is_textlike || ev.known_kind == .boost || ev.known_kind == .voice_message {
if self.events.insert(ev) { if self.events.insert(ev) {
self.objectWillChange.send() self.objectWillChange.send()
} }

View File

@@ -123,7 +123,7 @@ struct ProfileView: View {
var filters = ContentFilters.defaults(damus_state: damus_state) var filters = ContentFilters.defaults(damus_state: damus_state)
filters.append(fstate.filter) filters.append(fstate.filter)
switch fstate { switch fstate {
case .posts, .posts_and_replies, .follow_list: case .posts, .posts_and_replies, .follow_list, .voice_messages:
filters.append({ profile.pubkey == $0.pubkey }) filters.append({ profile.pubkey == $0.pubkey })
case .conversations: case .conversations:
filters.append({ profile.conversation_events.contains($0.id) } ) filters.append({ profile.conversation_events.contains($0.id) } )
@@ -438,7 +438,8 @@ struct ProfileView: View {
var tabs: [(String, FilterState)] { var tabs: [(String, FilterState)] {
var tabs = [ var tabs = [
(NSLocalizedString("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies)."), FilterState.posts), (NSLocalizedString("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies)."), FilterState.posts),
(NSLocalizedString("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes)."), FilterState.posts_and_replies) (NSLocalizedString("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes)."), FilterState.posts_and_replies),
(NSLocalizedString("Voice", comment: "Label for filter for seeing voice messages."), FilterState.voice_messages)
] ]
if profile.pubkey != damus_state.pubkey && !profile.conversation_events.isEmpty { if profile.pubkey != damus_state.pubkey && !profile.conversation_events.isEmpty {
tabs.append((NSLocalizedString("Conversations", comment: "Label for filter for seeing notes and replies that involve conversations between the signed in user and the current profile."), FilterState.conversations)) tabs.append((NSLocalizedString("Conversations", comment: "Label for filter for seeing notes and replies that involve conversations between the signed in user and the current profile."), FilterState.conversations))
@@ -469,6 +470,9 @@ struct ProfileView: View {
if filter_state == FilterState.posts_and_replies { if filter_state == FilterState.posts_and_replies {
InnerTimelineView(events: profile.events, damus: damus_state, filter: content_filter(FilterState.posts_and_replies)) InnerTimelineView(events: profile.events, damus: damus_state, filter: content_filter(FilterState.posts_and_replies))
} }
if filter_state == FilterState.voice_messages {
InnerTimelineView(events: profile.events, damus: damus_state, filter: content_filter(FilterState.voice_messages))
}
if filter_state == FilterState.conversations && !profile.conversation_events.isEmpty { if filter_state == FilterState.conversations && !profile.conversation_events.isEmpty {
InnerTimelineView(events: profile.events, damus: damus_state, filter: content_filter(FilterState.conversations)) InnerTimelineView(events: profile.events, damus: damus_state, filter: content_filter(FilterState.conversations))
} }

View File

@@ -14,6 +14,7 @@ enum FilterState : Int {
case posts_and_replies = 1 case posts_and_replies = 1
case conversations = 2 case conversations = 2
case follow_list = 3 case follow_list = 3
case voice_messages = 4
func filter(ev: NostrEvent) -> Bool { func filter(ev: NostrEvent) -> Bool {
switch self { switch self {
@@ -25,6 +26,8 @@ enum FilterState : Int {
return true return true
case .follow_list: case .follow_list:
return ev.known_kind == .follow_list return ev.known_kind == .follow_list
case .voice_messages:
return ev.known_kind == .voice_message
} }
} }
} }

View File

@@ -192,6 +192,8 @@ class HomeModel: ContactsDelegate {
switch kind { switch kind {
case .chat, .longform, .text, .highlight: case .chat, .longform, .text, .highlight:
handle_text_event(sub_id: sub_id, ev) handle_text_event(sub_id: sub_id, ev)
case .voice_message:
handle_voice_message_event(sub_id: sub_id, ev)
case .contacts: case .contacts:
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev) handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
case .metadata: case .metadata:
@@ -776,7 +778,28 @@ class HomeModel: ContactsDelegate {
handle_notification(ev: ev) handle_notification(ev: ev)
} }
} }
func handle_voice_message_event(sub_id: String, _ ev: NostrEvent) {
guard should_show_event(state: damus_state, ev: ev) else {
return
}
// TODO: will we need to process this in other places like zap request contents, etc?
process_image_metadatas(cache: damus_state.events, ev: ev)
damus_state.replies.count_replies(ev, keypair: self.damus_state.keypair)
damus_state.events.insert(ev)
if let quoted_event = ev.referenced_quote_ids.first {
handle_quote_repost_event(ev, target: quoted_event.note_id)
}
if sub_id == home_subid {
insert_home_event(ev)
} else if sub_id == notifications_subid {
handle_notification(ev: ev)
}
}
func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) { func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) {
notification_status.new_events = notifs notification_status.new_events = notifs

View File

@@ -0,0 +1,44 @@
//
// DamusAudioPlayerView.swift
// damus
//
// Created by Terry Yiu on 8/25/25.
//
import DSWaveformImageViews
import SwiftUI
struct DamusAudioPlayerView: View {
let remoteURL: URL
@State var localURL: URL
var body: some View {
GeometryReader { geometry in
WaveformView(audioURL: localURL) { shape in
shape.fill(.white)
shape.fill(.red).mask(alignment: .leading) {
Rectangle().frame(width: geometry.size.width * progress)
}
}
}
Text("Hello, World!")
.task {
URLSession.shared.downloadTask(with: remoteURL) { downloadedURL, urlResponse, error in
guard let downloadedURL = downloadedURL else { return }
let cachesFolderURL = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let audioFileURL = cachesFolderURL!.appendingPathComponent("yourLocalAudioFile.m4a")
try? FileManager.default.copyItem(at: downloadedURL, to: audioFileURL)
DispatchQueue.main.async {
self.localURL = audioFileURL
// self.WaveformView.waveformAudioURL = audioFileURL
}
}
}
}
#Preview {
DamusAudioPlayerView()
}

View File

@@ -57,7 +57,9 @@ struct ImageContainerView: View {
switch url { switch url {
case .image(let url): case .image(let url):
Img(url: url) Img(url: url)
case .video(let url): case .audio(let url):
DamusAudioPlayerView()
case .video(let url):
DamusVideoPlayerView(url: url, coordinator: video_coordinator, style: .no_controls(on_tap: nil)) DamusVideoPlayerView(url: url, coordinator: video_coordinator, style: .no_controls(on_tap: nil))
} }
} }

View File

@@ -223,7 +223,7 @@ class CarouselModel: ObservableObject {
private func observe_video_sizes() { private func observe_video_sizes() {
for media_url in urls { for media_url in urls {
switch media_url { switch media_url {
case .video(let url): case .video(let url), .audio(let url):
let video_player = damus_state.video.get_player(for: url) let video_player = damus_state.video.get_player(for: url)
if let video_size = video_player.video_size { if let video_size = video_player.video_size {
self.media_size_information[url] = video_size // Set the initial size if available self.media_size_information[url] = video_size // Set the initial size if available
@@ -302,6 +302,7 @@ class CarouselModel: ObservableObject {
struct ImageCarousel<Content: View>: View { struct ImageCarousel<Content: View>: View {
/// The event id of the note that this carousel is displaying /// The event id of the note that this carousel is displaying
let evid: NoteId let evid: NoteId
let event: NostrEvent
/// The model that holds information and state of this carousel /// The model that holds information and state of this carousel
/// This is observed to update the view when the model changes /// This is observed to update the view when the model changes
@ObservedObject var model: CarouselModel @ObservedObject var model: CarouselModel
@@ -354,6 +355,8 @@ struct ImageCarousel<Content: View>: View {
.onTapGesture { .onTapGesture {
present(full_screen_item: .full_screen_carousel(urls: model.urls, selectedIndex: $model.selectedIndex)) present(full_screen_item: .full_screen_carousel(urls: model.urls, selectedIndex: $model.selectedIndex))
} }
case .audio(let url):
DamusAudioPlayerView()
case .video(let url): case .video(let url):
let video_model = model.damus_state.video.get_player(for: url) let video_model = model.damus_state.video.get_player(for: url)
DamusVideoPlayerView( DamusVideoPlayerView(