Compare commits

..

74 Commits

Author SHA1 Message Date
b367382aae Export localizations and add Transifex config 2023-01-08 22:01:06 -05:00
bf1866056c Change development locale to en-US and add es-419 as a target locale 2023-01-08 21:57:38 -05:00
016dfa54f3 Internationalize amount formatting for sats 2023-01-08 21:57:38 -05:00
887eb902bf Add comments to localized strings 2023-01-08 21:57:28 -05:00
dcf328e7ac Fix localization bugs 2023-01-07 19:36:15 -05:00
William Casarin
c10fcc52e3 dms: always use the other persons pubkey in context menu
The DM view can sometimes show our last message, which means the pubkey
in the context menu would be ours. This is super confusing, so always
use the other persons pubkey when copying the user id in the DMs view.

Changlog-Fixed: Always copy other persons pubkey in DMs context menus
2023-01-07 11:29:24 -08:00
OlegAba
08dd2d5414 Fix picker bug and refactored 2023-01-07 10:22:39 -08:00
William Casarin
421245a2f8 v1.0.0 (5) Changelog 2023-01-06 14:21:23 -08:00
William Casarin
ec4790a8fe v1.0.0 (5) 2023-01-06 14:19:23 -08:00
OlegAba
5f22a7691f Fix TabView loading new view on scroll 2023-01-06 14:16:29 -08:00
OlegAba
d878ff6fdb Add system background color to profile pics
Changelog-Fixed: Add system background color to profile pics
Closes: #270
2023-01-06 14:14:57 -08:00
William Casarin
7081c4ac69 Profile: make key high-res and reintroduce key color
Changelog-Fixed: High res color pubkey on profile page
2023-01-06 14:09:41 -08:00
William Casarin
b03bef2f8b Profile: Add Universal Link share button
Changelog-Added: Added share button to profile
2023-01-06 14:01:57 -08:00
William Casarin
a153515366 Fix compilation 2023-01-06 10:41:56 -08:00
William Casarin
d5857325b1 Universal Links: share notes!
Changelog-Added: Added universal link sharing of notes
2023-01-06 10:24:08 -08:00
William Casarin
04e4bc7985 ocd refactor in load_profiles 2023-01-06 10:23:39 -08:00
William Casarin
e7d32d9ea7 pool: queue requests if we're disconnected
Changelog-Fixed: Don't spin forever if we're temporarily disconnected
2023-01-06 10:18:31 -08:00
aki-mizu
442a50f9ae update markdown format guides in Formatting Notes
In iOS for strikethrough only one tilde symbol is needed before and after words

Closes: #269
2023-01-06 05:30:30 -08:00
Joel Klabo
9f8379cb1e Fix Unassigned Child Image Asset Errors
Closes: #256
2023-01-05 16:11:31 -08:00
Ben Weeks
d0eb632ce6 PostButton: Very minor tweak to make it look more natural
Closes: #259
2023-01-05 16:08:00 -08:00
OlegAba
cd62418dda Add clear image cache button in settings
Closes: #260
Changelog-Added: Added clear cache button to wipe pfp/image cache
2023-01-05 16:06:05 -08:00
OlegAba
ba32d15a49 Refactor large image processor
Changelog-Fixed: Fixed a few issues with avatars not animating
2023-01-05 16:05:01 -08:00
Joel Klabo
b688fa98a5 Allow Adding Relay Without wss:// Prefix
Closes: #261
Changelog-Added: Allow Adding Relay Without wss:// Prefix
2023-01-05 16:03:14 -08:00
Joel Klabo
61a451184b Allow Saving Images to Library
Closes: #255
Changelog-Added: Allow Saving Images to Library
2023-01-05 13:12:19 -08:00
Aidan O'Loan
96741af97b Scroll to bottom when new DM received
Closes: #242
Closes: #241
Changelog-Fixed: Scroll to bottom when new DM received
2023-01-05 10:51:42 -08:00
Ben Weeks
553dcb785c Updated the new post button with gradient fill.
Changelog-Changed: Added damus gradient to post button
Closes: #244
2023-01-05 10:42:31 -08:00
Thomas
22008aeabc Center the Post Button
Closes: #245
Changelog-Changed: Center the Post Button
2023-01-05 10:31:29 -08:00
Joel Klabo
64bb28e017 Make Event in Reply View Scrollable
Closes: #248
Changelog-Fixed: Make reply view scrollable
2023-01-05 09:20:49 -08:00
Swift
c58c349053 Hide profile edit button for pubkey user
Closes: #249
Changelog-Fixed: Hide profile edit button when logged in with pubkey
2023-01-05 09:19:25 -08:00
William Casarin
4d358415bd nip05: switch yellowcheck to graycheck
Changelog-Changed: Switch yellow nip05 check to gray
2023-01-04 10:16:35 -08:00
William Casarin
5ba5a68628 Switch from bluecheck to purplecheck
This commit also refactors a bunch of crap

Changelog-Changed: Switch from bluecheck to purplecheck
2023-01-04 10:06:19 -08:00
William Casarin
89c857d3e9 v1.0.0-4 changelog 2023-01-04 02:04:00 -08:00
William Casarin
4d3a3184b4 nip05: make hosts searchable 2023-01-04 02:01:57 -08:00
William Casarin
71b1a9d14f nip05: never show full host in event profile names 2023-01-04 02:01:43 -08:00
William Casarin
8785f31834 NIP05 Verification
Changelog-Added: Added NIP05 Verification
2023-01-04 01:30:37 -08:00
William Casarin
104205394f nip05: move nip05 parsing to struct
Going to use this when checking the nip05 identifier
2023-01-04 00:07:08 -08:00
William Casarin
6593c9456d profile: show white circle on buttons in dark mode 2023-01-03 23:49:02 -08:00
William Casarin
186954195d profile: tweak button positioning 2023-01-03 23:42:01 -08:00
William Casarin
dba31a9d33 profile: remove redundant pfp border circle 2023-01-03 23:42:01 -08:00
Jonathan Milligan
a69fb5306c style: Make the post button purple
Since everything on damus is now purple to match the purple ostrich
mascot I made the background of the post button purple.
2023-01-03 23:42:01 -08:00
William Casarin
fb1bcdd31f Revert to old style ln/dm buttons
Until we have high-res icons

Changelog-Changed: Revert to old style ln/dm buttons
2023-01-03 23:42:01 -08:00
William Casarin
2ccc7e9a30 Build 4 2023-01-03 23:08:38 -08:00
Lionello Lunesu
e9380c3821 Fix ascii shrug guy
Changelog-Fixed: Fix ascii shrug guy
Closes: #238
2023-01-03 23:05:41 -08:00
OlegAba
39fa973a80 Add KingFisher downsampler processor for large images
Changelog-Added: Downscale images if they are unreasonably large
Closes: #240
2023-01-03 23:04:08 -08:00
William Casarin
b70ce53b88 navigation: fix navigation popping issues in threads
Changelog-Fixed: Fix navigation popping in threads
2023-01-03 23:00:22 -08:00
William Casarin
16e3c4e1cf Fix crash with link previews
Changelog-Fixed: Fixed crash with link previews
2023-01-03 11:42:23 -08:00
William Casarin
b3b8a708f3 v1.0.0-2 changelog 2023-01-03 11:18:48 -08:00
William Casarin
d89e3d32f8 v1.0.0-2 2023-01-03 11:16:41 -08:00
William Casarin
b99e9b9acc use muted shaka in EventActionBar 2023-01-03 11:15:10 -08:00
William Casarin
61974ca696 rename nostr-hello to shaka 2023-01-03 11:15:02 -08:00
Lionello Lunesu
42f484bc64 Fix detection of email addresses in profiles
Signed-off-by: Lionello Lunesu <lio+git@lunesu.com>
Changelog-Fixed: Fix detection of email addresses in profiles
Closes: #225
2023-01-03 10:55:25 -08:00
OlegAba
43c6084620 Fix padding on search results view
Changelog-Fixed: Fix padding on search results view
Closes: #232
2023-01-03 10:55:25 -08:00
CutClout
501412e75c Add muted shaka images
Uploading four variations of "🤙" images:
black outline
white outline
solid black
solid white

In three different sizes:
20x20, @2x, and at @3x

Added contents.json imageset file

*They are only the images, I have not changed any code within the app.

Would you please add the images where they are needed? or point me in the right direction? I have much yet to learn.

I hope these are the versions you need. If there are any adjustments, please let me know.

Changelog-Changed: Added muted shaka images instead of thumbs up
Closes: #226
2023-01-03 10:55:25 -08:00
OlegAba
5789cc0097 Fix home view moving after selecting from search result
Changelog-Fixed: Fix home view moving after selecting from search result
Closes: #233
2023-01-03 10:55:25 -08:00
William Casarin
d4995aa4bf Tweaks to the new profile page 2023-01-02 19:30:27 -08:00
Ben Weeks
57dbb6a487 Updated the profile look and feel
Closes: #203
Changelog-Changed: Updated profile page look and feel
2023-01-02 19:30:18 -08:00
Joel Klabo
7f71ddce1d Move Relay Add Button to Section Header 2023-01-02 19:06:18 -08:00
William Casarin
abf736ec2a Fix bug where boost event is loaded in the thread instead of the boosted event
Changelog-Fixed: Fix bug where boost event is loaded in the thread instead of the boosted event
2023-01-02 18:58:23 -08:00
Nitesh Balusu
2cfcc82b2d Filter out replies from global feed
Closes: #173
Changelog-Changed: Filter replies from global feed
2023-01-02 18:58:23 -08:00
William Casarin
aaa21bf1bf misc refactors 2023-01-02 18:58:23 -08:00
William Casarin
ba03be5b91 refactor handle_event in SearchHomeModel 2023-01-02 18:58:23 -08:00
William Casarin
3f3b78f9bc EventView: remove weird embedded thing 2023-01-02 18:45:30 -08:00
Swift
2348f64dff Hide Edit Button on Profile Page for the user not logged in w/Private Key
Changelog-Fixed: Hide edit button on profile page when no private key
Closes: #215
2023-01-02 18:33:09 -08:00
8428f0af43 Fix relative time test
Closes: #218
2023-01-02 18:30:56 -08:00
hewigovens
8c91ce3e10 Always check SecRandomCopyBytes return value
Closes: #223
2023-01-02 18:30:24 -08:00
William Casarin
5fd5593595 Revert "threadv2: Fix threads sometimes not loading on first click"
This reverts commit 2e99e5acaa.
2023-01-02 18:29:48 -08:00
William Casarin
2e99e5acaa threadv2: Fix threads sometimes not loading on first click
This makes the initial thread open a bit faster

Changelog-Fixed: Fix threads sometimes not loading on first click
2023-01-02 18:00:04 -08:00
William Casarin
0b7a600c67 perf: fix janky linkpreview animation 2023-01-02 15:25:48 -08:00
William Casarin
bd5390fbc0 actually add PreviewCache oops 2023-01-02 15:23:13 -08:00
William Casarin
068099c5a7 perf: run more kingfisher stuff in the background 2023-01-02 15:19:57 -08:00
William Casarin
7372c4847d perf: cache link previews
Changelog-Added: Cache link previews
2023-01-02 15:19:57 -08:00
William Casarin
b42f0ec5eb Show non-image link inline
Changelog-Changed: Show non-image links inline
2023-01-02 15:19:57 -08:00
William Casarin
0d2ab6aff3 NostrResponse: add sub_id helper
Handy to guard on this
2023-01-02 12:41:28 -08:00
William Casarin
e3732b3adc Add filter helper to FilterState
Will be used in upcoming commits
2023-01-02 12:40:49 -08:00
119 changed files with 2898 additions and 849 deletions

View File

