Compare commits
1 Commits
tx_transla
...
tyiu/swap-
| Author | SHA1 | Date | |
|---|---|---|---|
|
cc95402581
|
103
CHANGELOG.md
@@ -1,105 +1,3 @@
|
||||
## [1.0.0] - 2023-01-01
|
||||
|
||||
### Added
|
||||
|
||||
- Parse links in profiles (Lionello Lunesu)
|
||||
- Added Breez wallet to wallet selector (Lee Salminen)
|
||||
- Added Bitcoin Beach wallet to wallet selector (Lee Salminen)
|
||||
- Added ability to copy relay urls (Matt Ward)
|
||||
- Added option to choose default wallet (Suhail Saqan)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Switch like from ❤️ to 🤙 (William Casarin)
|
||||
- Internationalize relative dates (Terry Yiu)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix but where text was not showing after invoices (William Casarin)
|
||||
- Load profiles in DMs and notifications (William Casarin)
|
||||
- Fix expanding profile picture (nosestr bug) (Joel Klabo)
|
||||
- Fix padding on threads and search results views (OlegAba)
|
||||
- Don't badge DMs if sent by you (Joel Klabo)
|
||||
- Reset relay in Add Relay view after adding (Joel Klabo)
|
||||
|
||||
|
||||
[1.0.0]: https://github.com/damus-io/damus/releases/tag/v1.0.0
|
||||
|
||||
## [0.1.8-9] - 2022-12-29
|
||||
|
||||
### Added
|
||||
|
||||
- Relay list on user profiles
|
||||
|
||||
### Changed
|
||||
|
||||
- Show recommended relays in config. Currently just a fixed set. (William Casarin)
|
||||
- Ensure contact relay list is kept in sync with internal relay pool (William Casarin)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where contact list would sometimes revert to an older version (William Casarin)
|
||||
- Don't show boosts in threads (Thomas)
|
||||
|
||||
|
||||
[0.1.8-9]: https://github.com/damus-io/damus/releases/tag/v0.1.8-9
|
||||
|
||||
## [0.1.8-6] - 2022-12-28
|
||||
|
||||
### Added
|
||||
|
||||
- Lightning wallet selector (Suhail Saqan)
|
||||
- Cmd-{1,2,3,4} to switch between tabs on MacOS (Jonathan Milligan)
|
||||
- Shift-Cmd-N to create a post on MacOS (Jonathan Milligan)
|
||||
- Link Previews! (Sam DuBois)
|
||||
- Added paste and delete buttons to add relay field (Suhail Saqan)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Blur and opaque non-friend images rather than only display the link (Sam DuBois)
|
||||
- Remove URLs in content text when image is displayed (Sam DuBois)
|
||||
- Show non-image URLs as clickable link views (Sam DuBois)
|
||||
- Adjusted Pay button on invoices. (Sam DuBois)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix crash with @ sign in some posts (Pablo Fernandez)
|
||||
- Swapped order of Logout and Cancel alert buttons (Terry Yiu)
|
||||
- Fixed padding issue on tabbar on some devices (Sam DuBois)
|
||||
- Fix post button moving after selecting from search result (OlegAba)
|
||||
- Don't show white background on images in dark mode (William Casarin)
|
||||
|
||||
|
||||
|
||||
[0.1.8-6]: https://github.com/damus-io/damus/releases/tag/v0.1.8-6
|
||||
## [0.1.8-5] - 2022-12-27
|
||||
|
||||
### Added
|
||||
|
||||
- Added the ability to zoom profile pic on profile page
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Improve visual composition of threads
|
||||
- Show npub abbreviations instead of old-style hex
|
||||
- Added search placeholder and larger cancel button
|
||||
- Swap order of Boost and Cancel alert buttons
|
||||
- Rename "Copy Note" to "Copy Note JSON"
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Don't cutoff gifs
|
||||
- Fixed bug where booster's names are not displayed
|
||||
|
||||
|
||||
|
||||
[0.1.8-5]: https://github.com/damus-io/damus/releases/tag/v0.1.8-5
|
||||
## [0.1.8-4] - 2022-12-26
|
||||
|
||||
### Added
|
||||
@@ -266,3 +164,4 @@
|
||||
|
||||
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
||||
|
||||
|
||||
|
||||
@@ -1,804 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
|
||||
<file original="damus/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
<source>Damus</source>
|
||||
<target>Damus</target>
|
||||
<note>Bundle display name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||
<source>damus</source>
|
||||
<target>damus</target>
|
||||
<note>Bundle name</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="damus/en.lproj/Localizable.strings" source-language="en" target-language="en" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<source>%@</source>
|
||||
<target>%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ following" xml:space="preserve">
|
||||
<source>%@ following</source>
|
||||
<target>%@ following</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@'s Followers" xml:space="preserve">
|
||||
<source>%@'s Followers</source>
|
||||
<target>%@'s Followers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." xml:space="preserve">
|
||||
<source>%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.</source>
|
||||
<target>%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" xml:space="preserve">
|
||||
<source>%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs</source>
|
||||
<target>%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet." xml:space="preserve">
|
||||
<source>%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet.</source>
|
||||
<target>%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld" xml:space="preserve">
|
||||
<source>%lld</source>
|
||||
<target>%lld</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld/%lld" xml:space="preserve">
|
||||
<source>%lld/%lld</source>
|
||||
<target>%lld/%lld</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="&nbsp;" xml:space="preserve">
|
||||
<source>&nbsp;</source>
|
||||
<target>&nbsp;</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="+" xml:space="preserve">
|
||||
<source>+</source>
|
||||
<target>+</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="< e >" xml:space="preserve">
|
||||
<source>< e ></source>
|
||||
<target>< e ></target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="@" xml:space="preserve">
|
||||
<source>@</source>
|
||||
<target>@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="About" xml:space="preserve">
|
||||
<source>About</source>
|
||||
<target>About</target>
|
||||
<note>Label to prompt for about text entry for user to describe about themself.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="About Me" xml:space="preserve">
|
||||
<source>About Me</source>
|
||||
<target>About Me</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Absolute Boss" xml:space="preserve">
|
||||
<source>Absolute Boss</source>
|
||||
<target>Absolute Boss</target>
|
||||
<note>Placeholder text for About Me description.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Account ID" xml:space="preserve">
|
||||
<source>Account ID</source>
|
||||
<target>Account ID</target>
|
||||
<note>Label to indicate the public ID of the account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add" xml:space="preserve">
|
||||
<source>Add</source>
|
||||
<target>Add</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add Relay" xml:space="preserve">
|
||||
<source>Add Relay</source>
|
||||
<target>Add Relay</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Are you sure you want to boost this post?" xml:space="preserve">
|
||||
<source>Are you sure you want to boost this post?</source>
|
||||
<target>Are you sure you want to boost this post?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus." xml:space="preserve">
|
||||
<source>Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus.</source>
|
||||
<target>Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Bitcoin Beach" xml:space="preserve">
|
||||
<source>Bitcoin Beach</source>
|
||||
<target>Bitcoin Beach</target>
|
||||
<note>Dropdown option label for Lightning wallet, Bitcoin Beach.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Bitcoin Lightning Tips" xml:space="preserve">
|
||||
<source>Bitcoin Lightning Tips</source>
|
||||
<target>Bitcoin Lightning Tips</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blixt Wallet" xml:space="preserve">
|
||||
<source>Blixt Wallet</source>
|
||||
<target>Blixt Wallet</target>
|
||||
<note>Dropdown option label for Lightning wallet, Blixt Wallet</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blue Wallet" xml:space="preserve">
|
||||
<source>Blue Wallet</source>
|
||||
<target>Blue Wallet</target>
|
||||
<note>Dropdown option label for Lightning wallet, Blue Wallet.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Boost" xml:space="preserve">
|
||||
<source>Boost</source>
|
||||
<target>Boost</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Boosted" xml:space="preserve">
|
||||
<source>Boosted</source>
|
||||
<target>Boosted</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Breez" xml:space="preserve">
|
||||
<source>Breez</source>
|
||||
<target>Breez</target>
|
||||
<note>Dropdown option label for Lightning wallet, Breez.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Broadcast" xml:space="preserve">
|
||||
<source>Broadcast</source>
|
||||
<target>Broadcast</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancel" xml:space="preserve">
|
||||
<source>Cancel</source>
|
||||
<target>Cancel</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cash App" xml:space="preserve">
|
||||
<source>Cash App</source>
|
||||
<target>Cash App</target>
|
||||
<note>Dropdown option label for Lightning wallet, Cash App.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copied" xml:space="preserve">
|
||||
<source>Copied</source>
|
||||
<target>Copied</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy" xml:space="preserve">
|
||||
<source>Copy</source>
|
||||
<target>Copy</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy Account ID" xml:space="preserve">
|
||||
<source>Copy Account ID</source>
|
||||
<target>Copy Account ID</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy Image" xml:space="preserve">
|
||||
<source>Copy Image</source>
|
||||
<target>Copy Image</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy Image URL" xml:space="preserve">
|
||||
<source>Copy Image URL</source>
|
||||
<target>Copy Image URL</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy LNURL" xml:space="preserve">
|
||||
<source>Copy LNURL</source>
|
||||
<target>Copy LNURL</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy Note ID" xml:space="preserve">
|
||||
<source>Copy Note ID</source>
|
||||
<target>Copy Note ID</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy Note JSON" xml:space="preserve">
|
||||
<source>Copy Note JSON</source>
|
||||
<target>Copy Note JSON</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy Text" xml:space="preserve">
|
||||
<source>Copy Text</source>
|
||||
<target>Copy Text</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy User ID" xml:space="preserve">
|
||||
<source>Copy User ID</source>
|
||||
<target>Copy User ID</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy invoice" xml:space="preserve">
|
||||
<source>Copy invoice</source>
|
||||
<target>Copy invoice</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create" xml:space="preserve">
|
||||
<source>Create</source>
|
||||
<target>Create</target>
|
||||
<note>Button to create account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create Account" xml:space="preserve">
|
||||
<source>Create Account</source>
|
||||
<target>Create Account</target>
|
||||
<note>Button to create an account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Creator(s) of Bitcoin. Absolute legend." xml:space="preserve">
|
||||
<source>Creator(s) of Bitcoin. Absolute legend.</source>
|
||||
<target>Creator(s) of Bitcoin. Absolute legend.</target>
|
||||
<note>Example description about Bitcoin creator(s), Satoshi Nakamoto.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="DM" xml:space="preserve">
|
||||
<source>DM</source>
|
||||
<target>DM</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Damus" xml:space="preserve">
|
||||
<source>Damus</source>
|
||||
<target>Damus</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Default Wallet" xml:space="preserve">
|
||||
<source>Default Wallet</source>
|
||||
<target>Default Wallet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete" xml:space="preserve">
|
||||
<source>Delete</source>
|
||||
<target>Delete</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Dismiss" xml:space="preserve">
|
||||
<source>Dismiss</source>
|
||||
<target>Dismiss</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Display Name" xml:space="preserve">
|
||||
<source>Display Name</source>
|
||||
<target>Display Name</target>
|
||||
<note>Label to prompt display name entry.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Done" xml:space="preserve">
|
||||
<source>Done</source>
|
||||
<target>Done</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Earn Money" xml:space="preserve">
|
||||
<source>Earn Money</source>
|
||||
<target>Earn Money</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Edit" xml:space="preserve">
|
||||
<source>Edit</source>
|
||||
<target>Edit</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Edit Profile" xml:space="preserve">
|
||||
<source>Edit Profile</source>
|
||||
<target>Edit Profile</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypted" xml:space="preserve">
|
||||
<source>Encrypted</source>
|
||||
<target>Encrypted</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypted DMs" xml:space="preserve">
|
||||
<source>Encrypted DMs</source>
|
||||
<target>Encrypted DMs</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter your account key to login:" xml:space="preserve">
|
||||
<source>Enter your account key to login:</source>
|
||||
<target>Enter your account key to login:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error: %@" xml:space="preserve">
|
||||
<source>Error: %@</source>
|
||||
<target>Error: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter State" xml:space="preserve">
|
||||
<source>Filter State</source>
|
||||
<target>Filter State</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Follow" xml:space="preserve">
|
||||
<source>Follow</source>
|
||||
<target>Follow</target>
|
||||
<note>Button to follow a user.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Followers" xml:space="preserve">
|
||||
<source>Followers</source>
|
||||
<target>Followers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Following" xml:space="preserve">
|
||||
<source>Following</source>
|
||||
<target>Following</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Following..." xml:space="preserve">
|
||||
<source>Following...</source>
|
||||
<target>Following...</target>
|
||||
<note>Label to indicate that the user is in the process of following another user.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Follows" xml:space="preserve">
|
||||
<source>Follows</source>
|
||||
<target>Follows</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Global" xml:space="preserve">
|
||||
<source>Global</source>
|
||||
<target>Global</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Goto post %@" xml:space="preserve">
|
||||
<source>Goto post %@</source>
|
||||
<target>Goto post %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Goto profile %@" xml:space="preserve">
|
||||
<source>Goto profile %@</source>
|
||||
<target>Goto profile %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Home" xml:space="preserve">
|
||||
<source>Home</source>
|
||||
<target>Home</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="LNLink" xml:space="preserve">
|
||||
<source>LNLink</source>
|
||||
<target>LNLink</target>
|
||||
<note>Dropdown option label for Lightning wallet, LNLink.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Let's go!" xml:space="preserve">
|
||||
<source>Let's go!</source>
|
||||
<target>Let's go!</target>
|
||||
<note>Button to complete account creation and start using the app.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Lightning Address or LNURL" xml:space="preserve">
|
||||
<source>Lightning Address or LNURL</source>
|
||||
<target>Lightning Address or LNURL</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Lightning Invoice" xml:space="preserve">
|
||||
<source>Lightning Invoice</source>
|
||||
<target>Lightning Invoice</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local default" xml:space="preserve">
|
||||
<source>Local default</source>
|
||||
<target>Local default</target>
|
||||
<note>Dropdown option label for system default for Lightning wallet.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Login" xml:space="preserve">
|
||||
<source>Login</source>
|
||||
<target>Login</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Logout" xml:space="preserve">
|
||||
<source>Logout</source>
|
||||
<target>Logout</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Make sure your nsec account key is saved before you logout or you will lose access to this account" xml:space="preserve">
|
||||
<source>Make sure your nsec account key is saved before you logout or you will lose access to this account</source>
|
||||
<target>Make sure your nsec account key is saved before you logout or you will lose access to this account</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Muun" xml:space="preserve">
|
||||
<source>Muun</source>
|
||||
<target>Muun</target>
|
||||
<note>Dropdown option label for Lightning wallet, Muun.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NIP-05 Verification" xml:space="preserve">
|
||||
<source>NIP-05 Verification</source>
|
||||
<target>NIP-05 Verification</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nothing to see here. Check back later!" xml:space="preserve">
|
||||
<source>Nothing to see here. Check back later!</source>
|
||||
<target>Nothing to see here. Check back later!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Notifications</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Pay" xml:space="preserve">
|
||||
<source>Pay</source>
|
||||
<target>Pay</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Pay the lightning invoice" xml:space="preserve">
|
||||
<source>Pay the lightning invoice</source>
|
||||
<target>Pay the lightning invoice</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Phoenix" xml:space="preserve">
|
||||
<source>Phoenix</source>
|
||||
<target>Phoenix</target>
|
||||
<note>Dropdown option label for Lightning wallet, Phoenix.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Post" xml:space="preserve">
|
||||
<source>Post</source>
|
||||
<target>Post</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Posts" xml:space="preserve">
|
||||
<source>Posts</source>
|
||||
<target>Posts</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Posts & Replies" xml:space="preserve">
|
||||
<source>Posts & Replies</source>
|
||||
<target>Posts & Replies</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Private" xml:space="preserve">
|
||||
<source>Private</source>
|
||||
<target>Private</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Private Key" xml:space="preserve">
|
||||
<source>Private Key</source>
|
||||
<target>Private Key</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PrivateKey" xml:space="preserve">
|
||||
<source>PrivateKey</source>
|
||||
<target>PrivateKey</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile Picture" xml:space="preserve">
|
||||
<source>Profile Picture</source>
|
||||
<target>Profile Picture</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Public Account ID" xml:space="preserve">
|
||||
<source>Public Account ID</source>
|
||||
<target>Public Account ID</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Public Key" xml:space="preserve">
|
||||
<source>Public Key</source>
|
||||
<target>Public Key</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Public Key?" xml:space="preserve">
|
||||
<source>Public Key?</source>
|
||||
<target>Public Key?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recommended Relays" xml:space="preserve">
|
||||
<source>Recommended Relays</source>
|
||||
<target>Recommended Relays</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Relay" xml:space="preserve">
|
||||
<source>Relay</source>
|
||||
<target>Relay</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Relays" xml:space="preserve">
|
||||
<source>Relays</source>
|
||||
<target>Relays</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply to self" xml:space="preserve">
|
||||
<source>Reply to self</source>
|
||||
<target>Reply to self</target>
|
||||
<note>Label to indicate that the user is replying to themself.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Replying to:" xml:space="preserve">
|
||||
<source>Replying to:</source>
|
||||
<target>Replying to:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reset" xml:space="preserve">
|
||||
<source>Reset</source>
|
||||
<target>Reset</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="River" xml:space="preserve">
|
||||
<source>River</source>
|
||||
<target>River</target>
|
||||
<note>Dropdown option label for Lightning wallet, River</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Satoshi Nakamoto" xml:space="preserve">
|
||||
<source>Satoshi Nakamoto</source>
|
||||
<target>Satoshi Nakamoto</target>
|
||||
<note>Name of Bitcoin creator(s).</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save" xml:space="preserve">
|
||||
<source>Save</source>
|
||||
<target>Save</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search hashtag: #%@" xml:space="preserve">
|
||||
<source>Search hashtag: #%@</source>
|
||||
<target>Search hashtag: #%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search..." xml:space="preserve">
|
||||
<source>Search...</source>
|
||||
<target>Search...</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Secret Account Login Key" xml:space="preserve">
|
||||
<source>Secret Account Login Key</source>
|
||||
<target>Secret Account Login Key</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Select a lightning wallet" xml:space="preserve">
|
||||
<source>Select a lightning wallet</source>
|
||||
<target>Select a lightning wallet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Select default wallet" xml:space="preserve">
|
||||
<source>Select default wallet</source>
|
||||
<target>Select default wallet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send a message to start the conversation..." xml:space="preserve">
|
||||
<source>Send a message to start the conversation...</source>
|
||||
<target>Send a message to start the conversation...</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Settings" xml:space="preserve">
|
||||
<source>Settings</source>
|
||||
<target>Settings</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share" xml:space="preserve">
|
||||
<source>Share</source>
|
||||
<target>Share</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show" xml:space="preserve">
|
||||
<source>Show</source>
|
||||
<target>Show</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show wallet selector" xml:space="preserve">
|
||||
<source>Show wallet selector</source>
|
||||
<target>Show wallet selector</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Strike" xml:space="preserve">
|
||||
<source>Strike</source>
|
||||
<target>Strike</target>
|
||||
<note>Dropdown option label for Lightning wallet, Strike.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." xml:space="preserve">
|
||||
<source>This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective.</source>
|
||||
<target>This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key." xml:space="preserve">
|
||||
<source>This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key.</source>
|
||||
<target>This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is your account ID, you can give this to your friends so that they can follow you. Click to copy." xml:space="preserve">
|
||||
<source>This is your account ID, you can give this to your friends so that they can follow you. Click to copy.</source>
|
||||
<target>This is your account ID, you can give this to your friends so that they can follow you. Click to copy.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!" xml:space="preserve">
|
||||
<source>This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!</source>
|
||||
<target>This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Thread" xml:space="preserve">
|
||||
<source>Thread</source>
|
||||
<target>Thread</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Type your post here..." xml:space="preserve">
|
||||
<source>Type your post here...</source>
|
||||
<target>Type your post here...</target>
|
||||
<note>Text box prompt to ask user to type their post.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unfollow" xml:space="preserve">
|
||||
<source>Unfollow</source>
|
||||
<target>Unfollow</target>
|
||||
<note>Button to unfollow a user.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unfollowing" xml:space="preserve">
|
||||
<source>Unfollowing</source>
|
||||
<target>Unfollowing</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unfollowing..." xml:space="preserve">
|
||||
<source>Unfollowing...</source>
|
||||
<target>Unfollowing...</target>
|
||||
<note>Label to indicate that the user is in the process of unfollowing another user.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unfollows" xml:space="preserve">
|
||||
<source>Unfollows</source>
|
||||
<target>Unfollows</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Username" xml:space="preserve">
|
||||
<source>Username</source>
|
||||
<target>Username</target>
|
||||
<note>Label to prompt username entry.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wallet Of Satoshi" xml:space="preserve">
|
||||
<source>Wallet Of Satoshi</source>
|
||||
<target>Wallet Of Satoshi</target>
|
||||
<note>Dropdown option label for Lightning wallet, Wallet Of Satoshi.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wallet Selector" xml:space="preserve">
|
||||
<source>Wallet Selector</source>
|
||||
<target>Wallet Selector</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Website" xml:space="preserve">
|
||||
<source>Website</source>
|
||||
<target>Website</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Welcome to the social network %@ control." xml:space="preserve">
|
||||
<source>Welcome to the social network %@ control.</source>
|
||||
<target>Welcome to the social network %@ control.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Welcome, %@!" xml:space="preserve">
|
||||
<source>Welcome, %@!</source>
|
||||
<target>Welcome, %@!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your Name" xml:space="preserve">
|
||||
<source>Your Name</source>
|
||||
<target>Your Name</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Zebedee" xml:space="preserve">
|
||||
<source>Zebedee</source>
|
||||
<target>Zebedee</target>
|
||||
<note>Dropdown option label for Lightning wallet, Zebedee.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Zeus LN" xml:space="preserve">
|
||||
<source>Zeus LN</source>
|
||||
<target>Zeus LN</target>
|
||||
<note>Dropdown option label for Lightning wallet, Zeus LN.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="https://example.com/pic.jpg" xml:space="preserve">
|
||||
<source>https://example.com/pic.jpg</source>
|
||||
<target>https://example.com/pic.jpg</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="https://jb55.com" xml:space="preserve">
|
||||
<source>https://jb55.com</source>
|
||||
<target>https://jb55.com</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="jb55@jb55.com" xml:space="preserve">
|
||||
<source>jb55@jb55.com</source>
|
||||
<target>jb55@jb55.com</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="none" xml:space="preserve">
|
||||
<source>none</source>
|
||||
<target>none</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="now" xml:space="preserve">
|
||||
<source>now</source>
|
||||
<target>now</target>
|
||||
<note>String indicating that a given timestamp just occurred</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="optional" xml:space="preserve">
|
||||
<source>optional</source>
|
||||
<target>optional</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="replying_to_one_and_others" translate="no" xml:space="preserve">
|
||||
<source>replying_to_one_and_others</source>
|
||||
<target>replying_to_one_and_others</target>
|
||||
<note>Label to indicate that the user is replying to 1 user and others. (Key in .stringsdict)</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="replying_to_two_and_others" translate="no" xml:space="preserve">
|
||||
<source>replying_to_two_and_others</source>
|
||||
<target>replying_to_two_and_others</target>
|
||||
<note>Label to indicate that the user is replying to 2 users and others. (Key in .stringsdict)</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="satoshi" xml:space="preserve">
|
||||
<source>satoshi</source>
|
||||
<target>satoshi</target>
|
||||
<note>Example username of Bitcoin creator(s), Satoshi Nakamoto.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="wss://some.relay.com" xml:space="preserve">
|
||||
<source>wss://some.relay.com</source>
|
||||
<target>wss://some.relay.com</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you" xml:space="preserve">
|
||||
<source>you</source>
|
||||
<target>you</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="··· %lld other notes ···" xml:space="preserve">
|
||||
<source>··· %lld other notes ···</source>
|
||||
<target>··· %lld other notes ···</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="🤙" xml:space="preserve">
|
||||
<source>🤙</source>
|
||||
<target>🤙</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="damus/en.lproj/Localizable.stringsdict" source-language="en" target-language="en" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="/replying_to_one_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
<source>Replying to %@%#@others@</source>
|
||||
<target>Replying to %@%#@others@</target>
|
||||
<note>Label to indicate that the user is replying to 1 user and others.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="/replying_to_one_and_others:dict/others:dict/one:dict/:string" xml:space="preserve">
|
||||
<source> & 1 other</source>
|
||||
<target> & 1 other</target>
|
||||
<note>Label to indicate that the user is replying to 1 user and others.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="/replying_to_one_and_others:dict/others:dict/other:dict/:string" xml:space="preserve">
|
||||
<source> & %d others</source>
|
||||
<target> & %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> & 1 other</source>
|
||||
<target> & 1 other</target>
|
||||
<note>Label to indicate that the user is replying to 2 users and others.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="/replying_to_two_and_others:dict/others:dict/other:dict/:string" xml:space="preserve">
|
||||
<source> & %d others</source>
|
||||
<target> & %d others</target>
|
||||
<note>Label to indicate that the user is replying to 2 users and others.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="/replying_to_two_and_others:dict/others:dict/zero:dict/:string" xml:space="preserve">
|
||||
<source/>
|
||||
<target/>
|
||||
<note>Label to indicate that the user is replying to 2 users and others.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -1,4 +0,0 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "Damus";
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "damus";
|
||||
@@ -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> & 1 other</string>
|
||||
<key>other</key>
|
||||
<string> & %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> & 1 other</string>
|
||||
<key>other</key>
|
||||
<string> & %d others</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"developmentRegion" : "en",
|
||||
"project" : "damus.xcodeproj",
|
||||
"targetLocale" : "en",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
@@ -151,7 +151,7 @@ static bool from_numbers(u64 *res,
|
||||
return false;
|
||||
|
||||
if (!from_number(&p1, s1, len1, tens_factor)
|
||||
|| !from_number(&p2, s2, len2, tens_factor - (int)len2))
|
||||
|| !from_number(&p2, s2, len2, tens_factor - len2))
|
||||
return false;
|
||||
|
||||
if (add_overflows_u64(p1, p2))
|
||||
@@ -453,7 +453,7 @@ bool amount_msat_to_u32(struct amount_msat msat, u32 *millisatoshis)
|
||||
{
|
||||
if (amount_msat_greater_eq(msat, AMOUNT_MSAT(0x100000000)))
|
||||
return false;
|
||||
*millisatoshis = (u32)msat.millisatoshis;
|
||||
*millisatoshis = msat.millisatoshis;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ static char *decode_c(struct bolt11 *b11,
|
||||
if (!pull_uint(hu5, data, data_len, &c, data_length * 5))
|
||||
return tal_fmt(b11, "c: length %zu chars is excessive",
|
||||
*data_len);
|
||||
b11->min_final_cltv_expiry = (u32)c;
|
||||
b11->min_final_cltv_expiry = c;
|
||||
/* Can overflow, since c is 64 bits but value must be < 32 bits */
|
||||
if (b11->min_final_cltv_expiry != c)
|
||||
return tal_fmt(b11, "c: %"PRIu64" is too large", c);
|
||||
|
||||
@@ -241,7 +241,7 @@ static int parse_invoice(struct cursor *cur, struct block *block) {
|
||||
block->block.invoice.invstr.end = (const char*)end;
|
||||
block->block.invoice.bolt11 = bolt11;
|
||||
|
||||
cur->p = end;
|
||||
cur->p += end - start;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ void hash_u5(struct hash_u5 *hu5, const u8 *u5, size_t len)
|
||||
u5++;
|
||||
|
||||
if (hu5->num_bits >= 32) {
|
||||
be32 be32 = cpu_to_be32((u32)(hu5->buf >> (hu5->num_bits-32)));
|
||||
be32 be32 = cpu_to_be32(hu5->buf >> (hu5->num_bits-32));
|
||||
sha256_update(&hu5->hash, &be32, sizeof(be32));
|
||||
hu5->num_bits -= 32;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ void hash_u5(struct hash_u5 *hu5, const u8 *u5, size_t len)
|
||||
void hash_u5_done(struct hash_u5 *hu5, struct sha256 *res)
|
||||
{
|
||||
if (hu5->num_bits) {
|
||||
be32 be32 = cpu_to_be32((u32)(hu5->buf << (32 - hu5->num_bits)));
|
||||
be32 be32 = cpu_to_be32(hu5->buf << (32 - hu5->num_bits));
|
||||
|
||||
sha256_update(&hu5->hash, &be32, (hu5->num_bits + 7) / 8);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ static inline bool assign_overflow_u16(u16 *dst, uint64_t v)
|
||||
|
||||
static inline bool assign_overflow_u32(u32 *dst, uint64_t v)
|
||||
{
|
||||
*dst = (u32)v;
|
||||
*dst = v;
|
||||
return *dst == v;
|
||||
}
|
||||
#endif /* LIGHTNING_COMMON_OVERFLOWS_H */
|
||||
|
||||
@@ -7,14 +7,9 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
3165648B295B70D500C64604 /* LinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3165648A295B70D500C64604 /* LinkView.swift */; };
|
||||
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; };
|
||||
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
|
||||
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
|
||||
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
|
||||
3AB18056296375CA00FD1BD8 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3AB18052296375CA00FD1BD8 /* InfoPlist.strings */; };
|
||||
3AB18057296375CA00FD1BD8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3AB18054296375CA00FD1BD8 /* Localizable.strings */; };
|
||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
|
||||
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; };
|
||||
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; };
|
||||
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; };
|
||||
@@ -52,7 +47,6 @@
|
||||
4C363AA228296A7E006E126D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA128296A7E006E126D /* SearchView.swift */; };
|
||||
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA328296DEE006E126D /* SearchModel.swift */; };
|
||||
4C363AA828297703006E126D /* InsertSort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA728297703006E126D /* InsertSort.swift */; };
|
||||
4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3A1D322960DB0500558C0F /* Markdown.swift */; };
|
||||
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 */; };
|
||||
@@ -114,8 +108,6 @@
|
||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
@@ -137,12 +129,8 @@
|
||||
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */; };
|
||||
4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */; };
|
||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
|
||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
||||
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
|
||||
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
|
||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -163,18 +151,9 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
3165648A295B70D500C64604 /* LinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkView.swift; sourceTree = "<group>"; };
|
||||
3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = "<group>"; };
|
||||
3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; };
|
||||
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>"; };
|
||||
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; sourceTree = "<group>"; };
|
||||
4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = "<group>"; };
|
||||
4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; };
|
||||
4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
@@ -214,7 +193,6 @@
|
||||
4C363AA128296A7E006E126D /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
||||
4C363AA328296DEE006E126D /* SearchModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModel.swift; sourceTree = "<group>"; };
|
||||
4C363AA728297703006E126D /* InsertSort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertSort.swift; sourceTree = "<group>"; };
|
||||
4C3A1D322960DB0500558C0F /* Markdown.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Markdown.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -306,8 +284,6 @@
|
||||
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
|
||||
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
|
||||
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
|
||||
4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedRelayView.swift; sourceTree = "<group>"; };
|
||||
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRelaysView.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>"; };
|
||||
@@ -332,11 +308,7 @@
|
||||
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileName.swift; sourceTree = "<group>"; };
|
||||
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = "<group>"; };
|
||||
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = "<group>"; };
|
||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -460,8 +432,6 @@
|
||||
4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */,
|
||||
4C216F372871EDE300040376 /* DirectMessageModel.swift */,
|
||||
4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */,
|
||||
BA693073295D649800ADDB87 /* UserSettingsStore.swift */,
|
||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -508,10 +478,6 @@
|
||||
4C216F33286F5ACD00040376 /* DMView.swift */,
|
||||
4C06670028FC7C5900038D2A /* RelayView.swift */,
|
||||
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
|
||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */,
|
||||
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */,
|
||||
4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */,
|
||||
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -539,7 +505,6 @@
|
||||
4C7FF7D628233637009601DB /* Util */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C3A1D322960DB0500558C0F /* Markdown.swift */,
|
||||
4CEE2AF4280B29E600AB5EEF /* TimeAgo.swift */,
|
||||
4CE4F8CC281352B30009DFBB /* Notifications.swift */,
|
||||
4C363A8328233689006E126D /* Parser.swift */,
|
||||
@@ -549,7 +514,6 @@
|
||||
4C90BD19283AA67F008EE7EF /* Bech32.swift */,
|
||||
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */,
|
||||
3169CAEC294FCCFC00EE4006 /* Constants.swift */,
|
||||
3165648A295B70D500C64604 /* LinkView.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
@@ -603,9 +567,6 @@
|
||||
4CE6DEE827F7A08100C66700 /* ContentView.swift */,
|
||||
4CE6DEEA27F7A08200C66700 /* Assets.xcassets */,
|
||||
4CE6DEEC27F7A08200C66700 /* Preview Content */,
|
||||
3AB18052296375CA00FD1BD8 /* InfoPlist.strings */,
|
||||
3AB18054296375CA00FD1BD8 /* Localizable.strings */,
|
||||
3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */,
|
||||
);
|
||||
path = damus;
|
||||
sourceTree = "<group>";
|
||||
@@ -626,7 +587,6 @@
|
||||
4C363A9D2828A822006E126D /* ReplyTests.swift */,
|
||||
4CE6DEF727F7A08200C66700 /* damusTests.swift */,
|
||||
4C3EA67A28FF7B3900C48A62 /* InvoiceTests.swift */,
|
||||
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */,
|
||||
);
|
||||
path = damusTests;
|
||||
sourceTree = "<group>";
|
||||
@@ -741,8 +701,6 @@
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
es,
|
||||
fr,
|
||||
);
|
||||
mainGroup = 4CE6DEDA27F7A08100C66700;
|
||||
packageReferences = (
|
||||
@@ -768,11 +726,8 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3AB18057296375CA00FD1BD8 /* Localizable.strings in Resources */,
|
||||
4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */,
|
||||
3AB18056296375CA00FD1BD8 /* InfoPlist.strings in Resources */,
|
||||
4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */,
|
||||
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -814,7 +769,6 @@
|
||||
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
|
||||
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,
|
||||
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
|
||||
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
|
||||
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
||||
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
||||
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
||||
@@ -836,10 +790,8 @@
|
||||
4C363A8428233689006E126D /* Parser.swift in Sources */,
|
||||
4CE4F9E328528C5200C00DD9 /* AddRelayView.swift in Sources */,
|
||||
4C363A9A28283854006E126D /* Reply.swift in Sources */,
|
||||
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */,
|
||||
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
|
||||
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
|
||||
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
|
||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
||||
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
||||
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
||||
@@ -852,7 +804,6 @@
|
||||
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
||||
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
|
||||
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
|
||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */,
|
||||
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */,
|
||||
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
|
||||
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
|
||||
@@ -883,7 +834,6 @@
|
||||
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
||||
4CEE2AF3280B25C500AB5EEF /* ProfilePicView.swift in Sources */,
|
||||
4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */,
|
||||
3165648B295B70D500C64604 /* LinkView.swift in Sources */,
|
||||
4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */,
|
||||
4C3EA66028FF5E7700C48A62 /* node_id.c in Sources */,
|
||||
4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */,
|
||||
@@ -898,17 +848,14 @@
|
||||
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
|
||||
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
||||
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
||||
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
||||
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
||||
4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */,
|
||||
4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */,
|
||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */,
|
||||
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */,
|
||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
|
||||
4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */,
|
||||
4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */,
|
||||
4C06670B28FDE64700038D2A /* damus.c in Sources */,
|
||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */,
|
||||
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */,
|
||||
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
||||
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
||||
@@ -923,7 +870,6 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
||||
4C3EA67B28FF7B3900C48A62 /* InvoiceTests.swift in Sources */,
|
||||
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */,
|
||||
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
|
||||
@@ -956,43 +902,11 @@
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
3A4325A92961E11400BFCD9D /* en */,
|
||||
3AB1803D29636FB100FD1BD8 /* es */,
|
||||
3AB1805C2963EF7E00FD1BD8 /* fr */,
|
||||
);
|
||||
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 */
|
||||
4CE6DF0527F7A08200C66700 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
@@ -1039,7 +953,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.3;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
@@ -1054,7 +968,6 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
@@ -1095,7 +1008,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.3;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
@@ -1114,14 +1027,13 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = damus/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Damus;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
@@ -1135,7 +1047,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 0.1.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@@ -1154,14 +1066,13 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = damus/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Damus;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
@@ -1175,7 +1086,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 0.1.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SparrowTek/Vault",
|
||||
"state" : {
|
||||
"revision" : "87db56c3c8b6421c65b0745f73e08b0dc56f79d4",
|
||||
"version" : "1.0.3"
|
||||
"revision" : "f5707fac23f4a17b3e5ed32dd444f502773615ae",
|
||||
"version" : "1.0.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
21
damus/Assets.xcassets/bbw.imageset/Contents.json
vendored
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bbw.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/bbw.imageset/bbw.jpg
vendored
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "blixt-wallet.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 215 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bluewallet.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 61 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "breez.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/breez.imageset/breez.jpg
vendored
|
Before Width: | Height: | Size: 21 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "cashapp.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/cashapp.imageset/cashapp.png
vendored
|
Before Width: | Height: | Size: 44 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lnlink.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/lnlink.imageset/lnlink.png
vendored
|
Before Width: | Height: | Size: 547 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "muun.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/muun.imageset/muun.png
vendored
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "phoenix.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/phoenix.imageset/phoenix.png
vendored
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "river.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/river.imageset/river.png
vendored
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "strike.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/strike.imageset/strike.png
vendored
|
Before Width: | Height: | Size: 23 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "walletofsatoshi.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 42 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "zebedee.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/zebedee.imageset/zebedee.png
vendored
|
Before Width: | Height: | Size: 78 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "zeus.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/zeusln.imageset/zeus.png
vendored
|
Before Width: | Height: | Size: 109 KiB |
@@ -122,7 +122,6 @@ struct ImageCarousel: View {
|
||||
TabView {
|
||||
ForEach(urls, id: \.absoluteString) { url in
|
||||
Rectangle()
|
||||
.foregroundColor(Color.clear)
|
||||
.overlay {
|
||||
KFAnimatedImage(url)
|
||||
.configure { view in
|
||||
@@ -132,16 +131,11 @@ struct ImageCarousel: View {
|
||||
.loadDiskFileSynchronously()
|
||||
.scaleFactor(UIScreen.main.scale)
|
||||
.fade(duration: 0.1)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.tabItem {
|
||||
Text(url.absoluteString)
|
||||
}
|
||||
.id(url.absoluteString)
|
||||
.contextMenu {
|
||||
Button("Copy Image") {
|
||||
UIPasteboard.general.string = url.absoluteString
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,63 +7,25 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
func open_with_wallet(wallet: Wallet.Model, invoice: String) {
|
||||
if let url = URL(string: "\(wallet.link)\(invoice)"), UIApplication.shared.canOpenURL(url) {
|
||||
UIApplication.shared.open(url)
|
||||
} else {
|
||||
guard let store_link = wallet.appStoreLink else {
|
||||
// TODO: do something here if we don't have an appstore link
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = URL(string: store_link) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard UIApplication.shared.canOpenURL(url) else {
|
||||
return
|
||||
}
|
||||
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
|
||||
struct InvoiceView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.openURL) private var openURL
|
||||
|
||||
let invoice: Invoice
|
||||
@State var showing_select_wallet: Bool = false
|
||||
@ObservedObject var user_settings = UserSettingsStore()
|
||||
|
||||
var PayButton: some View {
|
||||
Button {
|
||||
if user_settings.show_wallet_selector {
|
||||
showing_select_wallet = true
|
||||
} else {
|
||||
open_with_wallet(wallet: user_settings.default_wallet.model, invoice: invoice.string)
|
||||
Button("Pay") {
|
||||
guard let url = URL(string: "lightning:" + invoice.string) else {
|
||||
return
|
||||
}
|
||||
} label: {
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
.foregroundColor(colorScheme == .light ? .black : .white)
|
||||
.overlay {
|
||||
Text("Pay")
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(colorScheme == .light ? .white : .black)
|
||||
}
|
||||
}
|
||||
//.buttonStyle(.bordered)
|
||||
.onTapGesture {
|
||||
// Temporary solution so that the "pay" button can be clicked (Yes we need an empty tap gesture)
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
.foregroundColor(.secondary.opacity(0.1))
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
VStack(alignment: .trailing, spacing: 12) {
|
||||
HStack {
|
||||
Label("", systemImage: "bolt.fill")
|
||||
.foregroundColor(.orange)
|
||||
@@ -71,21 +33,17 @@ struct InvoiceView: View {
|
||||
}
|
||||
Divider()
|
||||
Text(invoice.description)
|
||||
Text(invoice.amount.amount_sats_str())
|
||||
Text("\(invoice.amount / 1000) sats")
|
||||
.font(.title)
|
||||
PayButton
|
||||
.frame(height: 50)
|
||||
.zIndex(10.0)
|
||||
.zIndex(5.0)
|
||||
}
|
||||
.padding(30)
|
||||
}
|
||||
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
|
||||
SelectWalletView(showingSelectWallet: $showing_select_wallet, invoice: invoice.string).environmentObject(user_settings)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let test_invoice = Invoice(description: "this is a description", amount: .specific(10000), string: "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r", expiry: 604800, payment_hash: Data(), created_at: 1666139119)
|
||||
let test_invoice = Invoice(description: "this is a description", amount: 10000, string: "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r", expiry: 604800, payment_hash: Data(), created_at: 1666139119)
|
||||
|
||||
struct InvoiceView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
@@ -23,14 +23,13 @@ struct InvoicesView: View {
|
||||
.id(invoice.string)
|
||||
}
|
||||
}
|
||||
.frame(height: 240)
|
||||
.frame(height: 200)
|
||||
.tabViewStyle(PageTabViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
struct InvoicesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InvoicesView(invoices: [Invoice.init(description: "description", amount: .specific(10000), string: "invstr", expiry: 100000, payment_hash: Data(), created_at: 1000000)])
|
||||
.frame(width: 300)
|
||||
InvoicesView(invoices: [Invoice.init(description: "description", amount: 10000, string: "invstr", expiry: 100000, payment_hash: Data(), created_at: 1000000)])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,7 @@ var BOOTSTRAP_RELAYS = [
|
||||
"wss://relay.damus.io",
|
||||
"wss://nostr-relay.wlvs.space",
|
||||
"wss://nostr.fmt.wiz.biz",
|
||||
"wss://relay.nostr.bg",
|
||||
"wss://nostr.oxtr.dev",
|
||||
"wss://nostr.v0l.io",
|
||||
"wss://brb.io",
|
||||
]
|
||||
|
||||
struct TimestampedProfile {
|
||||
@@ -72,7 +69,6 @@ struct ContentView: View {
|
||||
@State var search_open: Bool = false
|
||||
@State var filter_state : FilterState = .posts_and_replies
|
||||
@StateObject var home: HomeModel = HomeModel()
|
||||
@StateObject var user_settings = UserSettingsStore()
|
||||
|
||||
// connect retry timer
|
||||
let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect()
|
||||
@@ -82,14 +78,17 @@ struct ContentView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
var PostingTimelineView: some View {
|
||||
VStack {
|
||||
TabView(selection: $filter_state) {
|
||||
ContentTimelineView
|
||||
.tag(FilterState.posts)
|
||||
ContentTimelineView
|
||||
.tag(FilterState.posts_and_replies)
|
||||
VStack{
|
||||
ZStack {
|
||||
if let damus = self.damus_state {
|
||||
TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter_event)
|
||||
}
|
||||
if privkey != nil {
|
||||
PostButtonContainer {
|
||||
self.active_sheet = .post
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
}
|
||||
.safeAreaInset(edge: .top) {
|
||||
VStack(spacing: 0) {
|
||||
@@ -103,19 +102,6 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
var ContentTimelineView: 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)
|
||||
}
|
||||
if privkey != nil {
|
||||
PostButtonContainer {
|
||||
self.active_sheet = .post
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var FiltersView: some View {
|
||||
VStack{
|
||||
Picker("Filter State", selection: $filter_state) {
|
||||
@@ -180,7 +166,8 @@ struct ContentView: View {
|
||||
var MaybeThreadView: some View {
|
||||
Group {
|
||||
if let evid = self.active_event_id {
|
||||
BuildThreadV2View(damus: damus_state!, event_id: evid)
|
||||
let thread_model = ThreadModel(evid: evid, damus_state: damus_state!)
|
||||
ThreadView(thread: thread_model, damus: damus_state!, is_chatroom: false)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
@@ -230,7 +217,7 @@ struct ContentView: View {
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
NavigationLink(destination: ConfigView(state: damus_state!).environmentObject(user_settings)) {
|
||||
NavigationLink(destination: ConfigView(state: damus_state!)) {
|
||||
Label("", systemImage: "gear")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
@@ -242,7 +229,6 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
TabBar(new_events: $home.new_events, selected: $selected_timeline, action: switch_timeline)
|
||||
.padding([.bottom], 8)
|
||||
}
|
||||
.onAppear() {
|
||||
self.connect()
|
||||
|
||||
@@ -15,23 +15,6 @@
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>river</string>
|
||||
<string>bitcoinbeach</string>
|
||||
<string>breez</string>
|
||||
<string>muun</string>
|
||||
<string>zeusln</string>
|
||||
<string>zebedee</string>
|
||||
<string>lightning</string>
|
||||
<string>squarecash</string>
|
||||
<string>phoenix</string>
|
||||
<string>lnlink</string>
|
||||
<string>strike</string>
|
||||
<string>bluewallet</string>
|
||||
<string>walletofsatoshi</string>
|
||||
<string>blixtwallet</string>
|
||||
</array>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
||||
@@ -139,12 +139,16 @@ func decode_json_relays(_ content: String) -> [String: RelayInfo]? {
|
||||
return decode_json(content)
|
||||
}
|
||||
|
||||
func remove_relay(ev: NostrEvent, current_relays: [RelayDescriptor], privkey: String, relay: String) -> NostrEvent? {
|
||||
var relays = ensure_relay_info(relays: current_relays, content: ev.content)
|
||||
func remove_relay(ev: NostrEvent, privkey: String, relay: String) -> NostrEvent? {
|
||||
let damus_relay = RelayDescriptor(url: URL(string: "wss://relay.damus.io")!, info: .rw)
|
||||
|
||||
var relays = ensure_relay_info(relays: [damus_relay], content: ev.content)
|
||||
guard relays.index(forKey: relay) != nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
relays.removeValue(forKey: relay)
|
||||
|
||||
print("remove_relay \(relays)")
|
||||
guard let content = encode_json(relays) else {
|
||||
return nil
|
||||
}
|
||||
@@ -155,9 +159,10 @@ func remove_relay(ev: NostrEvent, current_relays: [RelayDescriptor], privkey: St
|
||||
return new_ev
|
||||
}
|
||||
|
||||
func add_relay(ev: NostrEvent, privkey: String, current_relays: [RelayDescriptor], relay: String, info: RelayInfo) -> NostrEvent? {
|
||||
var relays = ensure_relay_info(relays: current_relays, content: ev.content)
|
||||
func add_relay(ev: NostrEvent, privkey: String, relay: String, info: RelayInfo) -> NostrEvent? {
|
||||
let damus_relay = RelayDescriptor(url: URL(string: "wss://relay.damus.io")!, info: .rw)
|
||||
|
||||
var relays = ensure_relay_info(relays: [damus_relay], content: ev.content)
|
||||
guard relays.index(forKey: relay) == nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ class HomeModel: ObservableObject {
|
||||
let notifications_subid = UUID().description
|
||||
let dms_subid = UUID().description
|
||||
let init_subid = UUID().description
|
||||
let profiles_subid = UUID().description
|
||||
|
||||
@Published var new_events: NewEventsBits = NewEventsBits()
|
||||
@Published var notifications: [NostrEvent] = []
|
||||
@@ -235,15 +234,7 @@ class HomeModel: ObservableObject {
|
||||
//self.events.insert(NostrEvent(content: "NOTICE from \(relay_id): \(msg)", pubkey: "system"), at: 0)
|
||||
print(msg)
|
||||
|
||||
case .eose(let sub_id):
|
||||
|
||||
if sub_id == dms_subid {
|
||||
let dms = dms.dms.flatMap { $0.1.events }
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: dms, damus_state: damus_state)
|
||||
} else if sub_id == notifications_subid {
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: notifications, damus_state: damus_state)
|
||||
}
|
||||
|
||||
case .eose:
|
||||
self.loading = false
|
||||
break
|
||||
}
|
||||
@@ -269,9 +260,6 @@ class HomeModel: ObservableObject {
|
||||
|
||||
var contacts_filter = NostrFilter.filter_kinds([0])
|
||||
contacts_filter.authors = friends
|
||||
|
||||
var our_contacts_filter = NostrFilter.filter_kinds([3, 0])
|
||||
our_contacts_filter.authors = [damus_state.pubkey]
|
||||
|
||||
var dms_filter = NostrFilter.filter_kinds([
|
||||
NostrKind.dm.rawValue,
|
||||
@@ -309,7 +297,7 @@ class HomeModel: ObservableObject {
|
||||
|
||||
var home_filters = [home_filter]
|
||||
var notifications_filters = [notifications_filter]
|
||||
var contacts_filters = [contacts_filter, our_contacts_filter]
|
||||
var contacts_filters = [contacts_filter]
|
||||
var dms_filters = [dms_filter, our_dms_filter]
|
||||
|
||||
let last_of_kind = relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
|
||||
@@ -347,14 +335,12 @@ class HomeModel: ObservableObject {
|
||||
return m[kind]
|
||||
}
|
||||
|
||||
func handle_last_event(ev: NostrEvent, timeline: Timeline, shouldNotify: Bool = true) {
|
||||
func handle_last_event(ev: NostrEvent, timeline: Timeline) {
|
||||
let last_ev = get_last_event(timeline)
|
||||
|
||||
if last_ev == nil || last_ev!.created_at < ev.created_at {
|
||||
save_last_event(ev, timeline: timeline)
|
||||
if shouldNotify {
|
||||
new_events = NewEventsBits(prev: new_events, setting: timeline)
|
||||
}
|
||||
new_events = NewEventsBits(prev: new_events, setting: timeline)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,13 +412,13 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
|
||||
if inserted {
|
||||
handle_last_event(ev: ev, timeline: .dms, shouldNotify: !ours)
|
||||
handle_last_event(ev: ev, timeline: .dms)
|
||||
|
||||
dms.dms = dms.dms.sorted { a, b in
|
||||
if a.1.events.count > 0 && b.1.events.count > 0 {
|
||||
return a.1.events.last!.created_at > b.1.events.last!.created_at
|
||||
}
|
||||
return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -457,33 +443,18 @@ func add_contact_if_friend(contacts: Contacts, ev: NostrEvent) {
|
||||
contacts.add_friend_contact(ev)
|
||||
}
|
||||
|
||||
func load_our_contacts(contacts: Contacts, our_pubkey: String, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
||||
var new_pks = Set<String>()
|
||||
func load_our_contacts(contacts: Contacts, our_pubkey: String, ev: NostrEvent) {
|
||||
guard ev.pubkey == our_pubkey else {
|
||||
return
|
||||
}
|
||||
|
||||
contacts.event = ev
|
||||
|
||||
// our contacts
|
||||
for tag in ev.tags {
|
||||
if tag.count >= 2 && tag[0] == "p" {
|
||||
new_pks.insert(tag[1])
|
||||
}
|
||||
}
|
||||
|
||||
var old_pks = Set<String>()
|
||||
// find removed contacts
|
||||
if let old_ev = m_old_ev {
|
||||
for tag in old_ev.tags {
|
||||
if tag.count >= 2 && tag[0] == "p" {
|
||||
old_pks.insert(tag[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let diff = new_pks.symmetricDifference(old_pks)
|
||||
for pk in diff {
|
||||
if new_pks.contains(pk) {
|
||||
notify(.followed, pk)
|
||||
contacts.add_friend_pubkey(pk)
|
||||
} else {
|
||||
notify(.unfollowed, pk)
|
||||
contacts.remove_friend(pk)
|
||||
if tag.count > 1 && tag[0] == "p" {
|
||||
// TODO: validate pubkey?
|
||||
contacts.add_friend_pubkey(tag[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -569,68 +540,51 @@ func robohash(_ pk: String) -> String {
|
||||
return "https://robohash.org/" + pk
|
||||
}
|
||||
|
||||
func load_our_stuff(pool: RelayPool, contacts: Contacts, pubkey: String, ev: NostrEvent) {
|
||||
guard ev.pubkey == pubkey else {
|
||||
return
|
||||
}
|
||||
|
||||
// only use new stuff
|
||||
if let current_ev = contacts.event {
|
||||
guard ev.created_at > current_ev.created_at else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let m_old_ev = contacts.event
|
||||
contacts.event = ev
|
||||
|
||||
load_our_contacts(contacts: contacts, our_pubkey: pubkey, m_old_ev: m_old_ev, ev: ev)
|
||||
load_our_relays(contacts: contacts, our_pubkey: pubkey, pool: pool, m_old_ev: m_old_ev, ev: ev)
|
||||
}
|
||||
|
||||
func process_contact_event(pool: RelayPool, contacts: Contacts, pubkey: String, ev: NostrEvent) {
|
||||
load_our_stuff(pool: pool, contacts: contacts, pubkey: pubkey, ev: ev)
|
||||
load_our_contacts(contacts: contacts, our_pubkey: pubkey, ev: ev)
|
||||
load_our_relays(our_pubkey: pubkey, pool: pool, ev: ev)
|
||||
add_contact_if_friend(contacts: contacts, ev: ev)
|
||||
}
|
||||
|
||||
func load_our_relays(contacts: Contacts, our_pubkey: String, pool: RelayPool, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
||||
let bootstrap_dict: [String: RelayInfo] = [:]
|
||||
let old_decoded = m_old_ev.flatMap { decode_json_relays($0.content) } ?? BOOTSTRAP_RELAYS.reduce(into: bootstrap_dict) { (d, r) in
|
||||
d[r] = .rw
|
||||
func load_our_relays(our_pubkey: String, pool: RelayPool, ev: NostrEvent) {
|
||||
guard ev.pubkey == our_pubkey else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let decoded = decode_json_relays(ev.content) else {
|
||||
return
|
||||
}
|
||||
|
||||
var changed = false
|
||||
|
||||
var new = Set<String>()
|
||||
|
||||
for key in decoded.keys {
|
||||
new.insert(key)
|
||||
}
|
||||
|
||||
var old = Set<String>()
|
||||
for key in old_decoded.keys {
|
||||
old.insert(key)
|
||||
}
|
||||
|
||||
let diff = old.symmetricDifference(new)
|
||||
|
||||
for d in diff {
|
||||
changed = true
|
||||
if new.contains(d) {
|
||||
if let url = URL(string: d) {
|
||||
try? pool.add_relay(url, info: decoded[d] ?? .rw)
|
||||
if let url = URL(string: key) {
|
||||
if let _ = try? pool.add_relay(url, info: decoded[key]!) {
|
||||
pool.connect(to: [key])
|
||||
}
|
||||
} else {
|
||||
pool.remove_relay(d)
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
notify(.relays_changed, ())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func remove_bootstrap_nodes(_ damus_state: DamusState) {
|
||||
guard let contacts = damus_state.contacts.event else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let relays = decode_json_relays(contacts.content) else {
|
||||
return
|
||||
}
|
||||
|
||||
let descriptors = relays.reduce(into: []) { arr, kv in
|
||||
guard let url = URL(string: kv.key) else {
|
||||
return
|
||||
}
|
||||
arr.append(RelayDescriptor(url: url, info: kv.value))
|
||||
}
|
||||
|
||||
for relay in BOOTSTRAP_RELAYS {
|
||||
if !(descriptors.contains { ($0 as! RelayDescriptor).url.absoluteString == relay }) {
|
||||
damus_state.pool.remove_relay(relay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ struct IdBlock: Identifiable {
|
||||
|
||||
struct Invoice {
|
||||
let description: String
|
||||
let amount: Amount
|
||||
let amount: Int64
|
||||
let string: String
|
||||
let expiry: UInt64
|
||||
let payment_hash: Data
|
||||
@@ -115,7 +115,7 @@ func parse_mentions(content: String, tags: [[String]]) -> [Block] {
|
||||
blocks_init(&bs)
|
||||
|
||||
let bytes = content.utf8CString
|
||||
let _ = bytes.withUnsafeBufferPointer { p in
|
||||
bytes.withUnsafeBufferPointer { p in
|
||||
damus_parse_content(&bs, p.baseAddress)
|
||||
}
|
||||
|
||||
@@ -180,23 +180,6 @@ func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>!) -> T? {
|
||||
return p.pointee
|
||||
}
|
||||
|
||||
enum Amount: Equatable {
|
||||
case any
|
||||
case specific(Int64)
|
||||
|
||||
func amount_sats_str() -> String {
|
||||
switch self {
|
||||
case .any:
|
||||
return "Any"
|
||||
case .specific(let amt):
|
||||
if amt < 1000 {
|
||||
return "\(Double(amt) / 1000.0) sats"
|
||||
}
|
||||
return "\(amt / 1000) sats"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func convert_invoice_block(_ b: invoice_block) -> Block? {
|
||||
guard let invstr = strblock_to_string(b.invstr) else {
|
||||
return nil
|
||||
@@ -211,8 +194,12 @@ func convert_invoice_block(_ b: invoice_block) -> Block? {
|
||||
description = String(cString: b11.description)
|
||||
}
|
||||
|
||||
let amount: Amount = maybe_pointee(b11.msat).map { .specific(Int64($0.millisatoshis)) } ?? .any
|
||||
guard let msat = maybe_pointee(b11.msat) else {
|
||||
return nil
|
||||
}
|
||||
let amount = Int64(msat.millisatoshis)
|
||||
let payment_hash = Data(bytes: &b11.payment_hash, count: 32)
|
||||
let hex = hex_encode(payment_hash)
|
||||
let created_at = b11.timestamp
|
||||
|
||||
tal_free(b.bolt11)
|
||||
|
||||
@@ -11,7 +11,6 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
@Published var events: [NostrEvent] = []
|
||||
@Published var contacts: NostrEvent? = nil
|
||||
@Published var following: Int = 0
|
||||
@Published var relays: [String: RelayInfo]? = nil
|
||||
|
||||
let pubkey: String
|
||||
let damus: DamusState
|
||||
@@ -61,7 +60,7 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
profile_filter.authors = [pubkey]
|
||||
|
||||
text_filter.authors = [pubkey]
|
||||
text_filter.limit = 500
|
||||
text_filter.limit = 1000
|
||||
|
||||
print("subscribing to profile \(pubkey) with sub_id \(sub_id)")
|
||||
print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]])
|
||||
@@ -70,18 +69,11 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
}
|
||||
|
||||
func handle_profile_contact_event(_ ev: NostrEvent) {
|
||||
process_contact_event(pool: damus.pool, contacts: damus.contacts, pubkey: damus.pubkey, ev: ev)
|
||||
|
||||
// only use new stuff
|
||||
if let current_ev = self.contacts {
|
||||
guard ev.created_at > current_ev.created_at else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.contacts = ev
|
||||
self.following = count_pubkeys(ev.tags)
|
||||
self.relays = decode_json_relays(ev.content)
|
||||
if damus.contacts.is_friend(ev.pubkey) {
|
||||
self.damus.contacts.add_friend_contact(ev)
|
||||
}
|
||||
}
|
||||
|
||||
func add_event(_ ev: NostrEvent) {
|
||||
@@ -96,8 +88,6 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
let _ = insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at})
|
||||
} else if ev.known_kind == .contacts {
|
||||
handle_profile_contact_event(ev)
|
||||
} else if ev.known_kind == .metadata {
|
||||
process_metadata_event(profiles: damus.profiles, ev: ev)
|
||||
}
|
||||
seen_event.insert(ev.id)
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// UserSettingsStore.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Suhail Saqan on 12/29/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class UserSettingsStore: ObservableObject {
|
||||
@Published var default_wallet: Wallet {
|
||||
didSet {
|
||||
UserDefaults.standard.set(default_wallet.rawValue, forKey: "default_wallet")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var show_wallet_selector: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(show_wallet_selector, forKey: "show_wallet_selector")
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
if let defaultWalletName = UserDefaults.standard.string(forKey: "default_wallet"),
|
||||
let default_wallet = Wallet(rawValue: defaultWalletName) {
|
||||
self.default_wallet = default_wallet
|
||||
} else {
|
||||
self.default_wallet = .system_default_wallet
|
||||
}
|
||||
self.show_wallet_selector = UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
//
|
||||
// Wallet.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Benjamin Hakes on 12/29/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum Wallet: String, CaseIterable, Identifiable {
|
||||
var id: String { self.rawValue }
|
||||
|
||||
struct Model: Identifiable, Hashable {
|
||||
var id: String { self.tag }
|
||||
var index: Int
|
||||
var tag: String
|
||||
var displayName : String
|
||||
var link : String
|
||||
var appStoreLink : String?
|
||||
var image: String
|
||||
}
|
||||
|
||||
// New url prefixes needed to be added to LSApplicationQueriesSchemes
|
||||
case system_default_wallet
|
||||
case strike
|
||||
case cashapp
|
||||
case muun
|
||||
case bluewallet
|
||||
case walletofsatoshi
|
||||
case zebedee
|
||||
case zeusln
|
||||
case lnlink
|
||||
case phoenix
|
||||
case breez
|
||||
case bitcoinbeach
|
||||
case blixtwallet
|
||||
case river
|
||||
|
||||
var model: Model {
|
||||
switch self {
|
||||
case .system_default_wallet:
|
||||
return .init(index: -1, tag: "systemdefaultwallet", displayName: NSLocalizedString("Local default", comment: "Dropdown option label for system default for Lightning wallet."),
|
||||
link: "lightning:", appStoreLink: "lightning:", image: "")
|
||||
case .strike:
|
||||
return .init(index: 0, tag: "strike", displayName: NSLocalizedString("Strike", comment: "Dropdown option label for Lightning wallet, Strike."), link: "strike:",
|
||||
appStoreLink: "https://apps.apple.com/us/app/strike-bitcoin-payments/id1488724463", image: "strike")
|
||||
case .cashapp:
|
||||
return .init(index: 1, tag: "cashapp", displayName: NSLocalizedString("Cash App", comment: "Dropdown option label for Lightning wallet, Cash App."), link: "squarecash://",
|
||||
appStoreLink: "https://apps.apple.com/us/app/cash-app/id711923939", image: "cashapp")
|
||||
case .muun:
|
||||
return .init(index: 2, tag: "muun", displayName: NSLocalizedString("Muun", comment: "Dropdown option label for Lightning wallet, Muun."), link: "muun:", appStoreLink: "https://apps.apple.com/us/app/muun-wallet/id1482037683", image: "muun")
|
||||
case .bluewallet:
|
||||
return .init(index: 3, tag: "bluewallet", displayName: NSLocalizedString("Blue Wallet", comment: "Dropdown option label for Lightning wallet, Blue Wallet."), link: "bluewallet:lightning:",
|
||||
appStoreLink: "https://apps.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040", image: "bluewallet")
|
||||
case .walletofsatoshi:
|
||||
return .init(index: 4, tag: "walletofsatoshi", displayName: NSLocalizedString("Wallet Of Satoshi", comment: "Dropdown option label for Lightning wallet, Wallet Of Satoshi."), link: "walletofsatoshi:lightning:",
|
||||
appStoreLink: "https://apps.apple.com/us/app/wallet-of-satoshi/id1438599608", image: "walletofsatoshi")
|
||||
case .zebedee:
|
||||
return .init(index: 5, tag: "zebedee", displayName: NSLocalizedString("Zebedee", comment: "Dropdown option label for Lightning wallet, Zebedee."), link: "zebedee:lightning:",
|
||||
appStoreLink: "https://apps.apple.com/us/app/zebedee-wallet/id1484394401", image: "zebedee")
|
||||
case .zeusln:
|
||||
return .init(index: 6, tag: "zeusln", displayName: NSLocalizedString("Zeus LN", comment: "Dropdown option label for Lightning wallet, Zeus LN."), link: "zeusln:lightning:",
|
||||
appStoreLink: "https://apps.apple.com/us/app/zeus-ln/id1456038895", image: "zeusln")
|
||||
case .lnlink:
|
||||
return .init(index: 7, tag: "lnlink", displayName: NSLocalizedString("LNLink", comment: "Dropdown option label for Lightning wallet, LNLink."), link: "lnlink:lightning:",
|
||||
appStoreLink: "https://testflight.apple.com/join/aNY4yuuZ", image: "lnlink")
|
||||
case .phoenix:
|
||||
return .init(index: 8, tag: "phoenix", displayName: NSLocalizedString("Phoenix", comment: "Dropdown option label for Lightning wallet, Phoenix."), link: "phoenix://",
|
||||
appStoreLink: "https://apps.apple.com/us/app/phoenix-wallet/id1544097028", image: "phoenix")
|
||||
case .breez:
|
||||
return .init(index: 9, tag: "breez", displayName: NSLocalizedString("Breez", comment: "Dropdown option label for Lightning wallet, Breez."), link: "breez:",
|
||||
appStoreLink: "https://apps.apple.com/us/app/breez-lightning-client-pos/id1463604142", image: "breez")
|
||||
case .bitcoinbeach:
|
||||
return .init(index: 10, tag: "bitcoinbeach", displayName: NSLocalizedString("Bitcoin Beach", comment: "Dropdown option label for Lightning wallet, Bitcoin Beach."), link: "bitcoinbeach://",
|
||||
appStoreLink: "https://apps.apple.com/sv/app/bitcoin-beach-wallet/id1531383905", image: "bbw")
|
||||
case .blixtwallet:
|
||||
return .init(index: 11, tag: "blixtwallet", displayName: NSLocalizedString("Blixt Wallet", comment: "Dropdown option label for Lightning wallet, Blixt Wallet"), link: "blixtwallet:lightning:",
|
||||
appStoreLink: nil, image: "blixt-wallet")
|
||||
case .river:
|
||||
return .init(index: 12, tag: "river", displayName: NSLocalizedString("River", comment: "Dropdown option label for Lightning wallet, River"), link: "river://",
|
||||
appStoreLink: "https://apps.apple.com/us/app/river-buy-mine-bitcoin/id1536176542", image: "river")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static var allModels: [Model] {
|
||||
return Self.allCases.map { $0.model }
|
||||
}
|
||||
}
|
||||
@@ -89,8 +89,7 @@ struct Profile: Codable {
|
||||
}
|
||||
|
||||
static func displayName(profile: Profile?, pubkey: String) -> String {
|
||||
let pk = bech32_nopre_pubkey(pubkey) ?? pubkey
|
||||
return profile?.name ?? abbrev_pubkey(pk)
|
||||
return profile?.name ?? abbrev_pubkey(pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,19 +45,11 @@ struct EventId: Identifiable, CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Hashable, Comparable {
|
||||
class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable {
|
||||
static func == (lhs: NostrEvent, rhs: NostrEvent) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
|
||||
static func < (lhs: NostrEvent, rhs: NostrEvent) -> Bool {
|
||||
return lhs.created_at < rhs.created_at
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
var id: String
|
||||
var sig: String
|
||||
var tags: [[String]]
|
||||
@@ -272,7 +264,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
||||
return (self.flags & 1) != 0
|
||||
}
|
||||
|
||||
init(content: String, pubkey: String, kind: Int = 1, tags: [[String]] = [], createdAt: Int64 = Int64(Date().timeIntervalSince1970)) {
|
||||
init(content: String, pubkey: String, kind: Int = 1, tags: [[String]] = []) {
|
||||
self.id = ""
|
||||
self.sig = ""
|
||||
|
||||
@@ -280,7 +272,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
||||
self.pubkey = pubkey
|
||||
self.kind = kind
|
||||
self.tags = tags
|
||||
self.created_at = createdAt
|
||||
self.created_at = Int64(Date().timeIntervalSince1970)
|
||||
}
|
||||
|
||||
/// Intiialization statement used to specificy ID
|
||||
@@ -448,7 +440,7 @@ 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!)
|
||||
SecRandomCopyBytes(kSecRandomDefault, count, mutableBytes)
|
||||
}
|
||||
return data
|
||||
}
|
||||
@@ -558,7 +550,7 @@ func make_like_event(pubkey: String, privkey: String, liked: NostrEvent) -> Nost
|
||||
var tags: [[String]] = liked.tags.filter { tag in tag.count >= 2 && (tag[0] == "e" || tag[0] == "p") }
|
||||
tags.append(["e", liked.id])
|
||||
tags.append(["p", liked.pubkey])
|
||||
let ev = NostrEvent(content: "🤙", pubkey: pubkey, kind: 7, tags: tags)
|
||||
let ev = NostrEvent(content: "", pubkey: pubkey, kind: 7, tags: tags)
|
||||
ev.calculate_id()
|
||||
ev.sign(privkey: privkey)
|
||||
|
||||
|
||||
@@ -28,14 +28,12 @@ class Relay: Identifiable {
|
||||
let descriptor: RelayDescriptor
|
||||
let connection: RelayConnection
|
||||
|
||||
var last_pong: UInt32
|
||||
var flags: Int
|
||||
|
||||
init(descriptor: RelayDescriptor, connection: RelayConnection) {
|
||||
self.flags = 0
|
||||
self.descriptor = descriptor
|
||||
self.connection = connection
|
||||
self.last_pong = 0
|
||||
}
|
||||
|
||||
func mark_broken() {
|
||||
|
||||
@@ -179,23 +179,8 @@ class RelayPool {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func record_last_pong(relay_id: String, event: NostrConnectionEvent) {
|
||||
if case .ws_event(let ws_event) = event {
|
||||
if case .pong = ws_event {
|
||||
for relay in relays {
|
||||
if relay.id == relay_id {
|
||||
relay.last_pong = UInt32(Date.now.timeIntervalSince1970)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func handle_event(relay_id: String, event: NostrConnectionEvent) {
|
||||
record_last_pong(relay_id: relay_id, event: event)
|
||||
|
||||
// handle reconnect logic, etc?
|
||||
for handler in handlers {
|
||||
handler.callback(relay_id, event)
|
||||
@@ -205,7 +190,6 @@ class RelayPool {
|
||||
|
||||
func add_rw_relay(_ pool: RelayPool, _ url: String) {
|
||||
let url_ = URL(string: url)!
|
||||
try? pool.add_relay(url_, info: RelayInfo.rw)
|
||||
try! pool.add_relay(url_, info: RelayInfo.rw)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -66,13 +66,6 @@ func bech32_pubkey(_ pubkey: String) -> String? {
|
||||
return bech32_encode(hrp: "npub", bytes)
|
||||
}
|
||||
|
||||
func bech32_nopre_pubkey(_ pubkey: String) -> String? {
|
||||
guard let bytes = hex_decode(pubkey) else {
|
||||
return nil
|
||||
}
|
||||
return bech32_encode(hrp: "", bytes)
|
||||
}
|
||||
|
||||
func bech32_note_id(_ evid: String) -> String? {
|
||||
guard let bytes = hex_decode(evid) else {
|
||||
return nil
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
//
|
||||
// LinkView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Sam DuBois on 12/27/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import LinkPresentation
|
||||
|
||||
class CustomLinkView: LPLinkView {
|
||||
override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
|
||||
}
|
||||
|
||||
struct LinkViewRepresentable: UIViewRepresentable {
|
||||
|
||||
typealias UIViewType = CustomLinkView
|
||||
|
||||
var metadata: LPLinkMetadata?
|
||||
var url: URL?
|
||||
|
||||
func makeUIView(context: Context) -> CustomLinkView {
|
||||
|
||||
if let metadata {
|
||||
let linkView = CustomLinkView(metadata: metadata)
|
||||
return linkView
|
||||
}
|
||||
|
||||
if let url {
|
||||
let linkView = CustomLinkView(url: url)
|
||||
return linkView
|
||||
}
|
||||
|
||||
return CustomLinkView()
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: CustomLinkView, context: Context) {
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
//
|
||||
// Markdown.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Lionello Lunesu on 2022-12-28.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Markdown {
|
||||
private let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
|
||||
|
||||
/// Ensure the specified URL has a scheme by prepending "https://" if it's absent.
|
||||
static func withScheme(_ url: any StringProtocol) -> any StringProtocol {
|
||||
return url.contains("://") ? url : "https://" + url
|
||||
}
|
||||
|
||||
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) {
|
||||
return txt
|
||||
} else {
|
||||
return AttributedString(stringLiteral: content)
|
||||
}
|
||||
}
|
||||
|
||||
/// Process the input text and add markdown for any embedded URLs.
|
||||
public func process(_ input: String) -> AttributedString {
|
||||
let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
|
||||
var output = input
|
||||
// Start with the last match, because replacing the first would invalidate all subsequent indices
|
||||
for match in matches.reversed() {
|
||||
guard let range = Range(match.range, in: input) else { continue }
|
||||
let url = input[range]
|
||||
output.replaceSubrange(range, with: "[\(url)](\(Markdown.withScheme(url)))")
|
||||
}
|
||||
// TODO: escape unintentional markdown
|
||||
return Markdown.parse(content: output)
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,6 @@ extension Notification.Name {
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification.Name {
|
||||
static var relays_changed: Notification.Name {
|
||||
return Notification.Name("relays_changed")
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification.Name {
|
||||
static var select_event: Notification.Name {
|
||||
return Notification.Name("select_event")
|
||||
|
||||
@@ -97,12 +97,6 @@ func parse_digit(_ p: Parser) -> Int? {
|
||||
func parse_hex_char(_ p: Parser) -> Character? {
|
||||
let ind = p.str.index(p.str.startIndex, offsetBy: p.pos)
|
||||
|
||||
// Check that we're within the bounds of p.str's length
|
||||
if p.pos >= p.str.count {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
if let c = p.str[ind].unicodeScalars.first {
|
||||
// hex chars
|
||||
let d = c.value
|
||||
|
||||
@@ -7,48 +7,40 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public func time_ago_since(_ date: Date, _ calendar: Calendar = Calendar.current) -> String {
|
||||
public func time_ago_since(_ date: Date) -> String {
|
||||
|
||||
let calendar = Calendar.current
|
||||
let now = Date()
|
||||
let unitFlags: NSCalendar.Unit = [.second, .minute, .hour, .day, .weekOfMonth, .month, .year]
|
||||
|
||||
let unitFlags: NSCalendar.Unit = [.second, .minute, .hour, .day, .weekOfYear, .month, .year]
|
||||
let components = (calendar as NSCalendar).components(unitFlags, from: date, to: now, options: [])
|
||||
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .abbreviated
|
||||
formatter.maximumUnitCount = 1
|
||||
formatter.allowedUnits = unitFlags
|
||||
|
||||
// Manually format date component from only the most significant time unit because
|
||||
// DateComponentsFormatter rounds up by default.
|
||||
|
||||
if let year = components.year, year >= 1 {
|
||||
return formatter.string(from: DateComponents(calendar: calendar, year: year))!
|
||||
return "\(year)yr"
|
||||
}
|
||||
|
||||
if let month = components.month, month >= 1 {
|
||||
return formatter.string(from: DateComponents(calendar: calendar, month: month))!
|
||||
return "\(month)mth"
|
||||
}
|
||||
|
||||
if let week = components.weekOfMonth, week >= 1 {
|
||||
return formatter.string(from: DateComponents(calendar: calendar, weekOfMonth: week))!
|
||||
if let week = components.weekOfYear, week >= 1 {
|
||||
return "\(week)wk"
|
||||
}
|
||||
|
||||
if let day = components.day, day >= 1 {
|
||||
return formatter.string(from: DateComponents(calendar: calendar, day: day))!
|
||||
return "\(day)d"
|
||||
}
|
||||
|
||||
if let hour = components.hour, hour >= 1 {
|
||||
return formatter.string(from: DateComponents(calendar: calendar, hour: hour))!
|
||||
return "\(hour)h"
|
||||
}
|
||||
|
||||
if let minute = components.minute, minute >= 1 {
|
||||
return formatter.string(from: DateComponents(calendar: calendar, minute: minute))!
|
||||
return "\(minute)m"
|
||||
}
|
||||
|
||||
if let second = components.second, second >= 3 {
|
||||
return formatter.string(from: DateComponents(calendar: calendar, second: second))!
|
||||
return "\(second)s"
|
||||
}
|
||||
|
||||
return NSLocalizedString("now", comment: "String indicating that a given timestamp just occurred")
|
||||
return "now"
|
||||
}
|
||||
|
||||
@@ -17,31 +17,9 @@ struct AddRelayView: View {
|
||||
VStack(alignment: .leading) {
|
||||
Form {
|
||||
Section("Add Relay") {
|
||||
ZStack(alignment: .leading) {
|
||||
HStack{
|
||||
TextField("wss://some.relay.com", text: $relay)
|
||||
.padding(2)
|
||||
.padding(.leading, 25)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
|
||||
Label("", systemImage: "xmark.circle.fill")
|
||||
.foregroundColor(.blue)
|
||||
.padding(.trailing, -25.0)
|
||||
.opacity((relay == "") ? 0.0 : 1.0)
|
||||
.onTapGesture {
|
||||
self.relay = ""
|
||||
}
|
||||
}
|
||||
|
||||
Label("", systemImage: "doc.on.clipboard")
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
if let pastedrelay = UIPasteboard.general.string {
|
||||
self.relay = pastedrelay
|
||||
}
|
||||
}
|
||||
}
|
||||
TextField("wss://some.relay.com", text: $relay)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +36,6 @@ struct AddRelayView: View {
|
||||
Button("Add") {
|
||||
show_add_relay = false
|
||||
action(relay)
|
||||
relay = ""
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.contentShape(Rectangle())
|
||||
|
||||
@@ -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, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.content))
|
||||
|
||||
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
|
||||
let bar = make_actionbar_model(ev: event, damus: damus_state)
|
||||
|
||||
@@ -17,15 +17,12 @@ struct ConfigView: View {
|
||||
@State var privkey: String
|
||||
@State var privkey_copied: Bool = false
|
||||
@State var pubkey_copied: Bool = false
|
||||
@State var relays: [RelayDescriptor]
|
||||
@EnvironmentObject var user_settings: UserSettingsStore
|
||||
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
|
||||
init(state: DamusState) {
|
||||
self.state = state
|
||||
_privkey = State(initialValue: self.state.keypair.privkey_bech32 ?? "")
|
||||
_relays = State(initialValue: state.pool.descriptors)
|
||||
}
|
||||
|
||||
// TODO: (jb55) could be more general but not gonna worry about it atm
|
||||
@@ -41,28 +38,16 @@ struct ConfigView: View {
|
||||
}
|
||||
}
|
||||
|
||||
var recommended: [RelayDescriptor] {
|
||||
let rs: [RelayDescriptor] = []
|
||||
return BOOTSTRAP_RELAYS.reduce(into: rs) { (xs, x) in
|
||||
if let _ = state.pool.get_relay(x) {
|
||||
} else {
|
||||
xs.append(RelayDescriptor(url: URL(string: x)!, info: .rw))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .leading) {
|
||||
Form {
|
||||
Section("Relays") {
|
||||
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)
|
||||
if let ev = state.contacts.event {
|
||||
Section("Relays") {
|
||||
if let relays = decode_json_relays(ev.content) {
|
||||
List(Array(relays.keys.sorted()), id: \.self) { relay in
|
||||
RelayView(state: state, ev: ev, relay: relay)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,17 +78,6 @@ struct ConfigView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Section("Wallet Selector") {
|
||||
Toggle("Show wallet selector", isOn: $user_settings.show_wallet_selector).toggleStyle(.switch)
|
||||
Picker("Select default wallet",
|
||||
selection: $user_settings.default_wallet) {
|
||||
ForEach(Wallet.allCases, id: \.self) { wallet in
|
||||
Text(wallet.model.displayName)
|
||||
.tag(wallet.model.tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section("Reset") {
|
||||
Button("Logout") {
|
||||
confirm_logout = true
|
||||
@@ -139,6 +113,7 @@ struct ConfigView: View {
|
||||
}
|
||||
.sheet(isPresented: $show_add_relay) {
|
||||
AddRelayView(show_add_relay: $show_add_relay, relay: $new_relay) { m_relay in
|
||||
|
||||
guard let relay = m_relay else {
|
||||
return
|
||||
}
|
||||
@@ -163,21 +138,17 @@ struct ConfigView: View {
|
||||
|
||||
state.pool.connect(to: [new_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, relay: new_relay, info: info) else {
|
||||
return
|
||||
}
|
||||
|
||||
process_contact_event(pool: state.pool, contacts: state.contacts, pubkey: state.pubkey, ev: ev)
|
||||
|
||||
state.contacts.event = new_ev
|
||||
state.pool.send(.event(new_ev))
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
.onReceive(handle_notify(.relays_changed)) { _ in
|
||||
self.relays = state.pool.descriptors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,26 +40,26 @@ struct CreateAccountView: View {
|
||||
}
|
||||
VStack {
|
||||
SignupForm {
|
||||
FormLabel(NSLocalizedString("Username", comment: "Label to prompt username entry."))
|
||||
FormLabel("Username")
|
||||
HStack(spacing: 0.0) {
|
||||
Text("@")
|
||||
.foregroundColor(.white)
|
||||
.padding(.leading, -25.0)
|
||||
|
||||
FormTextInput(NSLocalizedString("satoshi", comment: "Example username of Bitcoin creator(s), Satoshi Nakamoto."), text: $account.nick_name)
|
||||
FormTextInput("satoshi", text: $account.nick_name)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
|
||||
}
|
||||
|
||||
FormLabel(NSLocalizedString("Display Name", comment: "Label to prompt display name entry."), optional: true)
|
||||
FormTextInput(NSLocalizedString("Satoshi Nakamoto", comment: "Name of Bitcoin creator(s)."), text: $account.real_name)
|
||||
FormLabel("Display Name", optional: true)
|
||||
FormTextInput("Satoshi Nakamoto", text: $account.real_name)
|
||||
.textInputAutocapitalization(.words)
|
||||
|
||||
FormLabel(NSLocalizedString("About", comment: "Label to prompt for about text entry for user to describe about themself."), optional: true)
|
||||
FormTextInput(NSLocalizedString("Creator(s) of Bitcoin. Absolute legend.", comment: "Example description about Bitcoin creator(s), Satoshi Nakamoto."), text: $account.about)
|
||||
FormLabel("About", optional: true)
|
||||
FormTextInput("Creator(s) of Bitcoin. Absolute legend.", text: $account.about)
|
||||
|
||||
FormLabel(NSLocalizedString("Account ID", comment: "Label to indicate the public ID of the account."))
|
||||
FormLabel("Account ID")
|
||||
.onTapGesture {
|
||||
regen_key()
|
||||
}
|
||||
@@ -75,7 +75,7 @@ struct CreateAccountView: View {
|
||||
NavigationLink(destination: SaveKeysView(account: account), isActive: $is_done) {
|
||||
EmptyView()
|
||||
}
|
||||
DamusWhiteButton(NSLocalizedString("Create", comment: "Button to create account.")) {
|
||||
DamusWhiteButton("Create") {
|
||||
self.is_done = true
|
||||
}
|
||||
.padding()
|
||||
|
||||
@@ -21,9 +21,7 @@ struct DMView: View {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
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, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.get_content(damus_state.keypair.privkey)))
|
||||
.foregroundColor(is_ours ? Color.white : Color.primary)
|
||||
.padding(10)
|
||||
.background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15))
|
||||
|
||||
@@ -9,19 +9,10 @@ import SwiftUI
|
||||
|
||||
struct DirectMessagesView: View {
|
||||
let damus_state: DamusState
|
||||
|
||||
@State var open_dm: Bool = false
|
||||
@State var pubkey: String = ""
|
||||
@State var active_model: DirectMessageModel = DirectMessageModel()
|
||||
@EnvironmentObject var model: DirectMessagesModel
|
||||
|
||||
var MainContent: some View {
|
||||
ScrollView {
|
||||
let chat = DMChatView(damus_state: damus_state, pubkey: pubkey)
|
||||
.environmentObject(active_model)
|
||||
NavigationLink(destination: chat, isActive: $open_dm) {
|
||||
EmptyView()
|
||||
}
|
||||
LazyVStack {
|
||||
if model.dms.isEmpty, !model.loading {
|
||||
EmptyTimelineView()
|
||||
@@ -39,12 +30,12 @@ struct DirectMessagesView: View {
|
||||
func MaybeEvent(_ tup: (String, DirectMessageModel)) -> some View {
|
||||
Group {
|
||||
if let ev = tup.1.events.last {
|
||||
EventView(damus: damus_state, event: ev, pubkey: tup.0, show_friend_icon: true)
|
||||
.onTapGesture {
|
||||
pubkey = tup.0
|
||||
active_model = tup.1
|
||||
open_dm = true
|
||||
}
|
||||
let chat = DMChatView(damus_state: damus_state, pubkey: tup.0)
|
||||
.environmentObject(tup.1)
|
||||
NavigationLink(destination: chat) {
|
||||
EventView(damus: damus_state, event: ev, pubkey: tup.0, show_friend_icon: true)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ struct EditMetadataView: View {
|
||||
}
|
||||
|
||||
Section("About Me") {
|
||||
let placeholder = NSLocalizedString("Absolute Boss", comment: "Placeholder text for About Me description.")
|
||||
let placeholder = "Absolute Boss"
|
||||
ZStack(alignment: .topLeading) {
|
||||
TextEditor(text: $about)
|
||||
.textInputAutocapitalization(.sentences)
|
||||
@@ -169,9 +169,9 @@ struct EditMetadataView: View {
|
||||
Text("NIP-05 Verification")
|
||||
}, 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")
|
||||
} 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.")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -59,9 +59,9 @@ struct EventActionBar: View {
|
||||
HStack(alignment: .bottom) {
|
||||
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
|
||||
.font(.footnote.weight(.medium))
|
||||
.foregroundColor(bar.liked ? Color.orange : Color.gray)
|
||||
.foregroundColor(bar.liked ? Color.red : Color.gray)
|
||||
|
||||
LikeButton(liked: bar.liked) {
|
||||
EventActionButton(img: bar.liked ? "heart.fill" : "heart", col: bar.liked ? Color.red : nil) {
|
||||
if bar.liked {
|
||||
notify(.delete, bar.our_like)
|
||||
} else {
|
||||
@@ -86,6 +86,7 @@ struct EventActionBar: View {
|
||||
}
|
||||
*/
|
||||
}
|
||||
.padding(.top, 1)
|
||||
.alert("Boost", isPresented: $confirm_boost) {
|
||||
Button("Cancel") {
|
||||
confirm_boost = false
|
||||
@@ -138,32 +139,13 @@ struct EventActionBar: View {
|
||||
|
||||
func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View {
|
||||
Button(action: action) {
|
||||
Label(" ", systemImage: img)
|
||||
Label("", systemImage: img)
|
||||
.font(.footnote.weight(.medium))
|
||||
.foregroundColor(col == nil ? Color.gray : col!)
|
||||
}
|
||||
.padding(.trailing, 40)
|
||||
}
|
||||
|
||||
struct LikeButton: View {
|
||||
let liked: Bool
|
||||
let action: () -> ()
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
if liked {
|
||||
Text("🤙")
|
||||
} else {
|
||||
Label(" ", systemImage: "hand.thumbsup")
|
||||
.font(.footnote.weight(.medium))
|
||||
.foregroundColor(Color.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct EventActionBar_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
@@ -36,90 +36,6 @@ enum Highlight {
|
||||
}
|
||||
}
|
||||
|
||||
enum EventViewKind {
|
||||
case small
|
||||
case normal
|
||||
case big
|
||||
case selected
|
||||
}
|
||||
|
||||
func eventviewsize_to_font(_ size: EventViewKind) -> Font {
|
||||
switch size {
|
||||
case .small:
|
||||
return .body
|
||||
case .normal:
|
||||
return .body
|
||||
case .big:
|
||||
return .headline
|
||||
case .selected:
|
||||
return .custom("selected", size: 21.0)
|
||||
}
|
||||
}
|
||||
|
||||
struct BuilderEventView: View {
|
||||
let damus: DamusState
|
||||
let event_id: String
|
||||
@State var event: NostrEvent?
|
||||
@State var subscription_uuid: String = UUID().description
|
||||
|
||||
func unsubscribe() {
|
||||
damus.pool.unsubscribe(sub_id: subscription_uuid)
|
||||
}
|
||||
|
||||
func subscribe(filters: [NostrFilter]) {
|
||||
damus.pool.register_handler(sub_id: subscription_uuid, handler: handle_event)
|
||||
damus.pool.send(.subscribe(.init(filters: filters, sub_id: subscription_uuid)))
|
||||
}
|
||||
|
||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||
guard case .nostr_event(let nostr_response) = ev else {
|
||||
return
|
||||
}
|
||||
|
||||
guard case .event(let id, let nostr_event) = nostr_response else {
|
||||
return
|
||||
}
|
||||
|
||||
// Is current event
|
||||
if id == subscription_uuid {
|
||||
if event != nil {
|
||||
return
|
||||
}
|
||||
|
||||
event = nostr_event
|
||||
|
||||
unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
func load() {
|
||||
subscribe(filters: [
|
||||
NostrFilter(
|
||||
ids: [self.event_id],
|
||||
limit: 1
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if event == nil {
|
||||
ProgressView().padding()
|
||||
} else {
|
||||
NavigationLink(destination: BuildThreadV2View(damus: damus, event_id: event!.id)) {
|
||||
EventView(damus: damus, event: event!, show_friend_icon: true, size: .small, embedded: true)
|
||||
}.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.border(Color.gray.opacity(0.2), width: 1)
|
||||
.cornerRadius(2)
|
||||
.onAppear {
|
||||
self.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EventView: View {
|
||||
let event: NostrEvent
|
||||
let highlight: Highlight
|
||||
@@ -127,42 +43,34 @@ struct EventView: View {
|
||||
let damus: DamusState
|
||||
let pubkey: String
|
||||
let show_friend_icon: Bool
|
||||
let size: EventViewKind
|
||||
let embedded: Bool
|
||||
|
||||
@EnvironmentObject var action_bar: ActionBarModel
|
||||
|
||||
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
|
||||
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool) {
|
||||
self.event = event
|
||||
self.highlight = highlight
|
||||
self.has_action_bar = has_action_bar
|
||||
self.damus = damus
|
||||
self.pubkey = event.pubkey
|
||||
self.show_friend_icon = show_friend_icon
|
||||
self.size = size
|
||||
self.embedded = embedded
|
||||
}
|
||||
|
||||
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
|
||||
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool) {
|
||||
self.event = event
|
||||
self.highlight = .none
|
||||
self.has_action_bar = false
|
||||
self.damus = damus
|
||||
self.pubkey = event.pubkey
|
||||
self.show_friend_icon = show_friend_icon
|
||||
self.size = size
|
||||
self.embedded = embedded
|
||||
}
|
||||
|
||||
init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
|
||||
init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool) {
|
||||
self.event = event
|
||||
self.highlight = .none
|
||||
self.has_action_bar = false
|
||||
self.damus = damus
|
||||
self.pubkey = pubkey
|
||||
self.show_friend_icon = show_friend_icon
|
||||
self.size = size
|
||||
self.embedded = embedded
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -200,44 +108,25 @@ struct EventView: View {
|
||||
|
||||
func TextEvent(_ event: NostrEvent, pubkey: String) -> some View {
|
||||
let content = event.get_content(damus.keypair.privkey)
|
||||
|
||||
return HStack(alignment: .top) {
|
||||
let profile = damus.profiles.lookup(id: pubkey)
|
||||
|
||||
if size != .selected {
|
||||
VStack {
|
||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
|
||||
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
|
||||
|
||||
if !embedded {
|
||||
NavigationLink(destination: pv) {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
VStack {
|
||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
|
||||
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
|
||||
|
||||
NavigationLink(destination: pv) {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .center) {
|
||||
if size == .selected {
|
||||
VStack {
|
||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
|
||||
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
|
||||
|
||||
NavigationLink(destination: pv) {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon, size: size)
|
||||
if size != .selected {
|
||||
Text("\(format_relative_time(event.created_at))")
|
||||
.font(eventviewsize_to_font(size))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon)
|
||||
Text("\(format_relative_time(event.created_at))")
|
||||
.font(.body)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
if event.is_reply(damus.keypair.privkey) {
|
||||
@@ -247,57 +136,16 @@ struct EventView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
let should_show_img = should_show_images(contacts: damus.contacts, ev: event, our_pubkey: damus.pubkey)
|
||||
|
||||
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, show_images: should_show_img, artifacts: .just_content(content), size: self.size)
|
||||
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, show_images: should_show_images(contacts: damus.contacts, ev: event), artifacts: .just_content(content))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.allowsHitTesting(!embedded)
|
||||
|
||||
if !embedded {
|
||||
let blocks = event.blocks(damus.keypair.privkey).filter { block in
|
||||
guard case .mention(let mention) = block else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard case .event = mention.type else {
|
||||
return false
|
||||
}
|
||||
|
||||
if mention.ref.key != "e" {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/// MARK: - Preview
|
||||
if let firstBlock = blocks.first, case .mention(let mention) = firstBlock, mention.ref.key == "e" {
|
||||
BuilderEventView(damus: damus, event_id: mention.ref.id)
|
||||
}
|
||||
|
||||
if has_action_bar {
|
||||
let bar = make_actionbar_model(ev: event, damus: damus)
|
||||
EventActionBar(damus_state: damus, event: event, bar: bar)
|
||||
}
|
||||
|
||||
if !embedded {
|
||||
if has_action_bar {
|
||||
if size == .selected {
|
||||
Text("\(format_date(event.created_at))")
|
||||
.padding(.top, 10)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
Divider()
|
||||
.padding([.bottom], 4)
|
||||
} else {
|
||||
Rectangle().frame(height: 2).opacity(0)
|
||||
}
|
||||
|
||||
let bar = make_actionbar_model(ev: event, damus: damus)
|
||||
EventActionBar(damus_state: damus, event: event, bar: bar)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding([.top], 4)
|
||||
}
|
||||
Divider()
|
||||
.padding([.top], 4)
|
||||
}
|
||||
.padding([.leading], 2)
|
||||
}
|
||||
@@ -311,10 +159,7 @@ struct EventView: View {
|
||||
}
|
||||
|
||||
// blame the porn bots for this code
|
||||
func should_show_images(contacts: Contacts, ev: NostrEvent, our_pubkey: String) -> Bool {
|
||||
if ev.pubkey == our_pubkey {
|
||||
return true
|
||||
}
|
||||
func should_show_images(contacts: Contacts, ev: NostrEvent) -> Bool {
|
||||
if contacts.is_in_friendosphere(ev.pubkey) {
|
||||
return true
|
||||
}
|
||||
@@ -386,22 +231,13 @@ func format_relative_time(_ created_at: Int64) -> String
|
||||
return time_ago_since(Date(timeIntervalSince1970: Double(created_at)))
|
||||
}
|
||||
|
||||
func format_date(_ created_at: Int64) -> String {
|
||||
let date = Date(timeIntervalSince1970: TimeInterval(created_at))
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.timeStyle = .short
|
||||
dateFormatter.dateStyle = .short
|
||||
return dateFormatter.string(from: date)
|
||||
}
|
||||
|
||||
|
||||
func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
|
||||
let desc = make_reply_description(event.tags)
|
||||
let pubkeys = desc.pubkeys
|
||||
let n = desc.others
|
||||
|
||||
if desc.pubkeys.count == 0 {
|
||||
return NSLocalizedString("Reply to self", comment: "Label to indicate that the user is replying to themself.")
|
||||
return "Reply to self"
|
||||
}
|
||||
|
||||
let names: [String] = pubkeys.map {
|
||||
@@ -411,14 +247,20 @@ func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
|
||||
|
||||
if names.count == 2 {
|
||||
if n > 2 {
|
||||
let othersCount = n - pubkeys.count
|
||||
return String(format: NSLocalizedString("replying_to_two_and_others", comment: "Label to indicate that the user is replying to 2 users and others."), names[0], names[1], othersCount)
|
||||
let and_other = reply_others_desc(n: n, n_pubkeys: pubkeys.count)
|
||||
return "Replying to \(names[0]), \(names[1])\(and_other)"
|
||||
}
|
||||
return String.localizedStringWithFormat("Replying to %@ & %@", names[0], names[1])
|
||||
return "Replying to \(names[0]) & \(names[1])"
|
||||
}
|
||||
|
||||
let othersCount = n - pubkeys.count
|
||||
return String(format: NSLocalizedString("replying_to_one_and_others", comment: "Label to indicate that the user is replying to 1 user and others."), names[0], othersCount)
|
||||
let and_other = reply_others_desc(n: n, n_pubkeys: pubkeys.count)
|
||||
return "Replying to \(names[0])\(and_other)"
|
||||
}
|
||||
|
||||
func reply_others_desc(n: Int, n_pubkeys: Int) -> String {
|
||||
let other = n - n_pubkeys
|
||||
let plural = other == 1 ? "" : "s"
|
||||
return n > 1 ? " & \(other) other\(plural)" : ""
|
||||
}
|
||||
|
||||
|
||||
@@ -443,23 +285,6 @@ func make_actionbar_model(ev: NostrEvent, damus: DamusState) -> ActionBarModel {
|
||||
|
||||
struct EventView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .small)
|
||||
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .normal)
|
||||
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .big)
|
||||
|
||||
EventView(
|
||||
event: NostrEvent(
|
||||
content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool",
|
||||
pubkey: "pk",
|
||||
createdAt: Int64(Date().timeIntervalSince1970 - 100)
|
||||
),
|
||||
highlight: .none,
|
||||
has_action_bar: true,
|
||||
damus: test_damus_state(),
|
||||
show_friend_icon: true,
|
||||
size: .selected
|
||||
)
|
||||
}
|
||||
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,9 @@ import SwiftUI
|
||||
struct FollowUserView: View {
|
||||
let target: FollowTarget
|
||||
let damus_state: DamusState
|
||||
|
||||
static let markdown = Markdown()
|
||||
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
HStack(alignment: .top) {
|
||||
let pmodel = ProfileModel(pubkey: target.pubkey, damus: damus_state)
|
||||
let followers = FollowersModel(damus_state: damus_state, target: target.pubkey)
|
||||
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: followers)
|
||||
@@ -25,10 +23,8 @@ struct FollowUserView: View {
|
||||
VStack(alignment: .leading) {
|
||||
let profile = damus_state.profiles.lookup(id: target.pubkey)
|
||||
ProfileName(pubkey: target.pubkey, profile: profile, contacts: damus_state.contacts, show_friend_confirmed: false)
|
||||
if let about = profile?.about {
|
||||
Text(FollowUserView.markdown.process(about))
|
||||
.lineLimit(3)
|
||||
.font(.footnote)
|
||||
if let about = profile.flatMap { $0.about } {
|
||||
Text(about)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +51,6 @@ struct FollowersView: View {
|
||||
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationBarTitle("\(Profile.displayName(profile: profile, pubkey: whos))'s Followers")
|
||||
.onAppear {
|
||||
@@ -83,7 +78,6 @@ struct FollowingView: View {
|
||||
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.onAppear {
|
||||
following.subscribe()
|
||||
|
||||
@@ -76,11 +76,14 @@ struct TabBar: View {
|
||||
VStack {
|
||||
Divider()
|
||||
HStack {
|
||||
TabButton(timeline: .home, img: "house", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("1")
|
||||
TabButton(timeline: .dms, img: "bubble.left.and.bubble.right", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("2")
|
||||
TabButton(timeline: .search, img: "magnifyingglass.circle", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("3")
|
||||
TabButton(timeline: .notifications, img: "bell", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("4")
|
||||
TabButton(timeline: .home, img: "house", selected: $selected, new_events: $new_events, action: action)
|
||||
TabButton(timeline: .dms, img: "bubble.left.and.bubble.right", selected: $selected, new_events: $new_events, action: action)
|
||||
TabButton(timeline: .search, img: "magnifyingglass.circle", selected: $selected, new_events: $new_events, action: action)
|
||||
TabButton(timeline: .notifications, img: "bell", selected: $selected, new_events: $new_events, action: action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@ struct MentionView: View {
|
||||
var body: some View {
|
||||
switch mention.type {
|
||||
case .pubkey:
|
||||
let pk = bech32_pubkey(mention.ref.ref_id) ?? mention.ref.ref_id
|
||||
PubkeyView(pubkey: pk, relay: mention.ref.relay_id)
|
||||
PubkeyView(pubkey: mention.ref.ref_id, relay: mention.ref.relay_id)
|
||||
case .event:
|
||||
Text("< e >")
|
||||
//EventBlockView(pubkey: mention.ref.ref_id, relay: mention.ref.relay_id)
|
||||
|
||||
@@ -6,16 +6,14 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import LinkPresentation
|
||||
|
||||
struct NoteArtifacts {
|
||||
let content: String
|
||||
let images: [URL]
|
||||
let invoices: [Invoice]
|
||||
let links: [URL]
|
||||
|
||||
static func just_content(_ content: String) -> NoteArtifacts {
|
||||
NoteArtifacts(content: content, images: [], invoices: [], links: [])
|
||||
NoteArtifacts(content: content, images: [], invoices: [])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +21,6 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
|
||||
let blocks = ev.blocks(privkey)
|
||||
var invoices: [Invoice] = []
|
||||
var img_urls: [URL] = []
|
||||
var link_urls: [URL] = []
|
||||
let txt = blocks.reduce("") { str, block in
|
||||
switch block {
|
||||
case .mention(let m):
|
||||
@@ -36,20 +33,14 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
|
||||
invoices.append(invoice)
|
||||
return str
|
||||
case .url(let url):
|
||||
|
||||
// Handle Image URLs
|
||||
if is_image_url(url) {
|
||||
// Append Image
|
||||
img_urls.append(url)
|
||||
} else {
|
||||
link_urls.append(url)
|
||||
}
|
||||
|
||||
return str
|
||||
return str + url.absoluteString
|
||||
}
|
||||
}
|
||||
|
||||
return NoteArtifacts(content: txt, images: img_urls, invoices: invoices, links: link_urls)
|
||||
return NoteArtifacts(content: txt, images: img_urls, invoices: invoices)
|
||||
}
|
||||
|
||||
func is_image_url(_ url: URL) -> Bool {
|
||||
@@ -66,43 +57,30 @@ struct NoteContentView: View {
|
||||
|
||||
@State var artifacts: NoteArtifacts
|
||||
|
||||
@State var metaData: LPLinkMetadata? = nil
|
||||
let size: EventViewKind
|
||||
|
||||
func MainContent() -> some View {
|
||||
let md_opts: AttributedString.MarkdownParsingOptions =
|
||||
.init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
|
||||
|
||||
return VStack(alignment: .leading) {
|
||||
Text(Markdown.parse(content: artifacts.content))
|
||||
.font(eventviewsize_to_font(size))
|
||||
|
||||
if let txt = try? AttributedString(markdown: artifacts.content, options: md_opts) {
|
||||
Text(txt)
|
||||
.font(.body)
|
||||
} else {
|
||||
Text(artifacts.content)
|
||||
.font(.body)
|
||||
}
|
||||
if show_images && artifacts.images.count > 0 {
|
||||
ImageCarousel(urls: artifacts.images)
|
||||
} else if !show_images && artifacts.images.count > 0 {
|
||||
ImageCarousel(urls: artifacts.images)
|
||||
.blur(radius: 10)
|
||||
.overlay {
|
||||
Rectangle()
|
||||
.opacity(0.50)
|
||||
}
|
||||
.cornerRadius(10)
|
||||
}
|
||||
if artifacts.invoices.count > 0 {
|
||||
InvoicesView(invoices: artifacts.invoices)
|
||||
}
|
||||
|
||||
if show_images, self.metaData != nil {
|
||||
LinkViewRepresentable(metadata: self.metaData)
|
||||
} else {
|
||||
ForEach(artifacts.links, id:\.self) { link in
|
||||
LinkViewRepresentable(url: link)
|
||||
.frame(height: 50)
|
||||
}
|
||||
.frame(width: 200)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
MainContent()
|
||||
.animation(.easeInOut, value: metaData)
|
||||
.onAppear() {
|
||||
self.artifacts = render_note_content(ev: event, profiles: profiles, privkey: privkey)
|
||||
}
|
||||
@@ -122,28 +100,6 @@ struct NoteContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.task {
|
||||
if show_images, artifacts.links.count == 1 {
|
||||
|
||||
self.metaData = await getMetaData(for: artifacts.links.first!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getMetaData(for url: URL) async -> LPLinkMetadata? {
|
||||
// iOS 15 is crashing for some reason
|
||||
guard #available(iOS 16, *) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let provider = LPMetadataProvider()
|
||||
|
||||
do {
|
||||
return try await provider.startFetchingMetadata(for: url)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +115,8 @@ func mention_str(_ m: Mention, profiles: Profiles) -> String {
|
||||
let disp = Profile.displayName(profile: profile, pubkey: pk)
|
||||
return "[@\(disp)](nostr:\(encode_pubkey_uri(m.ref)))"
|
||||
case .event:
|
||||
let bevid = bech32_note_id(m.ref.ref_id) ?? m.ref.ref_id
|
||||
return "[@\(abbrev_pubkey(bevid))](nostr:\(encode_event_id_uri(m.ref)))"
|
||||
let evid = m.ref.ref_id
|
||||
return "[&\(abbrev_pubkey(evid))](nostr:\(encode_event_id_uri(m.ref)))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +125,7 @@ struct NoteContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let state = test_damus_state()
|
||||
let content = "hi there https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg"
|
||||
let artifacts = NoteArtifacts(content: content, images: [], invoices: [], links: [])
|
||||
NoteContentView(privkey: "", event: NostrEvent(content: content, pubkey: "pk"), profiles: state.profiles, show_images: true, artifacts: artifacts, size: .normal)
|
||||
let artifacts = NoteArtifacts(content: content, images: [], invoices: [])
|
||||
NoteContentView(privkey: "", event: NostrEvent(content: content, pubkey: "pk"), profiles: state.profiles, show_images: true, artifacts: artifacts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ func PostButton(action: @escaping () -> ()) -> some View {
|
||||
radius: 3,
|
||||
x: 3,
|
||||
y: 3)
|
||||
.keyboardShortcut("n", modifiers: [.command, .shift])
|
||||
}
|
||||
|
||||
func PostButtonContainer(action: @escaping () -> ()) -> some View {
|
||||
|
||||
@@ -12,7 +12,7 @@ enum NostrPostResult {
|
||||
case cancel
|
||||
}
|
||||
|
||||
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
|
||||
let POST_PLACEHOLDER = "Type your post here..."
|
||||
|
||||
struct PostView: View {
|
||||
@State var post: String = ""
|
||||
|
||||
@@ -73,12 +73,14 @@ struct ProfileName: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
|
||||
Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
||||
.font(.body)
|
||||
.fontWeight(prefix == "@" ? .none : .bold)
|
||||
if let friend = friend_icon {
|
||||
Image(systemName: friend)
|
||||
if let frend = friend_icon {
|
||||
Label("", systemImage: frend)
|
||||
.foregroundColor(.gray)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.profile_updated)) { notif in
|
||||
@@ -102,24 +104,20 @@ struct EventProfileName: View {
|
||||
|
||||
@State var display_name: String?
|
||||
|
||||
let size: EventViewKind
|
||||
|
||||
init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool, size: EventViewKind = .normal) {
|
||||
init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool) {
|
||||
self.pubkey = pubkey
|
||||
self.profile = profile
|
||||
self.prefix = ""
|
||||
self.contacts = contacts
|
||||
self.show_friend_confirmed = show_friend_confirmed
|
||||
self.size = size
|
||||
}
|
||||
|
||||
init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool, size: EventViewKind = .normal) {
|
||||
init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool) {
|
||||
self.pubkey = pubkey
|
||||
self.profile = profile
|
||||
self.prefix = prefix
|
||||
self.contacts = contacts
|
||||
self.show_friend_confirmed = show_friend_confirmed
|
||||
self.size = size
|
||||
}
|
||||
|
||||
var friend_icon: String? {
|
||||
@@ -146,10 +144,10 @@ struct EventProfileName: View {
|
||||
|
||||
Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
||||
.foregroundColor(.gray)
|
||||
.font(eventviewsize_to_font(size))
|
||||
.font(.body)
|
||||
} else {
|
||||
Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
||||
.font(eventviewsize_to_font(size))
|
||||
.font(.body)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ func pfp_line_width(_ h: Highlight) -> CGFloat {
|
||||
}
|
||||
|
||||
struct InnerProfilePicView: View {
|
||||
@Environment(\.redactionReasons) private var reasons
|
||||
|
||||
let url: URL?
|
||||
let pubkey: String
|
||||
let size: CGFloat
|
||||
@@ -52,17 +54,21 @@ struct InnerProfilePicView: View {
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
KFAnimatedImage(url)
|
||||
.configure { view in
|
||||
view.framePreloadCount = 1
|
||||
}
|
||||
.placeholder { _ in
|
||||
Placeholder
|
||||
}
|
||||
.cacheOriginalImage()
|
||||
.scaleFactor(UIScreen.main.scale)
|
||||
.loadDiskFileSynchronously()
|
||||
.fade(duration: 0.1)
|
||||
if reasons.isEmpty {
|
||||
KFAnimatedImage(url)
|
||||
.configure { view in
|
||||
view.framePreloadCount = 1
|
||||
}
|
||||
.placeholder { _ in
|
||||
Placeholder
|
||||
}
|
||||
.cacheOriginalImage()
|
||||
.scaleFactor(UIScreen.main.scale)
|
||||
.loadDiskFileSynchronously()
|
||||
.fade(duration: 0.1)
|
||||
} else {
|
||||
KFImage(url)
|
||||
}
|
||||
}
|
||||
.frame(width: size, height: size)
|
||||
.clipShape(Circle())
|
||||
|
||||
@@ -22,13 +22,13 @@ enum FollowState {
|
||||
func follow_btn_txt(_ fs: FollowState) -> String {
|
||||
switch fs {
|
||||
case .follows:
|
||||
return NSLocalizedString("Unfollow", comment: "Button to unfollow a user.")
|
||||
return "Unfollow"
|
||||
case .following:
|
||||
return NSLocalizedString("Following...", comment: "Label to indicate that the user is in the process of following another user.")
|
||||
return "Following..."
|
||||
case .unfollowing:
|
||||
return NSLocalizedString("Unfollowing...", comment: "Label to indicate that the user is in the process of unfollowing another user.")
|
||||
return "Unfollowing..."
|
||||
case .unfollows:
|
||||
return NSLocalizedString("Follow", comment: "Button to follow a user.")
|
||||
return "Follow"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,22 +119,16 @@ struct ProfileView: View {
|
||||
@StateObject var profile: ProfileModel
|
||||
@StateObject var followers: FollowersModel
|
||||
@State private var showingEditProfile = false
|
||||
@State var showing_select_wallet: Bool = false
|
||||
@State var is_zoomed: Bool = false
|
||||
@StateObject var user_settings = UserSettingsStore()
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
//@EnvironmentObject var profile: ProfileModel
|
||||
|
||||
func LNButton(lnurl: String, profile: Profile) -> some View {
|
||||
func LNButton(_ url: URL, profile: Profile) -> some View {
|
||||
Button(action: {
|
||||
if user_settings.show_wallet_selector {
|
||||
showing_select_wallet = true
|
||||
} else {
|
||||
open_with_wallet(wallet: user_settings.default_wallet.model, invoice: lnurl)
|
||||
}
|
||||
UIApplication.shared.open(url)
|
||||
}) {
|
||||
Image(systemName: "bolt.circle")
|
||||
.symbolRenderingMode(.palette)
|
||||
@@ -144,17 +138,12 @@ struct ProfileView: View {
|
||||
Button {
|
||||
UIPasteboard.general.string = profile.lnurl ?? ""
|
||||
} label: {
|
||||
Label("Copy LNURL", systemImage: "doc.on.doc")
|
||||
Label("Copy LNUrl", systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
}.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
|
||||
SelectWalletView(showingSelectWallet: $showing_select_wallet, invoice: lnurl)
|
||||
.environmentObject(user_settings)
|
||||
}
|
||||
}
|
||||
|
||||
static let markdown = Markdown()
|
||||
|
||||
|
||||
var DMButton: some View {
|
||||
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
|
||||
let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey)
|
||||
@@ -172,24 +161,25 @@ struct ProfileView: View {
|
||||
let data = damus_state.profiles.lookup(id: profile.pubkey)
|
||||
|
||||
HStack(alignment: .center) {
|
||||
ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||
ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE, highlight: .custom(Color.black, 2), profiles: damus_state.profiles)
|
||||
.onTapGesture {
|
||||
is_zoomed.toggle()
|
||||
}
|
||||
.sheet(isPresented: $is_zoomed) {
|
||||
ProfilePicView(pubkey: profile.pubkey, size: zoom_size, highlight: .none, profiles: damus_state.profiles)
|
||||
ProfilePicView(pubkey: profile.pubkey, size: zoom_size, highlight: .custom(Color.black, 2), profiles: damus_state.profiles)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
if let profile = data {
|
||||
if let lnurl = profile.lnurl {
|
||||
LNButton(lnurl: lnurl, profile: profile)
|
||||
if let lnuri = profile.lightning_uri {
|
||||
LNButton(lnuri, profile: profile)
|
||||
}
|
||||
}
|
||||
|
||||
DMButton
|
||||
|
||||
|
||||
if profile.pubkey != damus_state.pubkey {
|
||||
FollowButtonView(
|
||||
target: profile.get_follow_target(),
|
||||
@@ -206,7 +196,7 @@ struct ProfileView: View {
|
||||
ProfileNameView(pubkey: profile.pubkey, profile: data, contacts: damus_state.contacts)
|
||||
.padding(.bottom)
|
||||
|
||||
Text(ProfileView.markdown.process(data?.about ?? ""))
|
||||
Text(data?.about ?? "")
|
||||
.font(.subheadline)
|
||||
|
||||
Divider()
|
||||
@@ -241,17 +231,6 @@ struct ProfileView: View {
|
||||
followers.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
if let relays = profile.relays {
|
||||
NavigationLink(destination: UserRelaysView(state: damus_state, pubkey: profile.pubkey, relays: Array(relays.keys).sorted())) {
|
||||
Text("\(relays.keys.count)")
|
||||
.font(.subheadline.weight(.medium))
|
||||
Text("Relays")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
//
|
||||
// RecommendedRelayView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2022-12-29.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RecommendedRelayView: View {
|
||||
let damus: DamusState
|
||||
let relay: String
|
||||
let add_button: Bool
|
||||
|
||||
init(damus: DamusState, relay: String) {
|
||||
self.damus = damus
|
||||
self.relay = relay
|
||||
self.add_button = true
|
||||
}
|
||||
|
||||
init(damus: DamusState, relay: String, add_button: Bool) {
|
||||
self.damus = damus
|
||||
self.relay = relay
|
||||
self.add_button = add_button
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text(relay)
|
||||
Spacer()
|
||||
if let ev = damus.contacts.event, add_button {
|
||||
if let privkey = damus.keypair.privkey {
|
||||
Button("Add") {
|
||||
guard let ev = add_relay(ev: ev, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else {
|
||||
return
|
||||
}
|
||||
process_contact_event(pool: damus.pool, contacts: damus.contacts, pubkey: damus.pubkey, ev: ev)
|
||||
damus.pool.send(.event(ev))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RecommendedRelayView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RecommendedRelayView(damus: test_damus_state(), relay: "wss://relay.damus.io")
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import SwiftUI
|
||||
|
||||
struct RelayView: View {
|
||||
let state: DamusState
|
||||
let ev: NostrEvent
|
||||
let relay: String
|
||||
|
||||
let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
|
||||
@@ -44,48 +45,25 @@ struct RelayView: View {
|
||||
}
|
||||
.swipeActions {
|
||||
if let privkey = state.keypair.privkey {
|
||||
RemoveAction(privkey: privkey)
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
CopyAction(relay: relay)
|
||||
|
||||
if let privkey = state.keypair.privkey {
|
||||
RemoveAction(privkey: privkey)
|
||||
Button {
|
||||
guard let new_ev = remove_relay( ev: ev, privkey: privkey, relay: relay) else {
|
||||
return
|
||||
}
|
||||
|
||||
state.contacts.event = new_ev
|
||||
state.pool.send(.event(new_ev))
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CopyAction(relay: String) -> some View {
|
||||
Button {
|
||||
UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text")
|
||||
} label: {
|
||||
Label("Copy", systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveAction(privkey: String) -> some View {
|
||||
Button {
|
||||
guard let ev = state.contacts.event else {
|
||||
return
|
||||
}
|
||||
|
||||
let descriptors = state.pool.descriptors
|
||||
guard let new_ev = remove_relay( ev: ev, current_relays: descriptors, privkey: privkey, relay: relay) else {
|
||||
return
|
||||
}
|
||||
|
||||
process_contact_event(pool: state.pool, contacts: state.contacts, pubkey: state.pubkey, ev: new_ev)
|
||||
state.pool.send(.event(new_ev))
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
|
||||
struct RelayView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RelayView(state: test_damus_state(), relay: "wss://relay.damus.io", conn_color: .red)
|
||||
RelayView(state: test_damus_state(), ev: NostrEvent(content: "content", pubkey: "pk"), relay: "wss://relay.damus.io", conn_color: .red)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ struct ReplyQuoteView: View {
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
NoteContentView(privkey: privkey, event: event, profiles: profiles, show_images: false, artifacts: .just_content(event.content), size: .normal)
|
||||
NoteContentView(privkey: privkey, event: event, profiles: profiles, show_images: false, artifacts: .just_content(event.content))
|
||||
.font(.callout)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ struct SaveKeysView: View {
|
||||
complete_account_creation(account)
|
||||
}
|
||||
} else {
|
||||
DamusWhiteButton(NSLocalizedString("Let's go!", comment: "Button to complete account creation and start using the app.")) {
|
||||
DamusWhiteButton("Let's go!") {
|
||||
complete_account_creation(account)
|
||||
}
|
||||
}
|
||||
@@ -147,24 +147,20 @@ struct SaveKeyView: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
spacerBlock(width: 0, height: 0)
|
||||
Button(action: copy_text) {
|
||||
Label("", systemImage: is_copied ? "checkmark.circle.fill" : "doc.on.doc")
|
||||
.foregroundColor(is_copied ? .green : .white)
|
||||
.background {
|
||||
if is_copied {
|
||||
Circle()
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.padding(.leading, -8)
|
||||
.padding(.top, 1)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
Button(action: copy_text) {
|
||||
Label("", systemImage: is_copied ? "checkmark.circle.fill" : "doc.on.doc")
|
||||
.foregroundColor(is_copied ? .green : .white)
|
||||
.background {
|
||||
if is_copied {
|
||||
Circle()
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.padding(.leading, -8)
|
||||
.padding(.top, 1)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(text)
|
||||
@@ -178,15 +174,8 @@ struct SaveKeyView: View {
|
||||
.onTapGesture {
|
||||
copy_text()
|
||||
}
|
||||
|
||||
spacerBlock(width: 0, height: 0) /// set a 'width' > 0 here to vary key Text's aspect ratio
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func spacerBlock(width: CGFloat, height: CGFloat) -> some View {
|
||||
Color.orange.opacity(1)
|
||||
.frame(width: width, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
struct SaveKeysView_Previews: PreviewProvider {
|
||||
|
||||
@@ -55,7 +55,7 @@ struct SearchHomeView: View {
|
||||
// Fetch new information by unsubscribing and resubscribing to the relay
|
||||
model.unsubscribe()
|
||||
model.subscribe()
|
||||
}.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
var MainContent: some View {
|
||||
|
||||
@@ -53,11 +53,8 @@ struct SearchResultsView: View {
|
||||
let prof_model = ProfileModel(pubkey: h, damus: damus_state)
|
||||
let f = FollowersModel(damus_state: damus_state, target: h)
|
||||
let prof_view = ProfileView(damus_state: damus_state, profile: prof_model, followers: f)
|
||||
let ev_view = BuildThreadV2View(
|
||||
damus: damus_state,
|
||||
event_id: h
|
||||
)
|
||||
|
||||
let thread_model = ThreadModel(evid: h, damus_state: damus_state)
|
||||
let ev_view = ThreadView(thread: thread_model, damus: damus_state, is_chatroom: false)
|
||||
VStack(spacing: 50) {
|
||||
NavigationLink(destination: prof_view) {
|
||||
Text("Goto profile \(h)")
|
||||
@@ -69,10 +66,8 @@ struct SearchResultsView: View {
|
||||
case .note(let nid):
|
||||
let decoded = try? bech32_decode(nid)
|
||||
let hex = hex_encode(decoded!.data)
|
||||
let ev_view = BuildThreadV2View(
|
||||
damus: damus_state,
|
||||
event_id: hex
|
||||
)
|
||||
let thread_model = ThreadModel(evid: hex, damus_state: damus_state)
|
||||
let ev_view = ThreadView(thread: thread_model, damus: damus_state, is_chatroom: false)
|
||||
NavigationLink(destination: ev_view) {
|
||||
Text("Goto post \(nid)")
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
//
|
||||
// SelectWalletView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Suhail Saqan on 12/22/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SelectWalletView: View {
|
||||
@Binding var showingSelectWallet: Bool
|
||||
let invoice: String
|
||||
@Environment(\.openURL) private var openURL
|
||||
@State var invoice_copied: Bool = false
|
||||
@EnvironmentObject var user_settings: UserSettingsStore
|
||||
|
||||
@State var allWalletModels: [Wallet.Model] = Wallet.allModels
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section("Copy invoice") {
|
||||
HStack {
|
||||
Text(invoice).font(.body)
|
||||
.lineLimit(2)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: self.invoice_copied ? "checkmark.circle" : "doc.on.doc").foregroundColor(.blue)
|
||||
}.clipShape(RoundedRectangle(cornerRadius: 5)).onTapGesture {
|
||||
UIPasteboard.general.string = invoice
|
||||
self.invoice_copied = true
|
||||
generator.impactOccurred()
|
||||
}
|
||||
}
|
||||
Section("Select a lightning wallet"){
|
||||
List{
|
||||
Button() {
|
||||
let wallet_model = user_settings.default_wallet.model
|
||||
open_with_wallet(wallet: wallet_model, invoice: invoice)
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Default Wallet").font(.body).foregroundColor(.blue)
|
||||
}
|
||||
}.buttonStyle(.plain)
|
||||
List($allWalletModels) { $wallet in
|
||||
if wallet.index >= 0 {
|
||||
Button() {
|
||||
open_with_wallet(wallet: wallet, invoice: invoice)
|
||||
} label: {
|
||||
HStack {
|
||||
Image(wallet.image).resizable().frame(width: 32.0, height: 32.0,alignment: .center).cornerRadius(5)
|
||||
Text(wallet.displayName).font(.body)
|
||||
}
|
||||
}.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}.padding(.vertical, 2.5)
|
||||
}
|
||||
}.navigationBarTitle(Text("Pay the lightning invoice"), displayMode: .inline).navigationBarItems(trailing: Button(action: {
|
||||
self.showingSelectWallet = false
|
||||
}) {
|
||||
Text("Done").bold()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SelectWalletView_Previews: PreviewProvider {
|
||||
@State static var show: Bool = true
|
||||
@State static var invoice: String = ""
|
||||
|
||||
static var previews: some View {
|
||||
SelectWalletView(showingSelectWallet: $show, invoice: "")
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ struct SetupView: View {
|
||||
|
||||
CarouselView()
|
||||
|
||||
DamusWhiteButton(NSLocalizedString("Create Account", comment: "Button to create an account.")) {
|
||||
DamusWhiteButton("Create Account") {
|
||||
self.state = .create_account
|
||||
}
|
||||
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
//
|
||||
// ThreadV2View.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Thomas Tastet on 25/12/2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ThreadV2 {
|
||||
var parentEvents: [NostrEvent]
|
||||
var current: NostrEvent
|
||||
var childEvents: [NostrEvent]
|
||||
|
||||
mutating func clean() {
|
||||
// remove duplicates
|
||||
self.parentEvents = Array(Set(self.parentEvents))
|
||||
self.childEvents = Array(Set(self.childEvents))
|
||||
|
||||
// remove empty contents
|
||||
self.parentEvents = self.parentEvents.filter { event in
|
||||
return !event.content.isEmpty
|
||||
}
|
||||
self.childEvents = self.childEvents.filter { event in
|
||||
return !event.content.isEmpty
|
||||
}
|
||||
|
||||
// sort events by publication date
|
||||
self.parentEvents = self.parentEvents.sorted { event1, event2 in
|
||||
return event1 < event2
|
||||
}
|
||||
self.childEvents = self.childEvents.sorted { event1, event2 in
|
||||
return event1 < event2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct BuildThreadV2View: View {
|
||||
let damus: DamusState
|
||||
|
||||
@State var parents_ids: [String] = []
|
||||
let event_id: String
|
||||
|
||||
@State var current_event: NostrEvent? = nil
|
||||
|
||||
@State var thread: ThreadV2? = nil
|
||||
|
||||
@State var current_events_uuid: String = ""
|
||||
@State var childs_events_uuid: String = ""
|
||||
@State var parents_events_uuids: [String] = []
|
||||
|
||||
@State var subscriptions_uuids: [String] = []
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
init(damus: DamusState, event_id: String) {
|
||||
self.damus = damus
|
||||
self.event_id = event_id
|
||||
}
|
||||
|
||||
func unsubscribe_all() {
|
||||
print("ThreadV2View: Unsubscribe all..")
|
||||
|
||||
for subscriptions in subscriptions_uuids {
|
||||
unsubscribe(subscriptions)
|
||||
}
|
||||
}
|
||||
|
||||
func unsubscribe(_ sub_id: String) {
|
||||
if subscriptions_uuids.contains(sub_id) {
|
||||
damus.pool.unsubscribe(sub_id: sub_id)
|
||||
|
||||
subscriptions_uuids.remove(at: subscriptions_uuids.firstIndex(of: sub_id)!)
|
||||
}
|
||||
}
|
||||
|
||||
func subscribe(filters: [NostrFilter], sub_id: String = UUID().description) -> String {
|
||||
damus.pool.register_handler(sub_id: sub_id, handler: handle_event)
|
||||
damus.pool.send(.subscribe(.init(filters: filters, sub_id: sub_id)))
|
||||
|
||||
subscriptions_uuids.append(sub_id)
|
||||
|
||||
return sub_id
|
||||
}
|
||||
|
||||
func handle_current_events(ev: NostrEvent) {
|
||||
if current_event != nil {
|
||||
return
|
||||
}
|
||||
|
||||
current_event = ev
|
||||
|
||||
thread = ThreadV2(
|
||||
parentEvents: [],
|
||||
current: current_event!,
|
||||
childEvents: []
|
||||
)
|
||||
|
||||
// Get parents
|
||||
parents_ids = current_event!.tags.enumerated().filter { (index, tag) in
|
||||
return tag.count >= 2 && tag[0] == "e" && !current_event!.content.contains("#[\(index)]")
|
||||
}.map { tag in
|
||||
return tag.1[1]
|
||||
}
|
||||
|
||||
print("ThreadV2View: Parents list: (\(parents_ids)")
|
||||
|
||||
if parents_ids.count > 0 {
|
||||
// Ask for parents
|
||||
let parents_events = NostrFilter(
|
||||
ids: parents_ids,
|
||||
limit: UInt32(parents_ids.count)
|
||||
)
|
||||
|
||||
let uuid = subscribe(filters: [parents_events])
|
||||
parents_events_uuids.append(uuid)
|
||||
print("ThreadV2View: Ask for parents (\(uuid)) (\(parents_events))")
|
||||
}
|
||||
|
||||
// Ask for children
|
||||
let childs_events = NostrFilter(
|
||||
kinds: [1],
|
||||
referenced_ids: [self.event_id],
|
||||
limit: 50
|
||||
)
|
||||
childs_events_uuid = subscribe(filters: [childs_events])
|
||||
print("ThreadV2View: Ask for children (\(childs_events) (\(childs_events_uuid))")
|
||||
}
|
||||
|
||||
func handle_parent_events(sub_id: String, nostr_event: NostrEvent) {
|
||||
|
||||
// We are filtering this later
|
||||
thread!.parentEvents.append(nostr_event)
|
||||
|
||||
// Get parents of parents
|
||||
let local_parents_ids = nostr_event.tags.enumerated().filter { (index, tag) in
|
||||
return tag.count >= 2 && tag[0] == "e" && !nostr_event.content.contains("#[\(index)]")
|
||||
}.map { tag in
|
||||
return tag.1[1]
|
||||
}.filter { tag_id in
|
||||
return !parents_ids.contains(tag_id)
|
||||
}
|
||||
|
||||
print("ThreadV2View: Sub Parents list: (\(local_parents_ids))")
|
||||
|
||||
// Expand new parents id
|
||||
parents_ids.append(contentsOf: local_parents_ids)
|
||||
|
||||
if local_parents_ids.count > 0 {
|
||||
// Ask for parents
|
||||
let parents_events = NostrFilter(
|
||||
ids: local_parents_ids,
|
||||
limit: UInt32(local_parents_ids.count)
|
||||
)
|
||||
let uuid = subscribe(filters: [parents_events])
|
||||
parents_events_uuids.append(uuid)
|
||||
print("ThreadV2View: Ask for sub_parents (\(local_parents_ids)) \(uuid)")
|
||||
}
|
||||
|
||||
thread!.clean()
|
||||
unsubscribe(sub_id)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||
guard case .nostr_event(let nostr_response) = ev else {
|
||||
return
|
||||
}
|
||||
|
||||
guard case .event(let id, let nostr_event) = nostr_response else {
|
||||
return
|
||||
}
|
||||
|
||||
// Is current event
|
||||
if id == current_events_uuid {
|
||||
handle_current_events(ev: nostr_event)
|
||||
return
|
||||
}
|
||||
|
||||
if parents_events_uuids.contains(id) {
|
||||
handle_parent_events(sub_id: id, nostr_event: nostr_event)
|
||||
return
|
||||
}
|
||||
|
||||
if id == childs_events_uuid {
|
||||
// We are filtering this later
|
||||
thread!.childEvents.append(nostr_event)
|
||||
|
||||
thread!.clean()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func reload() {
|
||||
self.unsubscribe_all()
|
||||
print("ThreadV2View: Reload!")
|
||||
|
||||
// Get the current event
|
||||
current_events_uuid = subscribe(filters: [
|
||||
NostrFilter(
|
||||
ids: [self.event_id],
|
||||
limit: 1
|
||||
)
|
||||
])
|
||||
print("subscribing to threadV2 \(event_id) with sub_id \(current_events_uuid)")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if thread == nil {
|
||||
ProgressView()
|
||||
} else {
|
||||
ThreadV2View(damus: damus, thread: thread!)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if self.thread == nil {
|
||||
self.reload()
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
self.unsubscribe_all()
|
||||
}
|
||||
.onReceive(handle_notify(.switched_timeline)) { n in
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ThreadV2View: View {
|
||||
let damus: DamusState
|
||||
let thread: ThreadV2
|
||||
|
||||
var body: some View {
|
||||
ScrollViewReader { reader in
|
||||
ScrollView {
|
||||
VStack {
|
||||
// MARK: - Parents events view
|
||||
VStack {
|
||||
ForEach(thread.parentEvents, id: \.id) { event in
|
||||
NavigationLink(destination: BuildThreadV2View(
|
||||
damus: damus,
|
||||
event_id: event.id
|
||||
)){
|
||||
EventView(
|
||||
event: event,
|
||||
highlight: .none,
|
||||
has_action_bar: true,
|
||||
damus: damus,
|
||||
show_friend_icon: true, // TODO: change it
|
||||
size: .small
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.onAppear {
|
||||
// TODO: find another solution to prevent layout shifting and layout blocking on large responses
|
||||
reader.scrollTo("main", anchor: .bottom)
|
||||
}
|
||||
}
|
||||
}.background(GeometryReader { geometry in
|
||||
// get the height and width of the EventView view
|
||||
let eventHeight = geometry.frame(in: .global).height
|
||||
// let eventWidth = geometry.frame(in: .global).width
|
||||
|
||||
// vertical gray line in the background
|
||||
Rectangle()
|
||||
.fill(Color.gray.opacity(0.25))
|
||||
.frame(width: 2, height: eventHeight)
|
||||
.offset(x: 25, y: 40)
|
||||
})
|
||||
|
||||
// MARK: - Actual event view
|
||||
EventView(
|
||||
event: thread.current,
|
||||
highlight: .none,
|
||||
has_action_bar: true,
|
||||
damus: damus,
|
||||
show_friend_icon: true, // TODO: change it
|
||||
size: .selected
|
||||
).id("main")
|
||||
|
||||
// MARK: - Responses of the actual event view
|
||||
ForEach(thread.childEvents, id: \.id) { event in
|
||||
NavigationLink(destination: BuildThreadV2View(
|
||||
damus: damus,
|
||||
event_id: event.id
|
||||
)){
|
||||
EventView(
|
||||
event: event,
|
||||
highlight: .none,
|
||||
has_action_bar: true,
|
||||
damus: damus,
|
||||
show_friend_icon: true, // TODO: change it
|
||||
size: .small
|
||||
)
|
||||
}.buttonStyle(.plain)
|
||||
}
|
||||
}.padding()
|
||||
}.navigationBarTitle("Thread")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ThreadV2View_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
BuildThreadV2View(damus: test_damus_state(), event_id: "ac9fd97b53b0c1d22b3aea2a3d62e11ae393960f5f91ee1791987d60151339a7")
|
||||
ThreadV2View(
|
||||
damus: test_damus_state(),
|
||||
thread: ThreadV2(
|
||||
parentEvents: [
|
||||
NostrEvent(id: "1", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||
NostrEvent(id: "2", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||
NostrEvent(id: "3", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||
],
|
||||
current: NostrEvent(id: "4", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||
childEvents: [
|
||||
NostrEvent(id: "5", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||
NostrEvent(id: "6", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||
]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,11 @@ struct InnerTimelineView: View {
|
||||
EmptyTimelineView()
|
||||
} else {
|
||||
ForEach(events.filter(filter), id: \.id) { (ev: NostrEvent) in
|
||||
//let tm = ThreadModel(event: inner_event_or_self(ev: ev), damus_state: damus)
|
||||
//let is_chatroom = should_show_chatroom(ev)
|
||||
//let tv = ThreadView(thread: tm, damus: damus, is_chatroom: is_chatroom)
|
||||
let tm = ThreadModel(event: inner_event_or_self(ev: ev), damus_state: damus)
|
||||
let is_chatroom = should_show_chatroom(ev)
|
||||
let tv = ThreadView(thread: tm, damus: damus, is_chatroom: is_chatroom)
|
||||
|
||||
NavigationLink(destination: BuildThreadV2View(
|
||||
damus: damus,
|
||||
event_id: ev.id
|
||||
)) {
|
||||
NavigationLink(destination: tv) {
|
||||
EventView(event: ev, highlight: .none, has_action_bar: true, damus: damus, show_friend_icon: show_friend_icon)
|
||||
}
|
||||
.isDetailLink(true)
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// UserRelaysView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2022-12-29.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct UserRelaysView: View {
|
||||
let state: DamusState
|
||||
let pubkey: String
|
||||
let relays: [String]
|
||||
|
||||
@State var relay_state: [(String, Bool)]
|
||||
|
||||
init (state: DamusState, pubkey: String, relays: [String]) {
|
||||
self.state = state
|
||||
self.pubkey = pubkey
|
||||
self.relays = relays
|
||||
let relay_state = UserRelaysView.make_relay_state(pool: state.pool, relays: relays)
|
||||
self._relay_state = State(initialValue: relay_state)
|
||||
}
|
||||
|
||||
static func make_relay_state(pool: RelayPool, relays: [String]) -> [(String, Bool)] {
|
||||
return relays.map({ r in
|
||||
return (r, pool.get_relay(r) == nil)
|
||||
}).sorted { (a, b) in a.0 < b.0 }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List(relay_state, id: \.0) { (r, add) in
|
||||
RecommendedRelayView(damus: state, relay: r, add_button: add)
|
||||
}
|
||||
.onReceive(handle_notify(.relays_changed)) { _ in
|
||||
self.relay_state = UserRelaysView.make_relay_state(pool: state.pool, relays: self.relays)
|
||||
}
|
||||
.navigationBarTitle("Relays")
|
||||
}
|
||||
}
|
||||
|
||||
struct UserRelaysView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
UserRelaysView(state: test_damus_state(), pubkey: "", relays: [])
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "Damus";
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "damus";
|
||||
@@ -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> & 1 other</string>
|
||||
<key>other</key>
|
||||
<string> & %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> & 1 other</string>
|
||||
<key>other</key>
|
||||
<string> & %d others</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,4 +0,0 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "Damus";
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "damus";
|
||||
@@ -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> & 1 other</string>
|
||||
<key>other</key>
|
||||
<string> & %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> & 1 other</string>
|
||||
<key>other</key>
|
||||
<string> & %d others</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,4 +0,0 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "Damus";
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "damus";
|
||||
@@ -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> & 1 other</string>
|
||||
<key>other</key>
|
||||
<string> & %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> & 1 other</string>
|
||||
<key>other</key>
|
||||
<string> & %d others</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -18,40 +18,6 @@ final class InvoiceTests: XCTestCase {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testParseAnyAmountInvoice() throws {
|
||||
let invstr = "LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C"
|
||||
let parsed = parse_mentions(content: invstr, tags: [])
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 1)
|
||||
XCTAssertNotNil(parsed[0].is_invoice)
|
||||
guard let invoice = parsed[0].is_invoice else {
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(invoice.amount, .any)
|
||||
//XCTAssertEqual(invoice.expiry, 604800)
|
||||
//XCTAssertEqual(invoice.created_at, 1666139119)
|
||||
XCTAssertEqual(invoice.string, invstr)
|
||||
}
|
||||
|
||||
func testTextAfterInvoice() throws {
|
||||
let invstr = """
|
||||
LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C hi there
|
||||
"""
|
||||
let parsed = parse_mentions(content: invstr, tags: [])
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 2)
|
||||
XCTAssertNotNil(parsed[0].is_invoice)
|
||||
XCTAssertEqual(parsed[1].is_text, " hi there")
|
||||
guard let invoice = parsed[0].is_invoice else {
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(invoice.amount, .any)
|
||||
//XCTAssertEqual(invoice.expiry, 604800)
|
||||
//XCTAssertEqual(invoice.created_at, 1666139119)
|
||||
}
|
||||
|
||||
func testParseInvoiceUpper() throws {
|
||||
let invstr = "LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R"
|
||||
let parsed = parse_mentions(content: invstr, tags: [])
|
||||
@@ -62,7 +28,7 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
|
||||
guard let invoice = parsed[0].is_invoice else {
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(invoice.amount, .specific(10000))
|
||||
XCTAssertEqual(invoice.amount, 10000)
|
||||
XCTAssertEqual(invoice.expiry, 604800)
|
||||
XCTAssertEqual(invoice.created_at, 1666139119)
|
||||
XCTAssertEqual(invoice.string, invstr)
|
||||
@@ -96,7 +62,7 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
|
||||
guard let invoice = parsed[0].is_invoice else {
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(invoice.amount, .specific(10000))
|
||||
XCTAssertEqual(invoice.amount, 10000)
|
||||
XCTAssertEqual(invoice.expiry, 604800)
|
||||
XCTAssertEqual(invoice.created_at, 1666139119)
|
||||
XCTAssertEqual(invoice.string, invstr)
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
//
|
||||
// MarkdownTests.swift
|
||||
// damusTests
|
||||
//
|
||||
// Created by Lionello Lunesu on 2022-12-28.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import damus
|
||||
|
||||
class MarkdownTests: XCTestCase {
|
||||
let md_opts: AttributedString.MarkdownParsingOptions =
|
||||
.init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func test_convert_link() throws {
|
||||
let helper = Markdown()
|
||||
let md = helper.process("prologue https://nostr.build epilogue")
|
||||
let expected = try AttributedString(markdown: "prologue [https://nostr.build](https://nostr.build) epilogue", options: md_opts)
|
||||
XCTAssertEqual(md, expected)
|
||||
}
|
||||
|
||||
func test_convert_link_no_scheme() throws {
|
||||
let helper = Markdown()
|
||||
let md = helper.process("prologue damus.io epilogue")
|
||||
let expected = try AttributedString(markdown: "prologue [damus.io](https://damus.io) epilogue", options: md_opts)
|
||||
XCTAssertEqual(md, expected)
|
||||
}
|
||||
|
||||
func test_convert_links() throws {
|
||||
let helper = Markdown()
|
||||
let md = helper.process("prologue damus.io https://nostr.build epilogue")
|
||||
let expected = try AttributedString(markdown: "prologue [damus.io](https://damus.io) [https://nostr.build](https://nostr.build) epilogue", options: md_opts)
|
||||
XCTAssertEqual(md, expected)
|
||||
}
|
||||
}
|
||||