Commit Graph

555 Commits

Author SHA1 Message Date
6cd9d7b1da Remove unused Trie and UserSearchCache as they have been superseded by NostrDB 2024-06-09 11:04:06 -04:00
Daniel D’Aquino
901a6fc98f Revoke device token when user switches to local notification mode
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
2024-05-24 16:49:09 -07:00
Daniel D’Aquino
8feb228ea0 Add NIP-98 authentication to push notification client
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
2024-05-24 16:49:09 -07:00
Daniel D’Aquino
b148fb735e Move device token sending logic to its own component
This commit moves the device token logic to a new
PushNotificationClient, to move complexity from this specific feature
away from damusApp.swift

This commit also slightly improves the handling of device tokens, by
caching it on the client struct even if the user is using local
notifications, so that the device token can be sent to the server immediately after
switching to push notifications mode.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
2024-05-24 16:49:09 -07:00
Daniel D’Aquino
0a9bcb6189 Add notification mode setting
This allows the user to switch between local and push notifications

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
2024-05-24 16:49:09 -07:00
Daniel D’Aquino
46185c55d1 Chunk home filters to avoid hitting max filter item limits
When a user is following several accounts, they may get a stale feed
caused by the subscription request being rejected by relays (due to max filter item limits).

This commit implements a fix that gets around the issue by
creating several chunked filters for the home feed event and contact
metadata subscriptions.

This is a short to medium-term practical fix, where we get around the
practical limitations imposed by most relays. In the future we should
work on longer-term solutions, which will likely require protocol improvements

Main Test
---------

Procedure:
1. Login with Elsat's npub (Or some account that follows about 2K people)
2. Check the home feed. There should be fresh notes.

REPRO:
Device: iPhone 15 simulator
iOS: 17.4
Damus: 1.9 (3) (0d9954290a)
Results:
- No fresh notes, most recent post is from several hours ago (Feed is stale)

FIX TEST:
Device: iPhone 15 simulator
iOS: 17.4
Damus: This commit
Results:
- Fresh notes appear, most recent post is from a few seconds ago.

Other testing:
--------------

- New automated test passing
- All other automated tests passing
- Tested scrolling down the feed on these conditions:
  - Device: iPhone 13 Mini
  - iOS: 17.4.1
  - Accounts:
    - One with about 160 contacts and 10 relays (Daniel D’Aquino)
    - One with about 1K+ contacts and 9 relays (Freedom Smuggler)
    - One with about 981 contacts and 6 relays (jb55)
    - Elsat's account (2K+ accounts and 8 relays)
  - Result: None of those were stale

Changelog-Fixed: Fix stale feed issue when follow list is too big
Closes: https://github.com/damus-io/damus/issues/2194
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
2024-05-13 10:56:15 -07:00
William Casarin
52aefc8d64 nip10: simplify and fix reply-to-root bugs
This removes EventRefs alltogether and uses the form we use in Damus
Android.

This simplifies our ThreadReply logic and fixes a reply-to-root bug

Reported-by: NotBiebs <justinbieber@stemstr.app>
Changelog-Fixed: Fix thread bug where a quote isn't picked up as a reply
Signed-off-by: William Casarin <jb55@jb55.com>
2024-05-11 09:19:22 -07:00
William Casarin
514a053dce nip10: marker replies
This should drastically increase compatibility for damus replies in
other clients.

Also filter non-pubkey references when replying so we don't run into the
q-tag bug.

Changelog-Added: Added nip10 marker replies
Changelog-Fixed: Fixed issue where some replies were including the q tag
Fixes: https://github.com/damus-io/damus/issues/2239
Fixes: https://github.com/damus-io/damus/issues/2233
Signed-off-by: William Casarin <jb55@jb55.com>
2024-05-09 13:33:04 -07:00
Daniel D’Aquino
ceb6eb03fb Implement cache on MutelistManager
To filter muted events on a timeline, we need to check if an event is
muted. However, we need to do so in a very efficient manner, to avoid performance degradation.

This commit improves performance by caching mute statuses for as long as
the mutelist stays the same.

Testing
--------

PASS

Device: iPhone 13 Mini
iOS: 17.4.1
Damus: This commit
Damus baseline: bb8dba6df6e2534dfb193402399b31a4fae8052d
Steps:
1. Downgrade to baseline version, Run SwiftUI profiler on Xcode instruments
2. Scroll down home feed. Try to notice hangs and microhangs on the UI
3. Upgrade to version under test. Start the same profiler test.
4. Check that hangs/microhangs frequency and severity are roughly the same.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-05-07 18:05:45 -07:00
Daniel D’Aquino
e981ae247e Mute: Add user_keypair to MutelistManager
The user keypair is necessary to determine whether or not a given event
is muted or not. Previously, the user keypair had to be passed on each
function call as an optional parameter, and word filtering would not
work unless the caller remembered to add the keypair parameter.

