Skip to content

feat: Export statements, send feedback and show the tracking streak - #223

Draft
austin047 wants to merge 2 commits into
devfrom
feat/export-and-feedback
Draft

austin047 wants to merge 2 commits into
devfrom
feat/export-and-feedback

Conversation

@austin047

@austin047 austin047 commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator
  • Reports: export the financial statement (PDF, Excel, CSV) for the selected period via reports/export.
  • Feedback: send feedback and see past submissions via /feedback.
  • The export save/share sheet is now a shared ExportResultListener, reused by history and reports.
  • Streaks: the home screen shows the daily tracking streak

Backend dependency: GET /streaks is not available for

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

@sourceant

sourceant Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Code Review Summary

Reports gain PDF, Excel, and CSV statement export for the selected period via reports/export, plus a settings feedback screen that sends and lists past submissions through /feedback. The save/share sheet is extracted into a shared ExportResultListener reused by history and reports. Signed-in users see a tappable home streak chip from GET /streaks, hidden until the server responds and refreshed after sync.

🚀 Key Improvements

  • Shared ExportResultListener removes duplicated save/share sheet handling in history and reports.
  • Streak data refreshes after each sync, since a transaction counts once the server has it.
  • Feedback messages are trimmed and validated, and the chosen type persists between sends.

💡 Minor Suggestions

  • The early return silently drops the refresh that _StreakChipView fires when a sync completes, if the initial load is still in flight, so the chip keeps a stale count until the next sync. Record a pending reload and run it when the current load finishes instead of returning.
  • Subtracting a fixed Duration from a local midnight shifts the calendar date whenever a DST transition falls inside the window (e.g. a 30-day window spanning March), so the statement silently starts one day early and covers more days than the figures on screen. Use calendar arithmetic instead.
  • loadFeedback awaits the request and then emits, but the cubit can already be closed if the screen is left while the request is in flight; emit then throws StateError: Cannot emit new states after calling close. Bail out with if (isClosed) return; after the await, before folding.
  • Submitting and immediately leaving the screen closes the cubit, so the emits below throw StateError after await and the result is lost as an unhandled error. Add the same closed-cubit guard before folding.
  • submitFeedback also emits only after awaiting the network call, so a send that finishes after the screen is disposed emits into a closed cubit and throws. Guard the fold with the same closed-cubit early return used for loading.
  • Review coverage is incomplete. Inspect the execution details before approving.

@austin047
austin047 marked this pull request as ready for review October 5, 2026 08:31
@austin047 austin047 changed the title feat: Export the financial statement and send feedback from the app feat: Export statements, send feedback and show the tracking streak Oct 8, 2026
Add a statement export (PDF, Excel, CSV) to the reports screen, covering
the selected period, through the same guarded path as the transaction
export. The save/share sheet moves into a shared ExportResultListener.

Add a feedback screen under Settings for signed-in users that posts to
/feedback and lists past submissions with their status.
Read the user's streaks from GET /streaks and show the daily tracking
streak as a chip beside the Transactions heading, lit once it is running.
Tapping it opens a sheet with the tracking and check-in streaks, daily and
weekly, each beside its best run.

Streaks are counted on the server, so the chip is for signed-in users
only, stays hidden until the server answers, and keeps the last good
numbers when a refresh fails. A transaction counts once it reaches the
server, so the chip refreshes after each sync. Rows of a type or period
this build does not know are skipped.
@austin047
austin047 force-pushed the feat/export-and-feedback branch from c6c7fd8 to a70b64a Compare October 9, 2026 09:12

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

StreakCubit(this._getStreaks) : super(StreakState.initial());

Future<void> loadStreaks() async {
if (state.isLoading) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The early return silently drops the refresh that _StreakChipView fires when a sync completes, if the initial load is still in flight, so the chip keeps a stale count until the next sync. Record a pending reload and run it when the current load finishes instead of returning.


cubit.exportStatement(
format: format,
start: today.subtract(Duration(days: _periodDays - 1)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Subtracting a fixed Duration from a local midnight shifts the calendar date whenever a DST transition falls inside the window (e.g. a 30-day window spanning March), so the statement silently starts one day early and covers more days than the figures on screen. Use calendar arithmetic instead.

Suggested change
start: today.subtract(Duration(days: _periodDays - 1)),
start: DateTime(today.year, today.month, today.day - (_periodDays - 1)),

loadFailure: const Failure.none(),
));

final result = await _getFeedback(NoParams());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

loadFeedback awaits the request and then emits, but the cubit can already be closed if the screen is left while the request is in flight; emit then throws StateError: Cannot emit new states after calling close. Bail out with if (isClosed) return; after the await, before folding.

Suggested change
final result = await _getFeedback(NoParams());
final result = await _getFeedback(NoParams());
if (isClosed) return;
result.fold(

));

final trimmedSubject = subject?.trim();
final result = await _submitFeedback(SubmitFeedbackUseCaseParams(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Submitting and immediately leaving the screen closes the cubit, so the emits below throw StateError after await and the result is lost as an unhandled error. Add the same closed-cubit guard before folding.

Suggested change
final result = await _submitFeedback(SubmitFeedbackUseCaseParams(
final result = await _submitFeedback(SubmitFeedbackUseCaseParams(
type: type,
subject: trimmedSubject == null || trimmedSubject.isEmpty
? null
: trimmedSubject,
message: message.trim(),
));
if (isClosed) return;
result.fold(

message: message.trim(),
));

result.fold(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

submitFeedback also emits only after awaiting the network call, so a send that finishes after the screen is disposed emits into a closed cubit and throws. Guard the fold with the same closed-cubit early return used for loading.

@austin047
austin047 marked this pull request as draft October 9, 2026 09:28
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.

2 participants