Initial commit: SIBU 2.0 MISSION
This commit is contained in:
159
old/lib/presentation/taxi_screen/widgets/taxi_card_widget.dart
Normal file
159
old/lib/presentation/taxi_screen/widgets/taxi_card_widget.dart
Normal file
@ -0,0 +1,159 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../models/taxi_model.dart';
|
||||
|
||||
/// Individual taxi card widget displaying taxi information with call and favorite functionality
|
||||
class TaxiCardWidget extends StatelessWidget {
|
||||
final TaxiModel taxi;
|
||||
final bool isFavorite;
|
||||
final Function(String) onFavoriteToggle;
|
||||
|
||||
const TaxiCardWidget({
|
||||
super.key,
|
||||
required this.taxi,
|
||||
required this.isFavorite,
|
||||
required this.onFavoriteToggle,
|
||||
});
|
||||
|
||||
/// Launch phone call
|
||||
Future<void> _makePhoneCall(String phoneNumber) async {
|
||||
final Uri launchUri = Uri(scheme: 'tel', path: phoneNumber);
|
||||
try {
|
||||
if (await canLaunchUrl(launchUri)) {
|
||||
await launchUrl(launchUri);
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle error silently or show user feedback
|
||||
}
|
||||
}
|
||||
|
||||
/// Capitalize first letter of shift for display
|
||||
String _formatShift(String shift) {
|
||||
if (shift.isEmpty) return '';
|
||||
return shift[0].toUpperCase() + shift.substring(1);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header Row: Name and Favorite Button
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
taxi.name,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: const Color(0xFF101820),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => onFavoriteToggle(taxi.id),
|
||||
icon: Icon(
|
||||
isFavorite ? Icons.favorite : Icons.favorite_border,
|
||||
color:
|
||||
isFavorite
|
||||
? const Color(0xFFFEE715)
|
||||
: theme.iconTheme.color,
|
||||
),
|
||||
tooltip:
|
||||
isFavorite ? 'Remove from favorites' : 'Add to favorites',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Phone Row with Call Button
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.phone, size: 20, color: theme.iconTheme.color),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
taxi.phone,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: const Color(0xFF101820),
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _makePhoneCall(taxi.phone),
|
||||
icon: const Icon(Icons.call, size: 18),
|
||||
label: const Text('Call'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFFEE715),
|
||||
foregroundColor: const Color(0xFF101820),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Location and Shift Info
|
||||
Row(
|
||||
children: [
|
||||
// Corregimiento
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_on,
|
||||
size: 16,
|
||||
color: theme.iconTheme.color?.withAlpha(153),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
taxi.corregimiento,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.textTheme.bodySmall?.color?.withAlpha(
|
||||
153,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Shift
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.schedule,
|
||||
size: 16,
|
||||
color: theme.iconTheme.color?.withAlpha(153),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_formatShift(taxi.shift),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.textTheme.bodySmall?.color?.withAlpha(153),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Widget for displaying various empty states in the taxi screen
|
||||
class TaxiEmptyStateWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final String message;
|
||||
final IconData icon;
|
||||
final String? buttonText;
|
||||
final VoidCallback? onButtonPressed;
|
||||
|
||||
const TaxiEmptyStateWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.message,
|
||||
required this.icon,
|
||||
this.buttonText,
|
||||
this.onButtonPressed,
|
||||
});
|
||||
|
||||
/// Empty state when no filters are selected
|
||||
factory TaxiEmptyStateWidget.noFiltersSelected() {
|
||||
return const TaxiEmptyStateWidget(
|
||||
title: 'Select Filters',
|
||||
message: 'Choose a corregimiento and/or shift to see available taxis.',
|
||||
icon: Icons.filter_list,
|
||||
);
|
||||
}
|
||||
|
||||
/// Empty state when no results match the current filters
|
||||
factory TaxiEmptyStateWidget.noResultsFound({
|
||||
required VoidCallback onClearFilters,
|
||||
}) {
|
||||
return TaxiEmptyStateWidget(
|
||||
title: 'No taxis available for this selection.',
|
||||
message: 'Try adjusting your filters or search terms.',
|
||||
icon: Icons.search_off,
|
||||
buttonText: 'Clear Filters',
|
||||
onButtonPressed: onClearFilters,
|
||||
);
|
||||
}
|
||||
|
||||
/// Empty state when there's no data at all
|
||||
factory TaxiEmptyStateWidget.noData() {
|
||||
return const TaxiEmptyStateWidget(
|
||||
title: 'No taxis registered yet.',
|
||||
message: 'There are no taxi services available at the moment.',
|
||||
icon: Icons.local_taxi,
|
||||
);
|
||||
}
|
||||
|
||||
/// Empty state for error conditions
|
||||
factory TaxiEmptyStateWidget.error({
|
||||
required String error,
|
||||
required VoidCallback onRetry,
|
||||
}) {
|
||||
return TaxiEmptyStateWidget(
|
||||
title: 'Error Loading Taxis',
|
||||
message: error,
|
||||
icon: Icons.error_outline,
|
||||
buttonText: 'Retry',
|
||||
onButtonPressed: onRetry,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 80, color: theme.iconTheme.color?.withAlpha(128)),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
title,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
color: const Color(0xFF101820),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
message,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.textTheme.bodyMedium?.color?.withAlpha(153),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (buttonText != null && onButtonPressed != null) ...[
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: onButtonPressed,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFFEE715),
|
||||
foregroundColor: const Color(0xFF101820),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Text(buttonText!),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,189 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Widget for taxi search filters including corregimiento dropdown, shift dropdown and search input
|
||||
class TaxiFiltersWidget extends StatefulWidget {
|
||||
final List<String> corregimientos; // Changed from districts
|
||||
final List<String> shifts; // Added shifts list
|
||||
final String? selectedCorregimiento; // Changed from selectedDistrict
|
||||
final String? selectedShift; // Added shift selection
|
||||
final String searchText;
|
||||
final Function(String?)
|
||||
onCorregimientoChanged; // Changed from onDistrictChanged
|
||||
final Function(String?) onShiftChanged; // Added shift handler
|
||||
final Function(String) onSearchChanged;
|
||||
final VoidCallback onClearFilters;
|
||||
|
||||
const TaxiFiltersWidget({
|
||||
super.key,
|
||||
required this.corregimientos, // Changed from districts
|
||||
required this.shifts, // Added shifts
|
||||
required this.selectedCorregimiento, // Changed from selectedDistrict
|
||||
required this.selectedShift, // Added shift selection
|
||||
required this.searchText,
|
||||
required this.onCorregimientoChanged, // Changed from onDistrictChanged
|
||||
required this.onShiftChanged, // Added shift handler
|
||||
required this.onSearchChanged,
|
||||
required this.onClearFilters,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TaxiFiltersWidget> createState() => _TaxiFiltersWidgetState();
|
||||
}
|
||||
|
||||
class _TaxiFiltersWidgetState extends State<TaxiFiltersWidget> {
|
||||
late TextEditingController _searchController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_searchController = TextEditingController(text: widget.searchText);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: theme.shadowColor.withAlpha(26),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Filter Dropdowns Row
|
||||
Row(
|
||||
children: [
|
||||
// Corregimiento Dropdown
|
||||
Expanded(
|
||||
child: DropdownButtonFormField<String?>(
|
||||
value:
|
||||
widget
|
||||
.selectedCorregimiento, // Changed from selectedDistrict
|
||||
hint: const Text(
|
||||
'Seleccionar Corregimiento',
|
||||
), // Updated hint text
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
'Filter by Corregimiento', // Updated label as requested
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem<String?>(
|
||||
value: null,
|
||||
child: Text('Todos los corregimientos'), // Updated text
|
||||
),
|
||||
...widget.corregimientos.map(
|
||||
// Changed from districts
|
||||
(corregimiento) => DropdownMenuItem<String?>(
|
||||
value: corregimiento,
|
||||
child: Text(corregimiento),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged:
|
||||
widget
|
||||
.onCorregimientoChanged, // Changed from onDistrictChanged
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Shift Dropdown
|
||||
Expanded(
|
||||
child: DropdownButtonFormField<String?>(
|
||||
value: widget.selectedShift,
|
||||
hint: const Text('Seleccionar Turno'),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Filter by Shift', // Label as requested
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem<String?>(
|
||||
value: null,
|
||||
child: Text('Todos los turnos'),
|
||||
),
|
||||
...widget.shifts.map(
|
||||
(shift) => DropdownMenuItem<String?>(
|
||||
value: shift,
|
||||
child: Text(shift),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: widget.onShiftChanged,
|
||||
),
|
||||
),
|
||||
if (widget.selectedCorregimiento !=
|
||||
null || // Changed from selectedDistrict
|
||||
widget.selectedShift != null || // Added shift condition
|
||||
widget.searchText.isNotEmpty) ...[
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
onPressed: widget.onClearFilters,
|
||||
icon: const Icon(Icons.clear),
|
||||
tooltip: 'Clear Filters', // Updated tooltip text as requested
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.errorContainer,
|
||||
foregroundColor: theme.colorScheme.onErrorContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Search Input
|
||||
TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Buscar taxi o teléfono',
|
||||
hintText: 'Escribe el nombre o número...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
suffixIcon:
|
||||
widget.searchText.isNotEmpty
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
widget.onSearchChanged('');
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
tooltip: 'Limpiar búsqueda',
|
||||
)
|
||||
: null,
|
||||
),
|
||||
onChanged: widget.onSearchChanged,
|
||||
textInputAction: TextInputAction.search,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user