Skip to content

feat: add timestamp to P2P chat message bubbles#565

Merged
grunch merged 4 commits into
mainfrom
chat-timestamp
Apr 8, 2026
Merged

feat: add timestamp to P2P chat message bubbles#565
grunch merged 4 commits into
mainfrom
chat-timestamp

Conversation

@Catrya

@Catrya Catrya commented Apr 6, 2026

Copy link
Copy Markdown
Member

fix #562

  • Display local time inside each message bubble in the P2P chat
  • Timestamp styled inline at bottom-right corner
  • Applies to all message types: text, image, and file
  • Bubble width adapts to content size, no unnecessary expansion

Summary by CodeRabbit

  • New Features

    • Messages now display localized timestamps beneath message content for both text and media messages.
  • Style

    • Timestamps use a smaller, semi-transparent style with adjusted spacing and alignment for cleaner message bubbles.
    • Timestamp formatting respects device clock preferences (12/24-hour) for consistent display.

  - Display local time inside each message bubble in the P2P chat
  - Timestamp styled inline at bottom-right corner
  - Applies to all message types: text, image, and file
  - Bubble width adapts to content size, no unnecessary expansion
@coderabbitai

coderabbitai Bot commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@Catrya has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 50 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 9 minutes and 50 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ecda34b0-1610-4f2a-bbc0-00450c037484

📥 Commits

Reviewing files that changed from the base of the PR and between ca34c90 and 0c3a8a6.

📒 Files selected for processing (1)
  • lib/features/chat/widgets/message_bubble.dart

Walkthrough

Added localized time-of-day timestamps to chat and dispute message bubbles; message content widgets are now wrapped in a Column with a conditional timestamp rendered beneath using intl DateFormat. Removed prior datetime-extension usage.

Changes

Cohort / File(s) Summary
Chat message bubble
lib/features/chat/widgets/message_bubble.dart
Added package:intl/intl.dart; introduced _formatTime() to format createdAt (accepts int Unix seconds or DateTime, returns '' if null) and _buildTimestamp() to render a small semi-transparent timestamp; message content (text/image/file) now placed in a Column with the timestamp under it.
Dispute message bubble
lib/features/disputes/widgets/dispute_message_bubble.dart
Replaced timeAgoWithLocale with explicit DateFormat clock formatting (honors 24h setting), adjusted styles and padding for the timestamp, wrapped multimedia/text bubbles in a Column and appended the timestamp; removed datetime-extension import.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • arkanoider
  • AndreaDiazCorreia

Poem

🐰
I nudged the bubbles, soft and bright,
tucked tiny clocks beneath each light.
Pictures, files, and words now show the hour,
a quiet hop that adds a little power.
Time stamps tucked in — a rabbit's gentle rite.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding timestamps to P2P chat message bubbles, which aligns with the changeset modifications.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #562: timestamps are added to chat messages using DateFormat with device 12h/24h preference support across text, image, and file message types.
Out of Scope Changes check ✅ Passed All changes in both files (message_bubble.dart and dispute_message_bubble.dart) are directly related to adding timestamp functionality to chat bubbles, with no out-of-scope modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chat-timestamp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (2)
lib/features/chat/widgets/message_bubble.dart (2)

64-77: Optional: Extract repeated Column wrapper pattern.

The same Column structure (with CrossAxisAlignment.end, MainAxisSize.min, content widget + timestamp) is repeated three times for image, file, and text messages. Consider extracting a helper method to reduce duplication.

♻️ Example helper extraction
Widget _wrapWithTimestamp(Widget content, BuildContext context) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.end,
    mainAxisSize: MainAxisSize.min,
    children: [
      content,
      _buildTimestamp(context),
    ],
  );
}

Then replace each occurrence with:

