Compare commits

...

26 Commits

Author SHA1 Message Date
a72f964111 Fix localized string for privacy access description for photos 2023-01-21 16:01:31 -05:00
William Casarin
d658d1d987 DM Message Requests
Put strangers in a different tab

Changelog-Added: Add DM Message Requests
2023-01-21 11:13:44 -08:00
William Casarin
e14cd99c85 Add first_eref_mention helper to refactor embedded builder 2023-01-21 10:29:40 -08:00
William Casarin
0258ef792f Show embedded note references
This reverts commit 3f3b78f9bc.

Changelog-Fixed: Show embedded note references
2023-01-21 09:57:29 -08:00
William Casarin
1e6505abe3 v1.0.0-7 changelog 2023-01-20 09:35:51 -08:00
OlegAba
98c24147e8 Drastically improve image viewer
Changelog-Added: Drastically improved image viewer
Closes: #349
2023-01-20 09:33:22 -08:00
Joel Klabo
d1e7de5dcb Add Preview with 999 Everything 2023-01-20 09:23:42 -08:00
Joel Klabo
11e0a87f06 Fix ... when too many likes/reposts
Closes: #351
Changelog-Fixed: Fix ... when too many likes/reposts
2023-01-20 09:23:14 -08:00
Swift
1154cec719 Avoid showing reboost alert for pubkey user
Closes: #337
Changelog-Fixed: Don't show report alert if logged in as a pubkey
2023-01-19 10:10:07 -08:00
Joel Klabo
1ecfb0487e Swipe to Dismiss Images
Closes: #343
2023-01-18 13:36:57 -08:00
Swift
8ffa8446b6 Image Pinch Zooming
Changelog-Added: Added pinch to zoom on images
2023-01-18 10:10:22 -08:00
Ben Weeks
fb1f99e728 Fixed Jack's issue with homepage gap at the top.
Closes: #328
Changelog-Fixed: Fix padding issue at top of home timeline
2023-01-18 09:59:26 -08:00
Zach Hendel
031408dec3 Makes both name and @username clickable to go to profile
Changelog-Changed: Makes both name and username clickable in sidebar to go to profile
Closes: #329
2023-01-18 09:54:18 -08:00
Zach Hendel
16b6d029fa Icons for hardclick menu event view
changes copy user id icon to person
changes copy note id icon to note.text
changes copy note json icon to magnifying glass

Closes: #330
2023-01-18 09:48:58 -08:00
Brandon Holm
3e093e8572 Added Blixt Wallet TestFlight link
Closes: #331
2023-01-18 09:48:49 -08:00
John Bethancourt
fa11af4b1d Prevent absurdly large sidebar on Mac or iPad
Closes: #338
Changelog-Fixed: Fix absurdly large sidebar on Mac/iPad
2023-01-18 09:39:35 -08:00
William Casarin
91159d70ca Merge remote-tracking branch 'github/translations_damus-localizations-en-us-xcloc-localized-contents-en-us-xliff--master_es_419'
Changelog-Added: Add Latin American Spanish translations
2023-01-18 09:30:53 -08:00
OlegAba
a57d654f32 Fix tab views moving after selecting from search result
Closes: #339
Changelog-Fixed: Fix tab views moving after selecting from search result
2023-01-18 09:29:30 -08:00
OlegAba
0313480685 Change navigation title to bold
Closes: #341
2023-01-18 09:29:04 -08:00
OlegAba
e07b31e0a1 Consistent follow/unfollow button width
Changelog-Fixed: Make follow/unfollow button a consistent width
2023-01-18 09:28:29 -08:00
538a0ae5ea Import es-419 localization files 2023-01-16 20:44:41 -05:00
0f82db2440 Delete unused exported es-419 localization 2023-01-16 20:44:11 -05:00
transifex-integration[bot]
92ae2c7754 Apply translations in es_419
translation completed for the source file '/damus Localizations/en-US.xcloc/Localized Contents/en-US.xliff'
on the 'es_419' language.
2023-01-16 16:59:06 +00:00
Ben Weeks
aba758b143 Fixes damus-io/damus#246 ChangeLog-Changed: Resolved issue with image URLs in uppercase (@npub1jutptdc2m8kgjmudtws095qk2tcale0eemvp4j2xnjnl4nh6669slrf04x) 2023-01-06 00:39:57 +00:00
Ben Weeks
b7c7b0b3bf Merge branch 'damus-io:master' into beautify-image-zoom 2023-01-06 00:25:15 +00:00
Ben Weeks
2d10d4592b Fixes damus-io/damus#254 Changes background to Color("DamusDarkGrey") ChangeLog-Changed: Allowed pinch and zoom on profile picture (scoder1747) ChangeLog-Changed: Allowed pinch and zoom to post pictures (@npub1jutptdc2m8kgjmudtws095qk2tcale0eemvp4j2xnjnl4nh6669slrf04x) 2023-01-06 00:16:48 +00:00
34 changed files with 1590 additions and 390 deletions

View File

@@ -1,3 +1,37 @@
## [1.0.0-7] - 2023-01-20
### Added
- Drastically improved image viewer (OlegAba)
- Added pinch to zoom on images (Swift)
- Add Latin American Spanish translations (William Casarin)
- Added SVG profile picture support (OlegAba)
### Changed
- Makes both name and username clickable in sidebar to go to profile (Zach Hendel)
- Clicking pfp in sidebar opens profile as well (radixrat)
- Don't blur images if your friend boosted it (ericholguin)
### Fixed
- Fix ... when too many likes/reposts (Joel Klabo)
- Don't show report alert if logged in as a pubkey (Swift)
- Fix padding issue at top of home timeline (Ben Weeks)
- Fix absurdly large sidebar on Mac/iPad (John Bethancourt)
- Fix tab views moving after selecting from search result (OlegAba)
- Make follow/unfollow button a consistent width (OlegAba)
- Don't add events to notifications from buggy relays (William Casarin)
- Fixed some crashes with large images (OlegAba)
- Fix DM sorting on incoming messages (William Casarin)
- Fix text getting truncated next to link previews (William Casarin)
[1.0.0-7]: https://github.com/damus-io/damus/releases/tag/v1.0.0-7
## [1.0.0-6] - 2023-01-13
### Added
@@ -384,4 +418,3 @@
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2

View File

@@ -16,8 +16,8 @@
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSPhotoLibraryAddUsageDescription" xml:space="preserve">
<source>"Granting Damus access to your photo library allows you to save photos.</source>
<target>"Granting Damus access to your photo library allows you to save photos.</target>
<source>Granting Damus access to your photos allows you to save images.</source>
<target>Granting Damus access to your photos allows you to save images.</target>
<note>Privacy - Photo Library Additions Usage Description</note>
</trans-unit>
</body>
@@ -285,6 +285,16 @@ Number of profiles a user is following.</note>
<target>DM</target>
<note>Navigation title for DM view, which is the English abbreviation for Direct Message.</note>
</trans-unit>
<trans-unit id="DM Type" xml:space="preserve">
<source>DM Type</source>
<target>DM Type</target>
<note>DM selector for seeing either DMs or message requests, which are messages that have not been responded to yet.</note>
</trans-unit>
<trans-unit id="DMs" xml:space="preserve">
<source>DMs</source>
<target>DMs</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Damus" xml:space="preserve">
<source>Damus</source>
<target>Damus</target>
@@ -507,11 +517,6 @@ Part of a larger sentence to describe how many profiles a user is following.</no
<trans-unit id="Private Key" xml:space="preserve">
<source>Private Key</source>
<target>Private Key</target>
<note>Label to indicate that the text below is the user's private key used by only the user themself as a secret to login to access their account.</note>
</trans-unit>
<trans-unit id="PrivateKey" xml:space="preserve">
<source>PrivateKey</source>
<target>PrivateKey</target>
<note>Title of the secure field that holds the user's private key.</note>
</trans-unit>
<trans-unit id="Profile" xml:space="preserve">
@@ -590,6 +595,11 @@ Part of a larger sentence to describe how many profiles a user is following.</no
<target>Reposted</target>
<note>Text indicating that the post was reposted (i.e. re-shared).</note>
</trans-unit>
<trans-unit id="Requests" xml:space="preserve">
<source>Requests</source>
<target>Requests</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reset" xml:space="preserve">
<source>Reset</source>
<target>Reset</target>

