Compare commits
27 Commits
nprofile-q
...
nip-a0-voi
| Author | SHA1 | Date | |
|---|---|---|---|
|
ed749c5879
|
|||
|
|
719a0c8cb0 | ||
| 89ad22833d | |||
|
|
9407c75d60 | ||
|
|
c4e6e5e6a7 | ||
|
|
592e9f9405 | ||
|
|
d924485bb3 | ||
|
|
b774f28427 | ||
|
|
deae6c0636 | ||
|
|
da386f3bcd | ||
|
55dbb46bb5
|
|||
|
|
dc8e647c34
|
||
|
|
eb25ff3584
|
||
|
|
0ae03fc3f3
|
||
|
|
e60f74eb9f
|
||
|
|
0d75f9cdd9
|
||
|
|
33a3ddbfd6
|
||
|
|
6555531846
|
||
|
|
97b9d06774
|
||
|
|
198448b114
|
||
|
|
a0333058a6
|
||
|
|
e640d5185e
|
||
|
|
9723718bc5
|
||
|
|
08e19fd395
|
||
|
|
7f39c3c4b2
|
||
|
|
cd3314c068
|
||
| f73c0ec1c4 |
@@ -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" */;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
44
damus/Shared/Media/Audio/DamusAudioPlayerView.swift
Normal file
44
damus/Shared/Media/Audio/DamusAudioPlayerView.swift
Normal 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()
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
@@ -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 "nsec", 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 "nsec", 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>
|
||||
|
||||
@@ -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.
Binary file not shown.
Binary file not shown.
@@ -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$@ & %1$d outro da tua rede confiável</string>
|
||||
<key>many</key>
|
||||
<string>Notas de %2$@, %3$@, %4$@ & %1$d outros da tua rede confiável</string>
|
||||
<key>other</key>
|
||||
<string>Notas de %2$@, %3$@, %4$@ & %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.
Reference in New Issue
Block a user