All usages of MutelistManager functions indicate that the desired base
keypair is always the same as the DamusState's keypair that owns the
MutelistManager. Therefore, it is simpler and less error prone to simply
pass the keypair to MutelistManager during its initialization.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-05-07 18:05:45 -07:00
William Casarin
a721256e9b nip10: add marker nip10 support when reading notes
We still need to add these when writing notes.

Changelog-Added: Add marker nip10 support when reading notes
Signed-off-by: William Casarin <jb55@jb55.com>
2024-05-07 14:07:39 -07:00
Daniel D’Aquino
c6d9e0b3c9 Fix GIF uploads
This commit fixes GIF uploads and improves GIF support:
- MediaPicker will now skip location data removal processing, as it is not needed on GIF images and causes them to be converted to JPEG images
- The uploader now sets more accurate MIME types on the upload request

Issue Repro
-----------

Device: iPhone 13 Mini
iOS: 17.4.1
Damus: `ada99418f6fcdb1354bc5c1c3f3cc3b4db994ce6`
Steps:
1. Download a GIF from GIPHY to the iOS photo gallery
2. Upload that and attach into a post in Damus
3. Check if GIF is animated.
Results: GIF is not animated. Issue is reproduced.

Testing
-------

PASS

Device: iPhone 13 Mini
iOS: 17.4.1
Damus: this commit
Steps:
1. Create a new post
2. Upload the same GIF as the repro and post
3. Make sure GIF is animated. PASS
4. Create a new post
5. Upload a new GIF image (that has never been uploaded by the user on the app) and post
6. Make sure the GIF is animated on the post. PASS
7. Make sure that JPEGs can still be successfully uploaded. PASS
8. Make sure that MP4s can be uploaded.
9. Make a new post that contains 1 JPEG, 1 MP4 file, and 2 GIF files. Make sure they are all uploaded correctly and all GIF files are animated. PASS

Closes: https://github.com/damus-io/damus/issues/2157
Changelog-Fixed: Fix broken GIF uploads
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
2024-05-01 10:27:05 -07:00
Daniel D’Aquino
6d5a152c17 Fix Ghost notifications from Damus Purple Impending expiration
This commit fixes the "ghost notifications" experienced by Purple users
whose membership has expired (or about to expire).

It does that by using a similar mechanism as other notifications to keep
track of the last event date seen on the notifications tab in a
persistent way.

Testing
--------

iOS: 17.4
Device: iPhone 15 simulator
damus-api: bfe6c4240a0b3729724162896f0024a963586f7c
Damus: This commit
Setup:
1. Local Purple server
2. Damus running on local testing mode for Purple
3. An existing but expired Purple account (on the local server)

Steps:
1. Reopen app after pointing to the new server and setting things up.
2. Check that the bell icon shows there is a new notification. PASS
3. Check that purple expiration notifications are visible. PASS
4. Restart app.
5. Check the bell icon. This time there should be no new notifications. PASS
6. Using another account, engage with the primary test account to cause a new notification to appear.
7. After a second or two, the bell icon should indicate there is a new notification from the other user. PASS
8. Switch out and into the app. Check that the bell icon does not indicate any new notifications. PASS
9. Restart the app again. The bell icon should once again NOT indicate any new notifications. PASS

Changelog-Fixed: Fix ghost notifications caused by Purple impending expiration notifications
Closes: https://github.com/damus-io/damus/issues/2158
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
2024-04-29 17:04:06 -07:00
William Casarin
052ea9b727 Revert "Custom video loader caching technique"
This reverts commit ba494f94ab.
2024-04-25 14:56:29 -07:00
Daniel D’Aquino
19ba020bd0 contacts: save first list to storage during onboarding
This commit adds a mechanism to add the contact list to storage as soon
as it is generated, and thus it reduces the risk of poor network
conditions causing issues.

Changelog-Fixed: Improve reliability of contact list creation during onboarding
Closes: https://github.com/damus-io/damus/issues/2057
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Link: 20240422230912.65056-3-daniel@daquino.me
Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-23 16:40:50 -07:00
Daniel D’Aquino
43a5bbd53a contacts: save the users' latest contact event ID
... to a persistent setting, and try to load it from NostrDB on app start.

This commit causes the user's contact list event ID to be saved
persistently as a user-specific setting, and to be loaded immediately
after startup from the local NostrDB instance.

This helps improve reliability around contact lists, since we previously
relied on fetching that contact list from other relays.

Eventually we will not need the event ID to be stored at all, as we will
be able to query NostrDB, but for now having the latest event ID
persistently stored will allow us to get around this limitation in the
cleanest possible way (i.e. without having to store the event itself
into another mechanism, and migrating it later to NostrDB)

Other notes:

- It uses a mechanism similar to other user settings, so it is
  pubkey-specific and should handle login/logout cases

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Link: 20240422230912.65056-2-daniel@daquino.me
Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-23 16:40:25 -07:00
Daniel D’Aquino
ba494f94ab Custom video loader caching technique
This commit brings significant improvements to the video cache feature.

Previously, the cache would merely download the video when requested, in
parallel with AVPlayer which also triggers a video download.