View File

@@ -3,4 +3,4 @@
/* Bundle name */
"CFBundleName" = "damus";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "\"Granting Damus access to your photo library allows you to save photos.";
"NSPhotoLibraryAddUsageDescription" = "Granting Damus access to your photos allows you to save images.";

View File

@@ -1,154 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d other note</string>
<key>other</key>
<string>%d other notes</string>
</dict>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Follower</string>
<key>other</key>
<string>Followers</string>
</dict>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTIONS@</string>
<key>REACTIONS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Reaction</string>
<key>other</key>
<string>Reactions</string>
</dict>
</dict>
<key>relays_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@RELAYS@</string>
<key>RELAYS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Relay</string>
<key>other</key>
<string>Relays</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; %d other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@, %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; %d other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTS@</string>
<key>REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Repost</string>
<key>other</key>
<string>Reposts</string>
</dict>
</dict>
<key>sats_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@SATS@</string>
<key>SATS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>other</key>
<string>%2$@ sats</string>
</dict>
</dict>
<key>tips_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@TIPS@</string>
<key>TIPS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Tip</string>
<key>other</key>
<string>Tips</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -1,12 +0,0 @@
{
"developmentRegion" : "en-US",
"project" : "damus.xcodeproj",
"targetLocale" : "es-419",
"toolInfo" : {
"toolBuildNumber" : "14C18",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "14.2"
},
"version" : "1.0"
}

View File

@@ -12,6 +12,8 @@
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685A297633BC00C46468 /* InfoPlist.strings */; };
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685D297633BC00C46468 /* Localizable.strings */; };
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; };
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; };
@@ -145,6 +147,7 @@
4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */; };
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; };
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
@@ -157,6 +160,7 @@
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; };
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -183,6 +187,8 @@
31D2E846295218AF006D67F8 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; };
3A2B8B0A296A8982009CC16D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-US"; path = "en-US.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3ACB685B297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3ACB685E297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; sourceTree = "<group>"; };
4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = "<group>"; };
4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; };
@@ -351,6 +357,7 @@
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = "<group>"; };
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = "<group>"; };
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
7C45AE70297353390031D7BC /* KFImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KFImageModel.swift; sourceTree = "<group>"; };
@@ -360,6 +367,7 @@
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadV2View.swift; sourceTree = "<group>"; };
F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToDismiss.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -510,8 +518,8 @@
4C216F33286F5ACD00040376 /* DMView.swift */,
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
3169CAE4294E699400EE4006 /* Empty Views */,
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
4C75EFB82804A2740006080F /* EventView.swift */,
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
4C3AC79E2833115300E1F516 /* FollowButtonView.swift */,
4C3AC79C2833036D00E1F516 /* FollowingView.swift */,
4C90BD17283A9EE5008EE7EF /* LoginView.swift */,
@@ -543,6 +551,7 @@
647D9A8C2968520300A295DE /* SideMenuView.swift */,
9609F057296E220800069BF3 /* BannerImageView.swift */,
4CB8838E296F781C00DC99E7 /* ReactionsView.swift */,
6439E013296790CF0020672B /* ProfileZoomView.swift */,
);
path = Views;
sourceTree = "<group>";
@@ -645,12 +654,15 @@
4CE6DEE527F7A08100C66700 /* damus */ = {
isa = PBXGroup;
children = (
F7F0BA23297892AE009531F3 /* Modifiers */,
4C4A3A5A288A1B2200453788 /* damus.entitlements */,
4CE4F9DF285287A000C00DD9 /* Components */,
4C7FF7D628233637009601DB /* Util */,
4C0A3F8D280F63FF000448DE /* Models */,
4C75EFAB28049CC80006080F /* Nostr */,
4C75EFA72804823E0006080F /* Info.plist */,
3ACB685D297633BC00C46468 /* Localizable.strings */,
3ACB685A297633BC00C46468 /* InfoPlist.strings */,
4C75EFA227FA576C0006080F /* Views */,
4CE6DEE627F7A08100C66700 /* damusApp.swift */,
4CE6DEE827F7A08100C66700 /* ContentView.swift */,
@@ -701,6 +713,14 @@
name = Frameworks;
sourceTree = "<group>";
};
F7F0BA23297892AE009531F3 /* Modifiers */ = {
isa = PBXGroup;
children = (
F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */,
);
path = Modifiers;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -822,7 +842,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */,
4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */,
3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */,
4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */,
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */,
);
@@ -884,6 +906,7 @@
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */,
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */,
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */,
4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */,
4C649844285A952100EAE2B3 /* LocalUserConfig.swift in Sources */,
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
@@ -941,6 +964,7 @@
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */,
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
@@ -1032,6 +1056,22 @@
name = Localizable.stringsdict;
sourceTree = "<group>";
};
3ACB685A297633BC00C46468 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
3ACB685B297633BC00C46468 /* es-419 */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
3ACB685D297633BC00C46468 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
3ACB685E297633BC00C46468 /* es-419 */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
@@ -1163,7 +1203,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
@@ -1171,7 +1211,7 @@
INFOPLIST_FILE = damus/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Damus;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "\"Granting Damus access to your photo library allows you to save photos.";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -1204,7 +1244,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
@@ -1212,7 +1252,7 @@
INFOPLIST_FILE = damus/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Damus;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "\"Granting Damus access to your photo library allows you to save photos.";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;

View File