@@ -1,3 +1,88 @@
## [1.0.0-5] - 2023-01-06
### Added
- Added share button to profile (William Casarin)
- Added universal link sharing of notes (William Casarin)
- Added clear cache button to wipe pfp/image cache (OlegAba)
- Allow Adding Relay Without wss:// Prefix (Joel Klabo)
- Allow Saving Images to Library (Joel Klabo)
### Changed
- Added damus gradient to post button (Ben Weeks)
- Center the Post Button (Thomas)
- Switch yellow nip05 check to gray (William Casarin)
- Switch from bluecheck to purplecheck (William Casarin)
### Fixed
- Add system background color to profile pics (OlegAba)
- High res color pubkey on profile page (William Casarin)
- Don't spin forever if we're temporarily disconnected (William Casarin)
- Fixed a few issues with avatars not animating (OlegAba)
- Scroll to bottom when new DM received (Aidan O'Loan)
- Make reply view scrollable (Joel Klabo)
- Hide profile edit button when logged in with pubkey (Swift)
[1.0.0-5]: https://github.com/damus-io/damus/releases/tag/v1.0.0-5
## [1.0.0-4] - 2023-01-04
### Added
- Added NIP05 Verification (William Casarin)
- Downscale images if they are unreasonably large (OlegAba)
### Changed
- Revert to old style ln/dm buttons (William Casarin)
### Fixed
- Fix ascii shrug guy (Lionello Lunesu)
- Fix navigation popping in threads (William Casarin)
[1.0.0-4]: https://github.com/damus-io/damus/releases/tag/v1.0.0-4
## [1.0.0-2] - 2023-01-03
### Added
- Cache link previews (William Casarin)
- Added brb.io to recommended relay list (William Casarin)
- Add Blixt Wallet to Wallet Selector (Benjamin Hakes)
- Add River Wallet to Wallet Selector (Benjamin Hakes)
### Changed
- Added muted shaka images instead of thumbs up (CutClout)
- Updated profile page look and feel (Ben Weeks)
- Filter replies from global feed (Nitesh Balusu)
- Show non-image links inline (William Casarin)
- Add swipe gesture to switch between tabs (Thomas Rademaker)
- Parse links in profiles (Lionello Lunesu) (Lio李歐)
### Fixed
- Fix detection of email addresses in profiles (Lionello Lunesu)
- Fix padding on search results view (OlegAba)
- Fix home view moving after selecting from search result (OlegAba)
- Fix bug where boost event is loaded in the thread instead of the boosted event (William Casarin)
- Hide edit button on profile page when no private key (Swift)
- Fixed follows and relays getting out of sync on profile pages (William Casarin)
[1.0.0-2]: https://github.com/damus-io/damus/releases/tag/v1.0.0-2
## [1.0.0] - 2023-01-01 ## [1.0.0] - 2023-01-01
### Added ### Added
@@ -266,3 +351,5 @@
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2 [0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2

View File

@@ -56,7 +56,7 @@ damus implements the following [Nostr Implementation Possibilities][nips]
- Formatting Notes (may not format as intended in other web clients) - Formatting Notes (may not format as intended in other web clients)
- Italics: 1 asterisk `*italic*` - Italics: 1 asterisk `*italic*`
- Bold: 2 asterisk `**bold**` - Bold: 2 asterisk `**bold**`
- Strikethrough: 2 tildes `~~strikethrough~~` - Strikethrough: 1 tildes `~strikethrough~`
- Code: 1 back-tick ``code`` - Code: 1 back-tick ``code``
#### 💬 Encrypted DMs (chat app, bottom navigation) #### 💬 Encrypted DMs (chat app, bottom navigation)

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd"> <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="damus/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext"> <file original="damus/en-US.lproj/InfoPlist.strings" source-language="en-US" target-language="en-US" datatype="plaintext">
<header> <header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
</header> </header>
@@ -15,9 +15,14 @@
<target>damus</target> <target>damus</target>
<note>Bundle name</note> <note>Bundle name</note>
</trans-unit> </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>
<note>Privacy - Photo Library Additions Usage Description</note>
</trans-unit>
</body> </body>
</file> </file>
<file original="damus/en.lproj/Localizable.strings" source-language="en" target-language="en" datatype="plaintext"> <file original="damus/en-US.lproj/Localizable.strings" source-language="en-US" target-language="en-US" datatype="plaintext">
<header> <header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
</header> </header>
@@ -25,67 +30,87 @@
<trans-unit id=" " xml:space="preserve"> <trans-unit id=" " xml:space="preserve">
<source> </source> <source> </source>
<target> </target> <target> </target>
<note>No comment provided by engineer.</note> <note>Blank space to separate profile picture from profile editor form.</note>
</trans-unit> </trans-unit>
<trans-unit id="%@" xml:space="preserve"> <trans-unit id="%@" xml:space="preserve">
<source>%@</source> <source>%@</source>
<target>%@</target> <target>%@</target>
<note>No comment provided by engineer.</note> <note>Number of people following a user.</note>
</trans-unit> </trans-unit>
<trans-unit id="%@ following" xml:space="preserve"> <trans-unit id="%@ %@" xml:space="preserve">
<source>%@ following</source> <source>%@ %@</source>
<target>%@ following</target> <target>%@ %@</target>
<note>No comment provided by engineer.</note> <note>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'.</note>
</trans-unit>
<trans-unit id="%@'s Followers" xml:space="preserve">
<source>%@'s Followers</source>
<target>%@'s Followers</target>
<note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." xml:space="preserve"> <trans-unit id="%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." xml:space="preserve">
<source>%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.</source> <source>%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.</source>
<target>%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.</target> <target>%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.</target>
<note>No comment provided by engineer.</note> <note>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.</note>
</trans-unit> </trans-unit>
<trans-unit id="%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" xml:space="preserve"> <trans-unit id="%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" xml:space="preserve">
<source>%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs</source> <source>%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs</source>
<target>%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs</target> <target>%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs</target>
<note>No comment provided by engineer.</note> <note>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.</note>
</trans-unit> </trans-unit>
<trans-unit id="%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet." xml:space="preserve"> <trans-unit id="%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet." xml:space="preserve">
<source>%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet.</source> <source>%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet.</source>
<target>%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet.</target> <target>%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet.</target>
<note>No comment provided by engineer.</note> <note>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.</note>
</trans-unit> </trans-unit>
<trans-unit id="%lld" xml:space="preserve"> <trans-unit id="%lld" xml:space="preserve">
<source>%lld</source> <source>%lld</source>
<target>%lld</target> <target>%lld</target>
<note>No comment provided by engineer.</note> <note>Number of profiles a user is following.</note>
</trans-unit> </trans-unit>
<trans-unit id="%lld/%lld" xml:space="preserve"> <trans-unit id="%lld/%lld" xml:space="preserve">
<source>%lld/%lld</source> <source>%lld/%lld</source>
<target>%lld/%lld</target> <target>%lld/%lld</target>
<note>No comment provided by engineer.</note> <note>Fraction of how many of the user's relay servers that are operational.</note>
</trans-unit> </trans-unit>
<trans-unit id="&amp;nbsp;" xml:space="preserve"> <trans-unit id="&amp;nbsp;" xml:space="preserve">
<source>&amp;nbsp;</source> <source>&amp;nbsp;</source>
<target>&amp;nbsp;</target> <target>&amp;nbsp;</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="+" xml:space="preserve"> <trans-unit id="'%@' at '%@' will be used for verification" xml:space="preserve">
<source>+</source> <source>'%@' at '%@' will be used for verification</source>
<target>+</target> <target>'%@' at '%@' will be used for verification</target>
<note>No comment provided by engineer.</note> <note>Description of how the nip05 identifier would be used for verification.</note>
</trans-unit>
<trans-unit id="'%@' is an invalid nip05 identifier. It should look like an email." xml:space="preserve">
<source>'%@' is an invalid nip05 identifier. It should look like an email.</source>
<target>'%@' is an invalid nip05 identifier. It should look like an email.</target>
<note>Description of why the nip05 identifier is invalid.</note>
</trans-unit>
<trans-unit id="(Profile.displayName(profile: profile, pubkey: whos))'s Followers" xml:space="preserve">
<source>(Profile.displayName(profile: profile, pubkey: whos))'s Followers</source>
<target>(Profile.displayName(profile: profile, pubkey: whos))'s Followers</target>
<note>Navigation bar title for view that shows who is following a user.</note>
</trans-unit>
<trans-unit id="(formattedSats) sat" xml:space="preserve">
<source>(formattedSats) sat</source>
<target>(formattedSats) sat</target>
<note>Amount of 1 sat.</note>
</trans-unit>
<trans-unit id="(formattedSats) sats" xml:space="preserve">
<source>(formattedSats) sats</source>
<target>(formattedSats) sats</target>
<note>Amount of sats.</note>
</trans-unit>
<trans-unit id="(who) following" xml:space="preserve">
<source>(who) following</source>
<target>(who) following</target>
<note>Navigation bar title for view that shows who a user is following.</note>
</trans-unit> </trans-unit>
<trans-unit id="&lt; e &gt;" xml:space="preserve"> <trans-unit id="&lt; e &gt;" xml:space="preserve">
<source>&lt; e &gt;</source> <source>&lt; e &gt;</source>
<target>&lt; e &gt;</target> <target>&lt; e &gt;</target>
<note>No comment provided by engineer.</note> <note>Placeholder for event mention.</note>
</trans-unit> </trans-unit>
<trans-unit id="@" xml:space="preserve"> <trans-unit id="@" xml:space="preserve">
<source>@</source> <source>@</source>
<target>@</target> <target>@</target>
<note>No comment provided by engineer.</note> <note>Prefix character to username.</note>
</trans-unit> </trans-unit>
<trans-unit id="About" xml:space="preserve"> <trans-unit id="About" xml:space="preserve">
<source>About</source> <source>About</source>
@@ -95,7 +120,7 @@
<trans-unit id="About Me" xml:space="preserve"> <trans-unit id="About Me" xml:space="preserve">
<source>About Me</source> <source>About Me</source>
<target>About Me</target> <target>About Me</target>
<note>No comment provided by engineer.</note> <note>Label for About Me section of user profile form.</note>
</trans-unit> </trans-unit>
<trans-unit id="Absolute Boss" xml:space="preserve"> <trans-unit id="Absolute Boss" xml:space="preserve">
<source>Absolute Boss</source> <source>Absolute Boss</source>
@@ -110,22 +135,28 @@
<trans-unit id="Add" xml:space="preserve"> <trans-unit id="Add" xml:space="preserve">
<source>Add</source> <source>Add</source>
<target>Add</target> <target>Add</target>
<note>No comment provided by engineer.</note> <note>Button to add recommended relay server.
Button to confirm adding user inputted relay.</note>
</trans-unit> </trans-unit>
<trans-unit id="Add Relay" xml:space="preserve"> <trans-unit id="Add Relay" xml:space="preserve">
<source>Add Relay</source> <source>Add Relay</source>
<target>Add Relay</target> <target>Add Relay</target>
<note>No comment provided by engineer.</note> <note>Label for section for adding a relay server.</note>
</trans-unit>
<trans-unit id="Any" xml:space="preserve">
<source>Any</source>
<target>Any</target>
<note>Any amount of sats</note>
</trans-unit> </trans-unit>
<trans-unit id="Are you sure you want to boost this post?" xml:space="preserve"> <trans-unit id="Are you sure you want to boost this post?" xml:space="preserve">
<source>Are you sure you want to boost this post?</source> <source>Are you sure you want to boost this post?</source>
<target>Are you sure you want to boost this post?</target> <target>Are you sure you want to boost this post?</target>
<note>No comment provided by engineer.</note> <note>Alert message to ask if user wants to boost a post.</note>
</trans-unit> </trans-unit>
<trans-unit id="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." xml:space="preserve"> <trans-unit id="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." xml:space="preserve">
<source>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.</source> <source>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.</source>
<target>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.</target> <target>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.</target>
<note>No comment provided by engineer.</note> <note>Reminder to user that they should save their account information.</note>
</trans-unit> </trans-unit>
<trans-unit id="Bitcoin Beach" xml:space="preserve"> <trans-unit id="Bitcoin Beach" xml:space="preserve">
<source>Bitcoin Beach</source> <source>Bitcoin Beach</source>
@@ -135,7 +166,7 @@
<trans-unit id="Bitcoin Lightning Tips" xml:space="preserve"> <trans-unit id="Bitcoin Lightning Tips" xml:space="preserve">
<source>Bitcoin Lightning Tips</source> <source>Bitcoin Lightning Tips</source>
<target>Bitcoin Lightning Tips</target> <target>Bitcoin Lightning Tips</target>
<note>No comment provided by engineer.</note> <note>Label for Bitcoin Lightning Tips section of user profile form.</note>
</trans-unit> </trans-unit>
<trans-unit id="Blixt Wallet" xml:space="preserve"> <trans-unit id="Blixt Wallet" xml:space="preserve">
<source>Blixt Wallet</source> <source>Blixt Wallet</source>
@@ -150,12 +181,13 @@
<trans-unit id="Boost" xml:space="preserve"> <trans-unit id="Boost" xml:space="preserve">
<source>Boost</source> <source>Boost</source>
<target>Boost</target> <target>Boost</target>
<note>No comment provided by engineer.</note> <note>Button to confirm boosting a post.
Title of alert for confirming to boost a post.</note>
</trans-unit> </trans-unit>
<trans-unit id="Boosted" xml:space="preserve"> <trans-unit id="Boosted" xml:space="preserve">
<source>Boosted</source> <source>Boosted</source>
<target>Boosted</target> <target>Boosted</target>
<note>No comment provided by engineer.</note> <note>Text indicating that the post was boosted (i.e. re-shared).</note>
</trans-unit> </trans-unit>
<trans-unit id="Breez" xml:space="preserve"> <trans-unit id="Breez" xml:space="preserve">
<source>Breez</source> <source>Breez</source>
@@ -165,72 +197,90 @@
<trans-unit id="Broadcast" xml:space="preserve"> <trans-unit id="Broadcast" xml:space="preserve">
<source>Broadcast</source> <source>Broadcast</source>
<target>Broadcast</target> <target>Broadcast</target>
<note>No comment provided by engineer.</note> <note>Context menu option for broadcasting the user's note to all of the user's connected relay servers.</note>
</trans-unit> </trans-unit>
<trans-unit id="Cancel" xml:space="preserve"> <trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source> <source>Cancel</source>
<target>Cancel</target> <target>Cancel</target>
<note>No comment provided by engineer.</note> <note>Button to cancel out of posting a note.
Button to cancel out of view adding user inputted relay.
Cancel out of logging out the user.</note>
</trans-unit> </trans-unit>
<trans-unit id="Cash App" xml:space="preserve"> <trans-unit id="Cash App" xml:space="preserve">
<source>Cash App</source> <source>Cash App</source>
<target>Cash App</target> <target>Cash App</target>
<note>Dropdown option label for Lightning wallet, Cash App.</note> <note>Dropdown option label for Lightning wallet, Cash App.</note>
</trans-unit> </trans-unit>
<trans-unit id="Chat" xml:space="preserve">
<source>Chat</source>
<target>Chat</target>
<note>Navigation bar title for Chatroom view.</note>
</trans-unit>
<trans-unit id="Clear" xml:space="preserve">
<source>Clear</source>
<target>Clear</target>
<note>Button for clearing cached data.</note>
</trans-unit>
<trans-unit id="Clear Cache" xml:space="preserve">
<source>Clear Cache</source>
<target>Clear Cache</target>
<note>Section title for clearing cached data.</note>
</trans-unit>
<trans-unit id="Copied" xml:space="preserve"> <trans-unit id="Copied" xml:space="preserve">
<source>Copied</source> <source>Copied</source>
<target>Copied</target> <target>Copied</target>
<note>No comment provided by engineer.</note> <note>Label indicating that a user's key was copied.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy" xml:space="preserve"> <trans-unit id="Copy" xml:space="preserve">
<source>Copy</source> <source>Copy</source>
<target>Copy</target> <target>Copy</target>
<note>No comment provided by engineer.</note> <note>Button to copy a relay server address.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy Account ID" xml:space="preserve"> <trans-unit id="Copy Account ID" xml:space="preserve">
<source>Copy Account ID</source> <source>Copy Account ID</source>
<target>Copy Account ID</target> <target>Copy Account ID</target>
<note>No comment provided by engineer.</note> <note>Context menu option for copying the ID of the account that created the note.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy Image" xml:space="preserve"> <trans-unit id="Copy Image" xml:space="preserve">
<source>Copy Image</source> <source>Copy Image</source>
<target>Copy Image</target> <target>Copy Image</target>
<note>No comment provided by engineer.</note> <note>Context menu option to copy an image into clipboard.
Context menu option to copy an image to clipboard.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy Image URL" xml:space="preserve"> <trans-unit id="Copy Image URL" xml:space="preserve">
<source>Copy Image URL</source> <source>Copy Image URL</source>
<target>Copy Image URL</target> <target>Copy Image URL</target>
<note>No comment provided by engineer.</note> <note>Context menu option to copy the URL of an image into clipboard.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy LNURL" xml:space="preserve"> <trans-unit id="Copy LNURL" xml:space="preserve">
<source>Copy LNURL</source> <source>Copy LNURL</source>
<target>Copy LNURL</target> <target>Copy LNURL</target>
<note>No comment provided by engineer.</note> <note>Context menu option for copying a user's Lightning URL.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy Note ID" xml:space="preserve"> <trans-unit id="Copy Note ID" xml:space="preserve">
<source>Copy Note ID</source> <source>Copy Note ID</source>
<target>Copy Note ID</target> <target>Copy Note ID</target>
<note>No comment provided by engineer.</note> <note>Context menu option for copying the ID of the note.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy Note JSON" xml:space="preserve"> <trans-unit id="Copy Note JSON" xml:space="preserve">
<source>Copy Note JSON</source> <source>Copy Note JSON</source>
<target>Copy Note JSON</target> <target>Copy Note JSON</target>
<note>No comment provided by engineer.</note> <note>Context menu option for copying the JSON text from the note.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy Text" xml:space="preserve"> <trans-unit id="Copy Text" xml:space="preserve">
<source>Copy Text</source> <source>Copy Text</source>
<target>Copy Text</target> <target>Copy Text</target>
<note>No comment provided by engineer.</note> <note>Context menu option for copying the text from an note.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy User ID" xml:space="preserve"> <trans-unit id="Copy User ID" xml:space="preserve">
<source>Copy User ID</source> <source>Copy User ID</source>
<target>Copy User ID</target> <target>Copy User ID</target>
<note>No comment provided by engineer.</note> <note>Context menu option for copying the ID of the user who created the note.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy invoice" xml:space="preserve"> <trans-unit id="Copy invoice" xml:space="preserve">
<source>Copy invoice</source> <source>Copy invoice</source>
<target>Copy invoice</target> <target>Copy invoice</target>
<note>No comment provided by engineer.</note> <note>Title of section for copying a Lightning invoice identifier.</note>
</trans-unit> </trans-unit>
<trans-unit id="Create" xml:space="preserve"> <trans-unit id="Create" xml:space="preserve">
<source>Create</source> <source>Create</source>
@@ -250,27 +300,27 @@
<trans-unit id="DM" xml:space="preserve"> <trans-unit id="DM" xml:space="preserve">
<source>DM</source> <source>DM</source>
<target>DM</target> <target>DM</target>
<note>No comment provided by engineer.</note> <note>Navigation title for DM view, which is the English abbreviation for Direct Message.</note>
</trans-unit> </trans-unit>
<trans-unit id="Damus" xml:space="preserve"> <trans-unit id="Damus" xml:space="preserve">
<source>Damus</source> <source>Damus</source>
<target>Damus</target> <target>Damus</target>
<note>No comment provided by engineer.</note> <note>Name of the app, shown on the first screen when user is not logged in.</note>
</trans-unit> </trans-unit>
<trans-unit id="Default Wallet" xml:space="preserve"> <trans-unit id="Default Wallet" xml:space="preserve">
<source>Default Wallet</source> <source>Default Wallet</source>
<target>Default Wallet</target> <target>Default Wallet</target>
<note>No comment provided by engineer.</note> <note>Button to pay a Lightning invoice with the user's default Lightning wallet.</note>
</trans-unit> </trans-unit>
<trans-unit id="Delete" xml:space="preserve"> <trans-unit id="Delete" xml:space="preserve">
<source>Delete</source> <source>Delete</source>
<target>Delete</target> <target>Delete</target>
<note>No comment provided by engineer.</note> <note>Button to delete a relay server that the user connects to.</note>
</trans-unit> </trans-unit>
<trans-unit id="Dismiss" xml:space="preserve"> <trans-unit id="Dismiss" xml:space="preserve">
<source>Dismiss</source> <source>Dismiss</source>
<target>Dismiss</target> <target>Dismiss</target>
<note>No comment provided by engineer.</note> <note>Button to dismiss a text field alert.</note>
</trans-unit> </trans-unit>
<trans-unit id="Display Name" xml:space="preserve"> <trans-unit id="Display Name" xml:space="preserve">
<source>Display Name</source> <source>Display Name</source>
@@ -280,47 +330,47 @@
<trans-unit id="Done" xml:space="preserve"> <trans-unit id="Done" xml:space="preserve">
<source>Done</source> <source>Done</source>
<target>Done</target> <target>Done</target>
<note>No comment provided by engineer.</note> <note>Button to dismiss wallet selection view for paying Lightning invoice.</note>
</trans-unit> </trans-unit>
<trans-unit id="Earn Money" xml:space="preserve"> <trans-unit id="Earn Money" xml:space="preserve">
<source>Earn Money</source> <source>Earn Money</source>
<target>Earn Money</target> <target>Earn Money</target>
<note>No comment provided by engineer.</note> <note>Heading indicating that this application allows users to earn money.</note>
</trans-unit> </trans-unit>
<trans-unit id="Edit" xml:space="preserve"> <trans-unit id="Edit" xml:space="preserve">
<source>Edit</source> <source>Edit</source>
<target>Edit</target> <target>Edit</target>
<note>No comment provided by engineer.</note> <note>Button to edit user's profile.</note>
</trans-unit> </trans-unit>
<trans-unit id="Edit Profile" xml:space="preserve"> <trans-unit id="Edit Profile" xml:space="preserve">
<source>Edit Profile</source> <source>Edit Profile</source>
<target>Edit Profile</target> <target>Edit Profile</target>
<note>No comment provided by engineer.</note> <note>Title of navigation view for Edit Profile.</note>
</trans-unit> </trans-unit>
<trans-unit id="Encrypted" xml:space="preserve"> <trans-unit id="Encrypted" xml:space="preserve">
<source>Encrypted</source> <source>Encrypted</source>
<target>Encrypted</target> <target>Encrypted</target>
<note>No comment provided by engineer.</note> <note>Heading indicating that this application keeps private messaging end-to-end encrypted.</note>
</trans-unit> </trans-unit>
<trans-unit id="Encrypted DMs" xml:space="preserve"> <trans-unit id="Encrypted DMs" xml:space="preserve">
<source>Encrypted DMs</source> <source>Encrypted DMs</source>
<target>Encrypted DMs</target> <target>Encrypted DMs</target>
<note>No comment provided by engineer.</note> <note>Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message.</note>
</trans-unit> </trans-unit>
<trans-unit id="Enter your account key to login:" xml:space="preserve"> <trans-unit id="Enter your account key to login:" xml:space="preserve">
<source>Enter your account key to login:</source> <source>Enter your account key to login:</source>
<target>Enter your account key to login:</target> <target>Enter your account key to login:</target>
<note>No comment provided by engineer.</note> <note>Prompt for user to enter an account key to login.</note>
</trans-unit> </trans-unit>
<trans-unit id="Error: %@" xml:space="preserve"> <trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source> <source>Error: %@</source>
<target>Error: %@</target> <target>Error: %@</target>
<note>No comment provided by engineer.</note> <note>Error message indicating why saving keys failed.</note>
</trans-unit> </trans-unit>
<trans-unit id="Filter State" xml:space="preserve"> <trans-unit id="Filter State" xml:space="preserve">
<source>Filter State</source> <source>Filter State</source>
<target>Filter State</target> <target>Filter State</target>
<note>No comment provided by engineer.</note> <note>Filter state for seeing either only posts, or posts &amp; replies.</note>
</trans-unit> </trans-unit>
<trans-unit id="Follow" xml:space="preserve"> <trans-unit id="Follow" xml:space="preserve">
<source>Follow</source> <source>Follow</source>
@@ -330,12 +380,13 @@
<trans-unit id="Followers" xml:space="preserve"> <trans-unit id="Followers" xml:space="preserve">
<source>Followers</source> <source>Followers</source>
<target>Followers</target> <target>Followers</target>
<note>No comment provided by engineer.</note> <note>Label describing followers of a user.</note>
</trans-unit> </trans-unit>
<trans-unit id="Following" xml:space="preserve"> <trans-unit id="Following" xml:space="preserve">
<source>Following</source> <source>Following</source>
<target>Following</target> <target>Following</target>
<note>No comment provided by engineer.</note> <note>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.</note>
</trans-unit> </trans-unit>
<trans-unit id="Following..." xml:space="preserve"> <trans-unit id="Following..." xml:space="preserve">
<source>Following...</source> <source>Following...</source>
@@ -345,27 +396,32 @@
<trans-unit id="Follows" xml:space="preserve"> <trans-unit id="Follows" xml:space="preserve">
<source>Follows</source> <source>Follows</source>
<target>Follows</target> <target>Follows</target>
<note>No comment provided by engineer.</note> <note>Text to indicate that button next to it is in a state that will follow a profile when tapped.</note>
</trans-unit> </trans-unit>
<trans-unit id="Global" xml:space="preserve"> <trans-unit id="Global" xml:space="preserve">
<source>Global</source> <source>Global</source>
<target>Global</target> <target>Global</target>
<note>No comment provided by engineer.</note> <note>Navigation bar title for Global view where posts from all connected relay servers appear.</note>
</trans-unit> </trans-unit>
<trans-unit id="Goto post %@" xml:space="preserve"> <trans-unit id="Goto post %@" xml:space="preserve">
<source>Goto post %@</source> <source>Goto post %@</source>
<target>Goto post %@</target> <target>Goto post %@</target>
<note>No comment provided by engineer.</note> <note>Navigation link to go to post referenced by hex code.</note>
</trans-unit> </trans-unit>
<trans-unit id="Goto profile %@" xml:space="preserve"> <trans-unit id="Goto profile %@" xml:space="preserve">
<source>Goto profile %@</source> <source>Goto profile %@</source>
<target>Goto profile %@</target> <target>Goto profile %@</target>
<note>No comment provided by engineer.</note> <note>Navigation link to go to profile.</note>
</trans-unit> </trans-unit>
<trans-unit id="Home" xml:space="preserve"> <trans-unit id="Home" xml:space="preserve">
<source>Home</source> <source>Home</source>
<target>Home</target> <target>Home</target>
<note>No comment provided by engineer.</note> <note>Navigation bar title for Home view where posts and replies appear from those who the user is following.</note>
</trans-unit>
<trans-unit id="Invalid key" xml:space="preserve">
<source>Invalid key</source>
<target>Invalid key</target>
<note>Error message indicating that an invalid account key was entered for login.</note>
</trans-unit> </trans-unit>
<trans-unit id="LNLink" xml:space="preserve"> <trans-unit id="LNLink" xml:space="preserve">
<source>LNLink</source> <source>LNLink</source>
@@ -380,12 +436,12 @@
<trans-unit id="Lightning Address or LNURL" xml:space="preserve"> <trans-unit id="Lightning Address or LNURL" xml:space="preserve">
<source>Lightning Address or LNURL</source> <source>Lightning Address or LNURL</source>
<target>Lightning Address or LNURL</target> <target>Lightning Address or LNURL</target>
<note>No comment provided by engineer.</note> <note>Placeholder text for entry of Lightning Address or LNURL.</note>
</trans-unit> </trans-unit>
<trans-unit id="Lightning Invoice" xml:space="preserve"> <trans-unit id="Lightning Invoice" xml:space="preserve">
<source>Lightning Invoice</source> <source>Lightning Invoice</source>
<target>Lightning Invoice</target> <target>Lightning Invoice</target>
<note>No comment provided by engineer.</note> <note>Indicates that the view is for paying a Lightning invoice.</note>
</trans-unit> </trans-unit>
<trans-unit id="Local default" xml:space="preserve"> <trans-unit id="Local default" xml:space="preserve">
<source>Local default</source> <source>Local default</source>
@@ -395,17 +451,19 @@
<trans-unit id="Login" xml:space="preserve"> <trans-unit id="Login" xml:space="preserve">
<source>Login</source> <source>Login</source>
<target>Login</target> <target>Login</target>
<note>No comment provided by engineer.</note> <note>Button to log into account.</note>
</trans-unit> </trans-unit>
<trans-unit id="Logout" xml:space="preserve"> <trans-unit id="Logout" xml:space="preserve">
<source>Logout</source> <source>Logout</source>
<target>Logout</target> <target>Logout</target>
<note>No comment provided by engineer.</note> <note>Alert for logging out the user.
Button for logging out the user.
Button to logout the user.</note>
</trans-unit> </trans-unit>
<trans-unit id="Make sure your nsec account key is saved before you logout or you will lose access to this account" xml:space="preserve"> <trans-unit id="Make sure your nsec account key is saved before you logout or you will lose access to this account" xml:space="preserve">
<source>Make sure your nsec account key is saved before you logout or you will lose access to this account</source> <source>Make sure your nsec account key is saved before you logout or you will lose access to this account</source>
<target>Make sure your nsec account key is saved before you logout or you will lose access to this account</target> <target>Make sure your nsec account key is saved before you logout or you will lose access to this account</target>
<note>No comment provided by engineer.</note> <note>Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out.</note>
</trans-unit> </trans-unit>
<trans-unit id="Muun" xml:space="preserve"> <trans-unit id="Muun" xml:space="preserve">
<source>Muun</source> <source>Muun</source>
@@ -415,27 +473,27 @@
<trans-unit id="NIP-05 Verification" xml:space="preserve"> <trans-unit id="NIP-05 Verification" xml:space="preserve">
<source>NIP-05 Verification</source> <source>NIP-05 Verification</source>
<target>NIP-05 Verification</target> <target>NIP-05 Verification</target>
<note>No comment provided by engineer.</note> <note>Label for NIP-05 Verification section of user profile form.</note>
</trans-unit> </trans-unit>
<trans-unit id="Nothing to see here. Check back later!" xml:space="preserve"> <trans-unit id="Nothing to see here. Check back later!" xml:space="preserve">
<source>Nothing to see here. Check back later!</source> <source>Nothing to see here. Check back later!</source>
<target>Nothing to see here. Check back later!</target> <target>Nothing to see here. Check back later!</target>
<note>No comment provided by engineer.</note> <note>Indicates that there are no notes in the timeline to view.</note>
</trans-unit> </trans-unit>
<trans-unit id="Notifications" xml:space="preserve"> <trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source> <source>Notifications</source>
<target>Notifications</target> <target>Notifications</target>
<note>No comment provided by engineer.</note> <note>Navigation title for notifications.</note>
</trans-unit> </trans-unit>
<trans-unit id="Pay" xml:space="preserve"> <trans-unit id="Pay" xml:space="preserve">
<source>Pay</source> <source>Pay</source>
<target>Pay</target> <target>Pay</target>
<note>No comment provided by engineer.</note> <note>Button to pay a Lightning invoice.</note>
</trans-unit> </trans-unit>
<trans-unit id="Pay the lightning invoice" xml:space="preserve"> <trans-unit id="Pay the Lightning invoice" xml:space="preserve">
<source>Pay the lightning invoice</source> <source>Pay the Lightning invoice</source>
<target>Pay the lightning invoice</target> <target>Pay the Lightning invoice</target>
<note>No comment provided by engineer.</note> <note>Navigation bar title for view to pay Lightning invoice.</note>
</trans-unit> </trans-unit>
<trans-unit id="Phoenix" xml:space="preserve"> <trans-unit id="Phoenix" xml:space="preserve">
<source>Phoenix</source> <source>Phoenix</source>
@@ -445,82 +503,93 @@
<trans-unit id="Post" xml:space="preserve"> <trans-unit id="Post" xml:space="preserve">
<source>Post</source> <source>Post</source>
<target>Post</target> <target>Post</target>
<note>No comment provided by engineer.</note> <note>Button to post a note.</note>
</trans-unit> </trans-unit>
<trans-unit id="Posts" xml:space="preserve"> <trans-unit id="Posts" xml:space="preserve">
<source>Posts</source> <source>Posts</source>
<target>Posts</target> <target>Posts</target>
<note>No comment provided by engineer.</note> <note>Label for filter for seeing only posts (instead of posts and replies).</note>
</trans-unit> </trans-unit>
<trans-unit id="Posts &amp; Replies" xml:space="preserve"> <trans-unit id="Posts &amp; Replies" xml:space="preserve">
<source>Posts &amp; Replies</source> <source>Posts &amp; Replies</source>
<target>Posts &amp; Replies</target> <target>Posts &amp; Replies</target>
<note>No comment provided by engineer.</note> <note>Label for filter for seeing posts and replies (instead of only posts).</note>
</trans-unit> </trans-unit>
<trans-unit id="Private" xml:space="preserve"> <trans-unit id="Private" xml:space="preserve">
<source>Private</source> <source>Private</source>
<target>Private</target> <target>Private</target>
<note>No comment provided by engineer.</note> <note>Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading.</note>
</trans-unit> </trans-unit>
<trans-unit id="Private Key" xml:space="preserve"> <trans-unit id="Private Key" xml:space="preserve">
<source>Private Key</source> <source>Private Key</source>
<target>Private Key</target> <target>Private Key</target>
<note>No comment provided by engineer.</note> <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>
<trans-unit id="PrivateKey" xml:space="preserve"> <trans-unit id="PrivateKey" xml:space="preserve">
<source>PrivateKey</source> <source>PrivateKey</source>
<target>PrivateKey</target> <target>PrivateKey</target>
<note>No comment provided by engineer.</note> <note>Title of the secure field that holds the user's private key.</note>
</trans-unit> </trans-unit>
<trans-unit id="Profile Picture" xml:space="preserve"> <trans-unit id="Profile Picture" xml:space="preserve">
<source>Profile Picture</source> <source>Profile Picture</source>
<target>Profile Picture</target> <target>Profile Picture</target>
<note>No comment provided by engineer.</note> <note>Label for Profile Picture section of user profile form.</note>
</trans-unit> </trans-unit>
<trans-unit id="Public Account ID" xml:space="preserve"> <trans-unit id="Public Account ID" xml:space="preserve">
<source>Public Account ID</source> <source>Public Account ID</source>
<target>Public Account ID</target> <target>Public Account ID</target>
<note>No comment provided by engineer.</note> <note>Section title for the user's public account ID.</note>
</trans-unit> </trans-unit>
<trans-unit id="Public Key" xml:space="preserve"> <trans-unit id="Public Key" xml:space="preserve">
<source>Public Key</source> <source>Public Key</source>
<target>Public Key</target> <target>Public Key</target>
<note>No comment provided by engineer.</note> <note>Label indicating that the text is a user's public account key.</note>
</trans-unit> </trans-unit>
<trans-unit id="Public Key?" xml:space="preserve"> <trans-unit id="Public Key?" xml:space="preserve">
<source>Public Key?</source> <source>Public Key?</source>
<target>Public Key?</target> <target>Public Key?</target>
<note>No comment provided by engineer.</note> <note>Prompt to ask user if the key they entered is a public key.</note>
</trans-unit>
<trans-unit id="Public key" xml:space="preserve">
<source>Public key</source>
<target>Public key</target>
<note>Label indicating that the text is a user's public account key.</note>
</trans-unit> </trans-unit>
<trans-unit id="Recommended Relays" xml:space="preserve"> <trans-unit id="Recommended Relays" xml:space="preserve">
<source>Recommended Relays</source> <source>Recommended Relays</source>
<target>Recommended Relays</target> <target>Recommended Relays</target>
<note>No comment provided by engineer.</note> <note>Section title for recommend relay servers that could be added as part of configuration</note>
</trans-unit> </trans-unit>
<trans-unit id="Relay" xml:space="preserve"> <trans-unit id="Relay" xml:space="preserve">
<source>Relay</source> <source>Relay</source>
<target>Relay</target> <target>Relay</target>
<note>No comment provided by engineer.</note> <note>Text field for relay server. Used for testing purposes.</note>
</trans-unit> </trans-unit>
<trans-unit id="Relays" xml:space="preserve"> <trans-unit id="Relays" xml:space="preserve">
<source>Relays</source> <source>Relays</source>
<target>Relays</target> <target>Relays</target>
<note>No comment provided by engineer.</note> <note>Header text for relay server list for configuration.
Part of a larger sentence to describe how many relay servers a user is connected.</note>
</trans-unit> </trans-unit>
<trans-unit id="Reply to self" xml:space="preserve"> <trans-unit id="Reply to self" xml:space="preserve">
<source>Reply to self</source> <source>Reply to self</source>
<target>Reply to self</target> <target>Reply to self</target>
<note>Label to indicate that the user is replying to themself.</note> <note>Label to indicate that the user is replying to themself.</note>
</trans-unit> </trans-unit>
<trans-unit id="Replying to %@ &amp; %@" xml:space="preserve">
<source>Replying to %1$@ &amp; %2$@</source>
<target>Replying to %1$@ &amp; %2$@</target>
<note>Label to indicate that the user is replying to 2 users.</note>
</trans-unit>
<trans-unit id="Replying to:" xml:space="preserve"> <trans-unit id="Replying to:" xml:space="preserve">
<source>Replying to:</source> <source>Replying to:</source>
<target>Replying to:</target> <target>Replying to:</target>
<note>No comment provided by engineer.</note> <note>Indicating that the user is replying to the following listed people.</note>
</trans-unit> </trans-unit>
<trans-unit id="Reset" xml:space="preserve"> <trans-unit id="Reset" xml:space="preserve">
<source>Reset</source> <source>Reset</source>
<target>Reset</target> <target>Reset</target>
<note>No comment provided by engineer.</note> <note>Section title for resetting the user</note>
</trans-unit> </trans-unit>
<trans-unit id="River" xml:space="preserve"> <trans-unit id="River" xml:space="preserve">
<source>River</source> <source>River</source>
@@ -535,57 +604,62 @@
<trans-unit id="Save" xml:space="preserve"> <trans-unit id="Save" xml:space="preserve">
<source>Save</source> <source>Save</source>
<target>Save</target> <target>Save</target>
<note>No comment provided by engineer.</note> <note>Button for saving profile.</note>
</trans-unit>
<trans-unit id="Save Image" xml:space="preserve">
<source>Save Image</source>
<target>Save Image</target>
<note>Context menu option to save an image.</note>
</trans-unit> </trans-unit>
<trans-unit id="Search hashtag: #%@" xml:space="preserve"> <trans-unit id="Search hashtag: #%@" xml:space="preserve">
<source>Search hashtag: #%@</source> <source>Search hashtag: #%@</source>
<target>Search hashtag: #%@</target> <target>Search hashtag: #%@</target>
<note>No comment provided by engineer.</note> <note>Navigation link to search hashtag.</note>
</trans-unit> </trans-unit>
<trans-unit id="Search..." xml:space="preserve"> <trans-unit id="Search..." xml:space="preserve">
<source>Search...</source> <source>Search...</source>
<target>Search...</target> <target>Search...</target>
<note>No comment provided by engineer.</note> <note>Placeholder text to prompt entry of search query.</note>
</trans-unit> </trans-unit>
<trans-unit id="Secret Account Login Key" xml:space="preserve"> <trans-unit id="Secret Account Login Key" xml:space="preserve">
<source>Secret Account Login Key</source> <source>Secret Account Login Key</source>
<target>Secret Account Login Key</target> <target>Secret Account Login Key</target>
<note>No comment provided by engineer.</note> <note>Section title for user's secret account login key.</note>
</trans-unit> </trans-unit>
<trans-unit id="Select a lightning wallet" xml:space="preserve"> <trans-unit id="Select a Lightning wallet" xml:space="preserve">
<source>Select a lightning wallet</source> <source>Select a Lightning wallet</source>
<target>Select a lightning wallet</target> <target>Select a Lightning wallet</target>
<note>No comment provided by engineer.</note> <note>Title of section for selecting a Lightning wallet to pay a Lightning invoice.</note>
</trans-unit> </trans-unit>
<trans-unit id="Select default wallet" xml:space="preserve"> <trans-unit id="Select default wallet" xml:space="preserve">
<source>Select default wallet</source> <source>Select default wallet</source>
<target>Select default wallet</target> <target>Select default wallet</target>
<note>No comment provided by engineer.</note> <note>Prompt selection of user's default wallet</note>
</trans-unit> </trans-unit>
<trans-unit id="Send a message to start the conversation..." xml:space="preserve"> <trans-unit id="Send a message to start the conversation..." xml:space="preserve">
<source>Send a message to start the conversation...</source> <source>Send a message to start the conversation...</source>
<target>Send a message to start the conversation...</target> <target>Send a message to start the conversation...</target>
<note>No comment provided by engineer.</note> <note>Text prompt for user to send a message to the other user.</note>
</trans-unit> </trans-unit>
<trans-unit id="Settings" xml:space="preserve"> <trans-unit id="Settings" xml:space="preserve">
<source>Settings</source> <source>Settings</source>
<target>Settings</target> <target>Settings</target>
<note>No comment provided by engineer.</note> <note>Navigation title for Settings view.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
<source>Share</source> <source>Share</source>
<target>Share</target> <target>Share</target>
<note>No comment provided by engineer.</note> <note>Button to share an image.</note>
</trans-unit> </trans-unit>
<trans-unit id="Show" xml:space="preserve"> <trans-unit id="Show" xml:space="preserve">
<source>Show</source> <source>Show</source>
<target>Show</target> <target>Show</target>
<note>No comment provided by engineer.</note> <note>Toggle to show or hide user's secret account login key.</note>
</trans-unit> </trans-unit>
<trans-unit id="Show wallet selector" xml:space="preserve"> <trans-unit id="Show wallet selector" xml:space="preserve">
<source>Show wallet selector</source> <source>Show wallet selector</source>
<target>Show wallet selector</target> <target>Show wallet selector</target>
<note>No comment provided by engineer.</note> <note>Toggle to show or hide selection of wallet.</note>
</trans-unit> </trans-unit>
<trans-unit id="Strike" xml:space="preserve"> <trans-unit id="Strike" xml:space="preserve">
<source>Strike</source> <source>Strike</source>
@@ -595,27 +669,28 @@
<trans-unit id="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." xml:space="preserve"> <trans-unit id="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." xml:space="preserve">
<source>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.</source> <source>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.</source>
<target>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.</target> <target>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.</target>
<note>No comment provided by engineer.</note> <note>Warning that the inputted account key is a public key and the result of what happens because of it.</note>
</trans-unit> </trans-unit>
<trans-unit id="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." xml:space="preserve"> <trans-unit id="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." xml:space="preserve">
<source>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.</source> <source>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.</source>
<target>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.</target> <target>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.</target>
<note>No comment provided by engineer.</note> <note>Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key.</note>
</trans-unit> </trans-unit>
<trans-unit id="This is your account ID, you can give this to your friends so that they can follow you. Click to copy." xml:space="preserve"> <trans-unit id="This is your account ID, you can give this to your friends so that they can follow you. Click to copy." xml:space="preserve">
<source>This is your account ID, you can give this to your friends so that they can follow you. Click to copy.</source> <source>This is your account ID, you can give this to your friends so that they can follow you. Click to copy.</source>
<target>This is your account ID, you can give this to your friends so that they can follow you. Click to copy.</target> <target>This is your account ID, you can give this to your friends so that they can follow you. Click to copy.</target>
<note>No comment provided by engineer.</note> <note>Label to describe that a public key is the user's account ID and what they can do with it.</note>
</trans-unit> </trans-unit>
<trans-unit id="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!" xml:space="preserve"> <trans-unit id="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!" xml:space="preserve">
<source>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!</source> <source>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!</source>
<target>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!</target> <target>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!</target>
<note>No comment provided by engineer.</note> <note>Label to describe that a private key is the user's secret account key and what they should do with it.</note>
</trans-unit> </trans-unit>
<trans-unit id="Thread" xml:space="preserve"> <trans-unit id="Thread" xml:space="preserve">
<source>Thread</source> <source>Thread</source>
<target>Thread</target> <target>Thread</target>
<note>No comment provided by engineer.</note> <note>Navigation bar title for note thread.
Navigation bar title for threaded event detail view.</note>
</trans-unit> </trans-unit>
<trans-unit id="Type your post here..." xml:space="preserve"> <trans-unit id="Type your post here..." xml:space="preserve">
<source>Type your post here...</source> <source>Type your post here...</source>
@@ -630,7 +705,7 @@
<trans-unit id="Unfollowing" xml:space="preserve"> <trans-unit id="Unfollowing" xml:space="preserve">
<source>Unfollowing</source> <source>Unfollowing</source>
<target>Unfollowing</target> <target>Unfollowing</target>
<note>No comment provided by engineer.</note> <note>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.</note>
</trans-unit> </trans-unit>
<trans-unit id="Unfollowing..." xml:space="preserve"> <trans-unit id="Unfollowing..." xml:space="preserve">
<source>Unfollowing...</source> <source>Unfollowing...</source>
@@ -640,12 +715,13 @@
<trans-unit id="Unfollows" xml:space="preserve"> <trans-unit id="Unfollows" xml:space="preserve">
<source>Unfollows</source> <source>Unfollows</source>
<target>Unfollows</target> <target>Unfollows</target>
<note>No comment provided by engineer.</note> <note>Text to indicate that the button next to it is in a state that will unfollow a profile when tapped.</note>
</trans-unit> </trans-unit>
<trans-unit id="Username" xml:space="preserve"> <trans-unit id="Username" xml:space="preserve">
<source>Username</source> <source>Username</source>
<target>Username</target> <target>Username</target>
<note>Label to prompt username entry.</note> <note>Label for Username section of user profile form.
Label to prompt username entry.</note>
</trans-unit> </trans-unit>
<trans-unit id="Wallet Of Satoshi" xml:space="preserve"> <trans-unit id="Wallet Of Satoshi" xml:space="preserve">
<source>Wallet Of Satoshi</source> <source>Wallet Of Satoshi</source>
@@ -655,27 +731,27 @@
<trans-unit id="Wallet Selector" xml:space="preserve"> <trans-unit id="Wallet Selector" xml:space="preserve">
<source>Wallet Selector</source> <source>Wallet Selector</source>
<target>Wallet Selector</target> <target>Wallet Selector</target>
<note>No comment provided by engineer.</note> <note>Section title for selection of wallet.</note>
</trans-unit> </trans-unit>
<trans-unit id="Website" xml:space="preserve"> <trans-unit id="Website" xml:space="preserve">
<source>Website</source> <source>Website</source>
<target>Website</target> <target>Website</target>
<note>No comment provided by engineer.</note> <note>Label for Website section of user profile form.</note>
</trans-unit> </trans-unit>
<trans-unit id="Welcome to the social network %@ control." xml:space="preserve"> <trans-unit id="Welcome to the social network %@ control." xml:space="preserve">
<source>Welcome to the social network %@ control.</source> <source>Welcome to the social network %@ control.</source>
<target>Welcome to the social network %@ control.</target> <target>Welcome to the social network %@ control.</target>
<note>No comment provided by engineer.</note> <note>Welcoming message to the reader. The variable is 'you', the reader.</note>
</trans-unit> </trans-unit>
<trans-unit id="Welcome, %@!" xml:space="preserve"> <trans-unit id="Welcome, %@!" xml:space="preserve">
<source>Welcome, %@!</source> <source>Welcome, %@!</source>
<target>Welcome, %@!</target> <target>Welcome, %@!</target>
<note>No comment provided by engineer.</note> <note>Text to welcome user.</note>
</trans-unit> </trans-unit>
<trans-unit id="Your Name" xml:space="preserve"> <trans-unit id="Your Name" xml:space="preserve">
<source>Your Name</source> <source>Your Name</source>
<target>Your Name</target> <target>Your Name</target>
<note>No comment provided by engineer.</note> <note>Label for Your Name section of user profile form.</note>
</trans-unit> </trans-unit>
<trans-unit id="Zebedee" xml:space="preserve"> <trans-unit id="Zebedee" xml:space="preserve">
<source>Zebedee</source> <source>Zebedee</source>
@@ -687,35 +763,45 @@
<target>Zeus LN</target> <target>Zeus LN</target>
<note>Dropdown option label for Lightning wallet, Zeus LN.</note> <note>Dropdown option label for Lightning wallet, Zeus LN.</note>
</trans-unit> </trans-unit>
<trans-unit id="collapsed_event_view_other_notes" translate="no" xml:space="preserve">
<source>collapsed_event_view_other_notes</source>
<target>collapsed_event_view_other_notes</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="https://example.com/pic.jpg" xml:space="preserve"> <trans-unit id="https://example.com/pic.jpg" xml:space="preserve">
<source>https://example.com/pic.jpg</source> <source>https://example.com/pic.jpg</source>
<target>https://example.com/pic.jpg</target> <target>https://example.com/pic.jpg</target>
<note>No comment provided by engineer.</note> <note>Placeholder example text for profile picture URL.</note>
</trans-unit> </trans-unit>
<trans-unit id="https://jb55.com" xml:space="preserve"> <trans-unit id="https://jb55.com" xml:space="preserve">
<source>https://jb55.com</source> <source>https://jb55.com</source>
<target>https://jb55.com</target> <target>https://jb55.com</target>
<note>No comment provided by engineer.</note> <note>Placeholder example text for website URL for user profile.</note>
</trans-unit> </trans-unit>
<trans-unit id="jb55@jb55.com" xml:space="preserve"> <trans-unit id="jb55@jb55.com" xml:space="preserve">
<source>jb55@jb55.com</source> <source>jb55@jb55.com</source>
<target>jb55@jb55.com</target> <target>jb55@jb55.com</target>
<note>No comment provided by engineer.</note> <note>Placeholder example text for identifier used for NIP-05 verification.</note>
</trans-unit> </trans-unit>
<trans-unit id="none" xml:space="preserve"> <trans-unit id="none" xml:space="preserve">
<source>none</source> <source>none</source>
<target>none</target> <target>none</target>
<note>No comment provided by engineer.</note> <note>No search results.</note>
</trans-unit> </trans-unit>
<trans-unit id="now" xml:space="preserve"> <trans-unit id="now" xml:space="preserve">
<source>now</source> <source>now</source>
<target>now</target> <target>now</target>
<note>String indicating that a given timestamp just occurred</note> <note>String indicating that a given timestamp just occurred</note>
</trans-unit> </trans-unit>
<trans-unit id="nsec1..." xml:space="preserve">
<source>nsec1...</source>
<target>nsec1...</target>
<note>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.</note>
</trans-unit>
<trans-unit id="optional" xml:space="preserve"> <trans-unit id="optional" xml:space="preserve">
<source>optional</source> <source>optional</source>
<target>optional</target> <target>optional</target>
<note>No comment provided by engineer.</note> <note>Label indicating that a form input is optional.</note>
</trans-unit> </trans-unit>
<trans-unit id="replying_to_one_and_others" translate="no" xml:space="preserve"> <trans-unit id="replying_to_one_and_others" translate="no" xml:space="preserve">
<source>replying_to_one_and_others</source> <source>replying_to_one_and_others</source>
@@ -735,66 +821,81 @@
<trans-unit id="wss://some.relay.com" xml:space="preserve"> <trans-unit id="wss://some.relay.com" xml:space="preserve">
<source>wss://some.relay.com</source> <source>wss://some.relay.com</source>
<target>wss://some.relay.com</target> <target>wss://some.relay.com</target>
<note>No comment provided by engineer.</note> <note>Placeholder example for relay server address.</note>
</trans-unit> </trans-unit>
<trans-unit id="you" xml:space="preserve"> <trans-unit id="you" xml:space="preserve">
<source>you</source> <source>you</source>
<target>you</target> <target>you</target>
<note>No comment provided by engineer.</note> <note>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.</note>
</trans-unit>
<trans-unit id="··· %lld other notes ···" xml:space="preserve">
<source>··· %lld other notes ···</source>
<target>··· %lld other notes ···</target>
<note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="🤙" xml:space="preserve"> <trans-unit id="🤙" xml:space="preserve">
<source>🤙</source> <source>🤙</source>
<target>🤙</target> <target>🤙</target>
<note>No comment provided by engineer.</note> <note>Button with emoji to like an event.</note>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>
<file original="damus/en.lproj/Localizable.stringsdict" source-language="en" target-language="en" datatype="plaintext"> <file original="damus/en-US.lproj/Localizable.stringsdict" source-language="en-US" target-language="en-US" datatype="plaintext">
<header> <header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/> <tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
</header> </header>
<body> <body>
<trans-unit id="/collapsed_event_view_other_notes:dict/NOTES:dict/one:dict/:string" xml:space="preserve">
<source>1 other note</source>
<target>1 other note</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/collapsed_event_view_other_notes:dict/NOTES:dict/other:dict/:string" xml:space="preserve">
<source>%d other notes</source>
<target>%d other notes</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/collapsed_event_view_other_notes:dict/NOTES:dict/zero:dict/:string" xml:space="preserve">
<source>0 other notes</source>
<target>0 other notes</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/collapsed_event_view_other_notes:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>··· %#@NOTES@ ···</source>
<target>··· %#@NOTES@ ···</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve"> <trans-unit id="/replying_to_one_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>Replying to %@%#@others@</source> <source>Replying to %@%#@OTHERS@</source>
<target>Replying to %@%#@others@</target> <target>Replying to %@%#@OTHERS@</target>
<note>Label to indicate that the user is replying to 1 user and others.</note> <note>Label to indicate that the user is replying to 1 user and others.</note>
</trans-unit> </trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/others:dict/one:dict/:string" xml:space="preserve"> <trans-unit id="/replying_to_one_and_others:dict/OTHERS:dict/one:dict/:string" xml:space="preserve">
<source> &amp; 1 other</source> <source> &amp; 1 other</source>
<target> &amp; 1 other</target> <target> &amp; 1 other</target>
<note>Label to indicate that the user is replying to 1 user and others.</note> <note>Label to indicate that the user is replying to 1 user and others.</note>
</trans-unit> </trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/others:dict/other:dict/:string" xml:space="preserve"> <trans-unit id="/replying_to_one_and_others:dict/OTHERS:dict/other:dict/:string" xml:space="preserve">
<source> &amp; %d others</source> <source> &amp; %d others</source>
<target> &amp; %d others</target> <target> &amp; %d others</target>
<note>Label to indicate that the user is replying to 1 user and others.</note> <note>Label to indicate that the user is replying to 1 user and others.</note>
</trans-unit> </trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/others:dict/zero:dict/:string" xml:space="preserve"> <trans-unit id="/replying_to_one_and_others:dict/OTHERS:dict/zero:dict/:string" xml:space="preserve">
<source/> <source/>
<target/> <target/>
<note>Label to indicate that the user is replying to 1 user and others.</note> <note>Label to indicate that the user is replying to 1 user and others.</note>
</trans-unit> </trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve"> <trans-unit id="/replying_to_two_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>Replying to %@, %@%#@others@</source> <source>Replying to %@, %@%#@OTHERS@</source>
<target>Replying to %@, %@%#@others@</target> <target>Replying to %@, %@%#@OTHERS@</target>
<note>Label to indicate that the user is replying to 2 users and others.</note> <note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit> </trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/others:dict/one:dict/:string" xml:space="preserve"> <trans-unit id="/replying_to_two_and_others:dict/OTHERS:dict/one:dict/:string" xml:space="preserve">
<source> &amp; 1 other</source> <source> &amp; 1 other</source>
<target> &amp; 1 other</target> <target> &amp; 1 other</target>
<note>Label to indicate that the user is replying to 2 users and others.</note> <note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit> </trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/others:dict/other:dict/:string" xml:space="preserve"> <trans-unit id="/replying_to_two_and_others:dict/OTHERS:dict/other:dict/:string" xml:space="preserve">
<source> &amp; %d others</source> <source> &amp; %d others</source>
<target> &amp; %d others</target> <target> &amp; %d others</target>
<note>Label to indicate that the user is replying to 2 users and others.</note> <note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit> </trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/others:dict/zero:dict/:string" xml:space="preserve"> <trans-unit id="/replying_to_two_and_others:dict/OTHERS:dict/zero:dict/:string" xml:space="preserve">
<source/> <source/>
<target/> <target/>
<note>Label to indicate that the user is replying to 2 users and others.</note> <note>Label to indicate that the user is replying to 2 users and others.</note>

View File

@@ -0,0 +1,6 @@
/* 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.";

View File

@@ -5,8 +5,8 @@
<key>replying_to_one_and_others</key> <key>replying_to_one_and_others</key>
<dict> <dict>
<key>NSStringLocalizedFormatKey</key> <key>NSStringLocalizedFormatKey</key>
<string>Replying to %@%#@others@</string> <string>Replying to %@%#@OTHERS@</string>
<key>others</key> <key>OTHERS</key>
<dict> <dict>
<key>NSStringFormatSpecTypeKey</key> <key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string> <string>NSStringPluralRuleType</string>
@@ -23,8 +23,8 @@
<key>replying_to_two_and_others</key> <key>replying_to_two_and_others</key>
<dict> <dict>
<key>NSStringLocalizedFormatKey</key> <key>NSStringLocalizedFormatKey</key>
<string>Replying to %@, %@%#@others@</string> <string>Replying to %@, %@%#@OTHERS@</string>
<key>others</key> <key>OTHERS</key>
<dict> <dict>
<key>NSStringFormatSpecTypeKey</key> <key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string> <string>NSStringPluralRuleType</string>
@@ -38,5 +38,23 @@
<string> &amp; %d others</string> <string> &amp; %d others</string>
</dict> </dict>
</dict> </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>zero</key>
<string>0 other notes</string>
<key>one</key>
<string>1 other note</string>
<key>other</key>
<string>%d other notes</string>
</dict>
</dict>
</dict> </dict>
</plist> </plist>

View File

@@ -1,7 +1,7 @@
{ {
"developmentRegion" : "en", "developmentRegion" : "en-US",
"project" : "damus.xcodeproj", "project" : "damus.xcodeproj",
"targetLocale" : "en", "targetLocale" : "en-US",
"toolInfo" : { "toolInfo" : {
"toolBuildNumber" : "14C18", "toolBuildNumber" : "14C18",
"toolID" : "com.apple.dt.xcode", "toolID" : "com.apple.dt.xcode",

View File

@@ -1,4 +0,0 @@
/* Bundle display name */
"CFBundleDisplayName" = "Damus";
/* Bundle name */
"CFBundleName" = "damus";

View File

@@ -0,0 +1,743 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="damus/en-US.lproj/InfoPlist.strings" datatype="plaintext" source-language="en-US" target-language="es-419">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
<source>Damus</source>
<note>Bundle display name</note>
</trans-unit>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>damus</source>
<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>
<note>Privacy - Photo Library Additions Usage Description</note>
</trans-unit>
</body>
</file>
<file original="damus/en-US.lproj/Localizable.strings" source-language="en-US" target-language="es-419" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
</header>
<body>
<trans-unit id=" " xml:space="preserve">
<source> </source>
<note>Blank space to separate profile picture from profile editor form.</note>
</trans-unit>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<note>Number of people following a user.</note>
</trans-unit>
<trans-unit id="%@ %@" xml:space="preserve">
<source>%@ %@</source>
<note>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'.</note>
</trans-unit>
<trans-unit id="%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." xml:space="preserve">
<source>%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.</source>
<note>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.</note>
</trans-unit>
<trans-unit id="%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" xml:space="preserve">
<source>%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs</source>
<note>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.</note>
</trans-unit>
<trans-unit id="%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet." xml:space="preserve">
<source>%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet.</source>
<note>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.</note>
</trans-unit>
<trans-unit id="%lld" xml:space="preserve">
<source>%lld</source>
<note>Number of profiles a user is following.</note>
</trans-unit>
<trans-unit id="%lld/%lld" xml:space="preserve">
<source>%lld/%lld</source>
<note>Fraction of how many of the user's relay servers that are operational.</note>
</trans-unit>
<trans-unit id="&amp;nbsp;" xml:space="preserve">
<source>&amp;nbsp;</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="'%@' at '%@' will be used for verification" xml:space="preserve">
<source>'%@' at '%@' will be used for verification</source>
<note>Description of how the nip05 identifier would be used for verification.</note>
</trans-unit>
<trans-unit id="'%@' is an invalid nip05 identifier. It should look like an email." xml:space="preserve">
<source>'%@' is an invalid nip05 identifier. It should look like an email.</source>
<note>Description of why the nip05 identifier is invalid.</note>
</trans-unit>
<trans-unit id="(Profile.displayName(profile: profile, pubkey: whos))'s Followers" xml:space="preserve">
<source>(Profile.displayName(profile: profile, pubkey: whos))'s Followers</source>
<note>Navigation bar title for view that shows who is following a user.</note>
</trans-unit>
<trans-unit id="(formattedSats) sat" xml:space="preserve">
<source>(formattedSats) sat</source>
<note>Amount of 1 sat.</note>
</trans-unit>
<trans-unit id="(formattedSats) sats" xml:space="preserve">
<source>(formattedSats) sats</source>
<note>Amount of sats.</note>
</trans-unit>
<trans-unit id="(who) following" xml:space="preserve">
<source>(who) following</source>
<note>Navigation bar title for view that shows who a user is following.</note>
</trans-unit>
<trans-unit id="&lt; e &gt;" xml:space="preserve">
<source>&lt; e &gt;</source>
<note>Placeholder for event mention.</note>
</trans-unit>
<trans-unit id="@" xml:space="preserve">
<source>@</source>
<note>Prefix character to username.</note>
</trans-unit>
<trans-unit id="About" xml:space="preserve">
<source>About</source>
<note>Label to prompt for about text entry for user to describe about themself.</note>
</trans-unit>
<trans-unit id="About Me" xml:space="preserve">
<source>About Me</source>
<note>Label for About Me section of user profile form.</note>
</trans-unit>
<trans-unit id="Absolute Boss" xml:space="preserve">
<source>Absolute Boss</source>
<note>Placeholder text for About Me description.</note>
</trans-unit>
<trans-unit id="Account ID" xml:space="preserve">
<source>Account ID</source>
<note>Label to indicate the public ID of the account.</note>
</trans-unit>
<trans-unit id="Add" xml:space="preserve">
<source>Add</source>
<note>Button to add recommended relay server.
Button to confirm adding user inputted relay.</note>
</trans-unit>
<trans-unit id="Add Relay" xml:space="preserve">
<source>Add Relay</source>
<note>Label for section for adding a relay server.</note>
</trans-unit>
<trans-unit id="Any" xml:space="preserve">
<source>Any</source>
<note>Any amount of sats</note>
</trans-unit>
<trans-unit id="Are you sure you want to boost this post?" xml:space="preserve">
<source>Are you sure you want to boost this post?</source>
<note>Alert message to ask if user wants to boost a post.</note>
</trans-unit>
<trans-unit id="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." xml:space="preserve">
<source>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.</source>
<note>Reminder to user that they should save their account information.</note>
</trans-unit>
<trans-unit id="Bitcoin Beach" xml:space="preserve">
<source>Bitcoin Beach</source>
<note>Dropdown option label for Lightning wallet, Bitcoin Beach.</note>
</trans-unit>
<trans-unit id="Bitcoin Lightning Tips" xml:space="preserve">
<source>Bitcoin Lightning Tips</source>
<note>Label for Bitcoin Lightning Tips section of user profile form.</note>
</trans-unit>
<trans-unit id="Blixt Wallet" xml:space="preserve">
<source>Blixt Wallet</source>
<note>Dropdown option label for Lightning wallet, Blixt Wallet</note>
</trans-unit>
<trans-unit id="Blue Wallet" xml:space="preserve">
<source>Blue Wallet</source>
<note>Dropdown option label for Lightning wallet, Blue Wallet.</note>
</trans-unit>
<trans-unit id="Boost" xml:space="preserve">
<source>Boost</source>
<note>Button to confirm boosting a post.
Title of alert for confirming to boost a post.</note>
</trans-unit>
<trans-unit id="Boosted" xml:space="preserve">
<source>Boosted</source>
<note>Text indicating that the post was boosted (i.e. re-shared).</note>
</trans-unit>
<trans-unit id="Breez" xml:space="preserve">
<source>Breez</source>
<note>Dropdown option label for Lightning wallet, Breez.</note>
</trans-unit>
<trans-unit id="Broadcast" xml:space="preserve">
<source>Broadcast</source>
<note>Context menu option for broadcasting the user's note to all of the user's connected relay servers.</note>
</trans-unit>
<trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source>
<note>Button to cancel out of posting a note.
Button to cancel out of view adding user inputted relay.
Cancel out of logging out the user.</note>
</trans-unit>
<trans-unit id="Cash App" xml:space="preserve">
<source>Cash App</source>
<note>Dropdown option label for Lightning wallet, Cash App.</note>
</trans-unit>
<trans-unit id="Chat" xml:space="preserve">
<source>Chat</source>
<note>Navigation bar title for Chatroom view.</note>
</trans-unit>
<trans-unit id="Clear" xml:space="preserve">
<source>Clear</source>
<note>Button for clearing cached data.</note>
</trans-unit>
<trans-unit id="Clear Cache" xml:space="preserve">
<source>Clear Cache</source>
<note>Section title for clearing cached data.</note>
</trans-unit>
<trans-unit id="Copied" xml:space="preserve">
<source>Copied</source>
<note>Label indicating that a user's key was copied.</note>
</trans-unit>
<trans-unit id="Copy" xml:space="preserve">
<source>Copy</source>
<note>Button to copy a relay server address.</note>
</trans-unit>
<trans-unit id="Copy Account ID" xml:space="preserve">
<source>Copy Account ID</source>
<note>Context menu option for copying the ID of the account that created the note.</note>
</trans-unit>
<trans-unit id="Copy Image" xml:space="preserve">
<source>Copy Image</source>
<note>Context menu option to copy an image into clipboard.
Context menu option to copy an image to clipboard.</note>
</trans-unit>
<trans-unit id="Copy Image URL" xml:space="preserve">
<source>Copy Image URL</source>
<note>Context menu option to copy the URL of an image into clipboard.</note>
</trans-unit>
<trans-unit id="Copy LNURL" xml:space="preserve">
<source>Copy LNURL</source>
<note>Context menu option for copying a user's Lightning URL.</note>
</trans-unit>
<trans-unit id="Copy Note ID" xml:space="preserve">
<source>Copy Note ID</source>
<note>Context menu option for copying the ID of the note.</note>
</trans-unit>
<trans-unit id="Copy Note JSON" xml:space="preserve">
<source>Copy Note JSON</source>
<note>Context menu option for copying the JSON text from the note.</note>
</trans-unit>
<trans-unit id="Copy Text" xml:space="preserve">
<source>Copy Text</source>
<note>Context menu option for copying the text from an note.</note>
</trans-unit>
<trans-unit id="Copy User ID" xml:space="preserve">
<source>Copy User ID</source>
<note>Context menu option for copying the ID of the user who created the note.</note>
</trans-unit>
<trans-unit id="Copy invoice" xml:space="preserve">
<source>Copy invoice</source>
<note>Title of section for copying a Lightning invoice identifier.</note>
</trans-unit>
<trans-unit id="Create" xml:space="preserve">
<source>Create</source>
<note>Button to create account.</note>
</trans-unit>
<trans-unit id="Create Account" xml:space="preserve">
<source>Create Account</source>
<note>Button to create an account.</note>
</trans-unit>
<trans-unit id="Creator(s) of Bitcoin. Absolute legend." xml:space="preserve">
<source>Creator(s) of Bitcoin. Absolute legend.</source>
<note>Example description about Bitcoin creator(s), Satoshi Nakamoto.</note>
</trans-unit>
<trans-unit id="DM" xml:space="preserve">
<source>DM</source>
<note>Navigation title for DM view, which is the English abbreviation for Direct Message.</note>
</trans-unit>
<trans-unit id="Damus" xml:space="preserve">
<source>Damus</source>
<note>Name of the app, shown on the first screen when user is not logged in.</note>
</trans-unit>
<trans-unit id="Default Wallet" xml:space="preserve">
<source>Default Wallet</source>
<note>Button to pay a Lightning invoice with the user's default Lightning wallet.</note>
</trans-unit>
<trans-unit id="Delete" xml:space="preserve">
<source>Delete</source>
<note>Button to delete a relay server that the user connects to.</note>
</trans-unit>
<trans-unit id="Dismiss" xml:space="preserve">
<source>Dismiss</source>
<note>Button to dismiss a text field alert.</note>
</trans-unit>
<trans-unit id="Display Name" xml:space="preserve">
<source>Display Name</source>
<note>Label to prompt display name entry.</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
<note>Button to dismiss wallet selection view for paying Lightning invoice.</note>
</trans-unit>
<trans-unit id="Earn Money" xml:space="preserve">
<source>Earn Money</source>
<note>Heading indicating that this application allows users to earn money.</note>
</trans-unit>
<trans-unit id="Edit" xml:space="preserve">
<source>Edit</source>
<note>Button to edit user's profile.</note>
</trans-unit>
<trans-unit id="Edit Profile" xml:space="preserve">
<source>Edit Profile</source>
<note>Title of navigation view for Edit Profile.</note>
</trans-unit>
<trans-unit id="Encrypted" xml:space="preserve">
<source>Encrypted</source>
<note>Heading indicating that this application keeps private messaging end-to-end encrypted.</note>
</trans-unit>
<trans-unit id="Encrypted DMs" xml:space="preserve">
<source>Encrypted DMs</source>
<note>Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message.</note>
</trans-unit>
<trans-unit id="Enter your account key to login:" xml:space="preserve">
<source>Enter your account key to login:</source>
<note>Prompt for user to enter an account key to login.</note>
</trans-unit>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<note>Error message indicating why saving keys failed.</note>
</trans-unit>
<trans-unit id="Filter State" xml:space="preserve">
<source>Filter State</source>
<note>Filter state for seeing either only posts, or posts &amp; replies.</note>
</trans-unit>
<trans-unit id="Follow" xml:space="preserve">
<source>Follow</source>
<note>Button to follow a user.</note>
</trans-unit>
<trans-unit id="Followers" xml:space="preserve">
<source>Followers</source>
<note>Label describing followers of a user.</note>
</trans-unit>
<trans-unit id="Following" xml:space="preserve">
<source>Following</source>
<note>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.</note>
</trans-unit>
<trans-unit id="Following..." xml:space="preserve">
<source>Following...</source>
<note>Label to indicate that the user is in the process of following another user.</note>
</trans-unit>
<trans-unit id="Follows" xml:space="preserve">
<source>Follows</source>
<note>Text to indicate that button next to it is in a state that will follow a profile when tapped.</note>
</trans-unit>
<trans-unit id="Global" xml:space="preserve">
<source>Global</source>
<note>Navigation bar title for Global view where posts from all connected relay servers appear.</note>
</trans-unit>
<trans-unit id="Goto post %@" xml:space="preserve">
<source>Goto post %@</source>
<note>Navigation link to go to post referenced by hex code.</note>
</trans-unit>
<trans-unit id="Goto profile %@" xml:space="preserve">
<source>Goto profile %@</source>
<note>Navigation link to go to profile.</note>
</trans-unit>
<trans-unit id="Home" xml:space="preserve">
<source>Home</source>
<note>Navigation bar title for Home view where posts and replies appear from those who the user is following.</note>
</trans-unit>
<trans-unit id="Invalid key" xml:space="preserve">
<source>Invalid key</source>
<note>Error message indicating that an invalid account key was entered for login.</note>
</trans-unit>
<trans-unit id="LNLink" xml:space="preserve">
<source>LNLink</source>
<note>Dropdown option label for Lightning wallet, LNLink.</note>
</trans-unit>
<trans-unit id="Let's go!" xml:space="preserve">
<source>Let's go!</source>
<note>Button to complete account creation and start using the app.</note>
</trans-unit>
<trans-unit id="Lightning Address or LNURL" xml:space="preserve">
<source>Lightning Address or LNURL</source>
<note>Placeholder text for entry of Lightning Address or LNURL.</note>
</trans-unit>
<trans-unit id="Lightning Invoice" xml:space="preserve">
<source>Lightning Invoice</source>
<note>Indicates that the view is for paying a Lightning invoice.</note>
</trans-unit>
<trans-unit id="Local default" xml:space="preserve">
<source>Local default</source>
<note>Dropdown option label for system default for Lightning wallet.</note>
</trans-unit>
<trans-unit id="Login" xml:space="preserve">
<source>Login</source>
<note>Button to log into account.</note>
</trans-unit>
<trans-unit id="Logout" xml:space="preserve">
<source>Logout</source>
<note>Alert for logging out the user.
Button for logging out the user.
Button to logout the user.</note>
</trans-unit>
<trans-unit id="Make sure your nsec account key is saved before you logout or you will lose access to this account" xml:space="preserve">
<source>Make sure your nsec account key is saved before you logout or you will lose access to this account</source>
<note>Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out.</note>
</trans-unit>
<trans-unit id="Muun" xml:space="preserve">
<source>Muun</source>
<note>Dropdown option label for Lightning wallet, Muun.</note>
</trans-unit>
<trans-unit id="NIP-05 Verification" xml:space="preserve">
<source>NIP-05 Verification</source>
<note>Label for NIP-05 Verification section of user profile form.</note>
</trans-unit>
<trans-unit id="Nothing to see here. Check back later!" xml:space="preserve">
<source>Nothing to see here. Check back later!</source>
<note>Indicates that there are no notes in the timeline to view.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<note>Navigation title for notifications.</note>
</trans-unit>
<trans-unit id="Pay" xml:space="preserve">
<source>Pay</source>
<note>Button to pay a Lightning invoice.</note>
</trans-unit>
<trans-unit id="Pay the Lightning invoice" xml:space="preserve">
<source>Pay the Lightning invoice</source>
<note>Navigation bar title for view to pay Lightning invoice.</note>
</trans-unit>
<trans-unit id="Phoenix" xml:space="preserve">
<source>Phoenix</source>
<note>Dropdown option label for Lightning wallet, Phoenix.</note>
</trans-unit>
<trans-unit id="Post" xml:space="preserve">
<source>Post</source>
<note>Button to post a note.</note>
</trans-unit>
<trans-unit id="Posts" xml:space="preserve">
<source>Posts</source>
<note>Label for filter for seeing only posts (instead of posts and replies).</note>
</trans-unit>
<trans-unit id="Posts &amp; Replies" xml:space="preserve">
<source>Posts &amp; Replies</source>
<note>Label for filter for seeing posts and replies (instead of only posts).</note>
</trans-unit>
<trans-unit id="Private" xml:space="preserve">
<source>Private</source>
<note>Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading.</note>
</trans-unit>
<trans-unit id="Private Key" xml:space="preserve">
<source>Private Key</source>
<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>
<note>Title of the secure field that holds the user's private key.</note>
</trans-unit>
<trans-unit id="Profile Picture" xml:space="preserve">
<source>Profile Picture</source>
<note>Label for Profile Picture section of user profile form.</note>
</trans-unit>
<trans-unit id="Public Account ID" xml:space="preserve">
<source>Public Account ID</source>
<note>Section title for the user's public account ID.</note>
</trans-unit>
<trans-unit id="Public Key" xml:space="preserve">
<source>Public Key</source>
<note>Label indicating that the text is a user's public account key.</note>
</trans-unit>
<trans-unit id="Public Key?" xml:space="preserve">
<source>Public Key?</source>
<note>Prompt to ask user if the key they entered is a public key.</note>
</trans-unit>
<trans-unit id="Public key" xml:space="preserve">
<source>Public key</source>
<note>Label indicating that the text is a user's public account key.</note>
</trans-unit>
<trans-unit id="Recommended Relays" xml:space="preserve">
<source>Recommended Relays</source>
<note>Section title for recommend relay servers that could be added as part of configuration</note>
</trans-unit>
<trans-unit id="Relay" xml:space="preserve">
<source>Relay</source>
<note>Text field for relay server. Used for testing purposes.</note>
</trans-unit>
<trans-unit id="Relays" xml:space="preserve">
<source>Relays</source>
<note>Header text for relay server list for configuration.
Part of a larger sentence to describe how many relay servers a user is connected.</note>
</trans-unit>
<trans-unit id="Reply to self" xml:space="preserve">
<source>Reply to self</source>
<note>Label to indicate that the user is replying to themself.</note>
</trans-unit>
<trans-unit id="Replying to %@ &amp; %@" xml:space="preserve">
<source>Replying to %1$@ &amp; %2$@</source>
<note>Label to indicate that the user is replying to 2 users.</note>
</trans-unit>
<trans-unit id="Replying to:" xml:space="preserve">
<source>Replying to:</source>
<note>Indicating that the user is replying to the following listed people.</note>
</trans-unit>
<trans-unit id="Reset" xml:space="preserve">
<source>Reset</source>
<note>Section title for resetting the user</note>
</trans-unit>
<trans-unit id="River" xml:space="preserve">
<source>River</source>
<note>Dropdown option label for Lightning wallet, River</note>
</trans-unit>
<trans-unit id="Satoshi Nakamoto" xml:space="preserve">
<source>Satoshi Nakamoto</source>
<note>Name of Bitcoin creator(s).</note>
</trans-unit>
<trans-unit id="Save" xml:space="preserve">
<source>Save</source>
<note>Button for saving profile.</note>
</trans-unit>
<trans-unit id="Save Image" xml:space="preserve">
<source>Save Image</source>
<note>Context menu option to save an image.</note>
</trans-unit>
<trans-unit id="Search hashtag: #%@" xml:space="preserve">
<source>Search hashtag: #%@</source>
<note>Navigation link to search hashtag.</note>
</trans-unit>
<trans-unit id="Search..." xml:space="preserve">
<source>Search...</source>
<note>Placeholder text to prompt entry of search query.</note>
</trans-unit>
<trans-unit id="Secret Account Login Key" xml:space="preserve">
<source>Secret Account Login Key</source>
<note>Section title for user's secret account login key.</note>
</trans-unit>
<trans-unit id="Select a Lightning wallet" xml:space="preserve">
<source>Select a Lightning wallet</source>
<note>Title of section for selecting a Lightning wallet to pay a Lightning invoice.</note>
</trans-unit>
<trans-unit id="Select default wallet" xml:space="preserve">
<source>Select default wallet</source>
<note>Prompt selection of user's default wallet</note>
</trans-unit>
<trans-unit id="Send a message to start the conversation..." xml:space="preserve">
<source>Send a message to start the conversation...</source>
<note>Text prompt for user to send a message to the other user.</note>
</trans-unit>
<trans-unit id="Settings" xml:space="preserve">
<source>Settings</source>
<note>Navigation title for Settings view.</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
<note>Button to share an image.</note>
</trans-unit>
<trans-unit id="Show" xml:space="preserve">
<source>Show</source>
<note>Toggle to show or hide user's secret account login key.</note>
</trans-unit>
<trans-unit id="Show wallet selector" xml:space="preserve">
<source>Show wallet selector</source>
<note>Toggle to show or hide selection of wallet.</note>
</trans-unit>
<trans-unit id="Strike" xml:space="preserve">
<source>Strike</source>
<note>Dropdown option label for Lightning wallet, Strike.</note>
</trans-unit>
<trans-unit id="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." xml:space="preserve">
<source>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.</source>
<note>Warning that the inputted account key is a public key and the result of what happens because of it.</note>
</trans-unit>
<trans-unit id="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." xml:space="preserve">
<source>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.</source>
<note>Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key.</note>
</trans-unit>
<trans-unit id="This is your account ID, you can give this to your friends so that they can follow you. Click to copy." xml:space="preserve">
<source>This is your account ID, you can give this to your friends so that they can follow you. Click to copy.</source>
<note>Label to describe that a public key is the user's account ID and what they can do with it.</note>
</trans-unit>
<trans-unit id="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!" xml:space="preserve">
<source>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!</source>
<note>Label to describe that a private key is the user's secret account key and what they should do with it.</note>
</trans-unit>
<trans-unit id="Thread" xml:space="preserve">
<source>Thread</source>
<note>Navigation bar title for note thread.
Navigation bar title for threaded event detail view.</note>
</trans-unit>
<trans-unit id="Type your post here..." xml:space="preserve">
<source>Type your post here...</source>
<note>Text box prompt to ask user to type their post.</note>
</trans-unit>
<trans-unit id="Unfollow" xml:space="preserve">
<source>Unfollow</source>
<note>Button to unfollow a user.</note>
</trans-unit>
<trans-unit id="Unfollowing" xml:space="preserve">
<source>Unfollowing</source>
<note>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.</note>
</trans-unit>
<trans-unit id="Unfollowing..." xml:space="preserve">
<source>Unfollowing...</source>
<note>Label to indicate that the user is in the process of unfollowing another user.</note>
</trans-unit>
<trans-unit id="Unfollows" xml:space="preserve">
<source>Unfollows</source>
<note>Text to indicate that the button next to it is in a state that will unfollow a profile when tapped.</note>
</trans-unit>
<trans-unit id="Username" xml:space="preserve">
<source>Username</source>
<note>Label for Username section of user profile form.
Label to prompt username entry.</note>
</trans-unit>
<trans-unit id="Wallet Of Satoshi" xml:space="preserve">
<source>Wallet Of Satoshi</source>
<note>Dropdown option label for Lightning wallet, Wallet Of Satoshi.</note>
</trans-unit>
<trans-unit id="Wallet Selector" xml:space="preserve">
<source>Wallet Selector</source>
<note>Section title for selection of wallet.</note>
</trans-unit>
<trans-unit id="Website" xml:space="preserve">
<source>Website</source>
<note>Label for Website section of user profile form.</note>
</trans-unit>
<trans-unit id="Welcome to the social network %@ control." xml:space="preserve">
<source>Welcome to the social network %@ control.</source>
<note>Welcoming message to the reader. The variable is 'you', the reader.</note>
</trans-unit>
<trans-unit id="Welcome, %@!" xml:space="preserve">
<source>Welcome, %@!</source>
<note>Text to welcome user.</note>
</trans-unit>
<trans-unit id="Your Name" xml:space="preserve">
<source>Your Name</source>
<note>Label for Your Name section of user profile form.</note>
</trans-unit>
<trans-unit id="Zebedee" xml:space="preserve">
<source>Zebedee</source>
<note>Dropdown option label for Lightning wallet, Zebedee.</note>
</trans-unit>
<trans-unit id="Zeus LN" xml:space="preserve">
<source>Zeus LN</source>
<note>Dropdown option label for Lightning wallet, Zeus LN.</note>
</trans-unit>
<trans-unit id="collapsed_event_view_other_notes" translate="no" xml:space="preserve">
<source>collapsed_event_view_other_notes</source>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="https://example.com/pic.jpg" xml:space="preserve">
<source>https://example.com/pic.jpg</source>
<note>Placeholder example text for profile picture URL.</note>
</trans-unit>
<trans-unit id="https://jb55.com" xml:space="preserve">
<source>https://jb55.com</source>
<note>Placeholder example text for website URL for user profile.</note>
</trans-unit>
<trans-unit id="jb55@jb55.com" xml:space="preserve">
<source>jb55@jb55.com</source>
<note>Placeholder example text for identifier used for NIP-05 verification.</note>
</trans-unit>
<trans-unit id="none" xml:space="preserve">
<source>none</source>
<note>No search results.</note>
</trans-unit>
<trans-unit id="now" xml:space="preserve">
<source>now</source>
<note>String indicating that a given timestamp just occurred</note>
</trans-unit>
<trans-unit id="nsec1..." xml:space="preserve">
<source>nsec1...</source>
<note>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.</note>
</trans-unit>
<trans-unit id="optional" xml:space="preserve">
<source>optional</source>
<note>Label indicating that a form input is optional.</note>
</trans-unit>
<trans-unit id="replying_to_one_and_others" translate="no" xml:space="preserve">
<source>replying_to_one_and_others</source>
<note>Label to indicate that the user is replying to 1 user and others. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="replying_to_two_and_others" translate="no" xml:space="preserve">
<source>replying_to_two_and_others</source>
<note>Label to indicate that the user is replying to 2 users and others. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="satoshi" xml:space="preserve">
<source>satoshi</source>
<note>Example username of Bitcoin creator(s), Satoshi Nakamoto.</note>
</trans-unit>
<trans-unit id="wss://some.relay.com" xml:space="preserve">
<source>wss://some.relay.com</source>
<note>Placeholder example for relay server address.</note>
</trans-unit>
<trans-unit id="you" xml:space="preserve">
<source>you</source>
<note>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.</note>
</trans-unit>
<trans-unit id="🤙" xml:space="preserve">
<source>🤙</source>
<note>Button with emoji to like an event.</note>
</trans-unit>
</body>
</file>
<file original="damus/en-US.lproj/Localizable.stringsdict" source-language="en-US" target-language="es-419" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
</header>
<body>
<trans-unit id="/collapsed_event_view_other_notes:dict/NOTES:dict/one:dict/:string" xml:space="preserve">
<source>1 other note</source>
<target>1 other note</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/collapsed_event_view_other_notes:dict/NOTES:dict/other:dict/:string" xml:space="preserve">
<source>%d other notes</source>
<target>%d other notes</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/collapsed_event_view_other_notes:dict/NOTES:dict/zero:dict/:string" xml:space="preserve">
<source>0 other notes</source>
<target>0 other notes</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/collapsed_event_view_other_notes:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>··· %#@NOTES@ ···</source>
<target>··· %#@NOTES@ ···</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>Replying to %@%#@OTHERS@</source>
<target>Replying to %@%#@OTHERS@</target>
<note>Label to indicate that the user is replying to 1 user and others.</note>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/OTHERS:dict/one:dict/:string" xml:space="preserve">
<source> &amp; 1 other</source>
<target> &amp; 1 other</target>
<note>Label to indicate that the user is replying to 1 user and others.</note>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/OTHERS:dict/other:dict/:string" xml:space="preserve">
<source> &amp; %d others</source>
<target> &amp; %d others</target>
<note>Label to indicate that the user is replying to 1 user and others.</note>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/OTHERS:dict/zero:dict/:string" xml:space="preserve">
<source/>
<target/>
<note>Label to indicate that the user is replying to 1 user and others.</note>
</trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>Replying to %@, %@%#@OTHERS@</source>
<target>Replying to %@, %@%#@OTHERS@</target>
<note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/OTHERS:dict/one:dict/:string" xml:space="preserve">
<source> &amp; 1 other</source>
<target> &amp; 1 other</target>
<note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/OTHERS:dict/other:dict/:string" xml:space="preserve">
<source> &amp; %d others</source>
<target> &amp; %d others</target>
<note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/OTHERS:dict/zero:dict/:string" xml:space="preserve">
<source/>
<target/>
<note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,6 @@
/* 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.";

View File

@@ -5,8 +5,8 @@
<key>replying_to_one_and_others</key> <key>replying_to_one_and_others</key>
<dict> <dict>
<key>NSStringLocalizedFormatKey</key> <key>NSStringLocalizedFormatKey</key>
<string>Replying to %@%#@others@</string> <string>Replying to %@%#@OTHERS@</string>
<key>others</key> <key>OTHERS</key>
<dict> <dict>
<key>NSStringFormatSpecTypeKey</key> <key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string> <string>NSStringPluralRuleType</string>
@@ -23,8 +23,8 @@
<key>replying_to_two_and_others</key> <key>replying_to_two_and_others</key>
<dict> <dict>
<key>NSStringLocalizedFormatKey</key> <key>NSStringLocalizedFormatKey</key>
<string>Replying to %@, %@%#@others@</string> <string>Replying to %@, %@%#@OTHERS@</string>
<key>others</key> <key>OTHERS</key>
<dict> <dict>
<key>NSStringFormatSpecTypeKey</key> <key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string> <string>NSStringPluralRuleType</string>
@@ -38,5 +38,23 @@
<string> &amp; %d others</string> <string> &amp; %d others</string>
</dict> </dict>
</dict> </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>zero</key>
<string>0 other notes</string>
<key>one</key>
<string>1 other note</string>
<key>other</key>
<string>%d other notes</string>
</dict>
</dict>
</dict> </dict>
</plist> </plist>

View File

@@ -0,0 +1,12 @@
{
"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,8 +12,6 @@
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; }; 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; }; 31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; }; 3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
3AB18056296375CA00FD1BD8 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3AB18052296375CA00FD1BD8 /* InfoPlist.strings */; };
3AB18057296375CA00FD1BD8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3AB18054296375CA00FD1BD8 /* Localizable.strings */; };
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; }; 3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; }; 4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; };
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; }; 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; };
@@ -53,6 +51,7 @@
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA328296DEE006E126D /* SearchModel.swift */; }; 4C363AA428296DEE006E126D /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA328296DEE006E126D /* SearchModel.swift */; };
4C363AA828297703006E126D /* InsertSort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA728297703006E126D /* InsertSort.swift */; }; 4C363AA828297703006E126D /* InsertSort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA728297703006E126D /* InsertSort.swift */; };
4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3A1D322960DB0500558C0F /* Markdown.swift */; }; 4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3A1D322960DB0500558C0F /* Markdown.swift */; };
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3A1D3629637E0500558C0F /* PreviewCache.swift */; };
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79A28306D7B00E1F516 /* Contacts.swift */; }; 4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79A28306D7B00E1F516 /* Contacts.swift */; };
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79C2833036D00E1F516 /* FollowingView.swift */; }; 4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79C2833036D00E1F516 /* FollowingView.swift */; };
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79E2833115300E1F516 /* FollowButtonView.swift */; }; 4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79E2833115300E1F516 /* FollowButtonView.swift */; };
@@ -116,6 +115,7 @@
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; }; 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */; }; 4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */; };
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */; }; 4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */; };
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838529656C8B00DC99E7 /* NIP05.swift */; };
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; }; 4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; }; 4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; }; 4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; };
@@ -138,9 +138,11 @@
4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */; }; 4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */; };
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; }; 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; }; 4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; }; 6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; }; BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; }; BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; }; E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; }; E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -167,13 +169,8 @@
3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = "<group>"; }; 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = "<group>"; };
3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; }; 3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; };
31D2E846295218AF006D67F8 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; }; 31D2E846295218AF006D67F8 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; };
3A4325A92961E11400BFCD9D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 3A2B8B0A296A8982009CC16D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-US"; path = "en-US.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3AB1803D29636FB100FD1BD8 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.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>"; };
3AB18058296377E500FD1BD8 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3AB18059296377E700FD1BD8 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
3AB1805A2963EF7E00FD1BD8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3AB1805B2963EF7E00FD1BD8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
3AB1805C2963EF7E00FD1BD8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; 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>"; }; 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>"; }; 4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; };
@@ -215,6 +212,7 @@
4C363AA328296DEE006E126D /* SearchModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModel.swift; sourceTree = "<group>"; }; 4C363AA328296DEE006E126D /* SearchModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModel.swift; sourceTree = "<group>"; };
4C363AA728297703006E126D /* InsertSort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertSort.swift; sourceTree = "<group>"; }; 4C363AA728297703006E126D /* InsertSort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertSort.swift; sourceTree = "<group>"; };
4C3A1D322960DB0500558C0F /* Markdown.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Markdown.swift; sourceTree = "<group>"; }; 4C3A1D322960DB0500558C0F /* Markdown.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Markdown.swift; sourceTree = "<group>"; };
4C3A1D3629637E0500558C0F /* PreviewCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewCache.swift; sourceTree = "<group>"; };
4C3AC79A28306D7B00E1F516 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = "<group>"; }; 4C3AC79A28306D7B00E1F516 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = "<group>"; };
4C3AC79C2833036D00E1F516 /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = "<group>"; }; 4C3AC79C2833036D00E1F516 /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = "<group>"; };
4C3AC79E2833115300E1F516 /* FollowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowButtonView.swift; sourceTree = "<group>"; }; 4C3AC79E2833115300E1F516 /* FollowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowButtonView.swift; sourceTree = "<group>"; };
@@ -308,6 +306,7 @@
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; }; 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedRelayView.swift; sourceTree = "<group>"; }; 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedRelayView.swift; sourceTree = "<group>"; };
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRelaysView.swift; sourceTree = "<group>"; }; 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRelaysView.swift; sourceTree = "<group>"; };
4CB8838529656C8B00DC99E7 /* NIP05.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05.swift; sourceTree = "<group>"; };
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; }; 4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; }; 4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; }; 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; };
@@ -333,8 +332,10 @@
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; }; BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; }; BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
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>"; }; 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>"; }; E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadV2View.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -469,48 +470,48 @@
4C75EFA227FA576C0006080F /* Views */ = { 4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
3169CAE4294E699400EE4006 /* Empty Views */, 4CE4F9E228528C5200C00DD9 /* AddRelayView.swift */,
4C75EFA327FA577B0006080F /* PostView.swift */, 4C363A8728236948006E126D /* BlocksView.swift */,
4C75EFAC28049CFB0006080F /* PostButton.swift */, 4C285C8128385570008A31F1 /* CarouselView.swift */,
4C75EFB82804A2740006080F /* EventView.swift */,
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */,
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */,
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */,
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */,
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */,
4C0A3F8B280F5FCA000448DE /* ChatroomView.swift */, 4C0A3F8B280F5FCA000448DE /* ChatroomView.swift */,
4C0A3F90280F6528000448DE /* ChatView.swift */, 4C0A3F90280F6528000448DE /* ChatView.swift */,
4C0A3F94280F6C78000448DE /* ReplyQuoteView.swift */,
4C0A3F96280F8E02000448DE /* ThreadView.swift */,
4C8682862814DE470026224F /* ProfileView.swift */,
4C363A8728236948006E126D /* BlocksView.swift */,
4C363A8928236B57006E126D /* MentionView.swift */,
4C363A8B28236B92006E126D /* PubkeyView.swift */,
4C363A8D28236FE4006E126D /* NoteContentView.swift */,
4C363AA128296A7E006E126D /* SearchView.swift */,
4C3AC79C2833036D00E1F516 /* FollowingView.swift */,
4C3AC79E2833115300E1F516 /* FollowButtonView.swift */,
4C3AC7A02835A81400E1F516 /* SetupView.swift */,
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */,
4C285C8128385570008A31F1 /* CarouselView.swift */,
4C285C8328385690008A31F1 /* CreateAccountView.swift */,
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */,
4C90BD17283A9EE5008EE7EF /* LoginView.swift */,
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */,
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */, 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */,
4CE4F9E228528C5200C00DD9 /* AddRelayView.swift */, 4C285C8328385690008A31F1 /* CreateAccountView.swift */,
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */, 4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */,
4C216F31286E388800040376 /* DMChatView.swift */, 4C216F31286E388800040376 /* DMChatView.swift */,
4C216F33286F5ACD00040376 /* DMView.swift */, 4C216F33286F5ACD00040376 /* DMView.swift */,
4C06670028FC7C5900038D2A /* RelayView.swift */,
E990020E2955F837003BBC5A /* EditMetadataView.swift */, E990020E2955F837003BBC5A /* EditMetadataView.swift */,
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */, 3169CAE4294E699400EE4006 /* Empty Views */,
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */, 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */,
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
4C75EFB82804A2740006080F /* EventView.swift */,
4C3AC79E2833115300E1F516 /* FollowButtonView.swift */,
4C3AC79C2833036D00E1F516 /* FollowingView.swift */,
4C90BD17283A9EE5008EE7EF /* LoginView.swift */,
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
4C363A8928236B57006E126D /* MentionView.swift */,
4C363A8D28236FE4006E126D /* NoteContentView.swift */,
4C75EFAC28049CFB0006080F /* PostButton.swift */,
4C75EFA327FA577B0006080F /* PostView.swift */,
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */,
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */,
4C8682862814DE470026224F /* ProfileView.swift */,
4C363A8B28236B92006E126D /* PubkeyView.swift */,
4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */, 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */,
4C06670028FC7C5900038D2A /* RelayView.swift */,
4C0A3F94280F6C78000448DE /* ReplyQuoteView.swift */,
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */,
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */,
4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */,
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */,
4C363AA128296A7E006E126D /* SearchView.swift */,
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */,
4C3AC7A02835A81400E1F516 /* SetupView.swift */,
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */,
4C0A3F96280F8E02000448DE /* ThreadView.swift */,
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */,
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */, 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */,
); );
path = Views; path = Views;
@@ -550,6 +551,9 @@
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */, 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */,
3169CAEC294FCCFC00EE4006 /* Constants.swift */, 3169CAEC294FCCFC00EE4006 /* Constants.swift */,
3165648A295B70D500C64604 /* LinkView.swift */, 3165648A295B70D500C64604 /* LinkView.swift */,
4C3A1D3629637E0500558C0F /* PreviewCache.swift */,
64FBD06E296255C400D9D3B2 /* Theme.swift */,
4CB8838529656C8B00DC99E7 /* NIP05.swift */,
); );
path = Util; path = Util;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -603,8 +607,6 @@
4CE6DEE827F7A08100C66700 /* ContentView.swift */, 4CE6DEE827F7A08100C66700 /* ContentView.swift */,
4CE6DEEA27F7A08200C66700 /* Assets.xcassets */, 4CE6DEEA27F7A08200C66700 /* Assets.xcassets */,
4CE6DEEC27F7A08200C66700 /* Preview Content */, 4CE6DEEC27F7A08200C66700 /* Preview Content */,
3AB18052296375CA00FD1BD8 /* InfoPlist.strings */,
3AB18054296375CA00FD1BD8 /* Localizable.strings */,
3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */, 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */,
); );
path = damus; path = damus;
@@ -621,6 +623,7 @@
4CE6DEF627F7A08200C66700 /* damusTests */ = { 4CE6DEF627F7A08200C66700 /* damusTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */,
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */, 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */,
4C363A9F2828A8DD006E126D /* LikeTests.swift */, 4C363A9F2828A8DD006E126D /* LikeTests.swift */,
4C363A9D2828A822006E126D /* ReplyTests.swift */, 4C363A9D2828A822006E126D /* ReplyTests.swift */,
@@ -736,13 +739,12 @@
}; };
buildConfigurationList = 4CE6DEDE27F7A08100C66700 /* Build configuration list for PBXProject "damus" */; buildConfigurationList = 4CE6DEDE27F7A08100C66700 /* Build configuration list for PBXProject "damus" */;
compatibilityVersion = "Xcode 13.0"; compatibilityVersion = "Xcode 13.0";
developmentRegion = en; developmentRegion = "en-US";
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
en,
Base, Base,
es, "es-419",
fr, "en-US",
); );
mainGroup = 4CE6DEDA27F7A08100C66700; mainGroup = 4CE6DEDA27F7A08100C66700;
packageReferences = ( packageReferences = (
@@ -768,9 +770,7 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
3AB18057296375CA00FD1BD8 /* Localizable.strings in Resources */,
4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */, 4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */,
3AB18056296375CA00FD1BD8 /* InfoPlist.strings in Resources */,
4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */, 4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */,
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */, 3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */,
); );
@@ -854,8 +854,10 @@
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */, BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */,
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */, 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */,
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
4C3EA64928FF597700C48A62 /* bech32.c in Sources */, 4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */, 4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */,
4C3EA67528FF7A5A00C48A62 /* take.c in Sources */, 4C3EA67528FF7A5A00C48A62 /* take.c in Sources */,
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */, 4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */, 4C06670128FC7C5900038D2A /* RelayView.swift in Sources */,
@@ -877,6 +879,7 @@
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */, 4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
4C8682872814DE470026224F /* ProfileView.swift in Sources */, 4C8682872814DE470026224F /* ProfileView.swift in Sources */,
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */, 4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */, 4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */, 4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */, 4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
@@ -924,6 +927,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */, 3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */,
4C3EA67B28FF7B3900C48A62 /* InvoiceTests.swift in Sources */, 4C3EA67B28FF7B3900C48A62 /* InvoiceTests.swift in Sources */,
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */, 4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */,
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */, 4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
@@ -960,31 +964,12 @@
3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */ = { 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
children = ( children = (
3A4325A92961E11400BFCD9D /* en */, 3A5C4575296A879E0032D398 /* es-419 */,
3AB1803D29636FB100FD1BD8 /* es */, 3A2B8B0A296A8982009CC16D /* en-US */,
3AB1805C2963EF7E00FD1BD8 /* fr */,
); );
name = Localizable.stringsdict; name = Localizable.stringsdict;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
3AB18052296375CA00FD1BD8 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
3AB18058296377E500FD1BD8 /* es */,
3AB1805A2963EF7E00FD1BD8 /* fr */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
3AB18054296375CA00FD1BD8 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
3AB18059296377E700FD1BD8 /* es */,
3AB1805B2963EF7E00FD1BD8 /* fr */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */ /* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
@@ -1046,6 +1031,7 @@
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
}; };
name = Debug; name = Debug;
@@ -1101,6 +1087,7 @@
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
@@ -1114,7 +1101,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1154,7 +1141,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;

View File

@@ -1,6 +1,33 @@
{ {
"colors" : [ "colors" : [
{ {
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xC5",
"green" : "0x43",
"red" : "0xCC"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xC5",
"green" : "0x43",
"red" : "0xCC"
}
},
"idiom" : "universal" "idiom" : "universal"
} }
], ],

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0x00",
"red" : "0x00"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0x00",
"red" : "0x00"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0x4D",
"red" : "0x4B"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0x4D",
"red" : "0x4B"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x1E",
"green" : "0x1C",
"red" : "0x1C"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x1E",
"green" : "0x1C",
"red" : "0x1C"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x4F",
"green" : "0xC3",
"red" : "0x66"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x4F",
"green" : "0xC3",
"red" : "0x66"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF4",
"green" : "0xEE",
"red" : "0xEE"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF4",
"green" : "0xEE",
"red" : "0xEE"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x5F",
"green" : "0x5F",
"red" : "0x5F"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x5F",
"green" : "0x5F",
"red" : "0x5F"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xC5",
"green" : "0x43",
"red" : "0xCC"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xC5",
"green" : "0x43",
"red" : "0xCC"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xFF",
"red" : "0xFF"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xFF",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "ic-copy.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "ic-key.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

View File

@@ -0,0 +1,52 @@
{
"images" : [
{
"filename" : "ic-message-black.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "ic-message-white 1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "ic-nipverified.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "ic-qr.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "profile-banner.jpeg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "ic-lightning.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "ic-tick.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "nostr-hello-outline-black.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "nostr-hello-outline-black@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "nostr-hello-outline-black@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -41,19 +41,24 @@ struct ImageContextMenuModifier: ViewModifier {
Button { Button {
UIPasteboard.general.url = url UIPasteboard.general.url = url
} label: { } label: {
Label("Copy Image URL", systemImage: "doc.on.doc") Label(NSLocalizedString("Copy Image URL", comment: "Context menu option to copy the URL of an image into clipboard."), systemImage: "doc.on.doc")
} }
if let someImage = image { if let someImage = image {
Button { Button {
UIPasteboard.general.image = someImage UIPasteboard.general.image = someImage
} label: { } label: {
Label("Copy Image", systemImage: "photo.on.rectangle") Label(NSLocalizedString("Copy Image", comment: "Context menu option to copy an image into clipboard."), systemImage: "photo.on.rectangle")
}
Button {
UIImageWriteToSavedPhotosAlbum(someImage, nil, nil, nil)
} label: {
Label(NSLocalizedString("Save Image", comment: "Context menu option to save an image."), systemImage: "square.and.arrow.down")
} }
} }
Button { Button {
showShareSheet = true showShareSheet = true
} label: { } label: {
Label("Share", systemImage: "square.and.arrow.up") Label(NSLocalizedString("Share", comment: "Button to share an image."), systemImage: "square.and.arrow.up")
} }
} }
} }
@@ -138,7 +143,7 @@ struct ImageCarousel: View {
} }
.id(url.absoluteString) .id(url.absoluteString)
.contextMenu { .contextMenu {
Button("Copy Image") { Button(NSLocalizedString("Copy Image", comment: "Context menu option to copy an image to clipboard.")) {
UIPasteboard.general.string = url.absoluteString UIPasteboard.general.string = url.absoluteString
} }
} }

View File

@@ -47,7 +47,7 @@ struct InvoiceView: View {
RoundedRectangle(cornerRadius: 20) RoundedRectangle(cornerRadius: 20)
.foregroundColor(colorScheme == .light ? .black : .white) .foregroundColor(colorScheme == .light ? .black : .white)
.overlay { .overlay {
Text("Pay") Text("Pay", comment: "Button to pay a Lightning invoice.")
.fontWeight(.medium) .fontWeight(.medium)
.foregroundColor(colorScheme == .light ? .white : .black) .foregroundColor(colorScheme == .light ? .white : .black)
} }
@@ -67,7 +67,7 @@ struct InvoiceView: View {
HStack { HStack {
Label("", systemImage: "bolt.fill") Label("", systemImage: "bolt.fill")
.foregroundColor(.orange) .foregroundColor(.orange)
Text("Lightning Invoice") Text("Lightning Invoice", comment: "Indicates that the view is for paying a Lightning invoice.")
} }
Divider() Divider()
Text(invoice.description) Text(invoice.description)

View File

@@ -20,7 +20,7 @@ struct TextFieldAlert<Presenting>: View where Presenting: View {
.disabled(isShowing) .disabled(isShowing)
VStack { VStack {
Text(self.title) Text(self.title)
TextField("Relay", text: self.$text) TextField(NSLocalizedString("Relay", comment: "Text field for relay server. Used for testing purposes."), text: self.$text)
Divider() Divider()
HStack { HStack {
Button(action: { Button(action: {
@@ -28,7 +28,7 @@ struct TextFieldAlert<Presenting>: View where Presenting: View {
self.isShowing.toggle() self.isShowing.toggle()
} }
}) { }) {
Text("Dismiss") Text("Dismiss", comment: "Button to dismiss a text field alert.")
} }
} }
} }

View File

@@ -44,6 +44,15 @@ enum ThreadState {
enum FilterState : Int { enum FilterState : Int {
case posts_and_replies = 1 case posts_and_replies = 1
case posts = 0 case posts = 0
func filter(ev: NostrEvent) -> Bool {
switch self {
case .posts:
return !ev.is_reply(nil)
case .posts_and_replies:
return true
}
}
} }
struct ContentView: View { struct ContentView: View {
@@ -84,10 +93,12 @@ struct ContentView: View {
var PostingTimelineView: some View { var PostingTimelineView: some View {
VStack { VStack {
TabView(selection: $filter_state) { TabView(selection: $filter_state) {
ContentTimelineView contentTimelineView(filter: FilterState.posts.filter)
.tag(FilterState.posts) .tag(FilterState.posts)
ContentTimelineView .id(FilterState.posts)
contentTimelineView(filter: FilterState.posts_and_replies.filter)
.tag(FilterState.posts_and_replies) .tag(FilterState.posts_and_replies)
.id(FilterState.posts_and_replies)
} }
.tabViewStyle(.page(indexDisplayMode: .never)) .tabViewStyle(.page(indexDisplayMode: .never))
} }
@@ -101,12 +112,13 @@ struct ContentView: View {
} }
.background(colorScheme == .dark ? Color.black : Color.white) .background(colorScheme == .dark ? Color.black : Color.white)
} }
.ignoresSafeArea(.keyboard)
} }
var ContentTimelineView: some View { func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
ZStack { ZStack {
if let damus = self.damus_state { if let damus = self.damus_state {
TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter_event) TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter)
} }
if privkey != nil { if privkey != nil {
PostButtonContainer { PostButtonContainer {
@@ -118,22 +130,14 @@ struct ContentView: View {
var FiltersView: some View { var FiltersView: some View {
VStack{ VStack{
Picker("Filter State", selection: $filter_state) { Picker(NSLocalizedString("Filter State", comment: "Filter state for seeing either only posts, or posts & replies."), selection: $filter_state) {
Text("Posts").tag(FilterState.posts) Text("Posts", comment: "Label for filter for seeing only posts (instead of posts and replies).").tag(FilterState.posts)
Text("Posts & Replies").tag(FilterState.posts_and_replies) Text("Posts & Replies", comment: "Label for filter for seeing posts and replies (instead of only posts).").tag(FilterState.posts_and_replies)
} }
.pickerStyle(.segmented) .pickerStyle(.segmented)
} }
} }
func filter_event(_ ev: NostrEvent) -> Bool {
if self.filter_state == .posts {
return !ev.is_reply(nil)
}
return true
}
func MainContent(damus: DamusState) -> some View { func MainContent(damus: DamusState) -> some View {
VStack { VStack {
NavigationLink(destination: MaybeProfileView, isActive: $profile_open) { NavigationLink(destination: MaybeProfileView, isActive: $profile_open) {
@@ -154,7 +158,7 @@ struct ContentView: View {
case .notifications: case .notifications:
TimelineView(events: $home.notifications, loading: $home.loading, damus: damus, show_friend_icon: true, filter: { _ in true }) TimelineView(events: $home.notifications, loading: $home.loading, damus: damus, show_friend_icon: true, filter: { _ in true })
.navigationTitle("Notifications") .navigationTitle(NSLocalizedString("Notifications", comment: "Navigation title for notifications."))
case .dms: case .dms:
DirectMessagesView(damus_state: damus_state!) DirectMessagesView(damus_state: damus_state!)
@@ -164,7 +168,7 @@ struct ContentView: View {
EmptyView() EmptyView()
} }
} }
.navigationBarTitle(selected_timeline == .home ? "Home" : "Global", displayMode: .inline) .navigationBarTitle(selected_timeline == .home ? NSLocalizedString("Home", comment: "Navigation bar title for Home view where posts and replies appear from those who the user is following.") : NSLocalizedString("Global", comment: "Navigation bar title for Global view where posts from all connected relay servers appear."), displayMode: .inline)
} }
var MaybeSearchView: some View { var MaybeSearchView: some View {
@@ -225,7 +229,7 @@ struct ContentView: View {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
HStack(alignment: .center) { HStack(alignment: .center) {
if home.signal.signal != home.signal.max_signal { if home.signal.signal != home.signal.max_signal {
Text("\(home.signal.signal)/\(home.signal.max_signal)") Text("\(home.signal.signal)/\(home.signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
.font(.callout) .font(.callout)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
@@ -411,10 +415,11 @@ struct ContentView: View {
self.damus_state = DamusState(pool: pool, keypair: keypair, self.damus_state = DamusState(pool: pool, keypair: keypair,
likes: EventCounter(our_pubkey: pubkey), likes: EventCounter(our_pubkey: pubkey),
boosts: EventCounter(our_pubkey: pubkey), boosts: EventCounter(our_pubkey: pubkey),
contacts: Contacts(), contacts: Contacts(our_pubkey: pubkey),
tips: TipCounter(our_pubkey: pubkey), tips: TipCounter(our_pubkey: pubkey),
profiles: Profiles(), profiles: Profiles(),
dms: home.dms dms: home.dms,
previews: PreviewCache()
) )
home.damus_state = self.damus_state! home.damus_state = self.damus_state!

View File

@@ -15,6 +15,8 @@
</array> </array>
</dict> </dict>
</array> </array>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>&quot;Granting Damus access to your photo library allows you to save photos.</string>
<key>LSApplicationQueriesSchemes</key> <key>LSApplicationQueriesSchemes</key>
<array> <array>
<string>river</string> <string>river</string>

View File

@@ -11,8 +11,13 @@ import Foundation
class Contacts { class Contacts {
private var friends: Set<String> = Set() private var friends: Set<String> = Set()
private var friend_of_friends: Set<String> = Set() private var friend_of_friends: Set<String> = Set()
let our_pubkey: String
var event: NostrEvent? var event: NostrEvent?
init(our_pubkey: String) {
self.our_pubkey = our_pubkey
}
func get_friendosphere() -> [String] { func get_friendosphere() -> [String] {
var fs = get_friend_list() var fs = get_friend_list()
fs.append(contentsOf: get_friend_of_friend_list()) fs.append(contentsOf: get_friend_of_friend_list())
@@ -56,6 +61,10 @@ class Contacts {
return friends.contains(pubkey) return friends.contains(pubkey)
} }
func is_friend_or_self(_ pubkey: String) -> Bool {
return pubkey == our_pubkey || is_friend(pubkey)
}
func follow_state(_ pubkey: String) -> FollowState { func follow_state(_ pubkey: String) -> FollowState {
return is_friend(pubkey) ? .follows : .unfollows return is_friend(pubkey) ? .follows : .unfollows
} }

View File

@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import LinkPresentation
struct DamusState { struct DamusState {
let pool: RelayPool let pool: RelayPool
@@ -16,12 +17,13 @@ struct DamusState {
let tips: TipCounter let tips: TipCounter
let profiles: Profiles let profiles: Profiles
let dms: DirectMessagesModel let dms: DirectMessagesModel
let previews: PreviewCache
var pubkey: String { var pubkey: String {
return keypair.pubkey return keypair.pubkey
} }
static var empty: DamusState { static var empty: DamusState {
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel()) 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())
} }
} }

View File

@@ -544,7 +544,9 @@ func process_metadata_event(profiles: Profiles, ev: NostrEvent) {
return return
} }
var old_nip05: String? = nil
if let mprof = profiles.lookup_with_timestamp(id: ev.pubkey) { if let mprof = profiles.lookup_with_timestamp(id: ev.pubkey) {
old_nip05 = mprof.profile.nip05
if mprof.timestamp > ev.created_at { if mprof.timestamp > ev.created_at {
// skip if we already have an newer profile // skip if we already have an newer profile
return return
@@ -554,6 +556,20 @@ func process_metadata_event(profiles: Profiles, ev: NostrEvent) {
let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at) let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at)
profiles.add(id: ev.pubkey, profile: tprof) profiles.add(id: ev.pubkey, profile: tprof)
if let nip05 = profile.nip05, old_nip05 != profile.nip05 {
Task.detached(priority: .background) {
let validated = await validate_nip05(pubkey: ev.pubkey, nip05_str: nip05)
if validated != nil {
print("validated nip05 for '\(nip05)'")
}
DispatchQueue.main.async {
profiles.validated[ev.pubkey] = validated
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
}
}
// load pfps asap // load pfps asap
let picture = tprof.profile.picture ?? robohash(ev.pubkey) let picture = tprof.profile.picture ?? robohash(ev.pubkey)
if let _ = URL(string: picture) { if let _ = URL(string: picture) {

View File

@@ -187,12 +187,22 @@ enum Amount: Equatable {
func amount_sats_str() -> String { func amount_sats_str() -> String {
switch self { switch self {
case .any: case .any:
return "Any" return NSLocalizedString("Any", comment: "Any amount of sats")
case .specific(let amt): case .specific(let amt):
if amt < 1000 { let numberFormatter = NumberFormatter()
return "\(Double(amt) / 1000.0) sats" numberFormatter.numberStyle = .decimal
numberFormatter.minimumFractionDigits = 0
numberFormatter.maximumFractionDigits = 3
numberFormatter.roundingMode = .down
let sats = NSNumber(value: (Double(amt) / 1000.0))
let formattedSats = numberFormatter.string(from: sats) ?? sats.stringValue
if formattedSats == numberFormatter.string(from: 1) {
return NSLocalizedString("\(formattedSats) sat", comment: "Amount of 1 sat.")
} }
return "\(amt / 1000) sats"
return NSLocalizedString("\(formattedSats) sats", comment: "Amount of sats.")
} }
} }
} }

View File

@@ -41,20 +41,21 @@ class SearchHomeModel: ObservableObject {
} }
func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) { func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) {
switch conn_ev { guard case .nostr_event(let event) = conn_ev else {
case .ws_event: return
break }
case .nostr_event(let event):
switch event { switch event {
case .event(let sub_id, let ev): case .event(let sub_id, let ev):
guard sub_id == self.base_subid || sub_id == self.profiles_subid else { guard sub_id == self.base_subid || sub_id == self.profiles_subid else {
return return
} }
if ev.is_textlike && ev.should_show_event { if ev.is_textlike && ev.should_show_event && !ev.is_reply(nil) {
if seen_pubkey.contains(ev.pubkey) { if seen_pubkey.contains(ev.pubkey) {
return return
} }
seen_pubkey.insert(ev.pubkey) seen_pubkey.insert(ev.pubkey)
let _ = insert_uniq_sorted_event(events: &events, new_ev: ev) { let _ = insert_uniq_sorted_event(events: &events, new_ev: ev) {
$0.created_at > $1.created_at $0.created_at > $1.created_at
} }
@@ -77,7 +78,6 @@ class SearchHomeModel: ObservableObject {
} }
} }
} }
}
func find_profiles_to_fetch_pk(profiles: Profiles, event_pubkeys: [String]) -> [String] { func find_profiles_to_fetch_pk(profiles: Profiles, event_pubkeys: [String]) -> [String] {
var pubkeys = Set<String>() var pubkeys = Set<String>()
@@ -112,8 +112,12 @@ func load_profiles(profiles_subid: String, relay_id: String, events: [NostrEvent
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, events: events) let authors = find_profiles_to_fetch(profiles: damus_state.profiles, events: events)
filter.authors = authors filter.authors = authors
if !authors.isEmpty { guard !authors.isEmpty else {
return
}
print("loading \(authors.count) profiles from \(relay_id)") print("loading \(authors.count) profiles from \(relay_id)")
damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { sub_id, conn_ev in damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { sub_id, conn_ev in
let (sid, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: conn_ev) { sub_id, ev in let (sid, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: conn_ev) { sub_id, ev in
guard sub_id == profiles_subid else { guard sub_id == profiles_subid else {
@@ -134,5 +138,4 @@ func load_profiles(profiles_subid: String, relay_id: String, events: [NostrEvent
damus_state.pool.unsubscribe(sub_id: profiles_subid, to: [relay_id]) damus_state.pool.unsubscribe(sub_id: profiles_subid, to: [relay_id])
} }
} }
}

View File

@@ -115,7 +115,7 @@ class ThreadModel: ObservableObject {
ref_events.referenced_ids = ev.referenced_ids.map { $0.ref_id } ref_events.referenced_ids = ev.referenced_ids.map { $0.ref_id }
ref_events.referenced_ids?.append(ev.id) ref_events.referenced_ids?.append(ev.id)
ref_events.limit = 50 ref_events.limit = 50
events_filter.ids = ref_events.referenced_ids! events_filter.ids = ref_events.referenced_ids ?? []
events_filter.limit = 100 events_filter.limit = 100
events_filter.ids?.append(ev.id) events_filter.ids?.append(ev.id)
case .event_id(let evid): case .event_id(let evid):

View File

@@ -446,11 +446,13 @@ func hex_encode(_ data: Data) -> String {
func random_bytes(count: Int) -> Data { func random_bytes(count: Int) -> Data {
var data = Data(count: count) var bytes = [Int8](repeating: 0, count: count)
_ = data.withUnsafeMutableBytes { mutableBytes in guard
SecRandomCopyBytes(kSecRandomDefault, count, mutableBytes.baseAddress!) SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) == errSecSuccess
else {
fatalError("can't copy secure random data")
} }
return data return Data(bytes: bytes, count: count)
} }
func refid_to_tag(_ ref: ReferencedId) -> [String] { func refid_to_tag(_ ref: ReferencedId) -> [String] {

View File

@@ -80,7 +80,32 @@ func parse_nostr_ref_uri(_ p: Parser) -> ReferencedId? {
return ReferencedId(ref_id: pk, relay_id: nil, key: typ) return ReferencedId(ref_id: pk, relay_id: nil, key: typ)
} }
func decode_universal_link(_ s: String) -> NostrLink? {
var uri = s.replacingOccurrences(of: "https://damus.io/r/", with: "")
uri = uri.replacingOccurrences(of: "https://damus.io/", with: "")
uri = uri.replacingOccurrences(of: "/", with: "")
guard let decoded = try? bech32_decode(uri) else {
return nil
}
let h = hex_encode(decoded.data)
if decoded.hrp == "note" {
return .ref(ReferencedId(ref_id: h, relay_id: nil, key: "e"))
} else if decoded.hrp == "npub" {
return .ref(ReferencedId(ref_id: h, relay_id: nil, key: "p"))
}
// TODO: handle nprofile, etc
return nil
}
func decode_nostr_uri(_ s: String) -> NostrLink? { func decode_nostr_uri(_ s: String) -> NostrLink? {
if s.starts(with: "https://damus.io/") {
return decode_universal_link(s)
}
var uri = s.replacingOccurrences(of: "nostr://", with: "") var uri = s.replacingOccurrences(of: "nostr://", with: "")
uri = uri.replacingOccurrences(of: "nostr:", with: "") uri = uri.replacingOccurrences(of: "nostr:", with: "")

View File

@@ -12,6 +12,17 @@ enum NostrResponse: Decodable {
case notice(String) case notice(String)
case eose(String) case eose(String)
var subid: String? {
switch self {
case .event(let sub_id, _):
return sub_id
case .eose(let sub_id):
return sub_id
case .notice:
return nil
}
}
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer() var container = try decoder.unkeyedContainer()

View File

@@ -11,6 +11,11 @@ import UIKit
class Profiles { class Profiles {
var profiles: [String: TimestampedProfile] = [:] var profiles: [String: TimestampedProfile] = [:]
var validated: [String: NIP05] = [:]
func is_validated(_ pk: String) -> NIP05? {
return validated[pk]
}
func add(id: String, profile: TimestampedProfile) { func add(id: String, profile: TimestampedProfile) {
profiles[id] = profile profiles[id] = profile

View File

@@ -28,9 +28,20 @@ struct RelayHandler {
let callback: (String, NostrConnectionEvent) -> () let callback: (String, NostrConnectionEvent) -> ()
} }
struct QueuedRequest {
let req: NostrRequest
let relay: String
}
struct NostrRequestId: Equatable, Hashable {
let relay: String?
let sub_id: String
}
class RelayPool { class RelayPool {
var relays: [Relay] = [] var relays: [Relay] = []
var handlers: [RelayHandler] = [] var handlers: [RelayHandler] = []
var request_queue: [QueuedRequest] = []
var descriptors: [RelayDescriptor] { var descriptors: [RelayDescriptor] {
relays.map { $0.descriptor } relays.map { $0.descriptor }
@@ -148,13 +159,38 @@ class RelayPool {
send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: to) send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: to)
} }
func count_queued(relay: String) -> Int {
var c = 0
for request in request_queue {
if request.relay == relay {
c += 1
}
}
return c
}
func queue_req(r: NostrRequest, relay: String) {
let count = count_queued(relay: relay)
guard count <= 10 else {
print("can't queue, too many queued events for \(relay)")
return
}
print("queueing request: \(r) for \(relay)")
request_queue.append(QueuedRequest(req: r, relay: relay))
}
func send(_ req: NostrRequest, to: [String]? = nil) { func send(_ req: NostrRequest, to: [String]? = nil) {
let relays = to.map{ get_relays($0) } ?? self.relays let relays = to.map{ get_relays($0) } ?? self.relays
for relay in relays { for relay in relays {
if relay.connection.isConnected { guard relay.connection.isConnected else {
relay.connection.send(req) queue_req(r: req, relay: relay.id)
continue
} }
relay.connection.send(req)
} }
} }
@@ -193,9 +229,28 @@ class RelayPool {
} }
} }
func run_queue(_ relay_id: String) {
self.request_queue = request_queue.reduce(into: Array<QueuedRequest>()) { (q, req) in
guard req.relay == relay_id else {
q.append(req)
return
}
print("running queueing request: \(req.req) for \(relay_id)")
self.send(req.req, to: [relay_id])
}
}
func handle_event(relay_id: String, event: NostrConnectionEvent) { func handle_event(relay_id: String, event: NostrConnectionEvent) {
record_last_pong(relay_id: relay_id, event: event) record_last_pong(relay_id: relay_id, event: event)
// run req queue when we reconnect
if case .ws_event(let ws) = event {
if case .connected = ws {
run_queue(relay_id)
}
}
// handle reconnect logic, etc? // handle reconnect logic, etc?
for handler in handlers { for handler in handlers {
handler.callback(relay_id, event) handler.callback(relay_id, event)

View File

@@ -11,7 +11,7 @@ public class Constants {
static let PUB_KEY = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681" static let PUB_KEY = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
static let EXAMPLE_DEMOS = DamusState(pool: RelayPool(), keypair: Keypair(pubkey: PUB_KEY, privkey: "privkey"), likes: EventCounter(our_pubkey: PUB_KEY), boosts: EventCounter(our_pubkey: PUB_KEY), contacts: Contacts(), tips: TipCounter(our_pubkey: PUB_KEY), profiles: Profiles(), dms: DirectMessagesModel()) static let EXAMPLE_DEMOS: DamusState = .empty
static let EXAMPLE_EVENTS = [ static let EXAMPLE_EVENTS = [
NostrEvent(id: UUID().description, content: "Nostr - Damus... Haha get it? Bonjour Le Monde mon Ami! C'est la tres importante", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"), NostrEvent(id: UUID().description, content: "Nostr - Damus... Haha get it? Bonjour Le Monde mon Ami! C'est la tres importante", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),

View File

@@ -12,26 +12,24 @@ class CustomLinkView: LPLinkView {
override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) } override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
} }
enum Metadata {
case linkmeta(LPLinkMetadata)
case url(URL)
}
struct LinkViewRepresentable: UIViewRepresentable { struct LinkViewRepresentable: UIViewRepresentable {
typealias UIViewType = CustomLinkView typealias UIViewType = CustomLinkView
var metadata: LPLinkMetadata? let meta: Metadata
var url: URL?
func makeUIView(context: Context) -> CustomLinkView { func makeUIView(context: Context) -> CustomLinkView {
switch meta {
if let metadata { case .linkmeta(let linkmeta):
let linkView = CustomLinkView(metadata: metadata) return CustomLinkView(metadata: linkmeta)
return linkView case .url(let url):
return CustomLinkView(url: url)
} }
if let url {
let linkView = CustomLinkView(url: url)
return linkView
}
return CustomLinkView()
} }
func updateUIView(_ uiView: CustomLinkView, context: Context) { func updateUIView(_ uiView: CustomLinkView, context: Context) {

View File

@@ -15,12 +15,14 @@ public struct Markdown {
return url.contains("://") ? url : "https://" + url return url.contains("://") ? url : "https://" + url
} }
/// Parse a string with markdown into an `AttributedString`, if possible, or else return it as regular text.
public static func parse(content: String) -> AttributedString { public static func parse(content: String) -> AttributedString {
// Similar to the parsing in NoteContentView
let md_opts: AttributedString.MarkdownParsingOptions = let md_opts: AttributedString.MarkdownParsingOptions =
.init(interpretedSyntax: .inlineOnlyPreservingWhitespace) .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
if let txt = try? AttributedString(markdown: content, options: md_opts) { // TODO: escape unintentional markdown
let escaped = content.replacingOccurrences(of: "\\_", with: "\\\\\\_")
if let txt = try? AttributedString(markdown: escaped, options: md_opts) {
return txt return txt
} else { } else {
return AttributedString(stringLiteral: content) return AttributedString(stringLiteral: content)
@@ -33,11 +35,13 @@ public struct Markdown {
var output = input var output = input
// Start with the last match, because replacing the first would invalidate all subsequent indices // Start with the last match, because replacing the first would invalidate all subsequent indices
for match in matches.reversed() { for match in matches.reversed() {
guard let range = Range(match.range, in: input) else { continue } guard let range = Range(match.range, in: input)
let url = input[range] , let url = match.url else { continue }
output.replaceSubrange(range, with: "[\(url)](\(Markdown.withScheme(url)))") let text = input[range]
// Use the absoluteString from the matched URL, except when it defaults to http (since we default to https)
let uri = url.scheme == "http" ? Markdown.withScheme(text) : url.absoluteString
output.replaceSubrange(range, with: "[\(text)](\(uri))")
} }
// TODO: escape unintentional markdown
return Markdown.parse(content: output) return Markdown.parse(content: output)
} }
} }

65
damus/Util/NIP05.swift Normal file
View File

@@ -0,0 +1,65 @@
//
// NIP05.swift
// damus
//
// Created by William Casarin on 2023-01-04.
//
import Foundation
struct NIP05 {
let username: String
let host: String
var url: URL? {
URL(string: "https://\(host)/.well-known/nostr.json?name=\(username)")
}
static func parse(_ nip05: String) -> NIP05? {
let parts = nip05.split(separator: "@")
guard parts.count == 2 else {
return nil
}
return NIP05(username: String(parts[0]), host: String(parts[1]))
}
}
struct NIP05Response: Decodable {
let names: [String: String]
let relays: [String: [String]]?
}
enum NIP05Validation {
case invalid
case valid
}
func validate_nip05(pubkey: String, nip05_str: String) async -> NIP05? {
guard let nip05 = NIP05.parse(nip05_str) else {
return nil
}
guard let url = nip05.url else {
return nil
}
guard let ret = try? await URLSession.shared.data(from: url) else {
return nil
}
let dat = ret.0
guard let decoded = try? JSONDecoder().decode(NIP05Response.self, from: dat) else {
return nil
}
guard let stored_pk = decoded.names[nip05.username] else {
return nil
}
guard stored_pk == pubkey else {
return nil
}
return nip05
}

View File

@@ -0,0 +1,35 @@
//
// PreviewCache.swift
// damus
//
// Created by William Casarin on 2023-01-02.
//
import Foundation
import LinkPresentation
enum Preview {
case value(LinkViewRepresentable)
case failed
}
class PreviewCache {
var previews: [String: Preview]
func lookup(_ evid: String) -> Preview? {
return previews[evid]
}
func store(evid: String, preview: LinkViewRepresentable?) {
switch preview {
case .none:
previews[evid] = .failed
case .some(let meta):
previews[evid] = .value(meta)
}
}
init() {
self.previews = [:]
}
}

28
damus/Util/Theme.swift Normal file
View File

@@ -0,0 +1,28 @@
//
// Theme.swift
// damus
//
// Created by Ben Weeks on 1/1/23.
//
import Foundation
import UIKit
class Theme {
static func navigationBarColors(background : UIColor?,
titleColor : UIColor? = nil, tintColor : UIColor? = nil ){
let navigationAppearance = UINavigationBarAppearance()
navigationAppearance.configureWithOpaqueBackground()
navigationAppearance.backgroundColor = background ?? .clear
navigationAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .black]
navigationAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .black]
UINavigationBar.appearance().standardAppearance = navigationAppearance
UINavigationBar.appearance().compactAppearance = navigationAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navigationAppearance
UINavigationBar.appearance().tintColor = tintColor ?? titleColor ?? .black
}
}

View File

@@ -16,10 +16,10 @@ struct AddRelayView: View {
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Form { Form {
Section("Add Relay") { Section(NSLocalizedString("Add Relay", comment: "Label for section for adding a relay server.")) {
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
HStack{ HStack{
TextField("wss://some.relay.com", text: $relay) TextField(NSLocalizedString("wss://some.relay.com", comment: "Placeholder example for relay server address."), text: $relay)
.padding(2) .padding(2)
.padding(.leading, 25) .padding(.leading, 25)
.autocorrectionDisabled(true) .autocorrectionDisabled(true)
@@ -47,7 +47,7 @@ struct AddRelayView: View {
VStack { VStack {
HStack { HStack {
Button("Cancel") { Button(NSLocalizedString("Cancel", comment: "Button to cancel out of view adding user inputted relay.")) {
show_add_relay = false show_add_relay = false
action(nil) action(nil)
} }
@@ -55,7 +55,7 @@ struct AddRelayView: View {
Spacer() Spacer()
Button("Add") { Button(NSLocalizedString("Add", comment: "Button to confirm adding user inputted relay.")) {
show_add_relay = false show_add_relay = false
action(relay) action(relay)
relay = "" relay = ""

View File

@@ -15,13 +15,13 @@ struct CarouselItem: Identifiable {
} }
let carousel_items = [ let carousel_items = [
CarouselItem(image: Image("digital-nomad"), text: Text("Welcome to the social network \(Text("you").italic()) control.")), CarouselItem(image: Image("digital-nomad"), text: Text("Welcome to the social network \(Text("you", comment: "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.").italic()) control.", comment: "Welcoming message to the reader. The variable is 'you', the reader.")),
CarouselItem(image: Image("encrypted-message"), CarouselItem(image: Image("encrypted-message"),
text: Text("\(Text("Encrypted").bold()). End-to-End encrypted private messaging. Keep Big Tech out of your DMs")), text: Text("\(Text("Encrypted", comment: "Heading indicating that this application keeps private messaging end-to-end encrypted.").bold()). End-to-End encrypted private messaging. Keep Big Tech out of your DMs", comment: "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.")),
CarouselItem(image: Image("undercover"), CarouselItem(image: Image("undercover"),
text: Text("\(Text("Private").bold()). Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.")), text: Text("\(Text("Private", comment: "Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading.").bold()). Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.", comment: "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.")),
CarouselItem(image: Image("bitcoin-p2p"), CarouselItem(image: Image("bitcoin-p2p"),
text: Text("\(Text("Earn Money").bold()). Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet.")) text: Text("\(Text("Earn Money", comment: "Heading indicating that this application allows users to earn money.").bold()). Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet.", comment: "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."))
] ]
struct CarouselView: View { struct CarouselView: View {

View File

@@ -86,7 +86,7 @@ struct ChatView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if just_started { if just_started {
HStack { HStack {
ProfileName(pubkey: event.pubkey, profile: damus_state.profiles.lookup(id: event.pubkey), contacts: damus_state.contacts, show_friend_confirmed: true) ProfileName(pubkey: event.pubkey, profile: damus_state.profiles.lookup(id: event.pubkey), damus: damus_state, show_friend_confirmed: true)
.foregroundColor(colorScheme == .dark ? id_to_color(event.pubkey) : Color.black) .foregroundColor(colorScheme == .dark ? id_to_color(event.pubkey) : Color.black)
//.shadow(color: Color.black, radius: 2) //.shadow(color: Color.black, radius: 2)
Text("\(format_relative_time(event.created_at))") Text("\(format_relative_time(event.created_at))")
@@ -96,7 +96,7 @@ struct ChatView: View {
if let ref_id = thread.replies.lookup(event.id) { if let ref_id = thread.replies.lookup(event.id) {
if !is_reply_to_prev() { if !is_reply_to_prev() {
ReplyQuoteView(privkey: damus_state.keypair.privkey, quoter: event, event_id: ref_id, profiles: damus_state.profiles) ReplyQuoteView(privkey: damus_state.keypair.privkey, quoter: event, event_id: ref_id, profiles: damus_state.profiles, previews: damus_state.previews)
.frame(maxHeight: expand_reply ? nil : 100) .frame(maxHeight: expand_reply ? nil : 100)
.environmentObject(thread) .environmentObject(thread)
.onTapGesture { .onTapGesture {
@@ -106,7 +106,7 @@ struct ChatView: View {
} }
} }
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey), artifacts: .just_content(event.content), size: .normal) NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, previews: damus_state.previews, show_images: should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey), artifacts: .just_content(event.content), size: .normal)
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey { if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
let bar = make_actionbar_model(ev: event, damus: damus_state) let bar = make_actionbar_model(ev: event, damus: damus_state)

View File

@@ -24,7 +24,7 @@ struct ChatroomView: View {
next_ev: ind == count-1 ? nil : thread.events[ind+1], next_ev: ind == count-1 ? nil : thread.events[ind+1],
damus_state: damus damus_state: damus
) )
.event_context_menu(ev, privkey: damus.keypair.privkey) .event_context_menu(ev, pubkey: ev.pubkey, privkey: damus.keypair.privkey)
.onTapGesture { .onTapGesture {
if thread.initial_event.id == ev.id { if thread.initial_event.id == ev.id {
//dismiss() //dismiss()

View File

@@ -6,6 +6,7 @@
// //
import AVFoundation import AVFoundation
import SwiftUI import SwiftUI
import Kingfisher
struct ConfigView: View { struct ConfigView: View {
let state: DamusState let state: DamusState
@@ -54,19 +55,28 @@ struct ConfigView: View {
var body: some View { var body: some View {
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
Form { Form {
Section("Relays") { Section {
List(Array(relays), id: \.url) { relay in List(Array(relays), id: \.url) { relay in
RelayView(state: state, relay: relay.url.absoluteString) RelayView(state: state, relay: relay.url.absoluteString)
} }
} header: {
HStack {
Text("Relays", comment: "Header text for relay server list for configuration.")
Spacer()
Button(action: { show_add_relay = true }) {
Image(systemName: "plus")
.foregroundColor(.accentColor)
}
}
} }
Section("Recommended Relays") { Section(NSLocalizedString("Recommended Relays", comment: "Section title for recommend relay servers that could be added as part of configuration")) {
List(recommended, id: \.url) { r in List(recommended, id: \.url) { r in
RecommendedRelayView(damus: state, relay: r.url.absoluteString) RecommendedRelayView(damus: state, relay: r.url.absoluteString)
} }
} }
Section("Public Account ID") { Section(NSLocalizedString("Public Account ID", comment: "Section title for the user's public account ID.")) {
HStack { HStack {
Text(state.keypair.pubkey_bech32) Text(state.keypair.pubkey_bech32)
@@ -76,10 +86,10 @@ struct ConfigView: View {
} }
if let sec = state.keypair.privkey_bech32 { if let sec = state.keypair.privkey_bech32 {
Section("Secret Account Login Key") { Section(NSLocalizedString("Secret Account Login Key", comment: "Section title for user's secret account login key.")) {
HStack { HStack {
if show_privkey == false { if show_privkey == false {
SecureField("PrivateKey", text: $privkey) SecureField(NSLocalizedString("PrivateKey", comment: "Title of the secure field that holds the user's private key."), text: $privkey)
.disabled(true) .disabled(true)
} else { } else {
Text(sec) Text(sec)
@@ -89,13 +99,13 @@ struct ConfigView: View {
CopyButton(is_pk: false) CopyButton(is_pk: false)
} }
Toggle("Show", isOn: $show_privkey) Toggle(NSLocalizedString("Show", comment: "Toggle to show or hide user's secret account login key."), isOn: $show_privkey)
} }
} }
Section("Wallet Selector") { Section(NSLocalizedString("Wallet Selector", comment: "Section title for selection of wallet.")) {
Toggle("Show wallet selector", isOn: $user_settings.show_wallet_selector).toggleStyle(.switch) Toggle(NSLocalizedString("Show wallet selector", comment: "Toggle to show or hide selection of wallet."), isOn: $user_settings.show_wallet_selector).toggleStyle(.switch)
Picker("Select default wallet", Picker(NSLocalizedString("Select default wallet", comment: "Prompt selection of user's default wallet"),
selection: $user_settings.default_wallet) { selection: $user_settings.default_wallet) {
ForEach(Wallet.allCases, id: \.self) { wallet in ForEach(Wallet.allCases, id: \.self) { wallet in
Text(wallet.model.displayName) Text(wallet.model.displayName)
@@ -104,45 +114,43 @@ struct ConfigView: View {
} }
} }
Section("Reset") { Section(NSLocalizedString("Clear Cache", comment: "Section title for clearing cached data.")) {
Button("Logout") { Button(NSLocalizedString("Clear", comment: "Button for clearing cached data.")) {
KingfisherManager.shared.cache.clearMemoryCache()
KingfisherManager.shared.cache.clearDiskCache()
KingfisherManager.shared.cache.cleanExpiredDiskCache()
}
}
Section(NSLocalizedString("Reset", comment: "Section title for resetting the user")) {
Button(NSLocalizedString("Logout", comment: "Button to logout the user.")) {
confirm_logout = true confirm_logout = true
} }
} }
} }
VStack {
HStack {
Spacer()
Button(action: { show_add_relay = true }) {
Label("", systemImage: "plus")
.foregroundColor(.accentColor)
.padding()
} }
} .navigationTitle(NSLocalizedString("Settings", comment: "Navigation title for Settings view."))
Spacer()
}
}
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.large)
.alert("Logout", isPresented: $confirm_logout) { .alert(NSLocalizedString("Logout", comment: "Alert for logging out the user."), isPresented: $confirm_logout) {
Button("Cancel") { Button(NSLocalizedString("Cancel", comment: "Cancel out of logging out the user.")) {
confirm_logout = false confirm_logout = false
} }
Button("Logout") { Button(NSLocalizedString("Logout", comment: "Button for logging out the user.")) {
notify(.logout, ()) notify(.logout, ())
} }
} message: { } message: {
Text("Make sure your nsec account key is saved before you logout or you will lose access to this account") Text("Make sure your nsec account key is saved before you logout or you will lose access to this account", comment: "Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out.")
} }
.sheet(isPresented: $show_add_relay) { .sheet(isPresented: $show_add_relay) {
AddRelayView(show_add_relay: $show_add_relay, relay: $new_relay) { m_relay in AddRelayView(show_add_relay: $show_add_relay, relay: $new_relay) { m_relay in
guard let relay = m_relay else { guard var relay = m_relay else {
return return
} }
if relay.starts(with: "wss://") == false {
relay = "wss://" + relay
}
guard let url = URL(string: relay) else { guard let url = URL(string: relay) else {
return return
} }
@@ -161,9 +169,9 @@ struct ConfigView: View {
return return
} }
state.pool.connect(to: [new_relay]) state.pool.connect(to: [relay])
guard let new_ev = add_relay(ev: ev, privkey: privkey, current_relays: state.pool.descriptors, relay: new_relay, info: info) else { guard let new_ev = add_relay(ev: ev, privkey: privkey, current_relays: state.pool.descriptors, relay: relay, info: info) else {
return return
} }

View File

@@ -35,14 +35,14 @@ struct CreateAccountView: View {
HStack(alignment: .top) { HStack(alignment: .top) {
VStack { VStack {
Text(" ") Text(" ", comment: "Blank space to separate profile picture from profile editor form.")
.foregroundColor(.white) .foregroundColor(.white)
} }
VStack { VStack {
SignupForm { SignupForm {
FormLabel(NSLocalizedString("Username", comment: "Label to prompt username entry.")) FormLabel(NSLocalizedString("Username", comment: "Label to prompt username entry."))
HStack(spacing: 0.0) { HStack(spacing: 0.0) {
Text("@") Text("@", comment: "Prefix character to username.")
.foregroundColor(.white) .foregroundColor(.white)
.padding(.leading, -25.0) .padding(.leading, -25.0)
@@ -151,7 +151,7 @@ func FormLabel(_ title: String, optional: Bool = false) -> some View {
.bold() .bold()
.foregroundColor(.white) .foregroundColor(.white)
if optional { if optional {
Text("optional") Text("optional", comment: "Label indicating that a form input is optional.")
.font(.callout) .font(.callout)
.foregroundColor(.white.opacity(0.5)) .foregroundColor(.white.opacity(0.5))
} }

View File

@@ -19,7 +19,7 @@ struct DMChatView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
ForEach(Array(zip(dms.events, dms.events.indices)), id: \.0.id) { (ev, ind) in ForEach(Array(zip(dms.events, dms.events.indices)), id: \.0.id) { (ev, ind) in
DMView(event: dms.events[ind], damus_state: damus_state) DMView(event: dms.events[ind], damus_state: damus_state)
.event_context_menu(ev, privkey: damus_state.keypair.privkey) .event_context_menu(ev, pubkey: ev.pubkey, privkey: damus_state.keypair.privkey)
} }
EndBlock(height: 80) EndBlock(height: 80)
} }
@@ -27,6 +27,10 @@ struct DMChatView: View {
} }
.onAppear { .onAppear {
scroller.scrollTo("endblock") scroller.scrollTo("endblock")
}.onChange(of: dms.events.count) { _ in
withAnimation {
scroller.scrollTo("endblock")
}
} }
} }
} }
@@ -40,7 +44,7 @@ struct DMChatView: View {
HStack { HStack {
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles) ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles)
ProfileName(pubkey: pubkey, profile: profile, contacts: damus_state.contacts, show_friend_confirmed: true) ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_friend_confirmed: true)
} }
} }
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
@@ -138,14 +142,14 @@ struct DMChatView: View {
Footer Footer
} }
Text("Send a message to start the conversation...") Text("Send a message to start the conversation...", comment: "Text prompt for user to send a message to the other user.")
.lineLimit(nil) .lineLimit(nil)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.horizontal, 40) .padding(.horizontal, 40)
.opacity(((dms.events.count == 0) ? 1.0 : 0.0)) .opacity(((dms.events.count == 0) ? 1.0 : 0.0))
.foregroundColor(.gray) .foregroundColor(.gray)
} }
.navigationTitle("DM") .navigationTitle(NSLocalizedString("DM", comment: "Navigation title for DM view, which is the English abbreviation for Direct Message."))
.toolbar { Header } .toolbar { Header }
} }
} }

View File

@@ -23,7 +23,7 @@ struct DMView: View {
let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey) let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_img, artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), size: .normal) NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, previews: damus_state.previews, show_images: should_show_img, artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), size: .normal)
.foregroundColor(is_ours ? Color.white : Color.primary) .foregroundColor(is_ours ? Color.white : Color.primary)
.padding(10) .padding(10)
.background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15)) .background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15))

View File

@@ -53,7 +53,7 @@ struct DirectMessagesView: View {
var body: some View { var body: some View {
MainContent MainContent
.navigationTitle("Encrypted DMs") .navigationTitle(NSLocalizedString("Encrypted DMs", comment: "Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message."))
} }
} }

View File

@@ -15,11 +15,6 @@ func isHttpsUrl(_ string: String) -> Bool {
return urlTest.evaluate(with: string) return urlTest.evaluate(with: string)
} }
struct NIP05 {
let username: String
let host: String
}
func isImage(_ urlString: String) -> Bool { func isImage(_ urlString: String) -> Bool {
let imageTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/tiff", "image/bmp", "image/webp"] let imageTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/tiff", "image/bmp", "image/webp"]
@@ -101,11 +96,7 @@ struct EditMetadataView: View {
} }
var nip05_parts: NIP05? { var nip05_parts: NIP05? {
let parts = nip05.split(separator: "@") return NIP05.parse(nip05)
guard parts.count == 2 else {
return nil
}
return NIP05(username: String(parts[0]), host: String(parts[1]))
} }
var body: some View { var body: some View {
@@ -116,32 +107,32 @@ struct EditMetadataView: View {
Spacer() Spacer()
} }
Form { Form {
Section("Your Name") { Section(NSLocalizedString("Your Name", comment: "Label for Your Name section of user profile form.")) {
TextField("Satoshi Nakamoto", text: $display_name) TextField("Satoshi Nakamoto", text: $display_name)
.autocorrectionDisabled(true) .autocorrectionDisabled(true)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
} }
Section("Username") { Section(NSLocalizedString("Username", comment: "Label for Username section of user profile form.")) {
TextField("satoshi", text: $name) TextField("satoshi", text: $name)
.autocorrectionDisabled(true) .autocorrectionDisabled(true)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
} }
Section ("Profile Picture") { Section (NSLocalizedString("Profile Picture", comment: "Label for Profile Picture section of user profile form.")) {
TextField("https://example.com/pic.jpg", text: $picture) TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $picture)
.autocorrectionDisabled(true) .autocorrectionDisabled(true)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
} }
Section("Website") { Section(NSLocalizedString("Website", comment: "Label for Website section of user profile form.")) {
TextField("https://jb55.com", text: $website) TextField(NSLocalizedString("https://jb55.com", comment: "Placeholder example text for website URL for user profile."), text: $website)
.autocorrectionDisabled(true) .autocorrectionDisabled(true)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
} }
Section("About Me") { Section(NSLocalizedString("About Me", comment: "Label for About Me section of user profile form.")) {
let placeholder = NSLocalizedString("Absolute Boss", comment: "Placeholder text for About Me description.") let placeholder = NSLocalizedString("Absolute Boss", comment: "Placeholder text for About Me description.")
ZStack(alignment: .topLeading) { ZStack(alignment: .topLeading) {
TextEditor(text: $about) TextEditor(text: $about)
@@ -155,33 +146,33 @@ struct EditMetadataView: View {
} }
} }
Section("Bitcoin Lightning Tips") { Section(NSLocalizedString("Bitcoin Lightning Tips", comment: "Label for Bitcoin Lightning Tips section of user profile form.")) {
TextField("Lightning Address or LNURL", text: $ln) TextField(NSLocalizedString("Lightning Address or LNURL", comment: "Placeholder text for entry of Lightning Address or LNURL."), text: $ln)
.autocorrectionDisabled(true) .autocorrectionDisabled(true)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
} }
Section(content: { Section(content: {
TextField("jb55@jb55.com", text: $nip05) TextField(NSLocalizedString("jb55@jb55.com", comment: "Placeholder example text for identifier used for NIP-05 verification."), text: $nip05)
.autocorrectionDisabled(true) .autocorrectionDisabled(true)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
}, header: { }, header: {
Text("NIP-05 Verification") Text("NIP-05 Verification", comment: "Label for NIP-05 Verification section of user profile form.")
}, footer: { }, footer: {
if let parts = nip05_parts { if let parts = nip05_parts {
Text(String.localizedStringWithFormat("'%@' at '%@' will be used for verification", parts.username, parts.host)) Text("'\(parts.username)' at '\(parts.host)' will be used for verification", comment: "Description of how the nip05 identifier would be used for verification.")
} else { } else {
Text(String.localizedStringWithFormat("'%@' is an invalid nip05 identifier. It should look like an email.", nip05)) Text("'\(nip05)' is an invalid nip05 identifier. It should look like an email.", comment: "Description of why the nip05 identifier is invalid.")
} }
}) })
Button("Save") { Button(NSLocalizedString("Save", comment: "Button for saving profile.")) {
save() save()
dismiss() dismiss()
} }
} }
} }
.navigationTitle("Edit Profile") .navigationTitle(NSLocalizedString("Edit Profile", comment: "Title of navigation view for Edit Profile."))
} }
} }

View File

@@ -13,7 +13,7 @@ struct EmptyTimelineView: View {
Image(systemName: "tray.fill") Image(systemName: "tray.fill")
.font(.system(size: 35)) .font(.system(size: 35))
.padding() .padding()
Text("Nothing to see here. Check back later!") Text("Nothing to see here. Check back later!", comment: "Indicates that there are no notes in the timeline to view.")
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.font(.callout.weight(.medium)) .font(.callout.weight(.medium))
} }

View File

@@ -24,6 +24,7 @@ struct EventActionBar: View {
let generator = UIImpactFeedbackGenerator(style: .medium) let generator = UIImpactFeedbackGenerator(style: .medium)
@State var sheet: ActionBarSheet? = nil @State var sheet: ActionBarSheet? = nil
@State var confirm_boost: Bool = false @State var confirm_boost: Bool = false
@State var show_share_sheet: Bool = false
@StateObject var bar: ActionBarModel @StateObject var bar: ActionBarModel
var body: some View { var body: some View {
@@ -40,6 +41,7 @@ struct EventActionBar: View {
EventActionButton(img: "bubble.left", col: nil) { EventActionButton(img: "bubble.left", col: nil) {
notify(.reply, event) notify(.reply, event)
} }
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
} }
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
@@ -55,6 +57,7 @@ struct EventActionBar: View {
} }
} }
} }
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")") Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
@@ -69,6 +72,12 @@ struct EventActionBar: View {
} }
} }
} }
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
EventActionButton(img: "square.and.arrow.up", col: Color.gray) {
show_share_sheet = true
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
/* /*
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
@@ -86,15 +95,22 @@ struct EventActionBar: View {
} }
*/ */
} }
.alert("Boost", isPresented: $confirm_boost) { .sheet(isPresented: $show_share_sheet) {
if let note_id = bech32_note_id(event.id) {
if let url = URL(string: "https://damus.io/" + note_id) {
ShareSheet(activityItems: [url])
}
}
}
.alert(NSLocalizedString("Boost", comment: "Title of alert for confirming to boost a post."), isPresented: $confirm_boost) {
Button("Cancel") { Button("Cancel") {
confirm_boost = false confirm_boost = false
} }
Button("Boost") { Button(NSLocalizedString("Boost", comment: "Button to confirm boosting a post.")) {
send_boost() send_boost()
} }
} message: { } message: {
Text("Are you sure you want to boost this post?") Text("Are you sure you want to boost this post?", comment: "Alert message to ask if user wants to boost a post.")
} }
.onReceive(handle_notify(.liked)) { n in .onReceive(handle_notify(.liked)) { n in
let liked = n.object as! Counted let liked = n.object as! Counted
@@ -142,7 +158,6 @@ func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) ->
.font(.footnote.weight(.medium)) .font(.footnote.weight(.medium))
.foregroundColor(col == nil ? Color.gray : col!) .foregroundColor(col == nil ? Color.gray : col!)
} }
.padding(.trailing, 40)
} }
struct LikeButton: View { struct LikeButton: View {
@@ -154,11 +169,11 @@ struct LikeButton: View {
var body: some View { var body: some View {
Button(action: action) { Button(action: action) {
if liked { if liked {
Text("🤙") Text("🤙", comment: "Button with emoji to like an event.")
} else { } else {
Label("&nbsp;", systemImage: "hand.thumbsup") Image("shaka")
.font(.footnote.weight(.medium)) .renderingMode(.template)
.foregroundColor(Color.gray) .foregroundColor(.gray)
} }
} }
} }

View File

@@ -59,7 +59,7 @@ struct EventDetailView: View {
Group { Group {
switch cev { switch cev {
case .collapsed(let c): case .collapsed(let c):
Text("··· \(c.count) other notes ···") Text(String(format: NSLocalizedString("collapsed_event_view_other_notes", comment: "Text to indicate that the thread was collapsed and that there are other notes to view if tapped."), c.count))
.padding([.top,.bottom], 8) .padding([.top,.bottom], 8)
.font(.footnote) .font(.footnote)
.foregroundColor(.gray) .foregroundColor(.gray)
@@ -114,7 +114,7 @@ struct EventDetailView: View {
scroll_after_load(thread: thread, proxy: proxy) scroll_after_load(thread: thread, proxy: proxy)
} }
} }
.navigationBarTitle("Thread") .navigationBarTitle(NSLocalizedString("Thread", comment: "Navigation bar title for note thread."))
} }

View File

@@ -103,12 +103,13 @@ struct BuilderEventView: View {
var body: some View { var body: some View {
VStack { VStack {
if event == nil { if let event = event {
ProgressView().padding() let ev = event.inner_event ?? event
} else { NavigationLink(destination: BuildThreadV2View(damus: damus, event_id: ev.id)) {
NavigationLink(destination: BuildThreadV2View(damus: damus, event_id: event!.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) }.buttonStyle(.plain)
} else {
ProgressView().padding()
} }
} }
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
@@ -128,11 +129,10 @@ struct EventView: View {
let pubkey: String let pubkey: String
let show_friend_icon: Bool let show_friend_icon: Bool
let size: EventViewKind let size: EventViewKind
let embedded: Bool
@EnvironmentObject var action_bar: ActionBarModel @EnvironmentObject var action_bar: ActionBarModel
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) { init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal) {
self.event = event self.event = event
self.highlight = highlight self.highlight = highlight
self.has_action_bar = has_action_bar self.has_action_bar = has_action_bar
@@ -140,10 +140,9 @@ struct EventView: View {
self.pubkey = event.pubkey self.pubkey = event.pubkey
self.show_friend_icon = show_friend_icon self.show_friend_icon = show_friend_icon
self.size = size self.size = size
self.embedded = embedded
} }
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) { init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal) {
self.event = event self.event = event
self.highlight = .none self.highlight = .none
self.has_action_bar = false self.has_action_bar = false
@@ -151,7 +150,6 @@ struct EventView: View {
self.pubkey = event.pubkey self.pubkey = event.pubkey
self.show_friend_icon = show_friend_icon self.show_friend_icon = show_friend_icon
self.size = size self.size = size
self.embedded = embedded
} }
init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) { init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
@@ -162,7 +160,6 @@ struct EventView: View {
self.pubkey = pubkey self.pubkey = pubkey
self.show_friend_icon = show_friend_icon self.show_friend_icon = show_friend_icon
self.size = size self.size = size
self.embedded = embedded
} }
var body: some View { var body: some View {
@@ -179,10 +176,10 @@ struct EventView: View {
Image(systemName: "arrow.2.squarepath") Image(systemName: "arrow.2.squarepath")
.font(.footnote.weight(.bold)) .font(.footnote.weight(.bold))
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
ProfileName(pubkey: event.pubkey, profile: prof, contacts: damus.contacts, show_friend_confirmed: true) ProfileName(pubkey: event.pubkey, profile: prof, damus: damus, show_friend_confirmed: true)
.font(.footnote.weight(.bold)) .font(.footnote.weight(.bold))
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
Text("Boosted") Text("Boosted", comment: "Text indicating that the post was boosted (i.e. re-shared).")
.font(.footnote.weight(.bold)) .font(.footnote.weight(.bold))
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
} }
@@ -209,11 +206,9 @@ struct EventView: View {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus) let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey)) let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
if !embedded {
NavigationLink(destination: pv) { NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles) ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
} }
}
Spacer() Spacer()
} }
@@ -232,7 +227,7 @@ struct EventView: View {
} }
} }
EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon, size: size) EventProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: show_friend_icon, size: size)
if size != .selected { if size != .selected {
Text("\(format_relative_time(event.created_at))") Text("\(format_relative_time(event.created_at))")
.font(eventviewsize_to_font(size)) .font(eventviewsize_to_font(size))
@@ -249,35 +244,9 @@ struct EventView: View {
let should_show_img = should_show_images(contacts: damus.contacts, ev: event, our_pubkey: damus.pubkey) let should_show_img = should_show_images(contacts: damus.contacts, ev: event, our_pubkey: damus.pubkey)
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, show_images: should_show_img, artifacts: .just_content(content), size: self.size) 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) .frame(maxWidth: .infinity, alignment: .leading)
.allowsHitTesting(!embedded)
if !embedded {
let blocks = event.blocks(damus.keypair.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" {
BuilderEventView(damus: damus, event_id: mention.ref.id)
}
}
if !embedded {
if has_action_bar { if has_action_bar {
if size == .selected { if size == .selected {
Text("\(format_date(event.created_at))") Text("\(format_date(event.created_at))")
@@ -298,7 +267,6 @@ struct EventView: View {
Divider() Divider()
.padding([.top], 4) .padding([.top], 4)
} }
}
.padding([.leading], 2) .padding([.leading], 2)
} }
.contentShape(Rectangle()) .contentShape(Rectangle())
@@ -306,7 +274,7 @@ struct EventView: View {
.id(event.id) .id(event.id)
.frame(maxWidth: .infinity, minHeight: PFP_SIZE) .frame(maxWidth: .infinity, minHeight: PFP_SIZE)
.padding([.bottom], 2) .padding([.bottom], 2)
.event_context_menu(event, privkey: damus.keypair.privkey) .event_context_menu(event, pubkey: pubkey, privkey: damus.keypair.privkey)
} }
} }
@@ -340,41 +308,41 @@ extension View {
Button { Button {
UIPasteboard.general.string = bech32_pubkey UIPasteboard.general.string = bech32_pubkey
} label: { } label: {
Label("Copy Account ID", systemImage: "doc.on.doc") Label(NSLocalizedString("Copy Account ID", comment: "Context menu option for copying the ID of the account that created the note."), systemImage: "doc.on.doc")
} }
} }
} }
func event_context_menu(_ event: NostrEvent, privkey: String?) -> some View { func event_context_menu(_ event: NostrEvent, pubkey: String, privkey: String?) -> some View {
return self.contextMenu { return self.contextMenu {
Button { Button {
UIPasteboard.general.string = event.get_content(privkey) UIPasteboard.general.string = event.get_content(privkey)
} label: { } label: {
Label("Copy Text", systemImage: "doc.on.doc") Label(NSLocalizedString("Copy Text", comment: "Context menu option for copying the text from an note."), systemImage: "doc.on.doc")
} }
Button { Button {
UIPasteboard.general.string = bech32_pubkey(event.pubkey) ?? event.pubkey UIPasteboard.general.string = bech32_pubkey(pubkey) ?? pubkey
} label: { } label: {
Label("Copy User ID", systemImage: "tag") Label(NSLocalizedString("Copy User ID", comment: "Context menu option for copying the ID of the user who created the note."), systemImage: "tag")
} }
Button { Button {
UIPasteboard.general.string = bech32_note_id(event.id) ?? event.id UIPasteboard.general.string = bech32_note_id(event.id) ?? event.id
} label: { } label: {
Label("Copy Note ID", systemImage: "tag") Label(NSLocalizedString("Copy Note ID", comment: "Context menu option for copying the ID of the note."), systemImage: "tag")
} }
Button { Button {
UIPasteboard.general.string = event_to_json(ev: event) UIPasteboard.general.string = event_to_json(ev: event)
} label: { } label: {
Label("Copy Note JSON", systemImage: "note") Label(NSLocalizedString("Copy Note JSON", comment: "Context menu option for copying the JSON text from the note."), systemImage: "note")
} }
Button { Button {
NotificationCenter.default.post(name: .broadcast_event, object: event) NotificationCenter.default.post(name: .broadcast_event, object: event)
} label: { } label: {
Label("Broadcast", systemImage: "globe") Label(NSLocalizedString("Broadcast", comment: "Context menu option for broadcasting the user's note to all of the user's connected relay servers."), systemImage: "globe")
} }
} }
@@ -414,7 +382,7 @@ func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
let othersCount = n - pubkeys.count let othersCount = n - pubkeys.count
return String(format: NSLocalizedString("replying_to_two_and_others", comment: "Label to indicate that the user is replying to 2 users and others."), names[0], names[1], othersCount) return String(format: NSLocalizedString("replying_to_two_and_others", comment: "Label to indicate that the user is replying to 2 users and others."), names[0], names[1], othersCount)
} }
return String.localizedStringWithFormat("Replying to %@ & %@", names[0], names[1]) return String(format: NSLocalizedString("Replying to %@ & %@", comment: "Label to indicate that the user is replying to 2 users."), names[0], names[1])
} }
let othersCount = n - pubkeys.count let othersCount = n - pubkeys.count

