Commit Graph

13991 Commits

Author SHA1 Message Date
Vitor Pamplona 54a6109d83 Merge pull request #2969 from vitorpamplona/claude/hashtag-limit-warning-FQwtJ
Add excessive hashtag detection to hidden note UI
2026-05-18 17:44:54 -04:00
Vitor Pamplona 2fff2c137c Merge pull request #2968 from vitorpamplona/claude/fix-image-loading-layout-DlLYr
Support floating-point dimensions in NIP-92 imeta tags
2026-05-18 17:43:29 -04:00
Claude 6be26e1412 fix(media): route media-upload signing through launchSigner
Replaces the previous narrow try/catch fix. The earlier patch silently
swallowed SignerExceptions in NewMediaModel / EditPostViewModel — no
toast, no error dialog — so the user would tap Post, see the dialog
close, and never learn that the signer prompt timed out or was
rejected. It also bypassed the project's standard signer-error
pipeline.

This version routes the sign+publish phase through
AccountViewModel.launchSigner, which is the same path every other
signing entry point uses:
- ReadOnly / SignerNotFound / UnauthorizedDecryption / IllegalState →
  toastManager surfaces a localized alert.
- TimedOut / ManuallyUnauthorized / etc. → logged silently (no crash),
  matching the rest of the app's behavior when a user dismisses the
  external signer.

NewMediaModel.upload now takes an AccountViewModel parameter; the three
inner viewModelScope.launch(Dispatchers.IO) blocks become
accountViewModel.launchSigner { ... }. The joinAll wait is preserved
(launchSigner returns Job).

EditPostViewModel.uploadUnsafe routes its single launch the same way
and wraps the body in try/finally so mediaUploadTracker.finishUpload()
still runs if a signer throws mid-flow.

launchSigner is changed from Unit to Job (= viewModelScope.launch ...)
so callers can join — non-breaking for the ~190 existing callsites that
ignore the return value.
2026-05-18 21:35:38 +00:00
Claude 48d9e80e20 refactor: address self-audit on notification settings
- Promote the duplicate `SwitchTile` from SecurityFiltersScreen and
  NotificationSettingsScreen into a shared `SettingsSwitchTile` in
  SettingsSectionCard so all settings switches share one implementation.
- Render BatteryOptimizationBanner outside the in-app section card
  instead of nesting a Card inside a Card and splitting the section's
  divider away from the row it explains.
- Refresh the battery-optimization exemption on LifecycleResumeEffect
  so the banner disappears after the user returns from the system
  settings page; drop the racy post-button re-read.
- Demote `HasPushNotificationProvider` to a plain `hasPushNotificationProvider`
  since it returns a per-flavor constant and reads no Compose state.
2026-05-18 21:34:32 +00:00
Vitor Pamplona 90272fea6f Merge pull request #2964 from vitorpamplona/claude/fix-classcastexception-android-CYKoy
Replace AtomicReference with LargeCache in RelayAuthenticator
2026-05-18 17:23:51 -04:00
Claude 669199ab7e refactor(quartz): use LargeCache for RelayAuthenticator authStatus
#2946 fixed the ClassCastException with a bespoke AtomicReference<Map>
+ CAS copy-on-write helper. Quartz already has a concurrent-map
abstraction for exactly this purpose — LargeCache — with platform-tuned
actuals (ConcurrentSkipListMap on jvmAndroid, CacheMap on Apple, custom
on Linux). Swap to it.

Removes the bespoke putAuthStatus/removeAuthStatus helpers, the
ExperimentalAtomicApi opt-in, and the AtomicReference imports.

The RelayAuthenticatorConcurrencyTest from #2946 still passes against
the new implementation.
2026-05-18 21:21:02 +00:00
Vitor Pamplona a3b2d270c7 Merge pull request #2957 from mstrofnone/feat/search-bar-namecoin-resolution-indicator
feat(search): inline Namecoin resolution indicator in global search bar
2026-05-18 16:40:52 -04:00
m 1c5230cfc5 feat(search): inline Namecoin resolution indicator in global search bar
Reuses the NamecoinResolutionRow composable already shipping for the
onchain-zap recipient field, promoting it from
ui/screen/loggedIn/wallet/ to a generic ui/components/namecoin/
location so it can be mounted anywhere a .bit-shaped search input may
race the local-cache prefix search.