@@ -12,14 +12,14 @@ import Kingfisher
struct ShareSheet: UIViewControllerRepresentable {
typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
let activityItems: [URL]
let activityItems: [URL?]
let callback: Callback? = nil
let applicationActivities: [UIActivity]? = nil
let excludedActivityTypes: [UIActivity.ActivityType]? = nil
func makeUIViewController(context: Context) -> UIActivityViewController {
let controller = UIActivityViewController(
activityItems: activityItems,
activityItems: activityItems as [Any],
applicationActivities: applicationActivities)
controller.excludedActivityTypes = excludedActivityTypes
controller.completionWithItemsHandler = callback
@@ -32,7 +32,7 @@ struct ShareSheet: UIViewControllerRepresentable {
}
struct ImageContextMenuModifier: ViewModifier {
let url: URL
let url: URL?
let image: UIImage?
@Binding var showShareSheet: Bool
@@ -64,8 +64,21 @@ struct ImageContextMenuModifier: ViewModifier {
}
}
struct ImageViewer: View {
let urls: [URL]
private struct ImageContainerView: View {
@ObservedObject var imageModel: KFImageModel
@State private var image: UIImage?
@State private var showShareSheet = false
init(url: URL?) {
self.imageModel = KFImageModel(
url: url,
fallbackUrl: nil,
maxByteSize: 2000000, // 2 MB
downsampleSize: CGSize(width: 400, height: 400)
)
}
private struct ImageHandler: ImageModifier {
@Binding var handler: UIImage?
@@ -75,45 +88,178 @@ struct ImageViewer: View {
return image
}
}
@State private var image: UIImage?
@State private var showShareSheet = false
func onShared(completed: Bool) -> Void {
if (completed) {
showShareSheet = false
var body: some View {
KFAnimatedImage(imageModel.url)
.callbackQueue(.dispatch(.global(qos: .background)))
.processingQueue(.dispatch(.global(qos: .background)))
.cacheOriginalImage()
.configure { view in
view.framePreloadCount = 1
}
.scaleFactor(UIScreen.main.scale)
.loadDiskFileSynchronously()
.fade(duration: 0.1)
.imageModifier(ImageHandler(handler: $image))
.onFailure { _ in
imageModel.downloadFailed()
}
.id(imageModel.refreshID)
.clipped()
.modifier(ImageContextMenuModifier(url: imageModel.url, image: image, showShareSheet: $showShareSheet))
.sheet(isPresented: $showShareSheet) {
ShareSheet(activityItems: [imageModel.url])
}
// TODO: Update ImageCarousel with serializer and processor
// .serialize(by: imageModel.serializer)
// .setProcessor(imageModel.processor)
}
}
struct ZoomableScrollView<Content: View>: UIViewRepresentable {
private var content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
scrollView.maximumZoomScale = 20
scrollView.minimumZoomScale = 1
scrollView.bouncesZoom = true
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds
hostedView.backgroundColor = .clear
scrollView.addSubview(hostedView)
return scrollView
}
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: self.content))
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
context.coordinator.hostingController.rootView = self.content
assert(context.coordinator.hostingController.view.superview == uiView)
}
class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<Content>
init(hostingController: UIHostingController<Content>) {
self.hostingController = hostingController
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return hostingController.view
}
}
}
struct ImageView: View {
let urls: [URL?]
@Environment(\.presentationMode) var presentationMode
@State private var selectedIndex = 0
@State var showMenu = true
var navBarView: some View {
VStack {
HStack {
Text(urls[selectedIndex]?.lastPathComponent ?? "")
.bold()
Spacer()
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Image(systemName: "xmark")
})
}
.padding()
Divider()
.ignoresSafeArea()
}
.background(.regularMaterial)
}
var tabViewIndicator: some View {
HStack(spacing: 10) {
ForEach(urls.indices, id: \.self) { index in
Capsule()
.fill(index == selectedIndex ? Color(UIColor.label) : Color.secondary)
.frame(width: 7, height: 7)
}
}
.padding()
.background(.regularMaterial)
.clipShape(Capsule())
}
var body: some View {
TabView {
ForEach(urls, id: \.absoluteString) { url in
VStack{
Text(url.lastPathComponent)
KFAnimatedImage(url)
.configure { view in
view.framePreloadCount = 3
}
.cacheOriginalImage()
.imageModifier(ImageHandler(handler: $image))
.loadDiskFileSynchronously()
.scaleFactor(UIScreen.main.scale)
.fade(duration: 0.1)
.aspectRatio(contentMode: .fit)
.tabItem {
Text(url.absoluteString)
}
.id(url.absoluteString)
.modifier(ImageContextMenuModifier(url: url, image: image, showShareSheet: $showShareSheet))
.sheet(isPresented: $showShareSheet) {
ShareSheet(activityItems: [url])
}
ZStack {
Color(.systemBackground)
.ignoresSafeArea()
TabView(selection: $selectedIndex) {
ForEach(urls.indices, id: \.self) { index in
ZoomableScrollView {
ImageContainerView(url: urls[index])
.aspectRatio(contentMode: .fit)
}
.ignoresSafeArea()
.tag(index)
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
presentationMode.wrappedValue.dismiss()
}))
}
}
.ignoresSafeArea()
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.onChange(of: selectedIndex, perform: { _ in
showMenu = true
})
.onTapGesture {
showMenu.toggle()
}
.overlay(
VStack {
if showMenu {
navBarView
Spacer()
if (urls.count > 1) {
tabViewIndicator
}
}
}
.animation(.easeInOut, value: showMenu)
.padding(
.bottom,
UIApplication
.shared
.connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }?.safeAreaInsets.bottom
)
)
}
.tabViewStyle(PageTabViewStyle())
}
}
@@ -130,13 +276,15 @@ struct ImageCarousel: View {
.foregroundColor(Color.clear)
.overlay {
KFAnimatedImage(url)
.configure { view in
view.framePreloadCount = 3
}
.callbackQueue(.dispatch(.global(qos: .background)))
.processingQueue(.dispatch(.global(qos: .background)))
.cacheOriginalImage()
.loadDiskFileSynchronously()
.scaleFactor(UIScreen.main.scale)
.fade(duration: 0.1)
.configure { view in
view.framePreloadCount = 3
}
.aspectRatio(contentMode: .fit)
.tabItem {
Text(url.absoluteString)
@@ -151,8 +299,8 @@ struct ImageCarousel: View {
}
}
.cornerRadius(10)
.sheet(isPresented: $open_sheet) {
ImageViewer(urls: urls)
.fullScreenCover(isPresented: $open_sheet) {
ImageView(urls: urls)
}
.frame(height: 200)
.onTapGesture {
@@ -164,6 +312,6 @@ struct ImageCarousel: View {
struct ImageCarousel_Previews: PreviewProvider {
static var previews: some View {
ImageCarousel(urls: [URL(string: "https://jb55.com/red-me.jpg")!])
ImageCarousel(urls: [URL(string: "https://jb55.com/red-me.jpg")!,URL(string: "https://jb55.com/red-me.jpg")!])
}
}

View File

@@ -103,7 +103,7 @@ struct ContentView: View {
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
.safeAreaInset(edge: .top) {
.safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) {
FiltersView
//.frame(maxWidth: 275)
@@ -113,7 +113,6 @@ struct ContentView: View {
}
.background(colorScheme == .dark ? Color.black : Color.white)
}
.ignoresSafeArea(.keyboard)
}
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
@@ -180,16 +179,19 @@ struct ContentView: View {
.shadow(color: Color("DamusPurple"), radius: 2)
case .dms:
Text("DM", comment: "Toolbar label for DM view, which is the English abbreviation for Direct Message.")
.bold()
case .notifications:
Text("Notifications", comment: "Toolbar label for Notifications view.")
.bold()
case .search:
Text("Global", comment: "Toolbar label for Global view where posts from all connected relay servers appear.")
.bold()
case .none:
Text("", comment: "Toolbar label for unknown views. This label would be displayed only if a new timeline view is added but a toolbar label was not explicitly assigned to it yet.")
}
}
}
.ignoresSafeArea(.keyboard)
}
var MaybeSearchView: some View {

View File

@@ -22,8 +22,13 @@ struct DamusState {
var pubkey: String {
return keypair.pubkey
}
var is_privkey_user: Bool {
keypair.privkey != nil
}
static var empty: DamusState {
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(), previews: PreviewCache())
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache())
}
}

View File

@@ -8,13 +8,34 @@
import Foundation
class DirectMessageModel: ObservableObject {
@Published var events: [NostrEvent]
init(events: [NostrEvent]) {
self.events = events
@Published var events: [NostrEvent] {
didSet {
is_request = determine_is_request()
}
}
init() {
var is_request: Bool
var our_pubkey: String
func determine_is_request() -> Bool {
for event in events {
if event.pubkey == our_pubkey {
return false
}
}
return true
}
init(events: [NostrEvent], our_pubkey: String) {
self.events = events
self.is_request = false
self.our_pubkey = our_pubkey
}
init(our_pubkey: String) {
self.events = []
self.is_request = false
self.our_pubkey = our_pubkey
}
}

View File

@@ -10,13 +10,26 @@ import Foundation
class DirectMessagesModel: ObservableObject {
@Published var dms: [(String, DirectMessageModel)] = []
@Published var loading: Bool = false
let our_pubkey: String
init(our_pubkey: String) {
self.our_pubkey = our_pubkey
}
var message_requests: [(String, DirectMessageModel)] {
return dms.filter { dm in dm.1.is_request }
}
var friend_dms: [(String, DirectMessageModel)] {
return dms.filter { dm in !dm.1.is_request }
}
func lookup_or_create(_ pubkey: String) -> DirectMessageModel {
if let dm = lookup(pubkey) {
return dm
}
let new = DirectMessageModel()
let new = DirectMessageModel(our_pubkey: our_pubkey)
dms.append((pubkey, new))
return new
}

View File

@@ -48,17 +48,19 @@ class HomeModel: ObservableObject {
@Published var new_events: NewEventsBits = NewEventsBits()
@Published var notifications: [NostrEvent] = []
@Published var dms: DirectMessagesModel = DirectMessagesModel()
@Published var dms: DirectMessagesModel
@Published var events: [NostrEvent] = []
@Published var loading: Bool = false
@Published var signal: SignalModel = SignalModel()
init() {
self.damus_state = DamusState.empty
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
}
init(damus_state: DamusState) {
self.damus_state = damus_state
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
}
var pool: RelayPool {
@@ -644,7 +646,7 @@ func handle_incoming_dm(prev_events: NewEventsBits, dms: DirectMessagesModel, ou
if !found {
inserted = true
let model = DirectMessageModel(events: [ev])
let model = DirectMessageModel(events: [ev], our_pubkey: our_pubkey)
dms.dms.append((the_pk, model))
}

View File

@@ -75,7 +75,7 @@ enum Wallet: String, CaseIterable, Identifiable {
appStoreLink: "https://apps.apple.com/sv/app/bitcoin-beach-wallet/id1531383905", image: "bbw")
case .blixtwallet:
return .init(index: 11, tag: "blixtwallet", displayName: NSLocalizedString("Blixt Wallet", comment: "Dropdown option label for Lightning wallet, Blixt Wallet"), link: "blixtwallet:lightning:",
appStoreLink: nil, image: "blixt-wallet")
appStoreLink: "https://testflight.apple.com/join/EXvGhRzS", image: "blixt-wallet")
case .river:
return .init(index: 12, tag: "river", displayName: NSLocalizedString("River", comment: "Dropdown option label for Lightning wallet, River"), link: "river://",
appStoreLink: "https://apps.apple.com/us/app/river-buy-mine-bitcoin/id1536176542", image: "river")

View File

@@ -0,0 +1,35 @@
//
// SwipeToDismiss.swift
// damus
//
// Created by Joel Klabo on 1/18/23.
//
import SwiftUI
struct SwipeToDismissModifier: ViewModifier {
let minDistance: CGFloat?
var onDismiss: () -> Void
@State private var offset: CGSize = .zero
func body(content: Content) -> some View {
content
.offset(y: offset.height)
.animation(.interactiveSpring(), value: offset)
.simultaneousGesture(
DragGesture(minimumDistance: minDistance ?? 10)
.onChanged { gesture in
if gesture.translation.width < 50 {
offset = gesture.translation
}
}
.onEnded { _ in
if abs(offset.height) > 100 {
onDismiss()
} else {
offset = .zero
}
}
)
}
}

View File

@@ -789,3 +789,29 @@ func inner_event_or_self(ev: NostrEvent) -> NostrEvent {
return inner_ev
}
func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? {
let blocks = ev.blocks(privkey).filter { block in
guard case .mention(let mention) = block else {
return false
}
guard case .event = mention.type else {
return false
}
if mention.ref.key != "e" {
return false
}
return true
}
/// MARK: - Preview
if let firstBlock = blocks.first, case .mention(let mention) = firstBlock, mention.ref.key == "e" {
return mention
}
return nil
}

View File

@@ -33,62 +33,41 @@ struct EventActionBar: View {
EventActionButton(img: "bubble.left", col: nil) {
notify(.reply, event)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
HStack(alignment: .bottom) {
Spacer()
ZStack {
EventActionButton(img: "arrow.2.squarepath", col: bar.boosted ? Color.green : nil) {
if bar.boosted {
notify(.delete, bar.our_boost)
} else {
} else if damus_state.is_privkey_user {
self.confirm_boost = true
}
}.overlay {
Text("\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
.offset(x: 22)
.font(.footnote.weight(.medium))
.foregroundColor(bar.boosted ? Color.green : Color.gray)
}
Text("\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
.offset(x: 18)
.font(.footnote.weight(.medium))
.foregroundColor(bar.boosted ? Color.green : Color.gray)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
HStack(alignment: .bottom) {
Spacer()
ZStack {
LikeButton(liked: bar.liked) {
if bar.liked {
notify(.delete, bar.our_like)
} else {
send_like()
}
}.overlay {
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
.offset(x: 22)
.font(.footnote.weight(.medium))
.foregroundColor(bar.liked ? Color.accentColor : Color.gray)
}
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
.offset(x: 22)
.font(.footnote.weight(.medium))
.foregroundColor(bar.liked ? Color.accentColor : Color.gray)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
Spacer()
EventActionButton(img: "square.and.arrow.up", col: Color.gray) {
show_share_sheet = true
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
/*
HStack(alignment: .bottom) {
Text("\(bar.tips > 0 ? "\(bar.tips)" : "")")
.font(.footnote)
.foregroundColor(bar.tipped ? Color.orange : Color.gray)
EventActionButton(img: bar.tipped ? "bitcoinsign.circle.fill" : "bitcoinsign.circle", col: bar.tipped ? Color.orange : nil) {
if bar.tipped {
//notify(.delete, bar.our_tip)
} else {
//notify(.boost, event)
}
}
}
*/
}
.sheet(isPresented: $show_share_sheet) {
if let note_id = bech32_note_id(event.id) {
@@ -177,8 +156,9 @@ struct EventActionBar_Previews: PreviewProvider {
let ev = NostrEvent(content: "hi", pubkey: pk)
let bar = ActionBarModel(likes: 0, boosts: 0, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
let likedbar = ActionBarModel(likes: 10, boosts: 0, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
let likedbar_ours = ActionBarModel(likes: 10, boosts: 0, tips: 0, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: nil, our_tip: nil)
let likedbar = ActionBarModel(likes: 10, boosts: 10, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
let likedbar_ours = ActionBarModel(likes: 100, boosts: 100, tips: 0, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: nil, our_tip: nil)
let maxed_bar = ActionBarModel(likes: 999, boosts: 999, tips: 0, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: NostrEvent(id: "", content: "", pubkey: ""), our_tip: nil)
VStack(spacing: 50) {
EventActionBar(damus_state: ds, event: ev, bar: bar)
@@ -186,6 +166,8 @@ struct EventActionBar_Previews: PreviewProvider {
EventActionBar(damus_state: ds, event: ev, bar: likedbar)
EventActionBar(damus_state: ds, event: ev, bar: likedbar_ours)
EventActionBar(damus_state: ds, event: ev, bar: maxed_bar)
}
.padding(20)
}

View File

@@ -91,7 +91,7 @@ struct ConfigView: View {
Section(NSLocalizedString("Secret Account Login Key", comment: "Section title for user's secret account login key.")) {
HStack {
if show_privkey == false {
SecureField(NSLocalizedString("PrivateKey", comment: "Title of the secure field that holds the user's private key."), text: $privkey)
SecureField(NSLocalizedString("Private Key", comment: "Title of the secure field that holds the user's private key."), text: $privkey)
.disabled(true)
} else {
Text(sec)

View File

@@ -158,7 +158,7 @@ struct DMChatView_Previews: PreviewProvider {
static var previews: some View {
let ev = NostrEvent(content: "hi", pubkey: "pubkey", kind: 1, tags: [])
let model = DirectMessageModel(events: [ev])
let model = DirectMessageModel(events: [ev], our_pubkey: "pubkey")
DMChatView(damus_state: test_damus_state(), pubkey: "pubkey")
.environmentObject(model)

View File

@@ -7,15 +7,26 @@
import SwiftUI
enum DMType: Hashable {
case rando
case friend
}
struct DirectMessagesView: View {
let damus_state: DamusState
@State var dm_type: DMType = .friend
@State var open_dm: Bool = false
@State var pubkey: String = ""
@State var active_model: DirectMessageModel = DirectMessageModel()
@EnvironmentObject var model: DirectMessagesModel
@State var active_model: DirectMessageModel
var MainContent: some View {
init(damus_state: DamusState) {
self.damus_state = damus_state
self._active_model = State(initialValue: DirectMessageModel(our_pubkey: damus_state.pubkey))
}
func MainContent(requests: Bool) -> some View {
ScrollView {
let chat = DMChatView(damus_state: damus_state, pubkey: pubkey)
.environmentObject(active_model)
@@ -26,13 +37,12 @@ struct DirectMessagesView: View {
if model.dms.isEmpty, !model.loading {
EmptyTimelineView()
} else {
ForEach(model.dms, id: \.0) { tup in
let dms = requests ? model.message_requests : model.friend_dms
ForEach(dms, id: \.0) { tup in
MaybeEvent(tup)
}
}
}
.padding(.horizontal)
.padding(.top)
}
}
@@ -52,8 +62,29 @@ struct DirectMessagesView: View {
}
var body: some View {
MainContent
.navigationTitle(NSLocalizedString("Encrypted DMs", comment: "Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message."))
VStack {
Picker(NSLocalizedString("DM Type", comment: "DM selector for seeing either DMs or message requests, which are messages that have not been responded to yet."), selection: $dm_type) {
Text("DMs")
.tag(DMType.friend)
Text("Requests")
.tag(DMType.rando)
}
.pickerStyle(.segmented)
TabView(selection: $dm_type) {
MainContent(requests: false)
.tag(DMType.friend)
MainContent(requests: true)
.tag(DMType.rando)
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
.padding(.horizontal)
.padding(.top)
.navigationTitle(NSLocalizedString("Encrypted DMs", comment: "Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message."))
}
}
@@ -63,8 +94,9 @@ struct DirectMessagesView_Previews: PreviewProvider {
pubkey: "pubkey",
kind: 4,
tags: [])
let model = DirectMessageModel(events: [ev])
DirectMessagesView(damus_state: test_damus_state())
let ds = test_damus_state()
let model = DirectMessageModel(events: [ev], our_pubkey: ds.pubkey)
DirectMessagesView(damus_state: ds)
.environmentObject(model)
}
}

View File

@@ -106,7 +106,7 @@ struct BuilderEventView: View {
if let event = event {
let ev = event.inner_event ?? event
NavigationLink(destination: BuildThreadV2View(damus: damus, event_id: ev.id)) {
EventView(damus: damus, event: event, show_friend_icon: true, size: .small)
EventView(damus: damus, event: event, show_friend_icon: true, size: .small, embedded: true)
}.buttonStyle(.plain)
} else {
ProgressView().padding()
@@ -129,10 +129,11 @@ struct EventView: View {
let pubkey: String
let show_friend_icon: Bool
let size: EventViewKind
let embedded: Bool
@EnvironmentObject var action_bar: ActionBarModel
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal) {
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
self.event = event
self.highlight = highlight
self.has_action_bar = has_action_bar
@@ -140,9 +141,10 @@ struct EventView: View {
self.pubkey = event.pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal) {
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
self.event = event
self.highlight = .none
self.has_action_bar = false
@@ -150,6 +152,7 @@ struct EventView: View {
self.pubkey = event.pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
@@ -160,6 +163,7 @@ struct EventView: View {
self.pubkey = pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
var body: some View {
@@ -196,8 +200,10 @@ struct EventView: View {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
if !embedded {
NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
}
}
Spacer()
@@ -236,33 +242,33 @@ struct EventView: View {
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, previews: damus.previews, show_images: should_show_img, artifacts: .just_content(content), size: self.size)
.frame(maxWidth: .infinity, alignment: .leading)
.allowsHitTesting(!embedded)
if has_action_bar {
if size == .selected {
Text("\(format_date(event.created_at))")
.padding(.top, 10)
.font(.footnote)
.foregroundColor(.gray)
if !embedded {
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
BuilderEventView(damus: damus, event_id: mention.ref.id)
}
if has_action_bar {
if size == .selected {
Text("\(format_date(event.created_at))")
.padding(.top, 10)
.font(.footnote)
.foregroundColor(.gray)
Divider()
.padding([.bottom], 4)
} else {
Rectangle().frame(height: 2).opacity(0)
}
Divider()
.padding([.bottom], 4)
} else {
Rectangle().frame(height: 2).opacity(0)
let bar = make_actionbar_model(ev: event, damus: damus)
EventActionBar(damus_state: damus, event: event, bar: bar)
}
let bar = make_actionbar_model(ev: event, damus: damus)
if size == .selected && !bar.is_empty {
EventDetailBar(state: damus, target: event.id, bar: bar)
Divider()
.padding([.bottom], 4)
}
EventActionBar(damus_state: damus, event: event, bar: bar)
}
Divider()
.padding([.top], 4)
Divider()
.padding([.top], 4)
}
}
.padding([.leading], 2)
}
@@ -324,19 +330,19 @@ extension View {
Button {
UIPasteboard.general.string = bech32_pubkey(pubkey) ?? pubkey
} label: {
Label(NSLocalizedString("Copy User ID", comment: "Context menu option for copying the ID of the user who created the note."), systemImage: "tag")
Label(NSLocalizedString("Copy User ID", comment: "Context menu option for copying the ID of the user who created the note."), systemImage: "person")
}
Button {
UIPasteboard.general.string = bech32_note_id(event.id) ?? event.id
} label: {
Label(NSLocalizedString("Copy Note ID", comment: "Context menu option for copying the ID of the note."), systemImage: "tag")
Label(NSLocalizedString("Copy Note ID", comment: "Context menu option for copying the ID of the note."), systemImage: "note.text")
}
Button {
UIPasteboard.general.string = event_to_json(ev: event)
} label: {
Label(NSLocalizedString("Copy Note JSON", comment: "Context menu option for copying the JSON text from the note."), systemImage: "note")
Label(NSLocalizedString("Copy Note JSON", comment: "Context menu option for copying the JSON text from the note."), systemImage: "j.square.on.square")
}
Button {

View File

@@ -19,8 +19,7 @@ struct FollowButtonView: View {
follow_state = perform_follow_btn_action(follow_state, target: target)
} label: {
Text(follow_btn_txt(follow_state))
.frame(height: 30)
.padding(.horizontal, 25)
.frame(width: 105, height: 30)
//.padding(.vertical, 10)
.font(.caption.weight(.bold))
.foregroundColor(follow_state == .unfollows ? filledTextColor() : borderColor())

View File

@@ -0,0 +1,20 @@
//
// ImageView.swift
// damus
//
// Created by user232838 on 1/5/23.
//
import SwiftUI
struct ImageView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
struct ImageView_Previews: PreviewProvider {
static var previews: some View {
ImageView()
}
}

View File

@@ -0,0 +1,33 @@
//
// MagnificationGestureView.swift
// damus
//
// Created by user232838 on 1/5/23.
//
import SwiftUI
struct MagnificationGestureView: View {
@GestureState var magnifyBy = 1.0
var magnification: some Gesture {
MagnificationGesture()
.updating($magnifyBy) { currentState, gestureState, transaction in
gestureState = currentState
}
}
var body: some View {
Circle()
.frame(width: 100, height: 100)
.scaleEffect(magnifyBy)
.gesture(magnification)
}
}
struct MagnificationGestureView_Previews: PreviewProvider {
static var previews: some View {
MagnificationGestureView()
}
}

View File

@@ -53,7 +53,7 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
}
func is_image_url(_ url: URL) -> Bool {
let str = url.lastPathComponent
let str = url.lastPathComponent.lowercased()
return str.hasSuffix("png") || str.hasSuffix("jpg") || str.hasSuffix("jpeg") || str.hasSuffix("gif")
}

View File

@@ -211,9 +211,8 @@ struct ProfileView: View {
.onTapGesture {
is_zoomed.toggle()
}
.sheet(isPresented: $is_zoomed) {
ProfilePicView(pubkey: profile.pubkey, size: zoom_size, highlight: .none, profiles: damus_state.profiles)
}
.fullScreenCover(isPresented: $is_zoomed) {
ProfileZoomView(pubkey: profile.pubkey, profiles: damus_state.profiles) }
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
Spacer()
@@ -356,7 +355,7 @@ struct ProfileView_Previews: PreviewProvider {
func test_damus_state() -> DamusState {
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
let damus = DamusState(pool: RelayPool(), keypair: Keypair(pubkey: pubkey, privkey: "privkey"), likes: EventCounter(our_pubkey: pubkey), boosts: EventCounter(our_pubkey: pubkey), contacts: Contacts(our_pubkey: pubkey), tips: TipCounter(our_pubkey: pubkey), profiles: Profiles(), dms: DirectMessagesModel(), previews: PreviewCache())
let damus = DamusState(pool: RelayPool(), keypair: Keypair(pubkey: pubkey, privkey: "privkey"), likes: EventCounter(our_pubkey: pubkey), boosts: EventCounter(our_pubkey: pubkey), contacts: Contacts(our_pubkey: pubkey), tips: TipCounter(our_pubkey: pubkey), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: pubkey), previews: PreviewCache())
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io")
let tsprof = TimestampedProfile(profile: prof, timestamp: 0)

View File

@@ -0,0 +1,98 @@
//
// ProfileZoomView.swift
// damus
//
// Created by scoder1747 on 12/27/22.
//
import SwiftUI
struct ProfileZoomView: View {
@Environment(\.presentationMode) var presentationMode
let pubkey: String
let profiles: Profiles
@GestureState private var scaleState: CGFloat = 1
@GestureState private var offsetState = CGSize.zero
@State private var offset = CGSize.zero
@State private var scale: CGFloat = 1
func resetStatus(){
self.offset = CGSize.zero
self.scale = 1
}
var zoomGesture: some Gesture {
MagnificationGesture()
.updating($scaleState) { currentState, gestureState, _ in
gestureState = currentState
}
.onEnded { value in
scale *= value
}
}
var dragGesture: some Gesture {
DragGesture()
.updating($offsetState) { currentState, gestureState, _ in
gestureState = currentState.translation
}.onEnded { value in
offset.height += value.translation.height
offset.width += value.translation.width
}
}
var doubleTapGesture : some Gesture {
TapGesture(count: 2).onEnded { value in
resetStatus()
}
}
var body: some View {
ZStack(alignment: .topLeading) {
Color("DamusDarkGrey") // Or Color("DamusBlack")
.edgesIgnoringSafeArea(.all)
HStack() {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: "xmark")
.foregroundColor(.white)
.font(.largeTitle)
.frame(width: 40, height: 40)
.padding(20)
}
}
.zIndex(1)
VStack(alignment: .center) {
Spacer()
.frame(height: 120)
ProfilePicView(pubkey: pubkey, size: 200.0, highlight: .none, profiles: profiles)
.padding(100)
.scaledToFit()
.scaleEffect(self.scale * scaleState)
.offset(x: offset.width + offsetState.width, y: offset.height + offsetState.height)
.gesture(SimultaneousGesture(zoomGesture, dragGesture))
.gesture(doubleTapGesture)
.modifier(SwipeToDismissModifier(minDistance: nil, onDismiss: {
presentationMode.wrappedValue.dismiss()
}))
}
}
}
}
struct ProfileZoomView_Previews: PreviewProvider {
static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846"
static var previews: some View {
ProfileZoomView(
pubkey: pubkey,
profiles: make_preview_profiles(pubkey))
}
}

View File

@@ -16,7 +16,7 @@ struct SideMenuView: View {
@Environment(\.colorScheme) var colorScheme
var sideBarWidth = UIScreen.main.bounds.size.width * 0.65
var sideBarWidth = min(UIScreen.main.bounds.size.width * 0.65, 400.0)
func fillColor() -> Color {
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
@@ -53,23 +53,23 @@ struct SideMenuView: View {
let followers = FollowersModel(damus_state: damus_state, target: damus_state.pubkey)
let profile_model = ProfileModel(pubkey: damus_state.pubkey, damus: damus_state)
if let picture = damus_state.profiles.lookup(id: damus_state.pubkey)?.picture {
NavigationLink(destination: ProfileView(damus_state: damus_state, profile: profile_model, followers: followers)) {
NavigationLink(destination: ProfileView(damus_state: damus_state, profile: profile_model, followers: followers)) {
if let picture = damus_state.profiles.lookup(id: damus_state.pubkey)?.picture {
ProfilePicView(pubkey: damus_state.pubkey, size: 60, highlight: .none, profiles: damus_state.profiles, picture: picture)
} else {
Image(systemName: "person.fill")
}
} else {
Image(systemName: "person.fill")
}
VStack(alignment: .leading) {
if let display_name = profile?.display_name {
Text(display_name)
.foregroundColor(textColor())
.font(.title)
}
if let name = profile?.name {
Text("@" + name)
.foregroundColor(Color("DamusMediumGrey"))
.font(.body)
VStack(alignment: .leading) {
if let display_name = profile?.display_name {
Text(display_name)
.foregroundColor(textColor())
.font(.title)
}
if let name = profile?.name {
Text("@" + name)
.foregroundColor(Color("DamusMediumGrey"))
.font(.body)
}
}
}

View File

@@ -52,6 +52,7 @@ struct InnerTimelineView: View {
}
}
.padding(.horizontal)
.padding(.top,10)
}
}

View File

@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "Damus";
/* Bundle name */
"CFBundleName" = "damus";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "\"Granting Damus access to your photo library allows you to save photos.";
"NSPhotoLibraryAddUsageDescription" = "Si le concedes acceso a Damus a tu fototeca, podrás guardar fotos.";

View File

@@ -0,0 +1,493 @@
/* Blank space to separate profile picture from profile editor form. */
" " = "61b6edf1108e6f396680a33b02486a70_tr";
/* Description of how the nip05 identifier would be used for verification. */
"'%@' at '%@' will be used for verification" = "'%@' en '%@' se usarán con fines de verificación";
/* Description of why the nip05 identifier is invalid. */
"'%@' is an invalid nip05 identifier. It should look like an email." = "'%@' es un identificador nip05 no válido. Debería de tener la apariencia de un correo electrónico.";
/* Navigation bar title for view that shows who is following a user. */
"(Profile.displayName(profile: profile, pubkey: whos))'s Followers" = "Seguidores de (Profile.displayName(profile: profile, pubkey: whos))";
/* Navigation bar title for view that shows who a user is following. */
"(who) following" = "(who) sigue a";
/* Prefix character to username. */
"@" = "@";
/* Amount of time that has passed since reply quote event occurred.
Abbreviated version of a nostr public key. */
"%@" = "%@";
/* 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 profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'. */
"%@ %@" = "%@ %@";
/* Explanation of what is done to keep personally identifiable information private. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." = "%@. No se requiere un número de teléfono, correo electrónico ni nombre para crear una cuenta. Comienza de inmediato sin fricciones.";
/* Explanation of what is done to keep private data encrypted. There is a heading that precedes this explanation which is a variable to this string. */
"%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" = "%@. Mensajes privados con cifrado de un extremo a otro. Mantén a los gigantes tecnológicos fuera de tus mensajes directos.";
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet." = "%@. Deja propinas en las publicaciones de tus amigos y acumula sats con Bitcoin⚡, la moneda nativa de internet.";
/* Number of reposts.
Number of profiles a user is following. */
"%lld" = "%lld";
/* Fraction of how many of the user's relay servers that are operational. */
"%lld/%lld" = "%lld/%lld";
/* Placeholder for event mention. */
"< e >" = "< e >";
/* Label to prompt for about text entry for user to describe about themself. */
"About" = "Información";
/* Label for About Me section of user profile form. */
"About Me" = "Acerca de mí";
/* Placeholder text for About Me description. */
"Absolute Boss" = "Jefe supremo";
/* Label to indicate the public ID of the account. */
"Account ID" = "Identificador de cuenta";
/* Button to add recommended relay server.
Button to confirm adding user inputted relay. */
"Add" = "Agregar";
/* Label for section for adding a relay server. */
"Add Relay" = "Agregar relé";
/* Any amount of sats */
"Any" = "Cualquiera";
/* Alert message to ask if user wants to repost a post. */
"Are you sure you want to repost this?" = "¿Seguro quieres volver a publicar esto?";
/* Label for Banner Image section of user profile form. */
"Banner Image" = "Imagen del banner";
/* Reminder to user that they should save their account information. */
"Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus." = "Antes de empezar, tendrás que guardar la información de tu cuenta. De lo contrario, no podrás iniciar sesión en el futuro si llegas a desinstalar Damus.";
/* Dropdown option label for Lightning wallet, Bitcoin Beach. */
"Bitcoin Beach" = "Bitcoin Beach";
/* Label for Bitcoin Lightning Tips section of user profile form. */
"Bitcoin Lightning Tips" = "Propinas con Bitcoin Lightning";
/* Dropdown option label for Lightning wallet, Blixt Wallet */
"Blixt Wallet" = "Blixt Wallet";
/* Dropdown option label for Lightning wallet, Blue Wallet. */
"Blue Wallet" = "Blue Wallet";
/* Dropdown option label for Lightning wallet, Breez. */
"Breez" = "Breez";
/* Context menu option for broadcasting the user's note to all of the user's connected relay servers. */
"Broadcast" = "Transmitir";
/* Button to cancel out of posting a note.
Button to cancel out of reposting a post.
Button to cancel out of view adding user inputted relay.
Cancel out of logging out the user. */
"Cancel" = "Cancelar";
/* Dropdown option label for Lightning wallet, Cash App. */
"Cash App" = "Cash App";
/* Navigation bar title for Chatroom view. */
"Chat" = "Chat";
/* Button for clearing cached data. */
"Clear" = "Borrar";
/* Section title for clearing cached data. */
"Clear Cache" = "Borrar caché";
/* Label indicating that a user's key was copied. */
"Copied" = "Copiada";
/* Button to copy a relay server address. */
"Copy" = "Copiar";
/* Context menu option for copying the ID of the account that created the note. */
"Copy Account ID" = "Copiar identificador de cuenta";
/* Context menu option to copy an image into clipboard.
Context menu option to copy an image to clipboard. */
"Copy Image" = "Copiar imagen";
/* Context menu option to copy the URL of an image into clipboard. */
"Copy Image URL" = "Copiar URL de imagen";
/* Title of section for copying a Lightning invoice identifier. */
"Copy invoice" = "Copiar factura";
/* Context menu option for copying a user's Lightning URL. */
"Copy LNURL" = "Copiar LNURL";
/* Context menu option for copying the ID of the note. */
"Copy Note ID" = "Copiar identificador de nota";
/* Context menu option for copying the JSON text from the note. */
"Copy Note JSON" = "Copiar JSON de nota";
/* Context menu option for copying the text from an note. */
"Copy Text" = "Copiar texto";
/* Context menu option for copying the ID of the user who created the note. */
"Copy User ID" = "Copiar identificador de usuario";
/* Button to create account. */
"Create" = "Crear";
/* Button to create an account. */
"Create Account" = "Crear cuenta";
/* Example description about Bitcoin creator(s), Satoshi Nakamoto. */
"Creator(s) of Bitcoin. Absolute legend." = "Creador(es) de Bitcoin. Toda una leyenda.";
/* Name of the app, shown on the first screen when user is not logged in. */
"Damus" = "Damus";
/* Button to pay a Lightning invoice with the user's default Lightning wallet. */
"Default Wallet" = "Billetera predeterminada";
/* Button to delete a relay server that the user connects to. */
"Delete" = "Borrar";
/* Button to dismiss a text field alert. */
"Dismiss" = "Descartar";
/* Label to prompt display name entry. */
"Display Name" = "Mostrar nombre";
/* Navigation title for DM view, which is the English abbreviation for Direct Message. */
"DM" = "MD";
/* Button to dismiss wallet selection view for paying Lightning invoice. */
"Done" = "Listo";
/* Heading indicating that this application allows users to earn money. */
"Earn Money" = "Gana dinero";
/* Button to edit user's profile. */
"Edit" = "Editar";
/* Heading indicating that this application keeps private messaging end-to-end encrypted. */
"Encrypted" = "Cifrada";
/* Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message. */
"Encrypted DMs" = "MD cifrados";
/* Prompt for user to enter an account key to login. */
"Enter your account key to login:" = "Ingresa la clave de tu cuenta para iniciar sesión:";
/* Error message indicating why saving keys failed. */
"Error: %@" = "Error: %@";
/* Filter state for seeing either only posts, or posts & replies. */
"Filter State" = "Estado del filtro";
/* Button to follow a user. */
"Follow" = "Seguir";
/* Label describing followers of a user. */
"Followers" = "Seguidores";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of following a profile.
Part of a larger sentence to describe how many profiles a user is following. */
"Following" = "Siguiendo";
/* Label to indicate that the user is in the process of following another user. */
"Following..." = "Siguiendo...";
/* Text to indicate that button next to it is in a state that will follow a profile when tapped. */
"Follows" = "Sigue";
/* Navigation bar title for Global view where posts from all connected relay servers appear. */
"Global" = "Global";
/* Navigation link to go to post referenced by hex code. */
"Goto post %@" = "Ir a la publicación %@";
/* Navigation link to go to profile. */
"Goto profile %@" = "Ir al perfil %@";
/* Navigation bar title for Home view where posts and replies appear from those who the user is following. */
"Home" = "Inicio";
/* Placeholder example text for profile picture URL. */
"https://example.com/pic.jpg" = "https://ejemplo.com/foto.jpg";
/* Placeholder example text for website URL for user profile. */
"https://jb55.com" = "https://jb55.com";
/* Error message indicating that an invalid account key was entered for login. */
"Invalid key" = "Clave inválida";
/* Placeholder example text for identifier used for NIP-05 verification. */
"jb55@jb55.com" = "jb55@jb55.com";
/* Moves the post button to the left side of the screen */
"Left Handed" = "Zurdo";
/* Button to complete account creation and start using the app. */
"Let's go!" = "¡Vamos!";
/* Placeholder text for entry of Lightning Address or LNURL. */
"Lightning Address or LNURL" = "Dirección de Lightning o LNURL";
/* Indicates that the view is for paying a Lightning invoice. */
"Lightning Invoice" = "Factura de Lightning";
/* Dropdown option label for Lightning wallet, LNLink. */
"LNLink" = "LNLink";
/* Dropdown option label for system default for Lightning wallet. */
"Local default" = "Predeterminada del sistema";
/* Button to log into account.
Button to log into an account. */
"Login" = "Iniciar sesión";
/* Alert for logging out the user.
Button for logging out the user.
Button to logout the user. */
"Logout" = "Cerrar sesión";
/* Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out. */
"Make sure your nsec account key is saved before you logout or you will lose access to this account" = "Asegúrate de que tu clave de cuenta nsec esté guardada antes de cerrar sesión o perderás el acceso a esta cuenta";
/* Dropdown option label for Lightning wallet, Muun. */
"Muun" = "Muun";
/* Label for NIP-05 Verification section of user profile form. */
"NIP-05 Verification" = "Verificación NIP-05";
/* No search results. */
"none" = "ninguno";
/* Indicates that there are no notes in the timeline to view. */
"Nothing to see here. Check back later!" = "Nada para ver aquí. ¡Vuelve a consultar luego!";
/* Navigation title for notifications. */
"Notifications" = "Notificaciones";
/* String indicating that a given timestamp just occurred */
"now" = "ahora";
/* Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key. */
"nsec1..." = "nsec1...";
/* Label indicating that a form input is optional. */
"optional" = "opcional";
/* Button to pay a Lightning invoice. */
"Pay" = "Pagar";
/* Navigation bar title for view to pay Lightning invoice. */
"Pay the Lightning invoice" = "Paga la factura de Lightning";
/* Dropdown option label for Lightning wallet, Phoenix. */
"Phoenix" = "Phoenix";
/* Button to post a note. */
"Post" = "Publicar";
/* Label for filter for seeing only posts (instead of posts and replies). */
"Posts" = "Publicaciones";
/* Label for filter for seeing posts and replies (instead of only posts). */
"Posts & Replies" = "Publicaciones y respuestas";
/* Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading. */
"Private" = "Privada";
/* Label to indicate that the text below is the user's private key used by only the user themself as a secret to login to access their account. */
"Private Key" = "Clave privada";
/* Title of the secure field that holds the user's private key. */
"PrivateKey" = "ClavePrivada";
/* Sidebar menu label for Profile view. */
"Profile" = "Perfil";
/* Label for Profile Picture section of user profile form. */
"Profile Picture" = "Foto del perfil";
/* Section title for the user's public account ID. */
"Public Account ID" = "Identificador de cuenta pública";
/* Label indicating that the text is a user's public account key. */
"Public key" = "Clave pública";
/* Label indicating that the text is a user's public account key. */
"Public Key" = "Clave pública";
/* Prompt to ask user if the key they entered is a public key. */
"Public Key?" = "¿Clave pública?";
/* Navigation bar title for Reactions view. */
"Reactions" = "Reacciones";
/* Section title for recommend relay servers that could be added as part of configuration */
"Recommended Relays" = "Relés recomendados";
/* Text field for relay server. Used for testing purposes. */
"Relay" = "Relé";
/* Sidebar menu label for Relay servers view */
"Relays" = "Relés";
/* Label to indicate that the user is replying to themself. */
"Reply to self" = "Respuesta a sí mismo";
/* Label to indicate that the user is replying to 2 users. */
"Replying to %@ & %@" = "Respondiendo a %1$@ y %2$@";
/* Indicating that the user is replying to the following listed people. */
"Replying to:" = "Respondiendo a:";
/* Button to confirm reposting a post.
Title of alert for confirming to repost a post. */
"Repost" = "Republicar";
/* Text indicating that the post was reposted (i.e. re-shared). */
"Reposted" = "Republicada";
/* Section title for resetting the user */
"Reset" = "Reiniciar";
/* Button to retry completing account creation after an error occurred. */
"Retry" = "Reintentar";
/* Dropdown option label for Lightning wallet, River */
"River" = "River";
/* Example username of Bitcoin creator(s), Satoshi Nakamoto. */
"satoshi" = "satoshi";
/* Name of Bitcoin creator(s). */
"Satoshi Nakamoto" = "Satoshi Nakamoto";
/* Button for saving profile. */
"Save" = "Guardar";
/* Context menu option to save an image. */
"Save Image" = "Guardar imagen";
/* Navigation link to search hashtag. */
"Search hashtag: #%@" = "Buscar hashtag: #%@";
/* Placeholder text to prompt entry of search query. */
"Search..." = "Búsqueda...";
/* Section title for user's secret account login key. */
"Secret Account Login Key" = "Clave de inicio de sesión de cuenta secreta";
/* Title of section for selecting a Lightning wallet to pay a Lightning invoice. */
"Select a Lightning wallet" = "Seleccionar una billetera de Lightning";
/* Prompt selection of user's default wallet */
"Select default wallet" = "Seleccionar billetera predeterminada";
/* Text prompt for user to send a message to the other user. */
"Send a message to start the conversation..." = "Envía un mensaje para empezar la conversación...";
/* Navigation title for Settings view.
Sidebar menu label for accessing the app settings */
"Settings" = "Configuración";
/* Button to share an image. */
"Share" = "Compartir";
/* Toggle to show or hide user's secret account login key. */
"Show" = "Mostrar";
/* Toggle to show or hide selection of wallet. */
"Show wallet selector" = "Mostrar selector de billetera";
/* Sidebar menu label to sign out of the account. */
"Sign out" = "Cerrar sesión";
/* Dropdown option label for Lightning wallet, Strike. */
"Strike" = "Strike";
/* Warning that the inputted account key is a public key and the result of what happens because of it. */
"This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." = "Esta es una clave pública, por lo que no podrás hacer publicaciones ni interactuar de ningún modo. Se usa para ver cuentas desde su perspectiva.";
/* Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key. */
"This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key." = "Esta es una clave de nostr antigua. No sabemos con seguridad si es una clave pública o privada. Activa el siguiente botón si es una clave pública.";
/* Label to describe that a public key is the user's account ID and what they can do with it. */
"This is your account ID, you can give this to your friends so that they can follow you. Click to copy." = "Este es tu identificador de cuenta, que puedes compartir con tus amigos para que te sigan. Haz clic para copiarlo.";
/* Label to describe that a private key is the user's secret account key and what they should do with it. */
"This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!" = "Esta es tu clave de cuenta secreta, que necesitas para acceder a tu cuenta. ¡No la compartas con nadie! Guárdala en un administrador de contraseñas y protégela!";
/* Navigation bar title for note thread.
Navigation bar title for threaded event detail view. */
"Thread" = "Hilo";
/* Text box prompt to ask user to type their post. */
"Type your post here..." = "Ingresa tu publicación aquí...";
/* Non-breaking space character to fill in blank space next to event action button icons. */
"u{00A0}" = "u{00A0}";
/* Button to unfollow a user. */
"Unfollow" = "Dejar de seguir";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of unfollowing a profile. */
"Unfollowing" = "Dejando de seguir";
/* Label to indicate that the user is in the process of unfollowing another user. */
"Unfollowing..." = "Dejando de seguir...";
/* Text to indicate that the button next to it is in a state that will unfollow a profile when tapped. */
"Unfollows" = "Deja de seguir";
/* Label for Username section of user profile form.
Label to prompt username entry. */
"Username" = "Nombre de usuario";
/* Sidebar menu label for Wallet view. */
"Wallet" = "Billetera";
/* Dropdown option label for Lightning wallet, Wallet Of Satoshi. */
"Wallet Of Satoshi" = "Wallet Of Satoshi";
/* Section title for selection of wallet. */
"Wallet Selector" = "Selección de billetera";
/* Label for Website section of user profile form. */
"Website" = "Sitio web";
/* Welcoming message to the reader. The variable is 'you', the reader. */
"Welcome to the social network %@ control." = "Te damos la bienvenida a la red social que %@ controlas.";
/* Text to welcome user. */
"Welcome, %@!" = "¡Te damos la bienvenida, %@!";
/* Placeholder example for relay server address. */
"wss://some.relay.com" = "wss://algún.relé.com";
/* You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself. */
"you" = "tú";
/* Label for Your Name section of user profile form. */
"Your Name" = "Tu nombre";
/* Dropdown option label for Lightning wallet, Zebedee. */
"Zebedee" = "Zebedee";
/* Dropdown option label for Lightning wallet, Zeus LN. */
"Zeus LN" = "Zeus LN";

View File

@@ -4,8 +4,6 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@@ -13,15 +11,15 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d other note</string>
<string>%d otra nota</string>
<key>other</key>
<string>%d other notes</string>
<string>%d otras notas</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@@ -29,10 +27,12 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Follower</string>
<string>Seguidor</string>
<key>other</key>
<string>Followers</string>
<string>Seguidores</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@@ -45,9 +45,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Reaction</string>
<string>Reacción</string>
<key>other</key>
<string>Reactions</string>
<string>Reacciones</string>
</dict>
</dict>
<key>relays_count</key>
@@ -61,45 +61,45 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Relay</string>
<string>Relé</string>
<key>other</key>
<string>Relays</string>
<string>Relés</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@%#@OTHERS@</string>
<string>Respondiendo a %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> y %d otro</string>
<key>other</key>
<string> y %d otros</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; %d other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@, %@%#@OTHERS@</string>
<string>Respondiendo a %@, %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> y %d otro</string>
<key>other</key>
<string> y %d otros</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; %d other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>reposts_count</key>
@@ -113,9 +113,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Repost</string>
<string>Republicación</string>
<key>other</key>
<string>Reposts</string>
<string>Republicaciones</string>
</dict>
</dict>
<key>sats_count</key>
@@ -145,9 +145,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Tip</string>
<string>Propina</string>
<key>other</key>
<string>Tips</string>
<string>Propinas</string>
</dict>
</dict>
</dict>