child: _wrapWithTimestamp(
  EncryptedImageMessage(...),
  context,
),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/chat/widgets/message_bubble.dart` around lines 64 - 77, Extract
the repeated Column wrapper used around message content and timestamp into a
helper like _wrapWithTimestamp(Widget content, BuildContext context) that
returns a Column with crossAxisAlignment: CrossAxisAlignment.end and
mainAxisSize: MainAxisSize.min and contains the passed content and
_buildTimestamp(context); then replace the three repeated Column usages (the
ones wrapping EncryptedImageMessage, file message widget, and text message
widget) with calls to _wrapWithTimestamp(content, context) to remove duplication
and keep calls to chatNotifier getters unchanged.

185-207: Pass locale to DateFormat for proper i18n support.

DateFormat.Hm() without a locale argument falls back to the system default rather than the app's selected locale. To ensure the time format matches the user's language setting, pass the locale from BuildContext.

♻️ Suggested refactor
- String _formatTime() {
+ String _formatTime(String locale) {
    if (message.createdAt == null) return '';
    final date = message.createdAt is int
        ? DateTime.fromMillisecondsSinceEpoch(
            (message.createdAt as int) * 1000)
        : message.createdAt as DateTime;
-   return DateFormat.Hm().format(date.toLocal());
+   return DateFormat.Hm(locale).format(date.toLocal());
  }

- Widget _buildTimestamp() {
-   final time = _formatTime();
+ Widget _buildTimestamp(BuildContext context) {
+   final locale = Localizations.localeOf(context).toString();
+   final time = _formatTime(locale);
    if (time.isEmpty) return const SizedBox.shrink();
    return Padding(
      padding: const EdgeInsets.only(top: 4),
      child: Text(
        time,
        style: TextStyle(
          color: AppTheme.cream1.withValues(alpha: 0.6),
          fontSize: 11,
        ),
      ),
    );
  }

Then update calls to _buildTimestamp(context) in the build method.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/chat/widgets/message_bubble.dart` around lines 185 - 207, The
timestamp formatting currently uses DateFormat.Hm() without a locale; update
_formatTime to accept a BuildContext (or a locale String) and pass the app
locale from context into DateFormat.Hm(locale) so time respects i18n; then
change _buildTimestamp to take BuildContext and call _formatTime(context) and
update all calls to _buildTimestamp(...) accordingly (reference functions:
_formatTime, _buildTimestamp) ensuring you import/use the correct locale from
the BuildContext.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/features/chat/widgets/message_bubble.dart`:
- Around line 64-77: Extract the repeated Column wrapper used around message
content and timestamp into a helper like _wrapWithTimestamp(Widget content,
BuildContext context) that returns a Column with crossAxisAlignment:
CrossAxisAlignment.end and mainAxisSize: MainAxisSize.min and contains the
passed content and _buildTimestamp(context); then replace the three repeated
Column usages (the ones wrapping EncryptedImageMessage, file message widget, and
text message widget) with calls to _wrapWithTimestamp(content, context) to
remove duplication and keep calls to chatNotifier getters unchanged.
- Around line 185-207: The timestamp formatting currently uses DateFormat.Hm()
without a locale; update _formatTime to accept a BuildContext (or a locale
String) and pass the app locale from context into DateFormat.Hm(locale) so time
respects i18n; then change _buildTimestamp to take BuildContext and call
_formatTime(context) and update all calls to _buildTimestamp(...) accordingly
(reference functions: _formatTime, _buildTimestamp) ensuring you import/use the
correct locale from the BuildContext.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 750f3026-7be6-462c-b5fa-1e6912f1d75c

📥 Commits

Reviewing files that changed from the base of the PR and between 856469e and fb4498a.

📒 Files selected for processing (1)
  • lib/features/chat/widgets/message_bubble.dart

  - Replace relative time ago format with HH:mm local time
  - Unify timestamp style with P2P chat
  - Add timestamps to multimedia messages (image and file)
  - Bubble width adapts to content sizefeat: unify dispute chat timestamps with P2P chat style
  - Replace relative time ago format with HH:mm local time
  - Unify timestamp style with P2P chat
  - Add timestamps to multimedia messages (image and file)
  - Bubble width adapts to content size
@Catrya

Catrya commented Apr 7, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
lib/features/disputes/widgets/dispute_message_bubble.dart (1)

103-116: Consider separate alignment for text content vs timestamp.

Using CrossAxisAlignment.end right-aligns both the message text and the timestamp. While this correctly positions the timestamp at the bottom-right, it also right-aligns the message content, which may look visually odd for multi-line text messages.

A more conventional approach would keep text left-aligned while only the timestamp is right-aligned:

💡 Proposed layout refinement
         child: Column(
-          crossAxisAlignment: CrossAxisAlignment.end,
+          crossAxisAlignment: CrossAxisAlignment.stretch,
           mainAxisSize: MainAxisSize.min,
           children: [
             Text(
               message.content,
               style: const TextStyle(
                 color: AppTheme.cream1,
                 fontSize: 16,
                 height: 1.4,
               ),
             ),
-            _buildTimestamp(),
+            Align(
+              alignment: Alignment.centerRight,
+              child: _buildTimestamp(),
+            ),
           ],
         ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/disputes/widgets/dispute_message_bubble.dart` around lines 103 -
