Commit Graph
1309 Commits
Author SHA1 Message Date
Barry Deen df2f81fb3d fix(profile): keep notes feed paginating and render inline gallery media
The profile Notes tab degraded into gallery-only posts after scrolling past
recent notes, those gallery posts never loaded their media, and there was no
infinite scroll.

- Only surface kind 20/21/22 in the chronological notes feed when they arrive
  via the time-bounded, paginated userposts/userposts-more stream. The separate
  unbounded usergallery sub (limit 100, no time bound) now feeds galleryPosts
  only, so it no longer walls off the feed with far-back gallery events or
  poisons oldestNoteTimestamp (which broke loadMoreNotes' until anchor).
- Render gallery kinds with GalleryCard (imeta via Nip68/Nip71, Coil) instead
  of PostCard, which only extracts URLs from content text.
- Auto-load more on scroll for the Notes and Replies tabs (RECENCY sort only),
  not just the Media tab.
2026-07-01 12:49:43 -04:00
Barry DeenandGitHub 089f652c28 Merge pull request #603 from dmnyc/fix/linebreaks-before-mentions
fix(richcontent): preserve blank lines before inline profile/hashtag segments
2026-06-30 17:31:11 -04:00
Barry DeenandGitHub 5ab7ccf94a Merge pull request #604 from dmnyc/feat/multi-account-switcher
feat(accounts): multi-account switcher + cancel add-account flow
2026-06-30 17:30:50 -04:00
Barry DeenandGitHub 2fa2ff22ea Merge pull request #605 from dmnyc/feat/tx-history-sheet
feat(wallet): transaction history as swipe-up bottom sheet
2026-06-30 17:29:57 -04:00
The Daniel ea6d03b296 feat(wallet): transaction history as swipe-up bottom sheet
The Transactions page now overlays the home dashboard as a
ModalBottomSheet rather than replacing it. Swipe up 40px on the
recent transactions footer or tap "View all" to open; swipe down
or tap the scrim to dismiss.
2026-06-30 09:00:00 -04:00
The Daniel 4ba3b13cd2 feat(accounts): add/switch account affordances + cancel add-account flow
- Drawer header: show + circle (solo) or + N pill+chevron (multi) next to avatar for immediate account switcher access; remove redundant chevron beside username
- SplashScreen: accept onCancel param and render frosted Cancel pill when adding an account over an existing session
- Navigation: save previousAccountPubkey before navigating to splash, restore it fully on cancel (account switch + vm reload)
- AuthViewModel: add previousAccountPubkey field to support cancel-and-restore
2026-06-27 17:44:55 -04:00
The Daniel 7e37a952f2 fix(richcontent): preserve blank lines before inline profile/hashtag segments
The trimBlankLines pass was stripping \n\n before NostrProfileSegment and
HashtagSegment, treating them as block-level elements. They are inline —
they share a Text() composable with surrounding text and get no extra Column
spacing — so blank lines before them must be preserved.

Notes whose paragraphs start with a nostr: mention lost all paragraph
spacing on Android as a result.
2026-06-27 00:00:39 -04:00
Barry DeenandGitHub f4224ecdc3 Merge pull request #602 from Letdown2491/feat/strip-link-trackers
feat: strip tracking params from links in note content
2026-06-26 14:10:22 -04:00
Letdown2491 4d80d24f10 feat: strip tracking params from links in note content
Removes share/analytics tracking params from URLs as they're rendered:
- utm_* and well-known click-ids (fbclid, gclid, msclkid, igshid, and more) on every host
- YouTube share params (si/feature/pp) scoped to YouTube hosts, covering video, playlist, and channel links

Meaningful params (v, list, t, q, fragments) are preserved, and the URL is returned untouched when nothing is stripped.
2026-06-25 22:08:57 -06:00
Barry DeenandGitHub af643e1404 Merge pull request #599 from barrydeen/chore/bump-version-1.1.2
Bump version to 1.1.2
2026-06-18 12:43:21 -04:00
Barry Deen 7cba3deaf6 Bump version to 1.1.2 2026-06-18 12:42:58 -04:00
Barry DeenandGitHub 6016d4053a Merge pull request #573 from dmnyc/feat/zap-bolt-glow-pulse
feat(bolt): white-core glow pulse for in-flight zap
2026-06-18 12:33:11 -04:00
Barry DeenandGitHub 2a6455f35e Merge pull request #594 from dmnyc/fix/compose-uploading-pill
fix(compose): move uploading indicator to its own line as a capsule pill
2026-06-18 12:31:11 -04:00
Barry DeenandGitHub 685227453c Merge pull request #557 from dmnyc/fix/collapse-zap-spam
fix(zaps): collapse same-actor zap spam in notifications and post drawer
2026-06-18 12:21:55 -04:00
Barry Deen 747a10b996 Merge remote-tracking branch 'origin/main' into fix/collapse-zap-spam
# Conflicts:
#	app/src/main/kotlin/com/wisp/app/ui/component/ReactionDetailsSection.kt
2026-06-18 12:20:42 -04:00
Barry DeenandGitHub 32d3fa3434 Merge pull request #572 from dmnyc/feat/drop-relay-backup-status-list
fix(wallet): drop per-relay backup status list from Spark settings
2026-06-18 12:16:37 -04:00
Barry DeenandGitHub 72c0293029 Merge pull request #580 from dmnyc/feat/wallet-receive-redesign
feat(wallet): iOS-parity Receive screen — text input + note + invoice / address tabs
2026-06-18 12:14:00 -04:00
Barry DeenandGitHub 66a05a0a33 Merge pull request #563 from dmnyc/feat/wallet-nwc-backup
feat(wallet): cross-platform NWC connection backup via NIP-78 + parity polish
2026-06-18 12:10:30 -04:00
Barry DeenandGitHub 1755d4c7c4 Merge pull request #598 from barrydeen/feat/music-track-card
feat: render music-track (kind 36787) as inline playable card
2026-06-18 12:09:22 -04:00
Barry Deen 3b7ca349d8 feat: render music-track (kind 36787) as inline playable card
A kind-1 note embedding a nostr:naddr reference to a music track (kind
36787) previously fell through to the generic "Unsupported event kind"
placeholder. This adds a dedicated, playable card.

- RichContent.kt: dispatch kind 36787 to a new MusicTrackCard composable
  that resolves the addressable event via the existing kind-generic
  findAddressableEvent/requestAddressableEvent path and renders artwork,
  a MUSIC chip, title, artist, and an author row.
- The play/pause control is wired into the global AudioPlayerController,
  so playback survives row recycling and drives the floating mini-player
  plus lock-screen/notification controls. Shows a live scrubber while it
  owns the player, the static duration tag otherwise.
- EventPersistence.kt: persist kind 36787 so seen tracks resolve
  instantly on warm restart.

Ports wisp-ios #378 to Android. Single tracks only; the music-tracks
collection kind (30817) stays on the generic placeholder.
2026-06-18 12:08:41 -04:00
Barry DeenandGitHub 188a584c2b Merge pull request #590 from Letdown2491/fix/spam-score-anr
fix: compute spam score off-lock to prevent input-dispatch ANR
2026-06-18 11:57:00 -04:00
Barry DeenandGitHub f699fd0f93 Merge pull request #591 from Letdown2491/feat/staging-build-type
feat: add staging build type for release-performance on-device testing
2026-06-18 11:55:47 -04:00
Barry DeenandGitHub a4160a18d8 Merge pull request #597 from barrydeen/perf/feed-filter-and-spam-lock
perf: incremental feed filter + off-lock spam scoring
2026-06-18 11:55:18 -04:00
Barry Deen 4a5e382590 perf: incremental feed filter + off-lock spam scoring
Ports two performance fixes from the dark-wisp-android fork (PRs #34, #23).

EventRepository: maintain a parallel filteredFeed incrementally instead of
re-filtering the entire feedList (up to 5,000 events) on every 50ms publish
window. Membership is decided once at insert time via a single passesFilter()
predicate; the consumer loops now just snapshot the maintained list. The full
O(n) pass survives only in rebuildFilteredFeed() for the rare filter change.
Inserts that don't change the filtered view no longer trigger an emission,
avoiding redundant Compose recompositions during inbound bursts.

NotificationRepository: hoist regex-heavy spam classification out of
synchronized(lock). addEvent now calls warmSpamScore() before the lock, which
applies the same gating as mergeReply and populates SpamAuthorCache; inside the
lock mergeReply only consults the cache. Prevents the main thread from blocking
past the 5s input-dispatch ANR deadline during notification bursts.
2026-06-18 11:54:23 -04:00
Barry DeenandGitHub 6c817accef Merge pull request #596 from barrydeen/fix/chat-bubble-subcompose-crash
fix: avoid intrinsic measurement of chat bubble content (SubcomposeLayout crash)
2026-06-18 11:42:43 -04:00
Barry Deen e9cdb7f579 fix: avoid intrinsic measurement of chat bubble content (SubcomposeLayout crash)
Opening a group room or DM whose visible messages contained a media
carousel crashed with:

  IllegalStateException: Asking for intrinsic measurements of
  SubcomposeLayout layouts is not supported.

The message-bubble content Column used Modifier.width(IntrinsicSize.Max),
which forces an intrinsic-width measurement of every child. One child is
RichContent, which can render MediaCarousel (a BoxWithConstraints +
HorizontalPager). Those are SubcomposeLayout-based and cannot answer
intrinsic queries, so the measure pass threw.

Remove IntrinsicSize.Max from the group and DM bubble columns and let
the bubble hug its content. The received-message header row no longer
fills width (which would otherwise balloon every bubble to max width
without the intrinsic sizing); the sender name uses widthIn(max) so long
names still ellipsize.
2026-06-18 11:42:12 -04:00
Barry DeenandGitHub f74ec95d44 Merge pull request #595 from barrydeen/fix/event-relays-concurrent-modification
fix: prevent ConcurrentModificationException in eventRelays set
2026-06-18 11:37:04 -04:00
Barry Deen f51a6adeb1 fix: prevent ConcurrentModificationException in eventRelays set
eventRelays stored per-event relay URLs in plain LinkedHashSet instances
(mutableSetOf). addEventRelay mutates these live sets from relay IO threads
as events stream in, while getEventRelays returns the same live set that UI
coroutines and getRelayHintsForEvents iterate on the Main dispatcher. A
concurrent add mid-iteration threw ConcurrentModificationException
(LinkedHashMap$LinkedKeyIterator.next).

Use ConcurrentHashMap.newKeySet(), whose iterator is weakly consistent, so
concurrent adds during iteration no longer throw. This matches the existing
pattern already used for the sibling repostAuthors cache.
2026-06-18 11:34:23 -04:00
The Daniel aaf952233e fix(compose): move uploading indicator to its own line as a capsule pill
The "Uploading…" label was crammed into the compose toolbar's icon row
alongside six icon buttons, leaving no horizontal space — so the text
wrapped to three lines ("Uplo / ading / …"). This pulls the spinner +
label out of the icon row onto a dedicated line below it, wrapped in a
rounded surfaceVariant capsule with maxLines = 1 so it can never wrap.
It animates in/out with the upload lifecycle.

Mirrors barrydeen/dark-wisp-android#38.
2026-06-17 12:23:54 -04:00
Barry DeenandGitHub 9ea046ef78 Merge pull request #592 from Letdown2491/fix/lazy-inline-video-players
fix: lazily create inline video players to survive media-heavy notes
2026-06-12 14:52:00 -04:00
Letdown2491 9fafa1f20c fix: lazily create inline video players to survive media-heavy notes
InlineVideoPlayer and InlineVideoPlayerWithFullscreen created a fully
prepared ExoPlayer per video URL the moment it entered composition,
with no cap or visibility gating. A note carrying many video URLs
(such as a 155-video note seen in the wild) instantly spawned a
player per URL -- 156 players / 487 player threads measured upstream,
exhausting hardware codec instances (~16-32 device-wide) and memory
until input dispatch timed out (ANR).

The player now only exists while its video is near the viewport:
created when >50% visible with autoplay on, or on tap (which starts
playback immediately); released with its position remembered once the
video scrolls fully off-screen. Until then the slot renders the
uploader-provided NIP-92 imeta "image" preview frame, falling back
to the existing thumbhash/blurhash painter. parseImetaTags now parses
the "image" entry, and MediaCarousel video tiles use it too instead
of a blank box when no thumbhash/blurhash is present.

Port of barrydeen/dark-wisp-android#31.
2026-06-12 12:14:40 -06:00
Letdown2491 15261fba91 fix: compute spam score off-lock to prevent input-dispatch ANR
NotificationRepository.addEvent ran NSpamClassifier.score() inside
synchronized(lock) via mergeKind1 -> mergeReply. Feature extraction
runs several regex passes over up to ten notes per author; doing it
under the lock stalls every caller -- including main-thread callers
like getAllPostCardEventIds() and markRead -- past the 5s
input-dispatch deadline.

Fix: warmSpamScore(event) runs before the lock, computes the score
under the same spamFilterEnabled/isFollowing/safelist/reply-target
guards, and caches it in SpamAuthorCache (LruCache-backed, thread-
safe). mergeReply now only consults the cache.

Port of barrydeen/dark-wisp-android#23.
2026-06-12 10:47:02 -06:00
Letdown2491 7d08cc879f feat: add staging build type for release-performance on-device testing
Non-debuggable + R8-minified like release (initWith(release)), but
debug-signed so anyone can install it without the release keystore.
applicationIdSuffix ".staging" and app name "Wisp Staging" let it
install alongside debug and release builds.

debuggable=true makes ART ignore the baseline profile and run
JIT-only with deoptimization support, which cripples the per-event
hot path (JSON parse, hex decode, SHA-256, JNI Schnorr verify per
relay event) -- so debug builds can't be used to judge real
performance.

Port of barrydeen/dark-wisp-android#24.
2026-06-12 10:47:02 -06:00
Barry DeenandGitHub 04253bbcb1 Merge pull request #586 from dmnyc/fix/wallet-self-send-tx-order
fix(wallet): keep self-send legs distinct + deterministic tx order
2026-06-12 12:29:55 -04:00
The Daniel ff47689aaf fix(wallet): keep self-send legs distinct + deterministic tx order
A self-payment surfaces as two transaction records sharing one payment
hash — one outgoing, one incoming. Port the iOS WalletStore.dedupTransactions
hardening so they render correctly:

- Add dedupTransactions(): drop exact (paymentHash, type) repeats the
  backend returns, and sort newest-first with the incoming "received" leg
  above its outgoing "sent" leg on a timestamp tie. Apply on initial load,
  re-enrich, and load-more.
- Key the transaction LazyColumn by "paymentHash|type" so the two legs of a
  self-send keep distinct identities and both render (mirrors iOS
  WalletTransaction.id).
2026-06-06 15:36:00 -04:00
Barry DeenandGitHub 5175babef0 Merge pull request #585 from barrydeen/fix/lint-extra-translations
fix: remove stale wallet translation keys blocking release build
2026-06-05 17:33:19 -04:00
Barry Deen 23b11104c3 fix: remove stale wallet translation keys blocking release build
Six wallet string keys (wallet_connect_wallet, wallet_choose_how,
wallet_create_new, wallet_create_description, wallet_nwc_description,
wallet_spark) were renamed in the default locale but left behind in
10 translation files, causing 60 ExtraTranslation lint errors that
failed lintVitalRelease.
2026-06-05 17:30:12 -04:00
Barry DeenandGitHub fa82ac2a1a Merge pull request #583 from barrydeen/feat/wot-filter-threads
feat: apply web of trust filter to thread replies
2026-06-05 17:17:09 -04:00
Barry Deen 2e19a69249 chore: bump version to 1.1.1 2026-06-05 17:14:58 -04:00
Barry Deen 198313e6b3 feat: apply web of trust filter to thread replies
The WoT filter was already silently dropping events at EventRepository
ingestion and in NotificationRepository, but thread replies bypassed
both paths — ThreadViewModel keeps its own threadEvents map seeded via
cacheEvent() and getCachedThreadEvents(), neither of which checks WoT.

Apply isWotFiltered in rebuildTree() alongside the existing block /
mute / spam filters. The root note is always shown (user explicitly
navigated to it); replies from authors outside the qualified network
are silently dropped, matching how WoT behaves in the feed and
notifications.
2026-06-05 17:09:30 -04:00
The Daniel e1751f462c feat(wallet): iOS-parity Receive screen — text input + note + invoice / address tabs
Replaces the orange numpad-style Receive screen with a form layout matching
iOS ReceiveInvoiceSheet:

- AMOUNT label + single text field (numeric keyboard, "0" placeholder,
  "sats" or fiat suffix). In fiat mode shows the live sats conversion
  below the field.
- NOTE (OPTIONAL) label + single-line text field with "For coffee, etc."
  placeholder — plumbed through generateInvoice(amountSats, description)
  so the note is embedded in the BOLT11 invoice description.
- Create invoice full-width button; disabled (gray) until the amount
  parses to > 0 sats.
- Invoice / Lightning Address segmented control at the top, shown only
  when the user has a Spark lightning address. Switching to the address
  tab renders the address QR + Copy / Share row inline (no nav churn).

Touches:

- WalletScreen.kt: rewrites ReceiveAmountContent; adds ReceiveAddressBlock.
  Uses BasicTextField for the hero amount field (custom placeholder
  inside a rounded surface) and a second BasicTextField for the note.
- WalletViewModel.kt: generateInvoice now takes (amountSats, description);
  adds setReceiveAmount(value) for the new text-input path. The existing
  digit-by-digit updateReceiveAmount / receiveAmountBackspace stay
  untouched (still used by Send numpad).
- strings.xml: new English strings for the labels / placeholders / CTA.
  Translations to follow.

Tested on device.
2026-05-26 12:26:05 -04:00
The Daniel 8fa48936d8 feat(bolt): white-core glow pulse for in-flight zap
Ports iOS commit #6 from feat/one-tap-zap. Replaces the multi-layer
Canvas bolt animation that was smearing the silhouette at scale
peaks. New approach: always-white silhouette + three stacked
zap-color shadows underneath, driven by a single sin-eased
oscillator.

Math (period 0.9s):
  sine    ∈ [-1, 1]
  phase   ∈ [0, 1]      = (sine + 1) / 2
  iconScale       = 1.0 + 0.10 * sine   (0.90 → 1.10)
  verticalOffset  = -0.5 * sine          (±0.5dp centered on baseline)

Shadow layers (Canvas strokes — drawn outer → inner so the white
core sits on top):
  outer  — radius 8 + 6*phase dp,  α = 0.30 + 0.50*phase
  medium — radius 4 + 3*phase dp,  α = 0.55 + 0.45*phase
  inner  — radius 1.5dp constant,  α = 0.95
  core   — solid white silhouette, untinted

Vertical motion held to ±0.5dp so the icon doesn't lift off the
action-bar baseline and misalign with neighbouring glyphs. The
white IS the luminous core; the warm halos do the heat work.

LinearEasing on the sineAngle (not FastOutSlowInEasing) — the
sine function itself supplies the easing curve. Wrapping with
another easing would double-stack and visibly stutter.
2026-05-26 10:37:35 -04:00
The Daniel 89deea2617 fix(wallet): drop per-relay backup status list from Spark settings
iOS doesn't surface a per-relay backup status display in the wallet
settings screen, and the section is overkill for the Android side too —
the majority of users start with the default Spark wallet (which never
shows the relay-backup affordance at all, because the nsec is the
canonical backup), and the remaining minority on non-default Spark
wallets get the "Backup to Nostr Relays" button alongside the Recovery
Phrase row without needing a per-relay status card to interpret.

Removes the entire status block: the "Relay Backup Status" header +
refresh icon, the relay-URL + green/grey dot card, the "Delete Relay
Backup" TextButton + status messages, and the confirmation dialog.
The "Backup to Nostr Relays" button stays — non-default wallets can
still push the encrypted backup to relays, we just don't surface the
fine-grained per-relay state inline.

WalletViewModel plumbing (`relayBackupStatuses`, `relayBackupCheckLoading`,
`checkRelayBackupStatuses()`, `deleteRelayBackup()`) is left in place
deliberately. The status check + delete actions are still wired through
the WalletScreen parameter list so other entry points (or a future
debug-only view) can use them, and ripping the ViewModel state out
would balloon this diff into a refactor.
2026-05-26 10:36:20 -04:00
The Daniel 12a5b75872 Merge main into feat/wallet-nwc-backup to resolve PR conflicts
# Conflicts:
#	app/src/main/kotlin/com/wisp/app/ui/screen/WalletScreen.kt
2026-05-26 07:57:22 -04:00
The Daniel 29d8c9fcb1 feat(wallet): hide relay backup for default Spark wallet
The default Spark wallet derives from the user's nsec, so the nsec
already serves as the cross-device backup — the "Backup to Nostr
Relays" affordance is redundant and noisy alongside the equivalent
nsec-based recovery path. Recovery Phrase view is unchanged.

Hides the relay backup button + status section for default wallets;
non-default wallets keep both, since they have no nsec-derived
fallback.
2026-05-26 07:47:50 -04:00
Barry DeenandGitHub a7020603d2 Merge pull request #566 from dmnyc/fix/spark-default-wallet-banner
fix(wallet): refresh Spark display state on mnemonic swap + banner copy
2026-05-25 12:59:28 -04:00
Barry DeenandGitHub 14f237b6a1 Merge pull request #559 from dmnyc/feat/zap-with-image
feat(zap): render zap messages as mini-posts in the engagement drawer
2026-05-25 12:58:49 -04:00
Barry DeenandGitHub b0a9eb379d Merge pull request #561 from dmnyc/fix/nwc-connect-screen-ios-parity
fix(wallet): NWC connect screen — iOS 1:1 redesign
2026-05-25 12:58:01 -04:00
Barry DeenandGitHub bdef0b2748 Merge pull request #565 from dmnyc/feat/android-dark-mode-backgrounds
feat(theme): iOS-parity dark-mode polish for Android
2026-05-25 12:57:43 -04:00
Barry DeenandGitHub 6416a8353e Merge pull request #562 from dmnyc/feat/wallet-balance-toggle
feat(wallet): cycle balance display between sats, fiat, and hidden
2026-05-25 12:57:21 -04:00