Compare commits

..

27 Commits

Author SHA1 Message Date
ed749c5879 WIP NIP-A0 voice messages 2025-11-23 13:04:05 -06:00
Daniel D’Aquino
719a0c8cb0 Merge pull request #3209 from damus-io/translations
Translations
2025-08-25 18:53:36 -07:00
89ad22833d Reduce default zap amount and deduplicate from preset zap amount items
Changelog-Changed: Reduced default zap amount and deduplicated from preset zap amount items
Closes: https://github.com/damus-io/damus/issues/3198
Signed-off-by: Terry Yiu <git@tyiu.xyz>
2025-08-25 18:47:14 -07:00
transifex-integration[bot]
9407c75d60 Translate Localizable.strings in de
100% translated source file: 'Localizable.strings'
on 'de'.
2025-08-25 14:43:27 +00:00
transifex-integration[bot]
c4e6e5e6a7 Translate Localizable.strings in ja
100% translated source file: 'Localizable.strings'
on 'ja'.
2025-08-25 00:36:13 +00:00
transifex-integration[bot]
592e9f9405 Translate Localizable.strings in ja
100% translated source file: 'Localizable.strings'
on 'ja'.
2025-08-25 00:36:02 +00:00
transifex-integration[bot]
d924485bb3 Translate InfoPlist.strings in zh_CN
100% translated source file: 'InfoPlist.strings'
on 'zh_CN'.
2025-08-24 13:56:16 +00:00
transifex-integration[bot]
b774f28427 Translate Localizable.strings in th
100% translated source file: 'Localizable.strings'
on 'th'.
2025-08-24 01:22:28 +00:00
transifex-integration[bot]
deae6c0636 Translate Localizable.strings in pt_PT
100% translated source file: 'Localizable.strings'
on 'pt_PT'.
2025-08-23 19:40:14 +00:00
transifex-integration[bot]
da386f3bcd Translate Localizable.strings in nl
100% translated source file: 'Localizable.strings'
on 'nl'.
2025-08-23 17:57:15 +00:00
55dbb46bb5 Export strings for translation
Signed-off-by: Terry Yiu <git@tyiu.xyz>
2025-08-23 11:08:07 -04:00
transifex-integration[bot]
dc8e647c34 Translate Localizable.strings in pt_PT
100% translated source file: 'Localizable.strings'
on 'pt_PT'.
2025-08-23 11:05:14 -04:00
transifex-integration[bot]
eb25ff3584 Translate Localizable.strings in pt_PT
100% translated source file: 'Localizable.strings'
on 'pt_PT'.
2025-08-23 11:05:14 -04:00
transifex-integration[bot]
0ae03fc3f3 Translate Localizable.strings in pt_PT
100% translated source file: 'Localizable.strings'
on 'pt_PT'.
2025-08-23 11:05:14 -04:00
transifex-integration[bot]
e60f74eb9f Translate Localizable.strings in pt_PT
100% translated source file: 'Localizable.strings'
on 'pt_PT'.
2025-08-23 11:05:14 -04:00
transifex-integration[bot]
0d75f9cdd9 Translate Localizable.strings in pt_PT
100% translated source file: 'Localizable.strings'
on 'pt_PT'.
2025-08-23 11:05:14 -04:00
transifex-integration[bot]
33a3ddbfd6 Translate Localizable.stringsdict in pt_PT
100% translated source file: 'Localizable.stringsdict'
on 'pt_PT'.
2025-08-23 11:05:14 -04:00
transifex-integration[bot]
6555531846 Translate Localizable.strings in th
100% translated source file: 'Localizable.strings'
on 'th'.
2025-08-23 11:05:14 -04:00
transifex-integration[bot]
97b9d06774 Translate Localizable.strings in th
100% translated source file: 'Localizable.strings'
on 'th'.
2025-08-23 11:05:14 -04:00
transifex-integration[bot]
198448b114 Translate Localizable.strings in th
100% translated source file: 'Localizable.strings'
on 'th'.
2025-08-23 11:05:13 -04:00
transifex-integration[bot]
a0333058a6 Translate Localizable.strings in th
100% translated source file: 'Localizable.strings'
on 'th'.
2025-08-23 11:05:13 -04:00
transifex-integration[bot]
e640d5185e Translate Localizable.strings in th
100% translated source file: 'Localizable.strings'
on 'th'.
2025-08-23 11:05:13 -04:00
transifex-integration[bot]
9723718bc5 Translate Localizable.strings in th
100% translated source file: 'Localizable.strings'
on 'th'.
2025-08-23 11:05:13 -04:00
transifex-integration[bot]
08e19fd395 Translate Localizable.strings in th
100% translated source file: 'Localizable.strings'
on 'th'.
2025-08-23 11:05:13 -04:00
transifex-integration[bot]
7f39c3c4b2 Translate Localizable.strings in th
100% translated source file: 'Localizable.strings'
on 'th'.
2025-08-23 11:05:13 -04:00
transifex-integration[bot]
cd3314c068 Translate Localizable.strings in th
100% translated source file: 'Localizable.strings'
on 'th'.
2025-08-23 11:05:12 -04:00
f73c0ec1c4 Add support for scanning nprofile QR codes
Changelog-Added: Added support for scanning nprofile QR codes