View File

@@ -19,10 +19,11 @@ struct FollowButtonView: View {
follow_state = perform_follow_btn_action(follow_state, target: target) follow_state = perform_follow_btn_action(follow_state, target: target)
} label: { } label: {
Text(follow_btn_txt(follow_state)) Text(follow_btn_txt(follow_state))
.frame(height: 30)
.padding(.horizontal, 25) .padding(.horizontal, 25)
.padding(.vertical, 10) //.padding(.vertical, 10)
.font(.caption.weight(.bold)) .font(.caption.weight(.bold))
.foregroundColor(follow_state == .unfollows ? emptyColor() : fillColor()) .foregroundColor(follow_state == .unfollows ? filledTextColor() : borderColor())
.background(follow_state == .unfollows ? fillColor() : emptyColor()) .background(follow_state == .unfollows ? fillColor() : emptyColor())
.cornerRadius(20) .cornerRadius(20)
.overlay { .overlay {
@@ -48,16 +49,20 @@ struct FollowButtonView: View {
} }
} }
func filledTextColor() -> Color {
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
}
func fillColor() -> Color { func fillColor() -> Color {
colorScheme == .light ? .black : .white colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
} }
func emptyColor() -> Color { func emptyColor() -> Color {
colorScheme == .light ? .white : .black Color.black.opacity(0)
} }
func borderColor() -> Color { func borderColor() -> Color {
colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2) colorScheme == .light ? Color("DamusDarkGrey") : Color("DamusLightGrey")
} }
} }
@@ -65,16 +70,16 @@ struct FollowButtonPreviews: View {
let target: FollowTarget = .pubkey("") let target: FollowTarget = .pubkey("")
var body: some View { var body: some View {
VStack { VStack {
Text("Unfollows") Text("Unfollows", comment: "Text to indicate that the button next to it is in a state that will unfollow a profile when tapped.")
FollowButtonView(target: target, follow_state: .unfollows) FollowButtonView(target: target, follow_state: .unfollows)
Text("Following") Text("Following", comment: "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.")
FollowButtonView(target: target, follow_state: .following) FollowButtonView(target: target, follow_state: .following)
Text("Follows") Text("Follows", comment: "Text to indicate that button next to it is in a state that will follow a profile when tapped.")
FollowButtonView(target: target, follow_state: .follows) FollowButtonView(target: target, follow_state: .follows)
Text("Unfollowing") Text("Unfollowing", comment: "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.")
FollowButtonView(target: target, follow_state: .unfollowing) FollowButtonView(target: target, follow_state: .unfollowing)
} }
} }

View File

@@ -24,7 +24,7 @@ struct FollowUserView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
let profile = damus_state.profiles.lookup(id: target.pubkey) let profile = damus_state.profiles.lookup(id: target.pubkey)
ProfileName(pubkey: target.pubkey, profile: profile, contacts: damus_state.contacts, show_friend_confirmed: false) ProfileName(pubkey: target.pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false)
if let about = profile?.about { if let about = profile?.about {
Text(FollowUserView.markdown.process(about)) Text(FollowUserView.markdown.process(about))
.lineLimit(3) .lineLimit(3)
@@ -57,7 +57,7 @@ struct FollowersView: View {
} }
.padding() .padding()
} }
.navigationBarTitle("\(Profile.displayName(profile: profile, pubkey: whos))'s Followers") .navigationBarTitle(NSLocalizedString("\(Profile.displayName(profile: profile, pubkey: whos))'s Followers", comment: "Navigation bar title for view that shows who is following a user."))
.onAppear { .onAppear {
followers.subscribe() followers.subscribe()
} }
@@ -91,7 +91,7 @@ struct FollowingView: View {
.onDisappear { .onDisappear {
following.unsubscribe() following.unsubscribe()
} }
.navigationBarTitle("\(who) following") .navigationBarTitle(NSLocalizedString("\(who) following", comment: "Navigation bar title for view that shows who a user is following."))
} }
} }

