-
Notifications
You must be signed in to change notification settings - Fork 70
Add Google Meet integration to meeting creation #151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add Google Meet integration to meeting creation #151
Conversation
📝 WalkthroughWalkthroughThis pull request integrates Google Meet link creation directly into the meeting creation screen. A new GoogleMeetService handles OAuth authentication and Calendar API calls to generate Meet links, while the UI adds a button to trigger link creation with loading feedback and validation. Changes
Sequence DiagramsequenceDiagram
actor User
participant UI as Meeting Creation Screen
participant Service as GoogleMeetService
participant GoogleSignIn
participant CalendarAPI as Google Calendar API
User->>UI: Tap "Create Google Meet Link"
UI->>Service: createMeetLink(title, description, dateTime, duration)
Service->>GoogleSignIn: signIn()
GoogleSignIn-->>Service: Authentication token
Service->>CalendarAPI: POST /calendars/primary/events<br/>(with conferenceData.createRequest)
CalendarAPI-->>Service: Event created with hangoutLink
Service-->>UI: Meet link URL
UI->>UI: Update _urlController & validate
UI->>User: Show success snackbar with link
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@lib/screens/meetings/create_meeting_screen.dart`:
- Around line 107-109: The code currently treats a returned meet link `link` as
valid when non-null, but `createMeetLink` can return an empty string; update the
guard in the block that sets `_urlController.text` and calls `_checkUrl` to
verify the link is both non-null and non-empty (e.g., use `link != null &&
link.isNotEmpty`) before assigning `_urlController.text = link` and invoking
`_checkUrl(link)` so you don’t populate the field with an empty string.
- Around line 470-494: The "Create Meeting" button should be disabled while a
Google Meet link is being generated; update the Create Meeting button's
onPressed handler (the widget rendering the "Create Meeting" action in
create_meeting_screen.dart) to check _isCreatingMeetLink and set onPressed to
null when true (same pattern used in the Create Google Meet Link button),
ensuring the button is non-interactive during link creation and preventing form
submission mid-generation; keep any existing visual/loading behavior intact.
- Line 529: Add unit tests for GoogleMeetService and widget tests for the
CreateMeetingScreen's _createGoogleMeetLink flow: write unit tests that mock
Calendar API interactions used by GoogleMeetService (mock successful
createConference and error paths) and assert returned meeting link or thrown
errors; write widget tests that pump CreateMeetingScreen (or the widget
containing _createGoogleMeetLink), inject a mocked GoogleMeetService, simulate
button taps to trigger the create flow, and verify button enabled/disabled
states and SnackBar messages for success and failure cases. Use the service
class name GoogleMeetService and the private flow method name
_createGoogleMeetLink (or the public handler that calls it) to locate code, and
employ mocking libraries (mockito or mocktail) and flutter_test widget testers
to assert UI feedback and interactions.
In `@lib/services/google_meet_service.dart`:
- Around line 35-36: The code currently hardcodes a 1-hour expiry when
constructing AccessCredentials/AccessToken; instead, obtain the real expiry if
available from the GoogleSignInAuthentication response (e.g., an expiresIn
field) and use that to compute the DateTime, and if no expiry is exposed fall
back to a conservative short TTL (e.g., a few minutes) or force a fresh token
request; update the AccessCredentials/AccessToken creation to use
DateTime.now().toUtc().add(Duration(seconds: expiresIn)) when expiresIn is
present (and use a short fallback when absent) so the token lifetime is not
incorrectly assumed.
- Around line 69-76: The current return expression can yield an empty string
(EntryPoint(uri: '')) which is treated as non-null by the caller
(_createGoogleMeetLink) and causes a false-success; change the fallback so that
when there is no hangoutLink and no video entry in
createdEvent.conferenceData?.entryPoints you return null instead of an
EntryPoint with an empty uri. Locate the expression using
createdEvent.hangoutLink and the firstWhere(orElse: () => cal.EntryPoint(uri:
'')) and replace the orElse/uri usage with logic that returns null when no video
entry exists (or check for existence before returning .uri) so callers see a
true null and can handle the missing link correctly.
🧹 Nitpick comments (5)
lib/services/google_meet_service.dart (4)
7-11: Redundant scope:calendarEventsScopeis a subset ofcalendarScope.
CalendarApi.calendarScopealready grants full read/write access to calendars and events. RequestingcalendarEventsScopealongside it is redundant and unnecessarily broadens the displayed consent screen text.Suggested fix
final GoogleSignIn _googleSignIn = GoogleSignIn( scopes: [ cal.CalendarApi.calendarScope, - cal.CalendarApi.calendarEventsScope, ], );
20-32: CallingsignIn()on every link creation forces the account picker each time.
signIn()presents the interactive sign-in/account-chooser flow. For returning users who already granted consent, this is unnecessary friction. PrefersignInSilently()first, falling back tosignIn()only when needed.Suggested fix
- final account = await _googleSignIn.signIn(); + var account = await _googleSignIn.signInSilently(); + account ??= await _googleSignIn.signIn(); if (account == null) return null;
30-31: Replaceprint()withdebugPrint()or a structured logger.Bare
print()statements are not stripped in release builds and can leak operational details. The rest of the codebase (e.g.,create_meeting_screen.dartLine 216) usesdebugPrint.Suggested fix
- print('No access token available'); + debugPrint('No access token available');- print('Error creating Google Meet link: $e'); + debugPrint('Error creating Google Meet link: $e');You'll also need to add:
import 'package:flutter/foundation.dart';Also applies to: 80-82
86-88: Remove dead code or wire up thesignOut()method.The
signOut()method inGoogleMeetServiceis defined but never called anywhere in the codebase. Either remove it if disconnecting Google accounts is not a required feature, or implement the UI wiring to invoke it when users attempt to sign out.lib/screens/meetings/create_meeting_screen.dart (1)
51-90: Duplicated date/time combining and duration parsing logic.Lines 63–69 and 82–90 are near-identical copies of the same logic in
_createMeeting(Lines 171–177 and 180–187). Extract shared helpers to reduce duplication and prevent future drift.Example helper extraction
DateTime _buildMeetingDateTime() { return DateTime( _selectedDate!.year, _selectedDate!.month, _selectedDate!.day, _selectedTime!.hour, _selectedTime!.minute, ); } int _parseDuration() { try { final d = int.parse(_durationController.text.trim()); return d > 0 ? d : 60; } catch (_) { return 60; } }Then use
_buildMeetingDateTime()and_parseDuration()in both_createGoogleMeetLinkand_createMeeting.
| if (link != null) { | ||
| _urlController.text = link; | ||
| _checkUrl(link); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against empty-string Meet link from the service.
As noted in the service review, createMeetLink can return "" instead of null. Even after fixing the service, a defensive check here prevents a broken link from silently populating the URL field.
Suggested fix
- if (link != null) {
+ if (link != null && link.isNotEmpty) {
_urlController.text = link;
_checkUrl(link);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (link != null) { | |
| _urlController.text = link; | |
| _checkUrl(link); | |
| if (link != null && link.isNotEmpty) { | |
| _urlController.text = link; | |
| _checkUrl(link); |
🤖 Prompt for AI Agents
In `@lib/screens/meetings/create_meeting_screen.dart` around lines 107 - 109, The
code currently treats a returned meet link `link` as valid when non-null, but
`createMeetLink` can return an empty string; update the guard in the block that
sets `_urlController.text` and calls `_checkUrl` to verify the link is both
non-null and non-empty (e.g., use `link != null && link.isNotEmpty`) before
assigning `_urlController.text = link` and invoking `_checkUrl(link)` so you
don’t populate the field with an empty string.
| // Create Google Meet Link button | ||
| const SizedBox(height: 8), | ||
| SizedBox( | ||
| width: double.infinity, | ||
| child: ElevatedButton.icon( | ||
| onPressed: _isCreatingMeetLink ? null : _createGoogleMeetLink, | ||
| icon: _isCreatingMeetLink | ||
| ? const SizedBox( | ||
| width: 16, | ||
| height: 16, | ||
| child: CircularProgressIndicator( | ||
| strokeWidth: 2, | ||
| color: Colors.white, | ||
| ), | ||
| ) | ||
| : const Icon(Icons.video_call), | ||
| label: Text( | ||
| _isCreatingMeetLink ? 'Creating Meet Link...' : 'Create Google Meet Link', | ||
| ), | ||
| style: ElevatedButton.styleFrom( | ||
| backgroundColor: Colors.blue.shade800, | ||
| padding: const EdgeInsets.symmetric(vertical: 16), | ||
| ), | ||
| ), | ||
| ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Button UI and loading state look correct.
The button is properly disabled during link creation, shows a CircularProgressIndicator in-flight, and the label updates to indicate progress. This aligns with the issue requirement for loading states.
One minor UX note: the "Create Meeting" button (Line 512) is not disabled while _isCreatingMeetLink is true, so a user could submit the form mid-generation. Consider disabling it as well.
Suggested fix for the Create Meeting button
SizedBox(
width: double.infinity,
child: ElevatedButton(
- onPressed: _createMeeting,
+ onPressed: _isCreatingMeetLink ? null : _createMeeting,🤖 Prompt for AI Agents
In `@lib/screens/meetings/create_meeting_screen.dart` around lines 470 - 494, The
"Create Meeting" button should be disabled while a Google Meet link is being
generated; update the Create Meeting button's onPressed handler (the widget
rendering the "Create Meeting" action in create_meeting_screen.dart) to check
_isCreatingMeetLink and set onPressed to null when true (same pattern used in
the Create Google Meet Link button), ensuring the button is non-interactive
during link creation and preventing form submission mid-generation; keep any
existing visual/loading behavior intact.
| ); | ||
| } | ||
| } No newline at end of file | ||
| } No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check for test files and GoogleMeetService references
echo "=== Searching for test files with Google Meet related patterns ==="
find . -path './test*' -name '*.dart' -type f | head -50
echo -e "\n=== Searching for GoogleMeetService references in test files ==="
rg -i 'googlemeet|google_meet|createmeetlink' --type=dart -g '*test*' --max-count=20
echo -e "\n=== Checking if GoogleMeetService exists in lib ==="
rg -l 'class GoogleMeetService|GoogleMeetService' --type=dart -g '!test'
echo -e "\n=== Looking for test directory structure ==="
find . -type d -name test | head -20Repository: AOSSIE-Org/Ell-ena
Length of output: 410
🏁 Script executed:
#!/bin/bash
# Search for any meet-related test files
echo "=== Looking for meet-related test files ==="
fd -e dart . test -i meet
echo -e "\n=== Checking all test files ==="
find . -path '*/test/*' -name '*test.dart' -o -name '*_test.dart' | wc -l
echo -e "\n=== Searching for GoogleMeetService in all dart files ==="
rg 'GoogleMeetService' --type=dartRepository: AOSSIE-Org/Ell-ena
Length of output: 451
🏁 Script executed:
#!/bin/bash
echo "=== Contents of the only test file ==="
cat ./test/widget_test.dartRepository: AOSSIE-Org/Ell-ena
Length of output: 1187
Add unit and widget tests for Google Meet functionality.
The codebase currently has only a basic counter smoke test. Tests for GoogleMeetService (mocking Calendar API interactions) and widget tests verifying button states and snackbar feedback for the _createGoogleMeetLink flow are missing and required per the PR objectives.
🤖 Prompt for AI Agents
In `@lib/screens/meetings/create_meeting_screen.dart` at line 529, Add unit tests
for GoogleMeetService and widget tests for the CreateMeetingScreen's
_createGoogleMeetLink flow: write unit tests that mock Calendar API interactions
used by GoogleMeetService (mock successful createConference and error paths) and
assert returned meeting link or thrown errors; write widget tests that pump
CreateMeetingScreen (or the widget containing _createGoogleMeetLink), inject a
mocked GoogleMeetService, simulate button taps to trigger the create flow, and
verify button enabled/disabled states and SnackBar messages for success and
failure cases. Use the service class name GoogleMeetService and the private flow
method name _createGoogleMeetLink (or the public handler that calls it) to
locate code, and employ mocking libraries (mockito or mocktail) and flutter_test
widget testers to assert UI feedback and interactions.
| final credentials = AccessCredentials( | ||
| AccessToken('Bearer', accessToken, DateTime.now().add(const Duration(hours: 1)).toUtc()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded token expiry is unreliable.
The actual access token lifespan is determined by Google's auth server and may differ from the assumed 1-hour window. google_sign_in does not directly expose the real expiry. If the token is already partially expired by the time this code runs, the AccessCredentials object will carry a stale estimate. This is unlikely to cause issues for a single short-lived API call, but be aware this is a best-guess.
🤖 Prompt for AI Agents
In `@lib/services/google_meet_service.dart` around lines 35 - 36, The code
currently hardcodes a 1-hour expiry when constructing
AccessCredentials/AccessToken; instead, obtain the real expiry if available from
the GoogleSignInAuthentication response (e.g., an expiresIn field) and use that
to compute the DateTime, and if no expiry is exposed fall back to a conservative
short TTL (e.g., a few minutes) or force a fresh token request; update the
AccessCredentials/AccessToken creation to use
DateTime.now().toUtc().add(Duration(seconds: expiresIn)) when expiresIn is
present (and use a short fallback when absent) so the token lifetime is not
incorrectly assumed.
| // Return the meet link | ||
| return createdEvent.hangoutLink ?? | ||
| createdEvent.conferenceData?.entryPoints | ||
| ?.firstWhere( | ||
| (e) => e.entryPointType == 'video', | ||
| orElse: () => cal.EntryPoint(uri: ''), | ||
| ) | ||
| .uri; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fallback can return an empty string instead of null, breaking the caller's null-check.
When hangoutLink is null and entryPoints has no video entry, orElse returns EntryPoint(uri: ''). The caller (_createGoogleMeetLink) checks if (link != null) — an empty string passes that check, so the URL field gets set to "" and a "success" snackbar is shown with no usable link.
Suggested fix
- return createdEvent.hangoutLink ??
- createdEvent.conferenceData?.entryPoints
- ?.firstWhere(
- (e) => e.entryPointType == 'video',
- orElse: () => cal.EntryPoint(uri: ''),
- )
- .uri;
+ final link = createdEvent.hangoutLink ??
+ createdEvent.conferenceData?.entryPoints
+ ?.firstWhere(
+ (e) => e.entryPointType == 'video',
+ orElse: () => cal.EntryPoint(),
+ )
+ .uri;
+ return (link != null && link.isNotEmpty) ? link : null;🤖 Prompt for AI Agents
In `@lib/services/google_meet_service.dart` around lines 69 - 76, The current
return expression can yield an empty string (EntryPoint(uri: '')) which is
treated as non-null by the caller (_createGoogleMeetLink) and causes a
false-success; change the fallback so that when there is no hangoutLink and no
video entry in createdEvent.conferenceData?.entryPoints you return null instead
of an EntryPoint with an empty uri. Locate the expression using
createdEvent.hangoutLink and the firstWhere(orElse: () => cal.EntryPoint(uri:
'')) and replace the orElse/uri usage with logic that returns null when no video
entry exists (or check for existence before returning .uri) so callers see a
true null and can handle the missing link correctly.
|
Hi @SharkyBytes , |
Closes #150
📝 Description
This pull request improves the meeting creation experience by enabling Google Meet link generation directly within the Ell-ena app. Users can now create a Google Meet link on the same screen without switching to the Google Meet or Google Calendar app. The generated Meet URL is automatically filled, making the overall flow faster, smoother, and more user-friendly.
🔧 Changes Made
📷 Screenshots or Visual Changes (if applicable)
Screen.Recording.2026-02-06.204427.mp4
🤝 Collaboration
Collaborated with: (none)
✅ Checklist
Summary by CodeRabbit