The video cache has been updated to tap into the AVPlayer loading
process, removing the download duplication.

Here is how that works:
1. The player requests an AVAsset from the cache.
2. The cache will return a cached asset if possible, or a special AVURLAsset with a custom `AVAssetResourceLoaderDelegate`.
3. The video player will start sending loading requests to this loader delegate.
4. Upon receiving the first request, the loader delegate begins to download the video data on the background.
5. Upon receiving these requests, the loader delegate will also record the requests, so that it can serve them once possible
6. The loader delegate keeps track of all video data chunks as it receives them from the download task, through the `URLSessionDataDelegate` and `URLSessionTaskDelegate` protocols
7. As it receives data, it checks all pending loading requests from the AVPlayer, and fulfills them as soon as possible
8. If the download fails (e.g. timeout errors, loss of connection), it attempts to restart the download.
9. If the download succeeds, it saves the video to the cache on disk.

Closes: https://github.com/damus-io/damus/issues/1717
Changelog-Added: Add video cache to save network bandwidth
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Link: 20240411004129.84436-4-daniel@daquino.me
Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-17 15:06:12 -07:00
Daniel D’Aquino
26d2627a1c Cache videos
This commit implements a simple but functional video cache.

It works by providing a method called `maybe_cached_url`, where a video
URL can be passed in, and this method will either return the URL of a
cached version of this video if available, or the original URL if not. It also
downloads new video URLs on the background into the cache folder for use
next time.

Functional testing
-------------------

PASS

Device: iPhone 15 simulator
iOS: 17.4
Damus: Approximately this commit
Setup:
- Debug connection
- Expiry time locally changed to 5 minutes

Steps:
1. Basic functionality
  1. Go to a profile with lots of videos
  2. Scroll down
  3. Filter logs to only logs that start with "Loading video with URL"
  4. Check that most videos are being loaded from external URLs. PASS
  5. Now restart the app and go to that same profile
  6. Scroll down and watch logs. Videos should now be loaded with an internal file URL. PASS
2. Automatic cache refresh after expiry
  1. Go to the video-heavy profile, make note of the external URL.
  2. Go to a different screen and then come back to that video. Make sure the file was loaded from cache. PASS
  3. Now go to a different screen and wait 5 minutes.
  4. Come back to the same video. It should be loaded from the external URL. PASS
3. "Clear cache" button functionality
  1. Go to the video-heavy profile, make note of the external URL.
  2. Go to a different screen and then come back to that video. Make sure the file was loaded from cache. PASS
  3. Now quit the app (to ensure file is not in use when trying to delete it)
  4. Clear cache in settings
  5. Go back to the same video. It should now be loaded from the external URL. PASS

Performance testing
-----------------------

Device: iPhone 13 mini
iOS: 17.3.1
Damus: This commit
Baseline: 87de88861adb3b41d73998452e7c876ab5ee06bf
Setup:
- Debug connection
- Expiry time locally changed to 5 minutes
- Running on Profile mode, with XCode Instruments

Steps:
1. Start recording network activity with XCode Instruments
2. Go to a video-heavy profile (e.g. Julian Figueroa)
3. Scroll down to a specific video (Make sure to scroll through at least 5 videos)
4. Stop recording and measure the "Bytes In" from "Network connections"
5. Repeat this for all test configurations

Results:
- Baseline (No caching support): 26.74 MiB
- This commit (First run, cleared cache): 40.52 MiB
- This commit (Second run, cache filled with videos): 8.13 MiB

Automated test coverage
------------------------

PASS

Device: iPhone 15 simulator
iOS: 17.4
Damus: This commit
Coverage:
- Ran new automated tests multiple times. PASS 3/3 times
- Ran all other automated tests. PASS

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Link: 20240411004129.84436-3-daniel@daquino.me
Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-17 15:06:12 -07:00
ericholguin
e332a7f82c ui: Longform Improvements
This patch improves longform previews by including the image title and tags.
In addition with minor UI touch ups.

Testing:

iPhone 15 Pro Max (17.3.1) Dark Mode:
https://v.nostr.build/9zgvv.mp4

iPhone SE (3rd generation) (16.4) Light Mode:
https://v.nostr.build/VwEKQ.mp4

Closes: https://github.com/damus-io/damus/issues/1742
Changelog-Added: Added title image and tags to longform events
Signed-off-by: ericholguin <ericholguin@apache.org>
Link: 20240415031636.68846-1-ericholguin@apache.org
Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-17 15:04:38 -07:00
Sean Kibler
517f3714e8 postview: add haptic feedback on media upload result
Closes: https://github.com/damus-io/damus/pull/2115
Signed-off-by: Sean Kibler <skibler@protonmail.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-04 10:33:28 -07:00
Daniel D’Aquino
e951370a76 Fix relay URL trailing slash issues
This commit tries to replace all usage of `String` to represent relay
URLs and use `RelayURL` which automatically converts strings to a
canonical relay URL format that is more reliable and avoids issues related to
trailing slashes.

Test 1: Main issue fix
-----------------------

PASS