View File

@@ -122,21 +122,21 @@ struct LoginView: View {
ZStack(alignment: .top) { ZStack(alignment: .top) {
DamusGradient() DamusGradient()
VStack { VStack {
Text("Login") Text("Login", comment: "Title of view to log into an account.")
.foregroundColor(.white) .foregroundColor(.white)
.font(.title) .font(.title)
.padding() .padding()
Text("Enter your account key to login:") Text("Enter your account key to login:", comment: "Prompt for user to enter an account key to login.")
.foregroundColor(.white) .foregroundColor(.white)
.padding() .padding()
KeyInput("nsec1...", key: $key) KeyInput(NSLocalizedString("nsec1...", comment: "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."), key: $key)
let parsed = parse_key(key) let parsed = parse_key(key)
if parsed?.is_hex ?? false { if parsed?.is_hex ?? false {
Text("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.") Text("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.", comment: "Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key.")
.font(.subheadline.bold()) .font(.subheadline.bold())
.foregroundColor(.white) .foregroundColor(.white)
PubkeySwitch(isOn: $is_pubkey) PubkeySwitch(isOn: $is_pubkey)
@@ -150,15 +150,15 @@ struct LoginView: View {
} }
if parsed?.is_pub ?? false { if parsed?.is_pub ?? false {
Text("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.") Text("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.", comment: "Warning that the inputted account key is a public key and the result of what happens because of it.")
.foregroundColor(.white) .foregroundColor(.white)
.padding() .padding()
} }
if let p = parsed { if let p = parsed {
DamusWhiteButton("Login") { DamusWhiteButton(NSLocalizedString("Login", comment: "Button to log into account.")) {
if !process_login(p, is_pubkey: self.is_pubkey) { if !process_login(p, is_pubkey: self.is_pubkey) {
self.error = "Invalid key" self.error = NSLocalizedString("Invalid key", comment: "Error message indicating that an invalid account key was entered for login.")
} }
} }
} }
@@ -175,7 +175,7 @@ struct PubkeySwitch: View {
var body: some View { var body: some View {
HStack { HStack {
Toggle(isOn: $isOn) { Toggle(isOn: $isOn) {
Text("Public Key?") Text("Public Key?", comment: "Prompt to ask user if the key they entered is a public key.")
.foregroundColor(.white) .foregroundColor(.white)
} }
} }

View File

@@ -17,7 +17,7 @@ struct MentionView: View {
let pk = bech32_pubkey(mention.ref.ref_id) ?? mention.ref.ref_id let pk = bech32_pubkey(mention.ref.ref_id) ?? mention.ref.ref_id
PubkeyView(pubkey: pk, relay: mention.ref.relay_id) PubkeyView(pubkey: pk, relay: mention.ref.relay_id)
case .event: case .event:
Text("< e >") Text("< e >", comment: "Placeholder for event mention.")
//EventBlockView(pubkey: mention.ref.ref_id, relay: mention.ref.relay_id) //EventBlockView(pubkey: mention.ref.ref_id, relay: mention.ref.relay_id)
} }
} }

View File

@@ -41,11 +41,11 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
if is_image_url(url) { if is_image_url(url) {
// Append Image // Append Image
img_urls.append(url) img_urls.append(url)
return str
} else { } else {
link_urls.append(url) link_urls.append(url)
return str + url.absoluteString
} }
return str
} }
} }
@@ -61,12 +61,13 @@ struct NoteContentView: View {
let privkey: String? let privkey: String?
let event: NostrEvent let event: NostrEvent
let profiles: Profiles let profiles: Profiles
let previews: PreviewCache
let show_images: Bool let show_images: Bool
@State var artifacts: NoteArtifacts @State var artifacts: NoteArtifacts
@State var metaData: LPLinkMetadata? = nil @State var preview: LinkViewRepresentable? = nil
let size: EventViewKind let size: EventViewKind
func MainContent() -> some View { func MainContent() -> some View {
@@ -89,20 +90,21 @@ struct NoteContentView: View {
InvoicesView(invoices: artifacts.invoices) InvoicesView(invoices: artifacts.invoices)
} }
if show_images, self.metaData != nil { if show_images, self.preview != nil {
LinkViewRepresentable(metadata: self.metaData) self.preview
} else { } else {
ForEach(artifacts.links, id:\.self) { link in ForEach(artifacts.links, id:\.self) { link in
LinkViewRepresentable(url: link) if let url = link {
LinkViewRepresentable(meta: .url(url))
.frame(height: 50) .frame(height: 50)
} }
} }
} }
} }
}
var body: some View { var body: some View {
MainContent() MainContent()
.animation(.easeInOut, value: metaData)
.onAppear() { .onAppear() {
self.artifacts = render_note_content(ev: event, profiles: profiles, privkey: privkey) self.artifacts = render_note_content(ev: event, profiles: profiles, privkey: privkey)
} }
@@ -123,9 +125,22 @@ struct NoteContentView: View {
} }
} }
.task { .task {
if show_images, artifacts.links.count == 1 { if let preview = previews.lookup(self.event.id) {
switch preview {
case .value(let view):
self.preview = view
case .failed:
// don't try to refetch meta if we've failed
return
}
}
self.metaData = await getMetaData(for: artifacts.links.first!) if show_images, artifacts.links.count == 1 {
let meta = await getMetaData(for: artifacts.links.first!)
let view = meta.map { LinkViewRepresentable(meta: .linkmeta($0)) }
previews.store(evid: self.event.id, preview: view)
self.preview = view
} }
} }
} }
@@ -168,8 +183,8 @@ func mention_str(_ m: Mention, profiles: Profiles) -> String {
struct NoteContentView_Previews: PreviewProvider { struct NoteContentView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let state = test_damus_state() let state = test_damus_state()
let content = "hi there https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg" let content = "hi there ¯\\_(ツ)_/¯ https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg"
let artifacts = NoteArtifacts(content: content, images: [], invoices: [], links: []) let artifacts = NoteArtifacts(content: content, images: [], invoices: [], links: [])
NoteContentView(privkey: "", event: NostrEvent(content: content, pubkey: "pk"), profiles: state.profiles, show_images: true, artifacts: artifacts, size: .normal) NoteContentView(privkey: "", event: NostrEvent(content: content, pubkey: "pk"), profiles: state.profiles, previews: PreviewCache(), show_images: true, artifacts: artifacts, size: .normal)
} }
} }

