Compare commits

...

119 Commits

Author SHA1 Message Date
tyiu ccba726995 Export localizations and add transifex.yml config file 2023-01-06 20:27:47 -05: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
tyiu 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
William Casarin ed36a8c062 Add brb.io to recommended relay list
Changelog-Added: Added brb.io to recommended relay list
2023-01-02 08:44:54 -08:00
Thomas Rademaker 2f81a144c1 ContentTimelineView inside a page tabView to get a nice swipe gesture
Changelog-Changed: Add swipe gesture to switch between tabs
Closes: #202
2023-01-02 08:42:03 -08:00
William Casarin 4adb26e784 wallet: refactor a few things 2023-01-02 08:39:39 -08:00
Benjamin Hakes 707f31fca1 Add Blixt & River to Wallet Selector
Changelog-Added: Add Blixt Wallet to Wallet Selector
Changelog-Added: Add River Wallet to Wallet Selector
Closes: #206
2023-01-02 08:38:37 -08:00
tyiu 9e9077bab5 Internationalize all bundled user-facing strings
Enables localization to non-English locales in the future
2023-01-02 08:35:02 -08:00
@RandyMcMillan 7242c0bca3 target: decrement to 15.0
Closes: #207
2023-01-02 08:22:52 -08:00
tyiu badbd8a92e Fix flakey TimeAgo test by anchoring locale to en_US
Closes: #210
2023-01-02 08:22:17 -08:00
Lio李歐 aec61906ae Update CHANGELOG.md
Hey @jb55. I appreciate everything you’re doing for nostr but that
particular change was done by me. Not sure how come your name ended up
besides it. I was a bit bummed when I saw the latest changelog in
TestFlight.

Changelog-Changed: Parse links in profiles (Lionello Lunesu)
Closes: #208
2023-01-02 08:21:25 -08:00
tyiu 4f4557e4b6 Replace deprecated Data.withUnsafeMutableBytes call
Closes: #214
2023-01-02 08:19:47 -08:00
William Casarin 9c6b802f19 Fix follows and relays getting out of sync on profile pages
Fixes: #216
Changelog-Fixed: Fixed follows and relays getting out of sync on profile pages
2023-01-02 08:03:09 -08:00
William Casarin 4c7b8dc10b app category 2023-01-01 11:47:15 -08:00
William Casarin 202f6b1fb9 Update Changelog for App Store Release 2023-01-01 11:38:38 -08:00
William Casarin ab949db651 v1.0.0 2023-01-01 11:36:57 -08:00
William Casarin 2aaa58ada6 Fix relative date tests 2023-01-01 11:32:04 -08:00
William Casarin 5a956248f9 bolt11: fix bug where text not showing after invoice
Changelog-Fixed: Fix but where text was not showing after invoices
2023-01-01 11:31:36 -08:00
William Casarin cf615b82b2 bolt11: fix any amount invoices 2023-01-01 11:02:44 -08:00
William Casarin b65d9a49ff c: silence warnings 2023-01-01 10:30:40 -08:00
William Casarin 9666180db5 threadv2: refactor handle_event logic 2023-01-01 10:27:56 -08:00
William Casarin 3b81592ca2 Load profiles in DMs and notifications
Changelog-Fixed: Load profiles in DMs and notifications
2023-01-01 10:01:48 -08:00
William Casarin 696d341f79 DMs: maybe fix sorting bug
Changlog-Fixed: Potential fix for DM sorting issue
2023-01-01 09:40:32 -08:00
William Casarin 500845f619 likes: make the default like emoji more muted 2022-12-31 18:32:59 -08:00
William Casarin 977ae29949 Fix navigation popping in DM view 2022-12-31 18:10:29 -08:00
William Casarin 52b2407f49 Switch like from ❤️ to 🤙
Actually react with 🤙 at the protocol level as well

Changelog-Changed: Switch like from ❤️  to 🤙
2022-12-31 17:44:56 -08:00
Joel Klabo 9d181fea90 Remove Unused Variable Warning in convert_invoice_block 2022-12-31 16:54:10 -08:00
Joel Klabo 759ba398cc Fix Expanding Profile Picture (nosestr bug)
Changelog-Fixed: Fix expanding profile picture (nosestr bug)
2022-12-31 16:52:06 -08:00
William Casarin 6f5f86114b Parse links in profiles
Changelog-Added: Parse links in profiles
2022-12-31 13:03:52 -08:00
OlegAba d479f7a55a Fix padding on threads and search results view
Closes: #177
Changelog-Fixed: Fix padding on threads and search results views
2022-12-31 12:41:54 -08:00
Joel Klabo 79ea4da2d2 Don't Badge DMs if Sent By You
Closes: #176
Changelog-Fixed: Don't badge DMs if sent by you
2022-12-31 12:37:40 -08:00
Lee Salminen 4781795187 Add Breez & Bitcoin Beach wallets
Closes: #187
Changelog-Added: Added Breez wallet to wallet selector
Changelog-Added: Added Bitcoin Beach wallet to wallet selector
2022-12-31 12:34:52 -08:00
Matt Ward 2c15d22d86 Added copy relay action
Closes: #197
Changelog-Added: Added ability to copy relay urls
2022-12-31 12:33:55 -08:00
gladiusKatana 3d5a098423 SaveKeyView: add spacers to make Text frames invariant upon Copy-tap
Closes: #192
2022-12-31 10:02:40 -08:00
Joel Klabo d0e9e6c03a Reset Relay Add to Placeholder After Addding a Relay
Closes: #175
Changelog-Fixed: Reset relay in Add Relay view after adding
2022-12-31 09:47:29 -08:00
tyiu 0b27a49e32 Internationalize time ago since string
Switches to using standard date component formatting abbreviations and
enables it to be localized to non-English locales

