Commit Graph
1177 Commits
Author SHA1 Message Date
Barry DeenandGitHub 1ca9d9b58f Merge pull request #33 from Letdown2491/perf/strip-debug-logs
perf: strip Log.v/Log.d calls in minified builds
2026-06-15 23:15:46 -04:00
Barry DeenandGitHub f6276201a8 Merge pull request #34 from Letdown2491/perf/incremental-feed-filter
perf: maintain feed filter incrementally instead of re-filtering per update
2026-06-15 23:15:23 -04:00
Letdown2491 87d21e698c perf: maintain feed filter incrementally instead of re-filtering per update
The feed publish path re-filtered the entire master feedList (capped at
5,000 events) on every 50ms settle window — a full toList() copy plus a
.filter{} pass where each event paid a Set lookup and an isRepostedByAny
LRU lookup, then a fresh list allocation and StateFlow emit. During an
inbound burst this ran up to ~20x/sec.

Maintain a parallel filteredFeed list incrementally instead: membership is
decided once at insert time via a single passesFilter() predicate, and all
feedList mutation sites (binaryInsert, repost re-sort, removeEvent,
purgeUser, purgeThread, resetFeedDisplay) update both lists in lockstep
under the same monitor. The settle/immediate handlers now just snapshot the
maintained list; the full O(n) filter pass survives only in
rebuildFilteredFeed() for the rare author/kind filter change. Inserts that
don't change the filtered view no longer trigger a feed emission, avoiding
redundant Compose recompositions.

Measured on a Pixel 9a (staging/R8, one-build A/B timing both paths over the
identical feedList snapshot per publish): at ~350 notes the publish path
dropped from median 167us / p90 243us to median 5.7us / p90 18us — ~23-29x
faster, and the gap scales linearly with feed size since the per-update
re-filter is eliminated.
2026-06-13 12:36:55 -06:00
Letdown2491 ca3930ef15 perf: strip Log.v/Log.d calls in minified builds
Debug logging currently survives R8: RelayPool (51 Log.d calls) and
EventRepository (14) log in per-event paths, so release builds pay
string formatting and logd writes for every relay message, EOSE, and
poll/gallery event. -assumenosideeffects lets R8 remove the calls
along with their argument string building (verified: log strings
present in the dex before, gone after; Log.w/Log.e strings retained).
Debug builds are unminified and keep full logging.
2026-06-12 16:04:26 -06:00
Barry DeenandGitHub e48348eb28 Merge pull request #31 from Letdown2491/fix/inline-video-player-exhaustion
fix: lazily create inline video players to survive media-heavy notes
2026-06-12 14:52:27 -04:00
Letdown2491 5a59341560 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
(e.g. the circulating 155-video stress-test note) instantly spawned a
player per URL: 156 players / 487 player threads / 1800 process
threads measured on device, exhausting hardware codec instances
(~16-32 device-wide) and memory until input dispatch timed out (ANR)
in STACK media layout.

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.