View File

@@ -8,21 +8,29 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
let BUTTON_SIZE = 57.0
let LINEAR_GRADIENT = LinearGradient(gradient: Gradient(colors: [
Color("DamusPurple"),
Color("DamusBlue")
]), startPoint: .topTrailing, endPoint: .bottomTrailing)
func PostButton(action: @escaping () -> ()) -> some View { func PostButton(action: @escaping () -> ()) -> some View {
return Button(action: action, label: { return Button(action: action, label: {
Text("+") ZStack(alignment: .center) {
.font(.system(.largeTitle)) Circle()
.frame(width: 57, height: 50) .fill(LINEAR_GRADIENT)
.foregroundColor(Color.white) .frame(width: BUTTON_SIZE, height: BUTTON_SIZE, alignment: .center)
.padding(.bottom, 7) .rotationEffect(.degrees(20))
})
.background(Color.blue)
.cornerRadius(38.5)
.padding() .padding()
.shadow(color: Color.black.opacity(0.3), .shadow(color: Color.black.opacity(0.3),
radius: 3, radius: 3,
x: 3, x: 3,
y: 3) y: 3)
Image(systemName: "plus")
.font(.system(.title2))
.foregroundColor(Color.white)
}
})
.keyboardShortcut("n", modifiers: [.command, .shift]) .keyboardShortcut("n", modifiers: [.command, .shift])
} }

