Skip to content

feat(audio): show how often a sound has been reused while picking one - #8963

Merged
hm21 merged 2 commits into
mainfrom
feat/6931-sound-reuse-counts
Sep 9, 2026
Merged

hm21 merged 2 commits into
mainfrom
feat/6931-sound-reuse-counts

Conversation

@hm21

@hm21 hm21 commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

Description

The count path already existed — SoundsRepository.fetchVideosUsingSoundCount, soundUsageCountProvider, and the SoundDetailScreen header — but a creator comparing reusable sounds could not see reuse anywhere without opening each one.

The audio picker's Community tab (AudioSelectionBottomSheet, the Nostr/trending list) now reads 00:06 ∙ Artist ∙ 12 videos per row, using the existing localized plural key soundVideoCount — already translated in all 22 locales, and the same key the detail header uses. A count of 0 or unknown renders nothing, so an unused sound reads the same as one whose count has not arrived yet.

Avoiding the per-row relay fan-out

soundUsageCountProvider is a NIP-45 COUNT per sound; watching it per row is exactly the pattern #6726 rules out. Instead:

  • SoundsRepository.fetchVideosUsingSoundCounts(Iterable<String>) issues one queryEvents with Filter(kinds: [34236], e: [all ids], limit: 500) and tallies e tags client-side. One relay round trip for the whole list, regardless of row count. Because it matches the same #e predicate the relay applies, a batched entry equals what fetchVideosUsingSoundCount reports for that sound.
  • trendingSoundUsageCountsProvider derives its id set from trendingSoundsProvider, so there is no list-valued family key — a List argument would break Riverpod caching and refetch on every rebuild. Riverpod caches the result and re-runs only when trending sounds change.
  • Ids come from AudioEvent.attributionEventId, so bundled assets and unpublished imports are skipped, and a list made only of those never touches the repository at all.
  • Loading and failure are non-blocking: the sheet reads .value, so rows render immediately and simply carry no counts if the query is pending or fails. That matches SoundDetailScreen's existing usageCountAsync.value ?? 0.

The known trade, documented on the method: limit caps the video events for the whole batch, so a batch whose true total exceeds 500 under-reports. NIP-45 cannot return per-filter counts and there is no funnelcake sound-count endpoint, so this was the only route to a single round trip. Today's reuse volumes are well under it; if it ever becomes visible the fix is a server-side count endpoint, not a larger limit — these are full video events at ~1–2 KB each.

SoundTile is deleted

The issue's fourth acceptance criterion pointed at SoundTile's existing videoCount parameter. It has no production caller, duplicates AudioListTile, and carries raw-TextStyle and raw-Icons debt; wiring it into the picker would have meant replacing the tile that already owns selection, the playing indicator and the E2E semantics anchors. So it and its orphan test are removed, along with the four ARB keys only it read (soundUntitled, soundStopPreview, soundPreviewSemanticLabel, soundViewDetailsSemanticLabel) across all 23 ARB files. The raw_textstyle and raw_icons baselines are regenerated in the same change to lock that reduction in, and the hardcoded_visible_strings_test guard is re-pointed at audio_list_tile.dart.

Related Issue: Closes #6931

Out of Scope

  • The Library "Sounds" tab is unchanged. Its list comes from SavedSoundsBloc, not Riverpod, so counts there need a second batched query plus a Cubit and a Riverpod-dependency bridge — a separate, larger change that would fit naturally alongside feat(audio): add creator sound reuse analytics #6726's creator analytics, which needs the same shape.
  • Zero renders as nothing, not "No videos yet." Deliberate for a picker; the detail screen still says it. It is a product call and easy to flip.

Verification

  • dart format on the changed files, then dart format --output=none --set-exit-if-changed → 34 files (0 changed).
  • flutter analyze lib test integration_test → No issues found.
  • cd mobile/packages/sounds_repository && flutter test --coverage → 71 passed, coverage 100.00 % (177/177). That package's floor is 100.
  • flutter test test/providers/sounds_providers_test.dart test/widgets/video_editor/audio_editor/ test/screens/sound_detail_screen_test.dart test/l10n/ test/widgets/library/ → 492 passed, including arb_consistency_test.
  • 28 ratchet scripts run individually — orphaned ARB floor, l10n delegates, ungrouped/skip/placeholder/unpinned, shared-setup stubs, all four design-system ceilings, maestro copy drift, nostr-id truncation, pubkey log encoding, layer direction, package coverage and CI floors, empty catch, indeterminate progress — all pass.