116, The Column currently uses CrossAxisAlignment.end which right-aligns both
the message text and timestamp; change the Column to CrossAxisAlignment.start so
the message.content remains left-aligned and keep the timestamp right-aligned by
wrapping the timestamp widget returned by _buildTimestamp() in an
Align(alignment: Alignment.centerRight) (or place it in a Row with Expanded
spacer) so only the timestamp floats to the right while the text stays left.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/features/disputes/widgets/dispute_message_bubble.dart`:
- Around line 103-116: The Column currently uses CrossAxisAlignment.end which
right-aligns both the message text and timestamp; change the Column to
CrossAxisAlignment.start so the message.content remains left-aligned and keep
the timestamp right-aligned by wrapping the timestamp widget returned by
_buildTimestamp() in an Align(alignment: Alignment.centerRight) (or place it in
a Row with Expanded spacer) so only the timestamp floats to the right while the
text stays left.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fd87ed07-a5cb-4dc7-a85c-a725f186e709

📥 Commits

Reviewing files that changed from the base of the PR and between fb4498a and 12b5357.

📒 Files selected for processing (1)
  • lib/features/disputes/widgets/dispute_message_bubble.dart

  - Use MediaQuery.alwaysUse24HourFormatOf to read OS time format preference
  - Show HH:mm (14:30) or h:mm a (2:30 PM) based on device setting
  - Applied to both P2P and dispute chat message bubbles

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
lib/features/chat/widgets/message_bubble.dart (1)

185-193: Dead code: createdAt is int branch is unreachable.

According to lib/data/models/nostr_event.dart:369-373, createdAt is always constructed as a DateTime during deserialization (DateTime.fromMillisecondsSinceEpoch((event['created_at'] as int) * 1000)). The is int type check and its corresponding branch will never execute.

♻️ Simplify by removing the dead branch
   String _formatTime(BuildContext context) {
     if (message.createdAt == null) return '';
-    final date = message.createdAt is int
-        ? DateTime.fromMillisecondsSinceEpoch(
-            (message.createdAt as int) * 1000)
-        : message.createdAt as DateTime;
+    final date = message.createdAt!;
     final use24h = MediaQuery.alwaysUse24HourFormatOf(context);
     return DateFormat(use24h ? 'HH:mm' : 'h:mm a').format(date.toLocal());
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/chat/widgets/message_bubble.dart` around lines 185 - 193, The
_formatTime method contains a dead branch checking "message.createdAt is int"
because createdAt is always a DateTime; remove the int branch and simplify date
assignment to cast message.createdAt to DateTime directly (e.g., DateTime date =
message.createdAt as DateTime), leaving the
MediaQuery.alwaysUse24HourFormatOf(context) logic and DateFormat unchanged;
update any null check for message.createdAt to keep early return if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/features/chat/widgets/message_bubble.dart`:
- Around line 185-193: The _formatTime method contains a dead branch checking
"message.createdAt is int" because createdAt is always a DateTime; remove the
int branch and simplify date assignment to cast message.createdAt to DateTime
directly (e.g., DateTime date = message.createdAt as DateTime), leaving the
MediaQuery.alwaysUse24HourFormatOf(context) logic and DateFormat unchanged;
update any null check for message.createdAt to keep early return if needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78e92e6d-887f-4fd0-8a00-41d0c63ae92f

📥 Commits

Reviewing files that changed from the base of the PR and between 12b5357 and ca34c90.

📒 Files selected for processing (2)
  • lib/features/chat/widgets/message_bubble.dart
  • lib/features/disputes/widgets/dispute_message_bubble.dart

@mostronatorcoder mostronatorcoder Bot left a comment

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.

Review

Two-file change: adds localized time stamps to message bubbles in P2P chat and dispute chat.

lib/features/chat/widgets/message_bubble.dart

  • DateFormat with alwaysUse24HourFormatOf(context) correctly respects the device preference
  • withValues(alpha: 0.6) for timestamp opacity — consistent with the design
  • mainAxisSize: MainAxisSize.min prevents unnecessary bubble expansion
  • Padding(top: 4) instead of SizedBox(height: 6) — more idiomatic
  • Unused datetime_extensions_utils.dart import removed
  • message.createdAt is always DateTime post-deserialization (nostr_event.dart)

lib/features/disputes/widgets/dispute_message_bubble.dart

  • message.timestamp is DateTime in DisputeChat model — .toLocal() works correctly
  • Text messages: CrossAxisAlignment.end + mainAxisSize: MainAxisSize.min — consistent with the bottom-right design goal
  • CrossAxisAlignment.end on media bubbles vs the old CrossAxisAlignment.start — intentional design choice per the PR objective
  • Removed SizedBox(height: 6) and uses Padding(top: 4) — consistent with the other bubble

CodeRabbit nitpicks (non-blocking)

  1. Column wrapper repeated 3 times — valid observation; extractor helper is optional
  2. Text alignment on dispute bubbles — right-align on multi-line text is unconventional but intentional per the PR goal
  3. createdAt is int dead code — confirmed unreachable; createdAt is always DateTime after deserialization. Can be removed or left as a no-op defensive branch.

LGTM.

@grunch grunch 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.

LGTM

@grunch grunch merged commit a1fbf60 into main Apr 8, 2026
2 checks passed
@grunch grunch deleted the chat-timestamp branch April 8, 2026 13:33
@coderabbitai coderabbitai Bot mentioned this pull request Apr 19, 2026
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.

Show timestamps in chat messages

2 participants