231 lines
7.1 KiB
Dart
231 lines
7.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'dart:io' show Platform;
|
|
import '../theme/theme_extensions.dart';
|
|
import 'package:conduit/l10n/app_localizations.dart';
|
|
|
|
/// Utility functions for common UI patterns and helpers
|
|
/// Following Conduit design principles
|
|
|
|
class UiUtils {
|
|
static bool get isIOS => Platform.isIOS;
|
|
|
|
/// Returns platform-appropriate icon
|
|
static IconData platformIcon({
|
|
required IconData ios,
|
|
required IconData android,
|
|
}) {
|
|
return isIOS ? ios : android;
|
|
}
|
|
|
|
/// Common platform icons used throughout the app
|
|
static IconData get chatIcon =>
|
|
platformIcon(ios: CupertinoIcons.chat_bubble_2, android: Icons.chat);
|
|
|
|
static IconData get searchIcon =>
|
|
platformIcon(ios: CupertinoIcons.search, android: Icons.search);
|
|
|
|
static IconData get deleteIcon =>
|
|
platformIcon(ios: CupertinoIcons.delete, android: Icons.delete);
|
|
|
|
static IconData get archiveIcon =>
|
|
platformIcon(ios: CupertinoIcons.archivebox, android: Icons.archive);
|
|
|
|
static IconData get shareIcon =>
|
|
platformIcon(ios: CupertinoIcons.share, android: Icons.share);
|
|
|
|
static IconData get settingsIcon =>
|
|
platformIcon(ios: CupertinoIcons.gear, android: Icons.settings);
|
|
|
|
static IconData get editIcon =>
|
|
platformIcon(ios: CupertinoIcons.pencil, android: Icons.edit_outlined);
|
|
|
|
static IconData get menuIcon =>
|
|
platformIcon(ios: CupertinoIcons.line_horizontal_3, android: Icons.menu);
|
|
|
|
static IconData get addIcon =>
|
|
platformIcon(ios: CupertinoIcons.plus_circle, android: Icons.add);
|
|
|
|
static IconData get attachIcon =>
|
|
platformIcon(ios: CupertinoIcons.paperclip, android: Icons.attach_file);
|
|
|
|
static IconData get micIcon =>
|
|
platformIcon(ios: CupertinoIcons.mic, android: Icons.mic);
|
|
|
|
static IconData get sendIcon => platformIcon(
|
|
ios: CupertinoIcons.arrow_up_circle_fill,
|
|
android: Icons.send,
|
|
);
|
|
|
|
static IconData get moreIcon => platformIcon(
|
|
ios: CupertinoIcons.ellipsis_vertical,
|
|
android: Icons.more_vert,
|
|
);
|
|
|
|
static IconData get closeIcon =>
|
|
platformIcon(ios: CupertinoIcons.xmark, android: Icons.close);
|
|
|
|
static IconData get checkIcon =>
|
|
platformIcon(ios: CupertinoIcons.check_mark, android: Icons.check);
|
|
|
|
static IconData get globeIcon =>
|
|
platformIcon(ios: CupertinoIcons.globe, android: Icons.public);
|
|
|
|
static IconData get folderIcon =>
|
|
platformIcon(ios: CupertinoIcons.folder, android: Icons.folder);
|
|
|
|
static IconData get tagIcon =>
|
|
platformIcon(ios: CupertinoIcons.tag, android: Icons.label);
|
|
|
|
static IconData get copyIcon =>
|
|
platformIcon(ios: CupertinoIcons.doc_on_doc, android: Icons.copy);
|
|
|
|
static IconData get pinIcon =>
|
|
platformIcon(ios: CupertinoIcons.pin_fill, android: Icons.push_pin);
|
|
|
|
static IconData get unpinIcon => platformIcon(
|
|
ios: CupertinoIcons.pin_slash,
|
|
android: Icons.push_pin_outlined,
|
|
);
|
|
|
|
/// Shows a Conduit-styled snackbar with conversational messaging
|
|
static void showMessage(
|
|
BuildContext context,
|
|
String message, {
|
|
bool isError = false,
|
|
VoidCallback? onRetry,
|
|
Duration? duration,
|
|
}) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(message),
|
|
backgroundColor: isError
|
|
? context.conduitTheme.error
|
|
: context.conduitTheme.buttonPrimary,
|
|
behavior: SnackBarBehavior.floating,
|
|
action: onRetry != null
|
|
? SnackBarAction(
|
|
label: AppLocalizations.of(context)!.retry,
|
|
textColor: context.conduitTheme.textInverse,
|
|
onPressed: onRetry,
|
|
)
|
|
: null,
|
|
duration: duration ?? const Duration(seconds: 3),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Shows a Conduit-styled confirmation dialog
|
|
static Future<bool> showConfirmationDialog(
|
|
BuildContext context, {
|
|
required String title,
|
|
required String message,
|
|
String confirmText = 'Confirm',
|
|
String cancelText = 'Cancel',
|
|
bool isDestructive = false,
|
|
}) async {
|
|
return await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
backgroundColor: context.conduitTheme.surfaceBackground,
|
|
title: Text(
|
|
title,
|
|
style: TextStyle(color: context.conduitTheme.textPrimary),
|
|
),
|
|
content: Text(
|
|
message,
|
|
style: TextStyle(color: context.conduitTheme.textSecondary),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: Text(
|
|
cancelText.isNotEmpty
|
|
? cancelText
|
|
: AppLocalizations.of(context)!.cancel,
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
style: isDestructive
|
|
? TextButton.styleFrom(
|
|
foregroundColor: context.conduitTheme.error,
|
|
)
|
|
: null,
|
|
child: Text(
|
|
confirmText.isNotEmpty
|
|
? confirmText
|
|
: AppLocalizations.of(context)!.confirm,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
) ??
|
|
false;
|
|
}
|
|
|
|
/// Formats dates in a conversational way following Conduit patterns
|
|
static String formatDate(DateTime date) {
|
|
final now = DateTime.now();
|
|
final difference = now.difference(date);
|
|
|
|
if (difference.inDays == 0) {
|
|
if (difference.inHours == 0) {
|
|
if (difference.inMinutes == 0) {
|
|
return 'Just now';
|
|
}
|
|
return '${difference.inMinutes}m ago';
|
|
}
|
|
return '${difference.inHours}h ago';
|
|
} else if (difference.inDays == 1) {
|
|
return 'Yesterday';
|
|
} else if (difference.inDays < 7) {
|
|
return '${difference.inDays} days ago';
|
|
} else if (difference.inDays < 30) {
|
|
final weeks = (difference.inDays / 7).floor();
|
|
return weeks == 1 ? '1 week ago' : '$weeks weeks ago';
|
|
} else if (difference.inDays < 365) {
|
|
final months = (difference.inDays / 30).floor();
|
|
return months == 1 ? '1 month ago' : '$months months ago';
|
|
} else {
|
|
return '${date.month}/${date.day}/${date.year}';
|
|
}
|
|
}
|
|
|
|
/// Creates a smooth haptic feedback on iOS
|
|
static void hapticFeedback() {
|
|
if (isIOS) {
|
|
// iOS haptic feedback would be implemented here
|
|
// For now, we'll leave this as a placeholder
|
|
}
|
|
}
|
|
|
|
/// Safe area padding helper
|
|
static EdgeInsets safeAreaPadding(BuildContext context) {
|
|
return MediaQuery.of(context).padding;
|
|
}
|
|
|
|
/// Screen size helpers
|
|
static Size screenSize(BuildContext context) {
|
|
return MediaQuery.of(context).size;
|
|
}
|
|
|
|
static bool isSmallScreen(BuildContext context) {
|
|
return screenSize(context).width < 375;
|
|
}
|
|
|
|
static bool isLargeScreen(BuildContext context) {
|
|
return screenSize(context).width > 414;
|
|
}
|
|
|
|
/// Keyboard handling
|
|
static bool isKeyboardOpen(BuildContext context) {
|
|
return MediaQuery.of(context).viewInsets.bottom > 0;
|
|
}
|
|
|
|
/// Focus management
|
|
static void unfocus(BuildContext context) {
|
|
FocusScope.of(context).unfocus();
|
|
}
|
|
}
|