Compare commits

..

128 Commits

Author SHA1 Message Date
6870978e49 Fix string bugs 2023-01-14 15:30:19 -05:00
William Casarin
bcb40a6ec7 Fix text getting truncated next to link previews
Changelog-Fixed: Fix text getting truncated next to link previews
2023-01-14 09:30:22 -08:00
Swift
2f1063b49f Resolve issue on Roboash not appearing in sidebar pfp
Also works fine with users with profile pic url set

Closes: #315
2023-01-14 09:00:59 -08:00
Swift
73110952e5 Avoid showing wrong alert message for pubkey login user. 2023-01-14 08:58:27 -08:00
d01e7c0595 Fix localization bugs and export localizations
Changelog-Fixes: Fix some localization bugs
Closes: #320
2023-01-14 08:54:43 -08:00
William Casarin
4f9cef541b v1.0.0-6 changelog 2023-01-13 11:30:20 -08:00
William Casarin
2dfbc4b57e v1.0.0-6 2023-01-13 11:29:06 -08:00
William Casarin
b9750dab77 Rename Boost to Repost 2023-01-13 10:18:03 -08:00
William Casarin
d59331bc3c Only show EventDetailBar if we have tips/likes/reposts 2023-01-13 10:17:12 -08:00
Jason Jōb
abd5856f21 Fix bug where all banner images showed as the current users
Fixes: #313
2023-01-13 09:20:51 -08:00
Joel Klabo
42a475bd72 Purple Shaka icon
Changelog-Changed: Make Shaka button purple when liked
2023-01-12 11:50:57 -08:00
William Casarin
65c1325935 Add some eventbar previews 2023-01-12 11:48:06 -08:00
Joel Klabo
db64a73f87 Shaka button improvements
Changelog-Changed: Move counts to right side like Birdsite
Changelog-Changed: Use custom icon for shaka button
Changelog-Fixed: Fixed shaka moving when you press it
2023-01-12 11:40:54 -08:00
Jason Jōb
9d44ed0bfe Profile Banner Images
Changelog-Added: Profile banner images
Closes: #302
2023-01-12 10:51:32 -08:00
William Casarin
33383265c8 Fix horrible reactions view bug 2023-01-12 09:40:28 -08:00
Joel Klabo
76d1ee34d8 Add CI Testing
Closes: #304
2023-01-12 09:32:18 -08:00
punterwantsawhalepass
3b0a84bd43 Update README.md
Closes: #305
2023-01-12 09:32:18 -08:00
William Casarin
5a1daebeca Revert "feat(ci): Publish PR notifications to Nostr"
This reverts commit aee29f145a.
2023-01-12 09:32:18 -08:00
08666ff90d Fix spacing on transifex.yml
Closes: #307
2023-01-12 09:32:18 -08:00
William Casarin
b2b790a969 Reactions View
Changelog-Added: Added Reactions View
2023-01-11 16:13:09 -08:00
William Casarin
907f0d236f Rename boost to repost and removed nip05 domain from them
Changelog-Changed: Renamed boost to repost
Changelog-Changed: Removed nip05 domain from boosts/reposts
2023-01-11 14:49:14 -08:00
William Casarin
4b8f536a9b Refactor NIP05 badge into its own view
Also only make it clickable in profile view
2023-01-11 14:48:35 -08:00
Joel Klabo
c76f92c6ed Move Counts to Right Side Like Twitter 2023-01-10 19:36:25 -08:00
Joel Klabo
a165f4281c Use Overlay for Label to Prevent Views Moving 2023-01-10 18:51:22 -08:00
Joel Klabo
5dbf8029da Update Shaka Assets and Use for EventActionBar 2023-01-10 18:51:20 -08:00
Joel Klabo
b1885700ca Use A Disabled Version of Shaka to Prevent Size Change 2023-01-10 18:50:15 -08:00
Joel Klabo
9e5209b48d Hide the NIP-05 Domain in Follow List
Changelog-Chaged: Hide the NIP-05 Domain in Follow List
Closes: #267
2023-01-10 11:09:44 -08:00
William Casarin
6bcea13d5a minor cleanup from merge 2023-01-10 10:20:42 -08:00
William Casarin
9944a7bf2a sidebar: rename App settings to Settings
Also localize
2023-01-10 10:20:42 -08:00
Darrell
424fb55343 remove unused packages
Shimmer package was added to package dependencies in #56 but Shimmer.swift in Components was used instead.
2023-01-10 10:11:54 -08:00
Paul
901931443f Update README.md
The backticks around the code  markdown example need escaped with two backticks and a space.
2023-01-10 10:11:32 -08:00
Ben Weeks
40d11ad680 Add a Sidebar
Changlog-Added: Sidebar
Closes: #273
2023-01-10 10:08:20 -08:00
Jonathan Milligan
4d8088d0d0 feat: Add left handed option for post button
Added a left handed toggle in the app configuration section to turn on
left handed mode which moves the post button to the left side. Nearly
done. May just need to fix an initialization bug.

Closes: #282
Changelog-Added: Left hand option for post button
2023-01-10 09:46:23 -08:00
bndw
aee29f145a feat(ci): Publish PR notifications to Nostr
Sends pull request notifications to Nostr using Github Workflows and
noscl.

Closes: #297
2023-01-10 09:24:19 -08:00
Jonathan Milligan
c9463804a8 style: DMs only take up 80% of screen width
DMs now take up only 80% of the available screen width so it looks much
more like iMessages.

Closes: #286
Changelog-Changed: Make DMs only take up 80% of screen width
2023-01-10 09:24:19 -08:00
Ben Weeks
dccaf35410 Uses damus image on the homepage
Closes: #291
Changelog-Added: Damus icon at the top
2023-01-10 09:24:18 -08:00
Joel Klabo
45e64dc42c Hide Recommended Relays Section if Empty
Closes: #293
Changelog-Changed: Hide Recommended Relays Section if Empty
2023-01-10 09:23:44 -08:00
OlegAba
5a32a384c4 Add fallback image url to profile pic view
Closes: #294
Changlog-Changed: Add fallback image url to profile pic view
2023-01-10 09:22:57 -08:00
Fredrik Olofsson
35ae69740a Get event after Add button is pressed to make sure it is up to date.
Closes: #296
Changelog-Fixed: Fixed issue with relays not keeping in sync when adding
2023-01-10 09:22:55 -08:00
Joel Klabo
43cc3b6b6b Make NIP-05 Domain Tappable
Closes: #275
Changelog-Added: Make purple badges on profile page tappable
2023-01-10 09:22:20 -08:00
b367382aae Export localizations and add Transifex config 2023-01-08 22:01:06 -05:00
bf1866056c Change development locale to en-US and add es-419 as a target locale 2023-01-08 21:57:38 -05:00
016dfa54f3 Internationalize amount formatting for sats 2023-01-08 21:57:38 -05:00
887eb902bf Add comments to localized strings 2023-01-08 21:57:28 -05:00
Ben Weeks
966f330317 Added hidden dummy code for future options. 2023-01-08 23:32:35 +00:00
Ben Weeks
cb54ac0494 Updated sidebar to close when tab items at the bottom are clicked too. 2023-01-08 01:00:35 +00:00
Ben Weeks
fac1911524 Undid change to navigation appearing above the tabView as unfortunately has consequence hadn't spotted of the tab view dropping from profile page and settings page. 2023-01-08 00:38:02 +00:00
dcf328e7ac Fix localization bugs 2023-01-07 19:36:15 -05:00
Ben Weeks
38211a74f5 Moved the sidebar to above the tab buttons. 2023-01-08 00:26:35 +00:00
Ben Weeks
9b236f9583 Changed opacity to Damus on-brand grey. Removed unnecessary code. 2023-01-08 00:20:43 +00:00
Ben Weeks
4378397c4c Fixed animation. Made menu a touch smaller. 2023-01-07 23:59:35 +00:00
Ben Weeks
392281ca40 Removed unnecessary code. 2023-01-07 23:33:57 +00:00
Ben Weeks
3bf39dc04b Fixed overlay issue (now has faded background) 2023-01-07 23:29:22 +00:00
Ben Weeks
8161d55d05 Updated colors and swapped icons. 2023-01-07 22:31:45 +00:00
Ben Weeks
30151e14fa Merge branch 'damus-io:master' into sidebar 2023-01-07 22:15:11 +00:00
Ben Weeks
c6ca0a058e Updated text color to white and swapped icons round 2023-01-07 22:13:44 +00:00
William Casarin
c10fcc52e3 dms: always use the other persons pubkey in context menu
The DM view can sometimes show our last message, which means the pubkey
in the context menu would be ours. This is super confusing, so always
use the other persons pubkey when copying the user id in the DMs view.

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

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

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

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

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

Added contents.json imageset file

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

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

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

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

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

17
.github/workflows/run-tests.yaml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Test
on:
push:
branches:
- "master"
pull_request:
branches:
- "*"
jobs:
test:
name: Run Tests
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v1
- name: Running Tests
run: xcodebuild test -scheme damus -project damus.xcodeproj -destination 'platform=iOS Simulator,name=iPhone 14,OS=16.0' | xcpretty && exit ${PIPESTATUS[0]}

View File

