QueenFruits/Mobile Operasional/lib/features/supplier/presentation/screens/supplier_screen.dart

465 lines
18 KiB
Dart

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/constants/app_color.dart';
import 'package:niogu_app/core/constants/app_font_size.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/supplier/domain/entities/supplier.dart';
import 'package:niogu_app/features/supplier/presentation/providers/supplier_provider.dart';
import 'package:niogu_app/features/supplier/presentation/widgets/supplier_shimmer.dart';
import 'package:sizer/sizer.dart';
class SupplierScreen extends ConsumerStatefulWidget {
const SupplierScreen({super.key});
@override
ConsumerState<SupplierScreen> createState() => _SupplierScreenState();
}
class _SupplierScreenState extends ConsumerState<SupplierScreen> {
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(supplierSearchProvider.notifier).state = value;
});
}
Map<String, List<DisplaySuppliers>> _groupedSuppliers(
List<DisplaySuppliers> suppliers,
) {
suppliers.sort((a, b) => a.name.compareTo(b.name));
final Map<String, List<DisplaySuppliers>> groupedSuppliers = {};
for (final supplier in suppliers) {
if (supplier.name.isEmpty) continue;
String firstLetter = supplier.name[0].toUpperCase();
if (!RegExp(r'[A-Z]').hasMatch(firstLetter)) {
firstLetter = '#';
}
if (!groupedSuppliers.containsKey(firstLetter)) {
groupedSuppliers[firstLetter] = [];
}
groupedSuppliers[firstLetter]!.add(supplier);
}
return groupedSuppliers;
}
Future<void> _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.addSupplierScreen,
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 Pemasok",
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 pemasok 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.addSupplierScreen);
},
),
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 filteredSupplierState = ref.watch(filteredSupplierProvider);
final supplierEmptyState = ref.watch(supplierEmptyProvider);
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(
"Pemasok",
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 pemasok",
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: filteredSupplierState.when(
data: (suppliers) {
final groupedData = _groupedSuppliers(suppliers);
switch (supplierEmptyState) {
case SupplierEmpty.loading:
return SizedBox();
case SupplierEmpty.empty_database:
return CustomEmptyScreen(
title: "Tidak Ada Pemasok",
body: "Kamu belum memiliki pemasok",
);
case SupplierEmpty.empty_search_result:
return CustomEmptyScreen(
body: "Pemasok Tidak Ditemukan",
);
case SupplierEmpty.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<DisplaySuppliers> suppliers =
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,
),
),
),
...suppliers.map((supplier) {
return Container(
padding: isTablet
? EdgeInsets.symmetric(
vertical: 1.5.h,
)
: EdgeInsets.zero,
color: Colors.white,
child: Column(
children: [
ListTile(
onTap: () {
context.pushNamed(
AppRoute.editSupplierScreen,
pathParameters: {
"id": supplier.id,
},
);
},
contentPadding:
EdgeInsets.symmetric(
horizontal: 5.w,
vertical: 0.5.h,
),
title: Text(
supplier.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 SupplierShimmer(),
),
),
],
),
),
),
);
},
);
}
}