Closes: #194
Changelog-Changed: Internationalize relative dates
2022-12-31 09:46:16 -08:00
Joel Klabo 58b4d57f32 Clean Up Unused Variable Warning in parse_mentions
Closes: #195
2022-12-31 09:45:00 -08:00
William Casarin 6e709058c0 wallet: refactor, make it work with ln tip button
Changelog-Added: Added option to choose default wallet
2022-12-31 09:41:10 -08:00
William Casarin 0384191060 Improved Wallet Selector 2022-12-30 12:41:11 -08:00
William Casarin 7bee8fd0d1 bump to build 12 2022-12-30 12:41:00 -08:00
Suhail Saqan 35fac4a1c3 Merge branch 'master' into improve-wallet-selector 2022-12-30 01:41:05 -06:00
Suhail Saqan dfbeda5e36 Revert "@bhakes: Bh updates to improve wallet selector" (#6) 2022-12-29 23:39:16 -08:00
Suhail Saqan f7b7254b42 Merge pull request #5 from suhailsaqan/revert-4-bh-updates-to-improve-wallet-selector
@bhakes: Bh updates to improve wallet selector
2022-12-30 01:32:01 -06:00
Suhail Saqan 403a22125a Revert "Bh updates to improve wallet selector" 2022-12-29 23:30:56 -08:00
Suhail Saqan 0bcabc8e08 Merge pull request #4 from bhakes/bh-updates-to-improve-wallet-selector
Bh updates to improve wallet selector
2022-12-30 01:27:40 -06:00
Benjamin Hakes a2f5bb70d5 revert chgs to dev team 2022-12-29 16:40:07 -06:00
Benjamin Hakes ea7d6e96f9 revert chgs to bundle id 2022-12-29 16:38:44 -06:00
Benjamin Hakes f32c4947e1 make invoice view observed object 2022-12-29 16:25:48 -06:00
Benjamin Hakes fe90d02ea8 fix bug updating default wallet 2022-12-29 16:21:55 -06:00
Benjamin Hakes 79c42544d9 clean up dead code 2022-12-29 15:03:36 -06:00
Benjamin Hakes 429a765d42 finish updates to wallet selector improvements 2022-12-29 14:51:36 -06:00
Lio李歐 a94b698e9d Merge branch 'master' into profile-markdown 2022-12-29 09:57:54 -08:00
Suhail Saqan d10ee72fec add show toggle and default wallet selector 2022-12-29 06:04:58 -06:00
Lionello Lunesu a4eaa50ba7 Make links clickable in follow user view 2022-12-28 23:02:01 -08:00
Lionello Lunesu 75c67bc1e9 Make links clickable in profile view 2022-12-28 13:29:18 -08:00
122 changed files with 4136 additions and 746 deletions
+87
View File
@@ -1,5 +1,91 @@
## [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
### Added
- Parse links in profiles (Lionello Lunesu)
- Added Breez wallet to wallet selector (Lee Salminen)
- Added Bitcoin Beach wallet to wallet selector (Lee Salminen)
- Added ability to copy relay urls (Matt Ward)
- Added option to choose default wallet (Suhail Saqan)
### Changed
- Switch like from ❤️ to 🤙 (William Casarin)
- Internationalize relative dates (Terry Yiu)
### Fixed
- Fix but where text was not showing after invoices (William Casarin)
- Load profiles in DMs and notifications (William Casarin)
- Fix expanding profile picture (nosestr bug) (Joel Klabo)
- Fix padding on threads and search results views (OlegAba)
- Don't badge DMs if sent by you (Joel Klabo)
- Reset relay in Add Relay view after adding (Joel Klabo)
[1.0.0]: https://github.com/damus-io/damus/releases/tag/v1.0.0
## [0.1.8-9] - 2022-12-29
### Added
- Relay list on user profiles
### Changed
- Show recommended relays in config. Currently just a fixed set. (William Casarin)
@@ -233,3 +319,4 @@
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
+1 -1
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)
- Italics: 1 asterisk `*italic*`
- Bold: 2 asterisk `**bold**`
- Strikethrough: 2 tildes `~~strikethrough~~`
- Strikethrough: 1 tildes `~strikethrough~`
- Code: 1 back-tick ``code``
#### 💬 Encrypted DMs (chat app, bottom navigation)
@@ -0,0 +1,839 @@
<?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.lproj/InfoPlist.strings" source-language="en" target-language="en" 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="CFBundleDisplayName" xml:space="preserve">
<source>Damus</source>
<target>Damus</target>
<note>Bundle display name</note>
</trans-unit>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>damus</source>
<target>damus</target>
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSPhotoLibraryAddUsageDescription" xml:space="preserve">
<source>"Granting Damus access to your photo library allows you to save photos.</source>
<target>"Granting Damus access to your photo library allows you to save photos.</target>
<note>Privacy - Photo Library Additions Usage Description</note>
</trans-unit>
</body>
</file>
<file original="damus/en.lproj/Localizable.strings" source-language="en" target-language="en" 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>
<target> </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<target>%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ following" xml:space="preserve">
<source>%@ following</source>
<target>%@ following</target>
<note>No comment provided by engineer.</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 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>
<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>
</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>
<target>%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs</target>
<note>No comment provided by engineer.</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>
<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>
</trans-unit>
<trans-unit id="%lld" xml:space="preserve">
<source>%lld</source>
<target>%lld</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld/%lld" xml:space="preserve">
<source>%lld/%lld</source>
<target>%lld/%lld</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="&amp;nbsp;" xml:space="preserve">
<source>&amp;nbsp;</source>
<target>&amp;nbsp;</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="'%@' at '%@' will be used for verification" xml:space="preserve">
<source>'%1$@' at '%2$@' will be used for verification</source>
<target>'%1$@' at '%2$@' will be used for verification</target>
<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="&lt; e &gt;" xml:space="preserve">
<source>&lt; e &gt;</source>
<target>&lt; e &gt;</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="@" xml:space="preserve">
<source>@</source>
<target>@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="About" xml:space="preserve">
<source>About</source>
<target>About</target>
<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>
<target>About Me</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Absolute Boss" xml:space="preserve">
<source>Absolute Boss</source>
<target>Absolute Boss</target>
<note>Placeholder text for About Me description.</note>
</trans-unit>
<trans-unit id="Account ID" xml:space="preserve">
<source>Account ID</source>
<target>Account ID</target>
<note>Label to indicate the public ID of the account.</note>
</trans-unit>
<trans-unit id="Add" xml:space="preserve">
<source>Add</source>
<target>Add</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add Relay" xml:space="preserve">
<source>Add Relay</source>
<target>Add Relay</target>
<note>No comment provided by engineer.</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>
<target>Are you sure you want to boost this post?</target>
<note>No comment provided by engineer.</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>
<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>
</trans-unit>
<trans-unit id="Bitcoin Beach" xml:space="preserve">
<source>Bitcoin Beach</source>
<target>Bitcoin Beach</target>
<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>
<target>Bitcoin Lightning Tips</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Blixt Wallet" xml:space="preserve">
<source>Blixt Wallet</source>
<target>Blixt Wallet</target>
<note>Dropdown option label for Lightning wallet, Blixt Wallet</note>
</trans-unit>
<trans-unit id="Blue Wallet" xml:space="preserve">
<source>Blue Wallet</source>
<target>Blue Wallet</target>
<note>Dropdown option label for Lightning wallet, Blue Wallet.</note>
</trans-unit>
<trans-unit id="Boost" xml:space="preserve">
<source>Boost</source>
<target>Boost</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Boosted" xml:space="preserve">
<source>Boosted</source>
<target>Boosted</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Breez" xml:space="preserve">
<source>Breez</source>
<target>Breez</target>
<note>Dropdown option label for Lightning wallet, Breez.</note>
</trans-unit>
<trans-unit id="Broadcast" xml:space="preserve">
<source>Broadcast</source>
<target>Broadcast</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source>
<target>Cancel</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cash App" xml:space="preserve">
<source>Cash App</source>
<target>Cash App</target>
<note>Dropdown option label for Lightning wallet, Cash App.</note>
</trans-unit>
<trans-unit id="Clear" xml:space="preserve">
<source>Clear</source>
<target>Clear</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Clear Cache" xml:space="preserve">
<source>Clear Cache</source>
<target>Clear Cache</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copied" xml:space="preserve">
<source>Copied</source>
<target>Copied</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy" xml:space="preserve">
<source>Copy</source>
<target>Copy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Account ID" xml:space="preserve">
<source>Copy Account ID</source>
<target>Copy Account ID</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Image" xml:space="preserve">
<source>Copy Image</source>
<target>Copy Image</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Image URL" xml:space="preserve">
<source>Copy Image URL</source>
<target>Copy Image URL</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy LNUrl" xml:space="preserve">
<source>Copy LNUrl</source>
<target>Copy LNUrl</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Note ID" xml:space="preserve">
<source>Copy Note ID</source>
<target>Copy Note ID</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Note JSON" xml:space="preserve">
<source>Copy Note JSON</source>
<target>Copy Note JSON</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Text" xml:space="preserve">
<source>Copy Text</source>
<target>Copy Text</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy User ID" xml:space="preserve">
<source>Copy User ID</source>
<target>Copy User ID</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy invoice" xml:space="preserve">
<source>Copy invoice</source>
<target>Copy invoice</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create" xml:space="preserve">
<source>Create</source>
<target>Create</target>
<note>Button to create account.</note>
</trans-unit>
<trans-unit id="Create Account" xml:space="preserve">
<source>Create Account</source>
<target>Create Account</target>
<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>
<target>Creator(s) of Bitcoin. Absolute legend.</target>
<note>Example description about Bitcoin creator(s), Satoshi Nakamoto.</note>
</trans-unit>
<trans-unit id="DM" xml:space="preserve">
<source>DM</source>
<target>DM</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Damus" xml:space="preserve">
<source>Damus</source>
<target>Damus</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Default Wallet" xml:space="preserve">
<source>Default Wallet</source>
<target>Default Wallet</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete" xml:space="preserve">
<source>Delete</source>
<target>Delete</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Dismiss" xml:space="preserve">
<source>Dismiss</source>
<target>Dismiss</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Display Name" xml:space="preserve">
<source>Display Name</source>
<target>Display Name</target>
<note>Label to prompt display name entry.</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
<target>Done</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Earn Money" xml:space="preserve">
<source>Earn Money</source>
<target>Earn Money</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Edit" xml:space="preserve">
<source>Edit</source>
<target>Edit</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Edit Profile" xml:space="preserve">
<source>Edit Profile</source>
<target>Edit Profile</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted" xml:space="preserve">
<source>Encrypted</source>
<target>Encrypted</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted DMs" xml:space="preserve">
<source>Encrypted DMs</source>
<target>Encrypted DMs</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter your account key to login:" xml:space="preserve">
<source>Enter your account key to login:</source>
<target>Enter your account key to login:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<target>Error: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Filter State" xml:space="preserve">
<source>Filter State</source>
<target>Filter State</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Follow" xml:space="preserve">
<source>Follow</source>
<target>Follow</target>
<note>Button to follow a user.</note>
</trans-unit>
<trans-unit id="Followers" xml:space="preserve">
<source>Followers</source>
<target>Followers</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Following" xml:space="preserve">
<source>Following</source>
<target>Following</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Following..." xml:space="preserve">
<source>Following...</source>
<target>Following...</target>
<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>
<target>Follows</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Global" xml:space="preserve">
<source>Global</source>
<target>Global</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Goto post %@" xml:space="preserve">
<source>Goto post %@</source>
<target>Goto post %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Goto profile %@" xml:space="preserve">
<source>Goto profile %@</source>
<target>Goto profile %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Home" xml:space="preserve">
<source>Home</source>
<target>Home</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="LNLink" xml:space="preserve">
<source>LNLink</source>
<target>LNLink</target>
<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>
<target>Let's go!</target>
<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>
<target>Lightning Address or LNURL</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Lightning Invoice" xml:space="preserve">
<source>Lightning Invoice</source>
<target>Lightning Invoice</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Local default" xml:space="preserve">
<source>Local default</source>
<target>Local default</target>
<note>Dropdown option label for system default for Lightning wallet.</note>
</trans-unit>
<trans-unit id="Login" xml:space="preserve">
<source>Login</source>
<target>Login</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Logout" xml:space="preserve">
<source>Logout</source>
<target>Logout</target>
<note>No comment provided by engineer.</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>
<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>
</trans-unit>
<trans-unit id="Muun" xml:space="preserve">
<source>Muun</source>
<target>Muun</target>
<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>
<target>NIP-05 Verification</target>
<note>No comment provided by engineer.</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>
<target>Nothing to see here. Check back later!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Notifications</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Pay" xml:space="preserve">
<source>Pay</source>
<target>Pay</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Pay the lightning invoice" xml:space="preserve">
<source>Pay the lightning invoice</source>
<target>Pay the lightning invoice</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Phoenix" xml:space="preserve">
<source>Phoenix</source>
<target>Phoenix</target>
<note>Dropdown option label for Lightning wallet, Phoenix.</note>
</trans-unit>
<trans-unit id="Post" xml:space="preserve">
<source>Post</source>
<target>Post</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Posts" xml:space="preserve">
<source>Posts</source>
<target>Posts</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Posts &amp; Replies" xml:space="preserve">
<source>Posts &amp; Replies</source>
<target>Posts &amp; Replies</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Private" xml:space="preserve">
<source>Private</source>
<target>Private</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Private Key" xml:space="preserve">
<source>Private Key</source>
<target>Private Key</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PrivateKey" xml:space="preserve">
<source>PrivateKey</source>
<target>PrivateKey</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile Picture" xml:space="preserve">
<source>Profile Picture</source>
<target>Profile Picture</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Public Account ID" xml:space="preserve">
<source>Public Account ID</source>
<target>Public Account ID</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Public Key" xml:space="preserve">
<source>Public Key</source>
<target>Public Key</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Public Key?" xml:space="preserve">
<source>Public Key?</source>
<target>Public Key?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Public key" xml:space="preserve">
<source>Public key</source>
<target>Public key</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Recommended Relays" xml:space="preserve">
<source>Recommended Relays</source>
<target>Recommended Relays</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Relay" xml:space="preserve">
<source>Relay</source>
<target>Relay</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Relays" xml:space="preserve">
<source>Relays</source>
<target>Relays</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reply to self" xml:space="preserve">
<source>Reply to self</source>
<target>Reply to self</target>
<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>
<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">
<source>Replying to:</source>
<target>Replying to:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reset" xml:space="preserve">
<source>Reset</source>
<target>Reset</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="River" xml:space="preserve">
<source>River</source>
<target>River</target>
<note>Dropdown option label for Lightning wallet, River</note>
</trans-unit>
<trans-unit id="Satoshi Nakamoto" xml:space="preserve">
<source>Satoshi Nakamoto</source>
<target>Satoshi Nakamoto</target>
<note>Name of Bitcoin creator(s).</note>
</trans-unit>
<trans-unit id="Save" xml:space="preserve">
<source>Save</source>
<target>Save</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save Image" xml:space="preserve">
<source>Save Image</source>
<target>Save Image</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Search hashtag: #%@" xml:space="preserve">
<source>Search hashtag: #%@</source>
<target>Search hashtag: #%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Search..." xml:space="preserve">
<source>Search...</source>
<target>Search...</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Secret Account Login Key" xml:space="preserve">
<source>Secret Account Login Key</source>
<target>Secret Account Login Key</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Select a lightning wallet" xml:space="preserve">
<source>Select a lightning wallet</source>
<target>Select a lightning wallet</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Select default wallet" xml:space="preserve">
<source>Select default wallet</source>
<target>Select default wallet</target>
<note>No comment provided by engineer.</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>
<target>Send a message to start the conversation...</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Settings" xml:space="preserve">
<source>Settings</source>
<target>Settings</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
<target>Share</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show" xml:space="preserve">
<source>Show</source>
<target>Show</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show wallet selector" xml:space="preserve">
<source>Show wallet selector</source>
<target>Show wallet selector</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Strike" xml:space="preserve">
<source>Strike</source>
<target>Strike</target>
<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>
<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>
</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>
<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>
</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>
<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>
</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>
<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>
</trans-unit>
<trans-unit id="Thread" xml:space="preserve">
<source>Thread</source>
<target>Thread</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Type your post here..." xml:space="preserve">
<source>Type your post here...</source>
<target>Type your post here...</target>
<note>Text box prompt to ask user to type their post.</note>
</trans-unit>
<trans-unit id="Unfollow" xml:space="preserve">
<source>Unfollow</source>
<target>Unfollow</target>
<note>Button to unfollow a user.</note>
</trans-unit>
<trans-unit id="Unfollowing" xml:space="preserve">
<source>Unfollowing</source>
<target>Unfollowing</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unfollowing..." xml:space="preserve">
<source>Unfollowing...</source>
<target>Unfollowing...</target>
<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>
<target>Unfollows</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Username" xml:space="preserve">
<source>Username</source>
<target>Username</target>
<note>Label to prompt username entry.</note>
</trans-unit>
<trans-unit id="Wallet Of Satoshi" xml:space="preserve">
<source>Wallet Of Satoshi</source>
<target>Wallet Of Satoshi</target>
<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>
<target>Wallet Selector</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Website" xml:space="preserve">
<source>Website</source>
<target>Website</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Welcome to the social network %@ control." xml:space="preserve">
<source>Welcome to the social network %@ control.</source>
<target>Welcome to the social network %@ control.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Welcome, %@!" xml:space="preserve">
<source>Welcome, %@!</source>
<target>Welcome, %@!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your Name" xml:space="preserve">
<source>Your Name</source>
<target>Your Name</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Zebedee" xml:space="preserve">
<source>Zebedee</source>
<target>Zebedee</target>
<note>Dropdown option label for Lightning wallet, Zebedee.</note>
</trans-unit>
<trans-unit id="Zeus LN" xml:space="preserve">
<source>Zeus LN</source>
<target>Zeus LN</target>
<note>Dropdown option label for Lightning wallet, Zeus LN.</note>
</trans-unit>
<trans-unit id="https://example.com/pic.jpg" xml:space="preserve">
<source>https://example.com/pic.jpg</source>
<target>https://example.com/pic.jpg</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="https://jb55.com" xml:space="preserve">
<source>https://jb55.com</source>
<target>https://jb55.com</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="jb55@jb55.com" xml:space="preserve">
<source>jb55@jb55.com</source>
<target>jb55@jb55.com</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="none" xml:space="preserve">
<source>none</source>
<target>none</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="now" xml:space="preserve">
<source>now</source>
<target>now</target>
<note>String indicating that a given timestamp just occurred</note>
</trans-unit>
<trans-unit id="optional" xml:space="preserve">
<source>optional</source>
<target>optional</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="replying_to_one_and_others" translate="no" xml:space="preserve">
<source>replying_to_one_and_others</source>
<target>replying_to_one_and_others</target>
<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>
<target>replying_to_two_and_others</target>
<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>
<target>satoshi</target>
<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>
<target>wss://some.relay.com</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you" xml:space="preserve">
<source>you</source>
<target>you</target>
<note>No comment provided by engineer.</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 id="🤙" xml:space="preserve">
<source>🤙</source>
<target>🤙</target>
<note>No comment provided by engineer.</note>
</trans-unit>
</body>
</file>
<file original="damus/en.lproj/Localizable.stringsdict" source-language="en" target-language="en" 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="/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>
@@ -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.";
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@%#@others@</string>
<key>others</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@, %@%#@others@</string>
<key>others</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,12 @@
{
"developmentRegion" : "en",
"project" : "damus.xcodeproj",
"targetLocale" : "en",
"toolInfo" : {
"toolBuildNumber" : "14C18",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "14.2"
},
"version" : "1.0"
}
@@ -0,0 +1,676 @@
<?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.lproj/InfoPlist.strings" datatype="plaintext" source-language="en" target-language="es">
<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.lproj/Localizable.strings" source-language="en" target-language="es" 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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ following" xml:space="preserve">
<source>%@ following</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@'s Followers" xml:space="preserve">
<source>%@'s Followers</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld" xml:space="preserve">
<source>%lld</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld/%lld" xml:space="preserve">
<source>%lld/%lld</source>
<note>No comment provided by engineer.</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>'%1$@' at '%2$@' 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="&lt; e &gt;" xml:space="preserve">
<source>&lt; e &gt;</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="@" xml:space="preserve">
<source>@</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add Relay" xml:space="preserve">
<source>Add Relay</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Boosted" xml:space="preserve">
<source>Boosted</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source>
<note>No comment provided by engineer.</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="Clear" xml:space="preserve">
<source>Clear</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Clear Cache" xml:space="preserve">
<source>Clear Cache</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copied" xml:space="preserve">
<source>Copied</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy" xml:space="preserve">
<source>Copy</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Account ID" xml:space="preserve">
<source>Copy Account ID</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Image" xml:space="preserve">
<source>Copy Image</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Image URL" xml:space="preserve">
<source>Copy Image URL</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy LNUrl" xml:space="preserve">
<source>Copy LNUrl</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Note ID" xml:space="preserve">
<source>Copy Note ID</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Note JSON" xml:space="preserve">
<source>Copy Note JSON</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy Text" xml:space="preserve">
<source>Copy Text</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy User ID" xml:space="preserve">
<source>Copy User ID</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Copy invoice" xml:space="preserve">
<source>Copy invoice</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Damus" xml:space="preserve">
<source>Damus</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Default Wallet" xml:space="preserve">
<source>Default Wallet</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete" xml:space="preserve">
<source>Delete</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Dismiss" xml:space="preserve">
<source>Dismiss</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Earn Money" xml:space="preserve">
<source>Earn Money</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Edit" xml:space="preserve">
<source>Edit</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Edit Profile" xml:space="preserve">
<source>Edit Profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted" xml:space="preserve">
<source>Encrypted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted DMs" xml:space="preserve">
<source>Encrypted DMs</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter your account key to login:" xml:space="preserve">
<source>Enter your account key to login:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Filter State" xml:space="preserve">
<source>Filter State</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Following" xml:space="preserve">
<source>Following</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Global" xml:space="preserve">
<source>Global</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Goto post %@" xml:space="preserve">
<source>Goto post %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Goto profile %@" xml:space="preserve">
<source>Goto profile %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Home" xml:space="preserve">
<source>Home</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Lightning Invoice" xml:space="preserve">
<source>Lightning Invoice</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Logout" xml:space="preserve">
<source>Logout</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Pay" xml:space="preserve">
<source>Pay</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Pay the lightning invoice" xml:space="preserve">
<source>Pay the lightning invoice</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Posts" xml:space="preserve">
<source>Posts</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Posts &amp; Replies" xml:space="preserve">
<source>Posts &amp; Replies</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Private" xml:space="preserve">
<source>Private</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Private Key" xml:space="preserve">
<source>Private Key</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PrivateKey" xml:space="preserve">
<source>PrivateKey</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile Picture" xml:space="preserve">
<source>Profile Picture</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Public Account ID" xml:space="preserve">
<source>Public Account ID</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Public Key" xml:space="preserve">
<source>Public Key</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Public Key?" xml:space="preserve">
<source>Public Key?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Public key" xml:space="preserve">
<source>Public key</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Recommended Relays" xml:space="preserve">
<source>Recommended Relays</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Relay" xml:space="preserve">
<source>Relay</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Relays" xml:space="preserve">
<source>Relays</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reset" xml:space="preserve">
<source>Reset</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save Image" xml:space="preserve">
<source>Save Image</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Search hashtag: #%@" xml:space="preserve">
<source>Search hashtag: #%@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Search..." xml:space="preserve">
<source>Search...</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Secret Account Login Key" xml:space="preserve">
<source>Secret Account Login Key</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Select a lightning wallet" xml:space="preserve">
<source>Select a lightning wallet</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Select default wallet" xml:space="preserve">
<source>Select default wallet</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Settings" xml:space="preserve">
<source>Settings</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show" xml:space="preserve">
<source>Show</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show wallet selector" xml:space="preserve">
<source>Show wallet selector</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Thread" xml:space="preserve">
<source>Thread</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Username" xml:space="preserve">
<source>Username</source>
<note>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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Website" xml:space="preserve">
<source>Website</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Welcome to the social network %@ control." xml:space="preserve">
<source>Welcome to the social network %@ control.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Welcome, %@!" xml:space="preserve">
<source>Welcome, %@!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your Name" xml:space="preserve">
<source>Your Name</source>
<note>No comment provided by engineer.</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="https://example.com/pic.jpg" xml:space="preserve">
<source>https://example.com/pic.jpg</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="https://jb55.com" xml:space="preserve">
<source>https://jb55.com</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="jb55@jb55.com" xml:space="preserve">
<source>jb55@jb55.com</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="none" xml:space="preserve">
<source>none</source>
<note>No comment provided by engineer.</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="optional" xml:space="preserve">
<source>optional</source>
<note>No comment provided by engineer.</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>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you" xml:space="preserve">
<source>you</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="··· %lld other notes ···" xml:space="preserve">
<source>··· %lld other notes ···</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="🤙" xml:space="preserve">
<source>🤙</source>
<note>No comment provided by engineer.</note>
</trans-unit>
</body>
</file>
<file original="damus/en.lproj/Localizable.stringsdict" source-language="en" target-language="es" 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="/replying_to_one_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>Replying to %@%#@others@</source>
<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>
<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>
<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/>
<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>
<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>
<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>
<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/>
<note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit>
</body>
</file>
</xliff>
@@ -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.";
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@%#@others@</string>
<key>others</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@, %@%#@others@</string>
<key>others</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,12 @@
{
"developmentRegion" : "en",
"project" : "damus.xcodeproj",
"targetLocale" : "es",
"toolInfo" : {
"toolBuildNumber" : "14C18",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "14.2"
},
"version" : "1.0"
}
+2 -2
View File
@@ -151,7 +151,7 @@ static bool from_numbers(u64 *res,
return false;
if (!from_number(&p1, s1, len1, tens_factor)
|| !from_number(&p2, s2, len2, tens_factor - len2))
|| !from_number(&p2, s2, len2, tens_factor - (int)len2))
return false;
if (add_overflows_u64(p1, p2))
@@ -453,7 +453,7 @@ bool amount_msat_to_u32(struct amount_msat msat, u32 *millisatoshis)
{
if (amount_msat_greater_eq(msat, AMOUNT_MSAT(0x100000000)))
return false;
*millisatoshis = msat.millisatoshis;
*millisatoshis = (u32)msat.millisatoshis;
return true;
}
+1 -1
View File
@@ -297,7 +297,7 @@ static char *decode_c(struct bolt11 *b11,
if (!pull_uint(hu5, data, data_len, &c, data_length * 5))
return tal_fmt(b11, "c: length %zu chars is excessive",
*data_len);
b11->min_final_cltv_expiry = c;
b11->min_final_cltv_expiry = (u32)c;
/* Can overflow, since c is 64 bits but value must be < 32 bits */
if (b11->min_final_cltv_expiry != c)
return tal_fmt(b11, "c: %"PRIu64" is too large", c);
+1 -1
View File
@@ -241,7 +241,7 @@ static int parse_invoice(struct cursor *cur, struct block *block) {
block->block.invoice.invstr.end = (const char*)end;
block->block.invoice.bolt11 = bolt11;
cur->p += end - start;
cur->p = end;
return 1;
}
+2 -2
View File
@@ -30,7 +30,7 @@ void hash_u5(struct hash_u5 *hu5, const u8 *u5, size_t len)
u5++;
if (hu5->num_bits >= 32) {
be32 be32 = cpu_to_be32(hu5->buf >> (hu5->num_bits-32));
be32 be32 = cpu_to_be32((u32)(hu5->buf >> (hu5->num_bits-32)));
sha256_update(&hu5->hash, &be32, sizeof(be32));
hu5->num_bits -= 32;
}
@@ -40,7 +40,7 @@ void hash_u5(struct hash_u5 *hu5, const u8 *u5, size_t len)
void hash_u5_done(struct hash_u5 *hu5, struct sha256 *res)
{
if (hu5->num_bits) {
be32 be32 = cpu_to_be32(hu5->buf << (32 - hu5->num_bits));
be32 be32 = cpu_to_be32((u32)(hu5->buf << (32 - hu5->num_bits)));
sha256_update(&hu5->hash, &be32, (hu5->num_bits + 7) / 8);
}
+1 -1
View File
@@ -37,7 +37,7 @@ static inline bool assign_overflow_u16(u16 *dst, uint64_t v)
static inline bool assign_overflow_u32(u32 *dst, uint64_t v)
{
*dst = v;
*dst = (u32)v;
return *dst == v;
}
#endif /* LIGHTNING_COMMON_OVERFLOWS_H */
+118 -40
View File
@@ -11,6 +11,10 @@
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; };
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
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 */; };
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; };
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; };
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; };
@@ -48,6 +52,8 @@
4C363AA228296A7E006E126D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA128296A7E006E126D /* SearchView.swift */; };
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA328296DEE006E126D /* SearchModel.swift */; };
4C363AA828297703006E126D /* InsertSort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA728297703006E126D /* InsertSort.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 */; };
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79C2833036D00E1F516 /* FollowingView.swift */; };
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79E2833115300E1F516 /* FollowButtonView.swift */; };
@@ -111,6 +117,7 @@
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.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 */; };
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; };
@@ -132,8 +139,12 @@
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */; };
4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */; };
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.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 */; };
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; };
/* End PBXBuildFile section */
@@ -160,6 +171,11 @@
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; };
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>"; };
3AB1803D29636FB100FD1BD8 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.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>"; };
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; sourceTree = "<group>"; };
4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = "<group>"; };
4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; };
4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = "<group>"; };
@@ -199,6 +215,8 @@
4C363AA128296A7E006E126D /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.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>"; };
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>"; };
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>"; };
@@ -292,6 +310,7 @@
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>"; };
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>"; };
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>"; };
@@ -316,7 +335,11 @@
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileName.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>"; };
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>"; };
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>"; };
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadV2View.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -442,6 +465,8 @@
4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */,
4C216F372871EDE300040376 /* DirectMessageModel.swift */,
4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */,
BA693073295D649800ADDB87 /* UserSettingsStore.swift */,
4FE60CDC295E1C5E00105A1F /* Wallet.swift */,
);
path = Models;
sourceTree = "<group>";
@@ -449,48 +474,48 @@
4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup;
children = (
3169CAE4294E699400EE4006 /* Empty Views */,
4C75EFA327FA577B0006080F /* PostView.swift */,
4C75EFAC28049CFB0006080F /* PostButton.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 */,
4CE4F9E228528C5200C00DD9 /* AddRelayView.swift */,
4C363A8728236948006E126D /* BlocksView.swift */,
4C285C8128385570008A31F1 /* CarouselView.swift */,
4C0A3F8B280F5FCA000448DE /* ChatroomView.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 */,
4CE4F9E228528C5200C00DD9 /* AddRelayView.swift */,
4C285C8328385690008A31F1 /* CreateAccountView.swift */,
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */,
4C216F31286E388800040376 /* DMChatView.swift */,
4C216F33286F5ACD00040376 /* DMView.swift */,
4C06670028FC7C5900038D2A /* RelayView.swift */,
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */,
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */,
3169CAE4294E699400EE4006 /* Empty Views */,
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 */,
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 */,
);
path = Views;
@@ -519,6 +544,7 @@
4C7FF7D628233637009601DB /* Util */ = {
isa = PBXGroup;
children = (
4C3A1D322960DB0500558C0F /* Markdown.swift */,
4CEE2AF4280B29E600AB5EEF /* TimeAgo.swift */,
4CE4F8CC281352B30009DFBB /* Notifications.swift */,
4C363A8328233689006E126D /* Parser.swift */,
@@ -529,6 +555,9 @@
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */,
3169CAEC294FCCFC00EE4006 /* Constants.swift */,
3165648A295B70D500C64604 /* LinkView.swift */,
4C3A1D3629637E0500558C0F /* PreviewCache.swift */,
64FBD06E296255C400D9D3B2 /* Theme.swift */,
4CB8838529656C8B00DC99E7 /* NIP05.swift */,
);
path = Util;
sourceTree = "<group>";
@@ -582,6 +611,9 @@
4CE6DEE827F7A08100C66700 /* ContentView.swift */,
4CE6DEEA27F7A08200C66700 /* Assets.xcassets */,
4CE6DEEC27F7A08200C66700 /* Preview Content */,
3AB18052296375CA00FD1BD8 /* InfoPlist.strings */,
3AB18054296375CA00FD1BD8 /* Localizable.strings */,
3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */,
);
path = damus;
sourceTree = "<group>";
@@ -597,11 +629,13 @@
4CE6DEF627F7A08200C66700 /* damusTests */ = {
isa = PBXGroup;
children = (
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */,
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */,
4C363A9F2828A8DD006E126D /* LikeTests.swift */,
4C363A9D2828A822006E126D /* ReplyTests.swift */,
4CE6DEF727F7A08200C66700 /* damusTests.swift */,
4C3EA67A28FF7B3900C48A62 /* InvoiceTests.swift */,
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */,
);
path = damusTests;
sourceTree = "<group>";
@@ -715,6 +749,7 @@
hasScannedForEncodings = 0;
knownRegions = (
en,
es,
Base,
);
mainGroup = 4CE6DEDA27F7A08100C66700;
@@ -741,8 +776,11 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3AB18057296375CA00FD1BD8 /* Localizable.strings in Resources */,
4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */,
3AB18056296375CA00FD1BD8 /* InfoPlist.strings in Resources */,
4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */,
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -806,6 +844,7 @@
4C363A8428233689006E126D /* Parser.swift in Sources */,
4CE4F9E328528C5200C00DD9 /* AddRelayView.swift in Sources */,
4C363A9A28283854006E126D /* Reply.swift in Sources */,
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */,
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
@@ -823,8 +862,10 @@
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */,
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */,
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */,
4C3EA67528FF7A5A00C48A62 /* take.c in Sources */,
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */,
@@ -846,6 +887,7 @@
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
@@ -874,8 +916,10 @@
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */,
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */,
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */,
4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */,
4C06670B28FDE64700038D2A /* damus.c in Sources */,
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */,
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */,
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
4C75EFB528049D790006080F /* Relay.swift in Sources */,
@@ -890,6 +934,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */,
4C3EA67B28FF7B3900C48A62 /* InvoiceTests.swift in Sources */,
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */,
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
@@ -922,11 +968,40 @@
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */ = {
isa = PBXVariantGroup;
children = (
3A4325A92961E11400BFCD9D /* en */,
3AB1803D29636FB100FD1BD8 /* es */,
);
name = Localizable.stringsdict;
sourceTree = "<group>";
};
3AB18052296375CA00FD1BD8 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
3AB18058296377E500FD1BD8 /* es */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
3AB18054296375CA00FD1BD8 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
3AB18059296377E700FD1BD8 /* es */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
4CE6DF0527F7A08200C66700 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
@@ -973,7 +1048,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.3;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MACOSX_DEPLOYMENT_TARGET = 12.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
@@ -988,6 +1063,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
@@ -1028,7 +1104,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.3;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MACOSX_DEPLOYMENT_TARGET = 12.3;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
@@ -1047,13 +1123,14 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9;
CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = damus/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Damus;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -1067,7 +1144,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 0.1.8;
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -1086,13 +1163,14 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9;
CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = damus/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Damus;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -1106,7 +1184,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 0.1.8;
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -1,6 +1,33 @@
{
"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"
}
],
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

