diff --git a/lib/screens/meetings/create_meeting_screen.dart b/lib/screens/meetings/create_meeting_screen.dart index f74a72e..45bff65 100644 --- a/lib/screens/meetings/create_meeting_screen.dart +++ b/lib/screens/meetings/create_meeting_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../../services/supabase_service.dart'; +import '../../services/google_meet_service.dart'; class CreateMeetingScreen extends StatefulWidget { const CreateMeetingScreen({super.key}); @@ -15,11 +16,14 @@ class _CreateMeetingScreenState extends State { final _descriptionController = TextEditingController(); final _urlController = TextEditingController(); final _durationController = TextEditingController(text: '60'); // Default to 60 minutes + final _supabaseService = SupabaseService(); + final _googleMeetService = GoogleMeetService(); DateTime? _selectedDate; TimeOfDay? _selectedTime; bool _isLoading = false; + bool _isCreatingMeetLink = false; bool _isGoogleMeetUrl = true; @override @@ -44,6 +48,85 @@ class _CreateMeetingScreenState extends State { }); } + Future _createGoogleMeetLink() async { + if (_selectedDate == null || _selectedTime == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Please select date and time first'), + backgroundColor: Colors.red, + ), + ); + return; + } + + // Combine date and time + final meetingDateTime = DateTime( + _selectedDate!.year, + _selectedDate!.month, + _selectedDate!.day, + _selectedTime!.hour, + _selectedTime!.minute, + ); + + // Validate meeting is in the future + if (meetingDateTime.isBefore(DateTime.now())) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Meeting time must be in the future'), + backgroundColor: Colors.red, + ), + ); + return; + } + + // Parse duration + int duration = 60; + try { + duration = int.parse(_durationController.text.trim()); + if (duration <= 0) duration = 60; + } catch (e) { + // Default to 60 if parsing fails + duration = 60; + } + + setState(() => _isCreatingMeetLink = true); + + final link = await _googleMeetService.createMeetLink( + start: meetingDateTime, + durationMinutes: duration, + title: _titleController.text.trim().isNotEmpty + ? _titleController.text.trim() + : 'Meeting', + description: _descriptionController.text.trim(), + ); + + if (!mounted) return; + + setState(() => _isCreatingMeetLink = false); + + if (link != null) { + _urlController.text = link; + _checkUrl(link); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Google Meet link created successfully'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Failed to create Google Meet link. Please sign in with Google.'), + backgroundColor: Colors.orange, + action: SnackBarAction( + label: 'Retry', + onPressed: () => _createGoogleMeetLink(), + ), + ), + ); + } + } + Future _createMeeting() async { if (!_formKey.currentState!.validate()) return; @@ -384,6 +467,32 @@ class _CreateMeetingScreenState extends State { }, ), + // 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), + ), + ), + ), + // Warning message for non-Google Meet URLs if (_urlController.text.isNotEmpty && !_isGoogleMeetUrl) Padding( @@ -417,4 +526,4 @@ class _CreateMeetingScreenState extends State { ), ); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/lib/services/google_meet_service.dart b/lib/services/google_meet_service.dart new file mode 100644 index 0000000..a87e348 --- /dev/null +++ b/lib/services/google_meet_service.dart @@ -0,0 +1,89 @@ +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:googleapis/calendar/v3.dart' as cal; +import 'package:googleapis_auth/auth_io.dart'; +import 'package:http/http.dart' as http; + +class GoogleMeetService { + final GoogleSignIn _googleSignIn = GoogleSignIn( + scopes: [ + cal.CalendarApi.calendarScope, + cal.CalendarApi.calendarEventsScope, + ], + ); + + Future createMeetLink({ + required DateTime start, + required int durationMinutes, + required String title, + String? description, + }) async { + try { + // Sign in with Google + final account = await _googleSignIn.signIn(); + if (account == null) return null; + + // Get authentication + final authentication = await account.authentication; + final accessToken = authentication.accessToken; + + if (accessToken == null) { + print('No access token available'); + return null; + } + + // Create authenticated client using authenticatedClient function (FIXED) + final credentials = AccessCredentials( + AccessToken('Bearer', accessToken, DateTime.now().add(const Duration(hours: 1)).toUtc()), + null, // No refresh token needed for one-time use + _googleSignIn.scopes, + ); + + final authClient = authenticatedClient(http.Client(), credentials); + + try { + final calendarApi = cal.CalendarApi(authClient); + + final event = cal.Event( + summary: title, + description: description, + start: cal.EventDateTime(dateTime: start.toUtc()), + end: cal.EventDateTime( + dateTime: start.add(Duration(minutes: durationMinutes)).toUtc(), + ), + conferenceData: cal.ConferenceData( + createRequest: cal.CreateConferenceRequest( + requestId: DateTime.now().millisecondsSinceEpoch.toString(), + conferenceSolutionKey: cal.ConferenceSolutionKey( + type: 'hangoutsMeet', + ), + ), + ), + ); + + final createdEvent = await calendarApi.events.insert( + event, + 'primary', + conferenceDataVersion: 1, + ); + + // Return the meet link + return createdEvent.hangoutLink ?? + createdEvent.conferenceData?.entryPoints + ?.firstWhere( + (e) => e.entryPointType == 'video', + orElse: () => cal.EntryPoint(uri: ''), + ) + .uri; + } finally { + authClient.close(); + } + } catch (e) { + print('Error creating Google Meet link: $e'); + return null; + } + } + + Future signOut() async { + await _googleSignIn.signOut(); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index bba9397..9487694 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,6 +65,10 @@ dependencies: speech_to_text: ^7.3.0 + google_sign_in: ^6.2.1 + googleapis: ^13.1.0 + googleapis_auth: ^1.4.1 + dev_dependencies: flutter_test: sdk: flutter