282 lines
8.3 KiB
Dart
Raw Normal View History

2025-08-28 01:11:19 +07:00
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import '../../../common/theme/theme.dart';
import '../../components/field/field.dart';
2025-08-29 16:07:01 +07:00
import '../../router/app_router.gr.dart';
2025-08-28 01:11:19 +07:00
import 'widgets/empty_merchant_card.dart';
import 'widgets/merchant_card.dart';
@RoutePage()
class MerchantPage extends StatefulWidget {
const MerchantPage({super.key});
@override
State<MerchantPage> createState() => _MerchantPageState();
}
class _MerchantPageState extends State<MerchantPage> {
final TextEditingController _searchController = TextEditingController();
final List<MerchantModel> _allMerchants = _generateMockMerchants();
List<MerchantModel> _filteredMerchants = [];
2025-08-29 21:02:57 +07:00
String? _selectedCategory;
late List<String> _categories;
2025-08-28 01:11:19 +07:00
@override
void initState() {
super.initState();
_filteredMerchants = _allMerchants;
2025-08-29 21:02:57 +07:00
_categories = _getAllCategories();
2025-08-28 01:11:19 +07:00
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
2025-08-29 21:02:57 +07:00
List<String> _getAllCategories() {
final categories = _allMerchants
.map((merchant) => merchant.category)
.toSet()
.toList();
categories.sort();
return categories;
}
2025-08-28 01:11:19 +07:00
void _filterMerchants(String query) {
setState(() {
2025-08-29 21:02:57 +07:00
var merchants = _allMerchants;
// Filter by category first
if (_selectedCategory != null) {
merchants = merchants
.where((merchant) => merchant.category == _selectedCategory)
.toList();
}
// Then filter by search query
if (query.isNotEmpty) {
merchants = merchants
2025-08-28 01:11:19 +07:00
.where(
(merchant) =>
merchant.name.toLowerCase().contains(query.toLowerCase()) ||
merchant.category.toLowerCase().contains(query.toLowerCase()),
)
.toList();
}
2025-08-29 21:02:57 +07:00
_filteredMerchants = merchants;
});
}
void _onCategorySelected(String? category) {
setState(() {
_selectedCategory = category;
2025-08-28 01:11:19 +07:00
});
2025-08-29 21:02:57 +07:00
_filterMerchants(_searchController.text);
2025-08-28 01:11:19 +07:00
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.background,
appBar: AppBar(
2025-08-29 21:02:57 +07:00
title: const Text('Merchants'),
2025-08-28 01:11:19 +07:00
bottom: PreferredSize(
2025-08-29 21:02:57 +07:00
preferredSize: const Size.fromHeight(
130,
), // Increased height for filter chips
child: Column(
children: [
// Search Field
SearchTextField(
hintText: 'Search merchants...',
prefixIcon: Icons.search,
controller: _searchController,
// onChanged: _filterMerchants,
onClear: () {
_searchController.clear();
_filterMerchants('');
},
),
const SizedBox(height: 12),
// Category Filter Chips
// Category Filter Chips - Updated with AppColor and opacity 1
Container(
height: 40,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ListView(
scrollDirection: Axis.horizontal,
children: [
// "All" filter chip
Padding(
padding: const EdgeInsets.only(right: 8),
child: FilterChip(
label: const Text('All'),
selected: _selectedCategory == null,
onSelected: (selected) {
_onCategorySelected(null);
},
selectedColor: AppColor.primary,
backgroundColor: AppColor.white,
checkmarkColor: AppColor.white,
labelStyle: TextStyle(
color: _selectedCategory == null
? AppColor.white
: AppColor.primary,
),
side: BorderSide(color: AppColor.primary, width: 1),
),
),
// Category filter chips
..._categories.map(
(category) => Padding(
padding: const EdgeInsets.only(right: 8),
child: FilterChip(
label: Text(category),
selected: _selectedCategory == category,
onSelected: (selected) {
_onCategorySelected(selected ? category : null);
},
selectedColor: AppColor.primary,
backgroundColor: AppColor.white,
checkmarkColor: AppColor.white,
labelStyle: TextStyle(
color: _selectedCategory == category
? AppColor.white
: AppColor.primary,
),
side: BorderSide(color: AppColor.primary, width: 1),
),
),
),
],
),
),
const SizedBox(height: 8),
],
2025-08-28 01:11:19 +07:00
),
),
),
body: _filteredMerchants.isEmpty
? _buildEmptyState()
: Padding(
padding: const EdgeInsets.all(16),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 0.85,
),
itemCount: _filteredMerchants.length,
itemBuilder: (context, index) {
return _buildMerchantCard(_filteredMerchants[index]);
},
),
),
);
}
Widget _buildMerchantCard(MerchantModel merchant) {
return MerchantCard(
merchant: merchant,
onTap: () {
2025-08-29 16:07:01 +07:00
context.router.push(MerchantDetailRoute(merchant: merchant));
2025-08-28 01:11:19 +07:00
},
);
}
Widget _buildEmptyState() {
return const EmptyMerchantCard();
}
static List<MerchantModel> _generateMockMerchants() {
return [
MerchantModel(
id: '1',
name: 'Warung Makan Sederhana',
category: 'Food & Beverage',
rating: 4.5,
isOpen: true,
imageUrl: 'https://via.placeholder.com/150',
),
MerchantModel(
id: '2',
name: 'Toko Elektronik',
category: 'Electronics',
rating: 4.2,
isOpen: true,
imageUrl: 'https://via.placeholder.com/150',
),
MerchantModel(
id: '3',
name: 'Apotek Sehat',
category: 'Healthcare',
rating: 4.8,
isOpen: false,
imageUrl: 'https://via.placeholder.com/150',
),
MerchantModel(
id: '4',
name: 'Fashion Store',
category: 'Clothing',
rating: 4.1,
isOpen: true,
imageUrl: 'https://via.placeholder.com/150',
),
MerchantModel(
id: '5',
name: 'Bengkel Motor',
category: 'Automotive',
rating: 4.3,
isOpen: true,
imageUrl: 'https://via.placeholder.com/150',
),
MerchantModel(
id: '6',
name: 'Minimarket 24',
category: 'Retail',
rating: 4.0,
isOpen: true,
imageUrl: 'https://via.placeholder.com/150',
),
MerchantModel(
id: '7',
name: 'Salon Kecantikan',
category: 'Beauty',
rating: 4.6,
isOpen: false,
imageUrl: 'https://via.placeholder.com/150',
),
MerchantModel(
id: '8',
name: 'Laundry Express',
category: 'Service',
rating: 4.4,
isOpen: true,
imageUrl: 'https://via.placeholder.com/150',
),
];
}
}
class MerchantModel {
final String id;
final String name;
final String category;
final double rating;
final bool isOpen;
final String imageUrl;
MerchantModel({
required this.id,
required this.name,
required this.category,
required this.rating,
required this.isOpen,
required this.imageUrl,
});
}