Skip to content

feat: add direct contact button to flatmate profile sheet - #36

Merged
saksham1991999 merged 2 commits into
360ghar:mainfrom
kan9667:feat/flatmate-direct-contact
Aug 3, 2026
Merged

feat: add direct contact button to flatmate profile sheet#36
saksham1991999 merged 2 commits into
360ghar:mainfrom
kan9667:feat/flatmate-direct-contact

Conversation

@kan9667

@kan9667 kan9667 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a Contact button to the flatmate profile sheet, letting users start a chat directly instead of requiring a mutual like/match first.

Problem

Viewing a flatmate's profile (from "Meet potential flatmates" or the Likes/Liked tabs) offered no way to reach out. The only existing path was to send a like and wait for a match — high friction for a simple "I'd like to talk to this person" action.

Solution

  • ChatsRepository — added startConversation(), calling POST /flatmates/conversations to create a conversation directly.
  • ChatActionsController — wrapped startConversation() with proper cache invalidation so the new conversation shows up immediately in the Chats list.
  • FlatmateProfileSheet — added a full-width Contact button and _handleContact() flow that creates the conversation and navigates straight to it; includes a self-contact guard so the button is hidden on your own profile.
  • SwipeProfileCard — added a trailing parameter to SwipeProfileDetailBody so the Contact CTA can render below the profile content.

Flow

Before After
Tap flatmate → view profile → no contact option Tap flatmate → view profile → tap Contact → conversation created → navigated to chat

Testing

  1. Open the app → Discover home page.
  2. Scroll to Meet potential flatmates.
  3. Tap any flatmate card.
  4. Scroll to the bottom of the profile sheet.
  5. Tap Contact.
  6. Confirm you land in a new chat conversation with that flatmate.
  7. Confirm the conversation appears in the Chats list.

Notes

  • No backend changes — reuses the existing POST /flatmates/conversations endpoint.
  • Contact button is hidden when viewing your own profile.
  • matchIncomingLike() is untouched and still handles matching from the Likes tab.

Summary by cubic

Adds a Contact button to the flatmate profile sheet so users can start a chat directly without a mutual like. The conversation is created instantly, you’re taken to the new chat, and the Chats list updates right away.

  • New Features

    • Added startConversation() in ChatsRepository calling POST /flatmates/conversations (supports optional initial message).
    • Added ChatActionsController.startConversation() with cache invalidation so the new chat appears immediately.
    • FlatmateProfileSheet: full-width Contact button; creates the conversation and navigates to /chats/{id}; hidden on your own profile.
    • SwipeProfileDetailBody: new trailing slot to render a CTA under the profile content.
  • Bug Fixes

    • Prevent double taps with an in-flight guard.
    • Safer ID parsing and null handling; show a toast if no conversation is created.
    • Better network error messages using ErrorPresenter.fromDio().
    • Fix navigation ordering by capturing the router before closing the sheet.

Written for commit 6f3c053. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added the ability to contact flatmates directly from their profile.
    • New conversations can optionally include an initial message.
    • After contacting someone, the app opens the new chat automatically.
    • Contact actions are hidden when viewing your own profile.
  • Bug Fixes

    • Added user-friendly error feedback when starting a conversation fails.

- Add startConversation() to ChatsRepository for direct chat creation
- Wire ChatActionsController with startConversation wrapper
- Update FlatmateProfileSheet to use direct conversation (no like required)
- Add trailing parameter to SwipeProfileDetailBody for Contact CTA
- Contact button creates conversation immediately and navigates to chat
@kan9667
kan9667 requested a review from saksham1991999 as a code owner August 2, 2026 08:29
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds conversation creation through the chat repository and controller. Flatmate profile sheets now show a contact CTA for other users, create conversations, close the sheet, and navigate to chat. Profile detail bodies support optional trailing content.

Changes

Chat contact flow

Layer / File(s) Summary
Conversation creation API
lib/features/chats/chats_repository.dart, lib/features/chats/application/chat_actions_controller.dart
The repository creates conversations with an optional initial message. The controller refreshes chat state and returns the conversation ID.
Profile detail trailing content
lib/features/swipe/presentation/widgets/swipe_profile_card.dart
SwipeProfileDetailBody accepts and renders optional trailing content below the profile sections.
Contact CTA and chat navigation
lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart
The profile sheet displays a contact button for non-self profiles, starts a conversation, dismisses the sheet, and navigates to chat. Errors are logged and shown in a toast.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FlatmateProfileSheet
  participant ChatActionsController
  participant ChatsRepository
  participant ChatRouter
  FlatmateProfileSheet->>ChatActionsController: startConversation(peerId)
  ChatActionsController->>ChatsRepository: create conversation
  ChatsRepository-->>ChatActionsController: conversation ID
  ChatActionsController-->>FlatmateProfileSheet: conversation ID
  FlatmateProfileSheet->>ChatRouter: dismiss sheet and open chat