Re-verified after rebasing onto main at 3be4f0c (which now carries the Flutter 3.47.2 upgrade from #8915). The only rebase conflict was mobile/test/widgets/sound_tile_test.dart, which #8915 had reformatted and this branch deletes along with SoundTile — resolved by taking the deletion, since nothing references the widget any more. On the new base: format clean, flutter analyze lib test integration_test clean, the touched app tests and the full sounds_repository suite pass, and that package still measures 100.00 % (306/306).

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🧹 Code refactor

@hm21 hm21 self-assigned this Sep 9, 2026
@hm21
hm21 marked this pull request as ready for review September 9, 2026 12:47
@hm21
hm21 requested a review from a team as a code owner September 9, 2026 12:47
@hm21
hm21 requested review from rabble and realmeylisdev and removed request for a team September 9, 2026 12:47
@github-actions

This comment has been minimized.

@rabble rabble left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed commit 73a12ca. No blocking findings.

Checked the batched query through the Nostr client's filtering/deduplication, attribution ID mapping, picker loading behavior, and removal of the unused widget/localization keys. The documented 500-event batch cap remains a limitation: these are capped reuse counts, not an unrestricted aggregate.

Validation: 492 focused app tests and all 71 sounds_repository tests passed; repository coverage is 100%. Changed app-file analysis and package analysis both passed. No device or live-relay test was performed.

@hm21
hm21 force-pushed the feat/6931-sound-reuse-counts branch from 73a12ca to 939cb31 Compare September 9, 2026 17:05
@github-actions

This comment has been minimized.

hm21 added 2 commits September 9, 2026 19:17
The count path already existed — SoundsRepository.fetchVideosUsingSoundCount,
soundUsageCountProvider, and the sound detail header — but a creator choosing
a sound could not see reuse anywhere before opening each one. The audio
picker's Community tab now reads "00:06 . Artist . 12 videos" per row, using
the same localized plural key the detail header uses. A count of zero or
unknown renders nothing, so an unused sound reads the same as one whose count
has not arrived.

soundUsageCountProvider is a NIP-45 COUNT per sound, so watching it per row is
the unbounded fan-out #6726 rules out. Instead fetchVideosUsingSoundCounts
issues one queryEvents for the whole list and tallies e tags client-side: one
relay round trip regardless of row count, matching the same #e predicate the
relay applies, so a batched entry equals what the single-sound call reports.
trendingSoundUsageCountsProvider derives its id set from trendingSoundsProvider
rather than taking a list argument, which would break Riverpod caching and
refetch on every rebuild. Ids come from attributionEventId, so bundled assets
and unpublished imports are skipped and a list of only those never reaches the
repository. Loading and failure are non-blocking: the sheet reads .value, so
rows render immediately and simply carry no count.

The known trade, documented on the method: limit caps the video events for the
whole batch, so a batch whose true total exceeds 500 under-reports. NIP-45
cannot return per-filter counts and there is no funnelcake sound-count
endpoint, so this was the only route to a single round trip.

SoundTile is deleted along with its orphan test and the four ARB keys only it
read. It had no production caller, duplicated AudioListTile, and carried raw
TextStyle and raw Icons debt; wiring it in would have meant replacing the tile
that already owns selection, the playing indicator and the E2E semantics
anchors. The raw_textstyle and raw_icons baselines are regenerated to lock
that reduction in the same change.
The committed provider hash was stale against the final edit of
sounds_providers.dart, which CI's Generated Files job caught.
@hm21
hm21 force-pushed the feat/6931-sound-reuse-counts branch from 939cb31 to f7165bc Compare September 9, 2026 17:19
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Mobile PR Preview

Preview refreshed for f7165bc

Last refresh: f7165bc at 2026-09-09 17:23:40 UTC (preview run)

Property Value
Preview URL https://7ce1d2f8.openvine-app.pages.dev
Pages project openvine-app
Preview branch pr-8963
PR branch feat/6931-sound-reuse-counts
Commit f7165bc

@hm21
hm21 added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit a0d118c Sep 9, 2026
22 checks passed
@hm21
hm21 deleted the feat/6931-sound-reuse-counts branch September 9, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(audio): surface reusable sound counts outside detail

2 participants