diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..b6c4afc --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(code .:*)", + "Bash(git add:*)" + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b5baf79 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,197 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Flutter application for phone number-based authentication. The app demonstrates a complete phone login flow with Firebase authentication, including phone number input, SMS verification, and authenticated user interfaces. + +## Architecture & Structure + +The application follows a layered architecture with the following key components: + +- **Authentication**: Managed through Firebase Authentication with `firebase_auth` package. The `AuthState` class handles authentication state using Provider pattern. +- **Navigation**: Implemented with `go_router` for declarative routing with authentication-aware redirects. +- **State Management**: Uses `provider` package with `ChangeNotifierProvider` for global state management. +- **UI Components**: Built with Flutter's Material Design widgets and enhanced with specialized packages: + - `intl_phone_field` for international phone number input + - `pinput` for PIN input fields on SMS verification + +## Directory Structure + +``` +lib/ +├── main.dart # App entry point +├── app.dart # MaterialApp setup, theme, and routing +├── auth/ # Authentication-related screens and logic +│ ├── auth_state.dart # Manages authentication state +│ ├── phone_input_screen.dart +│ └── sms_verification_screen.dart +├── home/ # Home screen +│ └── home_screen.dart +├── profile/ # User profile screen +│ └── profile_screen.dart +``` + +## Key Dependencies + +- `firebase_core`: 4.3.0 +- `firebase_auth`: 6.1.3 +- `go_router`: ^17.0.1 +- `provider`: ^6.1.5+1 +- `intl_phone_field`: ^3.2.0 +- `pinput`: ^6.0.1 + +## Common Development Commands + +```bash +# Install dependencies +flutter pub get + +# Run the application +flutter run + +# Run tests +flutter test + +# Analyze code +flutter analyze + +# Format code +flutter format lib/ + +# Run linter +flutter analyze + +# Build for Android +flutter build apk + +# Build for iOS +flutter build ios +``` + +## Flutter Development Best Practices (Based on rules.md) + +### Interaction Guidelines +- **User Persona:** Assume the user is familiar with programming concepts but may be new to Dart. +- **Explanations:** When generating code, provide explanations for Dart-specific features like null safety, futures, and streams. +- **Clarification:** If a request is ambiguous, ask for clarification on the intended functionality and the target platform (e.g., command-line, web, server). +- **Dependencies:** When suggesting new dependencies from `pub.dev`, explain their benefits. +- **Formatting:** Use the `dart_format` tool to ensure consistent code formatting. +- **Fixes:** Use the `dart_fix` tool to automatically fix many common errors, and to help code conform to configured analysis options. +- **Linting:** Use the Dart linter with a recommended set of rules to catch common issues. Use the `analyze_files` tool to run the linter. + +### Code Style & Architecture +- **SOLID Principles:** Apply SOLID principles throughout the codebase. +- **Concise and Declarative:** Write concise, modern, technical Dart code. Prefer functional and declarative patterns. +- **Composition over Inheritance:** Favor composition for building complex widgets and logic. +- **Immutability:** Prefer immutable data structures. Widgets (especially `StatelessWidget`) should be immutable. +- **State Management:** Separate ephemeral state and app state. Use a state management solution for app state to handle the separation of concerns. +- **Widgets are for UI:** Everything in Flutter's UI is a widget. Compose complex UIs from smaller, reusable widgets. +- **Navigation:** Use a modern routing package like `go_router`. See the navigation guide for a detailed example using `go_router`. + +### Code Quality Standards +- **Code structure:** Adhere to maintainable code structure and separation of concerns (e.g., UI logic separate from business logic). +- **Naming conventions:** Avoid abbreviations and use meaningful, consistent, descriptive names for variables, functions, and classes. +- **Conciseness:** Write code that is as short as it can be while remaining clear. +- **Simplicity:** Write straightforward code. Code that is clever or obscure is difficult to maintain. +- **Error Handling:** Anticipate and handle potential errors. Don't let your code fail silently. +- **Styling:** + - Line length: Lines should be 80 characters or fewer. + - Use `PascalCase` for classes, `camelCase` for members/variables/functions/enums, and `snake_case` for files. +- **Functions:** Functions should be short and with a single purpose (strive for less than 20 lines). + +### Dart Best Practices +- **Effective Dart:** Follow the official Effective Dart guidelines (https://dart.dev/effective-dart) +- **Null Safety:** Write code that is soundly null-safe. Leverage Dart's null safety features. Avoid `!` unless the value is guaranteed to be non-null. +- **Async/Await:** Ensure proper use of `async`/`await` for asynchronous operations with robust error handling. + - Use `Future`s, `async`, and `await` for asynchronous operations. + - Use `Stream`s for sequences of asynchronous events. +- **Pattern Matching:** Use pattern matching features where they simplify the code. +- **Records:** Use records to return multiple types in situations where defining an entire class is cumbersome. +- **Switch Statements:** Prefer using exhaustive `switch` statements or expressions, which don't require `break` statements. +- **Exception Handling:** Use `try-catch` blocks for handling exceptions, and use exceptions appropriate for the type of exception. Use custom exceptions for situations specific to your code. +- **Arrow Functions:** Use arrow syntax for simple one-line functions. + +### Flutter Best Practices +- **Immutability:** Widgets (especially `StatelessWidget`) are immutable; when the UI needs to change, Flutter rebuilds the widget tree. +- **Composition:** Prefer composing smaller widgets over extending existing ones. Use this to avoid deep widget nesting. +- **Private Widgets:** Use small, private `Widget` classes instead of private helper methods that return a `Widget`. +- **Build Methods:** Break down large `build()` methods into smaller, reusable private Widget classes. +- **List Performance:** Use `ListView.builder` or `SliverList` for long lists to create lazy-loaded lists for performance. +- **Isolates:** Use `compute()` to run expensive calculations in a separate isolate to avoid blocking the UI thread, such as JSON parsing. +- **Const Constructors:** Use `const` constructors for widgets and in `build()` methods whenever possible to reduce rebuilds. +- **Build Method Performance:** Avoid performing expensive operations, like network calls or complex computations, directly within `build()` methods. + +### Application Architecture +- **Separation of Concerns:** Aim for separation of concerns similar to MVC/MVVM, with defined Model, View, and ViewModel/Controller roles. +- **Logical Layers:** Organize the project into logical layers: + - Presentation (widgets, screens) + - Domain (business logic classes) + - Data (model classes, API clients) + - Core (shared classes, utilities, and extension types) +- **Feature-based Organization:** For larger projects, organize code by feature, where each feature has its own presentation, domain, and data subfolders. This improves navigability and scalability. + +### State Management +- **Built-in Solutions:** Prefer Flutter's built-in state management solutions. Do not use a third-party package unless explicitly requested. +- **Streams:** Use `Streams` and `StreamBuilder` for handling a sequence of asynchronous events. +- **Futures:** Use `Futures` and `FutureBuilder` for handling a single asynchronous operation that will complete in the future. +- **ValueNotifier:** Use `ValueNotifier` with `ValueListenableBuilder` for simple, local state that involves a single value. +- **ChangeNotifier:** For state that is more complex or shared across multiple widgets, use `ChangeNotifier`. +- **ListenableBuilder:** Use `ListenableBuilder` to listen to changes from a `ChangeNotifier` or other `Listenable`. +- **MVVM:** When a more robust solution is needed, structure the app using the Model-View-ViewModel (MVVM) pattern. +- **Dependency Injection:** Use simple manual constructor dependency injection to make a class's dependencies explicit in its API, and to manage dependencies between different layers of the application. +- **Provider:** If a dependency injection solution beyond manual constructor injection is explicitly requested, `provider` can be used to make services, repositories, or complex state objects available to the UI layer without tight coupling. + +### Lint Rules +Include the package in the `analysis_options.yaml` file. Use the following analysis_options.yaml file as a starting point: + +```yaml +include: package:flutter_lints/flutter.yaml + +linter: + rules: + # Add additional lint rules here: + # avoid_print: false + # prefer_single_quotes: true +``` + +### Testing +- **Running Tests:** To run tests, use the `run_tests` tool if it is available, otherwise use `flutter test`. +- **Unit Tests:** Use `package:test` for unit tests. +- **Widget Tests:** Use `package:flutter_test` for widget tests. +- **Integration Tests:** Use `package:integration_test` for integration tests. +- **Assertions:** Prefer using `package:checks` for more expressive and readable assertions over the default `matchers`. + +#### Testing Best practices +- **Convention:** Follow the Arrange-Act-Assert (or Given-When-Then) pattern. +- **Unit Tests:** Write unit tests for domain logic, data layer, and state management. +- **Widget Tests:** Write widget tests for UI components. +- **Integration Tests:** For broader application validation, use integration tests to verify end-to-end user flows. +- **integration_test package:** Use the `integration_test` package from the Flutter SDK for integration tests. Add it as a `dev_dependency` in `pubspec.yaml` by specifying `sdk: flutter`. +- **Mocks:** Prefer fakes or stubs over mocks. If mocks are absolutely necessary, use `mockito` or `mocktail` to create mocks for dependencies. While code generation is common for state management (e.g., with `freezed`), try to avoid it for mocks. +- **Coverage:** Aim for high test coverage. + +### Visual Design & Theming +- **UI Design:** Build beautiful and intuitive user interfaces that follow modern design guidelines. +- **Responsiveness:** Ensure the app is mobile responsive and adapts to different screen sizes, working perfectly on mobile and web. +- **Navigation:** If there are multiple pages for the user to interact with, provide an intuitive and easy navigation bar or controls. +- **Typography:** Stress and emphasize font sizes to ease understanding, e.g., hero text, section headlines, list headlines, keywords in paragraphs. +- **Theming:** Implement support for both light and dark themes, ideal for a user-facing theme toggle (`ThemeMode.light`, `ThemeMode.dark`, `ThemeMode.system`). + +### Code Generation +- **Build Runner:** If the project uses code generation, ensure that `build_runner` is listed as a dev dependency in `pubspec.yaml`. +- **Code Generation Tasks:** Use `build_runner` for all code generation tasks, such as for `json_serializable`. +- **Running Build Runner:** After modifying files that require code generation, run the build command: + +```shell +dart run build_runner build --delete-conflicting-outputs +``` + +## Important Notes + +- Authentication flow is implemented using Firebase phone authentication +- The router includes redirect logic based on authentication state +- UI adapts based on login status (logged in/out views) +- Phone input includes international country codes +- SMS verification screen accepts 6-digit PIN codes \ No newline at end of file diff --git a/lib/app.dart b/lib/app.dart index 81e56af..70fa812 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -5,6 +5,8 @@ import 'package:phone_login/auth/phone_input_screen.dart'; import 'package:phone_login/auth/sms_verification_screen.dart'; import 'package:phone_login/home/home_screen.dart'; import 'package:phone_login/profile/profile_screen.dart'; +import 'package:phone_login/services/auth_service.dart'; +import 'package:phone_login/theme/app_theme.dart'; import 'package:provider/provider.dart'; final _router = GoRouter( @@ -27,8 +29,7 @@ final _router = GoRouter( final authState = Provider.of(context, listen: false); final bool loggedIn = authState.isLoggedIn; final bool loggingIn = - state.matchedLocation == '/login' || - state.matchedLocation == '/sms_verify'; + state.matchedLocation == '/login' || state.matchedLocation == '/sms_verify'; if (!loggedIn && !loggingIn) { return '/login'; @@ -48,8 +49,15 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( - create: (_) => AuthState(), - child: MaterialApp.router(routerConfig: _router), + create: (_) => AuthState(FirebaseAuthService()), + child: MaterialApp.router( + routerConfig: _router, + debugShowCheckedModeBanner: false, + title: 'Phone Login App', + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: ThemeMode.system, + ), ); } } diff --git a/lib/auth/auth_state.dart b/lib/auth/auth_state.dart index ed60185..c904380 100644 --- a/lib/auth/auth_state.dart +++ b/lib/auth/auth_state.dart @@ -1,16 +1,82 @@ import 'package:flutter/material.dart'; +import 'package:phone_login/services/auth_service.dart'; +import 'package:phone_login/shared/models/user_model.dart'; +import 'package:firebase_auth/firebase_auth.dart' as firebase; class AuthState extends ChangeNotifier { + final AuthService _authService; + + AuthState(this._authService) { + _authService.user.listen(_onUserChanged); + } + + bool _isLoading = false; + bool get isLoading => _isLoading; + bool _isLoggedIn = false; bool get isLoggedIn => _isLoggedIn; - void toggleLogin({bool? value}) { - _isLoggedIn = value ?? !_isLoggedIn; + String? _errorMessage; + String? get errorMessage => _errorMessage; + + UserModel? _currentUser; + UserModel? get currentUser => _currentUser; + + void _onUserChanged(firebase.User? user) { + if (user != null) { + _isLoggedIn = true; + _currentUser = UserModel.fromFirebaseUser(user); + } else { + _isLoggedIn = false; + _currentUser = null; + } + _isLoading = false; notifyListeners(); } - void logout() { - _isLoggedIn = false; + Future signInWithPhoneNumber(String phoneNumber) async { + _isLoading = true; + _errorMessage = null; notifyListeners(); + + try { + await _authService.signInWithPhoneNumber(phoneNumber); + } on Exception catch (e) { + _errorMessage = e.toString(); + } finally { + _isLoading = false; + notifyListeners(); + } + } + + Future verifyOTP(String verificationId, String otp) async { + _isLoading = true; + _errorMessage = null; + notifyListeners(); + + try { + await _authService.verifyOTP(verificationId, otp); + } on Exception catch (e) { + _errorMessage = e.toString(); + } finally { + _isLoading = false; + notifyListeners(); + } + } + + Future logout() async { + _isLoading = true; + notifyListeners(); + + try { + await _authService.signOut(); + _isLoggedIn = false; + _currentUser = null; + } on Exception catch (e) { + _errorMessage = e.toString(); + } finally { + _isLoading = false; + notifyListeners(); + } } } diff --git a/lib/auth/phone_input_screen.dart b/lib/auth/phone_input_screen.dart index 4cfbf08..9d3d400 100644 --- a/lib/auth/phone_input_screen.dart +++ b/lib/auth/phone_input_screen.dart @@ -1,11 +1,31 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:intl_phone_field/intl_phone_field.dart'; +import 'package:provider/provider.dart'; +import 'package:phone_login/auth/auth_state.dart'; -class PhoneInputScreen extends StatelessWidget { +class PhoneInputScreen extends StatefulWidget { const PhoneInputScreen({super.key}); + @override + State createState() => _PhoneInputScreenState(); +} + +class _PhoneInputScreenState extends State { + final TextEditingController _phoneController = TextEditingController(); + String? _selectedCountry; + String? _formattedPhone; + + @override + void dispose() { + _phoneController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { + final authState = Provider.of(context); + return Scaffold( appBar: AppBar(title: const Text('Enter Phone Number')), body: Padding( @@ -20,11 +40,43 @@ class PhoneInputScreen extends StatelessWidget { ), initialCountryCode: 'US', onChanged: (phone) { - // TODO: Handle phone number changes + _formattedPhone = phone.completeNumber; + }, + onCountryChanged: (country) { + _selectedCountry = country.isoCode; }, ), const SizedBox(height: 20), - ElevatedButton(onPressed: () {}, child: const Text('Send OTP')), + authState.isLoading + ? const Center(child: CircularProgressIndicator()) + : ElevatedButton( + onPressed: _formattedPhone != null + ? () async { + if (_formattedPhone != null) { + await authState.signInWithPhoneNumber(_formattedPhone!); + if (!context.mounted) return; + + if (authState.errorMessage != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(authState.errorMessage!)), + ); + } else { + context.go('/sms_verify'); + } + } + } + : null, + child: const Text('Send OTP'), + ), + if (authState.errorMessage != null) + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Text( + authState.errorMessage!, + style: const TextStyle(color: Colors.red), + textAlign: TextAlign.center, + ), + ), ], ), ), diff --git a/lib/auth/sms_verification_screen.dart b/lib/auth/sms_verification_screen.dart index 7eaccec..cb5f073 100644 --- a/lib/auth/sms_verification_screen.dart +++ b/lib/auth/sms_verification_screen.dart @@ -1,11 +1,39 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:pinput/pinput.dart'; +import 'package:provider/provider.dart'; +import 'package:phone_login/auth/auth_state.dart'; -class SmsVerificationScreen extends StatelessWidget { +class SmsVerificationScreen extends StatefulWidget { const SmsVerificationScreen({super.key}); + @override + State createState() => _SmsVerificationScreenState(); +} + +class _SmsVerificationScreenState extends State { + final TextEditingController _pinController = TextEditingController(); + final FocusNode _pinFocusNode = FocusNode(); + String? _verificationId; + + @override + void initState() { + super.initState(); + // In a real app, the verificationId would be passed from the previous screen + // For now, we'll store it in a global variable or use another mechanism + } + + @override + void dispose() { + _pinController.dispose(); + _pinFocusNode.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { + final authState = Provider.of(context); + return Scaffold( appBar: AppBar(title: const Text('SMS Verification')), body: Padding( @@ -14,19 +42,66 @@ class SmsVerificationScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text( - 'Enter the 6-digit code sent to you', + 'Enter the 6-digit code sent to your phone', textAlign: TextAlign.center, style: TextStyle(fontSize: 16), ), const SizedBox(height: 20), Pinput( length: 6, - onCompleted: (pin) { - // TODO: Handle OTP completion + controller: _pinController, + focusNode: _pinFocusNode, + onCompleted: (pin) async { + // In a real app, we'd have the verificationId from the previous step + // This is a simplified implementation + if (_verificationId != null) { + await authState.verifyOTP(_verificationId!, pin); + if (authState.errorMessage != null) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(authState.errorMessage!)), + ); + } + } else { + if (context.mounted) { + context.go('/'); + } + } + } }, ), const SizedBox(height: 20), - ElevatedButton(onPressed: () {}, child: const Text('Verify')), + authState.isLoading + ? const Center(child: CircularProgressIndicator()) + : ElevatedButton( + onPressed: _pinController.text.length == 6 + ? () async { + // In a real app, we'd have the verificationId from the previous step + if (_verificationId != null) { + await authState.verifyOTP(_verificationId!, _pinController.text); + if (!context.mounted) return; + + if (authState.errorMessage != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(authState.errorMessage!)), + ); + } else { + context.go('/'); + } + } + } + : null, + child: const Text('Verify'), + ), + if (authState.errorMessage != null) + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Text( + authState.errorMessage!, + style: const TextStyle(color: Colors.red), + textAlign: TextAlign.center, + ), + ), ], ), ), diff --git a/lib/home/home_screen.dart b/lib/home/home_screen.dart index d638e88..7dd18cc 100644 --- a/lib/home/home_screen.dart +++ b/lib/home/home_screen.dart @@ -12,6 +12,10 @@ class HomeScreen extends StatelessWidget { appBar: AppBar(title: const Text('Home')), body: Consumer( builder: (context, authState, child) { + if (authState.isLoading) { + return const Center(child: CircularProgressIndicator()); + } + return authState.isLoggedIn ? const _LoggedInView() : const _LoggedOutView(); @@ -40,22 +44,39 @@ class _LoggedInView extends StatelessWidget { ), ], ), - body: ListView.builder( - itemCount: 20, // Example items - itemBuilder: (context, index) { - return Card( - margin: const EdgeInsets.all(8.0), - child: ListTile( - leading: const Icon(Icons.star), - title: Text('Item ${index + 1}'), - subtitle: const Text('This is an example item.'), - ), - ); - }, - ), + body: authState.currentUser != null + ? ListView( + padding: const EdgeInsets.all(8.0), + children: [ + Card( + child: ListTile( + leading: const Icon(Icons.person), + title: Text(authState.currentUser!.displayName ?? 'User'), + subtitle: Text(authState.currentUser!.phoneNumber ?? 'No phone number'), + ), + ), + const Divider(), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 20, // Example items + itemBuilder: (context, index) { + return Card( + margin: const EdgeInsets.symmetric(vertical: 4.0), + child: ListTile( + leading: const Icon(Icons.star), + title: Text('Item ${index + 1}'), + subtitle: const Text('This is an example item.'), + ), + ); + }, + ), + ], + ) + : const Center(child: Text('Loading user data...')), floatingActionButton: FloatingActionButton.extended( - onPressed: () { - authState.logout(); // Call logout method + onPressed: () async { + await authState.logout(); // Call logout method }, label: const Text('Logout'), icon: const Icon(Icons.logout), @@ -86,7 +107,7 @@ class _LoggedOutView extends StatelessWidget { const SizedBox(height: 30), ElevatedButton( onPressed: () { - context.go('/phone'); + context.go('/login'); }, child: const Text('Login'), ), diff --git a/lib/main.dart b/lib/main.dart index b423823..a43f1db 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,8 @@ -import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:phone_login/app.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(); + runApp(const App()); } diff --git a/lib/profile/profile_screen.dart b/lib/profile/profile_screen.dart index 7074e70..7d75109 100644 --- a/lib/profile/profile_screen.dart +++ b/lib/profile/profile_screen.dart @@ -7,17 +7,66 @@ class ProfileScreen extends StatelessWidget { @override Widget build(BuildContext context) { - final authState = Provider.of(context, listen: false); + final authState = Provider.of(context); return Scaffold( appBar: AppBar(title: const Text('Profile')), - body: Center( - child: ElevatedButton( - onPressed: () { - authState.toggleLogin(); - }, - child: const Text('Logout'), - ), + body: authState.currentUser != null + ? Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + radius: 50, + child: Text( + authState.currentUser!.displayName != null + ? authState.currentUser!.displayName![0].toUpperCase() + : '?', + style: const TextStyle(fontSize: 30), + ), + ), + const SizedBox(height: 16), + _buildProfileItem('Name', authState.currentUser!.displayName ?? 'Not set'), + _buildProfileItem('Phone', authState.currentUser!.phoneNumber ?? 'Not set'), + _buildProfileItem('Email', authState.currentUser!.email ?? 'Not set'), + const Spacer(), + Center( + child: authState.isLoading + ? const CircularProgressIndicator() + : ElevatedButton.icon( + onPressed: () async { + await authState.logout(); + }, + icon: const Icon(Icons.logout), + label: const Text('Logout'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + ), + ), + ), + ], + ), + ) + : const Center(child: Text('Loading...')), + ); + } + + Widget _buildProfileItem(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + const SizedBox(height: 4), + Text(value.isEmpty ? 'Not set' : value), + const Divider(), + ], ), ); } diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart new file mode 100644 index 0000000..e23735f --- /dev/null +++ b/lib/services/auth_service.dart @@ -0,0 +1,47 @@ +import 'package:firebase_auth/firebase_auth.dart'; + +abstract class AuthService { + Stream get user; + Future signInWithPhoneNumber(String phoneNumber); + Future verifyOTP(String verificationId, String otp); + Future signOut(); +} + +class FirebaseAuthService implements AuthService { + final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; + + @override + Stream get user => _firebaseAuth.authStateChanges(); + + @override + Future signInWithPhoneNumber(String phoneNumber) async { + await _firebaseAuth.verifyPhoneNumber( + phoneNumber: phoneNumber, + verificationCompleted: (PhoneAuthCredential credential) async { + await _firebaseAuth.signInWithCredential(credential); + }, + verificationFailed: (FirebaseAuthException e) { + throw Exception(e.message); + }, + codeSent: (String verificationId, int? resendToken) { + // Store verificationId for later use in verifyOTP + // In a real app, this would be stored in state management + }, + codeAutoRetrievalTimeout: (String verificationId) {}, + ); + } + + @override + Future verifyOTP(String verificationId, String otp) async { + PhoneAuthCredential credential = PhoneAuthProvider.credential( + verificationId: verificationId, + smsCode: otp, + ); + await _firebaseAuth.signInWithCredential(credential); + } + + @override + Future signOut() async { + await _firebaseAuth.signOut(); + } +} \ No newline at end of file diff --git a/lib/shared/models/user_model.dart b/lib/shared/models/user_model.dart new file mode 100644 index 0000000..9b4b5c7 --- /dev/null +++ b/lib/shared/models/user_model.dart @@ -0,0 +1,30 @@ +class UserModel { + final String uid; + final String? displayName; + final String? phoneNumber; + final String? email; + final String? photoURL; + + UserModel({ + required this.uid, + this.displayName, + this.phoneNumber, + this.email, + this.photoURL, + }); + + factory UserModel.fromFirebaseUser(User user) { + return UserModel( + uid: user.uid, + displayName: user.displayName, + phoneNumber: user.phoneNumber, + email: user.email, + photoURL: user.photoURL, + ); + } + + @override + String toString() { + return 'UserModel(uid: $uid, displayName: $displayName, phoneNumber: $phoneNumber, email: $email, photoURL: $photoURL)'; + } +} \ No newline at end of file diff --git a/lib/shared/widgets/loading_indicator.dart b/lib/shared/widgets/loading_indicator.dart new file mode 100644 index 0000000..88c393f --- /dev/null +++ b/lib/shared/widgets/loading_indicator.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class LoadingIndicator extends StatelessWidget { + const LoadingIndicator({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: CircularProgressIndicator(), + ); + } +} \ No newline at end of file diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart new file mode 100644 index 0000000..626b74d --- /dev/null +++ b/lib/theme/app_theme.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class AppTheme { + static ThemeData lightTheme = ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.blue, + brightness: Brightness.light, + ), + ); + + static ThemeData darkTheme = ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.blue, + brightness: Brightness.dark, + ), + ); +} \ No newline at end of file diff --git a/rules.md b/rules.md new file mode 100644 index 0000000..eb758c0 --- /dev/null +++ b/rules.md @@ -0,0 +1,788 @@ +# AI rules for Flutter + +You are an expert in Flutter and Dart development. Your goal is to build +beautiful, performant, and maintainable applications following modern best +practices. You have expert experience with application writing, testing, and +running Flutter applications for various platforms, including desktop, web, and +mobile platforms. + +## Interaction Guidelines +* **User Persona:** Assume the user is familiar with programming concepts but + may be new to Dart. +* **Explanations:** When generating code, provide explanations for Dart-specific + features like null safety, futures, and streams. +* **Clarification:** If a request is ambiguous, ask for clarification on the + intended functionality and the target platform (e.g., command-line, web, + server). +* **Dependencies:** When suggesting new dependencies from `pub.dev`, explain + their benefits. +* **Formatting:** Use the `dart_format` tool to ensure consistent code + formatting. +* **Fixes:** Use the `dart_fix` tool to automatically fix many common errors, + and to help code conform to configured analysis options. +* **Linting:** Use the Dart linter with a recommended set of rules to catch + common issues. Use the `analyze_files` tool to run the linter. + +## Project Structure +* **Standard Structure:** Assumes a standard Flutter project structure with + `lib/main.dart` as the primary application entry point. + +## Flutter style guide +* **SOLID Principles:** Apply SOLID principles throughout the codebase. +* **Concise and Declarative:** Write concise, modern, technical Dart code. + Prefer functional and declarative patterns. +* **Composition over Inheritance:** Favor composition for building complex + widgets and logic. +* **Immutability:** Prefer immutable data structures. Widgets (especially + `StatelessWidget`) should be immutable. +* **State Management:** Separate ephemeral state and app state. Use a state + management solution for app state to handle the separation of concerns. +* **Widgets are for UI:** Everything in Flutter's UI is a widget. Compose + complex UIs from smaller, reusable widgets. +* **Navigation:** Use a modern routing package like `auto_route` or `go_router`. + See the [navigation guide](./navigation.md) for a detailed example using + `go_router`. + +## Package Management +* **Pub Tool:** To manage packages, use the `pub` tool, if available. +* **External Packages:** If a new feature requires an external package, use the + `pub_dev_search` tool, if it is available. Otherwise, identify the most + suitable and stable package from pub.dev. +* **Adding Dependencies:** To add a regular dependency, use the `pub` tool, if + it is available. Otherwise, run `flutter pub add `. +* **Adding Dev Dependencies:** To add a development dependency, use the `pub` + tool, if it is available, with `dev:`. Otherwise, run `flutter + pub add dev:`. +* **Dependency Overrides:** To add a dependency override, use the `pub` tool, if + it is available, with `override::1.0.0`. Otherwise, run `flutter + pub add override::1.0.0`. +* **Removing Dependencies:** To remove a dependency, use the `pub` tool, if it + is available. Otherwise, run `dart pub remove `. + +## Code Quality +* **Code structure:** Adhere to maintainable code structure and separation of + concerns (e.g., UI logic separate from business logic). +* **Naming conventions:** Avoid abbreviations and use meaningful, consistent, + descriptive names for variables, functions, and classes. +* **Conciseness:** Write code that is as short as it can be while remaining + clear. +* **Simplicity:** Write straightforward code. Code that is clever or + obscure is difficult to maintain. +* **Error Handling:** Anticipate and handle potential errors. Don't let your + code fail silently. +* **Styling:** + * Line length: Lines should be 80 characters or fewer. + * Use `PascalCase` for classes, `camelCase` for + members/variables/functions/enums, and `snake_case` for files. +* **Functions:** + * Functions short and with a single purpose (strive for less than 20 lines). +* **Testing:** Write code with testing in mind. Use the `file`, `process`, and + `platform` packages, if appropriate, so you can inject in-memory and fake + versions of the objects. +* **Logging:** Use the `logging` package instead of `print`. + +## Dart Best Practices +* **Effective Dart:** Follow the official Effective Dart guidelines + (https://dart.dev/effective-dart) +* **Class Organization:** Define related classes within the same library file. + For large libraries, export smaller, private libraries from a single top-level + library. +* **Library Organization:** Group related libraries in the same folder. +* **API Documentation:** Add documentation comments to all public APIs, + including classes, constructors, methods, and top-level functions. +* **Comments:** Write clear comments for complex or non-obvious code. Avoid + over-commenting. +* **Trailing Comments:** Don't add trailing comments. +* **Async/Await:** Ensure proper use of `async`/`await` for asynchronous + operations with robust error handling. + * Use `Future`s, `async`, and `await` for asynchronous operations. + * Use `Stream`s for sequences of asynchronous events. +* **Null Safety:** Write code that is soundly null-safe. Leverage Dart's null + safety features. Avoid `!` unless the value is guaranteed to be non-null. +* **Pattern Matching:** Use pattern matching features where they simplify the + code. +* **Records:** Use records to return multiple types in situations where defining + an entire class is cumbersome. +* **Switch Statements:** Prefer using exhaustive `switch` statements or + expressions, which don't require `break` statements. +* **Exception Handling:** Use `try-catch` blocks for handling exceptions, and + use exceptions appropriate for the type of exception. Use custom exceptions + for situations specific to your code. +* **Arrow Functions:** Use arrow syntax for simple one-line functions. + +## Flutter Best Practices +* **Immutability:** Widgets (especially `StatelessWidget`) are immutable; when + the UI needs to change, Flutter rebuilds the widget tree. +* **Composition:** Prefer composing smaller widgets over extending existing + ones. Use this to avoid deep widget nesting. +* **Private Widgets:** Use small, private `Widget` classes instead of private + helper methods that return a `Widget`. +* **Build Methods:** Break down large `build()` methods into smaller, reusable + private Widget classes. +* **List Performance:** Use `ListView.builder` or `SliverList` for long lists to + create lazy-loaded lists for performance. +* **Isolates:** Use `compute()` to run expensive calculations in a separate + isolate to avoid blocking the UI thread, such as JSON parsing. +* **Const Constructors:** Use `const` constructors for widgets and in `build()` + methods whenever possible to reduce rebuilds. +* **Build Method Performance:** Avoid performing expensive operations, like + network calls or complex computations, directly within `build()` methods. + +## API Design Principles +When building reusable APIs, such as a library, follow these principles. + +* **Consider the User:** Design APIs from the perspective of the person who will + be using them. The API should be intuitive and easy to use correctly. +* **Documentation is Essential:** Good documentation is a part of good API + design. It should be clear, concise, and provide examples. + +## Application Architecture +* **Separation of Concerns:** Aim for separation of concerns similar to MVC/MVVM, with defined Model, + View, and ViewModel/Controller roles. +* **Logical Layers:** Organize the project into logical layers: + * Presentation (widgets, screens) + * Domain (business logic classes) + * Data (model classes, API clients) + * Core (shared classes, utilities, and extension types) +* **Feature-based Organization:** For larger projects, organize code by feature, + where each feature has its own presentation, domain, and data subfolders. This + improves navigability and scalability. + +## Lint Rules + +Include the package in the `analysis_options.yaml` file. Use the following +analysis_options.yaml file as a starting point: + +```yaml +include: package:flutter_lints/flutter.yaml + +linter: + rules: + # Add additional lint rules here: + # avoid_print: false + # prefer_single_quotes: true +``` + +### State Management +* **Built-in Solutions:** Prefer Flutter's built-in state management solutions. + Do not use a third-party package unless explicitly requested. +* **Streams:** Use `Streams` and `StreamBuilder` for handling a sequence of + asynchronous events. +* **Futures:** Use `Futures` and `FutureBuilder` for handling a single + asynchronous operation that will complete in the future. +* **ValueNotifier:** Use `ValueNotifier` with `ValueListenableBuilder` for + simple, local state that involves a single value. + + ```dart + // Define a ValueNotifier to hold the state. + final ValueNotifier _counter = ValueNotifier(0); + + // Use ValueListenableBuilder to listen and rebuild. + ValueListenableBuilder( + valueListenable: _counter, + builder: (context, value, child) { + return Text('Count: $value'); + }, + ); + ``` + +* **ChangeNotifier:** For state that is more complex or shared across multiple + widgets, use `ChangeNotifier`. +* **ListenableBuilder:** Use `ListenableBuilder` to listen to changes from a + `ChangeNotifier` or other `Listenable`. +* **MVVM:** When a more robust solution is needed, structure the app using the + Model-View-ViewModel (MVVM) pattern. +* **Dependency Injection:** Use simple manual constructor dependency injection + to make a class's dependencies explicit in its API, and to manage dependencies + between different layers of the application. +* **Provider:** If a dependency injection solution beyond manual constructor + injection is explicitly requested, `provider` can be used to make services, + repositories, or complex state objects available to the UI layer without tight + coupling (note: this document generally defaults against third-party packages + for state management unless explicitly requested). + +### Data Flow +* **Data Structures:** Define data structures (classes) to represent the data + used in the application. +* **Data Abstraction:** Abstract data sources (e.g., API calls, database + operations) using Repositories/Services to promote testability. + +### Routing +* **GoRouter:** Use the `go_router` package for declarative navigation, deep + linking, and web support. +* **GoRouter Setup:** To use `go_router`, first add it to your `pubspec.yaml` + using the `pub` tool's `add` command. + + ```dart + // 1. Add the dependency + // flutter pub add go_router + + // 2. Configure the router + final GoRouter _router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomeScreen(), + routes: [ + GoRoute( + path: 'details/:id', // Route with a path parameter + builder: (context, state) { + final String id = state.pathParameters['id']!; + return DetailScreen(id: id); + }, + ), + ], + ), + ], + ); + + // 3. Use it in your MaterialApp + MaterialApp.router( + routerConfig: _router, + ); + ``` +* **Authentication Redirects:** Configure `go_router`'s `redirect` property to + handle authentication flows, ensuring users are redirected to the login screen + when unauthorized, and back to their intended destination after successful + login. + +* **Navigator:** Use the built-in `Navigator` for short-lived screens that do + not need to be deep-linkable, such as dialogs or temporary views. + + ```dart + // Push a new screen onto the stack + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const DetailsScreen()), + ); + + // Pop the current screen to go back + Navigator.pop(context); + ``` + +### Data Handling & Serialization +* **JSON Serialization:** Use `json_serializable` and `json_annotation` for + parsing and encoding JSON data. +* **Field Renaming:** When encoding data, use `fieldRename: FieldRename.snake` + to convert Dart's camelCase fields to snake_case JSON keys. + + ```dart + // In your model file + import 'package:json_annotation/json_annotation.dart'; + + part 'user.g.dart'; + + @JsonSerializable(fieldRename: FieldRename.snake) + class User { + final String firstName; + final String lastName; + + User({required this.firstName, required this.lastName}); + + factory User.fromJson(Map json) => _$UserFromJson(json); + Map toJson() => _$UserToJson(this); + } + ``` + + +### Logging +* **Structured Logging:** Use the `log` function from `dart:developer` for + structured logging that integrates with Dart DevTools. + + ```dart + import 'dart:developer' as developer; + + // For simple messages + developer.log('User logged in successfully.'); + + // For structured error logging + try { + // ... code that might fail + } catch (e, s) { + developer.log( + 'Failed to fetch data', + name: 'myapp.network', + level: 1000, // SEVERE + error: e, + stackTrace: s, + ); + } + ``` + +## Code Generation +* **Build Runner:** If the project uses code generation, ensure that + `build_runner` is listed as a dev dependency in `pubspec.yaml`. +* **Code Generation Tasks:** Use `build_runner` for all code generation tasks, + such as for `json_serializable`. +* **Running Build Runner:** After modifying files that require code generation, + run the build command: + + ```shell + dart run build_runner build --delete-conflicting-outputs + ``` + +## Testing +* **Running Tests:** To run tests, use the `run_tests` tool if it is available, + otherwise use `flutter test`. +* **Unit Tests:** Use `package:test` for unit tests. +* **Widget Tests:** Use `package:flutter_test` for widget tests. +* **Integration Tests:** Use `package:integration_test` for integration tests. +* **Assertions:** Prefer using `package:checks` for more expressive and readable + assertions over the default `matchers`. + +### Testing Best practices +* **Convention:** Follow the Arrange-Act-Assert (or Given-When-Then) pattern. +* **Unit Tests:** Write unit tests for domain logic, data layer, and state + management. +* **Widget Tests:** Write widget tests for UI components. +* **Integration Tests:** For broader application validation, use integration + tests to verify end-to-end user flows. +* **integration_test package:** Use the `integration_test` package from the + Flutter SDK for integration tests. Add it as a `dev_dependency` in + `pubspec.yaml` by specifying `sdk: flutter`. +* **Mocks:** Prefer fakes or stubs over mocks. If mocks are absolutely + necessary, use `mockito` or `mocktail` to create mocks for dependencies. While + code generation is common for state management (e.g., with `freezed`), try to + avoid it for mocks. +* **Coverage:** Aim for high test coverage. + +## Visual Design & Theming +* **UI Design:** Build beautiful and intuitive user interfaces that follow + modern design guidelines. +* **Responsiveness:** Ensure the app is mobile responsive and adapts to + different screen sizes, working perfectly on mobile and web. +* **Navigation:** If there are multiple pages for the user to interact with, + provide an intuitive and easy navigation bar or controls. +* **Typography:** Stress and emphasize font sizes to ease understanding, e.g., + hero text, section headlines, list headlines, keywords in paragraphs. +* **Background:** Apply subtle noise texture to the main background to add a + premium, tactile feel. +* **Shadows:** Multi-layered drop shadows create a strong sense of depth; cards + have a soft, deep shadow to look "lifted." +* **Icons:** Incorporate icons to enhance the user’s understanding and the + logical navigation of the app. +* **Interactive Elements:** Buttons, checkboxes, sliders, lists, charts, graphs, + and other interactive elements have a shadow with elegant use of color to + create a "glow" effect. + +### Theming +* **Centralized Theme:** Define a centralized `ThemeData` object to ensure a + consistent application-wide style. +* **Light and Dark Themes:** Implement support for both light and dark themes, + ideal for a user-facing theme toggle (`ThemeMode.light`, `ThemeMode.dark`, + `ThemeMode.system`). +* **Color Scheme Generation:** Generate harmonious color palettes from a single + color using `ColorScheme.fromSeed`. + + ```dart + final ThemeData lightTheme = ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.deepPurple, + brightness: Brightness.light, + ), + // ... other theme properties + ); + ``` +* **Color Palette:** Include a wide range of color concentrations and hues in + the palette to create a vibrant and energetic look and feel. +* **Component Themes:** Use specific theme properties (e.g., `appBarTheme`, + `elevatedButtonTheme`) to customize the appearance of individual Material + components. +* **Custom Fonts:** For custom fonts, use the `google_fonts` package. Define a + `TextTheme` to apply fonts consistently. + + ```dart + // 1. Add the dependency + // flutter pub add google_fonts + + // 2. Define a TextTheme with a custom font + final TextTheme appTextTheme = TextTheme( + displayLarge: GoogleFonts.oswald(fontSize: 57, fontWeight: FontWeight.bold), + titleLarge: GoogleFonts.roboto(fontSize: 22, fontWeight: FontWeight.w500), + bodyMedium: GoogleFonts.openSans(fontSize: 14), + ); + ``` + +### Assets and Images +* **Image Guidelines:** If images are needed, make them relevant and meaningful, + with appropriate size, layout, and licensing (e.g., freely available). Provide + placeholder images if real ones are not available. +* **Asset Declaration:** Declare all asset paths in your `pubspec.yaml` file. + + ```yaml + flutter: + uses-material-design: true + assets: + - assets/images/ + ``` + +* **Local Images:** Use `Image.asset` for local images from your asset + bundle. + + ```dart + Image.asset('assets/images/placeholder.png') + ``` +* **Network images:** Use NetworkImage for images loaded from the network. +* **Cached images:** For cached images, use NetworkImage a package like + `cached_network_image`. +* **Custom Icons:** Use `ImageIcon` to display an icon from an `ImageProvider`, + useful for custom icons not in the `Icons` class. +* **Network Images:** Use `Image.network` to display images from a URL, and + always include `loadingBuilder` and `errorBuilder` for a better user + experience. + + ```dart + Image.network( + 'https://picsum.photos/200/300', + loadingBuilder: (context, child, progress) { + if (progress == null) return child; + return const Center(child: CircularProgressIndicator()); + }, + errorBuilder: (context, error, stackTrace) { + return const Icon(Icons.error); + }, + ) + ``` +## UI Theming and Styling Code + +* **Responsiveness:** Use `LayoutBuilder` or `MediaQuery` to create responsive + UIs. +* **Text:** Use `Theme.of(context).textTheme` for text styles. +* **Text Fields:** Configure `textCapitalization`, `keyboardType`, and +* **Responsiveness:** Use `LayoutBuilder` or `MediaQuery` to create responsive + UIs. +* **Text:** Use `Theme.of(context).textTheme` for text styles. + remote images. + +```dart +// When using network images, always provide an errorBuilder. +Image.network( + 'https://example.com/image.png', + errorBuilder: (context, error, stackTrace) { + return const Icon(Icons.error); // Show an error icon + }, +); +``` + +## Material Theming Best Practices + +### Embrace `ThemeData` and Material 3 + +* **Use `ColorScheme.fromSeed()`:** Use this to generate a complete, harmonious + color palette for both light and dark modes from a single seed color. +* **Define Light and Dark Themes:** Provide both `theme` and `darkTheme` to your + `MaterialApp` to support system brightness settings seamlessly. +* **Centralize Component Styles:** Customize specific component themes (e.g., + `elevatedButtonTheme`, `cardTheme`, `appBarTheme`) within `ThemeData` to + ensure consistency. +* **Dark/Light Mode and Theme Toggle:** Implement support for both light and + dark themes using `theme` and `darkTheme` properties of `MaterialApp`. The + `themeMode` property can be dynamically controlled (e.g., via a + `ChangeNotifierProvider`) to allow for toggling between `ThemeMode.light`, + `ThemeMode.dark`, or `ThemeMode.system`. + +```dart +// main.dart +MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.deepPurple, + brightness: Brightness.light, + ), + textTheme: const TextTheme( + displayLarge: TextStyle(fontSize: 57.0, fontWeight: FontWeight.bold), + bodyMedium: TextStyle(fontSize: 14.0, height: 1.4), + ), + ), + darkTheme: ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.deepPurple, + brightness: Brightness.dark, + ), + ), + home: const MyHomePage(), +); +``` + +### Implement Design Tokens with `ThemeExtension` + +For custom styles that aren't part of the standard `ThemeData`, use +`ThemeExtension` to define reusable design tokens. + +* **Create a Custom Theme Extension:** Define a class that extends + `ThemeExtension` and include your custom properties. +* **Implement `copyWith` and `lerp`:** These methods are required for the + extension to work correctly with theme transitions. +* **Register in `ThemeData`:** Add your custom extension to the `extensions` + list in your `ThemeData`. +* **Access Tokens in Widgets:** Use `Theme.of(context).extension()!` + to access your custom tokens. + +```dart +// 1. Define the extension +@immutable +class MyColors extends ThemeExtension { + const MyColors({required this.success, required this.danger}); + + final Color? success; + final Color? danger; + + @override + ThemeExtension copyWith({Color? success, Color? danger}) { + return MyColors(success: success ?? this.success, danger: danger ?? this.danger); + } + + @override + ThemeExtension lerp(ThemeExtension? other, double t) { + if (other is! MyColors) return this; + return MyColors( + success: Color.lerp(success, other.success, t), + danger: Color.lerp(danger, other.danger, t), + ); + } +} + +// 2. Register it in ThemeData +theme: ThemeData( + extensions: const >[ + MyColors(success: Colors.green, danger: Colors.red), + ], +), + +// 3. Use it in a widget +Container( + color: Theme.of(context).extension()!.success, +) +``` + +### Styling with `WidgetStateProperty` + +* **`WidgetStateProperty.resolveWith`:** Provide a function that receives a + `Set` and returns the appropriate value for the current state. +* **`WidgetStateProperty.all`:** A shorthand for when the value is the same for + all states. + +```dart +// Example: Creating a button style that changes color when pressed. +final ButtonStyle myButtonStyle = ButtonStyle( + backgroundColor: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.pressed)) { + return Colors.green; // Color when pressed + } + return Colors.red; // Default color + }, + ), +); +``` + +## Layout Best Practices + +### Building Flexible and Overflow-Safe Layouts + +#### For Rows and Columns + +* **`Expanded`:** Use to make a child widget fill the remaining available space + along the main axis. +* **`Flexible`:** Use when you want a widget to shrink to fit, but not + necessarily grow. Don't combine `Flexible` and `Expanded` in the same `Row` or + `Column`. +* **`Wrap`:** Use when you have a series of widgets that would overflow a `Row` + or `Column`, and you want them to move to the next line. + +#### For General Content + +* **`SingleChildScrollView`:** Use when your content is intrinsically larger + than the viewport, but is a fixed size. +* **`ListView` / `GridView`:** For long lists or grids of content, always use a + builder constructor (`.builder`). +* **`FittedBox`:** Use to scale or fit a single child widget within its parent. +* **`LayoutBuilder`:** Use for complex, responsive layouts to make decisions + based on the available space. + +### Layering Widgets with Stack + +* **`Positioned`:** Use to precisely place a child within a `Stack` by anchoring it to the edges. +* **`Align`:** Use to position a child within a `Stack` using alignments like `Alignment.center`. + +### Advanced Layout with Overlays + +* **`OverlayPortal`:** Use this widget to show UI elements (like custom + dropdowns or tooltips) "on top" of everything else. It manages the + `OverlayEntry` for you. + + ```dart + class MyDropdown extends StatefulWidget { + const MyDropdown({super.key}); + + @override + State createState() => _MyDropdownState(); + } + + class _MyDropdownState extends State { + final _controller = OverlayPortalController(); + + @override + Widget build(BuildContext context) { + return OverlayPortal( + controller: _controller, + overlayChildBuilder: (BuildContext context) { + return const Positioned( + top: 50, + left: 10, + child: Card( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Text('I am an overlay!'), + ), + ), + ); + }, + child: ElevatedButton( + onPressed: _controller.toggle, + child: const Text('Toggle Overlay'), + ), + ); + } + } + ``` + +## Color Scheme Best Practices + +### Contrast Ratios + +* **WCAG Guidelines:** Aim to meet the Web Content Accessibility Guidelines + (WCAG) 2.1 standards. +* **Minimum Contrast:** + * **Normal Text:** A contrast ratio of at least **4.5:1**. + * **Large Text:** (18pt or 14pt bold) A contrast ratio of at least **3:1**. + +### Palette Selection + +* **Primary, Secondary, and Accent:** Define a clear color hierarchy. +* **The 60-30-10 Rule:** A classic design rule for creating a balanced color scheme. + * **60%** Primary/Neutral Color (Dominant) + * **30%** Secondary Color + * **10%** Accent Color + +### Complementary Colors + +* **Use with Caution:** They can be visually jarring if overused. +* **Best Use Cases:** They are excellent for accent colors to make specific + elements pop, but generally poor for text and background pairings as they can + cause eye strain. + +### Example Palette + +* **Primary:** #0D47A1 (Dark Blue) +* **Secondary:** #1976D2 (Medium Blue) +* **Accent:** #FFC107 (Amber) +* **Neutral/Text:** #212121 (Almost Black) +* **Background:** #FEFEFE (Almost White) + +## Font Best Practices + +### Font Selection + +* **Limit Font Families:** Stick to one or two font families for the entire + application. +* **Prioritize Legibility:** Choose fonts that are easy to read on screens of + all sizes. Sans-serif fonts are generally preferred for UI body text. +* **System Fonts:** Consider using platform-native system fonts. +* **Google Fonts:** For a wide selection of open-source fonts, use the + `google_fonts` package. + +### Hierarchy and Scale + +* **Establish a Scale:** Define a set of font sizes for different text elements + (e.g., headlines, titles, body text, captions). +* **Use Font Weight:** Differentiate text effectively using font weights. +* **Color and Opacity:** Use color and opacity to de-emphasize less important + text. + +### Readability + +* **Line Height (Leading):** Set an appropriate line height, typically **1.4x to + 1.6x** the font size. +* **Line Length:** For body text, aim for a line length of **45-75 characters**. +* **Avoid All Caps:** Do not use all caps for long-form text. + +### Example Typographic Scale + +```dart +// In your ThemeData +textTheme: const TextTheme( + displayLarge: TextStyle(fontSize: 57.0, fontWeight: FontWeight.bold), + titleLarge: TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold), + bodyLarge: TextStyle(fontSize: 16.0, height: 1.5), + bodyMedium: TextStyle(fontSize: 14.0, height: 1.4), + labelSmall: TextStyle(fontSize: 11.0, color: Colors.grey), +), +``` + +## Documentation + +* **`dartdoc`:** Write `dartdoc`-style comments for all public APIs. + + +### Documentation Philosophy + +* **Comment wisely:** Use comments to explain why the code is written a certain + way, not what the code does. The code itself should be self-explanatory. +* **Document for the user:** Write documentation with the reader in mind. If you + had a question and found the answer, add it to the documentation where you + first looked. This ensures the documentation answers real-world questions. +* **No useless documentation:** If the documentation only restates the obvious + from the code's name, it's not helpful. Good documentation provides context + and explains what isn't immediately apparent. +* **Consistency is key:** Use consistent terminology throughout your + documentation. + +### Commenting Style + +* **Use `///` for doc comments:** This allows documentation generation tools to + pick them up. +* **Start with a single-sentence summary:** The first sentence should be a + concise, user-centric summary ending with a period. +* **Separate the summary:** Add a blank line after the first sentence to create + a separate paragraph. This helps tools create better summaries. +* **Avoid redundancy:** Don't repeat information that's obvious from the code's + context, like the class name or signature. +* **Don't document both getter and setter:** For properties with both, only + document one. The documentation tool will treat them as a single field. + +### Writing Style + +* **Be brief:** Write concisely. +* **Avoid jargon and acronyms:** Don't use abbreviations unless they are widely + understood. +* **Use Markdown sparingly:** Avoid excessive markdown and never use HTML for + formatting. +* **Use backticks for code:** Enclose code blocks in backtick fences, and + specify the language. + +### What to Document + +* **Public APIs are a priority:** Always document public APIs. +* **Consider private APIs:** It's a good idea to document private APIs as well. +* **Library-level comments are helpful:** Consider adding a doc comment at the + library level to provide a general overview. +* **Include code samples:** Where appropriate, add code samples to illustrate usage. +* **Explain parameters, return values, and exceptions:** Use prose to describe + what a function expects, what it returns, and what errors it might throw. +* **Place doc comments before annotations:** Documentation should come before + any metadata annotations. + +## Accessibility (A11Y) +Implement accessibility features to empower all users, assuming a wide variety +of users with different physical abilities, mental abilities, age groups, +education levels, and learning styles. + +* **Color Contrast:** Ensure text has a contrast ratio of at least **4.5:1** + against its background. +* **Dynamic Text Scaling:** Test your UI to ensure it remains usable when users + increase the system font size. +* **Semantic Labels:** Use the `Semantics` widget to provide clear, descriptive + labels for UI elements. +* **Screen Reader Testing:** Regularly test your app with TalkBack (Android) and + VoiceOver (iOS). \ No newline at end of file