Skip to content

Tags: divinevideo/divine-mobile

Tags

1.0.26-beta.production.497811ce593a723d152442541f70c0c1961dd2a8

Toggle 1.0.26-beta.production.497811ce593a723d152442541f70c0c1961dd2a8's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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.

1.0.26-beta.production.fce7a4f943bdb1c9cf01ceb8eb457a0c3c4d8fcd

Toggle 1.0.26-beta.production.fce7a4f943bdb1c9cf01ceb8eb457a0c3c4d8fcd's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>

1.0.26-beta.production.43e5fef66260ce6d4c9719634268130ba8bc70ca

Toggle 1.0.26-beta.production.43e5fef66260ce6d4c9719634268130ba8bc70ca's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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.

1.0.25

Toggle 1.0.25's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(navigation): remove empty header on profile and notifications (#1…

…0297)

1.0.25-beta.production.0c54393dd0867afe9cb495d6662d3210c14f4faa

Toggle 1.0.25-beta.production.0c54393dd0867afe9cb495d6662d3210c14f4faa's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(navigation): remove empty header on profile and notifications (#1…

…0297)

1.0.24

Toggle 1.0.24's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>

1.0.24-beta.production.dd813da229072ceb4f9b0351c90fb47301847e45

Toggle 1.0.24-beta.production.dd813da229072ceb4f9b0351c90fb47301847e45's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>

1.0.23

Toggle 1.0.23's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>

1.0.22

Toggle 1.0.22's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>

1.0.20

Toggle 1.0.20's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>