+21
View File
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "bbw.jpg",
"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: 14 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "blixt-wallet.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: 215 KiB

+21
View File
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "breez.jpg",
"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: 21 KiB

@@ -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

+21
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

+21
View File
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "river.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: 26 KiB

+23
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

+5
View File
@@ -49,6 +49,11 @@ struct ImageContextMenuModifier: ViewModifier {
} label: {
Label("Copy Image", systemImage: "photo.on.rectangle")
}
Button {
UIImageWriteToSavedPhotosAlbum(someImage, nil, nil, nil)
} label: {
Label("Save Image", systemImage: "square.and.arrow.down")
}
}
Button {
showShareSheet = true
+33 -9
View File
@@ -7,18 +7,42 @@
import SwiftUI
func open_with_wallet(wallet: Wallet.Model, invoice: String) {
if let url = URL(string: "\(wallet.link)\(invoice)"), UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
} else {
guard let store_link = wallet.appStoreLink else {
// TODO: do something here if we don't have an appstore link
return
}
guard let url = URL(string: store_link) else {
return
}
guard UIApplication.shared.canOpenURL(url) else {
return
}
UIApplication.shared.open(url)
}
}
struct InvoiceView: View {
@Environment(\.colorScheme) var colorScheme
@Environment(\.openURL) private var openURL
let invoice: Invoice
@State var showingSelectWallet: Bool = false
@State var inv: String = ""
@State var showing_select_wallet: Bool = false
@ObservedObject var user_settings = UserSettingsStore()
var PayButton: some View {
Button {
inv = invoice.string
showingSelectWallet = true
if user_settings.show_wallet_selector {
showing_select_wallet = true
} else {
open_with_wallet(wallet: user_settings.default_wallet.model, invoice: invoice.string)
}
} label: {
RoundedRectangle(cornerRadius: 20)
.foregroundColor(colorScheme == .light ? .black : .white)
@@ -47,7 +71,7 @@ struct InvoiceView: View {
}
Divider()
Text(invoice.description)
Text("\(invoice.amount / 1000) sats")
Text(invoice.amount.amount_sats_str())
.font(.title)
PayButton
.frame(height: 50)
@@ -55,13 +79,13 @@ struct InvoiceView: View {
}
.padding(30)
}
.sheet(isPresented: $showingSelectWallet, onDismiss: {showingSelectWallet = false}) {
SelectWalletView(showingSelectWallet: $showingSelectWallet, invoice: $inv)
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
SelectWalletView(showingSelectWallet: $showing_select_wallet, invoice: invoice.string).environmentObject(user_settings)
}
}
}
let test_invoice = Invoice(description: "this is a description", amount: 10000, string: "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r", expiry: 604800, payment_hash: Data(), created_at: 1666139119)
let test_invoice = Invoice(description: "this is a description", amount: .specific(10000), string: "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r", expiry: 604800, payment_hash: Data(), created_at: 1666139119)
struct InvoiceView_Previews: PreviewProvider {
static var previews: some View {
+1 -1
View File
@@ -30,7 +30,7 @@ struct InvoicesView: View {
struct InvoicesView_Previews: PreviewProvider {
static var previews: some View {
InvoicesView(invoices: [Invoice.init(description: "description", amount: 10000, string: "invstr", expiry: 100000, payment_hash: Data(), created_at: 1000000)])
InvoicesView(invoices: [Invoice.init(description: "description", amount: .specific(10000), string: "invstr", expiry: 100000, payment_hash: Data(), created_at: 1000000)])
.frame(width: 300)
}
}
+45 -23
View File
@@ -16,7 +16,7 @@ var BOOTSTRAP_RELAYS = [
"wss://relay.nostr.bg",
"wss://nostr.oxtr.dev",
"wss://nostr.v0l.io",
"wss://nostr-2.zebedee.cloud",
"wss://brb.io",
]
struct TimestampedProfile {
@@ -44,6 +44,15 @@ enum ThreadState {
enum FilterState : Int {
case posts_and_replies = 1
case posts = 0
func filter(privkey: String?, ev: NostrEvent) -> Bool {
switch self {
case .posts:
return !ev.is_reply(privkey)
case .posts_and_replies:
return true
}
}
}
struct ContentView: View {
@@ -72,6 +81,7 @@ struct ContentView: View {
@State var search_open: Bool = false
@State var filter_state : FilterState = .posts_and_replies
@StateObject var home: HomeModel = HomeModel()
@StateObject var user_settings = UserSettingsStore()
// connect retry timer
let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect()
@@ -81,17 +91,14 @@ struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var PostingTimelineView: some View {
VStack{
ZStack {
if let damus = self.damus_state {
TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter_event)
}
if privkey != nil {
PostButtonContainer {
self.active_sheet = .post
}
}
}.ignoresSafeArea(.keyboard, edges: .bottom)
VStack {
TabView(selection: $filter_state) {
contentTimelineView(filter: posts_filter_event)
.tag(FilterState.posts)
contentTimelineView(filter: posts_and_replies_filter_event)
.tag(FilterState.posts_and_replies)
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
.safeAreaInset(edge: .top) {
VStack(spacing: 0) {
@@ -103,6 +110,28 @@ struct ContentView: View {
}
.background(colorScheme == .dark ? Color.black : Color.white)
}
.ignoresSafeArea(.keyboard)
}
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
ZStack {
if let damus = self.damus_state {
TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter)
}
if privkey != nil {
PostButtonContainer {
self.active_sheet = .post
}
}
}
}
func posts_and_replies_filter_event(_ ev: NostrEvent) -> Bool {
return true
}
func posts_filter_event(_ ev: NostrEvent) -> Bool {
return !ev.is_reply(nil)
}
var FiltersView: some View {
@@ -115,14 +144,6 @@ struct ContentView: View {
}
}
func filter_event(_ ev: NostrEvent) -> Bool {
if self.filter_state == .posts {
return !ev.is_reply(nil)
}
return true
}
func MainContent(damus: DamusState) -> some View {
VStack {
NavigationLink(destination: MaybeProfileView, isActive: $profile_open) {
@@ -219,7 +240,7 @@ struct ContentView: View {
.foregroundColor(.gray)
}
NavigationLink(destination: ConfigView(state: damus_state!)) {
NavigationLink(destination: ConfigView(state: damus_state!).environmentObject(user_settings)) {
Label("", systemImage: "gear")
}
.buttonStyle(PlainButtonStyle())
@@ -400,10 +421,11 @@ struct ContentView: View {
self.damus_state = DamusState(pool: pool, keypair: keypair,
likes: EventCounter(our_pubkey: pubkey),
boosts: EventCounter(our_pubkey: pubkey),
contacts: Contacts(),
contacts: Contacts(our_pubkey: pubkey),
tips: TipCounter(our_pubkey: pubkey),
profiles: Profiles(),
dms: home.dms
dms: home.dms,
previews: PreviewCache()
)
home.damus_state = self.damus_state!
+6
View File
@@ -15,8 +15,13 @@
</array>
</dict>
</array>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>&quot;Granting Damus access to your photo library allows you to save photos.</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>river</string>
<string>bitcoinbeach</string>
<string>breez</string>
<string>muun</string>
<string>zeusln</string>
<string>zebedee</string>
@@ -27,6 +32,7 @@
<string>strike</string>
<string>bluewallet</string>
<string>walletofsatoshi</string>
<string>blixtwallet</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
+9
View File
@@ -11,8 +11,13 @@ import Foundation
class Contacts {
private var friends: Set<String> = Set()
private var friend_of_friends: Set<String> = Set()
let our_pubkey: String
var event: NostrEvent?
init(our_pubkey: String) {
self.our_pubkey = our_pubkey
}
func get_friendosphere() -> [String] {
var fs = get_friend_list()
fs.append(contentsOf: get_friend_of_friend_list())
@@ -56,6 +61,10 @@ class Contacts {
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 {
return is_friend(pubkey) ? .follows : .unfollows
}
+3 -1
View File
@@ -6,6 +6,7 @@
//
import Foundation
import LinkPresentation
struct DamusState {
let pool: RelayPool
@@ -16,12 +17,13 @@ struct DamusState {
let tips: TipCounter
let profiles: Profiles
let dms: DirectMessagesModel
let previews: PreviewCache
var pubkey: String {
return keypair.pubkey
}
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())
}
}
+32 -5
View File
@@ -44,6 +44,7 @@ class HomeModel: ObservableObject {
let notifications_subid = UUID().description
let dms_subid = UUID().description
let init_subid = UUID().description
let profiles_subid = UUID().description
@Published var new_events: NewEventsBits = NewEventsBits()
@Published var notifications: [NostrEvent] = []
@@ -234,7 +235,15 @@ class HomeModel: ObservableObject {
//self.events.insert(NostrEvent(content: "NOTICE from \(relay_id): \(msg)", pubkey: "system"), at: 0)
print(msg)
case .eose:
case .eose(let sub_id):
if sub_id == dms_subid {
let dms = dms.dms.flatMap { $0.1.events }
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: dms, damus_state: damus_state)
} else if sub_id == notifications_subid {
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: notifications, damus_state: damus_state)
}
self.loading = false
break
}
@@ -338,12 +347,14 @@ class HomeModel: ObservableObject {
return m[kind]
}
func handle_last_event(ev: NostrEvent, timeline: Timeline) {
func handle_last_event(ev: NostrEvent, timeline: Timeline, shouldNotify: Bool = true) {
let last_ev = get_last_event(timeline)
if last_ev == nil || last_ev!.created_at < ev.created_at {
save_last_event(ev, timeline: timeline)
new_events = NewEventsBits(prev: new_events, setting: timeline)
if shouldNotify {
new_events = NewEventsBits(prev: new_events, setting: timeline)
}
}
}
@@ -415,13 +426,13 @@ class HomeModel: ObservableObject {
}
if inserted {
handle_last_event(ev: ev, timeline: .dms)
handle_last_event(ev: ev, timeline: .dms, shouldNotify: !ours)
dms.dms = dms.dms.sorted { a, b in
if a.1.events.count > 0 && b.1.events.count > 0 {
return a.1.events.last!.created_at > b.1.events.last!.created_at
}
return true
return false
}
}
}
@@ -533,7 +544,9 @@ func process_metadata_event(profiles: Profiles, ev: NostrEvent) {
return
}
var old_nip05: String? = nil
if let mprof = profiles.lookup_with_timestamp(id: ev.pubkey) {
old_nip05 = mprof.profile.nip05
if mprof.timestamp > ev.created_at {
// skip if we already have an newer profile
return
@@ -543,6 +556,20 @@ func process_metadata_event(profiles: Profiles, ev: NostrEvent) {
let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at)
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
let picture = tprof.profile.picture ?? robohash(ev.pubkey)
if let _ = URL(string: picture) {
+20 -7
View File
@@ -34,7 +34,7 @@ struct IdBlock: Identifiable {
struct Invoice {
let description: String
let amount: Int64
let amount: Amount
let string: String
let expiry: UInt64
let payment_hash: Data
@@ -115,7 +115,7 @@ func parse_mentions(content: String, tags: [[String]]) -> [Block] {
blocks_init(&bs)
let bytes = content.utf8CString
bytes.withUnsafeBufferPointer { p in
let _ = bytes.withUnsafeBufferPointer { p in
damus_parse_content(&bs, p.baseAddress)
}
@@ -180,6 +180,23 @@ func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>!) -> T? {
return p.pointee
}
enum Amount: Equatable {
case any
case specific(Int64)
func amount_sats_str() -> String {
switch self {
case .any:
return "Any"
case .specific(let amt):
if amt < 1000 {
return "\(Double(amt) / 1000.0) sats"
}
return "\(amt / 1000) sats"
}
}
}
func convert_invoice_block(_ b: invoice_block) -> Block? {
guard let invstr = strblock_to_string(b.invstr) else {
return nil
@@ -194,12 +211,8 @@ func convert_invoice_block(_ b: invoice_block) -> Block? {
description = String(cString: b11.description)
}
guard let msat = maybe_pointee(b11.msat) else {
return nil
}
let amount = Int64(msat.millisatoshis)
let amount: Amount = maybe_pointee(b11.msat).map { .specific(Int64($0.millisatoshis)) } ?? .any
let payment_hash = Data(bytes: &b11.payment_hash, count: 32)
let hex = hex_encode(payment_hash)
let created_at = b11.timestamp
tal_free(b.bolt11)
+12 -4
View File
@@ -61,7 +61,7 @@ class ProfileModel: ObservableObject, Equatable {
profile_filter.authors = [pubkey]
text_filter.authors = [pubkey]
text_filter.limit = 1000
text_filter.limit = 500
print("subscribing to profile \(pubkey) with sub_id \(sub_id)")
print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]])
@@ -70,12 +70,18 @@ class ProfileModel: ObservableObject, Equatable {
}
func handle_profile_contact_event(_ ev: NostrEvent) {
process_contact_event(pool: damus.pool, contacts: damus.contacts, pubkey: damus.pubkey, ev: ev)
// only use new stuff
if let current_ev = self.contacts {
guard ev.created_at > current_ev.created_at else {
return
}
}
self.contacts = ev
self.following = count_pubkeys(ev.tags)
self.relays = decode_json_relays(ev.content)
if damus.contacts.is_friend(ev.pubkey) {
self.damus.contacts.add_friend_contact(ev)
}
}
func add_event(_ ev: NostrEvent) {
@@ -90,6 +96,8 @@ class ProfileModel: ObservableObject, Equatable {
let _ = insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at})
} else if ev.known_kind == .contacts {
handle_profile_contact_event(ev)
} else if ev.known_kind == .metadata {
process_metadata_event(profiles: damus.profiles, ev: ev)
}
seen_event.insert(ev.id)
}
+51 -48
View File
@@ -41,40 +41,40 @@ class SearchHomeModel: ObservableObject {
}
func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) {
switch conn_ev {
case .ws_event:
break
case .nostr_event(let event):
switch event {
case .event(let sub_id, let ev):
guard sub_id == self.base_subid || sub_id == self.profiles_subid else {
guard case .nostr_event(let event) = conn_ev else {
return
}
switch event {
case .event(let sub_id, let ev):
guard sub_id == self.base_subid || sub_id == self.profiles_subid else {
return
}
if ev.is_textlike && ev.should_show_event && !ev.is_reply(nil) {
if seen_pubkey.contains(ev.pubkey) {
return
}
if ev.is_textlike && ev.should_show_event {
if seen_pubkey.contains(ev.pubkey) {
return
}
seen_pubkey.insert(ev.pubkey)
let _ = insert_uniq_sorted_event(events: &events, new_ev: ev) {
$0.created_at > $1.created_at
}
seen_pubkey.insert(ev.pubkey)
let _ = insert_uniq_sorted_event(events: &events, new_ev: ev) {
$0.created_at > $1.created_at
}
case .notice(let msg):
print("search home notice: \(msg)")
case .eose(let sub_id):
loading = false
if sub_id == self.base_subid {
// Make sure we unsubscribe after we've fetched the global events
// global events are not realtime
unsubscribe(to: relay_id)
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events, damus_state: damus_state)
}
break
}
case .notice(let msg):
print("search home notice: \(msg)")
case .eose(let sub_id):
loading = false
if sub_id == self.base_subid {
// Make sure we unsubscribe after we've fetched the global events
// global events are not realtime
unsubscribe(to: relay_id)
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events, damus_state: damus_state)
}
break
}
}
}
@@ -112,27 +112,30 @@ func load_profiles(profiles_subid: String, relay_id: String, events: [NostrEvent
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, events: events)
filter.authors = authors
if !authors.isEmpty {
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
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 {
return
}
if ev.known_kind == .metadata {
process_metadata_event(profiles: damus_state.profiles, ev: ev)
}
}
guard done && sid == profiles_subid else {
guard !authors.isEmpty else {
return
}
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
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 {
return
}
print("done loading \(authors.count) profiles from \(relay_id)")
damus_state.pool.unsubscribe(sub_id: profiles_subid, to: [relay_id])
if ev.known_kind == .metadata {
process_metadata_event(profiles: damus_state.profiles, ev: ev)
}
}
guard done && sid == profiles_subid else {
return
}
print("done loading \(authors.count) profiles from \(relay_id)")
damus_state.pool.unsubscribe(sub_id: profiles_subid, to: [relay_id])
}
}
+1 -1
View File
@@ -115,7 +115,7 @@ class ThreadModel: ObservableObject {
ref_events.referenced_ids = ev.referenced_ids.map { $0.ref_id }
ref_events.referenced_ids?.append(ev.id)
ref_events.limit = 50
events_filter.ids = ref_events.referenced_ids!
events_filter.ids = ref_events.referenced_ids ?? []
events_filter.limit = 100
events_filter.ids?.append(ev.id)
case .event_id(let evid):
+32
View File
@@ -0,0 +1,32 @@
//
// UserSettingsStore.swift
// damus
//
// Created by Suhail Saqan on 12/29/22.
//
import Foundation
class UserSettingsStore: ObservableObject {
@Published var default_wallet: Wallet {
didSet {
UserDefaults.standard.set(default_wallet.rawValue, forKey: "default_wallet")
}
}
@Published var show_wallet_selector: Bool {
didSet {
UserDefaults.standard.set(show_wallet_selector, forKey: "show_wallet_selector")
}
}
init() {
if let defaultWalletName = UserDefaults.standard.string(forKey: "default_wallet"),
let default_wallet = Wallet(rawValue: defaultWalletName) {
self.default_wallet = default_wallet
} else {
self.default_wallet = .system_default_wallet
}
self.show_wallet_selector = UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
}
}
+89
View File
@@ -0,0 +1,89 @@
//
// Wallet.swift
// damus
//
// Created by Benjamin Hakes on 12/29/22.
//
import Foundation
enum Wallet: String, CaseIterable, Identifiable {
var id: String { self.rawValue }
struct Model: Identifiable, Hashable {
var id: String { self.tag }
var index: Int
var tag: String
var displayName : String
var link : String
var appStoreLink : String?
var image: String
}
// New url prefixes needed to be added to LSApplicationQueriesSchemes
case system_default_wallet
case strike
case cashapp
case muun
case bluewallet
case walletofsatoshi
case zebedee
case zeusln
case lnlink
case phoenix
case breez
case bitcoinbeach
case blixtwallet
case river
var model: Model {
switch self {
case .system_default_wallet:
return .init(index: -1, tag: "systemdefaultwallet", displayName: NSLocalizedString("Local default", comment: "Dropdown option label for system default for Lightning wallet."),
link: "lightning:", appStoreLink: "lightning:", image: "")
case .strike:
return .init(index: 0, tag: "strike", displayName: NSLocalizedString("Strike", comment: "Dropdown option label for Lightning wallet, Strike."), link: "strike:",
appStoreLink: "https://apps.apple.com/us/app/strike-bitcoin-payments/id1488724463", image: "strike")
case .cashapp:
return .init(index: 1, tag: "cashapp", displayName: NSLocalizedString("Cash App", comment: "Dropdown option label for Lightning wallet, Cash App."), link: "squarecash://",
appStoreLink: "https://apps.apple.com/us/app/cash-app/id711923939", image: "cashapp")
case .muun:
return .init(index: 2, tag: "muun", displayName: NSLocalizedString("Muun", comment: "Dropdown option label for Lightning wallet, Muun."), link: "muun:", appStoreLink: "https://apps.apple.com/us/app/muun-wallet/id1482037683", image: "muun")
case .bluewallet:
return .init(index: 3, tag: "bluewallet", displayName: NSLocalizedString("Blue Wallet", comment: "Dropdown option label for Lightning wallet, Blue Wallet."), link: "bluewallet:lightning:",
appStoreLink: "https://apps.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040", image: "bluewallet")
case .walletofsatoshi:
return .init(index: 4, tag: "walletofsatoshi", displayName: NSLocalizedString("Wallet Of Satoshi", comment: "Dropdown option label for Lightning wallet, Wallet Of Satoshi."), link: "walletofsatoshi:lightning:",
appStoreLink: "https://apps.apple.com/us/app/wallet-of-satoshi/id1438599608", image: "walletofsatoshi")
case .zebedee:
return .init(index: 5, tag: "zebedee", displayName: NSLocalizedString("Zebedee", comment: "Dropdown option label for Lightning wallet, Zebedee."), link: "zebedee:lightning:",
appStoreLink: "https://apps.apple.com/us/app/zebedee-wallet/id1484394401", image: "zebedee")
case .zeusln:
return .init(index: 6, tag: "zeusln", displayName: NSLocalizedString("Zeus LN", comment: "Dropdown option label for Lightning wallet, Zeus LN."), link: "zeusln:lightning:",
appStoreLink: "https://apps.apple.com/us/app/zeus-ln/id1456038895", image: "zeusln")
case .lnlink:
return .init(index: 7, tag: "lnlink", displayName: NSLocalizedString("LNLink", comment: "Dropdown option label for Lightning wallet, LNLink."), link: "lnlink:lightning:",
appStoreLink: "https://testflight.apple.com/join/aNY4yuuZ", image: "lnlink")
case .phoenix:
return .init(index: 8, tag: "phoenix", displayName: NSLocalizedString("Phoenix", comment: "Dropdown option label for Lightning wallet, Phoenix."), link: "phoenix://",
appStoreLink: "https://apps.apple.com/us/app/phoenix-wallet/id1544097028", image: "phoenix")
case .breez:
return .init(index: 9, tag: "breez", displayName: NSLocalizedString("Breez", comment: "Dropdown option label for Lightning wallet, Breez."), link: "breez:",
appStoreLink: "https://apps.apple.com/us/app/breez-lightning-client-pos/id1463604142", image: "breez")
case .bitcoinbeach:
return .init(index: 10, tag: "bitcoinbeach", displayName: NSLocalizedString("Bitcoin Beach", comment: "Dropdown option label for Lightning wallet, Bitcoin Beach."), link: "bitcoinbeach://",
appStoreLink: "https://apps.apple.com/sv/app/bitcoin-beach-wallet/id1531383905", image: "bbw")
case .blixtwallet:
return .init(index: 11, tag: "blixtwallet", displayName: NSLocalizedString("Blixt Wallet", comment: "Dropdown option label for Lightning wallet, Blixt Wallet"), link: "blixtwallet:lightning:",
appStoreLink: nil, image: "blixt-wallet")
case .river:
return .init(index: 12, tag: "river", displayName: NSLocalizedString("River", comment: "Dropdown option label for Lightning wallet, River"), link: "river://",
appStoreLink: "https://apps.apple.com/us/app/river-buy-mine-bitcoin/id1536176542", image: "river")
}
}
static var allModels: [Model] {
return Self.allCases.map { $0.model }
}
}
+7 -5
View File
@@ -446,11 +446,13 @@ func hex_encode(_ data: Data) -> String {
func random_bytes(count: Int) -> Data {
var data = Data(count: count)
_ = data.withUnsafeMutableBytes { mutableBytes in
SecRandomCopyBytes(kSecRandomDefault, count, mutableBytes)
var bytes = [Int8](repeating: 0, count: count)
guard
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] {
@@ -558,7 +560,7 @@ func make_like_event(pubkey: String, privkey: String, liked: NostrEvent) -> Nost
var tags: [[String]] = liked.tags.filter { tag in tag.count >= 2 && (tag[0] == "e" || tag[0] == "p") }
tags.append(["e", liked.id])
tags.append(["p", liked.pubkey])
let ev = NostrEvent(content: "", pubkey: pubkey, kind: 7, tags: tags)
let ev = NostrEvent(content: "🤙", pubkey: pubkey, kind: 7, tags: tags)
ev.calculate_id()
ev.sign(privkey: privkey)
+25
View File
@@ -80,7 +80,32 @@ func parse_nostr_ref_uri(_ p: Parser) -> ReferencedId? {
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? {
if s.starts(with: "https://damus.io/") {
return decode_universal_link(s)
}
var uri = s.replacingOccurrences(of: "nostr://", with: "")
uri = uri.replacingOccurrences(of: "nostr:", with: "")
+11
View File
@@ -11,6 +11,17 @@ enum NostrResponse: Decodable {
case event(String, NostrEvent)
case notice(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 {
var container = try decoder.unkeyedContainer()
+5
View File
@@ -11,6 +11,11 @@ import UIKit
class Profiles {
var profiles: [String: TimestampedProfile] = [:]
var validated: [String: NIP05] = [:]
func is_validated(_ pk: String) -> NIP05? {
return validated[pk]
}
func add(id: String, profile: TimestampedProfile) {
profiles[id] = profile
+1 -1
View File
@@ -38,7 +38,7 @@ class RelayConnection: WebSocketDelegate {
self.connect(force: true)
}
}
func connect(force: Bool = false){
if !force && (self.isConnected || self.isConnecting) {
return
+57 -2
View File
@@ -28,9 +28,20 @@ struct RelayHandler {
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 {
var relays: [Relay] = []
var handlers: [RelayHandler] = []
var request_queue: [QueuedRequest] = []
var descriptors: [RelayDescriptor] {
relays.map { $0.descriptor }
@@ -148,13 +159,38 @@ class RelayPool {
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) {
let relays = to.map{ get_relays($0) } ?? self.relays
for relay in relays {
if relay.connection.isConnected {
relay.connection.send(req)
guard relay.connection.isConnected else {
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) {
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?
for handler in handlers {
handler.callback(relay_id, event)
+1 -16
View File
@@ -11,7 +11,7 @@ public class Constants {
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 = [
NostrEvent(id: UUID().description, content: "Nostr - Damus... Haha get it? Bonjour Le Monde mon Ami! C'est la tres importante", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
@@ -24,19 +24,4 @@ public class Constants {
NostrEvent(id: UUID().description, content: "Hello World! This is so cool!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "Bonjour Le Monde", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
]
// New url prefixes needed to be added to LSApplicationQueriesSchemes
static let WALLETS = """
[
{"id": 0, "name": "Strike", "link": "strike:", "appStoreLink": "https://apps.apple.com/us/app/strike-bitcoin-payments/id1488724463", "image": "strike"},
{"id": 1, "name": "Cash App", "link": "squarecash://", "appStoreLink": "https://apps.apple.com/us/app/cash-app/id711923939", "image": "cashapp"},
{"id": 2, "name": "Muun", "link": "muun:", "appStoreLink": "https://apps.apple.com/us/app/muun-wallet/id1482037683", "image": "muun"},
{"id": 3, "name": "Blue Wallet", "link": "bluewallet:lightning:", "appStoreLink": "https://apps.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040", "image": "bluewallet"},
{"id": 4, "name": "Wallet Of Satoshi", "link": "walletofsatoshi:lightning:", "appStoreLink": "https://apps.apple.com/us/app/wallet-of-satoshi/id1438599608", "image": "walletofsatoshi"},
{"id": 5, "name": "Zebedee", "link": "zebedee:lightning:", "appStoreLink": "https://apps.apple.com/us/app/zebedee-wallet/id1484394401", "image": "zebedee"},
{"id": 6, "name": "Zeus LN", "link": "zeusln:lightning:", "appStoreLink": "https://apps.apple.com/us/app/zeus-ln/id1456038895", "image": "zeusln"},
{"id": 7, "name": "LNLink", "link": "lnlink:lightning:", "appStoreLink": "https://testflight.apple.com/join/aNY4yuuZ", "image": "lnlink"},
{"id": 8, "name": "Phoenix", "link": "phoenix://", "appStoreLink": "https://apps.apple.com/us/app/phoenix-wallet/id1544097028", "image": "phoenix"},
]
""".data(using: .utf8)!
}
+11 -13
View File
@@ -12,26 +12,24 @@ class CustomLinkView: LPLinkView {
override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
}
enum Metadata {
case linkmeta(LPLinkMetadata)
case url(URL)
}
struct LinkViewRepresentable: UIViewRepresentable {
typealias UIViewType = CustomLinkView
var metadata: LPLinkMetadata?
var url: URL?
let meta: Metadata
func makeUIView(context: Context) -> CustomLinkView {
if let metadata {
let linkView = CustomLinkView(metadata: metadata)
return linkView
switch meta {
case .linkmeta(let linkmeta):
return CustomLinkView(metadata: linkmeta)
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) {
+47
View File
@@ -0,0 +1,47 @@
//
// Markdown.swift
// damus
//
// Created by Lionello Lunesu on 2022-12-28.
//
import Foundation
public struct Markdown {
private let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
/// Ensure the specified URL has a scheme by prepending "https://" if it's absent.
static func withScheme(_ url: any StringProtocol) -> any StringProtocol {
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 {
let md_opts: AttributedString.MarkdownParsingOptions =
.init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
// TODO: escape unintentional markdown
let escaped = content.replacingOccurrences(of: "\\_", with: "\\\\\\_")
if let txt = try? AttributedString(markdown: escaped, options: md_opts) {
return txt
} else {
return AttributedString(stringLiteral: content)
}
}
/// Process the input text and add markdown for any embedded URLs.
public func process(_ input: String) -> AttributedString {
let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
var output = input
// Start with the last match, because replacing the first would invalidate all subsequent indices
for match in matches.reversed() {
guard let range = Range(match.range, in: input)
, let url = match.url else { continue }
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))")
}
return Markdown.parse(content: output)
}
}
+65
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
}
+35
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
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
}
}
+20 -12
View File
@@ -7,40 +7,48 @@
import Foundation
public func time_ago_since(_ date: Date) -> String {
public func time_ago_since(_ date: Date, _ calendar: Calendar = Calendar.current) -> String {
let calendar = Calendar.current
let now = Date()
let unitFlags: NSCalendar.Unit = [.second, .minute, .hour, .day, .weekOfYear, .month, .year]
let unitFlags: NSCalendar.Unit = [.second, .minute, .hour, .day, .weekOfMonth, .month, .year]
let components = (calendar as NSCalendar).components(unitFlags, from: date, to: now, options: [])
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .abbreviated
formatter.maximumUnitCount = 1
formatter.allowedUnits = unitFlags
// Manually format date component from only the most significant time unit because
// DateComponentsFormatter rounds up by default.
if let year = components.year, year >= 1 {
return "\(year)yr"
return formatter.string(from: DateComponents(calendar: calendar, year: year))!
}
if let month = components.month, month >= 1 {
return "\(month)mth"
return formatter.string(from: DateComponents(calendar: calendar, month: month))!
}
if let week = components.weekOfYear, week >= 1 {
return "\(week)wk"
if let week = components.weekOfMonth, week >= 1 {
return formatter.string(from: DateComponents(calendar: calendar, weekOfMonth: week))!
}
if let day = components.day, day >= 1 {
return "\(day)d"
return formatter.string(from: DateComponents(calendar: calendar, day: day))!
}
if let hour = components.hour, hour >= 1 {
return "\(hour)h"
return formatter.string(from: DateComponents(calendar: calendar, hour: hour))!
}
if let minute = components.minute, minute >= 1 {
return "\(minute)m"
return formatter.string(from: DateComponents(calendar: calendar, minute: minute))!
}
if let second = components.second, second >= 3 {
return "\(second)s"
return formatter.string(from: DateComponents(calendar: calendar, second: second))!
}
return "now"
return NSLocalizedString("now", comment: "String indicating that a given timestamp just occurred")
}
+1
View File
@@ -58,6 +58,7 @@ struct AddRelayView: View {
Button("Add") {
show_add_relay = false
action(relay)
relay = ""
}
.buttonStyle(.borderedProminent)
.contentShape(Rectangle())
+3 -3
View File
@@ -86,7 +86,7 @@ struct ChatView: View {
VStack(alignment: .leading) {
if just_started {
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)
//.shadow(color: Color.black, radius: 2)
Text("\(format_relative_time(event.created_at))")
@@ -96,7 +96,7 @@ struct ChatView: View {
if let ref_id = thread.replies.lookup(event.id) {
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)
.environmentObject(thread)
.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 {
let bar = make_actionbar_model(ev: event, damus: damus_state)
+39 -20
View File
@@ -6,6 +6,7 @@
//
import AVFoundation
import SwiftUI
import Kingfisher
struct ConfigView: View {
let state: DamusState
@@ -17,8 +18,8 @@ struct ConfigView: View {
@State var privkey: String
@State var privkey_copied: Bool = false
@State var pubkey_copied: Bool = false
@State var relays: [RelayDescriptor]
@EnvironmentObject var user_settings: UserSettingsStore
let generator = UIImpactFeedbackGenerator(style: .light)
@@ -54,10 +55,19 @@ struct ConfigView: View {
var body: some View {
ZStack(alignment: .leading) {
Form {
Section("Relays") {
Section {
List(Array(relays), id: \.url) { relay in
RelayView(state: state, relay: relay.url.absoluteString)
}
} header: {
HStack {
Text("Relays")
Spacer()
Button(action: { show_add_relay = true }) {
Image(systemName: "plus")
.foregroundColor(.accentColor)
}
}
}
Section("Recommended Relays") {
@@ -93,26 +103,31 @@ struct ConfigView: View {
}
}
Section("Wallet Selector") {
Toggle("Show wallet selector", isOn: $user_settings.show_wallet_selector).toggleStyle(.switch)
Picker("Select default wallet",
selection: $user_settings.default_wallet) {
ForEach(Wallet.allCases, id: \.self) { wallet in
Text(wallet.model.displayName)
.tag(wallet.model.tag)
}
}
}
Section("Clear Cache") {
Button("Clear") {
KingfisherManager.shared.cache.clearMemoryCache()
KingfisherManager.shared.cache.clearDiskCache()
KingfisherManager.shared.cache.cleanExpiredDiskCache()
}
}
Section("Reset") {
Button("Logout") {
confirm_logout = true
}
}
}
VStack {
HStack {
Spacer()
Button(action: { show_add_relay = true }) {
Label("", systemImage: "plus")
.foregroundColor(.accentColor)
.padding()
}
}
Spacer()
}
}
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.large)
@@ -128,14 +143,18 @@ struct ConfigView: View {
}
.sheet(isPresented: $show_add_relay) {
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
}
if relay.starts(with: "wss://") == false {
relay = "wss://" + relay
}
guard let url = URL(string: relay) else {
return
}
guard let ev = state.contacts.event else {
return
}
@@ -150,9 +169,9 @@ struct ConfigView: View {
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
}
+8 -8
View File
@@ -40,26 +40,26 @@ struct CreateAccountView: View {
}
VStack {
SignupForm {
FormLabel("Username")
FormLabel(NSLocalizedString("Username", comment: "Label to prompt username entry."))
HStack(spacing: 0.0) {
Text("@")
.foregroundColor(.white)
.padding(.leading, -25.0)
FormTextInput("satoshi", text: $account.nick_name)
FormTextInput(NSLocalizedString("satoshi", comment: "Example username of Bitcoin creator(s), Satoshi Nakamoto."), text: $account.nick_name)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
FormLabel("Display Name", optional: true)
FormTextInput("Satoshi Nakamoto", text: $account.real_name)
FormLabel(NSLocalizedString("Display Name", comment: "Label to prompt display name entry."), optional: true)
FormTextInput(NSLocalizedString("Satoshi Nakamoto", comment: "Name of Bitcoin creator(s)."), text: $account.real_name)
.textInputAutocapitalization(.words)
FormLabel("About", optional: true)
FormTextInput("Creator(s) of Bitcoin. Absolute legend.", text: $account.about)
FormLabel(NSLocalizedString("About", comment: "Label to prompt for about text entry for user to describe about themself."), optional: true)
FormTextInput(NSLocalizedString("Creator(s) of Bitcoin. Absolute legend.", comment: "Example description about Bitcoin creator(s), Satoshi Nakamoto."), text: $account.about)
FormLabel("Account ID")
FormLabel(NSLocalizedString("Account ID", comment: "Label to indicate the public ID of the account."))
.onTapGesture {
regen_key()
}
@@ -75,7 +75,7 @@ struct CreateAccountView: View {
NavigationLink(destination: SaveKeysView(account: account), isActive: $is_done) {
EmptyView()
}
DamusWhiteButton("Create") {
DamusWhiteButton(NSLocalizedString("Create", comment: "Button to create account.")) {
self.is_done = true
}
.padding()
+5 -1
View File
@@ -27,6 +27,10 @@ struct DMChatView: View {
}
.onAppear {
scroller.scrollTo("endblock")
}.onChange(of: dms.events.count) { _ in
withAnimation {
scroller.scrollTo("endblock")
}
}
}
}
@@ -40,7 +44,7 @@ struct DMChatView: View {
HStack {
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())
+1 -1
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)
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)
.padding(10)
.background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15))
+15 -6
View File
@@ -9,10 +9,19 @@ import SwiftUI
struct DirectMessagesView: View {
let damus_state: DamusState
@State var open_dm: Bool = false
@State var pubkey: String = ""
@State var active_model: DirectMessageModel = DirectMessageModel()
@EnvironmentObject var model: DirectMessagesModel
var MainContent: some View {
ScrollView {
let chat = DMChatView(damus_state: damus_state, pubkey: pubkey)
.environmentObject(active_model)
NavigationLink(destination: chat, isActive: $open_dm) {
EmptyView()
}
LazyVStack {
if model.dms.isEmpty, !model.loading {
EmptyTimelineView()
@@ -30,12 +39,12 @@ struct DirectMessagesView: View {
func MaybeEvent(_ tup: (String, DirectMessageModel)) -> some View {
Group {
if let ev = tup.1.events.last {
let chat = DMChatView(damus_state: damus_state, pubkey: tup.0)
.environmentObject(tup.1)
NavigationLink(destination: chat) {
EventView(damus: damus_state, event: ev, pubkey: tup.0, show_friend_icon: true)
}
.buttonStyle(PlainButtonStyle())
EventView(damus: damus_state, event: ev, pubkey: tup.0, show_friend_icon: true)
.onTapGesture {
pubkey = tup.0
active_model = tup.1
open_dm = true
}
} else {
EmptyView()
}
+5 -14
View File
@@ -15,11 +15,6 @@ func isHttpsUrl(_ string: String) -> Bool {
return urlTest.evaluate(with: string)
}
struct NIP05 {
let username: String
let host: String
}
func isImage(_ urlString: String) -> Bool {
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? {
let parts = nip05.split(separator: "@")
guard parts.count == 2 else {
return nil
}
return NIP05(username: String(parts[0]), host: String(parts[1]))
return NIP05.parse(nip05)
}
var body: some View {
@@ -142,7 +133,7 @@ struct EditMetadataView: View {
}
Section("About Me") {
let placeholder = "Absolute Boss"
let placeholder = NSLocalizedString("Absolute Boss", comment: "Placeholder text for About Me description.")
ZStack(alignment: .topLeading) {
TextEditor(text: $about)
.textInputAutocapitalization(.sentences)
@@ -169,12 +160,12 @@ struct EditMetadataView: View {
Text("NIP-05 Verification")
}, footer: {
if let parts = nip05_parts {
Text("'\(parts.username)' at '\(parts.host)' will be used for verification")
Text(String(format: NSLocalizedString("'%@' at '%@' will be used for verification", comment: "Description of how the nip05 identifier would be used for verification."), parts.username, parts.host))
} else {
Text("'\(nip05)' is an invalid nip05 identifier. It should look like an email.")
Text(String(format: NSLocalizedString("'%@' is an invalid nip05 identifier. It should look like an email.", comment: "Description of why the nip05 identifier is invalid."), nip05))
}
})
Button("Save") {
save()
dismiss()
+37 -3
View File
@@ -24,6 +24,7 @@ struct EventActionBar: View {
let generator = UIImpactFeedbackGenerator(style: .medium)
@State var sheet: ActionBarSheet? = nil
@State var confirm_boost: Bool = false
@State var show_share_sheet: Bool = false
@StateObject var bar: ActionBarModel
var body: some View {
@@ -40,6 +41,7 @@ struct EventActionBar: View {
EventActionButton(img: "bubble.left", col: nil) {
notify(.reply, event)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
HStack(alignment: .bottom) {
@@ -55,13 +57,14 @@ struct EventActionBar: View {
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
HStack(alignment: .bottom) {
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
.font(.footnote.weight(.medium))
.foregroundColor(bar.liked ? Color.red : Color.gray)
.foregroundColor(bar.liked ? Color.orange : Color.gray)
EventActionButton(img: bar.liked ? "heart.fill" : "heart", col: bar.liked ? Color.red : nil) {
LikeButton(liked: bar.liked) {
if bar.liked {
notify(.delete, bar.our_like)
} else {
@@ -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) {
@@ -86,6 +95,13 @@ struct EventActionBar: View {
}
*/
}
.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("Boost", isPresented: $confirm_boost) {
Button("Cancel") {
confirm_boost = false
@@ -142,7 +158,25 @@ func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) ->
.font(.footnote.weight(.medium))
.foregroundColor(col == nil ? Color.gray : col!)
}
.padding(.trailing, 40)
}
struct LikeButton: View {
let liked: Bool
let action: () -> ()
@Environment(\.colorScheme) var colorScheme
var body: some View {
Button(action: action) {
if liked {
Text("🤙")
} else {
Image("shaka")
.renderingMode(.template)
.foregroundColor(.gray)
}
}
}
}
+33 -71
View File
@@ -103,12 +103,13 @@ struct BuilderEventView: View {
var body: some View {
VStack {
if event == nil {
ProgressView().padding()
} else {
NavigationLink(destination: BuildThreadV2View(damus: damus, event_id: event!.id)) {
EventView(damus: damus, event: event!, show_friend_icon: true, size: .small, embedded: true)
if let event = event {
let ev = event.inner_event ?? event
NavigationLink(destination: BuildThreadV2View(damus: damus, event_id: ev.id)) {
EventView(damus: damus, event: event, show_friend_icon: true, size: .small)
}.buttonStyle(.plain)
} else {
ProgressView().padding()
}
}
.frame(minWidth: 0, maxWidth: .infinity)
@@ -128,11 +129,10 @@ struct EventView: View {
let pubkey: String
let show_friend_icon: Bool
let size: EventViewKind
let embedded: Bool
@EnvironmentObject var action_bar: ActionBarModel
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal, 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.highlight = highlight
self.has_action_bar = has_action_bar
@@ -140,10 +140,9 @@ struct EventView: View {
self.pubkey = event.pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal) {
self.event = event
self.highlight = .none
self.has_action_bar = false
@@ -151,7 +150,6 @@ struct EventView: View {
self.pubkey = event.pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
@@ -162,7 +160,6 @@ struct EventView: View {
self.pubkey = pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
var body: some View {
@@ -179,7 +176,7 @@ struct EventView: View {
Image(systemName: "arrow.2.squarepath")
.font(.footnote.weight(.bold))
.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))
.foregroundColor(Color.gray)
Text("Boosted")
@@ -209,10 +206,8 @@ struct EventView: View {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
if !embedded {
NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
}
NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
}
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 {
Text("\(format_relative_time(event.created_at))")
.font(eventviewsize_to_font(size))
@@ -249,55 +244,28 @@ struct EventView: View {
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)
.allowsHitTesting(!embedded)
if !embedded {
let blocks = event.blocks(damus.keypair.privkey).filter { block in
guard case .mention(let mention) = block else {
return false
}
if has_action_bar {
if size == .selected {
Text("\(format_date(event.created_at))")
.padding(.top, 10)
.font(.footnote)
.foregroundColor(.gray)
guard case .event = mention.type else {
return false
}
if mention.ref.key != "e" {
return false
}
return true
Divider()
.padding([.bottom], 4)
} else {
Rectangle().frame(height: 2).opacity(0)
}
/// MARK: - Preview
if let firstBlock = blocks.first, case .mention(let mention) = firstBlock, mention.ref.key == "e" {
BuilderEventView(damus: damus, event_id: mention.ref.id)
}
let bar = make_actionbar_model(ev: event, damus: damus)
EventActionBar(damus_state: damus, event: event, bar: bar)
}
if !embedded {
if has_action_bar {
if size == .selected {
Text("\(format_date(event.created_at))")
.padding(.top, 10)
.font(.footnote)
.foregroundColor(.gray)
Divider()
.padding([.bottom], 4)
} else {
Rectangle().frame(height: 2).opacity(0)
}
let bar = make_actionbar_model(ev: event, damus: damus)
EventActionBar(damus_state: damus, event: event, bar: bar)
}
Divider()
.padding([.top], 4)
}
Divider()
.padding([.top], 4)
}
.padding([.leading], 2)
}
@@ -401,7 +369,7 @@ func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
let n = desc.others
if desc.pubkeys.count == 0 {
return "Reply to self"
return NSLocalizedString("Reply to self", comment: "Label to indicate that the user is replying to themself.")
}
let names: [String] = pubkeys.map {
@@ -411,20 +379,14 @@ func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
if names.count == 2 {
if n > 2 {
let and_other = reply_others_desc(n: n, n_pubkeys: pubkeys.count)
return "Replying to \(names[0]), \(names[1])\(and_other)"
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 "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 and_other = reply_others_desc(n: n, n_pubkeys: pubkeys.count)
return "Replying to \(names[0])\(and_other)"
}
func reply_others_desc(n: Int, n_pubkeys: Int) -> String {
let other = n - n_pubkeys
let plural = other == 1 ? "" : "s"
return n > 1 ? " & \(other) other\(plural)" : ""
let othersCount = n - pubkeys.count
return String(format: NSLocalizedString("replying_to_one_and_others", comment: "Label to indicate that the user is replying to 1 user and others."), names[0], othersCount)
}
+10 -5
View File
@@ -19,10 +19,11 @@ struct FollowButtonView: View {
follow_state = perform_follow_btn_action(follow_state, target: target)
} label: {
Text(follow_btn_txt(follow_state))
.frame(height: 30)
.padding(.horizontal, 25)
.padding(.vertical, 10)
//.padding(.vertical, 10)
.font(.caption.weight(.bold))
.foregroundColor(follow_state == .unfollows ? emptyColor() : fillColor())
.foregroundColor(follow_state == .unfollows ? filledTextColor() : borderColor())
.background(follow_state == .unfollows ? fillColor() : emptyColor())
.cornerRadius(20)
.overlay {
@@ -48,16 +49,20 @@ struct FollowButtonView: View {
}
}
func filledTextColor() -> Color {
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
}
func fillColor() -> Color {
colorScheme == .light ? .black : .white
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
}
func emptyColor() -> Color {
colorScheme == .light ? .white : .black
Color.black.opacity(0)
}
func borderColor() -> Color {
colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2)
colorScheme == .light ? Color("DamusDarkGrey") : Color("DamusLightGrey")
}
}
+6 -4
View File
@@ -10,7 +10,9 @@ import SwiftUI
struct FollowUserView: View {
let target: FollowTarget
let damus_state: DamusState
static let markdown = Markdown()
var body: some View {
HStack {
let pmodel = ProfileModel(pubkey: target.pubkey, damus: damus_state)
@@ -22,9 +24,9 @@ struct FollowUserView: View {
VStack(alignment: .leading) {
let profile = damus_state.profiles.lookup(id: target.pubkey)
ProfileName(pubkey: target.pubkey, profile: profile, contacts: damus_state.contacts, show_friend_confirmed: false)
if let about = profile.flatMap { $0.about } {
Text(about)
ProfileName(pubkey: target.pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false)
if let about = profile?.about {
Text(FollowUserView.markdown.process(about))
.lineLimit(3)
.font(.footnote)
}
+29 -21
View File
@@ -41,11 +41,11 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
if is_image_url(url) {
// Append Image
img_urls.append(url)
return str
} else {
link_urls.append(url)
return str + url.absoluteString
}
return str
}
}
@@ -61,26 +61,20 @@ struct NoteContentView: View {
let privkey: String?
let event: NostrEvent
let profiles: Profiles
let previews: PreviewCache
let show_images: Bool
@State var artifacts: NoteArtifacts
@State var metaData: LPLinkMetadata? = nil
@State var preview: LinkViewRepresentable? = nil
let size: EventViewKind
func MainContent() -> some View {
let md_opts: AttributedString.MarkdownParsingOptions =
.init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
return VStack(alignment: .leading) {
if let txt = try? AttributedString(markdown: artifacts.content, options: md_opts) {
Text(txt)
.font(eventviewsize_to_font(size))
} else {
Text(artifacts.content)
.font(eventviewsize_to_font(size))
}
Text(Markdown.parse(content: artifacts.content))
.font(eventviewsize_to_font(size))
if show_images && artifacts.images.count > 0 {
ImageCarousel(urls: artifacts.images)
} else if !show_images && artifacts.images.count > 0 {
@@ -96,12 +90,14 @@ struct NoteContentView: View {
InvoicesView(invoices: artifacts.invoices)
}
if show_images, self.metaData != nil {
LinkViewRepresentable(metadata: self.metaData)
if show_images, self.preview != nil {
self.preview
} else {
ForEach(artifacts.links, id:\.self) { link in
LinkViewRepresentable(url: link)
.frame(height: 50)
if let url = link {
LinkViewRepresentable(meta: .url(url))
.frame(height: 50)
}
}
}
}
@@ -109,7 +105,6 @@ struct NoteContentView: View {
var body: some View {
MainContent()
.animation(.easeInOut, value: metaData)
.onAppear() {
self.artifacts = render_note_content(ev: event, profiles: profiles, privkey: privkey)
}
@@ -130,9 +125,22 @@ struct NoteContentView: View {
}
}
.task {
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
}
}
if show_images, artifacts.links.count == 1 {
let meta = await getMetaData(for: artifacts.links.first!)
self.metaData = 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
}
}
}
@@ -175,8 +183,8 @@ func mention_str(_ m: Mention, profiles: Profiles) -> String {
struct NoteContentView_Previews: PreviewProvider {
static var previews: some View {
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: [])
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)
}
}
+20 -12
View File
@@ -8,21 +8,29 @@
import Foundation
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 {
return Button(action: action, label: {
Text("+")
.font(.system(.largeTitle))
.frame(width: 57, height: 50)
.foregroundColor(Color.white)
.padding(.bottom, 7)
ZStack(alignment: .center) {
Circle()
.fill(LINEAR_GRADIENT)
.frame(width: BUTTON_SIZE, height: BUTTON_SIZE, alignment: .center)
.rotationEffect(.degrees(20))
.padding()
.shadow(color: Color.black.opacity(0.3),
radius: 3,
x: 3,
y: 3)
Image(systemName: "plus")
.font(.system(.title2))
.foregroundColor(Color.white)
}
})
.background(Color.blue)
.cornerRadius(38.5)
.padding()
.shadow(color: Color.black.opacity(0.3),
radius: 3,
x: 3,
y: 3)
.keyboardShortcut("n", modifiers: [.command, .shift])
}
+1 -1
View File
@@ -12,7 +12,7 @@ enum NostrPostResult {
case cancel
}
let POST_PLACEHOLDER = "Type your post here..."
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
struct PostView: View {
@State var post: String = ""

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