Closes: https://github.com/damus-io/damus/issues/2671
Signed-off-by: Terry Yiu <git@tyiu.xyz>
2025-08-18 19:10:51 -07:00
25 changed files with 280 additions and 62 deletions

View File

@@ -33,6 +33,10 @@
3A515C542DF5371D002D3B34 /* 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 */; };
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 */; };
3A92C0FE2DE16E9800CEEBAC /* 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>"; };
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>"; };
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>"; };
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>"; };
@@ -2731,6 +2736,7 @@
buildActionMask = 2147483647;
files = (
3ACF94382DA9A52F00971A4E /* FaviconFinder in Frameworks */,
3A7379D42E5D5C4A00DF8B4E /* DSWaveformImageViews in Frameworks */,
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
D7DB1FE42D5A9AC900CF06DA /* CryptoSwift in Frameworks */,
3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */,
@@ -2824,6 +2830,14 @@
path = Tips;
sourceTree = "<group>";
};
3A7379D52E5D5DF000DF8B4E /* Audio */ = {
isa = PBXGroup;
children = (
3A7379D62E5D5E1900DF8B4E /* DamusAudioPlayerView.swift */,
);
path = Audio;
sourceTree = "<group>";
};
3AA24800297E3DAE0090C62D /* Reposts */ = {
isa = PBXGroup;
children = (
@@ -4392,6 +4406,7 @@
children = (
5C78A79D2E303D2600CF177D /* Models */,
4CFF8F6129CC9A80008DB934 /* Images */,
3A7379D52E5D5DF000DF8B4E /* Audio */,
4C1A9A2829DDF53B00516EAC /* Video */,
BA3759952ABCCF360018D73B /* Camera */,
4C198DEA29F88C6B004C165C /* BlurHash */,
@@ -5060,6 +5075,7 @@
D7C48C0A2D12DE0C00A3BACF /* SwiftyCrop */,
D7DB1FE32D5A9AC900CF06DA /* CryptoSwift */,
3ACF94372DA9A52F00971A4E /* FaviconFinder */,
3A7379D32E5D5C4A00DF8B4E /* DSWaveformImageViews */,
);
productName = damus;
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
@@ -5271,6 +5287,7 @@
D7C48C092D12DE0C00A3BACF /* XCRemoteSwiftPackageReference "SwiftyCrop" */,
D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */,
3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */,
3A7379D22E5D5C4A00DF8B4E /* XCRemoteSwiftPackageReference "DSWaveformImage" */,
);
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
projectDirPath = "";
@@ -5805,6 +5822,7 @@
E0EE9DD42B8E5FEA00F3002D /* ImageProcessing.swift in Sources */,
4CB883B0297705DD00DC99E7 /* NoteZapButton.swift in Sources */,
D7DF58342DFCF18D00E9AD28 /* SendPaymentView.swift in Sources */,
3A7379D72E5D5E1900DF8B4E /* DamusAudioPlayerView.swift in Sources */,
4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */,
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
4C32B9502A9AD44700DC3548 /* FlatBufferBuilder.swift in Sources */,
@@ -6095,6 +6113,7 @@
82D6FB092CD99F7900C925F4 /* SearchHeaderView.swift in Sources */,
82D6FB0A2CD99F7900C925F4 /* DamusGradient.swift in Sources */,
D7DB93052D66A44100DA1EE5 /* Undistractor.swift in Sources */,
3A7379D92E5D5E1900DF8B4E /* DamusAudioPlayerView.swift in Sources */,
82D6FB0C2CD99F7900C925F4 /* GoldSupportGradient.swift in Sources */,
82D6FB0D2CD99F7900C925F4 /* PinkGradient.swift in Sources */,
82D6FB0E2CD99F7900C925F4 /* GrayGradient.swift in Sources */,
@@ -6780,6 +6799,7 @@
D73E5F092C6A97F4007EB227 /* ProfilePicView.swift in Sources */,
D73E5F0A2C6A97F4007EB227 /* ProfileView.swift in Sources */,
D73E5F0B2C6A97F4007EB227 /* ProfileNameView.swift in Sources */,
3A7379D82E5D5E1900DF8B4E /* DamusAudioPlayerView.swift in Sources */,
D73E5F0C2C6A97F4007EB227 /* MaybeAnonPfpView.swift in Sources */,
D73E5F0D2C6A97F4007EB227 /* EventProfileName.swift in Sources */,
D73E5F0E2C6A97F4007EB227 /* FriendIcon.swift in Sources */,
@@ -7922,6 +7942,14 @@
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" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/will-lumley/FaviconFinder.git";
@@ -8010,6 +8038,11 @@
package = 3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */;
productName = EmojiPicker;
};
3A7379D32E5D5C4A00DF8B4E /* DSWaveformImageViews */ = {
isa = XCSwiftPackageProductDependency;
package = 3A7379D22E5D5C4A00DF8B4E /* XCRemoteSwiftPackageReference "DSWaveformImage" */;
productName = DSWaveformImageViews;
};
3ACF94372DA9A52F00971A4E /* FaviconFinder */ = {
isa = XCSwiftPackageProductDependency;
package = 3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */;

View File

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

View File

@@ -18,6 +18,7 @@ enum NostrKind: UInt32, Codable {
case boost = 6
case like = 7
case chat = 42
case voice_message = 1222
case mute_list = 10000
case relay_list = 10002
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 known_kind = ev.known_kind else { return .unknown_or_unsupported_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)))
case .dm:
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))
case "mp4", "mov", "m3u8":
return .media(.video(url))
case "m4a":
return .media(.audio(url))
default:
return .link(url)
}
@@ -452,6 +454,8 @@ enum UrlType {
return url
case .video(let url):
return url
case .audio(let url):
return url
}
case .link(let url):
return url
@@ -462,7 +466,7 @@ enum UrlType {
switch self {
case .media(let media_url):
switch media_url {
case .image:
case .image, .audio:
return nil
case .video(let url):
return url
@@ -478,14 +482,28 @@ enum UrlType {
switch media_url {
case .image(let url):
return url
case .video:
case .video, .audio:
return nil
}
case .link:
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? {
switch self {
case .media:
@@ -508,13 +526,16 @@ enum UrlType {
enum MediaUrl {
case image(URL)
case video(URL)
case audio(URL)
var url: URL {
switch self {
case .image(let url):
return url
case .video(let url):
return url
case .audio(let url):
return url
}
}
}

View File

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

View File

@@ -75,7 +75,7 @@ class ProfileModel: ObservableObject, Equatable {
}
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 relay_list_filter = NostrFilter(kinds: [.relay_list], authors: [pubkey])
@@ -98,7 +98,7 @@ class ProfileModel: ObservableObject, Equatable {
return
}
let conversation_kinds: [NostrKind] = [.text, .longform, .highlight]
let conversation_kinds: [NostrKind] = [.text, .longform, .highlight, .voice_message]
let limit: UInt32 = 500
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])
@@ -122,7 +122,7 @@ class ProfileModel: ObservableObject, Equatable {
}
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) {
self.objectWillChange.send()
}

View File

@@ -123,7 +123,7 @@ struct ProfileView: View {
var filters = ContentFilters.defaults(damus_state: damus_state)
filters.append(fstate.filter)
switch fstate {
case .posts, .posts_and_replies, .follow_list:
case .posts, .posts_and_replies, .follow_list, .voice_messages:
filters.append({ profile.pubkey == $0.pubkey })
case .conversations:
filters.append({ profile.conversation_events.contains($0.id) } )
@@ -438,7 +438,8 @@ struct ProfileView: View {
var tabs: [(String, FilterState)] {
var tabs = [
(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 {
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 {
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 {
InnerTimelineView(events: profile.events, damus: damus_state, filter: content_filter(FilterState.conversations))
}

View File

@@ -8,7 +8,7 @@
import Foundation
import UIKit
let fallback_zap_amount = 1000
let fallback_zap_amount = 21
let default_emoji_reactions = ["🤣", "🤙", "", "💜", "🔥", "😀", "😃", "😄", "🥶"]
func setting_property_key(key: String) -> String {

View File

@@ -14,6 +14,7 @@ enum FilterState : Int {
case posts_and_replies = 1
case conversations = 2
case follow_list = 3
case voice_messages = 4
func filter(ev: NostrEvent) -> Bool {
switch self {
@@ -25,6 +26,8 @@ enum FilterState : Int {
return true
case .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 {
case .chat, .longform, .text, .highlight:
handle_text_event(sub_id: sub_id, ev)
case .voice_message:
handle_voice_message_event(sub_id: sub_id, ev)
case .contacts:
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
case .metadata:
@@ -776,7 +778,28 @@ class HomeModel: ContactsDelegate {
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) {
notification_status.new_events = notifs

View File

@@ -8,6 +8,17 @@
import SwiftUI
import Combine
let zapAmounts: [Int: String] = [
69: "😘",
420: "🌿",
5000: "💜",
10_000: "😍",
20_000: "🤩",
50_000: "🔥",
100_000: "🚀",
1_000_000: "🤯",
]
struct ZapAmountItem: Identifiable, Hashable {
let amount: Int
let icon: String
@@ -22,19 +33,14 @@ func get_default_zap_amount_item(_ def: Int) -> ZapAmountItem {
}
func get_zap_amount_items(_ default_zap_amt: Int) -> [ZapAmountItem] {
let def_item = get_default_zap_amount_item(default_zap_amt)
var entries = [
ZapAmountItem(amount: 69, icon: "😘"),
ZapAmountItem(amount: 420, icon: "🌿"),
ZapAmountItem(amount: 5000, icon: "💜"),
ZapAmountItem(amount: 10_000, icon: "😍"),
ZapAmountItem(amount: 20_000, icon: "🤩"),
ZapAmountItem(amount: 50_000, icon: "🔥"),
ZapAmountItem(amount: 100_000, icon: "🚀"),
ZapAmountItem(amount: 1_000_000, icon: "🤯"),
]
entries.append(def_item)
var entries = zapAmounts.map { ZapAmountItem(amount: $0.key, icon: $0.value) }
// Add default zap amount to the list only if it is not one of the preset entries so that it is not duplicated.
if zapAmounts[default_zap_amt] == nil {
let def_item = get_default_zap_amount_item(default_zap_amt)
entries.append(def_item)
}
entries.sort { $0.amount < $1.amount }
return entries
}

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 {
case .image(let 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))
}
}

View File

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

Binary file not shown.

View File

@@ -62,6 +62,7 @@ Sentence composed of 2 variables to describe how many profiles a user is followi
Sentence composed of 2 variables to describe how many quoted reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.
Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.
Sentence composed of 2 variables to describe how many relays a note was found on. In source English, the first variable is the number of relays, and the second variable is 'Relay' or 'Relays'.
Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.</note>
</trans-unit>
@@ -645,11 +646,6 @@ Button label giving the user the option to close the sheet from which they were
<note>Button to connect to the relay.
Text for button to conect to Nostr Wallet Connect lightning wallet.</note>
</trans-unit>
<trans-unit id="Connect to Alby Wallet" xml:space="preserve">
<source>Connect to Alby Wallet</source>
<target>Connect to Alby Wallet</target>
<note>Button to attach an Alby Wallet, a service that provides a Lightning wallet for zapping sats. Alby is the name of the service and should not be translated.</note>
</trans-unit>
<trans-unit id="Connect to Coinos" xml:space="preserve">
<source>Connect to Coinos</source>
<target>Connect to Coinos</target>
@@ -2157,6 +2153,11 @@ Section title for deleting the user</note>
<target>Please contact the person who provided the link, and ask for another link.</target>
<note>User-visible tip on what to do if a link contains a deprecated "nrelay" reference.</note>
</trans-unit>
<trans-unit id="Please contact the person who provided the link, and ask for another link. Also, this link may have sensitive information, please use caution before sharing it." xml:space="preserve">
<source>Please contact the person who provided the link, and ask for another link. Also, this link may have sensitive information, please use caution before sharing it.</source>
<target>Please contact the person who provided the link, and ask for another link. Also, this link may have sensitive information, please use caution before sharing it.</target>
<note>User-visible tip on what to do if a link contains an unsupported "nsec" reference.</note>
</trans-unit>
<trans-unit id="Please copy the technical info and send it to our support team." xml:space="preserve">
<source>Please copy the technical info and send it to our support team.</source>
<target>Please copy the technical info and send it to our support team.</target>
@@ -3672,6 +3673,11 @@ User confirm Yes</note>
<target>You opened an invalid link. The link you tried to open refers to "nrelay", which has been deprecated and is not supported.</target>
<note>User-visible error description for a user who tries to open a deprecated "nrelay" link.</note>
</trans-unit>
<trans-unit id="You opened an invalid link. The link you tried to open refers to &quot;nsec&quot;, which is not supported." xml:space="preserve">
<source>You opened an invalid link. The link you tried to open refers to "nsec", which is not supported.</source>
<target>You opened an invalid link. The link you tried to open refers to "nsec", which is not supported.</target>
<note>User-visible error description for a user who tries to open an unsupported "nsec" link.</note>
</trans-unit>
<trans-unit id="You unlocked" xml:space="preserve">
<source>You unlocked</source>
<target>You unlocked</target>
@@ -4592,6 +4598,7 @@ Sentence composed of 2 variables to describe how many profiles a user is followi
Sentence composed of 2 variables to describe how many quoted reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.
Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.
Sentence composed of 2 variables to describe how many relays a note was found on. In source English, the first variable is the number of relays, and the second variable is 'Relay' or 'Relays'.
Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.</note>
</trans-unit>
@@ -5178,11 +5185,6 @@ Button label giving the user the option to close the view when no content is ava
<note>Button to connect to the relay.
Text for button to conect to Nostr Wallet Connect lightning wallet.</note>
</trans-unit>
<trans-unit id="Connect to Alby Wallet" xml:space="preserve">
<source>Connect to Alby Wallet</source>
<target state="new">Connect to Alby Wallet</target>
<note>Button to attach an Alby Wallet, a service that provides a Lightning wallet for zapping sats. Alby is the name of the service and should not be translated.</note>
</trans-unit>
<trans-unit id="Connect to Coinos" xml:space="preserve">
<source>Connect to Coinos</source>
<target state="new">Connect to Coinos</target>
@@ -6685,6 +6687,11 @@ Section title for deleting the user</note>
<target state="new">Please contact the person who provided the link, and ask for another link.</target>
<note>User-visible tip on what to do if a link contains a deprecated "nrelay" reference.</note>
</trans-unit>
<trans-unit id="Please contact the person who provided the link, and ask for another link. Also, this link may have sensitive information, please use caution before sharing it." xml:space="preserve">
<source>Please contact the person who provided the link, and ask for another link. Also, this link may have sensitive information, please use caution before sharing it.</source>
<target state="new">Please contact the person who provided the link, and ask for another link. Also, this link may have sensitive information, please use caution before sharing it.</target>
<note>User-visible tip on what to do if a link contains an unsupported "nsec" reference.</note>
</trans-unit>
<trans-unit id="Please copy the technical info and send it to our support team." xml:space="preserve">
<source>Please copy the technical info and send it to our support team.</source>
<target state="new">Please copy the technical info and send it to our support team.</target>
@@ -8195,6 +8202,11 @@ User confirm Yes</note>
<target state="new">You opened an invalid link. The link you tried to open refers to "nrelay", which has been deprecated and is not supported.</target>
<note>User-visible error description for a user who tries to open a deprecated "nrelay" link.</note>
</trans-unit>
<trans-unit id="You opened an invalid link. The link you tried to open refers to &quot;nsec&quot;, which is not supported." xml:space="preserve">
<source>You opened an invalid link. The link you tried to open refers to "nsec", which is not supported.</source>
<target state="new">You opened an invalid link. The link you tried to open refers to "nsec", which is not supported.</target>
<note>User-visible error description for a user who tries to open an unsupported "nsec" link.</note>
</trans-unit>
<trans-unit id="You unlocked" xml:space="preserve">
<source>You unlocked</source>
<target state="new">You unlocked</target>

View File

@@ -28,7 +28,7 @@
"comment" : "Amount of money required to publish to the Nostr relay. In English, this would look something like '10 sats / event', meaning it costs 10 sats to publish one event."
},
"%@ %@" : {
"comment" : "Sentence composed of 2 variables to describe how many imports were performed from loading a NostrScript. In source English, the first variable is the number of imports, and the second variable is 'Import' or 'Imports'.\nSentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.\nSentence composed of 2 variables to describe how many people are in the follow pack. In source English, the first variable is the number of users, and the second variable is 'user' or 'users'.\nSentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.\nSentence composed of 2 variables to describe how many quoted reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.\nSentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.\nSentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.\nSentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.\nSentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.",
"comment" : "Sentence composed of 2 variables to describe how many imports were performed from loading a NostrScript. In source English, the first variable is the number of imports, and the second variable is 'Import' or 'Imports'.\nSentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.\nSentence composed of 2 variables to describe how many people are in the follow pack. In source English, the first variable is the number of users, and the second variable is 'user' or 'users'.\nSentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.\nSentence composed of 2 variables to describe how many quoted reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.\nSentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.\nSentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.\nSentence composed of 2 variables to describe how many relays a note was found on. In source English, the first variable is the number of relays, and the second variable is 'Relay' or 'Relays'.\nSentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.\nSentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.",
"localizations" : {
"en-US" : {
"stringUnit" : {
@@ -423,9 +423,6 @@
"Connect" : {
"comment" : "Button to connect to the relay.\nText for button to conect to Nostr Wallet Connect lightning wallet."
},
"Connect to Alby Wallet" : {
"comment" : "Button to attach an Alby Wallet, a service that provides a Lightning wallet for zapping sats. Alby is the name of the service and should not be translated."
},
"Connect to Coinos" : {
"comment" : "Button to attach a Coinos Wallet, a service that provides a Lightning wallet for zapping sats. Coinos is the name of the service and should not be translated."
},
@@ -1352,6 +1349,9 @@
"Please contact the person who provided the link, and ask for another link." : {
"comment" : "User-visible tip on what to do if a link contains a deprecated \"nrelay\" reference."
},
"Please contact the person who provided the link, and ask for another link. Also, this link may have sensitive information, please use caution before sharing it." : {
"comment" : "User-visible tip on what to do if a link contains an unsupported \"nsec\" reference."
},
"Please copy the technical info and send it to our support team." : {
"comment" : "Tip on how to resolve issue when wallet returns an invalid response"
},
@@ -2254,6 +2254,9 @@
"You opened an invalid link. The link you tried to open refers to \"nrelay\", which has been deprecated and is not supported." : {
"comment" : "User-visible error description for a user who tries to open a deprecated \"nrelay\" link."
},
"You opened an invalid link. The link you tried to open refers to \"nsec\", which is not supported." : {
"comment" : "User-visible error description for a user who tries to open an unsupported \"nsec\" link."
},
"You unlocked" : {
"comment" : "Part 1 of 2 in message 'You unlocked automatic translations' the user gets when they sign up for Damus Purple"
},

Binary file not shown.

Binary file not shown.

View File

@@ -2,6 +2,24 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>follow_pack_user_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOW_PACK_USERS@</string>
<key>FOLLOW_PACK_USERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>utilizador</string>
<key>many</key>
<string>utilizadores</string>
<key>other</key>
<string>utilizadores</string>
</dict>
</dict>
<key>followed_by_three_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@@ -56,6 +74,24 @@
<string>A seguir</string>
</dict>
</dict>
<key>hellthread_notifications_disabled</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@HELLTHREAD_PROFILES@</string>
<key>HELLTHREAD_PROFILES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Ocultar notificações que marcam mais de %d perfil</string>
<key>many</key>
<string>Ocultar notificações que marcam mais de %d perfis</string>
<key>other</key>
<string>Ocultar notificações que marcam mais de %d perfis</string>
</dict>
</dict>
<key>imports_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@@ -74,6 +110,24 @@
<string>importações</string>
</dict>
</dict>
<key>notes_from_three_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Notas de %2$@, %3$@, %4$@ &amp; %1$d outro da tua rede confiável</string>
<key>many</key>
<string>Notas de %2$@, %3$@, %4$@ &amp; %1$d outros da tua rede confiável</string>
<key>other</key>
<string>Notas de %2$@, %3$@, %4$@ &amp; %1$d outros da tua rede confiável</string>
</dict>
</dict>
<key>people_reposted_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@@ -92,6 +146,24 @@
<string>%2$@ e mais %1$d republicaram</string>
</dict>
</dict>
<key>quoted_reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@QUOTE_REPOSTS@</string>
<key>QUOTE_REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Citação</string>
<key>many</key>
<string>Citações</string>
<key>other</key>
<string>Citações</string>
</dict>
</dict>
<key>reacted_tagged_in_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@@ -272,24 +344,6 @@
<string>Republicações</string>
</dict>
</dict>
<key>quoted_reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@QUOTE_REPOSTS@</string>
<key>QUOTE_REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Citação</string>
<key>many</key>
<string>Citações</string>
<key>other</key>
<string>Citações</string>
</dict>
</dict>
<key>sats</key>
<dict>
<key>NSStringLocalizedFormatKey</key>

Binary file not shown.

Binary file not shown.