import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:niogu_app/core/widgets/custom_error_screen.dart'; import 'package:niogu_app/core/router/app_route.dart'; import 'package:niogu_app/core/widgets/custom_empty_screen.dart'; import 'package:niogu_app/core/widgets/custom_snackbar.dart'; import 'package:niogu_app/features/customer/domain/entities/customer.dart'; import 'package:niogu_app/features/customer/presentation/providers/customer_provider.dart'; import 'package:niogu_app/features/customer/presentation/widgets/customer_shimmer.dart'; import 'package:sizer/sizer.dart'; import 'package:niogu_app/core/constants/app_color.dart'; import 'package:niogu_app/core/constants/app_font_size.dart'; class CustomerScreen extends ConsumerStatefulWidget { const CustomerScreen({super.key}); @override ConsumerState createState() => _CustomerScreenState(); } class _CustomerScreenState extends ConsumerState { final FocusNode _searchFocusNode = FocusNode(); Color _searchIconColor = Colors.grey; Timer? _debounce; final TextEditingController _searchController = TextEditingController(); @override void initState() { // TODO: implement initState super.initState(); _searchFocusNode.addListener(() { setState(() { _searchIconColor = _searchFocusNode.hasFocus ? Colors.black : Colors.grey; }); }); } @override void dispose() { _searchController.dispose(); super.dispose(); } void _onSearchChanged(String value) { if (_debounce?.isActive ?? false) _debounce?.cancel(); _debounce = Timer(const Duration(milliseconds: 800), () { ref.read(customerSearchProvider.notifier).state = value; }); } Map> _groupedCustomers( List customers, ) { customers.sort((a, b) => a.name.compareTo(b.name)); final Map> groupedCustomers = {}; for (final customer in customers) { if (customer.name.isEmpty) continue; String firstLetter = customer.name[0].toUpperCase(); if (!RegExp(r'[A-Z]').hasMatch(firstLetter)) { firstLetter = '#'; } if (!groupedCustomers.containsKey(firstLetter)) { groupedCustomers[firstLetter] = []; } groupedCustomers[firstLetter]!.add(customer); } return groupedCustomers; } Future _handleImportContact() async { final permission = await FlutterContacts.requestPermission(); if (permission) { final contact = await FlutterContacts.openExternalPick(); if (contact != null) { String name = contact.displayName; String phone = ""; if (contact.phones.isNotEmpty) { phone = contact.phones.first.number; } if (!mounted) return; context.pushNamed( AppRoute.addCustomerScreen, extra: {'name': name, 'phone': phone}, ); } } else { CustomSnackbar.showWarning(context, "Akses Ditolak"); } } void _showAddOptions(BuildContext context) { final bool isTablet = 100.w >= 600; showModalBottomSheet( context: context, backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(2.5.w)), ), constraints: const BoxConstraints(maxWidth: double.infinity), builder: (context) { return SafeArea( child: Container( width: 100.w, padding: EdgeInsets.symmetric(vertical: 2.h), clipBehavior: Clip.hardEdge, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(6.w)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: EdgeInsets.fromLTRB(5.w, 2.h, 5.w, 1.h), child: Text( "Tambah Pelanggan", style: TextStyle( fontSize: AppFontSize.medium.sp, fontWeight: FontWeight.bold, ), ), ), SizedBox(height: 2.h), ListTile( leading: Padding( padding: EdgeInsets.only(left: 5.w), child: Icon( Icons.edit_note_rounded, color: Colors.blue, size: 6.w, ), ), title: Text( "Input Manual", style: TextStyle( fontWeight: FontWeight.bold, fontSize: isTablet ? AppFontSize.medium.sp : AppFontSize.small.sp, ), ), subtitle: Text( "Isi informasi pelanggan secara manual", style: TextStyle( fontSize: isTablet ? (AppFontSize.medium - 1.25).sp : (AppFontSize.small - 1.25).sp, color: Colors.grey, ), ), onTap: () { context.pop(); context.pushNamed(AppRoute.addCustomerScreen); }, ), SizedBox(height: 2.h), ListTile( leading: Padding( padding: EdgeInsets.only(left: 5.w), child: Icon( Icons.contacts_rounded, color: Colors.green, size: 6.w, ), ), title: Text( "Ambil dari Kontak HP", style: TextStyle( fontWeight: FontWeight.bold, fontSize: isTablet ? AppFontSize.medium.sp : AppFontSize.small.sp, ), ), subtitle: Text( "Pilih langsung dari buku telepon", style: TextStyle( fontSize: isTablet ? (AppFontSize.medium - 1.25).sp : (AppFontSize.small - 1.25).sp, color: Colors.grey, ), ), onTap: () async { context.pop(); await _handleImportContact(); }, ), SizedBox(height: 2.h), ], ), ), ); }, ); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final bool isTablet = 100.w >= 600; const greyColor = Color(0xFFF5F5F5); final filteredCustomerState = ref.watch(filteredCustomerProvider); final customerEmptyState = ref.watch(customerEmptyProvider); return SafeArea( top: false, bottom: true, right: false, left: false, child: Scaffold( backgroundColor: const Color(0xFFF9FAFB), appBar: AppBar( backgroundColor: Colors.white, surfaceTintColor: Colors.white, elevation: 0, toolbarHeight: isTablet ? 7.5.h : kToolbarHeight, centerTitle: true, leading: Center( child: Material( color: Colors.transparent, type: MaterialType.canvas, child: InkWell( onTap: () => context.pop(), borderRadius: BorderRadius.circular(2.5.w), child: Container( width: 10.w, height: 10.w, decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.grey.shade200), borderRadius: BorderRadius.circular(2.5.w), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 5, offset: const Offset(0, 2), ), ], ), child: Icon( Icons.arrow_back_ios_new_rounded, color: Colors.black87, size: 5.w, ), ), ), ), ), leadingWidth: 18.w, title: Text( "Pelanggan", style: TextStyle( color: Colors.black87, fontWeight: FontWeight.bold, fontSize: AppFontSize.medium.sp, ), ), actions: [ IconButton( onPressed: () => _showAddOptions(context), icon: Icon( Icons.add_circle, color: AppColor.primaryColor, size: 8.w, ), ), SizedBox(width: 3.w), ], ), body: Padding( padding: EdgeInsets.symmetric(horizontal: 5.w), child: Column( children: [ Container( height: 6.h, padding: isTablet ? EdgeInsets.symmetric(vertical: 1.h, horizontal: 2.w) : EdgeInsets.zero, decoration: BoxDecoration( color: greyColor, borderRadius: BorderRadius.circular(2.5.w), ), child: TextField( focusNode: _searchFocusNode, onChanged: _onSearchChanged, textAlignVertical: TextAlignVertical.center, style: TextStyle( color: Colors.black87, fontSize: isTablet ? AppFontSize.medium.sp : AppFontSize.small.sp, ), decoration: InputDecoration( hintText: "Cari nama pelanggan", hintStyle: TextStyle( color: _searchIconColor, fontSize: isTablet ? AppFontSize.medium.sp : AppFontSize.small.sp, ), prefixIcon: Icon( Icons.search, color: _searchIconColor, size: 5.w, ), border: InputBorder.none, contentPadding: EdgeInsets.zero, isDense: true, ), ), ), SizedBox(height: 3.h), Expanded( child: filteredCustomerState.when( data: (customers) { final groupedData = _groupedCustomers(customers); switch (customerEmptyState) { case CustomerEmpty.loading: return SizedBox(); case CustomerEmpty.empty_database: return CustomEmptyScreen( title: "Tidak Ada Pelanggan", body: "Kamu belum memiliki pelanggan", ); case CustomerEmpty.empty_search_result: return CustomEmptyScreen( body: "Pelanggan Tidak Ditemukan", ); case CustomerEmpty.has_data: return ListView.builder( physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only(bottom: 5.h), itemCount: groupedData.keys.length, itemBuilder: (context, index) { String letter = groupedData.keys.elementAt( index, ); List customers = groupedData[letter]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, padding: EdgeInsets.symmetric( horizontal: 5.w, vertical: 1.h, ), color: Colors.grey[200], child: Text( letter, style: TextStyle( fontWeight: FontWeight.bold, color: Colors.grey[700], fontSize: isTablet ? AppFontSize.medium.sp : AppFontSize.small.sp, ), ), ), ...customers.map((customer) { return Container( padding: isTablet ? EdgeInsets.symmetric( vertical: 1.5.h, ) : EdgeInsets.zero, color: Colors.white, child: Column( children: [ ListTile( onTap: () { context.pushNamed( AppRoute.editCustomerScreen, pathParameters: { "id": customer.id, }, ); }, contentPadding: EdgeInsets.symmetric( horizontal: 5.w, vertical: 0.5.h, ), title: Text( customer.name, style: TextStyle( fontSize: isTablet ? AppFontSize.medium.sp : AppFontSize.small.sp, fontWeight: FontWeight.w500, color: Colors.black87, ), ), trailing: Icon( Icons.chevron_right_rounded, size: 5.w, color: Colors.grey[400], ), ), Divider( height: 1, thickness: 1, indent: 5.w, color: Colors.grey[100], ), ], ), ); }), ], ); }, ); } }, error: (error, stackTrace) { return CustomErrorScreen( message: "Ups, terjadi kesalahan", onRefresh: () {}, ); }, loading: () => const CustomerShimmer(), ), ), ], ), ), ), ); }, ); } }