View File

@@ -55,7 +55,7 @@ struct PostView: View {
var body: some View { var body: some View {
VStack { VStack {
HStack { HStack {
Button("Cancel") { Button(NSLocalizedString("Cancel", comment: "Button to cancel out of posting a note.")) {
self.cancel() self.cancel()
} }
.foregroundColor(.primary) .foregroundColor(.primary)
@@ -63,7 +63,7 @@ struct PostView: View {
Spacer() Spacer()
if !is_post_empty { if !is_post_empty {
Button("Post") { Button(NSLocalizedString("Post", comment: "Button to post a note.")) {
self.send_post() self.send_post()
} }
} }

View File

@@ -7,76 +7,73 @@
import SwiftUI import SwiftUI
struct ProfileFullName: View { func get_friend_icon(contacts: Contacts, pubkey: String, show_confirmed: Bool) -> String? {
let pubkey: String if !show_confirmed {
let profile: Profile?
let contacts: Contacts
@State var display_name: String?
var body: some View {
HStack {
if let real_name = profile?.display_name {
Text(real_name)
.bold()
ProfileName(pubkey: pubkey, profile: profile, prefix: "@", contacts: contacts, show_friend_confirmed: true)
.font(.footnote)
.foregroundColor(.gray)
} else {
// ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true)
}
}
}
}
struct ProfileName: View {
let pubkey: String
let profile: Profile?
let contacts: Contacts
let prefix: String
let show_friend_confirmed: Bool
@State var display_name: String?
init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool) {
self.pubkey = pubkey
self.profile = profile
self.prefix = ""
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed
}
init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool) {
self.pubkey = pubkey
self.profile = profile
self.prefix = prefix
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed
}
var friend_icon: String? {
if !show_friend_confirmed {
return nil return nil
} }
if self.contacts.is_friend(self.pubkey) { if contacts.is_friend_or_self(pubkey) {
return "person.fill.checkmark" return "person.fill.checkmark"
} }
if self.contacts.is_friend_of_friend(self.pubkey) { if contacts.is_friend_of_friend(pubkey) {
return "person.fill.and.arrow.left.and.arrow.right" return "person.fill.and.arrow.left.and.arrow.right"
} }
return nil return nil
} }
struct ProfileName: View {
let damus_state: DamusState
let pubkey: String
let profile: Profile?
let prefix: String
let show_friend_confirmed: Bool
@State var display_name: String?
@State var nip05: NIP05?
init(pubkey: String, profile: Profile?, damus: DamusState, show_friend_confirmed: Bool) {
self.pubkey = pubkey
self.profile = profile
self.prefix = ""
self.show_friend_confirmed = show_friend_confirmed
self.damus_state = damus
}
init(pubkey: String, profile: Profile?, prefix: String, damus: DamusState, show_friend_confirmed: Bool) {
self.pubkey = pubkey
self.profile = profile
self.prefix = prefix
self.damus_state = damus
self.show_friend_confirmed = show_friend_confirmed
}
var friend_icon: String? {
return get_friend_icon(contacts: damus_state.contacts, pubkey: pubkey, show_confirmed: show_friend_confirmed)
}
var current_nip05: NIP05? {
nip05 ?? damus_state.profiles.is_validated(pubkey)
}
var nip05_color: Color {
return get_nip05_color(pubkey: pubkey, contacts: damus_state.contacts)
}
var body: some View { var body: some View {
HStack { HStack(spacing: 2) {
Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
.font(.body) .font(.body)
.fontWeight(prefix == "@" ? .none : .bold) .fontWeight(prefix == "@" ? .none : .bold)
if let friend = friend_icon { if let nip05 = current_nip05 {
Image(systemName: "checkmark.seal.fill")
.foregroundColor(nip05_color)
Text(nip05.host)
.foregroundColor(nip05_color)
}
if let friend = friend_icon, current_nip05 == nil {
Image(systemName: friend) Image(systemName: friend)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
@@ -87,62 +84,57 @@ struct ProfileName: View {
return return
} }
display_name = Profile.displayName(profile: update.profile, pubkey: pubkey) display_name = Profile.displayName(profile: update.profile, pubkey: pubkey)
nip05 = damus_state.profiles.is_validated(pubkey)
} }
} }
} }
/// Profile Name used when displaying an event in the timeline /// Profile Name used when displaying an event in the timeline
struct EventProfileName: View { struct EventProfileName: View {
let damus_state: DamusState
let pubkey: String let pubkey: String
let profile: Profile? let profile: Profile?
let contacts: Contacts
let prefix: String let prefix: String
let show_friend_confirmed: Bool let show_friend_confirmed: Bool
@State var display_name: String? @State var display_name: String?
@State var nip05: NIP05?
let size: EventViewKind let size: EventViewKind
init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool, size: EventViewKind = .normal) { init(pubkey: String, profile: Profile?, damus: DamusState, show_friend_confirmed: Bool, size: EventViewKind = .normal) {
self.damus_state = damus
self.pubkey = pubkey self.pubkey = pubkey
self.profile = profile self.profile = profile
self.prefix = "" self.prefix = ""
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed self.show_friend_confirmed = show_friend_confirmed
self.size = size self.size = size
} }
init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool, size: EventViewKind = .normal) { init(pubkey: String, profile: Profile?, prefix: String, damus: DamusState, show_friend_confirmed: Bool, size: EventViewKind = .normal) {
self.damus_state = damus
self.pubkey = pubkey self.pubkey = pubkey
self.profile = profile self.profile = profile
self.prefix = prefix self.prefix = prefix
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed self.show_friend_confirmed = show_friend_confirmed
self.size = size self.size = size
} }
var friend_icon: String? { var friend_icon: String? {
if !show_friend_confirmed { return get_friend_icon(contacts: damus_state.contacts, pubkey: pubkey, show_confirmed: show_friend_confirmed)
return nil
} }
if self.contacts.is_friend(self.pubkey) { var current_nip05: NIP05? {
return "person.fill.checkmark" nip05 ?? damus_state.profiles.is_validated(pubkey)
}
if self.contacts.is_friend_of_friend(self.pubkey) {
return "person.fill.and.arrow.left.and.arrow.right"
}
return nil
} }
var body: some View { var body: some View {
HStack { HStack(spacing: 2) {
if let real_name = profile?.display_name { if let real_name = profile?.display_name {
Text(real_name) Text(real_name)
.font(.body.weight(.bold)) .font(.body.weight(.bold))
.padding([.trailing], 2)
Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
.foregroundColor(.gray) .foregroundColor(.gray)
@@ -153,7 +145,12 @@ struct EventProfileName: View {
.fontWeight(.bold) .fontWeight(.bold)
} }
if let frend = friend_icon { if let _ = current_nip05 {
Image(systemName: "checkmark.seal.fill")
.foregroundColor(get_nip05_color(pubkey: pubkey, contacts: damus_state.contacts))
}
if let frend = friend_icon, current_nip05 == nil {
Label("", systemImage: frend) Label("", systemImage: frend)
.foregroundColor(.gray) .foregroundColor(.gray)
.font(.footnote) .font(.footnote)
@@ -165,6 +162,11 @@ struct EventProfileName: View {
return return
} }
display_name = Profile.displayName(profile: update.profile, pubkey: pubkey) display_name = Profile.displayName(profile: update.profile, pubkey: pubkey)
nip05 = damus_state.profiles.is_validated(pubkey)
} }
} }
} }
func get_nip05_color(pubkey: String, contacts: Contacts) -> Color {
return contacts.is_friend_or_self(pubkey) ? .accentColor : .gray
}

View File

@@ -51,15 +51,19 @@ struct InnerProfilePicView: View {
} }
var body: some View { var body: some View {
Group { ZStack {
Color(uiColor: .systemBackground)
KFAnimatedImage(url) KFAnimatedImage(url)
.callbackQueue(.dispatch(.global(qos: .background)))
.processingQueue(.dispatch(.global(qos: .background)))
.appendProcessor(LargeImageProcessor())
.configure { view in .configure { view in
view.framePreloadCount = 1 view.framePreloadCount = 1
} }
.placeholder { _ in .placeholder { _ in
Placeholder Placeholder
} }
.cacheOriginalImage()
.scaleFactor(UIScreen.main.scale) .scaleFactor(UIScreen.main.scale)
.loadDiskFileSynchronously() .loadDiskFileSynchronously()
.fade(duration: 0.1) .fade(duration: 0.1)
@@ -71,7 +75,6 @@ struct InnerProfilePicView: View {
} }
struct ProfilePicView: View { struct ProfilePicView: View {
let pubkey: String let pubkey: String
let size: CGFloat let size: CGFloat
let highlight: Highlight let highlight: Highlight
@@ -103,6 +106,33 @@ struct ProfilePicView: View {
} }
} }
struct LargeImageProcessor: ImageProcessor {
let identifier: String = "com.damus.largeimageprocessor"
let maxSize: Int = 1000000
let downsampleSize = CGSize(width: 200, height: 200)
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
switch item {
case .image(let image):
guard let data = image.kf.data(format: .unknown) else {
return nil
}
if data.count > maxSize {
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
}
return image
case .data(let data):
if data.count > maxSize {
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
}
return KFCrossPlatformImage(data: data)
}
}
}
func get_profile_url(picture: String?, pubkey: String, profiles: Profiles) -> URL { func get_profile_url(picture: String?, pubkey: String, profiles: Profiles) -> URL {
let pic = picture ?? profiles.lookup(id: pubkey)?.picture ?? robohash(pubkey) let pic = picture ?? profiles.lookup(id: pubkey)?.picture ?? robohash(pubkey)
if let url = URL(string: pic) { if let url = URL(string: pic) {

View File

@@ -48,27 +48,24 @@ func follow_btn_enabled_state(_ fs: FollowState) -> Bool {
struct ProfileNameView: View { struct ProfileNameView: View {
let pubkey: String let pubkey: String
let profile: Profile? let profile: Profile?
let contacts: Contacts let damus: DamusState
var body: some View { var body: some View {
Group { Group {
if let real_name = profile?.display_name { if let real_name = profile?.display_name {
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack {
Text(real_name) Text(real_name)
.font(.title3.weight(.bold)) .font(.title3.weight(.bold))
ProfileName(pubkey: pubkey, profile: profile, prefix: "@", damus: damus, show_friend_confirmed: true)
.font(.callout)
.foregroundColor(.gray)
KeyView(pubkey: pubkey) KeyView(pubkey: pubkey)
.pubkey_context_menu(bech32_pubkey: pubkey) .pubkey_context_menu(bech32_pubkey: pubkey)
} }
ProfileName(pubkey: pubkey, profile: profile, prefix: "@", contacts: contacts, show_friend_confirmed: true)
.font(.callout)
.foregroundColor(.gray)
}
} else { } else {
HStack { VStack(alignment: .leading) {
ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true) ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: true)
.font(.title3.weight(.bold))
KeyView(pubkey: pubkey) KeyView(pubkey: pubkey)
.pubkey_context_menu(bech32_pubkey: pubkey) .pubkey_context_menu(bech32_pubkey: pubkey)
} }
@@ -84,30 +81,25 @@ struct EditButton: View {
var body: some View { var body: some View {
NavigationLink(destination: EditMetadataView(damus_state: damus_state)) { NavigationLink(destination: EditMetadataView(damus_state: damus_state)) {
Text("Edit") Text("Edit", comment: "Button to edit user's profile.")
.frame(height: 30)
.padding(.horizontal,25) .padding(.horizontal,25)
.padding(.vertical, 10)
.font(.caption.weight(.bold)) .font(.caption.weight(.bold))
.foregroundColor(fillColor()) .foregroundColor(fillColor())
.background(emptyColor()) .cornerRadius(24)
.cornerRadius(20)
.overlay { .overlay {
RoundedRectangle(cornerRadius: 16) RoundedRectangle(cornerRadius: 24)
.stroke(borderColor(), lineWidth: 1) .stroke(borderColor(), lineWidth: 1)
} }
} }
} }
func fillColor() -> Color { func fillColor() -> Color {
colorScheme == .light ? .black : .white colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
}
func emptyColor() -> Color {
colorScheme == .light ? .white : .black
} }
func borderColor() -> Color { func borderColor() -> Color {
colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2) colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
} }
} }
@@ -121,12 +113,29 @@ struct ProfileView: View {
@State private var showingEditProfile = false @State private var showingEditProfile = false
@State var showing_select_wallet: Bool = false @State var showing_select_wallet: Bool = false
@State var is_zoomed: Bool = false @State var is_zoomed: Bool = false
@State var show_share_sheet: Bool = false
@StateObject var user_settings = UserSettingsStore() @StateObject var user_settings = UserSettingsStore()
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
//@EnvironmentObject var profile: ProfileModel // We just want to have a white "< Home" text here, however,
// setting the initialiser is causing issues, and it's late.
// Ref: https://blog.techchee.com/navigation-bar-title-style-color-and-custom-back-button-in-swiftui/
/*
init(damus_state: DamusState, zoom_size: CGFloat = 350) {
self.damus_state = damus_state
self.zoom_size = zoom_size
Theme.navigationBarColors(background: nil, titleColor: .white, tintColor: nil)
}*/
func fillColor() -> Color {
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
}
func imageBorderColor() -> Color {
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
}
func LNButton(lnurl: String, profile: Profile) -> some View { func LNButton(lnurl: String, profile: Profile) -> some View {
Button(action: { Button(action: {
@@ -138,16 +147,19 @@ struct ProfileView: View {
}) { }) {
Image(systemName: "bolt.circle") Image(systemName: "bolt.circle")
.symbolRenderingMode(.palette) .symbolRenderingMode(.palette)
.font(.system(size: 34).weight(.thin)) .foregroundStyle(colorScheme == .dark ? .white : .black, colorScheme == .dark ? .white : .black)
.foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2)) .font(.system(size: 32).weight(.thin))
.contextMenu { .contextMenu {
Button { Button {
UIPasteboard.general.string = profile.lnurl ?? "" UIPasteboard.general.string = profile.lnurl ?? ""
} label: { } label: {
Label("Copy LNURL", systemImage: "doc.on.doc") Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
} }
} }
}.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
}
.cornerRadius(24)
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
SelectWalletView(showingSelectWallet: $showing_select_wallet, invoice: lnurl) SelectWalletView(showingSelectWallet: $showing_select_wallet, invoice: lnurl)
.environmentObject(user_settings) .environmentObject(user_settings)
} }
@@ -155,6 +167,18 @@ struct ProfileView: View {
static let markdown = Markdown() static let markdown = Markdown()
var ShareButton: some View {
Button(action: {
show_share_sheet = true
}) {
Image(systemName: "square.and.arrow.up.circle.fill")
.symbolRenderingMode(.palette)
.font(.system(size: 32))
.padding()
.foregroundStyle(.white, .black, .black.opacity(0.8))
}
}
var DMButton: some View { var DMButton: some View {
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey) let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey) let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey)
@@ -162,28 +186,43 @@ struct ProfileView: View {
return NavigationLink(destination: dmview) { return NavigationLink(destination: dmview) {
Image(systemName: "bubble.left.circle") Image(systemName: "bubble.left.circle")
.symbolRenderingMode(.palette) .symbolRenderingMode(.palette)
.font(.system(size: 34).weight(.thin)) .font(.system(size: 32).weight(.thin))
.foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2)) .foregroundStyle(colorScheme == .dark ? .white : .black, colorScheme == .dark ? .white : .black)
} }
} }
var TopSection: some View { var TopSection: some View {
ZStack(alignment: .top) {
GeometryReader { geo in
Image("profile-banner")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geo.size.width, height: 150)
.clipped()
ShareButton
.offset(x: geo.size.width - 80.0, y: 50.0 )
}
VStack(alignment: .leading) { VStack(alignment: .leading) {
let data = damus_state.profiles.lookup(id: profile.pubkey) let data = damus_state.profiles.lookup(id: profile.pubkey)
let pfp_size: CGFloat = 90.0
HStack(alignment: .center) { HStack(alignment: .center) {
ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles) ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles)
.onTapGesture { .onTapGesture {
is_zoomed.toggle() is_zoomed.toggle()
} }
.sheet(isPresented: $is_zoomed) { .sheet(isPresented: $is_zoomed) {
ProfilePicView(pubkey: profile.pubkey, size: zoom_size, highlight: .none, profiles: damus_state.profiles) ProfilePicView(pubkey: profile.pubkey, size: zoom_size, highlight: .none, profiles: damus_state.profiles)
} }
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
Spacer() Spacer()
Group {
if let profile = data { if let profile = data {
if let lnurl = profile.lnurl { if let lnurl = profile.lnurl, lnurl != "" {
LNButton(lnurl: lnurl, profile: profile) LNButton(lnurl: lnurl, profile: profile)
} }
} }
@@ -195,16 +234,20 @@ struct ProfileView: View {
target: profile.get_follow_target(), target: profile.get_follow_target(),
follow_state: damus_state.contacts.follow_state(profile.pubkey) follow_state: damus_state.contacts.follow_state(profile.pubkey)
) )
} else { } else if damus_state.keypair.privkey != nil {
NavigationLink(destination: EditMetadataView(damus_state: damus_state)) { NavigationLink(destination: EditMetadataView(damus_state: damus_state)) {
EditButton(damus_state: damus_state) EditButton(damus_state: damus_state)
} }
} }
} }
.offset(y: -15.0) // Increase if set a frame
ProfileNameView(pubkey: profile.pubkey, profile: data, contacts: damus_state.contacts) }
.padding(.bottom)
ProfileNameView(pubkey: profile.pubkey, profile: data, damus: damus_state)
//.padding(.bottom)
.padding(.top,-(pfp_size/2.0))
Text(ProfileView.markdown.process(data?.about ?? "")) Text(ProfileView.markdown.process(data?.about ?? ""))
.font(.subheadline) .font(.subheadline)
@@ -217,11 +260,7 @@ struct ProfileView: View {
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts) let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) { NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) {
HStack { HStack {
Text("\(profile.following)") Text("\(Text("\(profile.following)", comment: "Number of profiles a user is following.").font(.subheadline.weight(.medium))) \(Text("Following", comment: "Part of a larger sentence to describe how many profiles a user is following.").font(.subheadline).foregroundColor(.gray))", comment: "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'.")
.font(.subheadline.weight(.medium))
Text("Following")
.font(.subheadline)
.foregroundColor(.gray)
} }
} }
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
@@ -244,33 +283,36 @@ struct ProfileView: View {
if let relays = profile.relays { if let relays = profile.relays {
NavigationLink(destination: UserRelaysView(state: damus_state, pubkey: profile.pubkey, relays: Array(relays.keys).sorted())) { NavigationLink(destination: UserRelaysView(state: damus_state, pubkey: profile.pubkey, relays: Array(relays.keys).sorted())) {
Text("\(relays.keys.count)") Text("\(Text("\(relays.keys.count)", comment: "Number of relay servers a user is connected.").font(.subheadline.weight(.medium))) \(Text("Relays", comment: "Part of a larger sentence to describe how many relay servers a user is connected.").font(.subheadline).foregroundColor(.gray))", comment: "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 'Relays'.")
.font(.subheadline.weight(.medium))
Text("Relays")
.font(.subheadline)
.foregroundColor(.gray)
} }
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
} }
} }
} }
.padding(.horizontal,18)
//.offset(y:120)
.padding(.top,150)
}
} }
var FollowersCount: some View { var FollowersCount: some View {
HStack { HStack {
Text("\(followers.count_display)") if followers.count_display == "?" {
.font(.subheadline.weight(.medium)) Image(systemName: "square.and.arrow.down")
Text("Followers") Text("Followers", comment: "Label describing followers of a user.")
.font(.subheadline) .font(.subheadline)
.foregroundColor(.gray) .foregroundColor(.gray)
} else {
Text("\(Text("\(followers.count_display)", comment: "Number of people following a user.").font(.subheadline.weight(.medium))) \(Text("Followers", comment: "Part of a larger sentence to describe how many people are following a user.").font(.subheadline).foregroundColor(.gray))", comment: "Sentence 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 'Followers'.")
}
} }
} }
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
ScrollView { ScrollView {
TopSection TopSection
.padding(.horizontal)
Divider() Divider()
@@ -291,6 +333,14 @@ struct ProfileView: View {
followers.unsubscribe() followers.unsubscribe()
// our profilemodel needs a bit more help // our profilemodel needs a bit more help
} }
.sheet(isPresented: $show_share_sheet) {
if let npub = bech32_pubkey(profile.pubkey) {
if let url = URL(string: "https://damus.io/" + npub) {
ShareSheet(activityItems: [url])
}
}
}
.ignoresSafeArea()
} }
} }
@@ -306,9 +356,9 @@ struct ProfileView_Previews: PreviewProvider {
func test_damus_state() -> DamusState { func test_damus_state() -> DamusState {
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681" 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(), tips: TipCounter(our_pubkey: pubkey), profiles: Profiles(), dms: DirectMessagesModel()) 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 prof = Profile(name: "damus", display_name: "Damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io") let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io")
let tsprof = TimestampedProfile(profile: prof, timestamp: 0) let tsprof = TimestampedProfile(profile: prof, timestamp: 0)
damus.profiles.add(id: pubkey, profile: tsprof) damus.profiles.add(id: pubkey, profile: tsprof)
return damus return damus
@@ -321,20 +371,72 @@ struct KeyView: View {
@State private var isCopied = false @State private var isCopied = false
func fillColor() -> Color {
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
}
func keyColor() -> Color {
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
}
var body: some View { var body: some View {
let col = id_to_color(pubkey)
let bech32 = bech32_pubkey(pubkey) ?? pubkey let bech32 = bech32_pubkey(pubkey) ?? pubkey
HStack {
RoundedRectangle(cornerRadius: 24)
.frame(width: 275, height:22)
.foregroundColor(fillColor())
.overlay(
HStack {
Button { Button {
UIPasteboard.general.string = bech32 UIPasteboard.general.string = bech32
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
isCopied = true isCopied = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
isCopied = false
}
} label: { } label: {
Label(isCopied ? "Copied" : "", systemImage: "key.fill") Label(NSLocalizedString("Public Key", comment: "Label indicating that the text is a user's public account key."), systemImage: "key.fill")
.font(isCopied ? .caption : .system(size: 15).weight(.light)) .font(.custom("key", size: 12.0))
.labelStyle(IconOnlyLabelStyle())
.foregroundStyle(hex_to_rgb(pubkey))
.symbolRenderingMode(.palette)
}
.padding(.leading,4)
Text(abbrev_pubkey(bech32, amount: 16))
.font(.footnote)
.foregroundColor(keyColor())
.offset(x:-3) // Not sure why this is needed.
}
)
if isCopied != true {
Button {
UIPasteboard.general.string = bech32
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
isCopied = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
isCopied = false
}
} label: {
Label {
Text("Public key", comment: "Label indicating that the text is a user's public account key.")
} icon: {
Image("ic-copy")
.contentShape(Rectangle())
.frame(width: 20, height: 20)
}
.labelStyle(IconOnlyLabelStyle())
.symbolRenderingMode(.hierarchical) .symbolRenderingMode(.hierarchical)
.foregroundColor(isCopied ? .gray : col) }
} else {
HStack {
Image("ic-tick")
.frame(width: 20, height: 20)
Text(NSLocalizedString("Copied", comment: "Label indicating that a user's key was copied."))
.font(.footnote)
.foregroundColor(Color("DamusGreen"))
}
}
} }
} }
} }

