A brief overview of Dentix, its purpose, and key features. Explain what makes this app unique or valuable.
Features of Template app:
Before you begin, ensure you have met the following requirements:
- Flutter SDK 3.35.7
- Dart SDK 3.9.2
- FVM - Flutter Version Management
- Android Studio or VS Code with Flutter extensions
- Simulator/Emulator or physical device for testing
- Flutter: 3.35.7
- Dart: 3.9.2
fvm use 3.35.7Provide instructions on how to install your app. Include any prerequisites and step-by-step installation guide.
git clone https://github.com/essasabbagh/dentix.git
cd dentixfvm flutter pub getUse Flutter assets vscode extension Flutter assets
flutter_assets:
assets_path: assets/
output_path: lib/core/constants/
filename: images.dart
classname: AppImages
ignore_comments: true
field_prefix:Compress Flutter assets squoosh - tinypng
# Run on Mac
fvm flutter run -d macos
# Run on Windows
fvm flutter run -d windows- Run this command
fvm dart run rename_app:main all="Dentix"- lib/configs/app_configs.dart
static const String appName = 'Dentix';- lib/locale/intl_XX.arb
"appName": "Dentix",fvm dart run flutter_native_splash:createnote: to edit configs go to: flutter_native_splash.yaml
lib/core/themes
color: Theme.of(context).shadowColor
color: Theme.of(context).colorScheme.onSurface
style: Theme.of(context).textTheme.bodyMedium
style: Theme.of(context).textTheme.bodyLarge- Font Family: notoSansArabic Using Google font
class AppConfigs {
...
// font family
static const String fontFamily = 'Cairo';
}
class AppFont {
static const String fontFamily = AppConfigs.fontFamily;
}class AppGradient {
static const linearGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF069ACC),
Color(0xFF0594C6),
Color(0xFF0483B6),
Color(0xFF02679B),
Color(0xFF004177),
Color(0xFF003B71),
],
stops: [
0.1079,
0.2481,
0.446,
0.6603,
0.8995,
0.9325,
],
);
}Libraries & Tools Used
lib/core/router
Redirection changes the location to a new one based on auth state.
final routerProviderTo navigate to a route using its name, call goNamed():
// example
context.goNamed(
AppRouters.checkEmail.name,
pathParameters: {
"email": _emailnameController.text,
},
);To configure a named route, use the name parameter:
GoRoute(
path: AppRoutes.splash.path,
name: AppRoutes.splash.name,
builder: (_, _) => const SplashScreen(),
),lib\configs\routes\app_router.dart
enum AppRoutes {
splash('/', 'splash'),
onboarding('/onboarding', 'onboarding'),
login('/login', 'login'),
...
}flutter_localizations:
sdk: flutter
intl: ^0.20.2
dev_dependencies:
intl_utils: ^2.8.11
# dart run intl_utils:generateAdd the following vscode Extension flutter-intl
To generate boilerplate code for localization, run the generate program inside directory where your pubspec.yaml file is located:
Create Local Provider
const defaultLocale = Locale('ar', '');
const languageCodeKey = 'languageCode';
final storageService = locator<StorageService>();
final localProvider = StateNotifierProvider<LocalProvider, Locale>(
(_) {
final languageCode = storageService.read(languageCodeKey);
return LocalProvider(
languageCode.isEmpty ? defaultLocale.languageCode : languageCode,
);
},
);
class LocalProvider extends StateNotifier<Locale> {
LocalProvider(String languageCode) : super(Locale(languageCode));
void changeLocale(Locale? locale) {
state = locale ?? defaultLocale;
storageService.write(languageCodeKey, state.languageCode);
}
final supportedLocales = const [
Locale('en'),
Locale('ar'),
];
}To change loacal use LocaleChanger widget
class LocaleChanger extends ConsumerWidget {
const LocaleChanger({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final locale = ref.watch(localProvider);
final localeNotifier = ref.read(localProvider.notifier);
return Column(
children: [
Align(
alignment: AlignmentDirectional.centerStart,
child: Text(
S.of(context).applanguage,
style: body14,
),
),
const SizedBox(height: 6),
DropdownButtonFormField<Locale>(
value: locale,
onChanged: localeNotifier.changeLocale,
items: localeNotifier.supportedLocales.map((val) {
final lang =
languages.firstWhere((e) => e['code'] == val.languageCode);
return DropdownMenuItem(
value: val,
child: Row(
children: <Widget>[
Text(lang['flag']),
const SizedBox(width: 8.0),
Text(lang['name']),
],
),
);
}).toList(),
),
],
);
}
}To use translation
Text(
S.of(context).welcomeBack,
)The application uses a custom AppLogs utility for structured, colorful, and configurable logging across different environments.
- Multiple log levels: Success, Info, Warning, Debug, Error
- Color-coded console output
- Configurable log levels
- Metadata support
- Release mode logging control
- Success (🟢): Indicates successful operations
- Info (🔵): General informational messages
- Warning (🟠): Potential issues or important notifications
- Debug (⚪️): Detailed debugging information
- Error (🚫): Error and critical issue logs
// Basic logging
AppLogs.success('Operation completed successfully');
AppLogs.info('Application started', 'Startup');
AppLogs.warning('Low disk space', 'Storage');
AppLogs.debug('Fetching user data');
AppLogs.error('Failed to load user profile', 'Authentication',
metadata: {'userId': 123});
// Configuring log level
AppLogs.setLogLevel(LogLevel.warning); // Only show warnings and errors- Use appropriate log levels
- Include meaningful tags
- Add metadata for complex error tracking
- Avoid logging sensitive information
Control which log levels are displayed:
// Only show warnings and errors
AppLogs.setLogLevel(LogLevel.warning);By default, logs are disabled in release mode. To enable:
AppLogs.enableReleaseLogging = true;You can customize the appearance of log levels:
AppLogs.setLogStyle(LogLevel.success,
LogStyle(colorCode: '35', emoji: '🌟')
);The SnackBar utility provides a convenient and consistent way to display temporary notifications across the application. It supports different types of notifications with color-coded backgrounds and integrates with the app's logging system.
- Multiple SnackBar types: Success, Error, Warning, Info
- Consistent styling
- Automatic logging of messages
- Customizable actions
- Floating behavior
// Success Notification
AppSnackBar.success('Operation completed successfully');
// Error Notification
AppSnackBar.error('Something went wrong');
// Warning Notification
AppSnackBar.warning('Proceed with caution');
// Info Notification
AppSnackBar.info('Additional information');AppSnackBar.success(
'Saved successfully',
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Undo action logic
}
)
);- Success SnackBar (Green): Positive outcomes and successful operations
- Error SnackBar (Red): Error messages and critical notifications
- Warning SnackBar (Amber): Cautionary or important messages
- Info SnackBar (Blue): Informational messages and updates
- Requires a global
scaffoldKeyto be set up in your app - Automatically logs messages using
AppLogs - Default action is to dismiss the SnackBar
- Only one SnackBar is shown at a time
- Use appropriate SnackBar type based on the message context
- Keep messages concise and clear
- Use SnackBars for temporary, non-blocking notifications
- Avoid overusing SnackBars to prevent user fatigue
The CustomAlertDialog is a flexible and visually appealing alert dialog component that supports multiple dialog types with dynamic styling and behavior.
- Four distinct dialog types: Error, Warning, Info, Success
- Dynamic icon and color based on dialog type
- Customizable title, description, and button texts
- Centered layout with rounded design
- Themed typography
- Callback support for accept action
-
Error Dialog (Red)
- Used for critical or destructive actions
- Error-related confirmations
-
Warning Dialog (Orange)
- Cautions about potential consequences
- Actions requiring careful consideration
-
Info Dialog (Primary Color)
- Informational messages
- Neutral notifications
-
Success Dialog (Green)
- Positive confirmations
- Celebration or completion messages
await showDialog<bool>(
context: context,
builder: (ctx) => CustomAlertDialog(
title: 'Delete Account',
description: 'Are you sure you want to delete your account?',
acceptText: 'Delete',
cancelText: 'Cancel',
type: AlertDialogType.error,
onAccept: () {
// Perform deletion logic
},
),
);// Warning Dialog
CustomAlertDialog(
title: 'Unsaved Changes',
description: 'You have unsaved changes. Proceed?',
type: AlertDialogType.warning,
// ...
)
// Info Dialog
CustomAlertDialog(
title: 'Update Available',
description: 'A new version of the app is ready.',
type: AlertDialogType.info,
// ...
)
// Success Dialog
CustomAlertDialog(
title: 'Profile Updated',
description: 'Your profile has been successfully updated.',
type: AlertDialogType.success,
// ...
)- Fully customizable text for title, description, and buttons
- Supports custom accept action callback
- Inherits theme styling for consistent design
- Use appropriate dialog type based on context
- Keep titles and descriptions concise
- Provide clear and specific actions
- Use dialogs for important confirmations or information
- Avoid overusing dialogs to prevent user frustration
- Centered layout for better visibility
- Large, clear icons
- High-contrast color scheme
- Readable typography
The DateHelper class provides a human-readable, localized representation of time elapsed since a given date. It converts raw datetime differences into friendly, easy-to-read time phrases.
- Supports multiple time granularities:
- Just now
- Seconds
- Minutes
- Hours
- Days
- Weeks
- Months
- Years
- Fully localized time representations
- Simple, static method for easy usage
| Range | Output Format |
|---|---|
| < 5 seconds | "Just now" |
| < 1 minute | "X seconds ago" |
| < 1 hour | "X minutes ago" |
| < 24 hours | "X hours ago" |
| < 7 days | "X days ago" |
| < 30 days | "X weeks ago" |
| < 365 days | "X months ago" |
| 1 year+ | "X years ago" |
// Basic usage
DateTime pastDate = DateTime.now().subtract(Duration(hours: 3));
String readableTime = DateHelper.timeAgo(pastDate);
print(readableTime); // "3 hours ago"
// Different time ranges
DateTime justNow = DateTime.now().subtract(Duration(seconds: 3));
DateTime fewMinutesAgo = DateTime.now().subtract(Duration(minutes: 5));
DateTime yesterdayDate = DateTime.now().subtract(Duration(days: 1));
DateTime oldDate = DateTime.now().subtract(Duration(days: 365));
print(DateHelper.timeAgo(justNow)); // "Just now"
print(DateHelper.timeAgo(fewMinutesAgo)); // "5 minutes ago"
print(DateHelper.timeAgo(yesterdayDate)); // "1 day ago"
print(DateHelper.timeAgo(oldDate)); // "1 year ago"- Uses
S.currentfor language-specific time representations - Supports multiple languages through generated localization files
- Automatic translation of time phrases
- Use for displaying relative timestamps
- Ideal for social media feeds, chat logs, and activity streams
- Provides more user-friendly experience than raw datetime
- Lightweight and performant
- Calculations based on current system time
- Accuracy depends on device's current datetime setting
- Recommended for recent time differences
- Add custom date formatting options
- Support for more granular time representations
- Configurable time thresholds
A Flutter widget that provides a paginated list with built-in loading states, error handling, and infinite scrolling capabilities using Riverpod for state management.
- Infinite scrolling with customizable scroll threshold
- Pull-to-refresh functionality
- Built-in loading, error, and empty states
- Customizable widgets for all states
- Support for list separators
- Automatic pagination handling
- Built-in error handling and retry mechanisms
PaginatedListWidget<User>(
provider: userListProvider,
itemBuilder: (context, user) => UserListItem(user: user),
)PaginatedListWidget<User>(
// Required parameters
provider: userListProvider,
itemBuilder: (context, user) => UserListItem(user: user),
// Optional customization
loadTriggerThreshold: 0.8,
enablePullToRefresh: true,
padding: EdgeInsets.all(16.0),
separatorBuilder: (context, index) => Divider(),
// Custom state widgets
loadingWidget: CustomLoadingSpinner(),
errorWidget: CustomErrorWidget(),
emptyWidget: CustomEmptyState(),
bottomLoadingWidget: CustomBottomLoader(),
bottomErrorWidget: CustomBottomError(),
noMoreDataWidget: CustomNoMoreDataWidget(),
// Optional scroll controller
scrollController: myScrollController,
)Create a state notifier that extends PaginatedListNotifier:
final usersProvider = AutoDisposeStateNotifierProvider<UsersNotifier, PaginationState<User>>(
(ref) => UsersNotifier(),
);
class UsersNotifier extends PaginatedListNotifier<User> {
@override
Future<List<User>> fetchPage(int page) async {
// Implement your API call here
final response = await api.getUsers(page: page, limit: pageSize);
return response.users;
}
}The loadTriggerThreshold parameter (default: 0.8) determines when to load the next page. It represents the scroll position as a percentage of the total scrollable area:
PaginatedListWidget<User>(
provider: userListProvider,
itemBuilder: (context, user) => UserListItem(user: user),
loadTriggerThreshold: 0.7, // Load next page at 70% scroll
)Enable or disable pull-to-refresh functionality:
PaginatedListWidget<User>(
provider: userListProvider,
itemBuilder: (context, user) => UserListItem(user: user),
enablePullToRefresh: true, // Enables pull-to-refresh
)Customize the appearance of various states:
PaginatedListWidget<User>(
provider: userListProvider,
itemBuilder: (context, user) => UserListItem(user: user),
loadingWidget: Center(child: CircularProgressIndicator()),
errorWidget: CustomErrorWidget(),
emptyWidget: Center(child: Text('No users found')),
bottomLoadingWidget: CustomBottomLoader(),
bottomErrorWidget: CustomBottomError(),
noMoreDataWidget: Center(child: Text('No more users')),
)The widget automatically handles errors and provides retry functionality. You can customize the error display using the errorWidget and bottomErrorWidget parameters.
- Follow Flutter/Dart best practices
- Use meaningful variable and function names
- Write comprehensive documentation
- Common issues and their solutions
- Debugging tips specific to this template
- List any performance-related configurations
- Mention used optimization techniques
- Brief overview of security measures
- Recommendations for secure usage