In the global search bar, typing a bare ".bit" host (e.g.
"testls.bit") used to surface a cached sibling profile like
"m@testls.bit" first (LocalCache.findUsersStartingWith hits the
prefix) and only several seconds later be corrected by the slower
on-chain ElectrumX resolution from
SearchBarViewModel.directNip05Resolver. No in-flight indicator and no
feedback on hard failures (timeout, malformed record, etc.).

Changes:

  - git-rename NamecoinResolutionRow.kt and its test from
    ui/screen/loggedIn/wallet/ to ui/components/namecoin/, updating
    the package declaration only.
  - Add an optional `modifier: Modifier = Modifier` parameter to the
    composable (standard Compose convention) and wrap the spinner /
    result / error rows in a Column taking the caller-provided
    modifier. No visual change in OnchainZapSendDialog.
  - Update OnchainZapSendDialog import to the new package location.
  - Mount NamecoinResolutionRow in SearchScreen.SearchBar between
    SearchTextField and SearchFilterRow, with horizontal padding to
    match the rest of the bar. onUserResolved navigates to the user
    and clears the field, matching the bech32 auto-resolve path in
    SearchBarViewModel.directRouteResolver.

State is held in the shared
commons.NamecoinResolveState (no new state class introduced) and
diagnostic wording comes from the existing mapOutcomeToResolveState
helper, so every Namecoin surface continues to produce the same
message for the same outcome.
2026-05-19 06:33:47 +10:00
Vitor Pamplona 34cb4eb2ad Merge pull request #2956 from mstrofnone/feat/onchain-zap-namecoin-resolution-indicator
feat(onchain-zaps): inline Namecoin resolution indicator + result row
2026-05-18 16:26:02 -04:00
mstrofnone 012dae31d0 feat(onchain-zaps): inline Namecoin resolution indicator + result row
The local-cache suggestion dropdown can momentarily show a stale match
when the user types a .bit identifier. For example, after resolving
"m@testls.bit" earlier in the session, typing the bare host
"testls.bit" causes findUsersStartingWith() to return the cached
m@... profile first; the correct _@testls.bit profile only appears a
few seconds later when ElectrumX resolution finishes. There is no
visual hint that a Namecoin lookup is in flight, and on hard failures
(timeout, malformed record, no nostr field, etc.) the user gets no
feedback at all.