View File

@@ -30,7 +30,7 @@ struct RecommendedRelayView: View {
Spacer() Spacer()
if let ev = damus.contacts.event, add_button { if let ev = damus.contacts.event, add_button {
if let privkey = damus.keypair.privkey { if let privkey = damus.keypair.privkey {
Button("Add") { Button(NSLocalizedString("Add", comment: "Button to add recommended relay server.")) {
guard let ev = add_relay(ev: ev, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else { guard let ev = add_relay(ev: ev, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else {
return return
} }

View File

@@ -60,7 +60,7 @@ struct RelayView: View {
Button { Button {
UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text") UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text")
} label: { } label: {
Label("Copy", systemImage: "doc.on.doc") Label(NSLocalizedString("Copy", comment: "Button to copy a relay server address."), systemImage: "doc.on.doc")
} }
} }
@@ -78,7 +78,7 @@ struct RelayView: View {
process_contact_event(pool: state.pool, contacts: state.contacts, pubkey: state.pubkey, ev: new_ev) process_contact_event(pool: state.pool, contacts: state.contacts, pubkey: state.pubkey, ev: new_ev)
state.pool.send(.event(new_ev)) state.pool.send(.event(new_ev))
} label: { } label: {
Label("Delete", systemImage: "trash") Label(NSLocalizedString("Delete", comment: "Button to delete a relay server that the user connects to."), systemImage: "trash")
} }
.tint(.red) .tint(.red)
} }

View File

@@ -12,6 +12,7 @@ struct ReplyQuoteView: View {
let quoter: NostrEvent let quoter: NostrEvent
let event_id: String let event_id: String
let profiles: Profiles let profiles: Profiles
let previews: PreviewCache
@EnvironmentObject var thread: ThreadModel @EnvironmentObject var thread: ThreadModel
@@ -31,7 +32,7 @@ struct ReplyQuoteView: View {
.foregroundColor(.gray) .foregroundColor(.gray)
} }
NoteContentView(privkey: privkey, event: event, profiles: profiles, show_images: false, artifacts: .just_content(event.content), size: .normal) NoteContentView(privkey: privkey, event: event, profiles: profiles, previews: previews, show_images: false, artifacts: .just_content(event.content), size: .normal)
.font(.callout) .font(.callout)
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
@@ -58,7 +59,7 @@ struct ReplyQuoteView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let s = test_damus_state() let s = test_damus_state()
let quoter = NostrEvent(content: "a\nb\nc", pubkey: "pubkey") let quoter = NostrEvent(content: "a\nb\nc", pubkey: "pubkey")
ReplyQuoteView(privkey: s.keypair.privkey, quoter: quoter, event_id: "pubkey2", profiles: s.profiles) ReplyQuoteView(privkey: s.keypair.privkey, quoter: quoter, event_id: "pubkey2", profiles: s.profiles, previews: PreviewCache())
.environmentObject(ThreadModel(event: quoter, damus_state: s)) .environmentObject(ThreadModel(event: quoter, damus_state: s))
} }
} }

