diff --git a/lib/main.dart b/lib/main.dart index 7cb6f508..ef6ba1bf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; + import 'screens/splash_screen.dart'; import 'screens/home/home_screen.dart'; import 'screens/chat/chat_screen.dart'; + import 'services/navigation_service.dart'; import 'services/supabase_service.dart'; import 'services/ai_service.dart'; + import 'theme/theme_controller.dart'; import 'theme/app_themes.dart'; @@ -18,14 +21,15 @@ void main() async { } catch (e) { debugPrint('Error initializing services: $e'); } - + final themeController = await ThemeController.create(); -runApp( - WidgetsBindingObserverWidget( - child: ChangeNotifierProvider.value( - value: themeController, - child: const MyApp(), + runApp( + WidgetsBindingObserverWidget( + child: ChangeNotifierProvider.value( + value: themeController, + child: const MyApp(), + ), ), ), ); @@ -37,40 +41,51 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { final themeController = context.watch(); + return MaterialApp( title: 'Ell-ena', debugShowCheckedModeBanner: false, navigatorKey: NavigationService().navigatorKey, - navigatorObservers: [AppRouteObserver.instance], + navigatorObservers: [ + AppRouteObserver.instance, + ], theme: lightTheme, darkTheme: darkTheme, themeMode: themeController.flutterThemeMode, home: const SplashScreen(), + onGenerateRoute: (settings) { if (settings.name == '/') { return MaterialPageRoute( builder: (context) => const SplashScreen(), settings: settings, ); - } else if (settings.name == '/home') { - final args = (settings.arguments is Map) - ? settings.arguments as Map - : null; - + } + + else if (settings.name == '/home') { + final args = + settings.arguments is Map + ? settings.arguments as Map + : null; + return MaterialPageRoute( builder: (context) => HomeScreen(arguments: args), settings: settings, ); - } else if (settings.name == '/chat') { - final args = (settings.arguments is Map) - ? settings.arguments as Map - : null; - + } + + else if (settings.name == '/chat') { + final args = + settings.arguments is Map + ? settings.arguments as Map + : null; + return MaterialPageRoute( builder: (context) => ChatScreen(arguments: args), settings: settings, ); } + return null; }, ); @@ -78,50 +93,52 @@ class MyApp extends StatelessWidget { } class WidgetsBindingObserverWidget extends StatefulWidget { - final Widget child; + final Widget child; - const WidgetsBindingObserverWidget({ - super.key, - required this.child, - }); + const WidgetsBindingObserverWidget({ + super.key, + required this.child, + }); - @override - State createState() => - _WidgetsBindingObserverWidgetState(); - } + @override + State createState() => + _WidgetsBindingObserverWidgetState(); +} - class _WidgetsBindingObserverWidgetState - extends State - with WidgetsBindingObserver { +class _WidgetsBindingObserverWidgetState + extends State + with WidgetsBindingObserver { - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - } + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (state == AppLifecycleState.detached) { - SupabaseService().dispose(); // ✅ ONLY IMPORTANT LINE - } + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.detached) { + SupabaseService().dispose(); } + } - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } - @override - Widget build(BuildContext context) { - return widget.child; - } + @override + Widget build(BuildContext context) { + return widget.child; } +} // Simple singleton RouteObserver to allow screens to refresh on focus class AppRouteObserver extends RouteObserver> { AppRouteObserver._(); - static final AppRouteObserver instance = AppRouteObserver._(); + + static final AppRouteObserver instance = + AppRouteObserver._(); } diff --git a/lib/screens/calendar/calendar_screen.dart b/lib/screens/calendar/calendar_screen.dart index 37f0b39a..afa8cce0 100644 --- a/lib/screens/calendar/calendar_screen.dart +++ b/lib/screens/calendar/calendar_screen.dart @@ -4,6 +4,7 @@ import 'package:intl/intl.dart'; import 'dart:math'; import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; + import '../tasks/create_task_screen.dart'; import '../tickets/create_ticket_screen.dart'; import '../meetings/create_meeting_screen.dart'; @@ -23,23 +24,28 @@ class CalendarScreen extends StatefulWidget { class _CalendarScreenState extends State { CalendarFormat _calendarFormat = CalendarFormat.month; + DateTime _focusedDay = DateTime.now(); DateTime? _selectedDay; TimeOfDay? _selectedTime; + final Map> _events = {}; + final _supabaseService = SupabaseService(); + bool _isLoading = true; + String? _currentUserId; bool _isAdmin = false; - // Cache keys static const String _tasksKey = 'calendar_tasks'; static const String _ticketsKey = 'calendar_tickets'; static const String _meetingsKey = 'calendar_meetings'; static const String _lastFetchTimeKey = 'calendar_last_fetch_time'; - // Cache duration (5 minutes) static const Duration _cacheDuration = Duration(minutes: 5); + + double markerOpacity = 0.5; DateTime _getFirstBoundedDay(DateTime anchor) { return DateTime.utc(anchor.year - 1, anchor.month, anchor.day); @@ -58,32 +64,41 @@ class _CalendarScreenState extends State { @override void initState() { super.initState(); - // Safety clamp for initial dates in case system clock is off bounds + final now = DateTime.now(); final first = _getFirstBoundedDay(now); final last = _getLastBoundedDay(now); + final safeDay = _clampDay(now, first, last); _focusedDay = safeDay; _selectedDay = safeDay; + _loadCurrentUserInfo(); } Future _loadCurrentUserInfo() async { - setState(() { - _isLoading = true; - }); + if (mounted) { + setState(() { + _isLoading = true; + }); + } try { - final userProfile = await _supabaseService.getCurrentUserProfile(); + final userProfile = + await _supabaseService.getCurrentUserProfile(); + if (userProfile != null) { - _currentUserId = _supabaseService.client.auth.currentUser?.id; + _currentUserId = + _supabaseService.client.auth.currentUser?.id; + _isAdmin = userProfile['role'] == 'admin'; } await _loadEventsWithCache(); } catch (e) { debugPrint('Error loading user info: $e'); + } finally { if (mounted) { setState(() { _isLoading = false; @@ -92,25 +107,26 @@ class _CalendarScreenState extends State { } } - // Check if cache is valid Future _isCacheValid() async { try { final prefs = await SharedPreferences.getInstance(); - final lastFetchTimeStr = prefs.getString(_lastFetchTimeKey); + + final lastFetchTimeStr = + prefs.getString(_lastFetchTimeKey); if (lastFetchTimeStr == null) return false; - final lastFetchTime = DateTime.parse(lastFetchTimeStr); - final now = DateTime.now(); + final lastFetchTime = + DateTime.parse(lastFetchTimeStr); - return now.difference(lastFetchTime) < _cacheDuration; + return DateTime.now().difference(lastFetchTime) < + _cacheDuration; } catch (e) { - debugPrint('Error checking cache validity: $e'); + debugPrint('Cache validation error: $e'); return false; } } - // Load events from cache or network Future _loadEventsWithCache() async { try { if (!mounted) return; @@ -119,25 +135,18 @@ class _CalendarScreenState extends State { _isLoading = true; }); - // Clear existing events _events.clear(); - // Check if cache is valid - final isCacheValid = await _isCacheValid(); + final cacheValid = await _isCacheValid(); - if (isCacheValid) { - // Load from cache + if (cacheValid) { await _loadEventsFromCache(); } else { - // Load from network await _loadEventsFromNetwork(); - - // Save to cache await _saveEventsToCache(); } } catch (e) { - debugPrint('Error loading events with cache: $e'); - // Fallback to network if cache fails + debugPrint('Error loading events: $e'); await _loadEventsFromNetwork(); } finally { if (mounted) { @@ -148,222 +157,277 @@ class _CalendarScreenState extends State { } } - // Load events from SharedPreferences cache Future _loadEventsFromCache() async { try { final prefs = await SharedPreferences.getInstance(); - // Load tasks final tasksJson = prefs.getString(_tasksKey); + if (tasksJson != null) { - final tasks = List>.from( - jsonDecode(tasksJson).map((x) => Map.from(x))); + final tasks = + List>.from( + (jsonDecode(tasksJson) as List) + .map((x) => Map.from(x)), + ); + _processTasksData(tasks); } - // Load tickets - final ticketsJson = prefs.getString(_ticketsKey); + final ticketsJson = + prefs.getString(_ticketsKey); + if (ticketsJson != null) { - final tickets = List>.from( - jsonDecode(ticketsJson).map((x) => Map.from(x))); + final tickets = + List>.from( + (jsonDecode(ticketsJson) as List) + .map((x) => Map.from(x)), + ); + _processTicketsData(tickets); } - // Load meetings - final meetingsJson = prefs.getString(_meetingsKey); + final meetingsJson = + prefs.getString(_meetingsKey); + if (meetingsJson != null) { - final meetings = List>.from( - jsonDecode(meetingsJson).map((x) => Map.from(x))); + final meetings = + List>.from( + (jsonDecode(meetingsJson) as List) + .map((x) => Map.from(x)), + ); + _processMeetingsData(meetings); } - debugPrint('Events loaded from cache'); + debugPrint('Loaded events from cache'); } catch (e) { - debugPrint('Error loading events from cache: $e'); - // If cache loading fails, fall back to network + debugPrint('Cache load error: $e'); await _loadEventsFromNetwork(); } } - // Save events to SharedPreferences cache Future _saveEventsToCache() async { try { final prefs = await SharedPreferences.getInstance(); - // Save last fetch time await prefs.setString( - _lastFetchTimeKey, DateTime.now().toIso8601String()); - - // Tasks, tickets, and meetings are saved in their respective methods + _lastFetchTimeKey, + DateTime.now().toIso8601String(), + ); } catch (e) { - debugPrint('Error saving events to cache: $e'); + debugPrint('Save cache error: $e'); } } - // Load events from network Future _loadEventsFromNetwork() async { try { - // Load tasks, tickets, and meetings in parallel - final results = await Future.wait([ + await Future.wait([ _loadTasks(), _loadTickets(), _loadMeetings(), ]); - debugPrint('Events loaded from network'); + debugPrint('Loaded events from network'); } catch (e) { - debugPrint('Error loading events from network: $e'); + debugPrint('Network load error: $e'); } } - // Load tasks Future _loadTasks() async { try { final tasks = await _supabaseService.getTasks(); - // Filter tasks for current user (created by or assigned to) final filteredTasks = tasks.where((task) { final createdBy = task['created_by']; final assignedTo = task['assigned_to']; + return _isAdmin || createdBy == _currentUserId || assignedTo == _currentUserId || - assignedTo == null; + assignedTo == null || + assignedTo.toString().isEmpty; }).toList(); - // Process tasks data _processTasksData(filteredTasks); - // Save to cache final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_tasksKey, jsonEncode(filteredTasks)); + + await prefs.setString( + _tasksKey, + jsonEncode(filteredTasks), + ); } catch (e) { - debugPrint('Error loading tasks: $e'); + debugPrint('Task load error: $e'); } } - // Process tasks data - void _processTasksData(List> tasks) { + void _processTasksData( + List> tasks) { for (var task in tasks) { if (task['due_date'] != null) { - final dueDate = DateTime.parse(task['due_date']); - final dateOnly = DateTime(dueDate.year, dueDate.month, dueDate.day); - - if (!_events.containsKey(dateOnly)) { - _events[dateOnly] = []; - } - - _events[dateOnly]!.add(CalendarEvent( - title: task['title'] ?? 'Untitled Task', - startTime: const TimeOfDay(hour: 23, minute: 0), - endTime: const TimeOfDay(hour: 23, minute: 59), - type: EventType.task, - id: task['id'], - )); + final dueDate = + DateTime.parse(task['due_date']); + + final dateOnly = DateTime( + dueDate.year, + dueDate.month, + dueDate.day, + ); + + _events.putIfAbsent(dateOnly, () => []); + + _events[dateOnly]!.add( + CalendarEvent( + title: + task['title'] ?? 'Untitled Task', + startTime: + const TimeOfDay(hour: 23, minute: 0), + endTime: + const TimeOfDay(hour: 23, minute: 59), + type: EventType.task, + id: + task['id']?.toString() ?? '', + ), + ); } } } - // Load tickets Future _loadTickets() async { try { - final tickets = await _supabaseService.getTickets(); + final tickets = + await _supabaseService.getTickets(); - // Filter tickets for current user (created by or assigned to) final filteredTickets = tickets.where((ticket) { final createdBy = ticket['created_by']; final assignedTo = ticket['assigned_to']; + return _isAdmin || createdBy == _currentUserId || assignedTo == _currentUserId || - assignedTo == null; + assignedTo == null || + assignedTo.toString().isEmpty; }).toList(); - // Process tickets data _processTicketsData(filteredTickets); - // Save to cache final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_ticketsKey, jsonEncode(filteredTickets)); + + await prefs.setString( + _ticketsKey, + jsonEncode(filteredTickets), + ); } catch (e) { - debugPrint('Error loading tickets: $e'); + debugPrint('Ticket load error: $e'); } } - // Process tickets data - void _processTicketsData(List> tickets) { + void _processTicketsData( + List> tickets) { for (var ticket in tickets) { if (ticket['created_at'] != null) { - final createdAt = DateTime.parse(ticket['created_at']); - final dateOnly = - DateTime(createdAt.year, createdAt.month, createdAt.day); - - if (!_events.containsKey(dateOnly)) { - _events[dateOnly] = []; - } - - _events[dateOnly]!.add(CalendarEvent( - title: ticket['title'] ?? 'Untitled Ticket', - startTime: TimeOfDay(hour: createdAt.hour, minute: createdAt.minute), - endTime: - TimeOfDay.fromDateTime(createdAt.add(const Duration(hours: 1))), - type: EventType.ticket, - id: ticket['id'], - )); + final createdAt = + DateTime.parse(ticket['created_at']); + + final dateOnly = DateTime( + createdAt.year, + createdAt.month, + createdAt.day, + ); + + _events.putIfAbsent(dateOnly, () => []); + + _events[dateOnly]!.add( + CalendarEvent( + title: + ticket['title'] ?? 'Untitled Ticket', + startTime: TimeOfDay( + hour: createdAt.hour, + minute: createdAt.minute, + ), + endTime: TimeOfDay.fromDateTime( + createdAt.add( + const Duration(hours: 1), + ), + ), + type: EventType.ticket, + id: + ticket['id']?.toString() ?? '', + ), + ); } } } - // Load meetings Future _loadMeetings() async { try { - final meetings = await _supabaseService.getMeetings(); + final meetings = + await _supabaseService.getMeetings(); - // Process meetings data (all meetings are visible to everyone) _processMeetingsData(meetings); - // Save to cache final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_meetingsKey, jsonEncode(meetings)); + + await prefs.setString( + _meetingsKey, + jsonEncode(meetings), + ); } catch (e) { - debugPrint('Error loading meetings: $e'); + debugPrint('Meeting load error: $e'); } } - // Process meetings data - void _processMeetingsData(List> meetings) { + void _processMeetingsData( + List> meetings) { for (var meeting in meetings) { if (meeting['meeting_date'] != null) { - final meetingDate = DateTime.parse(meeting['meeting_date']); - final dateOnly = - DateTime(meetingDate.year, meetingDate.month, meetingDate.day); - - if (!_events.containsKey(dateOnly)) { - _events[dateOnly] = []; - } - - // For meetings, assume 1 hour duration - _events[dateOnly]!.add(CalendarEvent( - title: meeting['title'] ?? 'Untitled Meeting', - startTime: TimeOfDay.fromDateTime(meetingDate), - endTime: - TimeOfDay.fromDateTime(meetingDate.add(const Duration(hours: 1))), - type: EventType.meeting, - id: meeting['id'], - )); + final meetingDate = + DateTime.parse(meeting['meeting_date']); + + final dateOnly = DateTime( + meetingDate.year, + meetingDate.month, + meetingDate.day, + ); + + _events.putIfAbsent(dateOnly, () => []); + + _events[dateOnly]!.add( + CalendarEvent( + title: + meeting['title'] ?? + 'Untitled Meeting', + startTime: + TimeOfDay.fromDateTime( + meetingDate), + endTime: + TimeOfDay.fromDateTime( + meetingDate.add( + const Duration(hours: 1), + ), + ), + type: EventType.meeting, + id: + meeting['id']?.toString() ?? '', + ), + ); } } } - List _getEventsForDay(DateTime day) { - final normalizedDay = DateTime(day.year, day.month, day.day); + List _getEventsForDay( + DateTime day) { + final normalizedDay = + DateTime(day.year, day.month, day.day); + return _events[normalizedDay] ?? []; } @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, body: _isLoading ? const CalendarLoadingSkeleton() : SafeArea( @@ -372,7 +436,9 @@ class _CalendarScreenState extends State { const SizedBox(height: 8), _buildCalendar(), const SizedBox(height: 12), - Expanded(child: _buildTimeScale()), + Expanded( + child: _buildTimeScale(), + ), ], ), ), @@ -380,32 +446,36 @@ class _CalendarScreenState extends State { } Widget _buildCalendar() { - final colorScheme = Theme.of(context).colorScheme; + final colorScheme = + Theme.of(context).colorScheme; - // Create a dynamic rolling window to avoid unbounded memory growth final now = DateTime.now(); + final first = _getFirstBoundedDay(now); final last = _getLastBoundedDay(now); - // Safely clamp focused day to mathematically prevent table_calendar runtime crashes - final safeFocusedDay = _clampDay(_focusedDay, first, last); + final safeFocusedDay = + _clampDay(_focusedDay, first, last); return Container( - margin: const EdgeInsets.symmetric(horizontal: 8), + margin: + const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular(20), ), child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: TableCalendar( firstDay: first, lastDay: last, focusedDay: safeFocusedDay, calendarFormat: _calendarFormat, - selectedDayPredicate: (day) => isSameDay(_selectedDay, day), + selectedDayPredicate: (day) => + isSameDay(_selectedDay, day), eventLoader: _getEventsForDay, - onDaySelected: (selectedDay, focusedDay) { + onDaySelected: + (selectedDay, focusedDay) { setState(() { _selectedDay = selectedDay; _focusedDay = focusedDay; @@ -417,26 +487,44 @@ class _CalendarScreenState extends State { }); }, calendarBuilders: CalendarBuilders( - markerBuilder: (context, date, events) { - if (events.isEmpty) return const SizedBox.shrink(); - final isLight = Theme.of(context).brightness == Brightness.light; - final markerOpacity = isLight ? 0.55 : 0.3; + markerBuilder: + (context, date, events) { + if (events.isEmpty) { + return const SizedBox.shrink(); + } + + final isLight = + Theme.of(context).brightness == + Brightness.light; + + final markerOpacity = + isLight ? 0.55 : 0.3; + return Positioned( bottom: 1, child: Container( height: 16, - width: events.length > 3 ? 35 : (events.length * 8 + 10), + width: events.length > 3 + ? 35 + : (events.length * 8 + 10), decoration: BoxDecoration( - color: Colors.green.withOpacity(markerOpacity), - borderRadius: BorderRadius.circular(10), + color: Colors.green + .withOpacity( + markerOpacity, + ), + borderRadius: + BorderRadius.circular(10), ), child: Center( child: Text( '${events.length}', style: TextStyle( - color: Theme.of(context).colorScheme.onPrimary, + color: Theme.of(context) + .colorScheme + .onPrimary, fontSize: 10, - fontWeight: FontWeight.bold, + fontWeight: + FontWeight.bold, ), ), ), @@ -444,44 +532,6 @@ class _CalendarScreenState extends State { ); }, ), - calendarStyle: CalendarStyle( - defaultTextStyle: TextStyle(color: colorScheme.onSurface), - weekendTextStyle: TextStyle(color: colorScheme.onSurfaceVariant), - selectedTextStyle: TextStyle(color: colorScheme.onPrimary), - todayTextStyle: TextStyle(color: colorScheme.onSurface), - outsideTextStyle: - TextStyle(color: colorScheme.onSurface.withOpacity(0.38)), - selectedDecoration: const BoxDecoration( - color: Colors.green, - shape: BoxShape.circle, - ), - todayDecoration: BoxDecoration( - color: Colors.green.withOpacity(0.8), - shape: BoxShape.circle, - ), - markersMaxCount: 0, - markerDecoration: const BoxDecoration( - color: Colors.green, - shape: BoxShape.circle, - ), - ), - headerStyle: HeaderStyle( - formatButtonVisible: false, - titleCentered: true, - titleTextStyle: TextStyle( - color: colorScheme.onSurface, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - leftChevronIcon: - Icon(Icons.chevron_left, color: colorScheme.onSurface), - rightChevronIcon: - Icon(Icons.chevron_right, color: colorScheme.onSurface), - ), - daysOfWeekStyle: DaysOfWeekStyle( - weekdayStyle: TextStyle(color: colorScheme.onSurface), - weekendStyle: TextStyle(color: colorScheme.onSurfaceVariant), - ), ), ), ); @@ -489,34 +539,51 @@ class _CalendarScreenState extends State { Widget _buildTimeScale() { return ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), itemCount: 24, itemBuilder: (context, index) { final hour = index; - final time = TimeOfDay(hour: hour, minute: 0); - final events = _getEventsForHour(hour); - // Calculate dynamic height based on number of events (minimum 60) + final time = + TimeOfDay(hour: hour, minute: 0); + + final events = + _getEventsForHour(hour); + final double timeSlotHeight = - events.isEmpty ? 60 : max(60, events.length * 40.0); + events.isEmpty + ? 60 + : max(60, events.length * 40); return InkWell( onTap: () { - setState(() => _selectedTime = time); + setState(() { + _selectedTime = time; + }); + _showCreateDialog(time); }, child: Container( height: timeSlotHeight, - margin: const EdgeInsets.only(bottom: 1), + margin: + const EdgeInsets.only(bottom: 1), child: Row( children: [ SizedBox( width: 50, child: Text( - '${time.format(context).toLowerCase()}', + time + .format(context) + .toLowerCase(), style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: 12), + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + fontSize: 12, + ), ), ), const SizedBox(width: 8), @@ -525,18 +592,25 @@ class _CalendarScreenState extends State { decoration: BoxDecoration( border: Border( top: BorderSide( - color: Theme.of(context).colorScheme.outline, + color: Theme.of(context) + .colorScheme + .outline, width: 0.5, ), ), ), child: events.isEmpty - ? Container() // Empty container if no events + ? Container() : ListView.builder( - physics: const NeverScrollableScrollPhysics(), - itemCount: events.length, - itemBuilder: (context, index) { - return _buildEventCard(events[index]); + shrinkWrap: true, + physics: + const NeverScrollableScrollPhysics(), + itemCount: + events.length, + itemBuilder: + (context, index) { + return _buildEventCard( + events[index]); }, ), ), @@ -549,138 +623,158 @@ class _CalendarScreenState extends State { ); } - List _getEventsForHour(int hour) { + List _getEventsForHour( + int hour) { if (_selectedDay == null) return []; - final dayEvents = _getEventsForDay(_selectedDay!); - return dayEvents.where((event) => event.startTime.hour == hour).toList(); + + final dayEvents = + _getEventsForDay(_selectedDay!); + + return dayEvents + .where((event) => + event.startTime.hour == hour) + .toList(); } - Widget _buildEventCard(CalendarEvent event) { + Widget _buildEventCard( + CalendarEvent event) { return GestureDetector( onTap: () => _handleEventTap(event), child: Container( margin: const EdgeInsets.only(top: 4), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( - color: event.type.color.withOpacity(0.2), - borderRadius: BorderRadius.circular(8), - border: Border.all(color: event.type.color, width: 1), + color: + event.type.color.withOpacity(markerOpacity), + borderRadius: + BorderRadius.circular(8), + border: Border.all( + color: event.type.color, + ), ), child: Row( - mainAxisSize: MainAxisSize.min, children: [ - Icon(event.type.icon, color: event.type.color, size: 16), + Icon( + event.type.icon, + color: event.type.color, + size: 16, + ), const SizedBox(width: 4), Expanded( child: Text( event.title, + overflow: TextOverflow.ellipsis, style: TextStyle( color: event.type.color, + fontWeight: + FontWeight.bold, fontSize: 12, - fontWeight: FontWeight.bold, ), - overflow: TextOverflow.ellipsis, ), ), - Text( - '${event.startTime.format(context)} - ${event.endTime.format(context)}', - style: TextStyle(color: event.type.color, fontSize: 10), - ), - const SizedBox(width: 4), - Icon( - Icons.edit, - color: event.type.color, - size: 14, - ), ], ), ), ); } - Future _handleEventTap(CalendarEvent event) async { + Future _handleEventTap( + CalendarEvent event) async { dynamic result; switch (event.type) { case EventType.meeting: - // Navigate to meeting detail screen result = await Navigator.push( context, MaterialPageRoute( - builder: (context) => MeetingDetailScreen(meetingId: event.id), + builder: (_) => + MeetingDetailScreen( + meetingId: event.id, + ), ), ); break; + case EventType.task: - // Navigate to task detail screen result = await Navigator.push( context, MaterialPageRoute( - builder: (context) => TaskDetailScreen(taskId: event.id), + builder: (_) => + TaskDetailScreen( + taskId: event.id, + ), ), ); break; + case EventType.ticket: - // Navigate to ticket detail screen result = await Navigator.push( context, MaterialPageRoute( - builder: (context) => TicketDetailScreen(ticketId: event.id), + builder: (_) => + TicketDetailScreen( + ticketId: event.id, + ), ), ); break; } - // Refresh events if something was updated - if (result == true) { - await _loadEventsWithCache(); // Use cache loading + if (result == true && mounted) { + await _loadEventsWithCache(); } } - void _showCreateDialog(TimeOfDay selectedTime) { + void _showCreateDialog( + TimeOfDay selectedTime) { showDialog( context: context, - builder: (context) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.surface, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), + builder: (_) => AlertDialog( title: Text( 'Create at ${selectedTime.format(context)}', - style: Theme.of(context) - .textTheme - .titleLarge - ?.copyWith(fontWeight: FontWeight.bold), ), content: Column( mainAxisSize: MainAxisSize.min, children: [ _buildDialogOption( - 'Schedule a Meeting', + 'Schedule Meeting', Icons.people, Colors.blue, - () => _handleCreate(EventType.meeting, selectedTime), + () => _handleCreate( + EventType.meeting, + selectedTime, + ), ), const SizedBox(height: 8), _buildDialogOption( - 'Create a Task', + 'Create Task', Icons.task, Colors.green, - () => _handleCreate(EventType.task, selectedTime), + () => _handleCreate( + EventType.task, + selectedTime, + ), ), const SizedBox(height: 8), _buildDialogOption( - 'Create a Ticket', + 'Create Ticket', Icons.confirmation_number, Colors.orange, - () => _handleCreate(EventType.ticket, selectedTime), + () => _handleCreate( + EventType.ticket, + selectedTime, + ), ), const SizedBox(height: 8), _buildDialogOption( 'Create with Ell-ena AI', Icons.smart_toy, Colors.purple, - () => _handleCreateWithAI(selectedTime), + () => _handleCreateWithAI( + selectedTime), ), ], ), @@ -697,10 +791,12 @@ class _CalendarScreenState extends State { return InkWell( onTap: onTap, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), + color: + color.withOpacity(markerOpacity), + borderRadius: + BorderRadius.circular(12), ), child: Row( children: [ @@ -710,8 +806,8 @@ class _CalendarScreenState extends State { title, style: TextStyle( color: color, - fontSize: 16, - fontWeight: FontWeight.bold, + fontWeight: + FontWeight.bold, ), ), ], @@ -720,8 +816,11 @@ class _CalendarScreenState extends State { ); } - Future _handleCreate(EventType type, TimeOfDay selectedTime) async { - Navigator.of(context).pop(); // Close dialog + Future _handleCreate( + EventType type, + TimeOfDay selectedTime, + ) async { + Navigator.pop(context); if (_selectedDay == null) return; @@ -745,6 +844,7 @@ class _CalendarScreenState extends State { ), ); break; + case EventType.task: result = await Navigator.push( context, @@ -754,6 +854,7 @@ class _CalendarScreenState extends State { ), ); break; + case EventType.ticket: result = await Navigator.push( context, @@ -765,15 +866,14 @@ class _CalendarScreenState extends State { break; } - // Refresh events if something was created - if (result == true) { - await _loadEventsWithCache(); // Use cache loading + if (result == true && mounted) { + await _loadEventsWithCache(); } } - // Handle creation with AI assistant - void _handleCreateWithAI(TimeOfDay selectedTime) { - Navigator.of(context).pop(); // Close dialog + void _handleCreateWithAI( + TimeOfDay selectedTime) { + Navigator.pop(context); if (_selectedDay == null) return; @@ -785,18 +885,25 @@ class _CalendarScreenState extends State { selectedTime.minute, ); - // Format the date for the AI - final formattedDate = DateFormat('yyyy-MM-dd').format(selectedDateTime); - final formattedTime = selectedTime.format(context); + final formattedDate = + DateFormat('yyyy-MM-dd') + .format(selectedDateTime); + + final formattedTime = + selectedTime.format(context); + final message = - 'I need to create a task for $formattedDate at $formattedTime'; + 'I need to create a task for ' + '$formattedDate at $formattedTime'; - // Use the NavigationService to navigate to the chat screen Navigator.push( context, MaterialPageRoute( - builder: (context) => - ChatScreen(arguments: {'initial_message': message}), + builder: (_) => ChatScreen( + arguments: { + 'initial_message': message, + }, + ), ), ); } @@ -821,11 +928,19 @@ class CalendarEvent { enum EventType { meeting(Icons.people, Colors.blue, 'Meeting'), task(Icons.task, Colors.green, 'Task'), - ticket(Icons.confirmation_number, Colors.orange, 'Ticket'); + ticket( + Icons.confirmation_number, + Colors.orange, + 'Ticket', + ); final IconData icon; - final MaterialColor color; + final Color color; final String label; - const EventType(this.icon, this.color, this.label); + const EventType( + this.icon, + this.color, + this.label, + ); } diff --git a/lib/screens/meetings/create_meeting_screen.dart b/lib/screens/meetings/create_meeting_screen.dart index 4863a02c..e32ead2d 100644 --- a/lib/screens/meetings/create_meeting_screen.dart +++ b/lib/screens/meetings/create_meeting_screen.dart @@ -7,7 +7,8 @@ class CreateMeetingScreen extends StatefulWidget { CreateMeetingScreen({super.key,}); @override - State createState() => _CreateMeetingScreenState(); + State createState() => + _CreateMeetingScreenState(); } class _CreateMeetingScreenState extends State { diff --git a/lib/screens/tasks/create_task_screen.dart b/lib/screens/tasks/create_task_screen.dart index 66e52de9..88bdaac8 100644 --- a/lib/screens/tasks/create_task_screen.dart +++ b/lib/screens/tasks/create_task_screen.dart @@ -2,10 +2,16 @@ import 'package:flutter/material.dart'; import '../../services/supabase_service.dart'; class CreateTaskScreen extends StatefulWidget { - const CreateTaskScreen({super.key}); + final DateTime? initialDateTime; + + const CreateTaskScreen({ + super.key, + this.initialDateTime, + }); @override - State createState() => _CreateTaskScreenState(); + State createState() => + _CreateTaskScreenState(); } class _CreateTaskScreenState extends State { @@ -20,6 +26,7 @@ class _CreateTaskScreenState extends State { @override void initState() { + super.initState(); _loadTeamMembers(); } diff --git a/lib/screens/tickets/create_ticket_screen.dart b/lib/screens/tickets/create_ticket_screen.dart index 2455b465..dc11ed95 100644 --- a/lib/screens/tickets/create_ticket_screen.dart +++ b/lib/screens/tickets/create_ticket_screen.dart @@ -2,10 +2,16 @@ import 'package:flutter/material.dart'; import '../../services/supabase_service.dart'; class CreateTicketScreen extends StatefulWidget { - const CreateTicketScreen({super.key}); + final DateTime? initialDateTime; + + const CreateTicketScreen({ + super.key, + this.initialDateTime, + }); @override - State createState() => _CreateTicketScreenState(); + State createState() => + _CreateTicketScreenState(); } class _CreateTicketScreenState extends State { diff --git a/lib/services/ai_service.dart b/lib/services/ai_service.dart index 151cfc33..ade5a1a4 100644 --- a/lib/services/ai_service.dart +++ b/lib/services/ai_service.dart @@ -1,11 +1,27 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; + import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import 'package:ell_ena/services/supabase_service.dart'; import 'package:ell_ena/services/meeting_formatter.dart'; +import 'package:ell_ena/config/app_config.dart'; + +class AIService { + late final String _apiKey; + + AIService() { + _apiKey = AppConfig.geminiApiKey; + if (_apiKey.isEmpty) { + throw Exception("Missing GEMINI_API_KEY"); + } + } +} +class AppConfig { + static const geminiApiKey = + String.fromEnvironment('GEMINI_API_KEY'); +} class AIService { static final AIService _instance = AIService._internal(); final String _apiUrl = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent'; @@ -32,7 +48,7 @@ class AIService { debugPrint('Error loading .env file: $e'); }); - _apiKey = dotenv.env['GEMINI_API_KEY']; + _apiKey = AppConfig.geminiApiKey; if (_apiKey == null || _apiKey!.isEmpty) { throw Exception('Missing Gemini API key. Please check your .env file.'); diff --git a/lib/services/app_config.dart b/lib/services/app_config.dart new file mode 100644 index 00000000..a7184b3a --- /dev/null +++ b/lib/services/app_config.dart @@ -0,0 +1,13 @@ +class AppConfig { + static const supabaseUrl = + String.fromEnvironment('SUPABASE_URL'); + + static const supabaseAnonKey = + String.fromEnvironment('SUPABASE_ANON_KEY'); + + static const geminiApiKey = + String.fromEnvironment('GEMINI_API_KEY'); + + static const oauthRedirectUrl = + String.fromEnvironment('OAUTH_REDIRECT_URL'); +} \ No newline at end of file diff --git a/lib/services/supabase_service.dart b/lib/services/supabase_service.dart index d361b341..a2169b53 100644 --- a/lib/services/supabase_service.dart +++ b/lib/services/supabase_service.dart @@ -1,13 +1,20 @@ import 'dart:convert'; import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; + import 'package:supabase_flutter/supabase_flutter.dart'; import 'dart:async'; import 'package:shared_preferences/shared_preferences.dart'; +class AppConfig { + static const supabaseUrl = String.fromEnvironment('SUPABASE_URL'); + static const supabaseAnonKey = String.fromEnvironment('SUPABASE_ANON_KEY'); +} + class SupabaseService { static final SupabaseService _instance = SupabaseService._internal(); + + late final SupabaseClient _client; bool _isInitialized = false; bool _disposed = false; @@ -48,13 +55,13 @@ class SupabaseService { Future initialize() async { if (_isInitialized) return; - try { - await dotenv.load().catchError((e) { - debugPrint('Error loading .env file: $e'); - }); + if (AppConfig.supabaseUrl.isEmpty || + AppConfig.supabaseAnonKey.isEmpty) { + throw Exception("Missing environment variables"); +} - final supabaseUrl = dotenv.env['SUPABASE_URL'] ?? ''; - final supabaseAnonKey = dotenv.env['SUPABASE_ANON_KEY'] ?? ''; + final supabaseUrl = AppConfig.supabaseUrl; +final supabaseAnonKey = AppConfig.supabaseAnonKey; if (supabaseUrl.isEmpty || supabaseAnonKey.isEmpty) { throw Exception( diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 3792af4b..1a9d148c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,12 +7,16 @@ #include "generated_plugin_registrant.h" #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) gtk_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); gtk_plugin_register_with_registrar(gtk_registrar); + g_autoptr(FlPluginRegistrar) printing_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin"); + printing_plugin_register_with_registrar(printing_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 5d074230..1db2f435 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST gtk + printing url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 92b64978..6c34fdaf 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,13 +6,19 @@ import FlutterMacOS import Foundation import app_links +import google_sign_in_ios import path_provider_foundation +import printing import shared_preferences_foundation +import speech_to_text import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) + FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SpeechToTextPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 2d3a236b..7ba59976 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _discoveryapis_commons: + dependency: transitive + description: + name: _discoveryapis_commons + sha256: "113c4100b90a5b70a983541782431b82168b3cae166ab130649c36eb3559d498" + url: "https://pub.dev" + source: hosted + version: "1.0.7" adaptive_number: dependency: transitive description: @@ -93,10 +101,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -222,14 +230,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_dotenv: - dependency: "direct main" - description: - name: flutter_dotenv - sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b - url: "https://pub.dev" - source: hosted - version: "5.2.1" flutter_launcher_icons: dependency: "direct dev" description: @@ -272,6 +272,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.0" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" + url: "https://pub.dev" + source: hosted + version: "0.3.3+1" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + sha256: d0a2c3bcb06e607bb11e4daca48bd4b6120f0bbc4015ccebbe757d24ea60ed2a + url: "https://pub.dev" + source: hosted + version: "6.3.0" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: d5e23c56a4b84b6427552f1cf3f98f716db3b1d1a647f16b96dbb5b93afa2805 + url: "https://pub.dev" + source: hosted + version: "6.2.1" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: "102005f498ce18442e7158f6791033bbc15ad2dcc0afa4cf4752e2722a516c96" + url: "https://pub.dev" + source: hosted + version: "5.9.0" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded" + url: "https://pub.dev" + source: hosted + version: "0.12.4+4" + googleapis: + dependency: "direct main" + description: + name: googleapis + sha256: "864f222aed3f2ff00b816c675edf00a39e2aaf373d728d8abec30b37bee1a81c" + url: "https://pub.dev" + source: hosted + version: "13.2.0" + googleapis_auth: + dependency: "direct main" + description: + name: googleapis_auth + sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938 + url: "https://pub.dev" + source: hosted + version: "1.6.0" gotrue: dependency: transitive description: @@ -340,26 +404,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -388,26 +452,26 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -416,6 +480,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -608,6 +680,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.14.2" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" qr: dependency: transitive description: @@ -817,10 +897,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.10" typed_data: dependency: transitive description: @@ -897,10 +977,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -949,6 +1029,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" yet_another_json_isolate: dependency: transitive description: @@ -958,5 +1046,5 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=3.8.0 <=3.8.1" + dart: ">=3.9.0-0 <4.0.0" flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index b4854649..972e5343 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,7 +38,7 @@ dependencies: # Supabase supabase_flutter: ^2.3.4 - flutter_dotenv: ^5.2.1 + # State management provider: ^6.1.2 @@ -97,7 +97,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - .env + - ELL-ena-logo/png/ - ELL-ena-logo/svg/ # assets: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 785a046f..a16341e9 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,11 +7,20 @@ #include "generated_plugin_registrant.h" #include +#include +#include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { AppLinksPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + PrintingPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PrintingPlugin")); + SpeechToTextWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SpeechToTextWindows")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 8f8ee4f2..2d82e750 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,9 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links + permission_handler_windows + printing + speech_to_text_windows url_launcher_windows )