Loading

Possibly related PRs

Suggested reviewers: saksham1991999, ravisahu1520

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a direct contact button to the flatmate profile sheet.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add Contact CTA to flatmate profile sheet for direct chat creation

✨ Enhancement 🕐 20-40 Minutes

Grey Divider

AI Description

• Add a full-width Contact button on flatmate profiles to start chats without matching.
• Introduce a startConversation flow in chats controller/repository with cache invalidation.
• Extend the swipe profile detail body with a trailing slot to render CTAs.
Diagram

graph TD
  A["FlatmateProfileSheet"] --> B["ChatActionsController"] --> C["ChatsRepository"] --> D{{"POST /flatmates/conversations"}}
  B --> E[("Riverpod caches")]
  A --> F["Router /chats/:id"]

  subgraph Legend
    direction LR
    _ui["UI"] ~~~ _svc["Controller/Repo"] ~~~ _api{{"API"}} ~~~ _cache[("Cache")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Optimistic navigation + background creation
  • ➕ Perceived latency is lower (navigate immediately)
  • ➖ Needs robust error recovery/rollback if conversation creation fails
  • ➖ More complex state handling for a newly created conversation thread
2. Reuse match flow (like→match→chat) with prefilled message
  • ➕ No new API surface usage; leverages existing like/match mechanics
  • ➖ Does not solve the core friction problem (still requires mutual like)
  • ➖ Adds UX steps for users who just want to talk
3. Centralize CTA support via a dedicated “Profile CTA area” widget
  • ➕ Keeps SwipeProfileDetailBody simpler and isolates CTA layout decisions
  • ➖ Additional widget indirection for a single current use-case
  • ➖ May be premature unless more CTAs are planned soon

Recommendation: The PR’s approach (new startConversation API wrapper + cache invalidation + trailing CTA slot) is the right tradeoff for a low-friction contact action and keeps responsibilities well-layered. Before merging, ensure the repository request payload is correct (the diff shows ?initialMessage, which looks like a syntax/serialization bug) and align the FlatmateProfileSheet doc comment (it references matchIncomingLike, but the code calls startConversation). Also consider handling the ‘conversation already exists’ backend behavior (e.g., returning an existing id) if the endpoint supports it.

Files changed (4) +100 / -6

Enhancement (4) +100 / -6
chat_actions_controller.dartAdd startConversation action with chat list invalidation +16/-0

Add startConversation action with chat list invalidation

• Introduces ChatActionsController.startConversation() to create a conversation immediately (no mutual like). Invalidates chat list controllers and conversationsProvider so the new thread appears promptly.

lib/features/chats/application/chat_actions_controller.dart

chats_repository.dartAdd repository method to POST /flatmates/conversations +19/-0

Add repository method to POST /flatmates/conversations

• Adds ChatsRepository.startConversation() calling FlatmatesEndpoints.conversations with peer user id and optional initial message, returning the created conversation id from the response.

lib/features/chats/chats_repository.dart

flatmate_profile_sheet.dartAdd Contact CTA that starts conversation and routes to chat +51/-6

Add Contact CTA that starts conversation and routes to chat

• Adds a bottom Contact button (hidden for self) to start a direct conversation via ChatActionsController and navigate to /chats/:conversationId. Includes error handling with a toast and closes the sheet before routing.

lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart

swipe_profile_card.dartSupport trailing slot in SwipeProfileDetailBody for CTAs +14/-0

Support trailing slot in SwipeProfileDetailBody for CTAs

• Extends SwipeProfileDetailBody with an optional trailing widget rendered below the profile sections with consistent padding/spacing, enabling consumers to add CTAs like Contact.

lib/features/swipe/presentation/widgets/swipe_profile_card.dart

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart`:
- Line 98: Update the button callback invoking _handleContact to use a block
callback and wrap the returned Future<void> with unawaited(). Ensure the
required async utility import is available while preserving the existing
context, ref, and locale arguments.
- Around line 70-73: Update the Contact CTA rendering in the flatmate profile
sheet to require currentUserId to be non-null and different from userId. Use the
existing currentUserId and isSelf state in the relevant widget build logic,
ensuring the CTA remains hidden while bootstrapControllerProvider has no
resolved profile.
- Around line 59-62: Update the catch handling in
FlatmateProfileSheet._handleContact to convert the caught request error through
ErrorPresenter.fromDio() and the typed AppFailure flow, then present the
resulting actionable failure instead of always using locale.errorUnknown.
Preserve the context.mounted guard and remove the debugPrint/error.toString()
usage.
- Around line 23-24: Update the documentation link in the flatmate profile sheet
comment to reference ChatActionsController.startConversation instead of
ChatActionsController.matchIncomingLike, matching the action invoked by the
sheet.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 312ec578-6955-40cc-b514-6f7f85d61b85

📥 Commits

Reviewing files that changed from the base of the PR and between dc0395b and d685e9f.

📒 Files selected for processing (4)
  • lib/features/chats/application/chat_actions_controller.dart
  • lib/features/chats/chats_repository.dart
  • lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart
  • lib/features/swipe/presentation/widgets/swipe_profile_card.dart

Comment thread lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart Outdated
Comment thread lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart Outdated
Comment thread lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart Outdated
Comment thread lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart Outdated
@qodo-code-review

qodo-code-review Bot commented Aug 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 32 rules

Grey Divider


Action required

1. Navigate after sheet pop ✓ Resolved 🐞 Bug ☼ Reliability
Description
FlatmateProfileSheet._handleContact pops the bottom sheet and then uses the same sheet BuildContext
to call context.go(), which is lifecycle-sensitive and can fail if the sheet context is deactivated
during pop.
Code

lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[R55-58]

+      final conversationId = await controller.startConversation(peerId: userId);
+      if (!context.mounted) return;
+      Navigator.of(context).pop();
+      context.go('/chats/$conversationId');
Evidence
The handler explicitly pops the sheet route and immediately calls go_router navigation on the same
BuildContext, which belongs to the sheet route that is being removed.

lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[48-63]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`FlatmateProfileSheet._handleContact` does `Navigator.of(context).pop()` and then calls `context.go(...)` using the same bottom-sheet context. After a route is popped, that context may be deactivated/unmounted, making post-pop navigation flaky.

## Issue Context
This is a bottom sheet (modal route) and the code navigates to the newly created conversation immediately after dismissing the sheet.

## Fix Focus Areas
- lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[48-63]

## Suggested fix
- Capture the router (or a parent/root context) *before* popping, then pop, then navigate using the captured router:
 - `final router = GoRouter.of(context);`
 - `Navigator.of(context).pop();`
 - `router.go('/chats/$conversationId');`
- Alternatively, navigate using `rootNavigatorKey.currentContext` (if available in your routing setup) so navigation is executed from a stable root context.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Conversation id cast may throw ✓ Resolved 🐞 Bug ≡ Correctness
Description
ChatsRepository.startConversation force-casts response.data['id'] to num; if the backend returns an
empty body, a different key, or a non-numeric id, this throws and breaks the contact flow.
Code

lib/features/chats/chats_repository.dart[R142-145]

+    final data = response.data is Map
+        ? Map<String, dynamic>.from(response.data as Map)
+        : <String, dynamic>{};
+    return (data['id'] as num).toInt();
Evidence
The new method uses a hard cast on data['id'], whereas the existing conversation-creation-related
method uses nullable parsing, demonstrating a more defensive approach already used in this codebase.

lib/features/chats/chats_repository.dart[108-127]
lib/features/chats/chats_repository.dart[129-146]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`startConversation()` does `return (data['id'] as num).toInt();` after converting non-Map responses to `{}`. Any missing/null/non-numeric `id` will throw a runtime cast error.

## Issue Context
In the same repository, `matchIncomingLike()` parses `conversation_id` defensively as nullable: `(data['conversation_id'] as num?)?.toInt()`.

## Fix Focus Areas
- lib/features/chats/chats_repository.dart[108-146]
- lib/features/chats/application/chat_actions_controller.dart[63-77]

## Suggested fix
- Parse the conversation id as nullable and handle absence explicitly:
 - `final raw = (data['id'] ?? data['conversation_id']) as num?;`
 - If `raw == null`, either:
   - throw a typed failure with context (preferred for debugging), or
   - change the return type to `Future<int?>` and let the controller/UI show an error toast.
- Add a small unit test for `startConversation()` similar to the existing `matchIncomingLike` test to lock the expected response shape.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. No tap in-flight guard ✓ Resolved 🐞 Bug ☼ Reliability
Description
The new Contact CTA can be tapped repeatedly while the async startConversation call is in flight,
issuing multiple POSTs and potentially causing duplicate/extra requests and confusing navigation
outcomes.
Code

lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[R95-99]

+            child: FlatmatesButton(
+              key: const ValueKey('flatmate_contact_cta'),
+              label: locale.contactCta,
+              onPressed: () => _handleContact(context, ref, locale),
+              icon: Icons.send_rounded,
Evidence
FlatmateProfileSheet directly wires the button to _handleContact without any debounce/disabled
state, while ConversationsPage demonstrates an explicit in-flight guard pattern for similar
conversation-creation actions.

lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[91-102]
lib/features/chats/conversations_page.dart[75-120]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Contact button triggers an async network call but is never disabled while the request is pending. Rapid taps can fire multiple `startConversation()` calls.

## Issue Context
The codebase already uses an in-flight guard for match actions to prevent double-taps.

## Fix Focus Areas
- lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[48-109]

## Suggested fix
- Add an in-flight boolean state and disable the button while true.
 - Option A: Convert `FlatmateProfileSheet` to a `ConsumerStatefulWidget` and keep `_isContacting` in State.
 - Option B: Use a `StateProvider.autoDispose.family<bool, int>` keyed by `userId`.
- Update the button:
 - `onPressed: isContacting ? null : () => _handleContact(...)`
 - Optionally show a loading label/spinner while contacting.

## Reference pattern
Mirror the double-tap prevention used by `_matchingLikeIdsProvider` in ConversationsPage.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Unawaited _handleContact() callback ✓ Resolved 📘 Rule violation ☼ Reliability
Description
The new Contact button triggers _handleContact(...) (returns Future<void>) without awaiting it
or wrapping it in unawaited(...), making it an implicit fire-and-forget Future. This violates the
requirement to explicitly mark intentional unawaited Futures for clarity and lintability.
Code

lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[R96-99]

+              key: const ValueKey('flatmate_contact_cta'),
+              label: locale.contactCta,
+              onPressed: () => _handleContact(context, ref, locale),
+              icon: Icons.send_rounded,
Evidence
PR Compliance ID 2348636 requires standalone Future calls to be either awaited or explicitly
wrapped in unawaited(...). The FlatmatesButton uses onPressed: () => _handleContact(...),
which starts a Future without awaiting or unawaited.

Rule 2348636: Wrap intentional fire-and-forget futures with unawaited()
lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[95-100]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A `Future`-returning method (`_handleContact`) is invoked from an `onPressed` callback without being awaited or wrapped in `unawaited(...)`, creating an implicit fire-and-forget async call.

## Issue Context
Compliance requires intentional fire-and-forget Futures to be explicit (`unawaited`) or avoided by awaiting within an `async` callback.

## Fix Focus Areas
- lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[95-100]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. Wrong method in docs ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
FlatmateProfileSheet’s doc comment says the Contact CTA uses
ChatActionsController.matchIncomingLike, but the implementation calls startConversation, making the
comment misleading for future maintainers.
Code

lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[R23-24]

+/// A full-width Contact button at the bottom initiates a conversation with
+/// the flatmate via [ChatActionsController.matchIncomingLike].
Evidence
The comment references matchIncomingLike while the handler calls startConversation, so the
documentation no longer matches behavior.

lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[16-25]
lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[48-59]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The doc comment above `FlatmateProfileSheet` claims the Contact button initiates a conversation via `ChatActionsController.matchIncomingLike`, but the actual code calls `controller.startConversation(...)`.

## Issue Context
This PR introduces a new direct-contact flow (no mutual like), so `matchIncomingLike` is the wrong reference.

## Fix Focus Areas
- lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart[16-25]

## Suggested fix
- Change the doc reference to `ChatActionsController.startConversation` and align wording with the “direct conversation without mutual like” behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart Outdated
Comment thread lib/features/chats/chats_repository.dart Outdated
Comment thread lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart Outdated
Comment thread lib/features/chats/chats_repository.dart Outdated
Comment thread lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart Outdated
Comment thread lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart Outdated
Comment thread lib/features/discover/presentation/widgets/flatmate_profile_sheet.dart Outdated
- Fix doc comment: matchIncomingLike → startConversation
- Fix isSelf null guard: require currentUserId non-null before showing CTA
- Fix navigate-after-pop: capture GoRouter before popping sheet
- Fix unawaited Future: wrap _handleContact in unawaited()
- Fix in-flight guard: convert to ConsumerStatefulWidget with _isContacting
- Fix hard cast on data['id']: use nullable parsing like matchIncomingLike
- Fix error handling: use ErrorPresenter.fromDio() for typed error messages
- Handle null conversationId with matchCreateFailed toast
@saksham1991999
saksham1991999 merged commit cf313e3 into 360ghar:main Aug 3, 2026
3 of 4 checks passed
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.

3 participants