@@ -1,3 +1,120 @@
## [1.0.0-6] - 2023-01-13
### Added
- Profile banner images (Jason Jōb)
- Added Reactions View (William Casarin)
- Left hand option for post button (Jonathan Milligan)
- Damus icon at the top (Ben Weeks)
- Make purple badges on profile page tappable (Joel Klabo)
### Changed
- Make Shaka button purple when liked (Joel Klabo)
- Move counts to right side like Birdsite (Joel Klabo)
- Use custom icon for shaka button (Joel Klabo)
- Renamed boost to repost (William Casarin)
- Removed nip05 domain from boosts/reposts (William Casarin)
- Make DMs only take up 80% of screen width (Jonathan Milligan)
- Hide Recommended Relays Section if Empty (Joel Klabo)
### Fixed
- Fixed shaka moving when you press it (Joel Klabo)
- Fixed issue with relays not keeping in sync when adding (Fredrik Olofsson)
[1.0.0-6]: https://github.com/damus-io/damus/releases/tag/v1.0.0-6
## [1.0.0-5] - 2023-01-06
### Added
- Added share button to profile (William Casarin)
- Added universal link sharing of notes (William Casarin)
- Added clear cache button to wipe pfp/image cache (OlegAba)
- Allow Adding Relay Without wss:// Prefix (Joel Klabo)
- Allow Saving Images to Library (Joel Klabo)
### Changed
- Added damus gradient to post button (Ben Weeks)
- Center the Post Button (Thomas)
- Switch yellow nip05 check to gray (William Casarin)
- Switch from bluecheck to purplecheck (William Casarin)
### Fixed
- Add system background color to profile pics (OlegAba)
- High res color pubkey on profile page (William Casarin)
- Don't spin forever if we're temporarily disconnected (William Casarin)
- Fixed a few issues with avatars not animating (OlegAba)
- Scroll to bottom when new DM received (Aidan O'Loan)
- Make reply view scrollable (Joel Klabo)
- Hide profile edit button when logged in with pubkey (Swift)
[1.0.0-5]: https://github.com/damus-io/damus/releases/tag/v1.0.0-5
## [1.0.0-4] - 2023-01-04
### Added
- Added NIP05 Verification (William Casarin)
- Downscale images if they are unreasonably large (OlegAba)
### Changed
- Revert to old style ln/dm buttons (William Casarin)
### Fixed
- Fix ascii shrug guy (Lionello Lunesu)
- Fix navigation popping in threads (William Casarin)
[1.0.0-4]: https://github.com/damus-io/damus/releases/tag/v1.0.0-4
## [1.0.0-2] - 2023-01-03
### Added
- Cache link previews (William Casarin)
- Added brb.io to recommended relay list (William Casarin)
- Add Blixt Wallet to Wallet Selector (Benjamin Hakes)
- Add River Wallet to Wallet Selector (Benjamin Hakes)
### Changed
- Added muted shaka images instead of thumbs up (CutClout)
- Updated profile page look and feel (Ben Weeks)
- Filter replies from global feed (Nitesh Balusu)
- Show non-image links inline (William Casarin)
- Add swipe gesture to switch between tabs (Thomas Rademaker)
- Parse links in profiles (Lionello Lunesu) (Lio李歐)
### Fixed
- Fix detection of email addresses in profiles (Lionello Lunesu)
- Fix padding on search results view (OlegAba)
- Fix home view moving after selecting from search result (OlegAba)
- Fix bug where boost event is loaded in the thread instead of the boosted event (William Casarin)
- Hide edit button on profile page when no private key (Swift)
- Fixed follows and relays getting out of sync on profile pages (William Casarin)
[1.0.0-2]: https://github.com/damus-io/damus/releases/tag/v1.0.0-2
## [1.0.0] - 2023-01-01
### Added
@@ -266,3 +383,5 @@
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2

View File

@@ -25,7 +25,7 @@ damus implements the following [Nostr Implementation Possibilities][nips]
## Getting Started on Damus
### Damus iOS
1) Get the Damus app on Testflight: https://testflight.apple.com/join/CLwjLxWl
1) Get the Damus app on TestFlight: https://testflight.apple.com/join/CLwjLxWl
#### ⚙️ Settings (gear icon, top right)
- Relays: You can add more relays to send your notes to by tapping the "+".
@@ -56,8 +56,8 @@ 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~~`
- Code: 1 back-tick ``code``
- Strikethrough: 1 tildes `~strikethrough~`
- Code: 1 back-tick `` `code` ``
#### 💬 Encrypted DMs (chat app, bottom navigation)
- Tap the chat icon and you'll notice there's nothing to see at first. Go to a user profile and tap the 💬 chat icon next to the follow button to begin a DM

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>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>

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,8 +12,6 @@
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
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 */; };
@@ -53,6 +51,7 @@
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 */; };
@@ -116,6 +115,13 @@
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 */; };
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */; };
4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */; };
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838C296F710400DC99E7 /* Reposted.swift */; };
4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838E296F781C00DC99E7 /* ReactionsView.swift */; };
4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88392296F798300DC99E7 /* ReactionsModel.swift */; };
4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88395296F7F8B00DC99E7 /* ReactionView.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 */; };
@@ -138,9 +144,13 @@
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 */; };
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
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 */
@@ -167,13 +177,8 @@
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>"; };
3AB1805A2963EF7E00FD1BD8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3AB1805B2963EF7E00FD1BD8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
3AB1805C2963EF7E00FD1BD8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
3A2B8B0A296A8982009CC16D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-US"; path = "en-US.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
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>"; };
@@ -215,6 +220,7 @@
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>"; };
@@ -308,6 +314,13 @@
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>"; };
4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailBar.swift; sourceTree = "<group>"; };
4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05Badge.swift; sourceTree = "<group>"; };
4CB8838C296F710400DC99E7 /* Reposted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reposted.swift; sourceTree = "<group>"; };
4CB8838E296F781C00DC99E7 /* ReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsView.swift; sourceTree = "<group>"; };
4CB88392296F798300DC99E7 /* ReactionsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsModel.swift; sourceTree = "<group>"; };
4CB88395296F7F8B00DC99E7 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.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>"; };
@@ -333,8 +346,12 @@
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>"; };
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.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 */
@@ -462,6 +479,7 @@
4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */,
BA693073295D649800ADDB87 /* UserSettingsStore.swift */,
4FE60CDC295E1C5E00105A1F /* Wallet.swift */,
4CB88392296F798300DC99E7 /* ReactionsModel.swift */,
);
path = Models;
sourceTree = "<group>";
@@ -469,49 +487,53 @@
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 */,
4CB88394296F7F8100DC99E7 /* Reactions */,
4CB88387296AF97C00DC99E7 /* ActionBar */,
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 */,
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
4C75EFB82804A2740006080F /* EventView.swift */,
4C3AC79E2833115300E1F516 /* FollowButtonView.swift */,
4C3AC79C2833036D00E1F516 /* FollowingView.swift */,
4C90BD17283A9EE5008EE7EF /* LoginView.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 */,
4C3AC7A42836987600E1F516 /* MainTabView.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 */,
647D9A8C2968520300A295DE /* SideMenuView.swift */,
9609F057296E220800069BF3 /* BannerImageView.swift */,
4CB8838E296F781C00DC99E7 /* ReactionsView.swift */,
);
path = Views;
sourceTree = "<group>";
@@ -550,10 +572,30 @@
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */,
3169CAEC294FCCFC00EE4006 /* Constants.swift */,
3165648A295B70D500C64604 /* LinkView.swift */,
4C3A1D3629637E0500558C0F /* PreviewCache.swift */,
64FBD06E296255C400D9D3B2 /* Theme.swift */,
4CB8838529656C8B00DC99E7 /* NIP05.swift */,
);
path = Util;
sourceTree = "<group>";
};
4CB88387296AF97C00DC99E7 /* ActionBar */ = {
isa = PBXGroup;
children = (
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */,
4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */,
);
path = ActionBar;
sourceTree = "<group>";
};
4CB88394296F7F8100DC99E7 /* Reactions */ = {
isa = PBXGroup;
children = (
4CB88395296F7F8B00DC99E7 /* ReactionView.swift */,
);
path = Reactions;
sourceTree = "<group>";
};
4CE4F9DF285287A000C00DD9 /* Components */ = {
isa = PBXGroup;
children = (
@@ -563,6 +605,8 @@
4C06670528FCB08600038D2A /* ImageCarousel.swift */,
4C3EA67C28FFBBA200C48A62 /* InvoicesView.swift */,
4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */,
4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */,
4CB8838C296F710400DC99E7 /* Reposted.swift */,
);
path = Components;
sourceTree = "<group>";
@@ -603,8 +647,6 @@
4CE6DEE827F7A08100C66700 /* ContentView.swift */,
4CE6DEEA27F7A08200C66700 /* Assets.xcassets */,
4CE6DEEC27F7A08200C66700 /* Preview Content */,
3AB18052296375CA00FD1BD8 /* InfoPlist.strings */,
3AB18054296375CA00FD1BD8 /* Localizable.strings */,
3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */,
);
path = damus;
@@ -621,6 +663,7 @@
4CE6DEF627F7A08200C66700 /* damusTests */ = {
isa = PBXGroup;
children = (
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */,
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */,
4C363A9F2828A8DD006E126D /* LikeTests.swift */,
4C363A9D2828A822006E126D /* ReplyTests.swift */,
@@ -736,20 +779,18 @@
};
buildConfigurationList = 4CE6DEDE27F7A08100C66700 /* Build configuration list for PBXProject "damus" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
developmentRegion = "en-US";
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
es,
fr,
"es-419",
"en-US",
);
mainGroup = 4CE6DEDA27F7A08100C66700;
packageReferences = (
4CE6DF1027F7A2B300C66700 /* XCRemoteSwiftPackageReference "Starscream" */,
4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */,
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */,
3169CAE9294FCABA00EE4006 /* XCRemoteSwiftPackageReference "Shimmer" */,
6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */,
);
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
@@ -768,9 +809,7 @@
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 */,
);
@@ -810,6 +849,7 @@
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,
@@ -824,12 +864,14 @@
4C363A9028247A1D006E126D /* NostrLink.swift in Sources */,
4C0A3F8C280F5FCA000448DE /* ChatroomView.swift in Sources */,
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */,
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */,
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */,
4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */,
4C649844285A952100EAE2B3 /* LocalUserConfig.swift in Sources */,
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
@@ -838,6 +880,7 @@
4C363A9A28283854006E126D /* Reply.swift in Sources */,
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */,
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */,
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
@@ -854,14 +897,17 @@
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 */,
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */,
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */,
4C363A94282704FA006E126D /* Post.swift in Sources */,
4C216F32286E388800040376 /* DMChatView.swift in Sources */,
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
@@ -875,8 +921,11 @@
4C3EA66528FF5F6800C48A62 /* mem.c in Sources */,
4C3EA64128FF553900C48A62 /* hash_u5.c in Sources */,
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */,
4CB88396296F7F8B00DC99E7 /* ReactionView.swift 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 */,
@@ -896,6 +945,7 @@
4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */,
4C0A3F95280F6C78000448DE /* ReplyQuoteView.swift in Sources */,
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */,
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
@@ -924,6 +974,7 @@
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 */,
@@ -960,31 +1011,12 @@
3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */ = {
isa = PBXVariantGroup;
children = (
3A4325A92961E11400BFCD9D /* en */,
3AB1803D29636FB100FD1BD8 /* es */,
3AB1805C2963EF7E00FD1BD8 /* fr */,
3A5C4575296A879E0032D398 /* es-419 */,
3A2B8B0A296A8982009CC16D /* en-US */,
);
name = Localizable.stringsdict;
sourceTree = "<group>";
};
3AB18052296375CA00FD1BD8 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
3AB18058296377E500FD1BD8 /* es */,
3AB1805A2963EF7E00FD1BD8 /* fr */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
3AB18054296375CA00FD1BD8 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
3AB18059296377E700FD1BD8 /* es */,
3AB1805B2963EF7E00FD1BD8 /* fr */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
@@ -1046,6 +1078,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
@@ -1101,6 +1134,7 @@
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
@@ -1114,7 +1148,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 6;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
@@ -1154,7 +1188,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 6;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
@@ -1303,14 +1337,6 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
3169CAE9294FCABA00EE4006 /* XCRemoteSwiftPackageReference "Shimmer" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/joshuajhomann/Shimmer";
requirement = {
branch = master;
kind = branch;
};
};
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher";

View File

@@ -17,15 +17,6 @@
"revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
}
},
{
"identity" : "shimmer",
"kind" : "remoteSourceControl",
"location" : "https://github.com/joshuajhomann/Shimmer",
"state" : {
"branch" : "master",
"revision" : "2fde687b3f1d9c5409c53da095d3686361e41343"
}
},
{
"identity" : "starscream",
"kind" : "remoteSourceControl",

View File

@@ -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"
}
],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "shaka-full.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@@ -0,0 +1,88 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 -0.073975 cm
1.000000 1.000000 1.000000 scn
1.295334 8.661732 m
3.613694 8.367855 l
4.475733 8.733568 5.268113 9.771931 5.474915 10.327032 c
6.083156 11.959681 5.507567 14.604573 5.474915 15.061715 c
5.448792 15.427428 6.008246 15.693006 6.291239 15.780080 c
7.571236 15.858447 8.508359 14.876789 8.642253 13.984165 c
8.740212 13.331103 8.576948 11.752880 8.381030 10.849482 c
8.979668 10.936556 10.980525 10.901726 11.868687 10.849482 c
12.756847 10.797236 13.474895 10.196423 14.193260 9.412750 c
14.767952 8.237244 13.953805 7.725680 13.474895 7.616838 c
13.834077 7.257654 l
14.781013 5.918882 13.649043 5.178749 13.115711 5.004600 c
13.474895 4.743376 l
14.487136 3.763786 13.246323 2.751544 13.017752 2.882155 c
11.058574 3.176033 l
15.499378 1.673996 l
16.054478 0.400530 15.074889 0.073999 14.781013 0.073999 c
8.576947 1.673996 l
6.291239 1.673996 5.311650 1.869914 4.299407 2.163791 c
4.157911 2.131138 3.659409 1.987464 2.797370 1.673996 c
1.935332 1.360527 1.219143 2.087601 0.968804 2.490320 c
-0.285071 4.083785 -0.467927 7.257655 1.295334 8.661732 c
h
f
n
Q
endstream
endobj
3 0 obj
1149
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 15.666626 15.710510 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001239 00000 n
0000001262 00000 n
0000001435 00000 n
0000001509 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1568
%%EOF

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "shaka-line.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@@ -0,0 +1,323 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.474731 -0.563965 cm
1.000000 1.000000 1.000000 scn
3.613694 9.332577 m
3.553993 8.861599 l
3.637261 8.851044 3.721838 8.862753 3.799107 8.895533 c
3.613694 9.332577 l
h
1.295334 9.626453 m
1.355035 10.097433 l
1.227973 10.113539 1.099794 10.077623 0.999601 9.997839 c
1.295334 9.626453 l
h
0.968804 3.455042 m
1.372000 3.705677 l
1.362764 3.720535 1.352713 3.734872 1.341894 3.748621 c
0.968804 3.455042 l
h
4.299407 3.128512 m
4.431771 3.584435 l
4.353942 3.607030 4.271623 3.609325 4.192656 3.591103 c
4.299407 3.128512 l
h
8.576947 2.638718 m
8.695503 3.098424 l
8.656776 3.108411 8.616942 3.113465 8.576947 3.113465 c
8.576947 2.638718 l
h
14.781013 1.038721 m
14.662457 0.579016 l
14.701184 0.569027 14.741018 0.563974 14.781013 0.563974 c
14.781013 1.038721 l
h
15.499378 2.638718 m
15.934578 2.828420 l
15.881091 2.951125 15.778289 3.045548 15.651489 3.088437 c
15.499378 2.638718 l
h
11.058574 4.140755 m
11.128998 4.610250 l
10.885809 4.646729 10.655017 4.491467 10.597156 4.252461 c
10.539293 4.013455 10.673516 3.769826 10.906463 3.691035 c
11.058574 4.140755 l
h
13.017752 3.846877 m
13.253292 4.259073 l
13.202273 4.288227 13.146286 4.307655 13.088176 4.316372 c
13.017752 3.846877 l
h
13.474895 5.708097 m
13.805044 6.049252 l
13.789093 6.064689 13.772079 6.078987 13.754128 6.092043 c
13.474895 5.708097 l
h
13.115711 5.969321 m
12.968349 6.420619 l
12.798800 6.365256 12.674588 6.219535 12.646772 6.043359 c
12.618958 5.867183 12.692234 5.690281 12.836478 5.585376 c
13.115711 5.969321 l
h
13.834077 8.222376 m
14.221668 8.496526 l
14.206144 8.518474 14.188784 8.539063 14.169774 8.558073 c
13.834077 8.222376 l
h
13.474895 8.581559 m
13.369680 9.044500 l
13.201114 9.006190 13.066693 8.879284 13.018762 8.713197 c
12.970830 8.547110 13.016963 8.368095 13.139197 8.245862 c
13.474895 8.581559 l
h
14.193260 10.377472 m
14.619765 10.585986 l
14.599768 10.626891 14.573989 10.664707 14.543221 10.698271 c
14.193260 10.377472 l
h
8.381030 11.814203 m
7.917068 11.914822 l
7.884080 11.762714 7.927746 11.604099 8.033934 11.490305 c
8.140121 11.376513 8.295343 11.321997 8.449365 11.344399 c
8.381030 11.814203 l
h
8.642253 14.948887 m
9.111748 15.019311 l
8.642253 14.948887 l
h
6.291239 16.744801 m
6.262227 17.218662 l
6.224693 17.216364 6.187564 17.209614 6.151623 17.198555 c
6.291239 16.744801 l
h
5.474915 16.026436 m
5.948456 16.060261 l
5.474915 16.026436 l
h
5.474915 11.291754 m
5.030037 11.457493 l
5.474915 11.291754 l
h
3.673396 9.803555 m
1.355035 10.097433 l
1.235632 9.155476 l
3.553993 8.861599 l
3.673396 9.803555 l
h
0.999601 9.997839 m
-0.029049 9.178730 -0.454726 7.875908 -0.474048 6.619066 c
-0.493367 5.362488 -0.110331 4.058727 0.595713 3.161463 c
1.341894 3.748621 l
0.794064 4.444821 0.458734 5.524729 0.475334 6.604470 c
0.491930 7.683949 0.856455 8.670100 1.591066 9.255068 c
0.999601 9.997839 l
h
0.565608 3.204407 m
0.721970 2.952868 1.013515 2.611341 1.407507 2.372385 c
1.811404 2.127421 2.357187 1.973489 2.959612 2.192553 c
2.635129 3.084882 l
2.375515 2.990478 2.132184 3.043347 1.899893 3.184233 c
1.657696 3.331126 1.465977 3.554496 1.372000 3.705677 c
0.565608 3.204407 l
h
2.959612 2.192553 m
3.816493 2.504146 4.293336 2.639887 4.406158 2.665923 c
4.192656 3.591103 l
4.022485 3.551832 3.502325 3.400227 2.635129 3.084882 c
2.959612 2.192553 l
h
4.167043 2.672591 m
5.229115 2.364247 6.254152 2.163970 8.576947 2.163970 c
8.576947 3.113465 l
6.328326 3.113465 5.394184 3.305025 4.431771 3.584435 c
4.167043 2.672591 l
h
8.458392 2.179011 m
14.662457 0.579016 l
14.899569 1.498427 l
8.695503 3.098424 l
8.458392 2.179011 l
h
14.781013 0.563974 m
15.036198 0.563974 15.495326 0.684875 15.814721 1.047266 c
16.180891 1.462728 16.264221 2.072176 15.934578 2.828420 c
15.064179 2.449016 l
15.289635 1.931793 15.160722 1.741243 15.102402 1.675073 c
15.055794 1.622190 14.990156 1.579316 14.916806 1.549556 c
14.881134 1.535082 14.847747 1.525430 14.820526 1.519657 c
14.791491 1.513498 14.777695 1.513469 14.781013 1.513469 c
14.781013 0.563974 l
h
15.651489 3.088437 m
11.210685 4.590474 l
10.906463 3.691035 l
15.347267 2.188998 l
15.651489 3.088437 l
h
10.988150 3.671260 m
12.947328 3.377382 l
13.088176 4.316372 l
11.128998 4.610250 l
10.988150 3.671260 l
h
12.782211 3.434681 m
12.991495 3.315090 13.204453 3.370091 13.288217 3.396689 c
13.400116 3.432221 13.506123 3.490767 13.598186 3.554502 c
13.783985 3.683133 13.977411 3.877748 14.120350 4.119644 c
14.264680 4.363894 14.369576 4.678114 14.335162 5.031647 c
14.300108 5.391746 14.125634 5.739002 13.805044 6.049252 c
13.144745 5.366943 l
13.330275 5.187398 13.380290 5.040778 13.390134 4.939653 c
13.400617 4.831963 13.370820 4.717613 13.302905 4.602680 c
13.233600 4.485394 13.137231 4.390213 13.057724 4.335170 c
13.017135 4.307070 12.996612 4.300308 13.000857 4.301657 c
13.003194 4.302399 13.024761 4.309311 13.061064 4.310122 c
13.095938 4.310902 13.170414 4.306433 13.253292 4.259073 c
12.782211 3.434681 l
h
13.754128 6.092043 m
13.394944 6.353267 l
12.836478 5.585376 l
13.195662 5.324152 l
13.754128 6.092043 l
h
13.263074 5.518023 m
13.593105 5.625790 14.123367 5.907292 14.433812 6.409482 c
14.595931 6.671733 14.696482 6.993351 14.669847 7.364054 c
14.643518 7.730516 14.495621 8.109214 14.221668 8.496526 c
13.446486 7.948226 l
13.646002 7.666152 13.711709 7.450294 13.722795 7.296009 c
13.733575 7.145966 13.695351 7.020646 13.626177 6.908748 c
13.474038 6.662641 13.171650 6.487002 12.968349 6.420619 c
13.263074 5.518023 l
h
14.169774 8.558073 m
13.810592 8.917255 l
13.139197 8.245862 l
13.498380 7.886679 l
14.169774 8.558073 l
h
13.580109 8.118617 m
13.896242 8.190466 14.344993 8.395787 14.624650 8.816864 c
14.929440 9.275781 14.963785 9.882310 14.619765 10.585986 c
13.766754 10.168959 l
13.997427 9.697128 13.912044 9.460121 13.833706 9.342171 c
13.730235 9.186377 13.532457 9.081495 13.369680 9.044500 c
13.580109 8.118617 l
h
14.543221 10.698271 m
13.820906 11.486253 12.989320 12.223852 11.896564 12.288132 c
11.840808 11.340275 l
12.524374 11.300065 13.128883 10.836036 13.843298 10.056674 c
14.543221 10.698271 l
h
11.896564 12.288132 m
11.441970 12.314873 10.711069 12.336796 10.019300 12.341186 c
9.341933 12.345484 8.654247 12.333687 8.312695 12.284006 c
8.449365 11.344399 l
8.706450 11.381794 9.318512 11.396118 10.013274 11.391710 c
10.693633 11.387392 11.407242 11.365778 11.840808 11.340275 c
11.896564 12.288132 l
h
8.844993 11.713585 m
8.948084 12.188952 9.040332 12.829445 9.094679 13.432834 c
9.147870 14.023395 9.169946 14.631327 9.111748 15.019311 c
8.172758 14.878462 l
8.212520 14.613384 8.201942 14.105675 8.149012 13.518009 c
8.097237 12.943172 8.009893 12.342852 7.917068 11.914822 c
8.844993 11.713585 l
h
9.111748 15.019311 m
8.944062 16.137217 7.805658 17.313158 6.262227 17.218662 c
6.320251 16.270941 l
7.336813 16.333179 8.072657 15.545805 8.172758 14.878462 c
9.111748 15.019311 l
h
6.151623 17.198555 m
5.976391 17.144638 5.715709 17.036982 5.490986 16.876261 c
5.292936 16.734617 4.969444 16.439627 5.001374 15.992612 c
5.948456 16.060261 l
5.951383 16.019283 5.934667 15.999795 5.943361 16.012491 c
5.954769 16.029152 5.984430 16.061831 6.043331 16.103956 c
6.162553 16.189222 6.323094 16.257891 6.430855 16.291048 c
6.151623 17.198555 l
h
5.001374 15.992612 m
5.011176 15.855374 5.059216 15.566318 5.104405 15.255149 c
5.152757 14.922197 5.207128 14.509316 5.241940 14.062993 c
5.312967 13.152368 5.295928 12.171200 5.030037 11.457493 c
5.919792 11.126015 l
6.262142 12.044956 6.261431 13.202559 6.188560 14.136827 c
6.151423 14.612950 6.093790 15.049047 6.044043 15.391605 c
5.991133 15.755945 5.954979 15.968927 5.948456 16.060261 c
5.001374 15.992612 l
h
5.030037 11.457493 m
4.953650 11.252455 4.742510 10.903708 4.434547 10.555828 c
4.127778 10.209298 3.769400 9.914337 3.428282 9.769621 c
3.799107 8.895533 l
4.320028 9.116529 4.788858 9.523607 5.145489 9.926461 c
5.500926 10.327968 5.789377 10.775953 5.919792 11.126015 c
5.030037 11.457493 l
h
f
n
Q
endstream
endobj
3 0 obj
7995
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 16.615845 16.660034 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000008085 00000 n
0000008108 00000 n
0000008281 00000 n
0000008355 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
8414
%%EOF

View File

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

View File

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

View File

@@ -0,0 +1,65 @@
//
// NIP05Badge.swift
// damus
//
// Created by William Casarin on 2023-01-11.
//
import SwiftUI
struct NIP05Badge: View {
let nip05: NIP05
let pubkey: String
let contacts: Contacts
let show_domain: Bool
let clickable: Bool
@Environment(\.openURL) var openURL
init (nip05: NIP05, pubkey: String, contacts: Contacts, show_domain: Bool, clickable: Bool) {
self.nip05 = nip05
self.pubkey = pubkey
self.contacts = contacts
self.show_domain = show_domain
self.clickable = clickable
}
var nip05_color: Color {
return get_nip05_color(pubkey: pubkey, contacts: contacts)
}
var body: some View {
HStack(spacing: 2) {
Image(systemName: "checkmark.seal.fill")
.font(.footnote)
.foregroundColor(nip05_color)
if show_domain {
if clickable {
Text(nip05.host)
.foregroundColor(nip05_color)
.onTapGesture {
if let nip5url = nip05.siteUrl {
openURL(nip5url)
}
}
} else {
Text(nip05.host)
.foregroundColor(nip05_color)
}
}
}
}
}
func get_nip05_color(pubkey: String, contacts: Contacts) -> Color {
return contacts.is_friend_or_self(pubkey) ? .accentColor : .gray
}
struct NIP05Badge_Previews: PreviewProvider {
static var previews: some View {
let test_state = test_damus_state()
NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: test_state.contacts, show_domain: true, clickable: false)
}
}

View File

@@ -0,0 +1,34 @@
//
// Reposted.swift
// damus
//
// Created by William Casarin on 2023-01-11.
//
import SwiftUI
struct Reposted: View {
let damus: DamusState
let pubkey: String
let profile: Profile?
var body: some View {
HStack(alignment: .center) {
Image(systemName: "arrow.2.squarepath")
.font(.footnote)
.foregroundColor(Color.gray)
ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: true, show_nip5_domain: false)
.foregroundColor(Color.gray)
Text("Reposted", comment: "Text indicating that the post was reposted (i.e. re-shared).")
.font(.footnote)
.foregroundColor(Color.gray)
}
}
}
struct Reposted_Previews: PreviewProvider {
static var previews: some View {
let test_state = test_damus_state()
Reposted(damus: test_state, pubkey: test_state.pubkey, profile: make_test_profile())
}
}

View File

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

View File

@@ -44,6 +44,15 @@ enum ThreadState {
enum FilterState : Int {
case posts_and_replies = 1
case posts = 0
func filter(ev: NostrEvent) -> Bool {
switch self {
case .posts:
return !ev.is_reply(nil)
case .posts_and_replies:
return true
}
}
}
struct ContentView: View {
@@ -71,6 +80,7 @@ struct ContentView: View {
@State var thread_open: Bool = false
@State var search_open: Bool = false
@State var filter_state : FilterState = .posts_and_replies
@State private var isSideBarOpened = false
@StateObject var home: HomeModel = HomeModel()
@StateObject var user_settings = UserSettingsStore()
@@ -84,10 +94,12 @@ struct ContentView: View {
var PostingTimelineView: some View {
VStack {
TabView(selection: $filter_state) {
ContentTimelineView
contentTimelineView(filter: FilterState.posts.filter)
.tag(FilterState.posts)
ContentTimelineView
.id(FilterState.posts)
contentTimelineView(filter: FilterState.posts_and_replies.filter)
.tag(FilterState.posts_and_replies)
.id(FilterState.posts_and_replies)
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
@@ -101,15 +113,16 @@ struct ContentView: View {
}
.background(colorScheme == .dark ? Color.black : Color.white)
}
.ignoresSafeArea(.keyboard)
}
var ContentTimelineView: some View {
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_event)
TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter)
}
if privkey != nil {
PostButtonContainer {
PostButtonContainer(userSettings: user_settings) {
self.active_sheet = .post
}
}
@@ -118,22 +131,14 @@ struct ContentView: View {
var FiltersView: some View {
VStack{
Picker("Filter State", selection: $filter_state) {
Text("Posts").tag(FilterState.posts)
Text("Posts & Replies").tag(FilterState.posts_and_replies)
Picker(NSLocalizedString("Filter State", comment: "Filter state for seeing either only posts, or posts & replies."), selection: $filter_state) {
Text("Posts", comment: "Label for filter for seeing only posts (instead of posts and replies).").tag(FilterState.posts)
Text("Posts & Replies", comment: "Label for filter for seeing posts and replies (instead of only posts).").tag(FilterState.posts_and_replies)
}
.pickerStyle(.segmented)
}
}
func filter_event(_ ev: NostrEvent) -> Bool {
if self.filter_state == .posts {
return !ev.is_reply(nil)
}
return true
}
func MainContent(damus: DamusState) -> some View {
VStack {
NavigationLink(destination: MaybeProfileView, isActive: $profile_open) {
@@ -154,7 +159,7 @@ struct ContentView: View {
case .notifications:
TimelineView(events: $home.notifications, loading: $home.loading, damus: damus, show_friend_icon: true, filter: { _ in true })
.navigationTitle("Notifications")
.navigationTitle(NSLocalizedString("Notifications", comment: "Navigation title for notifications."))
case .dms:
DirectMessagesView(damus_state: damus_state!)
@@ -164,7 +169,27 @@ struct ContentView: View {
EmptyView()
}
}
.navigationBarTitle(selected_timeline == .home ? "Home" : "Global", displayMode: .inline)
.navigationBarTitle(selected_timeline == .home ? NSLocalizedString("Home", comment: "Navigation bar title for Home view where posts and replies appear from those who the user is following.") : NSLocalizedString("Global", comment: "Navigation bar title for Global view where posts from all connected relay servers appear."), displayMode: .inline)
.toolbar {
ToolbarItem(placement: .principal) {
switch selected_timeline {
case .home:
Image("damus-home")
.resizable()
.frame(width:30,height:30)
.shadow(color: Color("DamusPurple"), radius: 2)
case .dms:
Text("DM", comment: "Toolbar label for DM view, which is the English abbreviation for Direct Message.")
case .notifications:
Text("Notifications", comment: "Toolbar label for Notifications view.")
case .search:
Text("Global", comment: "Toolbar label for Global view where posts from all connected relay servers appear.")
case .none:
Text("", comment: "Toolbar label for unknown views. This label would be displayed only if a new timeline view is added but a toolbar label was not explicitly assigned to it yet.")
}
}
}
}
var MaybeSearchView: some View {
@@ -198,51 +223,49 @@ struct ContentView: View {
}
}
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
if let damus = self.damus_state {
NavigationView {
MainContent(damus: damus)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
let profile_model = ProfileModel(pubkey: damus_state!.pubkey, damus: damus_state!)
let followers_model = FollowersModel(damus_state: damus_state!, target: damus_state!.pubkey)
let prof_dest = ProfileView(damus_state: damus_state!, profile: profile_model, followers: followers_model)
ZStack {
VStack {
MainContent(damus: damus)
.toolbar() {
ToolbarItem(placement: .navigationBarLeading) {
Button {
isSideBarOpened.toggle()
} label: {
ProfilePicView(pubkey: damus_state!.pubkey, size: 32, highlight: .none, profiles: damus_state!.profiles)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
HStack(alignment: .center) {
if home.signal.signal != home.signal.max_signal {
Text("\(home.signal.signal)/\(home.signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
.font(.callout)
.foregroundColor(.gray)
}
NavigationLink(destination: prof_dest) {
/// Verify that the user has a profile picture, if not display a generic SF Symbol
/// (Resolves an in-app error where ``Robohash`` pictures are not generated so the button dissapears
if let picture = damus_state?.profiles.lookup(id: pubkey)?.picture {
ProfilePicView(pubkey: damus_state!.pubkey, size: 32, highlight: .none, profiles: damus_state!.profiles, picture: picture)
} else {
Image(systemName: "person.fill")
}
}
}
.buttonStyle(PlainButtonStyle())
}
ToolbarItem(placement: .navigationBarTrailing) {
HStack(alignment: .center) {
if home.signal.signal != home.signal.max_signal {
Text("\(home.signal.signal)/\(home.signal.max_signal)")
.font(.callout)
.foregroundColor(.gray)
}
NavigationLink(destination: ConfigView(state: damus_state!).environmentObject(user_settings)) {
Label("", systemImage: "gear")
}
.buttonStyle(PlainButtonStyle())
}
}
}
Color.clear
.overlay(
SideMenuView(damus_state: damus, isSidebarVisible: $isSideBarOpened)
)
}
.navigationBarHidden(isSideBarOpened ? true: false) // Would prefer a different way of doing this.
}
.navigationViewStyle(.stack)
TabBar(new_events: $home.new_events, selected: $selected_timeline, isSidebarVisible: $isSideBarOpened, action: switch_timeline)
.padding([.bottom], 8)
}
TabBar(new_events: $home.new_events, selected: $selected_timeline, action: switch_timeline)
.padding([.bottom], 8)
}
.onAppear() {
self.connect()
@@ -283,7 +306,6 @@ struct ContentView: View {
guard let privkey = self.privkey else {
return
}
let ev = notif.object as! NostrEvent
let boost = make_boost_event(pubkey: pubkey, privkey: privkey, boosted: ev)
self.damus_state?.pool.send(.event(boost))
@@ -316,10 +338,10 @@ struct ContentView: View {
let pk = target.pubkey
if let ev = unfollow_user(pool: damus.pool,
our_contacts: damus.contacts.event,
pubkey: damus.pubkey,
privkey: privkey,
unfollow: pk) {
our_contacts: damus.contacts.event,
pubkey: damus.pubkey,
privkey: privkey,
unfollow: pk) {
notify(.unfollowed, pk)
damus.contacts.event = ev
@@ -338,10 +360,10 @@ struct ContentView: View {
}
if let ev = follow_user(pool: damus.pool,
our_contacts: damus.contacts.event,
pubkey: damus.pubkey,
privkey: privkey,
follow: ReferencedId(ref_id: fnotify.pubkey, relay_id: nil, key: "p")) {
our_contacts: damus.contacts.event,
pubkey: damus.pubkey,
privkey: privkey,
follow: ReferencedId(ref_id: fnotify.pubkey, relay_id: nil, key: "p")) {
notify(.followed, fnotify.pubkey)
damus_state?.contacts.event = ev
@@ -411,10 +433,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!
@@ -430,7 +453,6 @@ struct ContentView_Previews: PreviewProvider {
}
}
func get_since_time(last_event: NostrEvent?) -> Int64? {
if let last_event = last_event {
return last_event.created_at - 60 * 10

View File

@@ -15,6 +15,8 @@
</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>

View File

@@ -16,6 +16,10 @@ class ActionBarModel: ObservableObject {
@Published var boosts: Int
@Published var tips: Int64
static func empty() -> ActionBarModel {
return ActionBarModel(likes: 0, boosts: 0, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
}
init(likes: Int, boosts: Int, tips: Int64, our_like: NostrEvent?, our_boost: NostrEvent?, our_tip: NostrEvent?) {
self.likes = likes
self.boosts = boosts
@@ -25,6 +29,10 @@ class ActionBarModel: ObservableObject {
self.our_tip = our_tip
}
var is_empty: Bool {
return likes == 0 && boosts == 0 && tips == 0
}
var tipped: Bool {
return our_tip != nil
}

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
}

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())
}
}

View File

@@ -18,11 +18,11 @@ class FollowersModel: ObservableObject {
let sub_id: String = UUID().description
let profiles_id: String = UUID().description
var count_display: String {
var count: Int? {
guard let contacts = self.contacts else {
return "?"
return nil
}
return "\(contacts.count)";
return contacts.count
}
init(damus_state: DamusState, target: String) {
@@ -73,31 +73,30 @@ class FollowersModel: ObservableObject {
}
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
switch ev {
case .ws_event:
break
case .nostr_event(let nev):
switch nev {
case .event(let sub_id, let ev):
guard sub_id == self.sub_id || sub_id == self.profiles_id else {
return
}
if ev.known_kind == .contacts {
handle_contact_event(ev)
} else if ev.known_kind == .metadata {
process_metadata_event(profiles: damus_state.profiles, ev: ev)
}
case .notice(let msg):
print("followingmodel notice: \(msg)")
case .eose(let sub_id):
if sub_id == self.sub_id {
load_profiles(relay_id: relay_id)
} else if sub_id == self.profiles_id {
damus_state.pool.unsubscribe(sub_id: profiles_id, to: [relay_id])
}
guard case .nostr_event(let nev) = ev else {
return
}
switch nev {
case .event(let sub_id, let ev):
guard sub_id == self.sub_id || sub_id == self.profiles_id else {
return
}
if ev.known_kind == .contacts {
handle_contact_event(ev)
} else if ev.known_kind == .metadata {
process_metadata_event(profiles: damus_state.profiles, ev: ev)
}
case .notice(let msg):
print("followingmodel notice: \(msg)")
case .eose(let sub_id):
if sub_id == self.sub_id {
load_profiles(relay_id: relay_id)
} else if sub_id == self.profiles_id {
damus_state.pool.unsubscribe(sub_id: profiles_id, to: [relay_id])
}
}
}

View File

@@ -544,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
@@ -554,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) {
@@ -562,6 +578,13 @@ func process_metadata_event(profiles: Profiles, ev: NostrEvent) {
}
}
let banner = tprof.profile.banner ?? ""
if let _ = URL(string: banner) {
DispatchQueue.main.async {
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
}
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}

View File

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

View File

@@ -0,0 +1,78 @@
//
// LikesModel.swift
// damus
//
// Created by William Casarin on 2023-01-11.
//
import Foundation
class ReactionsModel: ObservableObject {
let state: DamusState
let target: String
let sub_id: String
let profiles_id: String
@Published var reactions: [NostrEvent]
init (state: DamusState, target: String) {
self.state = state
self.target = target
self.sub_id = UUID().description
self.profiles_id = UUID().description
self.reactions = []
}
func get_filter() -> NostrFilter {
var filter = NostrFilter.filter_kinds([7])
filter.referenced_ids = [target]
filter.limit = 500
return filter
}
func subscribe() {
let filter = get_filter()
let filters = [filter]
self.state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_nostr_event)
}
func unsubscribe() {
self.state.pool.unsubscribe(sub_id: sub_id)
}
func handle_event(relay_id: String, ev: NostrEvent) {
guard ev.kind == 7 else {
return
}
guard let reacted_to = last_etag(tags: ev.tags) else {
return
}
guard reacted_to == self.target else {
return
}
if insert_uniq_sorted_event(events: &self.reactions, new_ev: ev, cmp: { a, b in a.created_at < b.created_at } ) {
objectWillChange.send()
}
}
func handle_nostr_event(relay_id: String, ev: NostrConnectionEvent) {
guard case .nostr_event(let nev) = ev else {
return
}
switch nev {
case .event(_, let ev):
handle_event(relay_id: relay_id, ev: ev)
case .notice(_):
break
case .eose(_):
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, events: reactions, damus_state: state)
break
}
}
}

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])
}
}

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):

View File

@@ -20,13 +20,22 @@ class UserSettingsStore: ObservableObject {
}
}
@Published var left_handed: Bool {
didSet {
UserDefaults.standard.set(left_handed, forKey: "left_handed")
}
}
init() {
if let defaultWalletName = UserDefaults.standard.string(forKey: "default_wallet"),
let default_wallet = Wallet(rawValue: defaultWalletName) {
let default_wallet = Wallet(rawValue: defaultWalletName)
{
self.default_wallet = default_wallet
} else {
self.default_wallet = .system_default_wallet
default_wallet = .system_default_wallet
}
self.show_wallet_selector = UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
show_wallet_selector = UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false
}
}

View File

@@ -10,12 +10,13 @@ import Foundation
struct Profile: Codable {
var value: [String: String]
init (name: String?, display_name: String?, about: String?, picture: String?, website: String?, lud06: String?, lud16: String?, nip05: String?) {
init (name: String?, display_name: String?, about: String?, picture: String?, banner: String?, website: String?, lud06: String?, lud16: String?, nip05: String?) {
self.value = [:]
self.name = name
self.display_name = display_name
self.about = about
self.picture = picture
self.banner = banner
self.website = website
self.lud06 = lud06
self.lud16 = lud16
@@ -42,6 +43,11 @@ struct Profile: Codable {
set(s) { value["picture"] = s }
}
var banner: String? {
get { return value["banner"]; }
set(s) { value["banner"] = s }
}
var website: String? {
get { return value["website"]; }
set(s) { value["website"] = s }
@@ -94,26 +100,9 @@ struct Profile: Codable {
}
}
/*
struct Profile: Decodable {
let name: String?
let display_name: String?
let about: String?
let picture: String?
let website: String?
let nip05: String?
let lud06: String?
let lud16: String?
var lightning_uri: URL? {
return make_ln_url(self.lud06) ?? make_ln_url(self.lud16)
}
static func displayName(profile: Profile?, pubkey: String) -> String {
return profile?.name ?? abbrev_pubkey(pubkey)
}
func make_test_profile() -> Profile {
return Profile(name: "jb55", display_name: "Will", about: "Its a me", picture: "https://cdn.jb55.com/img/red-me.jpg", banner: "https://pbs.twimg.com/profile_banners/9918032/1531711830/600x200", website: "jb55.com", lud06: "jb55@jb55.com", lud16: nil, nip05: "jb55@jb55.com")
}
*/
func make_ln_url(_ str: String?) -> URL? {
return str.flatMap { URL(string: "lightning:" + $0) }

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.baseAddress!)
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] {
@@ -770,6 +772,15 @@ func validate_event(ev: NostrEvent) -> ValidationResult {
return ok ? .ok : .bad_sig
}
func last_etag(tags: [[String]]) -> String? {
var e: String? = nil
for tag in tags {
if tag.count >= 2 && tag[0] == "e" {
e = tag[1]
}
}
return e
}
func inner_event_or_self(ev: NostrEvent) -> NostrEvent {
guard let inner_ev = ev.inner_event else {

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: "")

View File

@@ -15,10 +15,11 @@ struct NostrMetadata: Codable {
let website: String?
let nip05: String?
let picture: String?
let banner: String?
let lud06: String?
let lud16: String?
}
func create_account_to_metadata(_ model: CreateAccountModel) -> NostrMetadata {
return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: nil, lud06: nil, lud16: nil)
return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: nil, banner: nil, lud06: nil, lud16: nil)
}

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()

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

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

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)

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"),

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) {

View File

@@ -15,12 +15,14 @@ public struct Markdown {
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 {
// Similar to the parsing in NoteContentView
let md_opts: AttributedString.MarkdownParsingOptions =
.init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
if let txt = try? AttributedString(markdown: content, options: md_opts) {
// TODO: escape unintentional markdown
let escaped = content.replacingOccurrences(of: "\\_", with: "\\\\\\_")
if let txt = try? AttributedString(markdown: escaped, options: md_opts) {
return txt
} else {
return AttributedString(stringLiteral: content)
@@ -33,11 +35,13 @@ public struct Markdown {
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) else { continue }
let url = input[range]
output.replaceSubrange(range, with: "[\(url)](\(Markdown.withScheme(url)))")
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))")
}
// TODO: escape unintentional markdown
return Markdown.parse(content: output)
}
}

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

@@ -0,0 +1,69 @@
//
// 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)")
}
var siteUrl: URL? {
URL(string: "https://\(host)")
}
static func parse(_ nip05: String) -> NIP05? {
let parts = nip05.split(separator: "@")
guard parts.count == 2 else {
return nil
}
return NIP05(username: String(parts[0]), host: String(parts[1]))
}
}
struct NIP05Response: Decodable {
let names: [String: String]
let relays: [String: [String]]?
}
enum NIP05Validation {
case invalid
case valid
}
func validate_nip05(pubkey: String, nip05_str: String) async -> NIP05? {
guard let nip05 = NIP05.parse(nip05_str) else {
return nil
}
guard let url = nip05.url else {
return nil
}
guard let ret = try? await URLSession.shared.data(from: url) else {
return nil
}
let dat = ret.0
guard let decoded = try? JSONDecoder().decode(NIP05Response.self, from: dat) else {
return nil
}
guard let stored_pk = decoded.names[nip05.username] else {
return nil
}
guard stored_pk == pubkey else {
return nil
}
return nip05
}

View File

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

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

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

View File

@@ -24,28 +24,19 @@ 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 {
HStack {
/*
EventActionButton(img: "square.and.arrow.up") {
print("share")
}
Spacer()
*/
if damus_state.keypair.privkey != nil {
EventActionButton(img: "bubble.left", col: nil) {
notify(.reply, event)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
HStack(alignment: .bottom) {
Text("\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
.font(.footnote.weight(.medium))
.foregroundColor(bar.boosted ? Color.green : Color.gray)
EventActionButton(img: "arrow.2.squarepath", col: bar.boosted ? Color.green : nil) {
if bar.boosted {
@@ -53,22 +44,35 @@ struct EventActionBar: View {
} else {
self.confirm_boost = true
}
}.overlay {
Text("\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
.offset(x: 22)
.font(.footnote.weight(.medium))
.foregroundColor(bar.boosted ? Color.green : Color.gray)
}
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
HStack(alignment: .bottom) {
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
.font(.footnote.weight(.medium))
.foregroundColor(bar.liked ? Color.orange : Color.gray)
LikeButton(liked: bar.liked) {
if bar.liked {
notify(.delete, bar.our_like)
} else {
send_like()
}
}.overlay {
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
.offset(x: 22)
.font(.footnote.weight(.medium))
.foregroundColor(bar.liked ? Color.accentColor : Color.gray)
}
}
.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,15 +90,22 @@ struct EventActionBar: View {
}
*/
}
.alert("Boost", isPresented: $confirm_boost) {
Button("Cancel") {
.sheet(isPresented: $show_share_sheet) {
if let note_id = bech32_note_id(event.id) {
if let url = URL(string: "https://damus.io/" + note_id) {
ShareSheet(activityItems: [url])
}
}
}
.alert(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post."), isPresented: $confirm_boost) {
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of reposting a post.")) {
confirm_boost = false
}
Button("Boost") {
Button(NSLocalizedString("Repost", comment: "Button to confirm reposting a post.")) {
send_boost()
}
} message: {
Text("Are you sure you want to boost this post?")
Text("Are you sure you want to repost this?", comment: "Alert message to ask if user wants to repost a post.")
}
.onReceive(handle_notify(.liked)) { n in
let liked = n.object as! Counted
@@ -138,11 +149,10 @@ struct EventActionBar: View {
func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View {
Button(action: action) {
Label("&nbsp;", systemImage: img)
Label(NSLocalizedString("\u{00A0}", comment: "Non-breaking space character to fill in blank space next to event action button icons."), systemImage: img)
.font(.footnote.weight(.medium))
.foregroundColor(col == nil ? Color.gray : col!)
}
.padding(.trailing, 40)
}
struct LikeButton: View {
@@ -153,13 +163,8 @@ struct LikeButton: View {
var body: some View {
Button(action: action) {
if liked {
Text("🤙")
} else {
Label("&nbsp;", systemImage: "hand.thumbsup")
.font(.footnote.weight(.medium))
.foregroundColor(Color.gray)
}
Image(liked ? "shaka-full" : "shaka-line")
.foregroundColor(liked ? .accentColor : .gray)
}
}
}
@@ -169,8 +174,19 @@ struct EventActionBar_Previews: PreviewProvider {
static var previews: some View {
let pk = "pubkey"
let ds = test_damus_state()
let bar = ActionBarModel(likes: 0, boosts: 0, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
let ev = NostrEvent(content: "hi", pubkey: pk)
EventActionBar(damus_state: ds, event: ev, bar: bar)
let bar = ActionBarModel(likes: 0, boosts: 0, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
let likedbar = ActionBarModel(likes: 10, boosts: 0, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
let likedbar_ours = ActionBarModel(likes: 10, boosts: 0, tips: 0, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: nil, our_tip: nil)
VStack(spacing: 50) {
EventActionBar(damus_state: ds, event: ev, bar: bar)
EventActionBar(damus_state: ds, event: ev, bar: likedbar)
EventActionBar(damus_state: ds, event: ev, bar: likedbar_ours)
}
.padding(20)
}
}

View File

@@ -0,0 +1,39 @@
//
// EventDetailBar.swift
// damus
//
// Created by William Casarin on 2023-01-08.
//
import SwiftUI
struct EventDetailBar: View {
let state: DamusState
let target: String
@StateObject var bar: ActionBarModel
var body: some View {
HStack {
if bar.boosts > 0 {
Text("\(Text("\(bar.boosts)", comment: "Number of reposts.").font(.body.bold())) \(Text(String(format: NSLocalizedString("reposts_count", comment: "Part of a larger sentence to describe how many reposts there are."), bar.boosts)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
}
if bar.likes > 0 {
NavigationLink(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
Text("\(Text("\(bar.likes)", comment: "Number of reactions on a post.").font(.body.bold())) \(Text(String(format: NSLocalizedString("reactions_count", comment: "Part of a larger sentence to describe how many reactions there are on a post."), bar.likes)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")
}
.buttonStyle(PlainButtonStyle())
}
if bar.tips > 0 {
Text("\(Text("\(bar.tips)", comment: "Number of tip payments on a post.").font(.body.bold())) \(Text(String(format: NSLocalizedString("tips_count", comment: "Part of a larger sentence to describe how many tip payments there are on a post."), bar.boosts)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many tip payments there are on a post. In source English, the first variable is the number of tip payments, and the second variable is 'Tip' or 'Tips'.")
}
}
}
}
struct EventDetailBar_Previews: PreviewProvider {
static var previews: some View {
EventDetailBar(state: test_damus_state(), target: "", bar: ActionBarModel.empty())
}
}

View File

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

View File

@@ -0,0 +1,85 @@
//
// BannerImageView.swift
// damus
//
// Created by Jason Jōb on 2023-01-10.
//
import SwiftUI
import Kingfisher
struct InnerBannerImageView: View {
let url: URL?
let pubkey: String
var body: some View {
ZStack {
Color(uiColor: .systemBackground)
if (url != nil) {
KFAnimatedImage(url)
.callbackQueue(.dispatch(.global(qos: .background)))
.processingQueue(.dispatch(.global(qos: .background)))
.appendProcessor(LargeImageProcessor.shared)
.configure { view in
view.framePreloadCount = 1
}
.placeholder { _ in
Image("profile-banner").resizable()
}
.scaleFactor(UIScreen.main.scale)
.loadDiskFileSynchronously()
.fade(duration: 0.1)
} else {
Image("profile-banner").resizable()
}
}
}
}
struct BannerImageView: View {
let pubkey: String
let profiles: Profiles
@State var banner: String?
init (pubkey: String, profiles: Profiles, banner: String? = nil) {
self.pubkey = pubkey
self.profiles = profiles
self._banner = State(initialValue: banner)
}
var body: some View {
InnerBannerImageView(url: get_banner_url(banner: banner, pubkey: pubkey, profiles: profiles), pubkey: pubkey)
.onReceive(handle_notify(.profile_updated)) { notif in
let updated = notif.object as! ProfileUpdate
guard updated.pubkey == self.pubkey else {
return
}
if let bannerImage = updated.profile.banner {
self.banner = bannerImage
}
}
}
}
func get_banner_url(banner: String?, pubkey: String, profiles: Profiles) -> URL? {
let bannerUrlString = banner ?? profiles.lookup(id: pubkey)?.banner ?? ""
if let url = URL(string: bannerUrlString) {
return url
}
return nil
}
struct BannerImageView_Previews: PreviewProvider {
static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846"
static var previews: some View {
BannerImageView(
pubkey: pubkey,
profiles: make_preview_profiles(pubkey))
}
}

View File

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

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)

View File

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

View File

@@ -6,6 +6,7 @@
//
import AVFoundation
import SwiftUI
import Kingfisher
struct ConfigView: View {
let state: DamusState
@@ -54,19 +55,30 @@ 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)
}
}
Section("Recommended Relays") {
List(recommended, id: \.url) { r in
RecommendedRelayView(damus: state, relay: r.url.absoluteString)
} header: {
HStack {
Text("Relays", comment: "Header text for relay server list for configuration.")
Spacer()
Button(action: { show_add_relay = true }) {
Image(systemName: "plus")
.foregroundColor(.accentColor)
}
}
}
Section("Public Account ID") {
if recommended.count > 0 {
Section(NSLocalizedString("Recommended Relays", comment: "Section title for recommend relay servers that could be added as part of configuration")) {
List(recommended, id: \.url) { r in
RecommendedRelayView(damus: state, relay: r.url.absoluteString)
}
}
}
Section(NSLocalizedString("Public Account ID", comment: "Section title for the user's public account ID.")) {
HStack {
Text(state.keypair.pubkey_bech32)
@@ -76,10 +88,10 @@ struct ConfigView: View {
}
if let sec = state.keypair.privkey_bech32 {
Section("Secret Account Login Key") {
Section(NSLocalizedString("Secret Account Login Key", comment: "Section title for user's secret account login key.")) {
HStack {
if show_privkey == false {
SecureField("PrivateKey", text: $privkey)
SecureField(NSLocalizedString("PrivateKey", comment: "Title of the secure field that holds the user's private key."), text: $privkey)
.disabled(true)
} else {
Text(sec)
@@ -89,13 +101,13 @@ struct ConfigView: View {
CopyButton(is_pk: false)
}
Toggle("Show", isOn: $show_privkey)
Toggle(NSLocalizedString("Show", comment: "Toggle to show or hide user's secret account login key."), isOn: $show_privkey)
}
}
Section("Wallet Selector") {
Toggle("Show wallet selector", isOn: $user_settings.show_wallet_selector).toggleStyle(.switch)
Picker("Select default wallet",
Section(NSLocalizedString("Wallet Selector", comment: "Section title for selection of wallet.")) {
Toggle(NSLocalizedString("Show wallet selector", comment: "Toggle to show or hide selection of wallet."), isOn: $user_settings.show_wallet_selector).toggleStyle(.switch)
Picker(NSLocalizedString("Select default wallet", comment: "Prompt selection of user's default wallet"),
selection: $user_settings.default_wallet) {
ForEach(Wallet.allCases, id: \.self) { wallet in
Text(wallet.model.displayName)
@@ -103,50 +115,53 @@ struct ConfigView: View {
}
}
}
Section("Reset") {
Button("Logout") {
Section(NSLocalizedString("Left Handed", comment: "Moves the post button to the left side of the screen")) {
Toggle(NSLocalizedString("Left Handed", comment: "Moves the post button to the left side of the screen"), isOn: $user_settings.left_handed)
.toggleStyle(.switch)
}
Section(NSLocalizedString("Clear Cache", comment: "Section title for clearing cached data.")) {
Button(NSLocalizedString("Clear", comment: "Button for clearing cached data.")) {
KingfisherManager.shared.cache.clearMemoryCache()
KingfisherManager.shared.cache.clearDiskCache()
KingfisherManager.shared.cache.cleanExpiredDiskCache()
}
}
Section(NSLocalizedString("Reset", comment: "Section title for resetting the user")) {
Button(NSLocalizedString("Logout", comment: "Button to logout the user.")) {
confirm_logout = true
}
}
}
VStack {
HStack {
Spacer()
Button(action: { show_add_relay = true }) {
Label("", systemImage: "plus")
.foregroundColor(.accentColor)
.padding()
}
}
Spacer()
}
}
.navigationTitle("Settings")
.navigationTitle(NSLocalizedString("Settings", comment: "Navigation title for Settings view."))
.navigationBarTitleDisplayMode(.large)
.alert("Logout", isPresented: $confirm_logout) {
Button("Cancel") {
.alert(NSLocalizedString("Logout", comment: "Alert for logging out the user."), isPresented: $confirm_logout) {
Button(NSLocalizedString("Cancel", comment: "Cancel out of logging out the user.")) {
confirm_logout = false
}
Button("Logout") {
Button(NSLocalizedString("Logout", comment: "Button for logging out the user.")) {
notify(.logout, ())
}
} message: {
Text("Make sure your nsec account key is saved before you logout or you will lose access to this account")
Text("Make sure your nsec account key is saved before you logout or you will lose access to this account", comment: "Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out.")
}
.sheet(isPresented: $show_add_relay) {
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
}
@@ -161,9 +176,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
}

View File

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

View File

@@ -19,7 +19,7 @@ struct DMChatView: View {
VStack(alignment: .leading) {
ForEach(Array(zip(dms.events, dms.events.indices)), id: \.0.id) { (ev, ind) in
DMView(event: dms.events[ind], damus_state: damus_state)
.event_context_menu(ev, privkey: damus_state.keypair.privkey)
.event_context_menu(ev, pubkey: ev.pubkey, privkey: damus_state.keypair.privkey)
}
EndBlock(height: 80)
}
@@ -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())
@@ -138,14 +142,14 @@ struct DMChatView: View {
Footer
}
Text("Send a message to start the conversation...")
Text("Send a message to start the conversation...", comment: "Text prompt for user to send a message to the other user.")
.lineLimit(nil)
.multilineTextAlignment(.center)
.padding(.horizontal, 40)
.opacity(((dms.events.count == 0) ? 1.0 : 0.0))
.foregroundColor(.gray)
}
.navigationTitle("DM")
.navigationTitle(NSLocalizedString("DM", comment: "Navigation title for DM view, which is the English abbreviation for Direct Message."))
.toolbar { Header }
}
}

View File

@@ -10,25 +10,28 @@ import SwiftUI
struct DMView: View {
let event: NostrEvent
let damus_state: DamusState
var is_ours: Bool {
event.pubkey == damus_state.pubkey
}
var body: some View {
HStack {
if is_ours {
Spacer()
Spacer(minLength: UIScreen.main.bounds.width * 0.2)
}
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))
.cornerRadius(8.0)
.tint(is_ours ? Color.white : Color.accentColor)
if !is_ours {
Spacer(minLength: UIScreen.main.bounds.width * 0.2)
}
}
}
}

View File

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

View File

@@ -8,6 +8,7 @@
import SwiftUI
let PPM_SIZE: CGFloat = 80.0
let BANNER_HEIGHT: CGFloat = 150.0;
func isHttpsUrl(_ string: String) -> Bool {
let urlRegEx = "^https://.*$"
@@ -15,11 +16,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"]
@@ -61,12 +57,14 @@ struct EditMetadataView: View {
@State var display_name: String
@State var about: String
@State var picture: String
@State var banner: String
@State var nip05: String
@State var name: String
@State var ln: String
@State var website: String
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
init (damus_state: DamusState) {
self.damus_state = damus_state
@@ -77,10 +75,15 @@ struct EditMetadataView: View {
_about = State(initialValue: data?.about ?? "")
_website = State(initialValue: data?.website ?? "")
_picture = State(initialValue: data?.picture ?? "")
_banner = State(initialValue: data?.banner ?? "")
_nip05 = State(initialValue: data?.nip05 ?? "")
_ln = State(initialValue: data?.lud16 ?? data?.lud06 ?? "")
}
func imageBorderColor() -> Color {
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
}
func save() {
let metadata = NostrMetadata(
display_name: display_name,
@@ -89,6 +92,7 @@ struct EditMetadataView: View {
website: website,
nip05: nip05.isEmpty ? nil : nip05,
picture: picture.isEmpty ? nil : picture,
banner: banner.isEmpty ? nil : banner,
lud06: ln.contains("@") ? nil : ln,
lud16: ln.contains("@") ? ln : nil
);
@@ -101,47 +105,68 @@ struct EditMetadataView: View {
}
var nip05_parts: NIP05? {
let parts = nip05.split(separator: "@")
guard parts.count == 2 else {
return nil
return NIP05.parse(nip05)
}
var TopSection: some View {
ZStack(alignment: .top) {
GeometryReader { geo in
BannerImageView(pubkey: damus_state.pubkey, profiles: damus_state.profiles)
.aspectRatio(contentMode: .fill)
.frame(width: geo.size.width, height: BANNER_HEIGHT)
.clipped()
}.frame(height: BANNER_HEIGHT)
VStack(alignment: .leading) {
let pfp_size: CGFloat = 90.0
HStack(alignment: .center) {
ProfilePicView(pubkey: damus_state.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles)
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
Spacer()
}.padding(.bottom,-(pfp_size/2.0))
}
.padding(.horizontal,18)
.padding(.top,BANNER_HEIGHT)
}
return NIP05(username: String(parts[0]), host: String(parts[1]))
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Spacer()
InnerProfilePicView(url: URL(string: picture), pubkey: damus_state.pubkey, size: PPM_SIZE, highlight: .none)
Spacer()
}
TopSection
Form {
Section("Your Name") {
Section(NSLocalizedString("Your Name", comment: "Label for Your Name section of user profile form.")) {
TextField("Satoshi Nakamoto", text: $display_name)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section("Username") {
Section(NSLocalizedString("Username", comment: "Label for Username section of user profile form.")) {
TextField("satoshi", text: $name)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section ("Profile Picture") {
TextField("https://example.com/pic.jpg", text: $picture)
Section (NSLocalizedString("Profile Picture", comment: "Label for Profile Picture section of user profile form.")) {
TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $picture)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section("Website") {
TextField("https://jb55.com", text: $website)
Section (NSLocalizedString("Banner Image", comment: "Label for Banner Image section of user profile form.")) {
TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $banner)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section(NSLocalizedString("Website", comment: "Label for Website section of user profile form.")) {
TextField(NSLocalizedString("https://jb55.com", comment: "Placeholder example text for website URL for user profile."), text: $website)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section("About Me") {
Section(NSLocalizedString("About Me", comment: "Label for About Me section of user profile form.")) {
let placeholder = NSLocalizedString("Absolute Boss", comment: "Placeholder text for About Me description.")
ZStack(alignment: .topLeading) {
TextEditor(text: $about)
@@ -155,33 +180,33 @@ struct EditMetadataView: View {
}
}
Section("Bitcoin Lightning Tips") {
TextField("Lightning Address or LNURL", text: $ln)
Section(NSLocalizedString("Bitcoin Lightning Tips", comment: "Label for Bitcoin Lightning Tips section of user profile form.")) {
TextField(NSLocalizedString("Lightning Address or LNURL", comment: "Placeholder text for entry of Lightning Address or LNURL."), text: $ln)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section(content: {
TextField("jb55@jb55.com", text: $nip05)
TextField(NSLocalizedString("jb55@jb55.com", comment: "Placeholder example text for identifier used for NIP-05 verification."), text: $nip05)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}, header: {
Text("NIP-05 Verification")
Text("NIP-05 Verification", comment: "Label for NIP-05 Verification section of user profile form.")
}, footer: {
if let parts = nip05_parts {
Text(String.localizedStringWithFormat("'%@' at '%@' will be used for verification", parts.username, parts.host))
Text("'\(parts.username)' at '\(parts.host)' will be used for verification", comment: "Description of how the nip05 identifier would be used for verification.")
} else {
Text(String.localizedStringWithFormat("'%@' is an invalid nip05 identifier. It should look like an email.", nip05))
Text("'\(nip05)' is an invalid nip05 identifier. It should look like an email.", comment: "Description of why the nip05 identifier is invalid.")
}
})
Button("Save") {
Button(NSLocalizedString("Save", comment: "Button for saving profile.")) {
save()
dismiss()
}
}
}
.navigationTitle("Edit Profile")
.ignoresSafeArea()
}
}

View File

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

View File

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

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