View File

@@ -20,7 +20,7 @@ struct ReplyView: View {
var body: some View { var body: some View {
VStack { VStack {
Text("Replying to:") Text("Replying to:", comment: "Indicating that the user is replying to the following listed people.")
HStack(alignment: .top) { HStack(alignment: .top) {
let names = all_referenced_pubkeys(replying_to) let names = all_referenced_pubkeys(replying_to)
.map { pubkey in .map { pubkey in
@@ -33,7 +33,9 @@ struct ReplyView: View {
.foregroundColor(.gray) .foregroundColor(.gray)
.font(.footnote) .font(.footnote)
} }
ScrollView {
EventView(event: replying_to, highlight: .none, has_action_bar: false, damus: damus, show_friend_icon: true) EventView(event: replying_to, highlight: .none, has_action_bar: false, damus: damus, show_friend_icon: true)
}
PostView(replying_to: replying_to, references: gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to)) PostView(replying_to: replying_to, references: gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to))
} }
.padding() .padding()

View File

@@ -21,21 +21,21 @@ struct SaveKeysView: View {
DamusGradient() DamusGradient()
VStack(alignment: .center) { VStack(alignment: .center) {
Text("Welcome, \(account.rendered_name)!") Text("Welcome, \(account.rendered_name)!", comment: "Text to welcome user.")
.font(.title.bold()) .font(.title.bold())
.foregroundColor(.white) .foregroundColor(.white)
.padding(.bottom, 10) .padding(.bottom, 10)
Text("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.") Text("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.", comment: "Reminder to user that they should save their account information.")
.foregroundColor(.white) .foregroundColor(.white)
.padding(.bottom, 10) .padding(.bottom, 10)
Text("Public Key") Text("Public Key", comment: "Label to indicate that text below is the user's public key used by others to uniquely refer to the user.")
.font(.title2.bold()) .font(.title2.bold())
.foregroundColor(.white) .foregroundColor(.white)
.padding(.bottom, 10) .padding(.bottom, 10)
Text("This is your account ID, you can give this to your friends so that they can follow you. Click to copy.") Text("This is your account ID, you can give this to your friends so that they can follow you. Click to copy.", comment: "Label to describe that a public key is the user's account ID and what they can do with it.")
.foregroundColor(.white) .foregroundColor(.white)
.padding(.bottom, 10) .padding(.bottom, 10)
@@ -43,12 +43,12 @@ struct SaveKeysView: View {
.padding(.bottom, 10) .padding(.bottom, 10)
if pub_copied { if pub_copied {
Text("Private Key") Text("Private Key", comment: "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.")
.font(.title2.bold()) .font(.title2.bold())
.foregroundColor(.white) .foregroundColor(.white)
.padding(.bottom, 10) .padding(.bottom, 10)
Text("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!") Text("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!", comment: "Label to describe that a private key is the user's secret account key and what they should do with it.")
.foregroundColor(.white) .foregroundColor(.white)
.padding(.bottom, 10) .padding(.bottom, 10)
@@ -61,7 +61,7 @@ struct SaveKeysView: View {
ProgressView() ProgressView()
.progressViewStyle(.circular) .progressViewStyle(.circular)
} else if let err = error { } else if let err = error {
Text("Error: \(err)") Text("Error: \(err)", comment: "Error message indicating why saving keys failed.")
.foregroundColor(.red) .foregroundColor(.red)
DamusWhiteButton("Retry") { DamusWhiteButton("Retry") {
complete_account_creation(account) complete_account_creation(account)

View File

@@ -16,12 +16,12 @@ struct SearchHomeView: View {
var SearchInput: some View { var SearchInput: some View {
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
HStack{ HStack{
TextField("Search...", text: $search) TextField(NSLocalizedString("Search...", comment: "Placeholder text to prompt entry of search query."), text: $search)
.padding(8) .padding(8)
.padding(.leading, 35) .padding(.leading, 35)
.autocorrectionDisabled(true) .autocorrectionDisabled(true)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
Text("Cancel") Text("Cancel", comment: "Cancel out of search view.")
.foregroundColor(.blue) .foregroundColor(.blue)
.padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 10.0)) .padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 10.0))
.opacity((search == "") ? 0.0 : 1.0) .opacity((search == "") ? 0.0 : 1.0)
@@ -55,7 +55,7 @@ struct SearchHomeView: View {
// Fetch new information by unsubscribing and resubscribing to the relay // Fetch new information by unsubscribing and resubscribing to the relay
model.unsubscribe() model.unsubscribe()
model.subscribe() model.subscribe()
}.padding(.horizontal) }
} }
var MainContent: some View { var MainContent: some View {

View File

@@ -38,7 +38,7 @@ struct SearchResultsView: View {
let search_model = SearchModel(pool: damus_state.pool, search: .filter_hashtag([ht])) let search_model = SearchModel(pool: damus_state.pool, search: .filter_hashtag([ht]))
let dst = SearchView(appstate: damus_state, search: search_model) let dst = SearchView(appstate: damus_state, search: search_model)
NavigationLink(destination: dst) { NavigationLink(destination: dst) {
Text("Search hashtag: #\(ht)") Text("Search hashtag: #\(ht)", comment: "Navigation link to search hashtag.")
} }
case .profile(let prof): case .profile(let prof):
let decoded = try? bech32_decode(prof) let decoded = try? bech32_decode(prof)
@@ -47,7 +47,7 @@ struct SearchResultsView: View {
let f = FollowersModel(damus_state: damus_state, target: prof) let f = FollowersModel(damus_state: damus_state, target: prof)
let dst = ProfileView(damus_state: damus_state, profile: prof_model, followers: f) let dst = ProfileView(damus_state: damus_state, profile: prof_model, followers: f)
NavigationLink(destination: dst) { NavigationLink(destination: dst) {
Text("Goto profile \(prof)") Text("Goto profile \(prof)", comment: "Navigation link to go to profile.")
} }
case .hex(let h): case .hex(let h):
let prof_model = ProfileModel(pubkey: h, damus: damus_state) let prof_model = ProfileModel(pubkey: h, damus: damus_state)
@@ -60,10 +60,10 @@ struct SearchResultsView: View {
VStack(spacing: 50) { VStack(spacing: 50) {
NavigationLink(destination: prof_view) { NavigationLink(destination: prof_view) {
Text("Goto profile \(h)") Text("Goto profile \(h)", comment: "Navigation link to go to profile referenced by hex code.")
} }
NavigationLink(destination: ev_view) { NavigationLink(destination: ev_view) {
Text("Goto post \(h)") Text("Goto post \(h)", comment: "Navigation link to go to post referenced by hex code.")
} }
} }
case .note(let nid): case .note(let nid):
@@ -74,12 +74,12 @@ struct SearchResultsView: View {
event_id: hex event_id: hex
) )
NavigationLink(destination: ev_view) { NavigationLink(destination: ev_view) {
Text("Goto post \(nid)") Text("Goto post \(nid)", comment: "Navigation link to go to post referenced by note ID.")
} }
case .none: case .none:
Text("none") Text("none", comment: "No search results.")
}
} }
}.padding(.horizontal)
} }
} }
@@ -118,11 +118,14 @@ struct SearchResultsView: View {
let pk = els.element.key let pk = els.element.key
let prof = els.element.value.profile let prof = els.element.value.profile
let lowname = prof.name.map { $0.lowercased() } let lowname = prof.name.map { $0.lowercased() }
let lownip05 = damus_state.profiles.is_validated(pk).map { $0.host.lowercased() }
let lowdisp = prof.display_name.map { $0.lowercased() } let lowdisp = prof.display_name.map { $0.lowercased() }
let ok = new.count == 1 ? let ok = new.count == 1 ?
((lowname?.starts(with: new) ?? false) || ((lowname?.starts(with: new) ?? false) ||
(lownip05?.starts(with: new) ?? false) ||
(lowdisp?.starts(with: new) ?? false)) : (pk.starts(with: new) || String(new.dropFirst()) == pk (lowdisp?.starts(with: new) ?? false)) : (pk.starts(with: new) || String(new.dropFirst()) == pk
|| lowname?.contains(new) ?? false || lowname?.contains(new) ?? false
|| lownip05?.contains(new) ?? false
|| lowdisp?.contains(new) ?? false) || lowdisp?.contains(new) ?? false)
if ok { if ok {
acc.append((pk, prof)) acc.append((pk, prof))

Some files were not shown because too many files have changed in this diff Show More