diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 395bef1..984ed36 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -24,10 +24,14 @@ the Android process has started. This theme is visible to the user while the Flutter UI initializes. After that, this theme continues to determine the Window background behind the Flutter UI. --> - + /> --> + + @@ -44,6 +48,8 @@ + + diff --git a/android/app/src/main/kotlin/org/aossie/ell_ena/MainActivity.kt b/android/app/src/main/kotlin/org/aossie/ell_ena/MainActivity.kt index e61508f..0dd7a7f 100644 --- a/android/app/src/main/kotlin/org/aossie/ell_ena/MainActivity.kt +++ b/android/app/src/main/kotlin/org/aossie/ell_ena/MainActivity.kt @@ -1,5 +1,86 @@ package org.aossie.ell_ena +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.util.Log import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel -class MainActivity : FlutterActivity() \ No newline at end of file +class MainActivity : FlutterActivity() { + private val CHANNEL = "app_shortcuts" + private var pendingRoute: String? = null + private var methodChannel: MethodChannel? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + handleIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + handleIntent(intent) + setIntent(intent) // Important: update the current intent + + // ✅ Send route to Flutter immediately when app is resumed + sendRouteToFlutter() + } + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + + methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) + + // Setup method call handler + methodChannel?.setMethodCallHandler { call, result -> + when (call.method) { + "getInitialRoute" -> { + result.success(pendingRoute) + pendingRoute = null // Clear after sending + } + else -> result.notImplemented() + } + } + + // Try to send initial route immediately if Flutter is ready + sendRouteToFlutter() + } + + private fun handleIntent(intent: Intent?) { + // Method 1: Try to get route from deep link (app://shortcut/chat) + val uri: Uri? = intent?.data + + if (uri != null && uri.scheme == "app" && uri.host == "shortcut") { + val route = uri.path?.removePrefix("/") // Remove leading "/" + pendingRoute = route + return + } + + // Method 2: Try to get screen index from extras (fallback) + val screenIndex = intent?.getIntExtra("screen", -1) + if (screenIndex != -1) { + pendingRoute = when (screenIndex) { + 0 -> "dashboard" + 1 -> "calendar" + 2 -> "workspace" + 3 -> "chat" + 4 -> "profile" + else -> null + } + } + } + + private fun sendRouteToFlutter() { + if (pendingRoute != null && methodChannel != null) { + try { + methodChannel?.invokeMethod("navigate", pendingRoute) + pendingRoute = null + } catch (e: Exception) { + // ✅ Added logging for observability + Log.w("MainActivity", "Failed to send pendingRoute to Flutter: ${e.message}") + // Flutter might not be ready yet, route will be sent when getInitialRoute is called + } + } + } +} diff --git a/android/app/src/main/res/drawable-mdpi/ic_calendar.png b/android/app/src/main/res/drawable-mdpi/ic_calendar.png new file mode 100644 index 0000000..a88f93d Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_calendar.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_chat.png b/android/app/src/main/res/drawable-mdpi/ic_chat.png new file mode 100644 index 0000000..7d33ed8 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_chat.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_dashboard.png b/android/app/src/main/res/drawable-mdpi/ic_dashboard.png new file mode 100644 index 0000000..58c9062 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_dashboard.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_profile.png b/android/app/src/main/res/drawable-mdpi/ic_profile.png new file mode 100644 index 0000000..4f4d57f Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_profile.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_workspace.png b/android/app/src/main/res/drawable-mdpi/ic_workspace.png new file mode 100644 index 0000000..eaf5cef Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_workspace.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_dashboard.png b/android/app/src/main/res/mipmap-hdpi/ic_dashboard.png new file mode 100644 index 0000000..58c9062 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_dashboard.png differ diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..0531b63 --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,20 @@ + + + Ell-ena + + + Dashboard + Dashboard + + Calendar + Calendar + + Workspace + Workspace + + Chat + Chat + + Profile + Profile + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 5fac679..a9f75e5 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,18 +1,14 @@ - + + - + + diff --git a/android/app/src/main/res/xml/shortcuts.xml b/android/app/src/main/res/xml/shortcuts.xml new file mode 100644 index 0000000..d07f1c2 --- /dev/null +++ b/android/app/src/main/res/xml/shortcuts.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/main.dart b/lib/main.dart index b5f4e77..70aefac 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,24 +1,28 @@ - - import 'package:flutter/material.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 'services/app_shortcuts_service.dart'; // Add this import void main() async { WidgetsFlutterBinding.ensureInitialized(); - + try { - await SupabaseService().initialize(); + // Initialize app shortcuts service first + await AppShortcutsService.initialize(); + // Initialize other services + await SupabaseService().initialize(); await AIService().initialize(); } catch (e) { debugPrint('Error initializing services: $e'); } - + runApp(const MyApp()); } @@ -30,8 +34,12 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'Ell-ena', debugShowCheckedModeBanner: false, + navigatorKey: NavigationService().navigatorKey, - navigatorObservers: [AppRouteObserver.instance], + navigatorObservers: [ + AppRouteObserver.instance, + ], + theme: ThemeData( useMaterial3: true, brightness: Brightness.dark, @@ -49,37 +57,83 @@ class MyApp extends StatelessWidget { fontWeight: FontWeight.bold, letterSpacing: 1.2, ), - bodyLarge: TextStyle(fontSize: 16, letterSpacing: 0.5), + bodyLarge: TextStyle( + fontSize: 16, + letterSpacing: 0.5, + ), ), ), + 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 as Map?; - return MaterialPageRoute( - builder: (context) => HomeScreen(arguments: args), - settings: settings, - ); - } else if (settings.name == '/chat') { - final args = settings.arguments as Map?; - return MaterialPageRoute( - builder: (context) => ChatScreen(arguments: args), - settings: settings, - ); + // Get initial shortcut if any + final initialShortcut = AppShortcutsService.getPendingShortcut(); + + // Convert shortcut to screen index + int getScreenIndex(String? shortcut) { + switch (shortcut) { + case 'dashboard': return 0; + case 'calendar': return 1; + case 'workspace': return 2; + case 'chat': return 3; + case 'profile': return 4; + default: return 0; + } + } + + switch (settings.name) { + case '/': + return MaterialPageRoute( + builder: (_) => const SplashScreen(), + settings: settings, + ); + + case '/home': + Map? args; + + // If there's an initial shortcut, use it + if (initialShortcut != null) { + args = { + 'screen': getScreenIndex(initialShortcut), + 'initial_route': initialShortcut + }; + } + + // Merge with any existing arguments + if (settings.arguments != null) { + args = { + ...args ?? {}, + ...settings.arguments as Map, + }; + } + + return MaterialPageRoute( + builder: (_) => HomeScreen(arguments: args), + settings: settings, + ); + + case '/chat': + final args = settings.arguments as Map?; + return MaterialPageRoute( + builder: (_) => ChatScreen(arguments: args), + settings: settings, + ); + + default: + return MaterialPageRoute( + builder: (_) => const SplashScreen(), + settings: settings, + ); } - return null; }, ); } } -// Simple singleton RouteObserver to allow screens to refresh on focus +/// Simple singleton RouteObserver +/// Used so screens can refresh when they regain focus class AppRouteObserver extends RouteObserver> { AppRouteObserver._(); static final AppRouteObserver instance = AppRouteObserver._(); -} +} \ No newline at end of file diff --git a/lib/screens/auth/login_screen.dart b/lib/screens/auth/login_screen.dart index c8cf293..1ab0a49 100644 --- a/lib/screens/auth/login_screen.dart +++ b/lib/screens/auth/login_screen.dart @@ -3,13 +3,16 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import '../../widgets/custom_widgets.dart'; import '../../services/navigation_service.dart'; import '../../services/supabase_service.dart'; +import '../../services/app_shortcuts_service.dart'; import '../home/home_screen.dart'; import 'signup_screen.dart'; import 'forgot_password_screen.dart'; import 'team_selection_dialog.dart'; class LoginScreen extends StatefulWidget { - const LoginScreen({super.key}); + final Map? arguments; + + const LoginScreen({super.key, this.arguments}); @override State createState() => _LoginScreenState(); @@ -29,6 +32,10 @@ class _LoginScreenState extends State @override void initState() { super.initState(); + + // Debug arguments + print('[LoginScreen] initState called with arguments: ${widget.arguments}'); + _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1000), @@ -68,15 +75,32 @@ class _LoginScreenState extends State setState(() => _isLoading = true); try { - // Login with Supabase final response = await _supabaseService.client.auth.signInWithPassword( email: _emailController.text, password: _passwordController.text, ); if (response.user != null) { + // Merge pending shortcut and existing arguments + Map homeScreenArgs = Map.from(widget.arguments ?? {}); + final pendingShortcut = AppShortcutsService.getPendingShortcut(); + + if (pendingShortcut != null) { + final screenIndex = _getScreenIndex(pendingShortcut); + if (screenIndex != null) { + homeScreenArgs['screen'] = screenIndex; + homeScreenArgs['initial_route'] = pendingShortcut; + } + } + + print('[LoginScreen] Navigating to HomeScreen with args: $homeScreenArgs'); + if (mounted) { - NavigationService().navigateToReplacement(const HomeScreen()); + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => HomeScreen(arguments: homeScreenArgs), + ), + ); } } else { if (mounted) { @@ -89,15 +113,34 @@ class _LoginScreenState extends State } } } catch (e) { + print('[LoginScreen] Login error: $e'); // Log for debugging if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString()), backgroundColor: Colors.red), + const SnackBar( + content: Text('Login failed. Please check your credentials and try again.'), + backgroundColor: Colors.red, + ), ); } } finally { - if (mounted) { - setState(() => _isLoading = false); - } + if (mounted) setState(() => _isLoading = false); + } + } + + int? _getScreenIndex(String? route) { + switch (route) { + case 'dashboard': + return 0; + case 'calendar': + return 1; + case 'workspace': + return 2; + case 'chat': + return 3; + case 'profile': + return 4; + default: + return null; } } @@ -167,12 +210,8 @@ class _LoginScreenState extends State label: 'Email', icon: Icons.email_outlined, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your email'; - } - if (!value.contains('@')) { - return 'Please enter a valid email'; - } + if (value == null || value.isEmpty) return 'Please enter your email'; + if (!value.contains('@')) return 'Please enter a valid email'; return null; }, ), @@ -183,12 +222,8 @@ class _LoginScreenState extends State icon: Icons.lock_outline, isPassword: true, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your password'; - } - if (value.length < 6) { - return 'Password must be at least 6 characters'; - } + if (value == null || value.isEmpty) return 'Please enter your password'; + if (value.length < 6) return 'Password must be at least 6 characters'; return null; }, ), @@ -196,11 +231,7 @@ class _LoginScreenState extends State Align( alignment: Alignment.centerRight, child: TextButton( - onPressed: () { - NavigationService().navigateTo( - const ForgotPasswordScreen(), - ); - }, + onPressed: () => NavigationService().navigateTo(const ForgotPasswordScreen()), child: Text( 'Forgot Password?', style: TextStyle( @@ -265,14 +296,11 @@ class _LoginScreenState extends State Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - 'Don\'t have an account? ', - style: TextStyle(color: Colors.grey.shade400), - ), + Text('Don\'t have an account? ', style: TextStyle(color: Colors.grey.shade400)), TextButton( - onPressed: () { - NavigationService().navigateTo(const SignupScreen()); - }, + onPressed: () => NavigationService().navigateTo( + SignupScreen(arguments: widget.arguments), + ), child: Text( 'Sign Up', style: TextStyle( @@ -291,4 +319,4 @@ class _LoginScreenState extends State ], ); } -} +} \ No newline at end of file diff --git a/lib/screens/auth/signup_screen.dart b/lib/screens/auth/signup_screen.dart index 1074b6d..dcb5d9d 100644 --- a/lib/screens/auth/signup_screen.dart +++ b/lib/screens/auth/signup_screen.dart @@ -1,15 +1,17 @@ import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import '../../widgets/custom_widgets.dart'; import '../../services/navigation_service.dart'; import '../../services/supabase_service.dart'; +import '../../services/app_shortcuts_service.dart'; import '../home/home_screen.dart'; import 'login_screen.dart'; import 'verify_otp_screen.dart'; import 'team_selection_dialog.dart'; class SignupScreen extends StatefulWidget { - const SignupScreen({super.key}); + final Map? arguments; + + const SignupScreen({super.key, this.arguments}); @override State createState() => _SignupScreenState(); @@ -32,10 +34,11 @@ class _SignupScreenState extends State @override void initState() { super.initState(); + + print('[SignupScreen] initState called with arguments: ${widget.arguments}'); + _tabController = TabController(length: 2, vsync: this); - _tabController.addListener(() { - setState(() {}); - }); + _tabController.addListener(() => setState(() {})); } @override @@ -50,17 +53,12 @@ class _SignupScreenState extends State super.dispose(); } - // Handle team creation - Future _handleCreateTeam() async { - if (!_createTeamFormKey.currentState!.validate()) return; - + // Unified method to send verification OTP and navigate + Future _sendOtpAndNavigate(String verifyType, Map userData) async { setState(() => _isLoading = true); try { - // Only send signup email without creating user upfront - await _supabaseService.client.auth.signInWithOtp( - email: _emailController.text, - ); + await _supabaseService.client.auth.signInWithOtp(email: _emailController.text); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -70,41 +68,54 @@ class _SignupScreenState extends State ), ); + // Merge existing arguments into userData + if (widget.arguments != null) userData['initial_args'] = widget.arguments; + + print('[SignupScreen] Navigate to VerifyOTPScreen with userData: $userData'); + NavigationService().navigateTo( VerifyOTPScreen( email: _emailController.text, - verifyType: 'signup_create', - userData: { - 'teamName': _teamNameController.text, - 'adminName': _nameController.text, - 'password': _passwordController.text, - }, + verifyType: verifyType, + userData: userData, ), ); } } catch (e) { + print('[SignupScreen] Error sending OTP: $e'); // Log for debugging if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString()), backgroundColor: Colors.red), + const SnackBar( + content: Text('Failed to send verification email. Please try again.'), + backgroundColor: Colors.red, + ), ); } } finally { - if (mounted) { - setState(() => _isLoading = false); - } + if (mounted) setState(() => _isLoading = false); } } - // Handle joining a team + Future _handleCreateTeam() async { + if (!_createTeamFormKey.currentState!.validate()) return; + + final userData = { + 'teamName': _teamNameController.text, + 'adminName': _nameController.text, + 'password': _passwordController.text, + }; + + await _sendOtpAndNavigate('signup_create', userData); + } + Future _handleJoinTeam() async { if (!_joinTeamFormKey.currentState!.validate()) return; setState(() => _isLoading = true); try { - // First check if the team exists - final teamExists = - await _supabaseService.teamExists(_teamIdController.text); + // Check if the team exists + final teamExists = await _supabaseService.teamExists(_teamIdController.text); if (!teamExists) { if (mounted) { @@ -114,93 +125,30 @@ class _SignupScreenState extends State backgroundColor: Colors.red, ), ); - setState(() => _isLoading = false); } - return; - } - - // Only send signup email without creating user upfront - await _supabaseService.client.auth.signInWithOtp( - email: _emailController.text, - ); - - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Verification email sent. Please check your inbox.'), - backgroundColor: Colors.green, - ), - ); - - NavigationService().navigateTo( - VerifyOTPScreen( - email: _emailController.text, - verifyType: 'signup_join', - userData: { - 'teamId': _teamIdController.text, - 'fullName': _nameController.text, - 'password': _passwordController.text, - }, - ), - ); - } - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString()), backgroundColor: Colors.red), - ); - } - } finally { - if (mounted) { setState(() => _isLoading = false); + return; } - } - } - Future _handleGoogleSignIn() async { - setState(() => _isLoading = true); - - try { - final result = await _supabaseService.signInWithGoogle(); + final userData = { + 'teamId': _teamIdController.text, + 'fullName': _nameController.text, + 'password': _passwordController.text, + }; - if (mounted) { - if (result['success'] == true) { - if (result['isNewUser'] == true) { - // New user - show team selection dialog - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => TeamSelectionDialog( - userEmail: result['email'] ?? '', - googleRefreshToken: result['googleRefreshToken'], - ), - ); - } else { - // Existing user - go to home - NavigationService().navigateToReplacement(const HomeScreen()); - } - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(result['error'] ?? 'Google sign-in failed'), - backgroundColor: Colors.red, - ), - ); - } - } + await _sendOtpAndNavigate('signup_join', userData); } catch (e) { + print('[SignupScreen] Error checking team: $e'); // Log for debugging if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Error: $e'), + const SnackBar( + content: Text('Failed to verify team. Please try again.'), backgroundColor: Colors.red, ), ); } } finally { - if (mounted) { - setState(() => _isLoading = false); - } + if (mounted) setState(() => _isLoading = false); } } @@ -223,11 +171,11 @@ class _SignupScreenState extends State ), const SizedBox(height: 24), SizedBox( - height: 350, // Adjust height as needed + height: 350, child: TabBarView( controller: _tabController, children: [ - // Join Team Tab + // Join Team Form Form( key: _joinTeamFormKey, child: Column( @@ -237,12 +185,8 @@ class _SignupScreenState extends State label: 'Team ID', icon: Icons.people_outline, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter team ID'; - } - if (value.length != 6) { - return 'Team ID must be 6 characters'; - } + if (value == null || value.isEmpty) return 'Please enter team ID'; + if (value.length != 6) return 'Team ID must be 6 characters'; return null; }, ), @@ -252,9 +196,7 @@ class _SignupScreenState extends State label: 'Full Name', icon: Icons.person_outline, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your name'; - } + if (value == null || value.isEmpty) return 'Please enter your name'; return null; }, ), @@ -264,12 +206,8 @@ class _SignupScreenState extends State label: 'Email', icon: Icons.email_outlined, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your email'; - } - if (!value.contains('@')) { - return 'Please enter a valid email'; - } + if (value == null || value.isEmpty) return 'Please enter your email'; + if (!value.contains('@')) return 'Please enter a valid email'; return null; }, ), @@ -280,12 +218,8 @@ class _SignupScreenState extends State icon: Icons.lock_outline, isPassword: true, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your password'; - } - if (value.length < 6) { - return 'Password must be at least 6 characters'; - } + if (value == null || value.isEmpty) return 'Please enter your password'; + if (value.length < 6) return 'Password must be at least 6 characters'; return null; }, ), @@ -296,19 +230,15 @@ class _SignupScreenState extends State icon: Icons.lock_outline, isPassword: true, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please confirm your password'; - } - if (value != _passwordController.text) { - return 'Passwords do not match'; - } + if (value == null || value.isEmpty) return 'Please confirm your password'; + if (value != _passwordController.text) return 'Passwords do not match'; return null; }, ), ], ), ), - // Create Team Tab + // Create Team Form Form( key: _createTeamFormKey, child: Column( @@ -318,9 +248,7 @@ class _SignupScreenState extends State label: 'Team Name', icon: Icons.group, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter team name'; - } + if (value == null || value.isEmpty) return 'Please enter team name'; return null; }, ), @@ -330,9 +258,7 @@ class _SignupScreenState extends State label: 'Admin Name', icon: Icons.person_outline, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter admin name'; - } + if (value == null || value.isEmpty) return 'Please enter admin name'; return null; }, ), @@ -342,12 +268,8 @@ class _SignupScreenState extends State label: 'Admin Email', icon: Icons.email_outlined, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter admin email'; - } - if (!value.contains('@')) { - return 'Please enter a valid email'; - } + if (value == null || value.isEmpty) return 'Please enter admin email'; + if (!value.contains('@')) return 'Please enter a valid email'; return null; }, ), @@ -358,12 +280,8 @@ class _SignupScreenState extends State icon: Icons.lock_outline, isPassword: true, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your password'; - } - if (value.length < 6) { - return 'Password must be at least 6 characters'; - } + if (value == null || value.isEmpty) return 'Please enter your password'; + if (value.length < 6) return 'Password must be at least 6 characters'; return null; }, ), @@ -374,12 +292,8 @@ class _SignupScreenState extends State icon: Icons.lock_outline, isPassword: true, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please confirm your password'; - } - if (value != _passwordController.text) { - return 'Passwords do not match'; - } + if (value == null || value.isEmpty) return 'Please confirm your password'; + if (value != _passwordController.text) return 'Passwords do not match'; return null; }, ), @@ -394,9 +308,7 @@ class _SignupScreenState extends State text: _tabController.index == 0 ? 'Join Team' : 'Create Team', onPressed: _isLoading ? null - : (_tabController.index == 0 - ? _handleJoinTeam - : _handleCreateTeam), + : (_tabController.index == 0 ? _handleJoinTeam : _handleCreateTeam), isLoading: _isLoading, ), const SizedBox(height: 24), @@ -451,7 +363,7 @@ class _SignupScreenState extends State text: 'Already have an account? Sign In', onPressed: () { NavigationService().navigateToReplacement( - const LoginScreen(), + LoginScreen(arguments: widget.arguments), ); }, isOutlined: true, diff --git a/lib/screens/auth/verify_otp_screen.dart b/lib/screens/auth/verify_otp_screen.dart index e5fc5ac..ca2c1c2 100644 --- a/lib/screens/auth/verify_otp_screen.dart +++ b/lib/screens/auth/verify_otp_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import '../../widgets/custom_widgets.dart'; import '../../services/navigation_service.dart'; import '../../services/supabase_service.dart'; +import '../../services/app_shortcuts_service.dart'; import '../home/home_screen.dart'; import '../auth/set_new_password_screen.dart'; @@ -10,10 +11,10 @@ class VerifyOTPScreen extends StatefulWidget { final String email; final String verifyType; // 'signup_join', 'signup_create', or 'reset_password' final Map userData; - + const VerifyOTPScreen({ - super.key, - required this.email, + super.key, + required this.email, required this.verifyType, this.userData = const {}, }); @@ -23,15 +24,19 @@ class VerifyOTPScreen extends StatefulWidget { } class _VerifyOTPScreenState extends State { - final List _controllers = List.generate( - 6, - (index) => TextEditingController(), - ); + final List _controllers = + List.generate(6, (index) => TextEditingController()); final List _focusNodes = List.generate(6, (index) => FocusNode()); bool _isLoading = false; String? _errorMessage; final _supabaseService = SupabaseService(); + @override + void initState() { + super.initState(); + // Removed debug prints for production safety + } + @override void dispose() { for (var controller in _controllers) { @@ -52,69 +57,96 @@ class _VerifyOTPScreenState extends State { }); try { - // Verify OTP with Supabase final result = await _supabaseService.verifyOTP( email: widget.email, token: otp, type: widget.verifyType, userData: widget.userData, ); - + if (result['success']) { - // Handle successful verification based on verify type - if (widget.verifyType == 'signup_create') { - // Show team ID dialog for team creators - if (result.containsKey('teamId')) { - _showTeamIdDialog(result['teamId']); - } + if (widget.verifyType == 'signup_create' && result.containsKey('teamId')) { + _showTeamIdDialog(result['teamId']); } else if (widget.verifyType == 'signup_join') { - // Navigate directly to home for team joiners - NavigationService().navigateToReplacement(const HomeScreen()); + _navigateToHomeScreen(); } else if (widget.verifyType == 'reset_password') { - // Navigate to reset password screen NavigationService().navigateTo( SetNewPasswordScreen(email: widget.email), ); } } else { setState(() { - String errorMsg = result['error'] ?? 'Verification failed'; - - // Make the error message more user-friendly - if (errorMsg.contains('expired') || errorMsg.contains('otp_expired')) { - errorMsg = 'Verification code has expired. Please request a new code.'; - } else if (errorMsg.contains('invalid')) { - errorMsg = 'Invalid verification code. Please try again.'; - } - - _errorMessage = errorMsg; + _errorMessage = _friendlyErrorMessage(result['error']); }); } } catch (e) { setState(() { - String errorMsg = e.toString(); - - // Make the error message more user-friendly - if (errorMsg.contains('expired') || errorMsg.contains('otp_expired')) { - errorMsg = 'Verification code has expired. Please request a new code.'; - } else if (errorMsg.contains('invalid')) { - errorMsg = 'Invalid verification code. Please try again.'; - } else { - errorMsg = 'An error occurred. Please try again.'; - } - - _errorMessage = errorMsg; + _errorMessage = _friendlyErrorMessage(e.toString()); }); } finally { if (mounted) { - setState(() { - _isLoading = false; - }); + setState(() => _isLoading = false); } } } } - + + String _friendlyErrorMessage(String? rawMessage) { + if (rawMessage == null) return 'Verification failed. Please try again.'; + if (rawMessage.contains('expired') || rawMessage.contains('otp_expired')) { + return 'Verification code has expired. Please request a new code.'; + } else if (rawMessage.contains('invalid')) { + return 'Invalid verification code. Please try again.'; + } + return 'An error occurred. Please try again.'; + } + + void _navigateToHomeScreen() { + final pendingShortcut = AppShortcutsService.getPendingShortcut(); + + // Use a copy of initial_args safely + Map homeScreenArgs = {}; + final initialArgs = widget.userData['initial_args']; + if (initialArgs is Map) { + homeScreenArgs = Map.from(initialArgs); + } + + if (pendingShortcut != null) { + final screenIndex = _getScreenIndex(pendingShortcut); + if (screenIndex != null) { + homeScreenArgs['screen'] = screenIndex; + homeScreenArgs['initial_route'] = pendingShortcut; + } + } + + if (homeScreenArgs.isEmpty) { + homeScreenArgs = {'screen': 0}; + } + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => HomeScreen(arguments: homeScreenArgs), + ), + ); + } + + int? _getScreenIndex(String? route) { + switch (route) { + case 'dashboard': + return 0; + case 'calendar': + return 1; + case 'workspace': + return 2; + case 'chat': + return 3; + case 'profile': + return 4; + default: + return null; + } + } + Future _resendCode() async { setState(() { _isLoading = true; @@ -126,7 +158,7 @@ class _VerifyOTPScreenState extends State { widget.email, type: widget.verifyType, ); - + if (result['success']) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -136,45 +168,18 @@ class _VerifyOTPScreenState extends State { ); } else { setState(() { - String errorMsg = result['error'] ?? 'Failed to resend code'; - - // Make the error message more user-friendly - if (errorMsg.contains('Rate limit')) { - errorMsg = 'Too many attempts. Please try again later.'; - } else if (errorMsg.contains('not found') || errorMsg.contains('Invalid email')) { - errorMsg = 'Email address not found or invalid.'; - } - - _errorMessage = errorMsg; + _errorMessage = _friendlyErrorMessage(result['error']); }); } } catch (e) { setState(() { - String errorMsg = e.toString(); - - // Make the error message more user-friendly - if (errorMsg.contains('Rate limit')) { - errorMsg = 'Too many attempts. Please try again later.'; - } else if (errorMsg.contains('not found') || errorMsg.contains('Invalid email')) { - errorMsg = 'Email address not found or invalid.'; - } else if (errorMsg.contains('Assertion failed')) { - errorMsg = 'Unable to resend code. Please go back and try again.'; - } else { - errorMsg = 'An error occurred. Please try again.'; - } - - _errorMessage = errorMsg; + _errorMessage = _friendlyErrorMessage(e.toString()); }); } finally { - if (mounted) { - setState(() { - _isLoading = false; - }); - } + if (mounted) setState(() => _isLoading = false); } } - - // Show dialog with the generated team ID + void _showTeamIdDialog(String teamId) { showDialog( context: context, @@ -182,20 +187,16 @@ class _VerifyOTPScreenState extends State { builder: (BuildContext context) { return AlertDialog( backgroundColor: const Color(0xFF2A2A2A), - title: const Text( - 'Team Created Successfully!', - style: TextStyle(color: Colors.white), - ), + title: const Text('Team Created Successfully!', + style: TextStyle(color: Colors.white)), content: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text( - 'Your Team ID is:', - style: TextStyle(color: Colors.grey), - ), + const Text('Your Team ID is:', style: TextStyle(color: Colors.grey)), const SizedBox(height: 16), Container( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + padding: + const EdgeInsets.symmetric(vertical: 12, horizontal: 16), decoration: BoxDecoration( color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(8), @@ -206,11 +207,10 @@ class _VerifyOTPScreenState extends State { Text( teamId, style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - letterSpacing: 2, - color: Colors.white, - ), + fontSize: 24, + fontWeight: FontWeight.bold, + letterSpacing: 2, + color: Colors.white), ), IconButton( icon: const Icon(Icons.copy, color: Colors.green), @@ -238,12 +238,10 @@ class _VerifyOTPScreenState extends State { actions: [ TextButton( onPressed: () { - NavigationService().navigateToReplacement(const HomeScreen()); + Navigator.of(context).pop(); + _navigateToHomeScreen(); }, - child: Text( - 'Continue', - style: TextStyle(color: Colors.green.shade400), - ), + child: Text('Continue', style: TextStyle(color: Colors.green.shade400)), ), ], ); @@ -336,4 +334,4 @@ class _VerifyOTPScreenState extends State { ], ); } -} \ No newline at end of file +} diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index 46fc229..6d2d543 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../widgets/custom_widgets.dart'; import '../../services/navigation_service.dart'; +import '../../services/app_shortcuts_service.dart'; import '../workspace/workspace_screen.dart'; import '../calendar/calendar_screen.dart'; import '../profile/profile_screen.dart'; @@ -10,7 +11,7 @@ import 'dashboard_screen.dart'; class HomeScreen extends StatefulWidget { final Map? arguments; - + const HomeScreen({super.key, this.arguments}); @override @@ -28,11 +29,12 @@ class _HomeScreenState extends State late AnimationController _fabController; late Animation _fabAnimation; - List _screens = []; + late final List _screens; @override void initState() { super.initState(); + _fabController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, @@ -42,35 +44,29 @@ class _HomeScreenState extends State curve: Curves.easeOut, reverseCurve: Curves.easeIn, ); - - // Initialize screens + _initializeScreens(); - - // Handle initial arguments if provided + + // Handle initial arguments safely WidgetsBinding.instance.addPostFrameCallback((_) { - if (widget.arguments != null) { - if (widget.arguments!.containsKey('screen') && widget.arguments!['screen'] is int) { - setState(() { - _selectedIndex = widget.arguments!['screen']; - }); - } - - // Handle initial message for chat screen - if (widget.arguments!.containsKey('initial_message') && - widget.arguments!['initial_message'] is String && - _selectedIndex == 3) { - // Update the chat screen with the initial message - setState(() { - _screens[3] = ChatScreen( - arguments: {'initial_message': widget.arguments!['initial_message']} - ); - }); + final args = widget.arguments; + if (args != null) { + if (args['screen'] is int) _selectedIndex = args['screen']; + if (args['initial_message'] is String && _selectedIndex == 3) { + _screens[3] = ChatScreen( + arguments: {'initial_message': args['initial_message']}, + ); } + setState(() {}); } }); + + // Initialize AppShortcutsService + AppShortcutsService.init(_handleShortcut); } - + void _initializeScreens() { + // Initialize screens once to prevent unnecessary rebuilds _screens = [ const DashboardScreen(), const CalendarScreen(), @@ -80,6 +76,33 @@ class _HomeScreenState extends State ]; } + void _handleShortcut(String route) { + int index; + switch (route) { + case 'dashboard': + index = 0; + break; + case 'calendar': + index = 1; + break; + case 'workspace': + index = 2; + break; + case 'chat': + index = 3; + // Refresh chat screen if needed + _screens[3] = ChatScreen(); + break; + case 'profile': + index = 4; + break; + default: + index = 0; + } + + if (mounted) setState(() => _selectedIndex = index); + } + @override void dispose() { _messageController.dispose(); @@ -89,21 +112,19 @@ class _HomeScreenState extends State } void _sendMessage() { - if (_messageController.text.trim().isEmpty) return; + final text = _messageController.text.trim(); + if (text.isEmpty) return; setState(() { - _messages.add( - ChatMessage( - text: _messageController.text, - isUser: true, - timestamp: DateTime.now(), - ), - ); + _messages.add(ChatMessage( + text: text, + isUser: true, + timestamp: DateTime.now(), + )); _messageController.clear(); }); _scrollToBottom(); - // TODO: Implement AI response } void _scrollToBottom() { @@ -119,10 +140,7 @@ class _HomeScreenState extends State } void _toggleListening() { - setState(() { - _isListening = !_isListening; - }); - // TODO: Implement voice recognition + setState(() => _isListening = !_isListening); } void _toggleFab() { @@ -157,18 +175,25 @@ class _HomeScreenState extends State icon: Icon(Icons.calendar_today), label: 'Calendar', ), - BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Workspace'), + BottomNavigationBarItem( + icon: Icon(Icons.work), + label: 'Workspace', + ), BottomNavigationBarItem( icon: Icon(Icons.chat_bubble_outline), label: 'Chat', ), - BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'), + BottomNavigationBarItem( + icon: Icon(Icons.person), + label: 'Profile', + ), ], ), ); } } +// Chat bubble widget class _ChatBubble extends StatelessWidget { final ChatMessage message; @@ -177,12 +202,14 @@ class _ChatBubble extends StatelessWidget { @override Widget build(BuildContext context) { return Align( - alignment: message.isUser ? Alignment.centerRight : Alignment.centerLeft, + alignment: + message.isUser ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( - color: message.isUser ? Colors.green.shade400 : Colors.grey.shade800, + color: + message.isUser ? Colors.green.shade400 : Colors.grey.shade800, borderRadius: BorderRadius.circular(20), ), child: Text(message.text, style: const TextStyle(color: Colors.white)), @@ -191,6 +218,7 @@ class _ChatBubble extends StatelessWidget { } } +// Chat message model class ChatMessage { final String text; final bool isUser; diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart index b20bb00..a456256 100644 --- a/lib/screens/splash_screen.dart +++ b/lib/screens/splash_screen.dart @@ -1,13 +1,17 @@ import 'package:flutter/material.dart'; import 'dart:async'; + import 'onboarding/onboarding_screen.dart'; import '../services/navigation_service.dart'; import '../services/supabase_service.dart'; +import '../services/app_shortcuts_service.dart'; import 'home/home_screen.dart'; import 'auth/login_screen.dart'; class SplashScreen extends StatefulWidget { - const SplashScreen({super.key}); + final Map? arguments; + + const SplashScreen({super.key, this.arguments}); @override State createState() => _SplashScreenState(); @@ -23,6 +27,9 @@ class _SplashScreenState extends State @override void initState() { super.initState(); + + debugPrint('[SplashScreen] initState called with arguments: ${widget.arguments}'); + _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1500), @@ -38,40 +45,48 @@ class _SplashScreenState extends State _animationController.forward(); - Timer(const Duration(milliseconds: 1500), () { - _checkSession(); - }); + // Give a small delay to ensure everything is initialized + Timer(const Duration(milliseconds: 1500), _checkSession); } Future _checkSession() async { try { final currentUser = _supabaseService.client.auth.currentUser; - final args = ModalRoute.of(context)?.settings.arguments; + // Get initial shortcut from AppShortcutsService + final initialShortcut = AppShortcutsService.getPendingShortcut(); + debugPrint('[SplashScreen] Initial shortcut: $initialShortcut'); + + // Convert shortcut to screen index + int? screenIndex = _getScreenIndex(initialShortcut); + debugPrint('[SplashScreen] Converted screen index: $screenIndex'); + + // Create a copy of arguments to avoid mutating potentially unmodifiable map + Map homeScreenArgs = Map.from(widget.arguments ?? {}); + if (screenIndex != null) { + homeScreenArgs['screen'] = screenIndex; + homeScreenArgs['initial_route'] = initialShortcut; + } + + debugPrint('[SplashScreen] HomeScreen arguments: $homeScreenArgs'); if (currentUser != null) { - if (args != null && args is Map) { - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => HomeScreen(arguments: args), - ), - ); - } else { - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const HomeScreen(), - ), - ); - } + // User is logged in, go to HomeScreen + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => HomeScreen(arguments: homeScreenArgs), + ), + ); } else { + // User is not logged in, go to LoginScreen Navigator.of(context).pushReplacement( MaterialPageRoute( - builder: (context) => const LoginScreen(), + builder: (context) => LoginScreen(arguments: homeScreenArgs), ), ); } } catch (e) { - debugPrint('Error checking session: $e'); + debugPrint('[SplashScreen] Error checking session: $e'); Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (context) => const OnboardingScreen(), @@ -80,6 +95,25 @@ class _SplashScreenState extends State } } + int? _getScreenIndex(String? route) { + if (route == null) return null; + + switch (route) { + case 'dashboard': + return 0; + case 'calendar': + return 1; + case 'workspace': + return 2; + case 'chat': + return 3; + case 'profile': + return 4; + default: + return null; + } + } + @override void dispose() { _animationController.dispose(); @@ -108,10 +142,7 @@ class _SplashScreenState extends State decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( - colors: [ - Colors.green.shade400, - Colors.green.shade700 - ], + colors: [Colors.green.shade400, Colors.green.shade700], begin: Alignment.topLeft, end: Alignment.bottomRight, ), @@ -123,11 +154,7 @@ class _SplashScreenState extends State ), ], ), - child: const Icon( - Icons.task_alt, - size: 80, - color: Colors.white, - ), + child: const Icon(Icons.task_alt, size: 80, color: Colors.white), ); }, ), diff --git a/lib/services/app_shortcuts_service.dart b/lib/services/app_shortcuts_service.dart new file mode 100644 index 0000000..e278228 --- /dev/null +++ b/lib/services/app_shortcuts_service.dart @@ -0,0 +1,113 @@ +import 'package:flutter/services.dart'; + +class AppShortcutsService { + AppShortcutsService._(); + + static const MethodChannel _channel = MethodChannel('app_shortcuts'); + static String? _pendingShortcut; + static Function(String route)? _onShortcutPressed; + + /// Initialize the shortcuts service and get initial shortcut if any + static Future initialize() async { + try { + // Get initial shortcut when app launches + final initialRoute = await _channel.invokeMethod('getInitialRoute'); + if (initialRoute != null && initialRoute.isNotEmpty) { + _pendingShortcut = initialRoute; + } + } catch (e) { + print('Error initializing shortcuts service: $e'); + } + } + + /// Get and clear pending shortcut (for initial app launch) + static String? getPendingShortcut() { + final shortcut = _pendingShortcut; + _pendingShortcut = null; + return shortcut; + } + + /// Initialize shortcut handler for in-app navigation + static void init(void Function(String route) onShortcutPressed) { + _onShortcutPressed = onShortcutPressed; + + _channel.setMethodCallHandler((call) async { + if (call.method == 'navigate') { + final String route = call.arguments?.toString() ?? ''; + if (route.isNotEmpty) { + _handleShortcut(route); + } + } + return null; + }); + } + + /// Handle shortcut route and convert to index for backward compatibility + static void initWithIndex(void Function(int index) changeTab) { + init((route) { + int index = _routeToIndex(route); + changeTab(index); + }); + } + + /// Convert route string to tab index + static int _routeToIndex(String route) { + switch (route) { + case 'dashboard': return 0; + case 'calendar': return 1; + case 'workspace': return 2; + case 'chat': return 3; + case 'profile': return 4; + default: return 0; + } + } + + /// Convert tab index to route string + static String _indexToRoute(int index) { + switch (index) { + case 0: return 'dashboard'; + case 1: return 'calendar'; + case 2: return 'workspace'; + case 3: return 'chat'; + case 4: return 'profile'; + default: return 'dashboard'; + } + } + + /// Handle shortcut internally + static void _handleShortcut(String route) { + if (_onShortcutPressed != null) { + _onShortcutPressed!(route); + } + } + + /// Manually trigger navigation (for testing or programmatic navigation) + static void navigateTo(String route) { + _handleShortcut(route); + } + + /// Navigate to specific tab index + static void navigateToIndex(int index) { + final route = _indexToRoute(index); + _handleShortcut(route); + } + + /// Check if there's a pending shortcut + static bool hasPendingShortcut() { + return _pendingShortcut != null && _pendingShortcut!.isNotEmpty; + } + + /// Clear any pending shortcuts + static void clearPendingShortcut() { + _pendingShortcut = null; + } + + /// Send a shortcut to native (for testing or cross-platform communication) + static Future sendShortcutToNative(String route) async { + try { + await _channel.invokeMethod('navigate', route); + } catch (e) { + print('Error sending shortcut to native: $e'); + } + } +} \ No newline at end of file diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 3792af4..1a9d148 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 5d07423..1db2f43 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 92b6497..145cd5f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,12 +7,16 @@ import Foundation import app_links 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")) 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 2d3a236..7bb4dec 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -958,5 +958,5 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=3.8.0 <=3.8.1" - flutter: ">=3.32.0" + dart: ">=3.7.0 <=3.8.1" + flutter: ">=3.31.0-0.0.pre" diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 785a046..a16341e 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 8f8ee4f..2d82e75 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 )