Skip to content

Share Information with deep linking and Join Bottom Sheet.#167

Merged
anisha-e10 merged 21 commits intomainfrom
anisha/share-user-info
Nov 25, 2025
Merged

Share Information with deep linking and Join Bottom Sheet.#167
anisha-e10 merged 21 commits intomainfrom
anisha/share-user-info

Conversation

@anisha-e10
Copy link
Copy Markdown
Collaborator

@anisha-e10 anisha-e10 commented Nov 19, 2025

File (1)

File

Summary by CodeRabbit

  • New Features

    • Deep links and share links can carry a sharer and a personal message; these are saved to sheets and shown in previews.
  • Accessibility / UI

    • Share sheet adds a message input, scrollable layout with keyboard inset handling, emoji avatar support, and a shared-info card on join preview.
  • Localization

    • Added strings for message input, "Shared by", and "Message".
  • Tests

    • Expanded coverage for query params, encoding, trimming, persistence, UI rendering, and share flows.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 19, 2025

Walkthrough

Extracts sharedBy and message from deep links and share UI, threads them into deep-link generation and previews, persists them on SheetModel via updateSheetShareInfo, and surfaces them in sheet join/share UIs and updated tests.

Changes

Cohort / File(s) Summary
Deep Link & Init
lib/core/deeplink/deep_link_initializer.dart
Parse sharedBy and message query params from incoming URIs and forward them into navigation; conditionally persist share metadata via updateSheetShareInfo before showing join sheet or navigating.
Share utils & tests
lib/features/share/utils/share_utils.dart, test/features/share/utils/share_utils_test.dart
Rename linkPrefixUrlbaseUrl; add getSheetDeepLinkUrl(sheetId, sharedBy, message); replace getLinkPrefixUrl with getLinkPostfixUrl(endpoint, {queryParams}); include optional sharedBy/message query params in share links; tests updated for trimming, encoding, and null/empty cases.
Share sheet UI & tests
lib/features/share/widgets/share_items_bottom_sheet.dart, test/features/share/widgets/share_items_bottom_sheet_test.dart
Convert to stateful ConsumerStatefulWidget; add _userMessage state and message wiring; support scrolling/keyboard insets; persist sheet share info via sheetListProvider.notifier.updateSheetShareInfo when sharing; tests for trimming, persistence, and null-current-user flows.
Sheet share preview & tests
lib/features/share/widgets/sheet_share_preview_widget.dart, test/features/share/widgets/sheet_share_preview_widget_test.dart
Convert to stateful widget with TextEditingController; add ValueChanged<String>? onMessageChanged and AnimatedTextField for message input; update tests for controller lifecycle and input behavior.
Sheet join preview & tests
lib/features/share/widgets/sheet_join_preview_widget.dart, test/features/share/widgets/sheet_join_preview_widget_test.dart
Add conditional shared-info UI (GlassyContainer) showing optional sharedBy user chip and/or message; render description conditionally; tests validate presence/absence and rendering.
Sheet domain & providers + tests
lib/features/sheet/models/sheet_model.dart, lib/features/sheet/providers/sheet_providers.dart, lib/features/sheet/providers/sheet_providers.g.dart, test/features/sheet/providers/sheet_providers_test.dart
Add sharedBy and message to SheetModel (constructor, copyWith, preserved in removeCoverImage); add SheetList.updateSheetShareInfo to immutably update share metadata; generated provider hash updated; tests cover null/empty handling and isolation.
User lookup provider & generated
lib/features/users/providers/user_providers.dart, lib/features/users/providers/user_providers.g.dart
Add getUserByName provider (and generated family) to resolve users by name; generated provider scaffolding and hashes updated.
Animated TextField
lib/common/widgets/animated_textfield_widget.dart
Minor formatting/styling reflows in InputDecoration; no behavior or API changes.
Localization
lib/l10n/app_en.arb, lib/l10n/app_gu.arb, lib/l10n/app_hi.arb
Add localization keys and metadata: addAMessage, sharedBy, and message.
Tests (widget/unit)
test/features/share/widgets/*, test/features/sheet/providers/sheet_providers_test.dart
Add/update tests for UI rendering, message input behavior, persistence via provider, deep-link parsing, trimming/encoding, and unit tests for updateSheetShareInfo.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as ShareItemsBottomSheet
    participant Preview as SheetSharePreviewWidget
    participant Provider as SheetList
    participant OS as OS_Share
    participant DeepLink as DeepLinkInitializer
    participant Nav as Navigator

    User->>UI: open share sheet
    UI->>Preview: render preview + message input
    User->>Preview: type message
    Preview-->>UI: onMessageChanged(message)
    UI->>Provider: updateSheetShareInfo(sheetId, sharedBy?, message?)
    Provider-->>UI: sheet state updated
    UI->>OS: trigger share with link?sharedBy=...&message=...

    Note over User,DeepLink: Recipient opens link later
    Recipient->>DeepLink: open link (sheetId + query params)
    DeepLink->>Provider: updateSheetShareInfo(sheetId, sharedBy, message)
    DeepLink->>Nav: navigate to join-sheet or sheet view
    Nav-->>Recipient: show sheet with shared info
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to TextEditingController lifecycle and state wiring in SheetSharePreviewWidget and ShareItemsBottomSheet.
  • Verify URL encoding/decoding, trimming, and conditional inclusion of sharedBy/message in ShareUtils and deep-link parsing.
  • Confirm updateSheetShareInfo immutably preserves unrelated fields and generated provider hashes are consistent.

Possibly related PRs

Suggested reviewers

  • kumarpalsinh25

Poem

🐇
I stitched a name and note into a tiny link,
Hid a message in a sheet and waited for a blink.
It hops through previews, trims each little line,
Lands back on join-screen where shared memories shine —
The rabbit nods: "Sent — now watch it bind." ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding share information (sharedBy and message) with deep linking support and updates to the join bottom sheet UI.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch anisha/share-user-info

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.

@anisha-e10 anisha-e10 marked this pull request as ready for review November 20, 2025 11:28
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (13)
lib/features/share/widgets/sheet_join_preview_widget.dart (2)

83-84: Consider adding parentheses for clarity.

While the operator precedence is correct, the condition would be more readable with explicit parentheses to make the grouping immediately clear.

-          if (sharedBy != null && sharedBy.isNotEmpty ||
-              (message != null && message.trim().isNotEmpty)) ...[
+          if ((sharedBy != null && sharedBy.isNotEmpty) ||
+              (message != null && message.trim().isNotEmpty)) ...[

83-84: Inconsistent trim() usage for sharedBy checks.

Line 83 checks sharedBy.isNotEmpty without trim, while line 107 checks sharedBy.trim().isNotEmpty. This inconsistency could lead to showing an empty card if sharedBy contains only whitespace.

Apply this diff to use consistent trimming:

-          if (sharedBy != null && sharedBy.isNotEmpty ||
+          if (sharedBy != null && sharedBy.trim().isNotEmpty ||
               (message != null && message.trim().isNotEmpty)) ...[

Also applies to: 107-107

test/features/share/widgets/sheet_join_preview_widget_test.dart (1)

360-645: Shared Info Card tests are solid; consider scoping the finders

The new tests cover all important sharedBy/message combinations (including trimming and empty/whitespace cases) and verify styling, which is great. To make them more robust if additional GlassyContainer or ZoeUserChipWidget instances are added elsewhere in the tree, consider scoping the matchers, e.g. using find.descendant(of: find.byType(SheetJoinPreviewWidget), matching: find.byType(GlassyContainer)), rather than a global find.byType.

lib/features/share/utils/share_utils.dart (2)

17-27: getLinkPrefixUrl query param handling looks correct; small ergonomics tweak possible

Merging existing uri.queryParameters with the provided queryParams and returning the same 🔗-prefixed format preserves behavior for existing callers while enabling deep-link metadata. You could optionally move the isNotEmpty guard into this method (treat empty maps like null) so callers don’t need to compute queryParams.isNotEmpty ? queryParams : null, but that’s purely an ergonomics/refactor point.


30-65: Sheet share message logic is sound; confirm desired UX for user message visibility

The additions here look good: emoji-only when AvatarType.emoji, safe sheet null-check, and query param construction that trims userMessage and avoids sending whitespace-only values while omitting query params entirely when there’s nothing to add.

One thing to double-check from a product perspective: right now the user-entered userMessage only appears URL-encoded in the link (?message=...), not as a separate human-readable paragraph in the share text body. If users are expected to see the message directly in the shared text (even if the deep link isn’t opened), you may want to also append userMessage.trim() before the link. You might also consider trimming userName for consistency.

lib/features/share/widgets/sheet_share_preview_widget.dart (1)

52-145: contentText is no longer used in the build; consider removing or rendering it

The widget still requires a contentText argument but it isn’t referenced anywhere in build() now that the UI shows only the message input and stats. That can surprise callers and future readers. Either render the content preview (e.g., above the message field) or, if that responsibility has moved into the bottom sheet, consider dropping contentText from the API to avoid dead parameters.

test/features/share/widgets/share_items_bottom_sheet_test.dart (1)

78-415: Sheet share tests give strong coverage of sharedBy/message flows; one possible enhancement

The new sheet-share tests do a nice job of covering: basic UI (preview + SheetSharePreviewWidget), message typing via AnimatedTextField, persistence of sharedBy/message on the SheetModel (including trimming and empty-message cases), and the null-currentUser path where sharing still works but metadata isn’t updated. One gap is that includes user message in share content when provided currently only validates the text field/controller state; if the requirement is that the actual outbound share payload includes the message (not just as a query param), you could extend the platform share stub to capture the shared text and assert on it.

test/features/share/utils/share_utils_test.dart (2)

77-128: Query-param tests are thorough but tightly coupled to encoding format

The new getLinkPrefixUrl tests cover presence, empties, nulls, and special characters well. They do, however, hard-code expectations like sharedBy=John+Doe and specific % encodings, which tightly couples the tests to the exact encoding strategy (+ for spaces, order, etc.). If you ever change implementation details (e.g., switch to %20 for spaces or reorder params), these will fail even though behavior is still correct.

Consider parsing the link with Uri.parse and asserting on uri.queryParameters instead of raw string fragments to keep the tests focused on semantics rather than encoding representation and ordering.


139-155: Sheet share message tests nicely cover userName/userMessage combinations

The changes to pumpSheetShareUtilsWidget and the added tests around userName/userMessage (presence, null, empty, whitespace-only, trimming) give very solid coverage of the new behavior and edge cases, including the URL encoding of the message.

One minor improvement would be to also assert that, when query params are present, the link line itself is structurally correct (e.g., parse the last line as a Uri and inspect queryParameters) rather than only looking for contains('sharedBy=...')/contains('message=...'). That would catch regressions where the query string is malformed while still keeping tests resilient to parameter ordering.

Also applies to: 265-419

lib/features/share/widgets/share_items_bottom_sheet.dart (4)

38-71: Stateful conversion and contentText computation are functionally sound, but trim logic can be centralized

Converting ShareItemsBottomSheet to a ConsumerStatefulWidget with _userMessage state is appropriate for driving the live preview and the final shared text. The sheet branch of contentText correctly pulls currentUser from currentUserProvider and threads userName/userMessage into ShareUtils.getSheetShareMessage.

You currently pre-filter userName and userMessage with:

userName: userName.isNotEmpty ? userName : null,
userMessage: _userMessage.trim().isNotEmpty ? _userMessage : null,

Given the ShareUtils/tests already handle trimming and empty/whitespace-only messages, you could simplify this by passing _userMessage (and even userName) as-is and letting ShareUtils centralize the trimming/emptiness rules. That avoids duplication of logic and keeps all normalization in one place.

Functionally this is fine as-is; this is just a simplification/maintainability suggestion.


73-107: Be mindful of nested scroll views when not sharing a sheet

The outer SingleChildScrollView around the entire column is helpful for making the bottom sheet scrollable on smaller screens or when the keyboard is open. For non-sheet content, _buildContentPreview also wraps the text in its own SingleChildScrollView, which can lead to nested vertical scroll views.

This usually works but can result in slightly odd scrolling behavior or unnecessary complexity. If you run into UX issues, a small refactor (e.g., let the outer SingleChildScrollView be the only one and make the inner preview a simple Text/SelectableText) would simplify the hierarchy.


112-123: Sheet header helper is straightforward; consider handling null sheet more explicitly

_getSheetInfo nicely reuses providers to render the sheet avatar and title for isSheet flows. When sheet is null, you currently return SizedBox.shrink() but still allow the rest of the bottom sheet (including share) to render.

If a missing sheet is considered an error state, you might want to also disable the share button or show an error/placeholder instead of silently hiding the header. Not a blocker, just something to align with expected UX.


197-220: Sheet share metadata update matches link behavior; minor cleanup possible

The updated _buildShareButton correctly:

  • Uses localized l10n.share for the subject and button text.
  • When isSheet is true and a currentUser exists, calls updateSheetShareInfo with sheetId, sharedBy, and a trimmed message (or null when empty/whitespace), then shares contentText.

This aligns with the tests that ensure sharedBy/message are only propagated when non-empty and that userMessage is trimmed.

Two small, non-blocking refinements you might consider:

  • Guard against an empty userName when updating sharedBy (similar to how you guard in contentText) to avoid persisting sharedBy: '' if that’s ever possible.
  • Avoid trimming _userMessage twice by storing final trimmed = _userMessage.trim(); and reusing it for both the isNotEmpty check and the value.

Behavior is otherwise solid.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01baf94 and 97df396.

📒 Files selected for processing (15)
  • lib/core/deeplink/deep_link_initializer.dart (3 hunks)
  • lib/features/share/utils/share_utils.dart (4 hunks)
  • lib/features/share/widgets/share_items_bottom_sheet.dart (4 hunks)
  • lib/features/share/widgets/sheet_join_preview_widget.dart (4 hunks)
  • lib/features/share/widgets/sheet_share_preview_widget.dart (3 hunks)
  • lib/features/sheet/models/sheet_model.dart (5 hunks)
  • lib/features/sheet/providers/sheet_providers.dart (1 hunks)
  • lib/l10n/app_en.arb (1 hunks)
  • lib/l10n/app_gu.arb (1 hunks)
  • lib/l10n/app_hi.arb (1 hunks)
  • test/features/share/utils/share_utils_test.dart (3 hunks)
  • test/features/share/widgets/share_items_bottom_sheet_test.dart (13 hunks)
  • test/features/share/widgets/sheet_join_preview_widget_test.dart (2 hunks)
  • test/features/share/widgets/sheet_share_preview_widget_test.dart (4 hunks)
  • test/features/sheet/providers/sheet_providers_test.dart (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Test zoe_native plugin
  • GitHub Check: test
  • GitHub Check: windows
  • GitHub Check: macos
  • GitHub Check: linux
  • GitHub Check: android
  • GitHub Check: ios
🔇 Additional comments (6)
lib/features/share/widgets/sheet_share_preview_widget.dart (1)

15-50: Stateful conversion and message controller wiring look correct

Using ConsumerStatefulWidget with a lazily created TextEditingController, a single listener that proxies to onMessageChanged, and proper disposal in dispose() is a clean way to surface the free-form message while keeping Riverpod ref.watch usage in build(). No lifecycle or reactivity issues stand out here.

test/features/share/widgets/share_items_bottom_sheet_test.dart (2)

35-69: Test container setup and L10n helper improve clarity; note container reassignments

Initializing an initialContainer to fetch a test user id and then creating the shared container with a loggedInUserProvider override keeps the sheet-share tests realistic. Centralizing localization via getL10n also removes duplication. Just be aware that in some sub-groups (e.g., Event/Poll share) you later reassign container = ProviderContainer.test(...) with different overrides, which drops the logged-in-user override; that’s fine for the current assertions but is a subtle behavior to keep in mind if future tests in those groups start depending on the current user.


708-757: Non-sheet share button behavior test is straightforward and valuable

The share button calls share for non-sheet content test cleanly verifies that the same share callback is triggered for non-sheet items, without entangling it with sheet-specific state like updateSheetShareInfo. This helps guard against regressions where future refactors inadvertently gate sharing logic on isSheet.

test/features/share/widgets/sheet_share_preview_widget_test.dart (1)

40-228: Message input tests thoroughly validate the new SheetSharePreviewWidget contract

Extending the pump helper to pass onMessageChanged and adding the Message Input Field group gives very good coverage of the new stateful behavior: AnimatedTextField configuration, callback invocation (including multiple updates), null-callback safety, and controller text lifecycle (typing and clearing). This should make future refactors of the message field or controller wiring much safer.

lib/features/share/widgets/share_items_bottom_sheet.dart (2)

29-36: Bottom inset padding integration looks good

Wrapping the bottom sheet content in a Padding that uses MediaQuery.of(context).viewInsets.bottom is a good way to keep the sheet above the keyboard while retaining isScrollControlled: true and the existing shape/drag handle. No functional issues spotted here.


125-179: Title/message dispatch by content type is correct and consistent

Updating _getShareTitle and _getContentShareMessage to use widget.parentId and the richer ContentType/listType switching keeps the bottom sheet’s title and message aligned with the existing share utils for text, event, list, task, bullet, and poll.

This logic is clean and matches the behavior exercised in the tests for each content type. No issues from a correctness standpoint.

Comment thread lib/features/share/widgets/sheet_join_preview_widget.dart Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
lib/features/share/widgets/sheet_join_preview_widget.dart (1)

72-82: Consider simplifying the null handling for better readability.

While the null check on lines 72-73 prevents issues, the double force unwrap on lines 77-78 is not idiomatic. Consider extracting to a local variable for cleaner code.

Apply this diff to improve readability:

-          if (sheet.description?.plainText != null &&
-              sheet.description!.plainText!.isNotEmpty)
+          if (sheet.description?.plainText?.isNotEmpty ?? false)
             Padding(
               padding: const EdgeInsets.only(top: 8),
               child: Text(
-                sheet.description!.plainText!,
+                sheet.description!.plainText!,
                 style: theme.textTheme.bodySmall,
                 maxLines: 2,
                 overflow: TextOverflow.ellipsis,
               ),
             ),

Or use a local variable:

+          final descriptionText = sheet.description?.plainText;
-          if (sheet.description?.plainText != null &&
-              sheet.description!.plainText!.isNotEmpty)
+          if (descriptionText != null && descriptionText.isNotEmpty)
             Padding(
               padding: const EdgeInsets.only(top: 8),
               child: Text(
-                sheet.description!.plainText!,
+                descriptionText,
                 style: theme.textTheme.bodySmall,
                 maxLines: 2,
                 overflow: TextOverflow.ellipsis,
               ),
             ),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97df396 and e74e5d1.

📒 Files selected for processing (1)
  • lib/features/share/widgets/sheet_join_preview_widget.dart (4 hunks)
🔇 Additional comments (4)
lib/features/share/widgets/sheet_join_preview_widget.dart (4)

4-5: LGTM! New imports properly support the shared info UI.

The imports for ZoeUserChipWidget, GlassyContainer, and ZoeUserChipType are correctly added to support the new shared information card functionality.

Also applies to: 8-8


38-38: LGTM! Localization and data extraction are correctly implemented.

The localization setup and extraction of sharedBy and message from the sheet model follow standard patterns.

Also applies to: 42-43


83-87: LGTM! Conditional rendering logic is correct.

The condition properly checks for the presence of sharedBy or a non-empty message before rendering the shared info card.


96-162: Review comment contains incorrect assumption about data model; only address efficiency and UX concerns.

The review's core suggestion to change u.id == sharedBy is incorrect. The code intentionally stores user names (not IDs) in sharedBy, as confirmed by SheetModel definition and test data using 'John Doe', 'Jane Smith'. The current name-based lookup is by design.

However, two valid concerns remain:

  1. Inefficiency: Replace where().firstOrNull with firstWhereOrNull for early exit

    -    final sharedByUser = userList.where((u) => u.name == sharedBy).firstOrNull;
    +    final sharedByUser = userList.firstWhereOrNull((u) => u.name == sharedBy);
  2. Silent UX failure: When no user is found, the "shared by" section disappears entirely. Show the name as fallback text:

    -          if (hasSharedBy && sharedByUser != null) ...[
    +          if (hasSharedBy) ...[
                ...
    -                  child: ZoeUserChipWidget(
    -                    user: sharedByUser,
    +                  child: sharedByUser != null
    +                      ? ZoeUserChipWidget(
    +                          user: sharedByUser,
    +                        )
    +                      : Text(sharedBy, style: theme.textTheme.bodyMedium),

Likely an incorrect or invalid review comment.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
lib/features/share/widgets/share_items_bottom_sheet.dart (1)

101-112: Consider showing a loading state for better UX.

When the sheet is null (e.g., during initial load), the method returns an empty widget. Consider showing a loading indicator to provide feedback to the user while the sheet data is being fetched.

For example:

 Widget _getSheetInfo(BuildContext context, WidgetRef ref) {
   final sheet = ref.watch(sheetProvider(widget.parentId));
-  if (sheet == null) return const SizedBox.shrink();
+  if (sheet == null) {
+    return const Center(
+      child: Padding(
+        padding: EdgeInsets.all(8.0),
+        child: CircularProgressIndicator(),
+      ),
+    );
+  }
   return Column(
     crossAxisAlignment: CrossAxisAlignment.center,
     children: [
       SheetAvatarWidget(sheetId: sheet.id, padding: const EdgeInsets.all(8)),
       const SizedBox(height: 8),
       Text(sheet.title, style: Theme.of(context).textTheme.bodyMedium),
     ],
   );
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e74e5d1 and bd583a8.

📒 Files selected for processing (1)
  • lib/features/share/widgets/share_items_bottom_sheet.dart (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Test zoe_native plugin
  • GitHub Check: format
  • GitHub Check: test
  • GitHub Check: windows
  • GitHub Check: linux
  • GitHub Check: macos
  • GitHub Check: android
  • GitHub Check: ios
🔇 Additional comments (5)
lib/features/share/widgets/share_items_bottom_sheet.dart (5)

12-14: LGTM!

The new imports are necessary for the sheet sharing functionality and are properly utilized throughout the file.


34-51: LGTM!

The conversion to ConsumerStatefulWidget is appropriate for managing the user's custom share message. The implementation follows Flutter and Riverpod conventions correctly.


53-99: LGTM!

The build method updates correctly handle keyboard insets, enable scrolling for better UX, and properly wire up the state management for the user message. The conditional rendering based on isSheet is clean and appropriate.


114-133: LGTM!

The localization handling is clean, and all content types are properly mapped to their corresponding localized share titles.


147-185: LGTM!

The method correctly references widget.parentId throughout and properly handles all content types with their respective share message generation logic.

Comment thread lib/features/share/widgets/share_items_bottom_sheet.dart
Comment thread lib/features/share/widgets/share_items_bottom_sheet.dart Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
lib/features/share/widgets/sheet_join_preview_widget.dart (1)

83-87: Guard sharedBy/message sections to avoid null crashes and partial data cases

Right now _buildSharedInfoCard:

  • Computes hasMessage / hasSharedBy, but still always renders:
    • ZoeUserChipWidget(user: sharedByUser!)
    • Text(message!)

This will crash when:

  • Only message is present (no sharedBy), or
  • sharedBy is set but no matching sharedByUser exists.

It also forces both sections even if only one has data.

Recommend:

  • Use hasSharedBy to gate the "shared by" row.
  • Use hasMessage to gate the message section.
  • Only use ! where the corresponding has* is true.

Example refactor:

   final hasMessage = (message != null && message.trim().isNotEmpty);
   final hasSharedBy =
       (sharedBy != null &&
        sharedBy.trim().isNotEmpty &&
        sharedByUser != null);

   // If nothing to show, no need to build card
   if (!hasMessage && !hasSharedBy) return const SizedBox.shrink();

   return GlassyContainer(
@@
-      child: Column(
-        crossAxisAlignment: CrossAxisAlignment.start,
-        children: [
-          Row(
-            children: [
-              Text(
-                '${l10n.sharedBy}: ',
-                style: theme.textTheme.labelSmall?.copyWith(
-                  color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
-                  fontWeight: FontWeight.w500,
-                ),
-              ),
-              const SizedBox(width: 4),
-              Flexible(
-                child: ZoeUserChipWidget(
-                  user: sharedByUser!,
-                  type: ZoeUserChipType.userNameWithAvatarChip,
-                ),
-              ),
-            ],
-          ),
-
-          const SizedBox(height: 12),
-          Text(
-            '${l10n.message}:',
-            style: theme.textTheme.labelSmall?.copyWith(
-              color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
-            ),
-          ),
-          const SizedBox(height: 6),
-          Text(
-            message!,
-            style: theme.textTheme.bodyMedium?.copyWith(
-              color: theme.colorScheme.onSurface.withValues(alpha: 0.9),
-            ),
-          ),
-        ],
-      ),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          if (hasSharedBy) ...[
+            Row(
+              children: [
+                Text(
+                  '${l10n.sharedBy}: ',
+                  style: theme.textTheme.labelSmall?.copyWith(
+                    color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
+                    fontWeight: FontWeight.w500,
+                  ),
+                ),
+                const SizedBox(width: 4),
+                Flexible(
+                  child: ZoeUserChipWidget(
+                    user: sharedByUser!,
+                    type: ZoeUserChipType.userNameWithAvatarChip,
+                  ),
+                ),
+              ],
+            ),
+          ],
+          if (hasMessage) ...[
+            if (hasSharedBy) const SizedBox(height: 12),
+            Text(
+              '${l10n.message}:',
+              style: theme.textTheme.labelSmall?.copyWith(
+                color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
+              ),
+            ),
+            const SizedBox(height: 6),
+            Text(
+              message!.trim(),
+              style: theme.textTheme.bodyMedium?.copyWith(
+                color: theme.colorScheme.onSurface.withValues(alpha: 0.9),
+              ),
+            ),
+          ],
+        ],
+      ),

This avoids null crashes and handles "only message" / "only sharedBy" cleanly.

Also applies to: 96-160

🧹 Nitpick comments (1)
lib/features/share/widgets/share_items_bottom_sheet.dart (1)

70-90: Sheet share message relies on upstream callback wiring; consider trimming consistency

The overall sheet-sharing flow here looks good: _userMessage is fed into both:

  • _getSheetShareMessage (for the preview text), and
  • updateSheetShareInfo in _buildShareButton (to persist sharedBy/message).

Two points to be aware of:

  1. Dependency on message callback wiring

    _userMessage only updates via onMessageChanged from SheetSharePreviewWidget. With the current callback implementation there (and in AnimatedTextField), this is never invoked, so messages will always be empty. Once that callback wiring is fixed (see comments in sheet_share_preview_widget.dart and animated_textfield_widget.dart), this code path should work as intended.

  2. Minor trim inconsistency (optional)

    • _getSheetShareMessage checks userMessage: _userMessage.trim().isNotEmpty ? _userMessage : null,
    • _buildShareButton uses message: _userMessage.trim().isNotEmpty ? _userMessage.trim() : null,

    For consistent behavior and to avoid persisting trailing spaces while sharing a trimmed preview (or vice versa), you may want to use the trimmed value in both places:

    String _getSheetShareMessage(WidgetRef ref) {

@@

  • return ShareUtils.getSheetShareMessage(
  •  ref: ref,
    
  •  parentId: widget.parentId,
    
  •  userName: userName.isNotEmpty ? userName : null,
    
  •  userMessage: _userMessage.trim().isNotEmpty ? _userMessage : null,
    
  • );
  • final trimmedMessage = _userMessage.trim();
  • return ShareUtils.getSheetShareMessage(
  •  ref: ref,
    
  •  parentId: widget.parentId,
    
  •  userName: userName.isNotEmpty ? userName : null,
    
  •  userMessage: trimmedMessage.isNotEmpty ? trimmedMessage : null,
    
  • );
    }

(Button handler is already using `trim()` consistently.)





Also applies to: 135-145, 198-218

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: CodeRabbit UI

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between bd583a857f73347a354508dfef0a708b8caee549 and 013fa1d27f7dba8f770f83e2311428bf48193355.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `lib/common/widgets/animated_textfield_widget.dart` (4 hunks)
* `lib/features/share/widgets/share_items_bottom_sheet.dart` (5 hunks)
* `lib/features/share/widgets/sheet_join_preview_widget.dart` (4 hunks)
* `lib/features/share/widgets/sheet_share_preview_widget.dart` (3 hunks)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread lib/common/widgets/animated_textfield_widget.dart Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
lib/features/share/widgets/sheet_share_preview_widget.dart (1)

32-46: Consider initializing the controller with existing sheet message.

The TextEditingController is initialized empty, but according to the PR context, the sheet model now contains a message field (extracted from deep links or previous input). If a message already exists on the sheet, it won't be displayed in the text field.

Consider initializing the controller with the existing message:

  @override
  void initState() {
    super.initState();
-    _messageController = TextEditingController();
+    final sheet = ref.read(sheetProvider(widget.parentId));
+    _messageController = TextEditingController(text: sheet?.message ?? '');
  }

This ensures that any existing message (e.g., from a deep link) is displayed when the widget is created.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c21f3f8 and 06d02af.

📒 Files selected for processing (2)
  • lib/common/widgets/animated_textfield_widget.dart (4 hunks)
  • lib/features/share/widgets/sheet_share_preview_widget.dart (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/common/widgets/animated_textfield_widget.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Test zoe_native plugin
  • GitHub Check: test
  • GitHub Check: format
  • GitHub Check: macos
  • GitHub Check: windows
  • GitHub Check: linux
  • GitHub Check: android
  • GitHub Check: ios
🔇 Additional comments (5)
lib/features/share/widgets/sheet_share_preview_widget.dart (5)

1-13: LGTM! Imports are appropriate.

The imports correctly include the new dependencies needed for the refactored widget: AnimatedTextField and localization support.


15-30: LGTM! Proper StatefulWidget conversion.

The refactoring from ConsumerWidget to ConsumerStatefulWidget is correctly implemented to support the TextEditingController lifecycle and the new onMessageChanged callback enables parent widgets to receive message updates.


48-77: LGTM! Data filtering and setup are correct.

The filtering logic consistently uses widget.parentId to isolate sheet-specific data, and the early return for null sheet ensures safe operation. Localization is properly initialized.


88-102: ✅ Previous callback issue has been fixed!

The onTextChange handler now correctly invokes widget.onMessageChanged with the current text value from _messageController.text (lines 93-98). This addresses the previous review comment where the callback was only referenced but never called.


105-201: LGTM! Statistics rendering is well-structured.

The conditional rendering ensures statistics are only displayed when relevant data exists, providing good UX. The _buildStatCard helper method is reusable and properly styled with theme-aware colors and localized labels.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
test/features/share/widgets/sheet_join_preview_widget_test.dart (4)

342-380: Consider consistent provider overrides for user resolution.

Test 2 (lines 393-395) explicitly overrides getUserByNameProvider when creating a custom user, while this test uses getUserByIndex(container) without the override. While this may work due to test utilities automatically configuring providers, the inconsistency could lead to confusion or brittleness if test utilities change.

Consider adding the provider override for consistency:

 final testContainer = ProviderContainer.test(
   overrides: [
     loggedInUserProvider.overrideWithValue(AsyncValue.data(testUserId)),
+    getUserByNameProvider(sharingUser.name).overrideWith((ref) => sharingUser),
     sheetListProvider.overrideWith(
       () => SheetList()..state = [sheetWithBoth],
     ),
     sheetProvider(testSheetId).overrideWith((ref) => sheetWithBoth),
   ],
 );

382-413: Rename variable and simplify test scope.

Two issues in this test:

  1. Misleading variable name (line 385): The variable sheetWithTrimmedMessage is misleading—the message is not trimmed and contains whitespace. Consider renaming to sheetWithWhitespaceMessage for clarity.

  2. Mixed concerns: The test creates a new UserModel and overrides getUserByNameProvider (lines 384, 393-395), when the test is focused on whitespace preservation, not user resolution. This adds unnecessary complexity. Consider using getUserByIndex(container) like Test 1 to keep the test focused on its stated purpose.

Apply this diff to rename the variable:

-final sheetWithTrimmedMessage = testSheet.copyWith(
+final sheetWithWhitespaceMessage = testSheet.copyWith(
   message: testMessage,
   sharedBy: sharingUser.name,
 );

 final testContainer = ProviderContainer.test(
   overrides: [
     loggedInUserProvider.overrideWithValue(AsyncValue.data(testUserId)),
     getUserByNameProvider(
       sharingUser.name,
     ).overrideWith((ref) => sharingUser),
     sheetListProvider.overrideWith(
-      () => SheetList()..state = [sheetWithTrimmedMessage],
+      () => SheetList()..state = [sheetWithWhitespaceMessage],
     ),
     sheetProvider(
       testSheetId,
-    ).overrideWith((ref) => sheetWithTrimmedMessage),
+    ).overrideWith((ref) => sheetWithWhitespaceMessage),
   ],
 );

544-577: Consider consistent provider overrides for user resolution.

Similar to the first test in this group (lines 342-380), this test uses getUserByIndex(container) without explicitly overriding getUserByNameProvider, creating inconsistency with Test 2. While this likely works due to test utilities, consider adding the override for consistency and clarity.


341-578: Excellent test coverage with minor gaps.

The "Shared Info Card" test group provides comprehensive coverage of the shared info functionality. The tests are well-structured and cover both positive and negative scenarios effectively.

Strengths:

  • Thorough edge case coverage (null, empty, whitespace-only)
  • Clear test names that describe expected behavior
  • Separate tests for each concern
  • Styling verification included

Minor gaps:
Consider adding tests for:

  • sharedBy with only whitespace (you test this for message but not sharedBy)
  • Only sharedBy present (message null/empty)
  • Only message present (sharedBy null/empty)

These scenarios would ensure the card displays correctly when only one field is populated.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06d02af and 55234df.

📒 Files selected for processing (1)
  • test/features/share/widgets/sheet_join_preview_widget_test.dart (10 hunks)
🔇 Additional comments (2)
test/features/share/widgets/sheet_join_preview_widget_test.dart (2)

5-5: LGTM: Import additions support new test coverage.

The new imports for GlassyContainerWidget, ZoeUserChipWidget, and UserModel are appropriate and necessary for the expanded "Shared Info Card" test group.

Also applies to: 8-8, 15-15


66-66: LGTM: Formatting consolidation improves readability.

The consolidation of multi-line function calls to single lines when they fit comfortably improves code compactness without affecting functionality.

Also applies to: 109-109, 196-196, 214-214, 232-232, 246-246, 275-275, 314-316

Copy link
Copy Markdown
Collaborator

@kumarpalsinh25 kumarpalsinh25 left a comment

Choose a reason for hiding this comment

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

Looks good overall, just minor feedback which need to address before merge.

Comment thread lib/common/widgets/animated_textfield_widget.dart Outdated
Comment thread lib/features/share/utils/share_utils.dart
Comment thread lib/features/share/utils/share_utils.dart Outdated
Comment thread lib/features/share/utils/share_utils.dart Outdated
Comment thread lib/features/share/utils/share_utils.dart Outdated
Comment thread lib/features/share/widgets/sheet_share_preview_widget.dart Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
lib/features/share/widgets/sheet_share_preview_widget.dart (1)

32-46: User message isn’t reliably propagated to parent; onMessageChanged only fires on submit

SheetSharePreviewWidget owns _messageController, and the only place onMessageChanged is called is inside onSubmitted. If the user types a message and taps the Share button without hitting the keyboard’s submit action, the parent never sees the text, so the sheet’s shared message will be empty even though the UI shows it.

Hook the controller to the callback instead of relying solely on submit:

 class _SheetSharePreviewWidgetState
     extends ConsumerState<SheetSharePreviewWidget> {
-  late TextEditingController _messageController;
+  late final TextEditingController _messageController;
 
   @override
   void initState() {
     super.initState();
     _messageController = TextEditingController();
+    _messageController.addListener(_handleMessageChanged);
   }
 
+  void _handleMessageChanged() {
+    final callback = widget.onMessageChanged;
+    if (callback != null) {
+      callback(_messageController.text);
+    }
+  }
+
   @override
   void dispose() {
+    _messageController.removeListener(_handleMessageChanged);
     _messageController.dispose();
     super.dispose();
   }
@@
-            AnimatedTextField(
+            AnimatedTextField(
               controller: _messageController,
               hintText: l10n.addAMessage,
               onErrorChanged: (error) {},
-              onSubmitted: () {
-                final callback = widget.onMessageChanged;
-                if (callback != null) {
-                  callback(_messageController.text);
-                }
-              },
+              onSubmitted: _handleMessageChanged,
               maxLines: 3,
               keyboardType: TextInputType.multiline,
               autofocus: false,
             ),

This ensures onMessageChanged reflects the current message as the user types, and still fires on submit.

Also applies to: 88-101

🧹 Nitpick comments (2)
lib/features/share/utils/share_utils.dart (2)

15-40: Avoid emitting empty sharedBy/message query params in deep links

getSheetDeepLinkUrl always adds sharedBy/message keys, even when null/empty, leading to URLs like ...?sharedBy=&message=. If consumers distinguish between “absent” and “empty” or you want cleaner links, build the map only for non-empty values and pass null when it’s empty.

   static String getSheetDeepLinkUrl(
     String sheetId,
     String? sharedBy,
     String? message,
   ) {
-    final queryParams = <String, String>{
-      'sharedBy': sharedBy ?? '',
-      'message': message ?? '',
-    };
-    return getLinkPrefixUrl('sheet/$sheetId', queryParams: queryParams);
+    final queryParams = <String, String>{
+      if (sharedBy != null && sharedBy.isNotEmpty) 'sharedBy': sharedBy,
+      if (message != null && message.isNotEmpty) 'message': message,
+    };
+
+    return getLinkPrefixUrl(
+      'sheet/$sheetId',
+      queryParams: queryParams.isEmpty ? null : queryParams,
+    );
   }

47-61: Confirm whether userMessage should also appear in the visible share text

Right now userMessage is only threaded into the deep link (message query param) and never written into the visible share body built by getSheetShareMessage. That’s fine if the intent is to show the message only inside the app on the join flow, but if you expect the outbound share (e.g., WhatsApp/iMessage) to visibly include the typed message too, you’ll need to prepend/append it to buffer before the link.

Also applies to: 71-72

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55234df and a3c94cf.

📒 Files selected for processing (3)
  • lib/common/widgets/animated_textfield_widget.dart (1 hunks)
  • lib/features/share/utils/share_utils.dart (2 hunks)
  • lib/features/share/widgets/sheet_share_preview_widget.dart (3 hunks)
🔇 Additional comments (2)
lib/common/widgets/animated_textfield_widget.dart (1)

98-119: Border, fill, and padding tweaks look safe

The adjustments to focusedBorder/errorBorder/focusedErrorBorder, fillColor, and contentPadding are purely stylistic; they don’t affect controller behavior, shake animation, or callbacks. No functional regressions spotted.

lib/features/share/widgets/sheet_share_preview_widget.dart (1)

53-71: Sheet-scoped filtering and localized stats preview look consistent

Filtering events/tasks/documents/polls by widget.parentId plus the isToday checks, and using localized labels/subtitles in _buildStatCard, all look consistent with the new sheet-centric share preview. Always showing the cards with a count (including zero) also aligns with prior feedback.

Also applies to: 77-137

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
lib/features/share/widgets/share_items_bottom_sheet.dart (1)

197-213: Add error handling for sheet share info update.

The updateSheetShareInfo call lacks error handling. If it fails, the user won't receive feedback, and the share proceeds with potentially incomplete metadata. While the share functionality itself still works, error handling would improve the user experience.

Consider wrapping the update in try-catch:

if (widget.isSheet) {
  final currentUser = await ref.read(currentUserProvider.future);
  if (currentUser != null) {
    try {
      final trimmedMessage = _userMessage.trim();
      ref.read(sheetListProvider.notifier).updateSheetShareInfo(
            sheetId: widget.parentId,
            sharedBy: currentUser.name,
            message: trimmedMessage.isNotEmpty ? trimmedMessage : null,
          );
    } catch (e) {
      // Log error or show user feedback
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Failed to update share info: $e')),
        );
      }
    }
  }
}
lib/features/share/utils/share_utils.dart (1)

66-66: Consider handling AsyncValue states explicitly.

Accessing .value directly on currentUserProvider doesn't distinguish between loading, error, and actual null states. While the current code handles null gracefully by omitting the sharedBy parameter, explicitly handling AsyncValue states would be more robust.

Consider using when or maybeWhen for clearer state handling:

final userName = ref.watch(currentUserProvider).when(
  data: (user) => user?.name,
  loading: () => null,
  error: (_, __) => null,
);

This makes the intent clearer and ensures consistent behavior across all AsyncValue states.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3c94cf and d01e3fa.

📒 Files selected for processing (3)
  • lib/features/share/utils/share_utils.dart (8 hunks)
  • lib/features/share/widgets/share_items_bottom_sheet.dart (5 hunks)
  • test/features/share/utils/share_utils_test.dart (11 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: macos
  • GitHub Check: Test zoe_native plugin
  • GitHub Check: linux
  • GitHub Check: android
  • GitHub Check: test
  • GitHub Check: windows
  • GitHub Check: ios
🔇 Additional comments (14)
test/features/share/utils/share_utils_test.dart (5)

36-44: LGTM! Clean test setup.

The test scaffolding properly overrides currentUserProvider to simulate a logged-in user, which is essential for testing the new sharedBy query parameter functionality.


84-150: Excellent test coverage for query parameter handling.

The tests thoroughly cover edge cases including null/empty query params, proper URL encoding (spaces as +, special characters like & and !), and multiple parameters. This ensures the link generation is robust.


177-201: Well-designed test helper.

The getSheetShareLink helper function mirrors the implementation's trimming and encoding logic, making tests more maintainable and readable. The conditional query parameter inclusion matches the expected behavior.


293-400: Comprehensive test coverage for share message query parameters.

The tests thoroughly validate all scenarios: both parameters present, only sharedBy, trimming behavior, and exclusion of empty/whitespace messages. This ensures the sharing feature works correctly across all user input variations.


403-1671: Consistent test coverage across all content types.

All content type tests (text, event, list, task, bullet, poll) have been properly updated to use the new baseUrl and getLinkPostfixUrl API. The test patterns are consistent and comprehensive.

lib/features/share/widgets/share_items_bottom_sheet.dart (4)

34-51: Clean migration to stateful widget.

The refactoring from ConsumerWidget to ConsumerStatefulWidget is correct and necessary to track the user's message input (_userMessage) locally. The state management is straightforward and appropriate for this use case.


85-89: Proper state update callback.

The onMessageChanged callback correctly uses setState to update _userMessage, ensuring the widget rebuilds when the user modifies their share message. This provides a good reactive flow from input to preview to sharing.


101-112: Clean sheet info display.

The _getSheetInfo method properly handles the null case and displays the sheet avatar and title in a clear layout. The use of SizedBox.shrink() for missing sheets is appropriate.


135-141: Message construction delegated correctly.

The method properly delegates to ShareUtils.getSheetShareMessage with conditional userMessage inclusion. The trimming logic ensures empty messages aren't passed.

lib/features/share/utils/share_utils.dart (5)

15-16: LGTM! Clean constant definition.

The baseUrl constant is appropriately defined and replaces the previous linkPrefixUrl. The hardcoded URL is acceptable for a known application domain.


17-40: Well-implemented deep link URL generation.

The method correctly trims and validates query parameters before including them, preventing empty or whitespace-only values from appearing in the URL. The use of conditional map population is clean and efficient.


42-54: Robust URL construction with proper encoding.

The method correctly uses Uri.parse and uri.replace to build URLs with query parameters. The automatic URL encoding by Uri ensures special characters are handled properly, and the conditional logic handles null/empty params correctly.


72-74: Clean conditional emoji handling.

The code properly checks the avatar type before including the emoji in the share message. This ensures that non-emoji avatars (like images) don't incorrectly appear in the text.


115-115: Consistent link generation updates across all content types.

All content share methods have been uniformly updated to use getLinkPostfixUrl. The consistent pattern makes the code maintainable and easy to understand.

Also applies to: 155-155, 204-204, 246-246, 273-273, 300-300

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
test/features/share/widgets/share_items_bottom_sheet_test.dart (5)

13-25: Good centralization of localization access via getL10n

Extracting getL10n and reusing it across all content-type tests removes duplication and keeps the byType: ShareItemsBottomSheet detail in one place. The updated call sites for titles and labels all line up with the expected l10n keys and improve readability.

If you find yourself needing getL10n in other share-related test files too, consider moving it into a shared test helper to avoid re-defining it in multiple places.

Also applies to: 66-68, 427-429, 452-453, 482-483, 520-521, 558-559, 604-605, 646-647


37-53: Container setup works but could be made more robust and reusable

The new global setUp correctly wires loggedInUserProvider and currentUserProvider using a test user, which is what the new sheet-sharing tests rely on. A couple of small robustness tweaks are worth considering:

  • You’re now creating an extra ProviderContainer.test() (initialContainer) purely to fetch a user, but not disposing it. You can dispose it after extracting testUserId/user to avoid lingering containers in long-running test suites.
  • Several inner setUp blocks (events, tasks, polls) replace container with a fresh ProviderContainer.test(overrides: [...contentProvider...]) that drops the logged-in overrides from the global setUp. That’s fine for the current tests (they don’t rely on user info there), but it’s easy to forget if future tests around non-sheet sharing start depending on current user context.

You could factor out a small helper that builds a “base test container with logged-in user” and then layer content-specific overrides on top of that to keep behavior consistent across groups.

Also applies to: 439-446, 547-551, 635-640


145-198: Message-handling tests are thorough but a bit redundant; tighten or rename one test

The group of tests around message entry and sharedBy/message persistence is comprehensive:

  • Verifies text entry and controller state.
  • Persists sharedBy and message (including trimming and empty-message behavior).
  • Confirms updateSheetShareInfo semantics when currentUser is present.

Two minor improvements:

  1. The test 'includes user message in share content when provided' currently only checks that the AnimatedTextField’s controller has the entered text, which is already covered by 'updates message state when user types'. Either:

    • Rename it to reflect what it actually asserts (e.g. “keeps user message in text field”), or
    • Extend it to assert on the generated share payload (if you have a hook to inspect what’s sent to the share layer), so the name matches the behavior.
  2. The “saves X...” tests and the “calls updateSheetShareInfo...” tests validate essentially the same state on sheetProvider(testSheet.id). You might collapse them into fewer tests with parameterized inputs (message vs empty) to reduce duplication while still covering trimming and null-handling.

None of this affects correctness, but trimming the overlap will keep the suite faster and easier to maintain.

Also applies to: 200-229, 231-266, 268-289, 291-364


366-413: Null-currentUser test is valuable; consider seeding the sheet state explicitly

The 'does not call updateSheetShareInfo when currentUser is null' test nicely covers the negative path: it forces both loggedInUserProvider and currentUserProvider to null, then ensures the sheet’s sharedBy/message fields stay unchanged while the platform share still fires.

To make this test more explicit and less dependent on whatever default state sheetProvider(testSheet.id) returns in a fresh ProviderContainer.test():

  • Consider overriding the underlying sheet state/provider in testContainer with a known SheetModel (e.g., the testSheet from the outer container) before pumping the bottom sheet.
  • That way, you’re asserting that a known concrete sheet instance is not modified, rather than relying on whatever sheetProvider happens to produce by default for that id.

This would make the test more self-contained and resilient to future changes in how sheets are seeded in tests.


725-729: Share button interaction tests look good; small consistency tweak possible

The new tests confirming that:

  • The primary button has the correct icon/text (share), and
  • Tapping it triggers the share platform handler for both sheet and non-sheet content

are aligned with the intended behavior and give good confidence in the wiring.

For consistency with most Flutter testing patterns (and with your own non-sheet test here), you might also add an await tester.pump(); after the tap in 'share button has correct properties', even though the boolean flag is set synchronously today. It keeps the pattern uniform if the handler grows more asynchronous later.

Also applies to: 731-756

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d01e3fa and 508ce4d.

📒 Files selected for processing (2)
  • test/features/share/widgets/share_items_bottom_sheet_test.dart (13 hunks)
  • test/features/share/widgets/sheet_share_preview_widget_test.dart (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/features/share/widgets/sheet_share_preview_widget_test.dart
🔇 Additional comments (1)
test/features/share/widgets/share_items_bottom_sheet_test.dart (1)

78-99: Sheet share UI tests cover the new preview and message input well

The added expectations for SheetSharePreviewWidget, GlassyContainer, the share button, and the AnimatedTextField (including checking the addAMessage hint) give good coverage of the new sheet-specific share UI. The tests are focused, and the structure makes it clear which part of the UI each test is validating.

Also applies to: 112-144

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
lib/features/share/widgets/share_items_bottom_sheet.dart (1)

207-224: AsyncValue handling improved, but error handling is still missing.

The previous review flagged improper AsyncValue handling. This has been addressed by using await ref.read(currentUserProvider.future), which correctly awaits the async value.

However, error handling for the updateSheetShareInfo call is still missing. If the update fails, the share proceeds silently with potentially stale data, and the user receives no feedback.

Consider wrapping in try-catch:

       if (widget.isSheet) {
-        final currentUser = await ref.read(currentUserProvider.future);
-        if (currentUser != null) {
-          final userName = currentUser.name;
-          ref
-              .read(sheetListProvider.notifier)
-              .updateSheetShareInfo(
-                sheetId: widget.parentId,
-                sharedBy: userName,
-                message: _messageController.text.trim().isNotEmpty
-                    ? _messageController.text.trim()
-                    : null,
-              );
+        try {
+          final currentUser = await ref.read(currentUserProvider.future);
+          if (currentUser != null) {
+            final userName = currentUser.name;
+            ref.read(sheetListProvider.notifier).updateSheetShareInfo(
+                  sheetId: widget.parentId,
+                  sharedBy: userName,
+                  message: _messageController.text.trim().isNotEmpty
+                      ? _messageController.text.trim()
+                      : null,
+                );
+          }
+        } catch (e) {
+          // Log error or show feedback - share still proceeds
         }
       }
🧹 Nitpick comments (2)
lib/features/share/widgets/sheet_share_preview_widget.dart (2)

15-30: Unused contentText parameter.

The contentText parameter is declared and required in the constructor but is never used within the widget. This appears to be dead code.

Consider removing it if it's no longer needed:

 class SheetSharePreviewWidget extends ConsumerStatefulWidget {
   final String parentId;
-  final String contentText;
   final TextEditingController messageController;

   const SheetSharePreviewWidget({
     super.key,
     required this.parentId,
-    required this.contentText,
     required this.messageController,
   });

Note: This would require updating all call sites that pass contentText.


73-74: Duplicate comment.

The comment // Message Text Field is repeated twice on consecutive lines.

-            // Message Text Field
             // Message Text Field
             AnimatedTextField(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 508ce4d and db6b028.

📒 Files selected for processing (3)
  • lib/features/share/widgets/share_items_bottom_sheet.dart (5 hunks)
  • lib/features/share/widgets/sheet_share_preview_widget.dart (3 hunks)
  • test/features/share/widgets/sheet_share_preview_widget_test.dart (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/features/share/widgets/sheet_share_preview_widget_test.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test
  • GitHub Check: format
  • GitHub Check: windows
  • GitHub Check: Test zoe_native plugin
  • GitHub Check: macos
  • GitHub Check: ios
  • GitHub Check: android
  • GitHub Check: linux
🔇 Additional comments (6)
lib/features/share/widgets/sheet_share_preview_widget.dart (2)

75-83: Controller-based approach resolves the previous callback issue.

The previous review flagged that onMessageChanged callback was never properly invoked. This has been correctly addressed by switching to a controller-based approach where the parent owns _messageController and passes it directly. This is a cleaner pattern as the parent can read _messageController.text at any time without needing callback synchronization.


46-61: Filtering logic is correct.

The filtering by widget.parentId for events, tasks, documents, and polls is implemented correctly, and the isToday checks for deriving today's counts are appropriate.

lib/features/share/widgets/share_items_bottom_sheet.dart (4)

49-62: Good lifecycle management for TextEditingController.

The _messageController is properly initialized in initState and disposed in dispose, preventing memory leaks.


143-151: Previous review concern resolved.

The previous review flagged improper AsyncValue handling in _getSheetShareMessage. This has been addressed by removing the currentUserProvider dependency entirely from this method - the user name is now only used in _buildShareButton where it's properly awaited.


109-120: Sheet info widget renders correctly.

The _getSheetInfo method properly watches the sheet provider, handles the null case, and displays the avatar and title appropriately.


93-98: Verify contentText is reactive to message changes.

contentText is computed once during build using _getSheetShareMessage(ref), which reads _messageController.text. However, since _messageController is not listened to with a setState trigger, the contentText won't update as the user types.

If the share message should reflect the latest typed message, consider adding a listener:

  @override
  void initState() {
    super.initState();
    _messageController = TextEditingController();
+   _messageController.addListener(() => setState(() {}));
  }

Alternatively, if the current text is only needed at share-time (which appears to be the case given line 217-218 reads from the controller directly), this may be intentional and the preview text is expected to remain static. Please verify the intended behavior.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/features/share/widgets/sheet_share_preview_widget_test.dart (1)

40-55: Use the onMessageChanged parameter instead of hardcoded callback.

The helper function accepts onMessageChanged as a parameter (line 45) but ignores it and always passes a hardcoded empty callback (message) {} on line 52. This prevents tests from verifying that the callback is invoked with the correct values.

Apply this diff to use the parameter:

 Future<void> pumpSheetSharePreviewWidget(
   WidgetTester tester, {
   required String parentId,
   required String contentText,
   ProviderContainer? testContainer,
   ValueChanged<String>? onMessageChanged,
 }) async {
   await tester.pumpMaterialWidgetWithProviderScope(
     container: testContainer ?? container,
     child: SheetSharePreviewWidget(
       parentId: parentId,
       contentText: contentText,
-      onMessageChanged: (message) {},
+      onMessageChanged: onMessageChanged ?? (message) {},
     ),
   );
 }
♻️ Duplicate comments (1)
lib/features/share/widgets/share_items_bottom_sheet.dart (1)

197-213: Add error handling for updateSheetShareInfo.

The call to updateSheetShareInfo (lines 203-211) lacks error handling. If it fails, the share action will still proceed with potentially stale metadata, and the user won't be notified of the failure.

Apply this diff to add error handling:

       onPressed: () async {
         if (widget.isSheet) {
           // Try to get the current user; if not available, fallback to logged in user ID
           final currentUser = await ref.read(currentUserProvider.future);
           if (currentUser != null) {
             final userName = currentUser.name;
-            ref
-                .read(sheetListProvider.notifier)
-                .updateSheetShareInfo(
-                  sheetId: widget.parentId,
-                  sharedBy: userName,
-                  message: _userMessage.trim().isNotEmpty
-                      ? _userMessage.trim()
-                      : null,
-                );
+            try {
+              ref
+                  .read(sheetListProvider.notifier)
+                  .updateSheetShareInfo(
+                    sheetId: widget.parentId,
+                    sharedBy: userName,
+                    message: _userMessage.trim().isNotEmpty
+                        ? _userMessage.trim()
+                        : null,
+                  );
+            } catch (e) {
+              // Log error or show user feedback
+              debugPrint('Failed to update sheet share info: $e');
+            }
           }
         }
         CommonUtils.shareText(contentText, subject: l10n.share);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db6b028 and fb57b95.

📒 Files selected for processing (3)
  • lib/features/share/widgets/share_items_bottom_sheet.dart (5 hunks)
  • lib/features/share/widgets/sheet_share_preview_widget.dart (3 hunks)
  • test/features/share/widgets/sheet_share_preview_widget_test.dart (5 hunks)
🔇 Additional comments (8)
test/features/share/widgets/sheet_share_preview_widget_test.dart (2)

99-123: LGTM!

The test correctly verifies all AnimatedTextField properties including localized hint text, input configuration, and focus behavior.


141-162: LGTM!

The test correctly verifies that clearing the text field updates the controller state appropriately.

lib/features/share/widgets/sheet_share_preview_widget.dart (2)

36-49: LGTM!

The lifecycle management is correct. The controller listener properly invokes the callback with the current text, which addresses the previous review comment about the callback not being called.


63-74: LGTM!

The filtering logic correctly uses widget.parentId consistently across all entity types (events, tasks, documents, polls).

lib/features/share/widgets/share_items_bottom_sheet.dart (4)

34-50: LGTM!

The migration to a stateful widget is appropriate for managing the user message state, and the implementation is correct.


101-112: LGTM!

The sheet info rendering correctly displays the avatar and title with appropriate null handling.


197-217: Good improvement on AsyncValue handling.

Using await ref.read(currentUserProvider.future) (line 200) properly handles the AsyncValue states, which addresses the previous review comment. The context.mounted check (line 215) is also good practice.


135-141: The review comment is incorrect and should be dismissed.

The method signature at lib/features/share/utils/share_utils.dart:57 shows that getSheetShareMessage accepts only three parameters: ref, parentId, and optional userMessage. There is no userName parameter. The method internally derives the username via ref.watch(currentUserProvider).value?.name within its implementation.

The current code at lines 135-141 correctly passes all required parameters (ref, parentId) and optionally passes userMessage. This aligns with the actual API signature and matches the pattern used in other call sites (e.g., lib/features/sheet/actions/sheet_actions.dart:111 and the test case).

Likely an incorrect or invalid review comment.

Comment thread lib/features/share/widgets/share_items_bottom_sheet.dart Outdated
Comment thread lib/features/share/widgets/sheet_share_preview_widget.dart
Comment thread test/features/share/widgets/sheet_share_preview_widget_test.dart
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
test/features/share/widgets/sheet_share_preview_widget_test.dart (1)

40-55: Wire onMessageChanged through the test helper.

pumpSheetSharePreviewWidget accepts an onMessageChanged parameter (Line 45) but always passes a no-op closure to SheetSharePreviewWidget (Line 52). This makes the parameter misleading and prevents tests from asserting callback behavior.

Consider forwarding the parameter:

   Future<void> pumpSheetSharePreviewWidget(
     WidgetTester tester, {
     required String parentId,
     required String contentText,
     ProviderContainer? testContainer,
-    ValueChanged<String>? onMessageChanged,
+    ValueChanged<String>? onMessageChanged,
   }) async {
     await tester.pumpMaterialWidgetWithProviderScope(
       container: testContainer ?? container,
       child: SheetSharePreviewWidget(
         parentId: parentId,
         contentText: contentText,
-        onMessageChanged: (message) {},
+        onMessageChanged: onMessageChanged,
       ),
     );
   }

This keeps the helper honest and enables future tests to verify onMessageChanged wiring if needed.

Please re-run the widget tests (e.g., flutter test test/features/share/widgets/sheet_share_preview_widget_test.dart) after this change to confirm behavior is unchanged.

lib/features/share/widgets/sheet_share_preview_widget.dart (2)

48-51: Reconsider when onMessageChanged is fired (only on submit today).

Right now onMessageChanged is invoked only in onSubmitted (Lines 92–97). If the parent is expected to always have the latest message (e.g., when the user taps a separate "Share" button without submitting from the keyboard), this can lead to stale or empty values unless the user explicitly submits the field.

Two possible improvements:

  • Attach a listener to _messageController in initState that calls widget.onMessageChanged on every change, and drop onSubmitted, or
  • If AnimatedTextField exposes an onChanged callback, map that directly to onMessageChanged.

If the intended behavior is truly "only fire when submitted", consider documenting that in the widget’s doc comment so the contract is clear to callers.

Please verify against your share flow (and its tests) whether you rely on live updates or only on submit, and adjust wiring accordingly.

Also applies to: 60-71, 77-101


88-101: Optional: remove no-op onErrorChanged if not required.

onErrorChanged: (error) {}, is currently a no-op. If validation errors are not used for this field and onErrorChanged is optional on AnimatedTextField, you can omit this parameter entirely to reduce noise.

Check the AnimatedTextField API: if onErrorChanged is non-nullable/required, keeping the no-op is fine; otherwise consider removing it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb57b95 and a83800b.

📒 Files selected for processing (3)
  • lib/features/share/widgets/share_items_bottom_sheet.dart (5 hunks)
  • lib/features/share/widgets/sheet_share_preview_widget.dart (3 hunks)
  • test/features/share/widgets/sheet_share_preview_widget_test.dart (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/features/share/widgets/share_items_bottom_sheet.dart
🔇 Additional comments (3)
test/features/share/widgets/sheet_share_preview_widget_test.dart (1)

69-71: AnimatedTextField tests now assert real behavior and props.

The new "Message Input Field" tests correctly:

  • Assert that AnimatedTextField is rendered.
  • Verify key properties (hint text from l10n.addAMessage, maxLines, keyboardType, autofocus).
  • Check that tester.enterText updates the underlying controller text, and that clearing input resets it.

This replaces the previous trivial assertion and gives good coverage of the input field’s behavior.

Please keep these tests in sync with any future API changes to AnimatedTextField (e.g., renaming hintText or changing default maxLines).

Also applies to: 98-123, 125-165

lib/features/share/widgets/sheet_share_preview_widget.dart (2)

15-47: Stateful conversion and controller lifecycle look correct.

Switching to ConsumerStatefulWidget and managing _messageController in initState/dispose is done properly and keeps the text field’s state local to the widget. This is a clean way to support richer message input while preserving provider usage via ref.

After any further tweaks to this widget, rerun the share-related widget tests to ensure there are no regressions in the preview UI.


103-137: Localized stat cards and sheet-based filtering are consistent.

Using widget.parentId to filter events/tasks/documents/polls and driving labels/subtitles from l10n (events, tasks, documents, polls, today, dueToday) keeps the stats aligned with sheet context and localization. The _buildStatCard usage is straightforward and matches expectations from the tests.

If you later add more sheet-scoped entities, following this same pattern (filtered lists + localized labels) will keep the UI consistent.

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