This adds a dedicated NamecoinResolutionRow composable mounted between
the recipient field and the local-cache dropdown:

  - "Resolving <name> on Namecoin…" spinner while the on-chain
    lookup is in flight (after a 300 ms debounce that matches the
    dropdown's own debounce).
  - On success, a tappable row showing the resolved profile with a
    distinct "Namecoin" badge (MaterialSymbols.Link), so the user
    can pick the on-chain-verified profile unambiguously.
  - On failure, a single explanatory error line covering all
    NamecoinResolveOutcome variants the resolver already produces:
    NameNotFound, NoNostrField, MalformedRecord (with the underlying
    parser error verbatim), ServersUnreachable, InvalidIdentifier and
    Timeout.

State is held in the shared NamecoinResolveState sealed class already
used by NamecoinNameService and the desktop SearchScreen — no new
state model is introduced. A small mapOutcomeToResolveState() helper
mirrors the same wording desktop ships, so all Namecoin surfaces
produce the same diagnostic string for the same outcome.

The row owns its own LaunchedEffect keyed on the typed query, so it
cancels in-flight lookups whenever the user keeps typing, and it
unmounts cleanly once a recipient is selected. It uses the existing
Amethyst.instance.namecoinResolver instance, so no DI plumbing or new
network calls beyond what was already wired up for .bit resolution.

The dropdown is left untouched: local-cache results are still valid
hits (just not necessarily the *intended* on-chain identity), so they
remain available below the new row.

Pure helpers (looksLikeNamecoinIdentifier + the outcome-to-state
mapper) are covered by unit tests in NamecoinResolutionRowTest.
2026-05-19 06:11:39 +10:00
Vitor Pamplona 67e2ee6bd7 Merge pull request #2955 from mstrofnone/feat/onchain-zap-bit-recipient
feat(onchain-zaps): enable Send when typed name resolves via NIP-05
2026-05-18 15:33:04 -04:00
Vitor Pamplona 994923fb84 Merge pull request #2958 from mstrofnone/feat/profile-fields-long-press-copy
feat(profile): long-press to copy Nostr Address, Website, LN Address, identities, payment targets
2026-05-18 15:31:13 -04:00
Vitor Pamplona 869447cfb1 Merge pull request #2962 from davotoula/fix/relay-authenticator-concurrent-map
Thread-safe authStatus in RelayAuthenticator (#2946)
2026-05-18 15:30:36 -04:00
Vitor Pamplona f57facab06 Merge pull request #2963 from vitorpamplona/claude/fix-git-repo-ui-overlap-LWtb9
Apply scaffold padding to GitRepositoryOverview
2026-05-18 15:24:44 -04:00
Claude ee18490761 fix: pad GitRepositoryOverview content to clear top/bottom bars
The Overview tab is a plain vertical-scroll Column inside DisappearingScaffold,
which lays content under the top app bar + tab row and bottom nav. The Issues
and Patches tabs go through RefresheableFeedView and already consume
LocalDisappearingScaffoldPadding internally, but the Overview tab did not, so
its top and bottom were hidden behind the surrounding UI.
2026-05-18 19:16:32 +00:00
davotoula fcf704a3c6 fix(quartz): make RelayAuthenticator authStatus thread-safe (#2946)
OkHttp dispatches WebSocket callbacks on one thread per relay socket,
so RelayAuthenticator's plain LinkedHashMap was mutated concurrently
from many threads during connection storms. When a bucket crossed
HashMap's TREEIFY_THRESHOLD the racing treeify corrupted internal
state and threw ClassCastException: LinkedHashMap$Entry cannot be
cast to HashMap$TreeNode from onDisconnected.
2026-05-18 18:34:17 +02:00
David Kaspar 5cd9d3d873 Merge pull request #2950 from mstrofnone/feat/electrumx-add-ethicnology
feat(electrumx): add electrum.nmc.ethicnology.com to default server set
2026-05-18 18:26:21 +02:00
davotoula 67fc5608cc sonar fixes 2026-05-18 16:54:19 +02:00
David Kaspar 6c71eec2fa Merge pull request #2961 from nrobi144/fix/desktop-proguard-jackson-crash
fix(desktop): fix release build crashes (ProGuard, VLC, jlink modules)
2026-05-18 16:39:47 +02:00
David Kaspar b51de1ae19 Merge pull request #2960 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-18 16:38:59 +02:00
nrobi144 de5dc34cb7 fix(desktop): fix macOS VLC bundled discovery and video rendering
MacOsVlcDiscoverer.setPluginPath() called LibC.INSTANCE.setenv() via
JNA, but macOS 13+ uses versioned symbols (setenv$3b99ba0d) that JNA
can't resolve, breaking all video playback without system VLC.

Changes:
- Replace LibC.setenv with direct JNA Function.getFunction("c","setenv")
  call that bypasses the problematic interface binding
- Store discoveredPluginPath for --plugin-path factory arg fallback
- Add -Dvlc.plugin.path JVM property as ultimate fallback
- Delete stale VLC plugin cache on macOS before factory creation
- Pass --plugin-path to audio factory too when env var fails
- Add jdk.unsupported module to jlink (VLCJ ByteBufferFactory needs
  sun.misc.Unsafe for video frame buffer allocation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-18 16:52:43 +03:00
nrobi144 935ad84ac3 fix(desktop): fix release build ProGuard rules that crash app on launch
ProGuard in release builds (packageReleaseDmg/Deb/Rpm) strips classes
accessed via reflection, JNI, or service loading — causing multiple
runtime failures:

- Jackson ExceptionInInitializerError: enum constants stripped, NPE in
  MapperConfig.collectFeatureDefaults() (#2929, 5000 sats bounty)
- JNA/VLCJ: static methods stripped, SIGABRT in native callbacks
- secp256k1/SQLite/kmp-tor: JNI loader classes stripped
- Coil/okhttp/okio: image loading and networking broken
- Kotlin metadata stripped: Jackson can't call default constructors

Changes:
- Upgrade ProGuard 7.7.0 → 7.9.1 (Kotlin 2.3 metadata support)
- Disable optimization (-dontoptimize) to prevent bytecode rewriting
  that produces VerifyError (Guardsquare/proguard#460)
- Add -keep rules for all JNI/reflection-dependent libraries
- Keep Kotlin @Metadata annotations for Jackson deserialization
- Suppress Kotlin 2.3 compile-time stub warnings

Tested: release DMG builds and launches without crash on macOS.

Closes #2929

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-18 16:52:27 +03:00
Crowdin Bot 543c4dfeb8 New Crowdin translations by GitHub Action 2026-05-18 12:50:46 +00:00
Vitor Pamplona 34f5dcac69 Merge pull request #2959 from greenart7c3/claude/fix-blossom-cache-url-8zolt
fix(blossom): only bridge to local cache when URL is BUD-01 layout
2026-05-18 08:49:22 -04:00
mstrofnone 69190e7810 feat(profile): long-press to copy Nostr Address, Website, LN Address, identities, payment targets
Currently the only profile fields with an explicit copy affordance are
npub and nprofile (small ContentCopy IconButtons next to the keys).
The other identifier-shaped fields rendered by DrawAdditionalInfo and
DisplayLNAddress (NIP-05 Nostr Address, Website URL, lud16 Lightning
Address, NIP-39 external identities, NIP-A3 payment targets) all have
a tap action that *uses* the value (opens the URL, expands the zap
sheet, etc.) but no way to actually copy the value itself short of
selecting the underlying "View source" or opening the link and
copying from the browser. Closes that gap with the platform-native
long-press gesture.

Adds a small reusable LongPressCopyText composable in
ui/components/util/ that:

  - renders plain Text styled in primary colour (visual match for the
    previous ClickableTextPrimary),
  - wires onClick through Modifier.combinedClickable (so existing tap
    behaviour is preserved),
  - adds onLongClick to copy the supplied raw value to the system
    Clipboard and surface a Toast.makeText "Copied to clipboard"
    confirmation,
  - announces the long-press action to TalkBack via onLongClickLabel.

The composable deliberately uses a plain Text + outer
combinedClickable rather than an AnnotatedString with an inline
LinkAnnotation.Clickable: an annotation-level click can't see a
parent combinedClickable's long-press, because the parent's tap area
sits above the annotation's hit-test region and would consume the
tap before the annotation could fire it. Plain Text + outer modifier
keeps both gestures behaving correctly.

Wired into:
  - DisplayNip05ProfileStatus (NIP-05 Nostr Address) — tap opens the
    domain in the browser, long-press copies the full
    user@domain value.
  - The website row in DrawAdditionalInfo — tap opens the URL,
    long-press copies the *raw* website value as stored in the
    profile (the displayed form is stripped of the scheme + trailing
    slash for readability; the copy preserves the original).
  - The external-identity rows (Twitter, Mastodon, Telegram, GitHub)
    — tap opens the proof URL, long-press copies the identity handle.
  - DisplayLNAddress lud16 — tap toggles the zap sheet (unchanged),
    long-press copies the lud16 address.
  - PaymentTargetRow authority — tap opens the payto: URI, long-press
    copies the authority.

The existing inline ContentCopy IconButtons on npub and nprofile are
left in place; the long-press gesture is purely additive for the
other rows.

Two notable side-effects from the ClickableTextPrimary -> Text +
combinedClickable rewrite:
  - Touch ripple feedback now appears on tap (combinedClickable's
    default indication). Previously the inline LinkAnnotation rendered
    no ripple. This is consistent with how other clickable rows in the
    app behave and provides better tap feedback.
  - The NIP-05 row no longer renders the click region as a styled
    inline span; the entire text behaves as one tap+long-press target.
    Visually identical for short addresses; for any address long
    enough to wrap, the click region now matches the visible bounds
    rather than the per-glyph bounds.

Adds a new copied_to_clipboard string. Picks up the existing
copy_to_clipboard string for the TalkBack long-press hint.
2026-05-18 15:53:16 +10:00
mstrofnone 1f47a63470 feat(onchain-zaps): enable Send when typed name resolves via NIP-05
The Send-onchain-zap Recipient field already supported typing a NIP-05
or bare .bit name and tapping the resolved suggestion. If a user typed
a fully-qualified name (e.g. "m@testls.bit" or "testls.bit") and hit
Send without first tapping the suggestion, the eager resolver only ran
decodePublicKeyAsHexOrNull(), which returns null for non-npub inputs,
so the button stayed disabled with no inline feedback.

Mirror the dropdown's existing NIP-05 / .bit resolution into the dialog
state via UserSuggestionState.nip05ResolutionFlow, and use the resolved
User's pubkey as one more fallback in resolvedRecipient. The flow is
already debounced (300ms) and shared with the suggestion list, so this
adds zero extra network traffic and zero new code paths -- just enables
Send 300ms after a valid name resolves.

Behaviour:
 - Typing "m@testls.bit" + waiting 300ms -> Send enables.
 - Typing "testls.bit" + waiting 300ms -> Send enables (same path the
   dropdown uses for bare .bit names; resolves as _@testls.bit).
 - Typing an npub or hex pubkey -> unchanged (already worked).
 - Typing an unrecognised string -> Send stays disabled, unchanged.
 - Tapping the suggestion before Send -> unchanged (selectedUser wins).

No behaviour change anywhere outside the onchain zap dialog.
2026-05-18 13:50:47 +10:00
Vitor Pamplona 447c662dff Merge pull request #2954 from vitorpamplona/claude/fix-illegal-argument-exception-b572Q
Fix public chat deduplication by channel ID extraction
2026-05-17 22:12:58 -04:00
Claude cc83c24427 fix: dedupe public-channel rows in chatroom list updates
When the initial chatroom list picked a ChannelMetadataEvent or
ChannelCreateEvent as the representative note for a public channel
(because no ChannelMessageEvent had been seen yet), the arrival of a
later ChannelMessageEvent caused updateListWith to append a second
entry for the same channel — its match check only recognized old
notes whose event was a ChannelMessageEvent. Two notes for the same
channelId then produced the same PublicChannelLazyKey in the LazyColumn
and crashed with IllegalArgumentException: Key was already used.

Extract the channel id from any of the three public-chat event types
when matching the existing entry so the new message replaces the
placeholder instead of duplicating it.
2026-05-18 01:43:08 +00:00
Claude da2145459e fix: normalize video/x-m4v to video/mp4 when saving to MediaStore
Android's MediaStore rejects video/x-m4v (returned by MimeTypeMap for .m4v
URLs) with IllegalArgumentException. M4V is an MP4 container variant, so
map it to video/mp4 before the ContentResolver.insert call.
2026-05-18 01:40:00 +00:00
Vitor Pamplona 51e136abdd Merge pull request #2953 from greenart7c3/claude/fix-payment-targets-loading-fx9jo
fix: subscribe and observe PaymentTargetsEvent for other users
2026-05-17 21:03:59 -04:00
Claude 961c649901 feat: explain hashtag-limit hide reason in HiddenNote
When a post is filtered out by the max-hashtag-per-post setting, the
"Show Anyway" card previously displayed the generic "Post was muted or
reported by" text with no avatars below it, which was confusing.

Plumb a hasExcessiveHashtags flag and the configured limit through
NoteComposeReportState and BlockReportChecker so HiddenNote can render a
hashtag-specific message ("This post has more than N hashtags"). When
both reports and the hashtag limit apply, show both reasons.
2026-05-18 00:58:36 +00:00
Claude e719d9ef00 fix(imeta): accept floating-point dimensions so image space is reserved pre-load
Primal-style clients emit "dim 317.0x498.0" in NIP-92 imeta tags. DimensionTag.parse
called Int.parseInt on each component, threw NumberFormatException, and returned
null. With dim==null and no cached aspect ratio, GifVideoView / UrlImageView built
the container without an aspectRatio modifier, so the inline image collapsed to
zero height and the post body looked empty until Coil delivered the bitmap.

Parse each component as Double then truncate to Int. Adds DimensionTagTest covering
integer, float, truncation, 0x0 and malformed inputs.

https://claude.ai/code/session_01W1crao6Hwip8k5ByoLxVrc
2026-05-17 23:22:10 +00:00
Claude 27488f8f34 fix: subscribe and observe PaymentTargetsEvent for other users
PaymentButton and DisplayPaymentTargets read note?.event inside a
remember(note) keyed on the AddressableNote reference, which is stable
across event arrival. They also rely solely on LoadAddressableNote's
cache lookup with no relay subscription, so when viewing a profile
outside the path that activates UserProfileMetadataFilterSubAssembler
(or before the event arrives) the UI never updates.

Match the canonical pattern from ProfileBadgesScreen: pair
LoadAddressableNote with EventFinderFilterAssemblerSubscription to
request the event from relays, and observeNoteEvent<PaymentTargetsEvent>
to recompose when it arrives.
2026-05-17 22:20:48 +00:00
David Kaspar 358bd6a325 Merge pull request #2952 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-17 22:36:21 +02:00
Claude 9390c25b2c feat: extract notification settings into dedicated screen
Group every notification-related preference under a new
"Notifications" entry in the settings menu, rendered with the modern
card/section design used by SecurityFiltersScreen.

Moves out of "UI preferences":
- Push notification provider (UnifiedPush, fdroid)
- Always-on notification service + battery optimization banner
- Split notifications by Follows

The fdroid PushNotificationSettingsRow becomes PushNotificationProviderTile
built around SettingsBlockTile; the play stub gains a matching
HasPushNotificationProvider() so the push section is hidden on Play builds.
2026-05-17 17:49:45 +00:00
Crowdin Bot 7b6293b945 New Crowdin translations by GitHub Action 2026-05-17 15:49:38 +00:00
David Kaspar 4044c9341c Merge pull request #2951 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-17 17:48:02 +02:00
Crowdin Bot 92a25e3a38 New Crowdin translations by GitHub Action 2026-05-17 12:34:52 +00:00
davotoula 53c6d5d14d i18n: translate call-permission and git-repo strings (cs, pt-BR, sv, de)
17 strings: 5 for the call-permission-denied dialog plus 12 for the new
git repository overview / issues / patches screen and status pills.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:26:26 +02:00
m 968a396779 feat(electrumx): add electrum.nmc.ethicnology.com to default server set
Adds a third public Namecoin ElectrumX server to the default and
Tor-preferred lists in DEFAULT_ELECTRUMX_SERVERS / TOR_ELECTRUMX_SERVERS:

  electrum.nmc.ethicnology.com:50002  (IPv4 142.44.246.181, OVH Canada)

Operated by @ethicnology, who ships the namecoind + ElectrumX + mempool
podman stack at github.com/ethicnology/namecoin-compose. Probed live:

  - server.version      -> ElectrumX 1.19.0, protocol 1.4
  - server.features     -> Namecoin mainnet genesis 000000000062b72c...c770
  - scripthash.get_history for d/testls -> full history (heights up to
    822885), and blockchain.transaction.get decodes the OP_NAME_UPDATE
    output correctly. Same code path used by ElectrumXClient against all
    other public servers, no client changes required.

TLS uses a publicly-trusted Let's Encrypt cert, so usePinnedTrustStore
is left at the default (false). This makes it the first entry in the
list whose TLS does NOT depend on PINNED_ELECTRUMX_CERTS, and adds
useful diversity:

  - electrumx.testls.space      (self-signed, pinned, often ECONNRESETs)
  - nmc2.bitcoins.sk / 46.229.238.187  (self-signed, pinned)
  - relay.testls.bit / 23.158.233.10   (self-signed, pinned)
  - electrum.nmc.ethicnology.com       (LE cert, system trust store)

If every self-signed peer is unreachable (e.g. corporate networks that
strip unknown CAs but allow LE chains), resolution can still succeed.

No bare-IP companion entry is added for 142.44.246.181: unlike the
46.229.238.187 / 23.158.233.10 pinned peers (which use DER-SHA256
pinning that ignores hostname verification), an IP-literal endpoint
against the LE cert would fail standard hostname verification under
the system trust manager (SAN covers only the hostname). The IP is
captured in this commit message and the source comment for reference.

Verification on this branch:
  - :quartz:spotlessCheck   OK
  - :quartz:jvmTest         OK (BitRelayResolverTest etc. unchanged)
2026-05-17 17:09:42 +10:00
Vitor Pamplona 0cf8ecef13 Merge pull request #2949 from vitorpamplona/claude/add-zaps-to-stats-Z2qHO
feat(notifications): include onchain zaps in summary stats
2026-05-16 19:07:39 -04:00
Vitor Pamplona 57acfe200a Merge pull request #2948 from vitorpamplona/claude/change-hashtag-limit-Jujia
Raises the default maximum hashtag limit from 5 to 8
2026-05-16 19:06:22 -04:00
Vitor Pamplona 55802fa21b Merge pull request #2947 from vitorpamplona/claude/restore-lightning-url-field-LdXOc
feat(profile): restore Lightning Address + LNURL fields in Edit Profile
2026-05-16 19:05:53 -04:00
Claude 09c4d70048 fix(blossom): only bridge to local cache when URL is BUD-01 layout
`bridgeUrl` accepted the imeta `x` hash as a sufficient signal to route
a media URL through the local Blossom cache, even when the URL itself
didn't have the sha256 in its last path segment. For URLs like
https://i.nostr.build/M5AwJ.gif the upstream serves the blob under an
opaque filename, so the resulting `xs=https://i.nostr.build` hint sent
the local cache after https://i.nostr.build/<sha>.gif on miss — which
404s because the real blob isn't at that path.

Match the behaviour `bridgeProfilePictureUrl` already had: require
`extractSha256FromUrlPath(url)` to succeed before bridging. The imeta
hash is still preferred for canonical casing when present, but is no
longer enough on its own.
2026-05-16 22:45:06 +00:00
Claude 92b1930228 feat(notifications): include onchain zaps in summary stats
Add OnchainZapEvent handling to NotificationSummaryState so NIP-BC
onchain zaps targeting the current user contribute their claimed sats
to the daily zap total and chart alongside LnZapEvent receipts.
2026-05-16 22:28:30 +00:00
Claude e5cf575602 feat(video): release MediaController after 30s in background
Pause-on-background (0de70e51) stops audio/video from leaking past the
foreground, but the MediaController itself stays bound — so the
underlying ExoPlayer keeps holding its codec and decoder buffer the
whole time the app is away.

Schedule a 30s timer on ON_PAUSE; if the activity is still backgrounded
when it fires, flip a keepAlive gate that swaps the controllerAsFlow
out for flowOf(null). collectAsState cancels the previous collection,
awaitClose releases the MediaController, the session disconnects, and
the ExoPlayer goes back to the pool (warm slot keyed by URI). ON_RESUME
cancels any pending timer and flips the gate back, so the flow rebuilds
a fresh MediaController. The onEach warm-pool fast path then re-attaches
to the same paused player — currentMediaItem still matches, no
setMediaItem call, no re-prepare, so position + buffer come back intact.

PiP is exempt via the existing BackgroundMedia.isMutex check, same
pattern the pause-on-background handler uses.

https://claude.ai/code/session_01RoEUbAN8ejF21Ns3eM6xad
2026-05-16 22:25:19 +00:00
Claude 8ffe1aee4f feat(wallet): clickable onchain card opens transaction history
Tapping the Bitcoin card on the wallet screen now navigates to a new
OnchainTransactionsScreen that lists transactions touching the account's
Taproot address, mirroring the NWC transactions view.

- OnchainBackend gains getTxsForAddress(address, afterTxid) returning
  BitcoinAddressTx rows (netValueSats, confirmations, blockHeight,
  blockTime, counterparty addresses). EsploraBackend implements it via
  GET /address/{addr}/txs and /address/{addr}/txs/chain/{last_seen}
  for pagination; CachingOnchainBackend passes through.
- OnchainTransactionsViewModel loads the address from the account
  signer + LocalCache.onchainBackend, paginates, and for each chain
  row scans LocalCache for an OnchainZapEvent with a matching txid so
  the UI can render the Nostr counterparty (sender pubkey for
  incoming, p-tagged recipient for outgoing).
- ALL / ZAPS / NON-ZAPS filter chips reuse the existing
  TransactionFilter enum. Mempool rows are flagged "Pending" in
  bitcoin-orange.
2026-05-16 22:19:47 +00:00
Claude 44234aa648 feat(profile): inline Lightning Address + LNURL fields in Edit Profile
Promote the two lightning fields out of an expandable section so they
sit alongside name/about/nip05/website as regular always-visible inputs.

https://claude.ai/code/session_01R2E6rMfhxKA8csVarjxKJd
2026-05-16 22:16:52 +00:00
Claude 7e750d2517 feat(profile): restore Lightning Address + LNURL fields in Edit Profile
Lightning fields had moved entirely into the NWC wallet setup screen
(NIP47SetupScreen), so a user with no NWC connection had no way to set
their lud16/lud06 — required for receiving zaps. Add an expandable
Lightning section in the profile editor that reads/writes lud16 and
lud06, auto-expanded when either is set. NIP47SetupScreen keeps its
lud16 field; both flows go through sendNewUserMetadata which already
no-ops null params, so editing only one side never wipes the other.

https://claude.ai/code/session_01R2E6rMfhxKA8csVarjxKJd
2026-05-16 22:00:30 +00:00
Claude 4a23ecf7ef Raises the default maximum hashtag limit from 5 to 8 2026-05-16 21:38:58 +00:00