Skip to content

Develop - #796

Merged
tentamdin merged 98 commits into
mainfrom
develop
Sep 21, 2026
Merged

tentamdin merged 98 commits into
mainfrom
develop

Conversation

@tentamdin

Copy link
Copy Markdown
Member

🆕 New Features

🛠 Improvements & Fixes

🌐 Localization

tentamdin and others added 30 commits September 8, 2026 11:25
Joined button opens a sheet with per-group chat and content push toggles
and a confirmed Leave group action. Master switch off now unregisters the
push device and greys out the toggles. Route EVENT_REMINDER pushes to the
event, and skip the foreground banner for the open chat room.
Claiming the active room from initState and dispose tripped Riverpod's
lifecycle guard and red-screened the chat screen. Use a plain holder
object instead; push suppression reads it the same way.
Master switch flips during an in-flight register or unregister could leave
the device registered after opt-out, or lose the id of a fresh registration.
Route both through one reconcile loop that re-reads state after each pass.
Track open chat rooms as a stack so popping one chat re-mutes the one under
it.
A master-switch opt-out whose DELETE failed offline left the device
registered until some unrelated event reconciled again. Retry failed
passes with linear backoff up to five times; an explicit request cancels
a pending retry.
- Added support for selecting multiple messages in the group chat thread.
- Introduced a pill UI for reactions that appears above selected messages.
- Implemented functionality to copy selected messages and handle message deletion.
- Updated the GroupChatThread widget to manage selection state and provide callbacks for selection changes.
- Enhanced the ChatMessageParentDTO to handle deleted messages and ensure proper serialization.
- Added tests for new selection and copy functionalities, ensuring correct behavior for various message states.
- Removed the deprecated group_chat_message_menu_test as its functionality is now integrated into the main thread.
…languages and adjust sharing functionality in the poem sharing utility. Update Podfile.lock with new dependency checksums.
- Introduced _deletedOriginals to track deleted messages for accurate quoting.
- Updated _applyPendingDeletions to handle deleted originals.
- Added deleteInFlight state to prevent duplicate delete requests.
- Implemented chatSelectionStaleIds to manage stale selections.
- Enhanced tests for deletion scenarios and selection state.
…_providers.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
tentamdin and others added 23 commits September 18, 2026 00:45
The curve animation was never disposed, leaking on every reader and live
plan-details screen that mounts the header.
chore(l10n): sync translations from Tolgee
The bell was silent with the screen off on iOS: the app is suspended a
moment after locking, so the Dart timer never reached the bell. Hold the
audio session open for the length of a session — the app already declares
the `audio` background mode — by looping a silent track, which keeps the
isolate ticking so the real bell plays at the start and at the end.
Category playback (heard with the ringer switch on silent) with
mixWithOthers, so music the user already has playing is left alone.
Android is a no-op there; its timers keep firing with the screen off.

Scheduled notifications stay as a backstop for a suspended app, but now
disarm themselves two seconds before the bell when a tick proves the app
is still alive — which also removes the double ring on Android, where
both used to fire at the deadline. Locking during the pre-roll arms both
bells up front, and a session that starts while locked counts from when
the pre-roll was due rather than from when the screen came back.

Wait for playback to finish before releasing the keep-alive: the release
tears down the session the bell is playing through, which silenced the
completion bell on a locked screen. Verify a scheduled bell is actually
pending afterwards — iOS keeps only the 64 soonest — and fall back to the
in-app bell when the OS dropped it.

Also: retry a bell schedule that failed on the next lifecycle event, move
both bells onto one timer_bell channel and delete the silent channels it
replaces, queue the end-of-session cancel behind in-flight schedules so a
late one cannot leave a bell armed, and restore the lockfiles and the
formatting the previous commits churned.
Pausing and immediately resuming fired stop() and start() without either
awaiting or re-reading the wanted flag, so a release could restore the
app's audio configuration on top of the hold that replaced it and leave
the session unheld — a suspended app and a missed bell on a locked iOS
screen. Queue the two, re-check after every platform call, and let a
failed hold be retried by a later start.
Update notification channels and timer handling
…how load errors

Serialize PATCHes per toggle so the backend ends on the value shown.
Release the active chat room while another page covers it. Show a
retry row instead of defaults when preferences fail to load.
Adopt develop's ForegroundPushFilter for banner suppression and drop the
branch's own active-room mechanism; keep the master switch, reminder
routing and preference work.
…target

A preference write that succeeded while a newer value was queued was
skipped entirely, so a later failure reverted the switch to a stale value
the backend no longer held. Record every confirmed value before checking
for a queued write.
…controls

feat(group_profile): add group notification toggles and leave sheet
A connect rejection usually carries only `detail`, with no code, so it
read as non-fatal and the reader reconnected into the same refusal for as
long as it stayed open. It is now fatal on its shape alone.

jumpToSegment matched the operator's segment id literally, ignoring the
segments' mappings, so following across languages worked only while the
live line happened to sit in the loaded window. Both checks now resolve
through the mappings.

The frame the server sends on connect is where the room already was, not
the operator moving on, but the two were indistinguishable — and since
that frame usually lands just after the reader's first build, it pushed
the user off the text they had opened (and marked it complete for plan
texts). The state now carries positionIsSnapshot, and the reader pauses
following instead of navigating.
Nothing marks a frame as the snapshot the server sends on connect, so it
is inferred from arriving before the grace window closes. In a session
with no stored position, an operator's first move inside that window
looks exactly the same — and pausing there stranded the user off the
recitation until they re-armed Sync by hand, since both the switch and
the scroll early-return once following is off.