Device: iPhone 15 Simulator
iOS: 17.4
Damus: This commit
Steps:
1. Delete all connected relays
2. Add `wss://relay.damus.io/` (with the trailing slash) to the relay list
3. Try to post. Post should succeed. PASS
4. Try removing this newly added relay. Relay should be removed successfully. PASS

Test 2: Persistent relay list after upgrade
--------------------------------------------

PASS

Device: iPhone 15 Simulator
iOS: 17.4
Damus: 1.8 (1) `247f313b` + This commit
Steps:
1. Downgrade to old version
2. Add some relays to the list, some without a trailing slash, some with
3. Upgrade to this commit
4. All relays added in step 2 should still be there, and ones with a trailing slash should have been corrected to remove the trailing slash

Test 3: Miscellaneous regression tests
--------------------------------------

Device: iPhone 15 Simulator
iOS: 17.4
Damus: This commit
Coverage:
1. Posting works
2. Search works
3. Relay connection status works
4. Adding relays work
5. Removing relays work
6. Adding relay with trailing slashes works (it fixes itself to remove the trailing slash)
7. Adding relays with different paths works (e.g. wss://yabu.me/v1 and wss://yabu.me/v2)
8. Adding duplicate relay (but with trailing slash) gets rejected as expected
9. Relay details page works. All items on that view loads correctly
10. Relay logs work
11. Getting follower counts and seeing follow lists on profiles still work
12. Relay list changes persist after app restart
13. Notifications view still work
14. Copying the user's pubkey and profile link works
15. Share note + copy link button still works
16. Connecting NWC wallet works
17. One-tap zaps work
18. Onboarding works
19. Unit tests all passing

Closes: https://github.com/damus-io/damus/issues/2072
Changelog-Fixed: Fix bug that would cause connection issues with relays defined with a trailing slash URL, and an inability to delete them.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-25 09:24:17 +01:00
William Casarin
c521998158 ui: add quoted reposts view to threads
This adds quote reposts as an additional detail view on threads. It will
list quoted reposts that have the `q` tag. Not all clients have updated
to this yet (like primal), but hopefully they will soon.

Changelog-Added: Show list of quoted reposts in threads
Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
3f1f257df2 model: upgrade EventsModel to support quote reposts queries
We also switch to using an eventholder because that is a bit nicer
when it comes to rendering quotes in timelines.

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
770a845b36 filters: add ContentFilters helper constructor
This is slightly faster for timeline code that needs default filters

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
68dd47130e eventsmodel: remove inheritence in Reactions/Reposts model
Simplify with new EventsModel constructors. This is slightly less
typesafe but its not a big deal, I hate inheritence more.

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
8cdbc84093 home: add quote repost counter and handler
This adds the initial support code for counting and handling
quote reposts.

Eventually we are going to replace all of the event counts by stats
within nostrdb, but we do this in the meantime now.

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
d83d618829 Merge branch 'v1.7-madeira-release' 2024-03-11 09:58:50 +00:00
Daniel D’Aquino
ad8d30ded1 Improve mechanism of IAP verification with the server right after purchase
It was observed that sending the receipt to the server right after
performing the StoreKit purchase caused issues due to the receipt not
being completely ready when it got sent, causing rejections by the
server.

This commit, in conjuction with backend changes, implements purchase
verification through transaction IDs directly.

Testing
--------

PASS

Device: iPhone 13 Mini (physical device)
iOS: 17.3.1
Damus: This commit
damus-api: `a878da5598a9344a4d351f9a9da16712ce0615b7`
Setup:
- Local server Setup
- Local testing mode on Damus developer settings
- Clean Sandbox account (Create new sandbox account if clearing purchase history does not work)
- Fresh DB
- App is closed before starting.
- Run app on release target
- MOCK_VERIFY=false on server, with all IAP environment correctly setup
Steps:
1. Make Purple IAP purchase. Make sure that:
    - Server logs indicate `/apple-iap/transaction-id` is being hit and returning HTTP 200. PASS
    - No errors appear on the iOS side. After purchase the welcome sheet should appear, and then the account info. PASS

Part of: https://github.com/damus-io/damus/issues/2036
Changelog-Fixed: Fix in-app purchase issue that would trigger an error on purchase before confirming the account information.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-11 09:29:02 +00:00
Daniel D’Aquino
f738aaf358 Increase verbosity of IAP-related logs
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-03-11 09:28:15 +00:00
kernelkind
6de44223f2 add performance upgrades to media picker
- Use jpeg instead of png data when processing a UIImage.
- Make processing of media occur after user confirms upload selection when possible for better responsiveness.
- Reduce redundant data fetching.

Lightning-address: kernelkind@getalby.com
Signed-off-by: kernelkind <kernelkind@gmail.com>
Link: 20240228033235.66935-2-kernelkind@gmail.com
Signed-off-by: William Casarin <jb55@jb55.com>
2024-02-29 12:12:22 +00:00
William Casarin
75d87fee9d Merge tag 'v1.7-rc2'
v1.7 Madeira release RC2

55c26d22cb v1.7 (11)
4c8134908c Enable IAP feature for release
3ac7d75235 Add UI error message when IAP succeeds but receipt verification fails
b49a5f4d29 Purple: Improve UX on Damus Purple renewals
3569919eaf Add Damus Purple impending expiry notification support
2024-02-29 03:11:15 -08:00
Daniel D’Aquino
4c8134908c Enable IAP feature for release
Changelog-Changed: Add support for Apple In-App purchases
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-02-28 23:21:48 -08:00
Daniel D’Aquino
3ac7d75235 Add UI error message when IAP succeeds but receipt verification fails
This should not be visible to end-users on normal circumstances, but we
should regardless show an error message if something goes wrong with the
IAP receipt verification, to prompt them to contact support.

Testing
-------

PASS

Device: iPhone simulator
iOS: 17.2
Damus: This commit
damus-api: d3801376fa204433661be6de8b7974f12b0ad25f
Setup:
- Local Testing server
- Xcode StoreKit environment
Steps:
1. Set MOCK_VERIFY to false on the server (that means receipt verification will fail on Xcode environment)
2. Try to make IAP purchase. Error should appear on UI
3. Set MOCK_VERIFY to true on the server and restart StoreKit environment (Receipt validation will always work)
5. Try to make IAP purchase. Onboarding flow should go as normal with no error messages. PASS

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-02-28 23:21:34 -08:00
Daniel D’Aquino
b49a5f4d29 Purple: Improve UX on Damus Purple renewals
This commit changes when the Damus Purple onboarding gets triggered. Now
it only shows the onboarding if it is the first time they are purchasing
Damus Purple for a Nostr account. If it is not the first they see the
onboarding, they are directed to a sheet that displays their new account
info (e.g. a renewal)

However, if the website links to `damus:purple:welcome` links, the app
will still show the onboarding. This will be addressed on the
website-side by taking them to `damus:purple:landing` instead when it is
a renewal.

Testing
---------

PASS

Device: iPhone 13 mini (Physical device)
iOS: 17.3.1
Damus: This commit
damus-api: d3801376fa204433661be6de8b7974f12b0ad25f
damus-website: 6bb425e324c318ca474417cbd2b2f8bb74f9505f
Setup:
- iOS configured to use a local test environment
- Local damus-api server and local damus-website server setup and properly configured
- Fresh db (i.e. Delete mdb files before starting)
Coverage:
1. LN flow with switching to the app instead of clicking "continue". PASS
  a. Ensure that first purchase will result in onboarding being shown. PASS
  b. Ensure that second purchase will result in onboarding NOT being shown (User should be taken to the account info screen). PASS
  c. Restart app completely. Ensure third purchase will result in onboarding NOT being shown (only account info screen). PASS
2. LN flow with clicking continue (Since website changes are not ready, we will simulate them by manually generally appropriate "continue" URL QR codes)
  a. Note: Clear DB again before starting this portion, and completely restart app.
  b. Ensure that first purchase will result in onboarding being shown. PASS
  c. Ensure that second purchase (with continue URL manually hard-coded to `damus:purple:landing`) results in account info being shown with updated info. PASS
  d. Restart app completely and repeat test 2(c). PASS

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-02-28 23:21:14 -08:00
Daniel D’Aquino
3569919eaf Add Damus Purple impending expiry notification support
This commit adds Damus Purple expiry notification support.

How it works: Whenever the app initiates or enters the foreground, it
checks the user's account expiry, and calculates what notifications to
display (It is functional, not imperative, to better match how
the notifications view works)

The notification handlers work the same as every other notification
handler for Nostr events. However, local iOS notifications were not
implemented to maintain these reminders more discreet.

Current limitations:
- Notifications cannot be dismissed
- Notifications are dismissed only when Damus Purple is extended
- After making a purchase, notifications are not dismissed right away
- Bell icon with purple badge shows up on every app restart if user's account is expired

Testing
-------

Device: iPhone 13 Mini
iOS: 17.3.1
Damus: This commit
damus-api: d3801376fa204433661be6de8b7974f12b0ad25f
Setup:
- Local servers Setup
- Debug endpoints enabled for changing expiry date on the fly
Coverage:
1. Expired account
  1. Starting the app on home screen shows bell icon with purple badge. PASS
  2. 4 notifications appear on notifications view (7,3,1,0 days to expiry). PASS
  3. Notifications appear in correct chronological order. PASS
  4. Notifications look consistent in appearance. PASS
  5. Expiry notifications' text size follows text size settings. PASS
  6. Clicking on notification CTA takes user to account info page. PASS
2. Non-expired account (set expiry, restart app)
  1. No expiry notifications, no bell icon. PASS
3. Expiry in 6 days (set expiry, restart app)
  1. Starting the app on home screen shows bell icon with purple badge. PASS
  2. Starting the app on the notification screen renders notifications the same way. PASS
  3. Only one notification (7 days remaining) appears. PASS
4. Expiry in 2 days. PASS
5. General
  1. Clicking bell icon clears away "new notifications" badge. PASS
  2. Performance of notifications view does not seem affected. PASS
  3. Performance of app on startup does not seem affected. PASS
6. IAP
  1. Active IAP + expiry date in 2 days does not trigger reminder notification (Because it is auto-renewed). PASS

Closes: https://github.com/damus-io/damus/issues/1973
Changelog-Added: Notification reminders for Damus Purple impending expiration
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-02-28 23:20:48 -08:00
William Casarin
55000e9d4d Merge improved mute functionality from Charlie
This merge adds a bunch of new features from charlie's work on the new
mutelist changes:

- Muted words

- Mute performance optimizations

- New mute list UI

I needed to make a few changes to fix the tests in this merge. Otherwise
it seems to work ok!

Thank to Charlie for getting all of this working after many rounds of
review!

* branch `mute` of https://github.com/damus-io/damus:
  mute: fix bug with duplicate Indefinite items in MuteDurationMenu
  mute: fix mute hashtag from search view if no existing mutelist
  mute: integrate new MutelistManager
  mute: adding MutelistManager.swift
  mute: add maybe_get_content function to NdbNote
  mute: fix bug where mutes can't be added without existing mutelist
  mute: fix issue with not being able to change mute duration
  mute: don't mutate string when adding hashtag
  mute: implement fast MuteItem decoder
  tags: add u64 decoding function
  mute: migrating muted_threads to new mute list
  mute: adding ability to mute hashtag from SearchView
  mute: updating UI to support new mute list
  mute: adding filtering support for MuteItem events
  mute: receiving New Mute List Type
  mute: migrate Lists.swift to use new MuteItem
  mute: add new UI views for new mute list
  mute: adding new structs/enums for new mute list

Changelog-Added: Add ability to mute words, add new mutelist interface (Charlie)
2024-02-26 12:02:41 -08:00
Charlie Fish
1d4d2b0204 mute: integrate new MutelistManager
This patch is slightly large (I think still within the guidelines tho) ,
but also pretty straightforward. I thought for a while about how I could
split this up in a straightforward way, and I couldn’t come up with
anything without breaking intermediate builds.

- Deleted a lot of old/unnecessary code (ie. the Collection extension
  for MuteItem, since we’re now using the MutelistManager sets)

- Changed damus_state.contacts to damus_state.mutelist_manager for all
  mute list manager work

- Updated MutelistView to take advantage of new code (this is probably
  the largest change in this patch)

Lighting-Address: fishcharlie@strike.me
Signed-off-by: Charlie Fish <contact@charlie.fish>
Reviewed-by: William Casarin <jb55@jb55.com>
Link: 20240210163650.42884-4-contact@charlie.fish
Signed-off-by: William Casarin <jb55@jb55.com>
2024-02-26 11:30:03 -08:00
Charlie Fish
a9baef7a21 mute: adding MutelistManager.swift
This patch adds MutelistManager which contains separate sets for each
type of muted item.

Whenever we receive a new event we parse it into those sets.

When checking to see if an event is muted, we simply check in each of
those sets if it contains the given mute. For words/phrases we have to
loop through each item in the set to see if the event contains the given
word.

In future patches I will be implementing and integrating this new
MutelistManager.

Lighting-Address: fishcharlie@strike.me
Signed-off-by: Charlie Fish <contact@charlie.fish>
Reviewed-by: William Casarin <jb55@jb55.com>
Link: 20240210163650.42884-3-contact@charlie.fish
Signed-off-by: William Casarin <jb55@jb55.com>
2024-02-26 11:29:41 -08:00
Daniel D’Aquino
bdc811aa82 purple: show welcome sheet after ln payment
Automatically show welcome sheet for people who completed a Lightning
checkout but did not click on the "Continue" button

Some people get confused in the last step of the lightning checkout and
they do not click on the "Continue in app", which causes the welcome
screen to not show up and the translation setting to not be setup.

This ticket addresses this issue to ensure they get the welcome screen
in such cases.

This is how it works:

- When they go through the mandatory checkout verification step, the
  verify screen registers that there is a checkout in progress (and
  which checkout is in progress) on the DamusPurple struct

- Every time the app enters the foreground, it checks for that flag:

  - If there are no checkouts in progress, it does nothing

  - If there is one or multiple checkouts in progress, it checks the
    checkout status for those. If any checkout is freshly completed and
    successful, and the account is now active, it shows the welcome
    screen and onboarding

- Once the welcome screen is shown, those checkouts are marked
  internally as handled (so that we only show it once)

===========
Testing
===========

PASS

Damus: This commit
Device: iPhone 13 Mini (real physical device)
iOS: 17.3.1
damus-api: 044150fedddc5ba4135a80579e41e9c1c5743fc0
damus-website: af8089128159e25df31141be624b4090c66d6ddf
Setup:
- Local test Setup
- Clean db before starting

PART 1: LN flow without clicking on the link
---------------------------------------------

Steps:

1. Go through the normal LN flow, EXCEPT at the last step. On the last
   step, instead of clicking "continue in the app", just switch to the
   app.

2. Ensure the welcome screen and onboarding shows up, and the purple
   screen updates to show account info. PASS

3. Switch out and into the app again. Welcome screen should NOT show up
   again. PASS

4. Close the app completely and open the app again. Purple welcome
   screen should NOT show up again. PASS

PART 2: Normal LN flow
---------------------------------------------
Steps:

1. Reset the entire test setup

2. Go through the normal LN flow (this time click on "continue in app").
   The welcome screen should show up without issues or glitches. PASS

PART 3: Crazy flow with multiple incomplete checkouts
---------------------------------------------
(This is to simulate a confused user who accidentally opens multiple new
checkouts, but in the end only completes one of them)

Steps:
1. Reset the entire test setup

2. Open 5 LN checkouts and verify npub with all of them.

3. Only pay one of those checkouts (to make it more confusing, don't pay
   the first or last one, but a random one in between)

4. Go to the app directly (Do not use the link, just go directly to the app)

Related: https://github.com/damus-io/damus/issues/1892
Changelog-Fixed: Fix welcome screen not showing if the user enters the app directly after a successful checkout without going through the link
Closes: https://github.com/damus-io/damus/issues/2021
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Link: 20240223212945.37384-2-daniel@daquino.me
Signed-off-by: William Casarin <jb55@jb55.com>
2024-02-26 09:54:07 -08:00
Daniel D’Aquino
4a4a58c7b5 iap: handle login and logout
This commit adapts the functionality around login/logout with relation
to Damus Purple In-App purchases (IAP). Due to (apparent) limitations on
Renewable subscription In-app purchases (It seems that there can only be
one active IAP subscription per device or Apple ID), these changes add
support for only one IAP subscription at a time.

To prevent confusion, a customer who logs out and logs into a separate
account will see a message indicating the limitation. Any other Nostr
account won't be able to manage IAP on a device that contains an IAP
registered to a different user.

To make this feature possible, the following changes were made to the
code:

1. IAP purchases are now associated with an account UUID. This account
   UUID is generated by the server. Each npub gets one and only one UUID
   for this purpose.

2. This UUID is used to determine which npub owns the IAP on the device.
   It is used as the source of truth when determining whether a
   particular Purple account is manageable on a device or not

3. `DamusPurple` was changed to adhere to a new IAP flow API design
   changes. Previously, the client would create an (inactive) account,
   and then send the IAP receipt to the server for activation. Now, the
   client fetches the npub's UUID from the server, associates it with an
   IAP during purchase, and sends the IAP receipt to the server. The
   server will then bump the expiry (if it's a renewal) or create a new
   active account (if it's the first time).

4. Several changes were made to the StoreKit handling code to improve
   handling:

  a. The `DamusPurple.StoreKitManager` class now records all purchased
     product updates, and sends them to the delegate each time the
     delegate is updated. This helps ensure we do not miss purchased
     product updates regardless of when and if `DamusPurpleView` is ever
     instantiated.

  b. `DamusPurple.StoreKitManager` is now used by `DamusPurple` in a
     singleton pattern via `DamusPurple.StoreKitManager.standard`. This
     helps maintain the local purchase history consistent (and avoid
     losing such data) after `DamusState` or its `DamusPurple` are
     destroyed and re-initialized.

  c. Added logs (using the logger) to help us debug/troubleshoot
     problems in the future

5. Changed the views around DamusPurple, to only show IAP
   purchase/management options if applicable to a particular account. It
   also shows instructive messages in other scenarios.

Testing
-------

damus: This commit
damus-api: d3956ee004a358a39c8570fdbd481d2f5f6f94ab
Device: iPhone 15 simulator
iOS: 17.2
Setup:

- Xcode (local) StoreKit environment
- All StoreKit transactions deleted before starting
- Running `damus` app target (which contains test StoreKit products)

- Local damus-api server running with `npm run dev` and
  `MOCK_VERIFY=true` to disable real receipt verification

- Damus setup with experimental IAP support enabled, and Purple
  environment set to "Test (local)" (localhost)

- Two `nsecs` readily available for account switching
- Clean DB (Delete db files before starting)

Steps:
1. Open the app and sign in to the first account

2. Go to Damus Purple screen. Marketing screen with buttons to purchase
   products should be visible. PASS

3. Buy a product and monitor server logs as well as the screen.
  a. IAP confirmation dialog should appear. PASS

  b. After confirmation, server logs should show a receipt was sent
     IMMEDIATELY and the response should be an HTTP 200. PASS

  c. The welcome and onboarding screens should appear as normal. PASS

  d. Once the onboarding sheet goes away, the Purple screen should now
     show the account information. PASS

  e. The account information should be correct. PASS

  f. Under the account information, there should be a "manage" button. PASS

4. Click on "manage" and verify that the iOS subscription management
   screen appears. PASS

5. Now log out and sign in to the second account

6. Go to Damus Purple screen.
  a. Marketing screen should be visible. PASS

  b. There should be no purchase buttons. instead, there should be a
     message indicating that there can only be one active subscription
     at a time, and that the app is unable to manage subscription for
     this second acocunt. PASS

7. Log out and sign in to the first account. Go to the Purple screen.
  a. Account info with the manage button should be visible like before. PASS

8. Through Xcode, delete transactions, and restart the app. This will
   simulate the case where the user bought the subscription externally.

9. Go to the Purple screen.
  a. Account info should be visible and correct. PASS
  b. Below the account info, there should be a small note telling the
     user to visit the website to manage their billing. PASS

Closes: https://github.com/damus-io/damus/issues/1815
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-02-19 10:38:59 -08:00
Daniel D’Aquino
d694c26b83 iap: move StoreKit logic out of DamusPurpleView and into a new PurpleStoreKitManager
This commit moves most of StoreKit-specific logic that was embedded into
DamusPurpleView and places it into a new PurpleStoreKitManager struct,
to make code more reusable and readable by separating view concerns from
StoreKit-specific concerns.

Most of the code here should be in feature parity with the previous
behavior. However, a few logical improvements were made alongside this
refactoring:

- Improved StoreKit transaction update monitoring logic: Previously the
  view would stop listening for purchase updates after the first update.
  However, I made the program continuously listen for purchase updates,
  as recommended by Apple's documentation
  (https://developer.apple.com/documentation/storekit/transaction/3851206-updates)

- Improved/simplified logic around getting extra information from the
  products: Information and the handling of product information was
  spread in a few separate places. I incorporated those bits of
  information into central and uniform interfaces on DamusPurpleType, to
  simplify logic and future changes.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-02-19 10:38:43 -08:00
Daniel D’Aquino
da82663634 Add option for custom test host in Damus Purple developer settings
This commit adds a developer setting that allows the use of a custom
host for testing. This was added to allow testing on real devices
without the need for pushing changes into staging.

========
Testing
========

Test 1: Production not affected
-------------------------------

PASS

Device: iPhone 13 Mini
iOS: 17.3
Damus: This version
Steps:
1. Run app on a device logged into a real Damus Purple account
2. Scroll down the home feed. Make sure that other Purple members still show up with a star next to their profile. PASS
3. Go to the Damus Purple screen. Ensure that account info shows up and is correct. PASS
4. Ensure auto-translations appear. PASS

Test 2: Check custom test URL
-----------------------------

PASS

(Continued from test 1)
1. Run local damus-api and damus-website on the same computer
2. Change developer purple env setting to local test
3. Set the host url to the local IP address of the test server
4. Go through LN purchase flow. Ensure it works correctly. PASS

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-02-08 10:08:59 -08:00
Daniel D’Aquino
aeafdccb02 Non-functional auto-formatter refactor
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-02-08 10:08:24 -08:00
William Casarin
3d190a7388 purple: fix crash in account cache
otherwise there is contention and tends to crash

Fixes: f06b882139 ("purple: consolidate UserBadgeInfo with Account")
Changelog-Fixed: Fix crash when accessing cached purple accounts
Signed-off-by: William Casarin <jb55@jb55.com>
2024-02-01 11:08:07 -08:00
William Casarin
b25d7d1c0d enable purple
Changelog-Added: Damus Purple membership!
Signed-off-by: William Casarin <jb55@jb55.com>
2024-01-31 09:24:46 -08:00
William Casarin
f06b882139 purple: consolidate UserBadgeInfo with Account
Rename get_account to fetch_account to make it clear that it is always a
call to the server.

Add get_maybe_cached_account method that checks cached before calling
fetch_account.

Signed-off-by: William Casarin <jb55@jb55.com>
2024-01-30 17:30:12 -08:00
Daniel D’Aquino
cdd5327829 purple: disable apple IAP ui by default
We do not have Apple In-app purchases ready for the upcoming release.

This commit hides IAP UI/UX behind a developer feature flag which is off
by default, and shows a link inviting the user to visit the website to
learn more.

It also makes the link go to the normal Damus website.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-01-30 10:29:48 -08:00
Daniel D’Aquino
e649c49981 purple: adapt to integrate better with the LN flow
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-01-30 10:29:48 -08:00
Daniel D’Aquino
a6b430284f purple: feature flag management
Originally the Damus Purple feature was gated behind a setting which
served as a feature flag.

However, when we release, we need to enable Purple features for all
users running on a particular version.

This commit improves feature flag management by replacing the setting
with a computed property (which still points to the setting, but can be
very easily replaced)

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-01-30 10:29:48 -08:00
Daniel D’Aquino
dd240899cf purple: improve environment handling
This commit improves the handling of different Purple environments:

- 3 environments were added (test, staging, production) to fulfill all
  testing needs

- Damus website constants were also added (This will become important
  for the next few commits)

- Toggle settings were replaced with a picker, where the user can select
  one of the 3 environments (test, staging, production)

- Damus purple page and website address links can now be obtained
  directly from the DamusPurple struct, which improves flexibility and
  reduces complexity for code that consumes these constants.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-01-30 10:29:48 -08:00