Verified on device against the 155-video note: peak 7 ExoPlayer
threads (was 549), no ANR, playback/mute/fullscreen/PiP unaffected.
2026-06-12 11:32:22 -06:00
Barry DeenandGitHub 86eb6424b8 Merge pull request #10 from dmnyc/feat/wallet-nav-icon
feat(ui): credit-card style wallet icon in bottom nav and drawer
2026-06-12 12:13:58 -04:00
Barry DeenandGitHub 91afed827e Merge pull request #9 from dmnyc/feat/media-gallery
feat(media): horizontally scrolling image galleries
2026-06-12 12:13:29 -04:00
Barry DeenandGitHub c7ac6730ba Merge pull request #24 from Letdown2491/feat/staging-build-type
feat: add staging build type for release-performance on-device testing
2026-06-12 12:12:28 -04:00
The Daniel 8863eeb293 feat(ui): credit-card style wallet icon in bottom nav and drawer (port wisp #547) 2026-06-12 11:29:56 -04:00
The Daniel 9ae6fdd896 feat(media): horizontally scrolling image galleries (port wisp #527)
- MediaCarousel: horizontal swipe gallery for multi-image posts with
  page indicators
- FullScreenMediaPager: swipe between images in full screen
- ZoomableAsyncImage: pinch-zoom, pan, double-tap and swipe-down dismiss
- Media layout setting (gallery/stacked) in Interface preferences
2026-06-12 11:29:55 -04:00
Letdown2491 ccb9484870 feat: add staging build type for release-performance on-device testing
Debuggable builds make ART run JIT-only and ignore the baseline
profile, which cripples the per-event hot path (JSON parse, hex
decode, SHA-256, JNI Schnorr verify per relay event). The staging
variant is non-debuggable with R8 like release, but debug-signed so
anyone can install it without the release keystore. It installs
alongside debug/release via the .staging applicationId suffix.
2026-06-12 08:57:34 -06:00
Barry DeenandGitHub 0bcceffae2 Merge pull request #8 from dmnyc/feat/zap-sheet-fiat-image
feat(zap): fiat currency input + inline images in zap messages
2026-06-12 10:32:57 -04:00
Barry DeenandGitHub 2e0a5b9e8e Merge pull request #23 from Letdown2491/fix/notification-spam-score-anr
fix: move spam scoring out of NotificationRepository lock
2026-06-12 10:29:06 -04:00
Letdown2491 d68781b2f8 fix: move spam scoring out of NotificationRepository lock 2026-06-11 22:02:20 -06:00
The Daniel bf64edc8bd feat(zap): fiat currency input + inline images in zap messages (port wisp #519, #559)
- Register-style fiat amount entry in the zap dialog with live sats
  conversion via ExchangeRateRepository
- Zap messages render image URLs inline in the engagement drawer,
  collapsed to [image] on the top banner (new ZapMessageImage util)
- Redesigned zap rows in reaction details as mini-posts via RichContent
- Adds String.toNpub() helper used by the new zap row fallbacks
2026-06-11 19:36:55 -04:00
Barry DeenandGitHub 21ce7ee2a8 Merge pull request #5 from roguehashrate/feature/payment-targets-zec-dash-bch-ltc
feat: add ZEC, DASH, BCH, LTC payment targets
2026-06-11 06:32:53 -04:00
roguehashrate 1369f61595 feat: add ZEC, DASH, BCH, LTC payment targets 2026-06-10 21:59:24 -05:00
Barry DeenandGitHub f3f8c0b199 Merge pull request #4 from barrydeen/feat/nip-a3-payment-targets
feat: add NIP-A3 payment targets (kind 10133)
2026-06-10 13:43:45 -04:00
Barry Deen edbb5ab549 feat: add NIP-A3 payment targets (kind 10133)
Users can publish payment addresses for other cryptocurrencies and
payment apps (payto tags per draft NIP-A3) and pay other users
through them.

- NipA3.kt: parse/build payto tags, type validation, payto:// and
  native wallet URIs, recognized-type stylization
- PaymentTargetRepository: LRU + SharedPrefs cache per pubkey;
  stores empty lists so cleared targets propagate
- ZapDialog: "Other ways to pay" chip section; targets-only dialog
  when lightning zapping isn't possible but targets exist
- PaymentTargetSheet: QR, copy, open-in-wallet bottom sheet
- Wallet settings: manage and publish own targets with network
  read-back before editing to avoid clobbering the replaceable event
- Profile screen: targets shown under the lightning address
- On-demand fetch only (zap dialog / profile open), ingested via
  EventRouter with created_at freshness guard
2026-06-10 13:41:36 -04:00
Barry DeenandGitHub dc35c0f49e Merge pull request #3 from barrydeen/feat/tor-support
feat(tor): embedded Tor with fail-closed routing for all connections
2026-06-10 13:04:23 -04:00
Barry Deen 28cb2dad42 feat(tor): embedded Tor with fail-closed routing for all connections
Add an embedded Tor daemon (kmp-tor) with an onion toggle in the sidebar
drawer and on the pre-login Splash/Auth screens. When enabled, every
connection — relay WebSockets, Coil images, ExoPlayer media, NIP-05/NIP-11,
LNURL/zaps, Blossom uploads, link previews, exchange rates, DM media, live
metrics — routes through Tor's SOCKS proxy.

Routing is fail-closed: from the moment the toggle flips, new clients point
at a dead loopback port until bootstrap completes, and existing clients have
their in-flight requests cancelled and keep-alive pools evicted so no
pre-toggle socket survives. OkHttp passes unresolved hostnames to SOCKS
proxies, so DNS also resolves inside Tor.

- TorManager: kmp-tor runtime in a foreground service; state machine
  (Off/Starting %/On/Stopping/Error) observed by all toggles; start retries
  around kmp-tor's fixed 1s service-bind timeout, which a busy main thread
  misses during cold start
- TorPreferences: device-level pref so the toggle works pre-login and
  survives logout
- HttpClientFactory: single proxy choke point; client registry +
  setTorSocks() invalidation; new getRelayClient/getLoopbackClient/
  getDmMediaClient
- Relay/RelayPool: clients resolved through providers on every (re)connect;
  suspendForTorSwitch/resumeAfterTorSwitch tear down sockets and suppress
  reconnects during bootstrap so the 5-min relay cooldown can't trip; local
  relay stays direct (Tor refuses loopback/LAN targets)
- Captured-client fixes so a mid-session toggle can't leak: Coil delegating
  Call.Factory (it caches its lambda forever), ZapSender, DmBubble,
  NwcRepository, SplashViewModel raw client, ExchangeRateRepository refetch
- Cold start with Tor on: relay init gated on bootstrap
- kmp-tor pinned to 2.4.x: 2.5+ needs Kotlin 2.2 metadata, unreadable by
  this project's Kotlin 2.0.21

Verified on-device by socket-inode audit: app process held 43 sockets, all
to 127.0.0.1:<socks>; only the tor child process held external connections.

Known gaps: Breez/Spark SDK (own Rust networking) and ML Kit model
downloads bypass OkHttp and are not routed.
2026-06-10 13:00:23 -04:00
Barry DeenandGitHub 7e733098e4 Merge pull request #2 from barrydeen/feat/private-replies-reactions-zaps
feat: private replies, private reactions, and private zaps
2026-06-10 12:12:35 -04:00
Barry Deen 8177cfc115 feat(private-replies): gift-wrapped reactions + DIP-03 zaps (port wisp#543)
Generalizes the private-event marker (markPrivateReply/isPrivateReply →
markPrivate/isPrivate) and re-enables React and Zap on private replies.
Reactions on a private reply are kind 7 rumors gift-wrapped to every
thread participant (the k tag distinguishes k=1 reply reactions from
k=14 DM reactions); PrivateReactionPublisher mirrors the reply publisher
with per-recipient relay resolution and a self-copy. Zapping a private
reply locks the ZapDialog to DIP-03 private mode (forcePrivate) so a
public zap can never attach an e-tag to the rumor id, and sendZap forces
the flag server-side as a defensive guard. Repost/Quote stay hidden.

Dark Wisp addition over the upstream PR: PrivateRumorHandler gains
handlePrivateReaction and the remote-signer pending-decrypt paths route
k=1 reaction rumors through it, matching the EventRouter behavior.
2026-06-10 12:04:58 -04:00
Barry Deen 4a87103944 feat(zaps): private zaps via DIP-03 + DM-relay routing (port wisp#541)
Replaces the relays-tag heuristic with DIP-03: the real sender signs an
inner kind 9733 event, NIP-04-encrypted into the outer kind 9734's anon
tag using an ephemeral key derived as sha256(privkey + eventId +
createdAt). Relays and the LNURL provider only see the unlinkable
ephemeral pubkey; receipts route through both parties' DM relays.
EventRepository.resolveZapSender() decrypts incoming private zaps (as
recipient) and re-derives the ephemeral key to self-attribute our own
outgoing ones, so notifications, zap lists, and wallet history show the
real counterparty. Private zaps require a local keypair, so the UI gate
adds hasLocalKeypair; live-stream (addressable) zaps can't derive the
ephemeral key and stay public.
2026-06-10 12:01:23 -04:00
Barry Deen 0308bad87c feat(compose): private replies via NIP-17 gift wrap (port wisp#540)
Kind 1 rumors inside kind 1059 gift wraps, delivered to the recipient's
kind 10050 DM relays (NIP-65 read relays as fallback). PoW is mined on
the rumor so the badge survives unwrapping; a self-copy wrap keeps other
devices in sync; optimistic local insert updates reply counts at once.
Compose gains a private-reply toggle that auto-enables and locks when
replying to a private reply; thread and notifications show a lock icon
and hide repost/quote/react/zap on private replies.

Dark Wisp addition over the upstream PR: private reply rumor handling is
extracted into PrivateRumorHandler and wired into the remote-signer
pending-decrypt paths (DmListViewModel/DmConversationViewModel), which
upstream dropped along with NIP-55 support — without this, gift-wrapped
replies would be misfiled as DM messages for remote-signer accounts.
2026-06-10 12:00:20 -04:00
Barry DeenandGitHub aa607cc705 Merge pull request #1 from barrydeen/fix/grapheneos-perf-apk-build
fix: revert 16KB packaging changes behind GrapheneOS perf regression
2026-06-10 11:51:32 -04:00
Barry Deen e0a889d2be fix: revert 16KB packaging changes behind GrapheneOS perf regression
Fully revert f68b40f, which shipped in 1.0.2 alongside reports of severe
slowdown and freezes on GrapheneOS:

- jniLibs.useLegacyPackaging back to true: compressed native libs cut the
  download from 82MB to 37MB, and uncompressed 16KB-aligned packaging is
  only needed for Google Play, which this build no longer targets
- secp256k1-kmp pinned back to known-good 0.16.0 (0.19.0 = libsecp256k1
  0.7.0 rebuilt with NDK 28; runs once per relay event on the verify path)

Also fix adjacent issues found while auditing every shipped release APK
(all were clean: release-signed, R8-minified, correctly aligned — ruling
out a malformed artifact as the cause):

- baseline-prof.txt still targeted com/wisp/app after the rebrand; the
  next release would have shipped zero app AOT rules. Fixed paths now
  yield 12,888 app rules; verified on-device (cold start 2048ms -> 830ms
  once compiled, ProfileVerifier reports compiledWithProfile=true)
- release signing moved into Gradle (RELEASE_STORE_* via local.properties
  or env) so the published APK is exactly what assembleRelease emits
- tools/audit-apk.sh: pre-release gate checking debuggable flag, lib
  packaging consistency, alignment, R8, baseline profile, signing cert
- ProfileVerifier status logged at startup for field diagnostics
- <profileable android:shell="true"/> so testers can capture simpleperf/
  Perfetto traces from release builds
- docs/grapheneos-perf-investigation.md: full findings plus the A/B
  attribution matrix (incl. GrapheneOS MTE toggle test) for confirming
  which half of f68b40f was guilty
2026-06-10 11:48:57 -04:00
Barry Deen 80f9e42cc6 chore: rebrand to Dark Wisp (com.darkwisp.app)
- Rename package com.wisp.app -> com.darkwisp.app
- Set applicationId/namespace to com.darkwisp.app
- Reset version to 1.0.0 (versionCode 1) as a new app
- Update display name to Dark Wisp / Dark Wisp Debug
- Update NIP-89 client tag to "Dark Wisp"
- Rebrand user-facing strings across all locales and README
- rootProject.name -> dark-wisp-android
2026-06-10 10:36:58 -04:00
Barry DeenandGitHub 9a6b100e3a Merge pull request #517 from barrydeen/chore/bump-version-1.0.5
chore: bump version to 1.0.5 (79)
2026-05-04 09:28:08 -04:00
Barry Deen d5a6f729d2 chore: bump version to 1.0.5 (79) 2026-05-04 09:26:26 -04:00
Barry DeenandGitHub 00d70f20f0 Merge pull request #516 from barrydeen/fix/bottom-tab-state-restore
fix: stop restoring stale tab back stacks on bottom-nav switch
2026-05-04 09:25:29 -04:00
Barry Deen 5b745ddb90 fix: stop restoring stale tab back stacks on bottom-nav switch
PR #514 changed the bottom-nav handler to use popUpTo with the graph's
start destination plus saveState/restoreState. Two regressions resulted:

- Tapping a tab restored the tab's saved back stack, returning the user
  to a previously-open thread instead of the tab's main screen.
- The graph's start destination (LOADING) is popped inclusive on first
  successful load, so popUpTo never matched. The resulting back-stack
  state could surface the splash/auth screen on system back from
  Notifications.

Revert that block to popUpTo(FEED) { inclusive = false } + launchSingleTop,
which always lands the user on the tab's main screen with a shallow stack.
The refreshDmsAndNotifications() throttle introduced alongside the broken
nav block is preserved — that part addresses real REQ churn jank and is
independent of how the back stack is structured.
2026-05-04 09:23:09 -04:00
Barry DeenandGitHub 04501e41de Merge pull request #515 from barrydeen/fix/tab-switch-jank
perf: reduce startup and feed rendering work
2026-05-04 08:33:37 -04:00
Barry Deen a55ebdfee6 chore: bump version to 1.0.4 (78) 2026-05-04 08:33:22 -04:00
Barry Deen c4a8aacee3 perf: restore tight NIP-05 timeouts, isolate download client, harden notif rebuild
- give getNip05Client a dedicated 5/10s client (was aliasing the 10/15s
  general client); slow .well-known/nostr.json endpoints no longer tie
  up verification threads
- add getDownloadClient (30/60s) for MediaDownloader; the shared media
  client (10/30s) is fine for ExoPlayer streaming but its read timeout
  is too tight for full-file downloads on flaky networks
- tighten notification rebuild coalesce window from 50ms to one frame
  (16ms) so single-arrival updates feel responsive
- add NotificationRepository.shutdown() and call it from
  FeedViewModel.onCleared() so rebuildScope doesn't outlive the VM
2026-05-04 08:18:03 -04:00
Barry Deen e3373889e0 perf: reduce startup and feed rendering work 2026-05-04 08:08:44 -04:00
Barry DeenandGitHub de7af45149 Merge pull request #514 from barrydeen/fix/tab-switch-jank
fix: reduce bottom-tab switching jank
2026-05-04 07:46:53 -04:00
Barry Deen a0ec123e0c fix: preserve bottom-tab state during tab switching
Restore top-level tab destinations instead of recreating them on each tap, and avoid immediately resubscribing inbox streams when users bounce between tabs. This reduces navigation jank and repeated hangs on slower devices.
2026-05-04 07:44:39 -04:00
Barry DeenandGitHub b028007a7c Merge pull request #513 from barrydeen/chore/bump-version-0.3
bump
2026-05-03 21:59:32 -04:00
Barry Deen 55e3adfc74 bump 2026-05-03 21:58:58 -04:00
Barry DeenandGitHub 550b6d1997 Merge pull request #512 from barrydeen/feat/persist-decrypted-dms
Persist decrypted DMs to skip per-boot signer round-trips
2026-05-03 21:57:12 -04:00
Barry Deen 4894ccee40 feat: persist decrypted DMs to skip per-boot signer round-trips
NIP-17 gift wraps are re-fetched on every cold start (the kind-1059
subscription has no since cursor because gift-wrap timestamps are
randomized up to 2 days in the past). Without a decrypted-DM cache,
remote-signer mode (Amber) re-runs two NIP-44 IPC decrypts per wrap on
every launch.

Adds an ObjectBox-backed cache keyed by ownerPubkey|giftWrapId. On
construction, DmRepository hydrates conversations, seenGiftWraps,
rumorIdIndex and the DM notification list from disk off the main thread.
addPendingGiftWrap now short-circuits on seenGiftWraps before queuing,
so relay-redelivered wraps never reach the signer; only newly arrived
wraps are decrypted. addMessage / addReaction / addZap re-persist the
parent message so reactions and zaps survive restarts. clear() and
purgeUser() propagate to the persistence layer.
2026-05-03 21:54:41 -04:00
Barry DeenandGitHub 81aeaa05f8 Merge pull request #511 from barrydeen/refactor/remove-tor
refactor: remove Tor functionality
2026-05-03 21:16:01 -04:00
Barry Deen 0223e1b9e7 refactor: remove Tor functionality
Drops the embedded kmp-tor client, all .onion relay support, the Tor
toggle UI on splash/auth/drawer screens, the SOCKS proxy plumbing in
HttpClientFactory, all Tor-related strings across 11 locales, the
TorManager singleton, and the kmp-tor dependencies and ProGuard rules.

Relay URL validation now accepts only wss:// (with hostname, no port,
non-localhost, non-IP). The cleartextTrafficPermitted network security
flag stays — still needed for local relays on RFC1918 / loopback.
2026-05-03 21:14:51 -04:00
Barry DeenandGitHub 94b9d7b78c Merge pull request #510 from barrydeen/fix/relay-connect-anr
fix: dispatch Relay.connect() off the calling thread to avoid ANR
2026-05-03 20:51:25 -04:00
Barry Deen 955787eaad fix: dispatch Relay.connect() off the calling thread
OkHttpClient.newWebSocket() can block on its shared TaskRunner lock
for several seconds under contention. UI callbacks that invoke
RelayPool.sendToRelayOrEphemeral were calling Relay.connect() inline
on the main thread, producing 5s+ ANRs. Dispatch connect() through a
small dedicated thread pool so callers never wait on WebSocket setup.
2026-05-03 20:47:03 -04:00
Barry DeenandGitHub 683e3072b2 Merge pull request #509 from barrydeen/feat/inline-video-loop
feat: loop inline mp4 videos in note cards
2026-05-03 20:36:19 -04:00
Barry Deen 08d1340e9e feat: loop inline mp4 videos in note cards
Inline video players in RichContent now use REPEAT_MODE_ONE so MP4s
auto-repeat when rendered inside a note card. Applied to both freshly
created and PiP-reclaimed players. Fullscreen and audio paths are
unchanged.
2026-05-03 20:35:48 -04:00
Barry DeenandGitHub b6105eb134 Merge pull request #494 from dmnyc/feat/fiat-mode-coin-stack-icon
feat: coin stack icon for zaps in fiat mode
2026-05-03 20:01:28 -04:00