Repository navigation
Tags: divinevideo/divine-mobile
Tags
feat(video-editor): add star, triangle, diamond, hexagon and bubble m… …asks (#10421) * feat(video-editor): add star, triangle, diamond, hexagon and bubble masks The shape mask offered a circle, a rectangle, a heart and a linear edge. A star, a speech bubble, a triangle, a diamond and a hexagon give clips and stickers playful and picture-in-picture frames. pro_image_editor draws the shapes, so the export needs no native change. A new triangle starts equilateral and a new hexagon regular, and a shape that was never stretched takes the new shape's proportions when switched, so a circle switched to a hexagon stays regular. The mask editor's panel now puts the shapes at the bottom, with the sliders growing in above them. Its height follows the sliders it shows, and the canvas follows the panel, so no empty space is left above the shapes. The row of shapes scrolls out to the screen edges. * build(deps): bump pro_image_editor to 14.19.0 14.19.0 carries the mask shapes from 14.18.0 together with the text layer changes that the next text-wrapping PR builds on, so both land on one package version. Text without a background now wraps only when what it draws reaches the layer bounds; the gradient fill and the option to run past the bounds are opt-in and unused here.
feat(profile): show supporter status as an animated halo and wings (#… …10382) * feat(profile): show supporter status as an animated halo and wings Prototype for #9914, gated by --dart-define=SUPPORTER_HALO_PREVIEW=true on the own profile: a halo and two wings positioned from the Figma frame, driven by one repeating controller, paused for reduced motion and when not visible. Co-Authored-By: Claude <noreply@anthropic.com> * feat(profile): hide supporter halo and wings while the avatar lightbox is open They used to stay behind on the profile while only the avatar flew to the lightbox. They now fade and draw in toward the avatar as it takes off and return as it lands, following the lightbox route animation so an interrupted or swiped-back close stays in step. Refs #9914. Co-Authored-By: Claude <noreply@anthropic.com> * feat(profile): open a supporter view from other supporters' avatars Tapping another supporter's avatar now opens a full-screen view where the avatar, halo and wings fly in as one piece and keep animating, with sparkles and hearts behind them, a short pitch, three benefits and a "Become a supporter" button. Your own profile keeps the plain lightbox. The new strings are English-only for now (known untranslated debt). Refs #9914. Co-Authored-By: Claude <noreply@anthropic.com> * feat(profile): show the supporter halo for public supporters The halo and wings were behind a dev-only preview flag. They now follow each profile's public supporter status (haloVisible from the supporters API), replacing the old heart-and-label badge. Another supporter's avatar opens the supporter view; everyone else gets the plain photo lightbox. Refs #9914 Co-Authored-By: Claude <noreply@anthropic.com> * fix(profile): stop the supporter halo flickering after the lightbox closes A Hero swaps its child for a placeholder while it flies, so the halo and wings remounted on landing and replayed their status fade-in. The fade now lives above the Hero and only plays when supporter status first arrives. Refs #9914 Co-Authored-By: Claude <noreply@anthropic.com> * feat(profile): replace the supporter label with the own supporter view The halo and wings already mark a supporter, so the heart label on the owner's profile was redundant. The owner's halo now follows their public status, and tapping it opens a supporter view in the same style as the one other people see. Refs #9914 Co-Authored-By: Claude <noreply@anthropic.com> * feat(profile): add the supporter visibility toggle to edit profile Supporters can now choose whether their halo shows publicly from Edit Profile, next to the rest of what others see on their profile. The setting flips as soon as it is tapped and reverts with an error if saving fails. Refs #9914 Co-Authored-By: Claude <noreply@anthropic.com> * feat(l10n): translate the supporter view into every locale The supporter view's copy was English-only and listed as untranslated debt; it now ships in all 22 locales, using each one's existing word for supporter. Refs #9914 Co-Authored-By: Claude <noreply@anthropic.com> * fix(profile): keep supporter name honorifics on the same line * docs(profile): drop the public setting from the own supporter view's doc The avatar's isOwnProfile doc said the owner's supporter view carries the public setting, but that view deliberately has none: a test asserts there is no switch, and the setting lives in Edit Profile and the Supporter screen. Also rejoins a wrapped doc line on SupporterHaloWings. * refactor(profile): share one route between the avatar lightboxes The supporter view copied the photo lightbox's whole route: the root navigator, the transparent barrier, the fade and the detached push. Both now go through one helper, so the comment explaining why the route sits on the root navigator, and must be a PageRoute for the Hero, covers both. * test(profile): pin who the supporter view invites to join The description promises that viewers who already support Divine see no invitation in another supporter's view, but no test checked either side. These fail if the call to action is always or never shown. * fix(profile): keep the space after a mid-sentence supporter name The headline trimmed the leading space of every piece after the name, not only the one the line break replaces. A translation that puts the name in the middle of the sentence, which the ARB note invites, would render it glued to the next word. No shipped locale does that today, so the test overrides the English headline to put the name mid-sentence. * fix(l10n): use standalone supporter titles outside the headline * fix(l10n): keep the Telugu supporter title free of subtitle text * fix(profile): use the light-mode accent for the supporter badge text The "Divine Supporter" words in the supporter view headline used the fixed brand green, which sits at about 2:1 against the light page and fails the 3:1 bar for large text. The accent token is that same green in dark and a deep green in light, so dark mode is unchanged. * fix(profile): stop leaking curve listeners from the supporter view The supporter view built a CurvedAnimation in build, and its Hero shuttle built another per flight. Each one adds a status listener to the route animation and is never disposed, so every rebuild added another. Driving the route animation through a CurveTween gives the same values without registering anything of its own. With leak tracking enabled for CurvedAnimation, opening the supporter view reported one undisposed CurvedAnimation before this change and none after. * fix(profile): skip the membership read for signed-out viewers The supporter view's call to action read the account-scoped supporter repository for every viewer. For a guest that builds a repository for an 'unauthenticated' account and the kept-alive store validator, which starts listening to in-app purchases, only to show the invitation guests always see. Guests now get the invitation without that read, as the settings entry already does. * fix(profile): scale the supporter view's avatar group on narrow screens The group claims the avatar's width plus both wings, about 307pt in the supporter view. Below roughly 355pt of screen width the column clamps that box while the avatar keeps its offset, so the right wing slid about 30pt over the avatar and the Hero box stopped matching the header's shape. The group now scales down as one piece when it does not fit. * fix(profile): hold the supporter halo level when motion is off With reduced motion the loop stopped at its first frame, which is not a rest pose: the halo sat at its full 2.5 degree tilt, the wings at 5.5 and 3.3 degrees, and the avatar itself turned and shifted slightly. Without motion the halo and the group now stay untransformed and both wings rest at the middle of their swing, so they stay tucked behind the avatar. * test(supporter): read membership labels from l10n instead of English The membership tests asserted on literal English copy, which breaks on a copy change and cannot show the widgets still read their labels from localizations. They now resolve the same keys the widgets render. * refactor(supporter): look up optional supporter cubits by nullable type Four places found an optional SupporterCubit or PublicSupporterCubit by catching ProviderNotFoundException. A nullable lookup answers the same question with null and no exception, so each becomes a plain null check. * refactor(supporter): keep the failure wording beside the other l10n maps The SupporterFailure-to-text extension lived in the Settings screen file, so the Edit Profile section imported that whole screen to word an error. It now sits in lib/l10n with the app's other code-to-string maps, and both screens import it from there. The extension itself is unchanged. * refactor(profile): share the supporter view's icon badge The owner's membership row and each benefit drew the same filled circle around an accent icon from two copies of the decoration. One small widget now draws it for both, so the two cannot drift apart. * fix(profile): drop the empty gap the hidden supporter setting left The Edit Profile form spaces its fields 16pt apart, and a Column spaces its children even when one of them is empty. The supporter setting is empty for everyone who is not a supporter, so most accounts got 16pt of blank space at the end of the form. The setting now sits outside the spaced fields and adds the same gap above itself only when it shows. * perf(profile): fetch supporter status alongside the profile, once The public supporter status lived inside the identity skeleton and was created lazily, so its request only started after the profile had loaded, and the halo always arrived a round trip late. The skeleton also remounts its child when it switches to content, which would have dropped an earlier answer. The status now sits above the skeleton and starts with the header, so both requests run together and the answer survives the switch. * fix(supporter): keep the profile header when a remote signer gets ready The membership scope keyed its cubit on whether it could refresh, so the cubit was replaced when a Keycast signer became usable after sign-in. On your own profile the scope now wraps the avatar and bio, so that swap remounted the whole identity block on cold start, collapsing an expanded bio among other state. The cubit is now keyed on the account's repository alone, and the refresh it waited for runs from a listener on the signer becoming ready. * fix(profile): trust public support after cached membership expiry * Revert "fix(profile): skip the membership read for signed-out viewers" This reverts commit 3aeeefc. A signed-out viewer never reaches a profile. The router sends every unauthenticated location outside the auth, public-support and recorder routes to /welcome (app_router_redirect.dart), so the supporter view's call to action never builds for a guest. The guard and its test covered a path that cannot run. * Revert "fix(profile): drop the empty gap the hidden supporter setting left" This reverts commit a6c0ecd. The gap is 16pt of extra space below the last field of a scrolled form that already ends in 24pt of padding. Closing it nested the form's main Column and made the supporter section carry the form's spacing, with two tests that pin padding values. That coupling costs more than the space. * test(l10n): drop the test that pins one Telugu string The test fixed supporterTitle in app_te.arb to its current wording, so any correction from the fluent-speaker pass this PR still needs would fail it. The ARB fix from 28818df stays. * test(profile): cover vanished profiles with active supporter status --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Daniel Cadenas <dcadenas@gmail.com> Co-authored-by: hm21 <alex.frei@waio.ch> Co-authored-by: Meylis <meylis@divine.video>
fix(dm): count every failed DM retry once, whoever ran it (#10346) * fix(dm): charge each failed DM retry once, in the call that ran it A Resend, the sentPartial Retry, the reel-reply Retry, the collaborator invite banner and the moderation report delivery never charged a hard failure: only the retry sweep did, after the call returned. A failed self-copy replay reached through recoverFullSend was free on those paths, and so was a replay the relay rejected again (#8628). recoverFullSend and recoverSelfWrap now charge every failed attempt they run inside their locked bodies, as they already did for a soft-unconfirmed one, and the sweep stops charging the result. A caller that joins an attempt in flight therefore never charges it twice, and the sweep's failed-send arm no longer double-charges a row a Resend promoted to self-copy-only underneath it. An attempt that reached no relay stays free (#8516); a dispatch that throws is still charged by the sweep, since it never reached the repository's charge. * fix(dm): stop charging a self-copy retry that reached no relay The sweep's self-copy arm charged a retry that never left the device, offline or with no relay connected, and re-armed its 30-second follow-up for it, while the full-send arm has left those free since #8516. publishSelfWrap could not tell the two apart: it reported a PublishNoRelays and an offline fail-fast as the same plain failure as a relay refusing the copy. publishSelfWrap now marks those two outcomes reachedNoRelay. recoverSelfWrap still marks the self-copy failed but neither charges the attempt nor nudges the sweep, and the sweep counts it as not sent and leaves it to a relay connecting, as the full-send arm does. A timeout or a refused copy is still a charged failure. * fix(dm): treat a self-copy to your own inbox as unsent while no relay is connected An account that advertises its own kind-10050 inbox publishes its self-copy to named relays, and publishEvent skips its PublishNoRelays check for named relays: with nothing connected it returns a plain PublishFailed. So the previous commit's no-relay rule never fired for those accounts, which is every account with an inbox, and an outage still used up a self-copy retry. publishSelfWrap now also reports reachedNoRelay when no relay is connected, the same test sendRumor applies to the recipient copy. * test(dm): pin the retry-charge paths that no test could fail on Review of the branch found charge decisions nothing guarded: a full send that reached no relay, two callers joining one failing self-copy, a replay abandoned when the session ends, the charge after a failed status write, and the interrupted arm's charge for a dispatch that threw. Each now has a test, checked by removing the code it guards. The sweep's two "does not use up a retry" tests for no-relay results are removed: the sweep no longer charges any result, so they could not fail, and the repository test now holds that rule. Sweep comments and two dartdocs that still described the sweep as the charger are corrected. * fix(dm): let a Retry on a sent-but-not-copied message re-arm the sweep Now that a failed manual retry is charged, the Retry on a message that reached the recipient but not the sender's other devices could use up the row's budget. That Retry is a one-shot snackbar: once the user leaves the thread nothing else re-drives the row, so after the sweep gave up the copy would never reach those devices. The collaborator invite banner had the same no-reset call. recoverSelfWrap takes the same resetRetryBudget flag as recoverFullSend, sharing its owner-checked reset, and both buttons pass it: a tap hands the row back to the sweep with a fresh budget and is then charged like any attempt, as Resend already does. * chore(ci): re-run roborev review, no code change roborev returned "Review unavailable" for e393334 during the repo-wide outage. This commit only gives it a new head to review; the tree is identical to e393334.
fix(clips): observe preview player futures (#10050) * fix(clips): observe preview player futures * test(clips): pin a preview source that fails to resolve * test(clips): restore unrelated formatting in the preview test Three existing tests were reflowed by the previous commit with no behavior change. Both layouts pass dart format, so the churn only muddies blame on tests this PR does not touch. * test(clips): pin that preview player failures are logged The preview converted four discarded player futures to runDetached, but only the initialization path had a test, and it asserted nothing about the failure being observed. Cover dismissal and a failed native setup, each with a rejecting dispose, and assert the failure is logged rather than leaked. Each converted call site now fails a test when it goes back to being discarded. --------- Co-authored-by: Daniel Cadenas <dcadenas@gmail.com> Co-authored-by: Meylis <meylis@divine.video>
fix(clips): observe preview player futures (#10050) * fix(clips): observe preview player futures * test(clips): pin a preview source that fails to resolve * test(clips): restore unrelated formatting in the preview test Three existing tests were reflowed by the previous commit with no behavior change. Both layouts pass dart format, so the churn only muddies blame on tests this PR does not touch. * test(clips): pin that preview player failures are logged The preview converted four discarded player futures to runDetached, but only the initialization path had a test, and it asserted nothing about the failure being observed. Cover dismissal and a failed native setup, each with a rejecting dispose, and assert the failure is logged rather than leaked. Each converted call site now fails a test when it goes back to being discarded. --------- Co-authored-by: Daniel Cadenas <dcadenas@gmail.com> Co-authored-by: Meylis <meylis@divine.video>
feat(supporters): show membership and optional profile badges (#9413) * feat(supporters): acknowledge membership and show optional profile badges * fix(supporters): support compact profile entries and isolate fixtures * test(supporters): pin public badge to the listed account and its opt-in Removing the pubkey match or the haloVisible check left every test green. Claude-Session: https://claude.ai/code/session_01FscwT5Nw4noChSRnGbbybo * test(profile): pin supporter chip placement on own, other and vanished profiles Showing the private membership chip on every profile, or either chip on a vanished account, left every test green. Claude-Session: https://claude.ai/code/session_01FscwT5Nw4noChSRnGbbybo * test(supporters): pin shared in-flight refresh requests Removing the in-flight sharing in refreshFromServer left every test green. Claude-Session: https://claude.ai/code/session_01FscwT5Nw4noChSRnGbbybo * fix(supporters): end a recognition save that fails unexpectedly --------- Co-authored-by: Matthew Bradley <mbradley@gmail.com> Co-authored-by: Daniel Cadenas <dcadenas@gmail.com>
fix(account): prevent signer failures from partially deleting accounts ( #8357) * fix(account): finish signing before account deletion starts Prepare every kind-5 fallback event and the kind-62 vanish before publishing any irreversible request. This keeps the vanish timestamp after the fallback events while ensuring a signer failure cannot leave a partial deletion behind.\n\nPin that unconfirmed vanish failures never destroy local keys.\n\nRefs #6215 * test(account): make the sign-before-publish assertion able to fail The test named for this change's central claim passes on the code it is meant to guard. Checked out against origin/main it goes green, because asserting sign order and publish order as two separate lists is satisfied by the sign/publish/sign/publish interleaving the change removes: both lists read [5, 5, 62] either way. Recording one interleaved timeline instead fails on origin/main with ['sign:5', 'publish:5', 'sign:5', 'publish:5', 'sign:62', 'publish:62'] and passes here. The timestamp assertion it replaces could not fail either. created_at came from the stub's own append-only counter (100 + signedEvents.length), so "last signature is not older than the earlier ones" was true whatever the service did. Production never sets created_at here — SignerFactory does, via NostrTimestamp.getDriftToleranceForKind — and that invariant is already pinned by nostr_timestamp_test.dart's "should not backdate Kind 62 deletion boundaries". Drops the createTestEvent createdAt parameter, which only existed to feed that counter. --------- Co-authored-by: Meylis <meylis@divine.video>
refactor(profile): finish endpoint injection and fix NIP-98 URL canon… …icalization (#7696) * fix(profile): canonicalize injected endpoints so NIP-98 signing holds The `u` tag is signed from the base URL verbatim while the request URL is built with `Uri.parse`, which lowercases the host, drops a default port and resolves dot segments. divine-name-server compares the two as raw strings (src/middleware/nip98.ts), so a non-canonical injected base made every claim and release 401 while the unauthenticated /check kept returning 200 — a misleading signal that only appears once someone actually injects a base, which is what #3367 added the parameter for. `_stripTrailingSlash` only handled a trailing slash. `_normalizeEndpoint` canonicalizes first, then strips, because canonicalization can reintroduce a trailing slash (`https://x/.` -> `https://x/`). Verified against the three divergence classes; the production defaults are already canonical, so behavior there is byte-identical. Refs #3367 * test(profile): assert injected endpoints instead of production URLs Acceptance criterion 4 of #3367. The username tests asserted the production name-server and Keycast hosts, so they could not distinguish "the repository used the injected endpoint" from "the repository used its default" — the one thing the injection parameters exist to make testable. All 14 sites in profile_repository_test.dart are served by a single shared setUp repository, so this is one construction change plus the literals; the two sibling files get the same treatment. The fakes are distinct per backend (names.test.example vs login.test.example) so a call site that reads the wrong endpoint fails loudly rather than passing by coincidence. The two `production defaults` tests in profile_repository_endpoints_test.dart deliberately keep asserting the production literals — they are what makes acceptance criterion 1 falsifiable. Closes #3367 * test(profile): finish pending-save endpoint injection --------- Co-authored-by: Liz Sweigart <127434495+NotThatKindOfDrLiz@users.noreply.github.com>
PreviousNext