A snapshot on another text of the sequence now falls through instead:
the user stays on the text they opened and keeps following, so the next
move carries them along. Pausing is left to the unambiguous case, a
position already live before the screen had rendered.
@greptile-apps

greptile-apps Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 3/5

The PR is not yet safe to merge because timer deadlines can become silent, and the previously reported online-attendance live-sync issue remains unresolved.

Findings

  1. P1 Timer boundary can be silent
  2. P1 Online attendance enables live sync
  3. P1 Closed prayer room reconnects
Fix with agent prompt
### Issue 1
lib/features/timer/presentation/screens/active_timer_screen.dart:334-337
If cancellation wins the race with OS alarm delivery when a tick reaches the deadline, `_reclaimBellIfDue` cancels the pending exact alarm while the earlier `wasExact` snapshot suppresses the in-app bell. Neither bell then fires, so the timer starts or completes silently. The start-bell path has the same ordering at lines 281–287.

### Issue 2
lib/features/group_profile/presentation/screens/group_event_detail_screen.dart:399-406
For a hybrid event backed directly by a plan, this branch passes `eventId` to the preview regardless of the selected attendance mode. Online attendees therefore enable reader live-sync, while the series path intentionally enables it only for in-person attendees because online users receive the text through the video stream.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 3
lib/features/group_chat/presentation/providers/prayer_requests_providers.dart:282-284
`markClosed` changes only the status and leaves `state.roomId` intact. After the sheet clears its local room ID, the resulting rebuild reads this stale ID and calls `_syncRoom`, reconnecting to the room the server just closed and potentially refreshing it again.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This update addresses the two recently reviewed lifecycle races by guarding closed prayer rooms against reconnection and preserving exact-alarm state before timer handoff. The prayer-room correction is complete, but the timer correction creates a cancellation-versus-delivery race that can suppress both bell mechanisms.

  • Prevents a closed prayer room from reconnecting after its state rebuilds.
  • Reads exact-alarm state before reclaiming start and completion alarms.
  • Adds lifecycle coverage for a timer tick delayed until the deadline.
  • The previously reported direct-plan attendance issue remains outstanding: the plan-preview branch still passes eventId for online attendees and therefore enables live synchronization.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Timer tick reaches deadline] --> B[Snapshot wasExact]
  B --> C[Reclaim and cancel exact OS alarm]
  C --> D{wasExact?}
  D -->|No| E[Play in-app bell]
  D -->|Yes| F[Suppress in-app bell]
  F --> G{OS alarm already delivered?}
  G -->|Yes| H[Bell heard once]
  G -->|No; cancellation won| I[Silent timer boundary]
Loading

Reviews (2) · Last reviewed commit: "Fix duplicate timer bell and closed pray..."

Comment thread lib/features/timer/presentation/screens/active_timer_screen.dart Outdated
Comment on lines 399 to +406
if (seriesId != null) {
await _enterSeries(event, seriesId);
await _enterSeries(
event,
seriesId,
showLiveStream: participation == GroupEventParticipationType.online,
);
} else {
await _openPlanPreview(planId!);
await _openPlanPreview(planId!, eventId: event.id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Online attendance enables live sync

For a hybrid event backed directly by a plan, this branch passes eventId to the preview regardless of the selected attendance mode. Online attendees therefore enable reader live-sync, while the series path intentionally enables it only for in-person attendees because online users receive the text through the video stream.

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/features/group_profile/presentation/screens/group_event_detail_screen.dart
Line: 399-406

Comment:
**Online attendance enables live sync**

For a hybrid event backed directly by a plan, this branch passes `eventId` to the preview regardless of the selected attendance mode. Online attendees therefore enable reader live-sync, while the series path intentionally enables it only for in-person attendees because online users receive the text through the video stream.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +282 to +284
void markClosed() {
state = state.copyWith(roomStatus: PrayerRoomStatus.closed);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Closed prayer room reconnects

markClosed changes only the status and leaves state.roomId intact. After the sheet clears its local room ID, the resulting rebuild reads this stale ID and calls _syncRoom, reconnecting to the room the server just closed and potentially refreshing it again.

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/features/group_chat/presentation/providers/prayer_requests_providers.dart
Line: 282-284

Comment:
**Closed prayer room reconnects**

`markClosed` changes only the status and leaves `state.roomId` intact. After the sheet clears its local room ID, the resulting rebuild reads this stale ID and calls `_syncRoom`, reconnecting to the room the server just closed and potentially refreshing it again.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +334 to +337
final wasExact = _completionBell.isExactFor(_endsAt!);
_reclaimBellIfDue(_completionBell, remainingMs, _cancelCompletionBell);
if (remainingMs <= 0) {
_completeSession(playBell: true);
_completeSession(playBell: !wasExact);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Timer boundary can be silent

If cancellation wins the race with OS alarm delivery when a tick reaches the deadline, _reclaimBellIfDue cancels the pending exact alarm while the earlier wasExact snapshot suppresses the in-app bell. Neither bell then fires, so the timer starts or completes silently. The start-bell path has the same ordering at lines 281–287.

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/features/timer/presentation/screens/active_timer_screen.dart
Line: 334-337

Comment:
**Timer boundary can be silent**

If cancellation wins the race with OS alarm delivery when a tick reaches the deadline, `_reclaimBellIfDue` cancels the pending exact alarm while the earlier `wasExact` snapshot suppresses the in-app bell. Neither bell then fires, so the timer starts or completes silently. The start-bell path has the same ordering at lines 281–287.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@tentamdin
tentamdin merged commit 3da6b3d into main Sep 21, 2026
2 of 5 checks passed

This branch was successfully deployed

No deployments
development 988adccd Deployed Sep 21, 2026 by tentamdin via android / Android • dev #176
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.

4 participants