QueenFruits/Mobile Operasional/lib/features/pos/presentation/screens/already_customer_screen.dart

744 lines
33 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/widgets/custom_error_screen.dart';
import 'package:niogu_app/core/providers/app_provider.dart';
import 'package:niogu_app/core/router/app_route.dart';
import 'package:niogu_app/core/enums/user_role.dart';
import 'package:niogu_app/core/widgets/custom_empty_screen.dart';
import 'package:niogu_app/core/widgets/custom_snackbar.dart';
import 'package:niogu_app/core/widgets/pop_up_notification.dart';
import 'package:niogu_app/features/pos/domain/entities/pos.dart';
import 'package:niogu_app/features/pos/presentation/providers/pos_provider.dart';
import 'package:niogu_app/features/pos/presentation/widgets/already_customer_shimmer.dart';
import 'package:niogu_app/features/pos/presentation/widgets/checkbox_customer.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 AlreadyCustomerScreen extends ConsumerStatefulWidget {
const AlreadyCustomerScreen({super.key});
@override
ConsumerState<AlreadyCustomerScreen> createState() =>
_AlreadyCustomerScreenState();
}
class _AlreadyCustomerScreenState extends ConsumerState<AlreadyCustomerScreen> {
final FocusNode _searchFocusNode = FocusNode();
Color _searchIconColor = Colors.grey;
Timer? _debounce;
String? _selectedCustomerId;
String? _selectedCustomerAddress;
SelectedCustomer? _selectedCustomer;
@override
void initState() {
// TODO: implement initState
super.initState();
_searchFocusNode.addListener(() {
setState(() {
_searchIconColor = _searchFocusNode.hasFocus
? Colors.black
: Colors.grey;
});
});
}
@override
void dispose() {
// TODO: implement dispose
_searchFocusNode.dispose();
_debounce?.cancel();
super.dispose();
}
void _onSearchChanged(String value) {
if (_debounce?.isActive ?? false) _debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 800), () {
ref.read(alreadyCustomerSearchProvider.notifier).state = value;
});
}
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.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),
],
),
),
);
},
);
}
void _selectCustomer(
SelectedCustomer selectedCustomer,
String customerId,
String address,
) {
setState(() {
if (_selectedCustomerId == customerId &&
_selectedCustomerAddress == address) {
_selectedCustomerId = null;
_selectedCustomerAddress = null;
_selectedCustomer = null;
} else {
_selectedCustomerId = customerId;
_selectedCustomerAddress = address;
_selectedCustomer = selectedCustomer;
}
});
}
void _submitSelection() {
ref.read(selectedCustomerProvider.notifier).state = _selectedCustomer;
context.pop();
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final bool isTablet = 100.w >= 600;
const greyColor = Color(0xFFF5F5F5);
final messageActivity = ref.watch(messageActivityProvider);
final currentUserRole = ref.watch(currentUserRoleProvider);
final filteredCustomerState = ref.watch(
filteredAlreadyCustomerProvider,
);
final customerEmptyState = ref.watch(alreadyCustomerEmptyProvider);
return SafeArea(
top: false,
bottom: true,
right: false,
left: false,
child: Stack(
children: [
Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
elevation: 0,
toolbarHeight: 10.h,
titleSpacing: 0,
automaticallyImplyLeading: false,
title: Padding(
padding: EdgeInsets.symmetric(horizontal: 4.w),
child: Row(
children: [
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,
),
),
),
),
),
SizedBox(width: 3.w),
Expanded(
child: 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: (value) => _onSearchChanged(value),
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(width: 2.w),
IconButton(
onPressed: () => _showAddOptions(context),
icon: Icon(
Icons.add_circle,
color: AppColor.primaryColor,
size: 8.w,
),
),
],
),
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(2),
child: Container(color: Colors.grey.shade100, height: 1),
),
),
body: filteredCustomerState.when(
data: (customers) {
switch (customerEmptyState) {
case AlreadyCustomerEmpty.loading:
return SizedBox();
case AlreadyCustomerEmpty.empty_database:
return CustomEmptyScreen(
title: "Tidak Ada Pelanggan",
body: "Kamu belum memiliki pelanggan",
);
case AlreadyCustomerEmpty.empty_search_result:
return CustomEmptyScreen(
body: "Pelanggan Tidak Ditemukan",
);
case AlreadyCustomerEmpty.has_data:
return ListView.builder(
padding: EdgeInsets.symmetric(
vertical: 2.h,
horizontal: 5.w,
),
itemCount: customers.length,
itemBuilder: (context, index) {
final customer = customers[index];
final bool hasMultiAddress =
customer.addresses.length > 1;
final bool isSingleSelected =
!hasMultiAddress &&
_selectedCustomerId == customer.id;
final bool isExpanded = ref.watch(
expandedAlreadyCustomerProvider(customer.id),
);
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: EdgeInsets.only(bottom: 2.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4.w),
border: Border.all(
color: Colors.grey.shade200,
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Column(
children: [
InkWell(
onTap: () {
if (hasMultiAddress) {
ref
.read(
expandedAlreadyCustomerProvider(
customer.id,
).notifier,
)
.state = !ref.read(
expandedAlreadyCustomerProvider(
customer.id,
),
);
} else {
_selectCustomer(
SelectedCustomer(
id: customer.id,
outletId: ref.read(
currentOutletIdProvider,
)!,
name: customer.name,
phoneNumber: customer.phoneNumber,
address: customer.addresses.first,
),
customer.id,
customer.addresses.first,
);
}
},
borderRadius: BorderRadius.vertical(
top: Radius.circular(4.w),
bottom: (hasMultiAddress && isExpanded)
? Radius.zero
: Radius.circular(4.w),
),
child: Padding(
padding: EdgeInsets.all(3.w),
child: Row(
children: [
Container(
width: 12.w,
height: 12.w,
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
customer.name[0].toUpperCase(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: isTablet
? AppFontSize.medium.sp
: AppFontSize.small.sp,
color: Colors.grey,
),
),
),
SizedBox(width: 4.w),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
customer.name,
style: TextStyle(
fontSize: isTablet
? AppFontSize.medium.sp
: AppFontSize.small.sp,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
if (customer
.phoneNumber
.isNotEmpty) ...[
SizedBox(height: 0.75.h),
Row(
children: [
Icon(
Icons.phone_android,
size: 5.w,
color: Colors.grey,
),
SizedBox(width: 1.w),
Text(
customer.phoneNumber,
style: TextStyle(
fontSize: isTablet
? AppFontSize
.medium
.sp
: AppFontSize
.small
.sp,
color:
Colors.grey[600],
),
),
],
),
],
if (customer
.addresses
.first
.isNotEmpty &&
!hasMultiAddress) ...[
SizedBox(height: 0.75.h),
Row(
children: [
Icon(
Icons
.location_on_outlined,
color: Colors.grey,
size: 5.w,
),
SizedBox(width: 1.w),
Expanded(
child: Text(
customer
.addresses
.first,
overflow: TextOverflow
.ellipsis,
style: TextStyle(
fontSize: isTablet
? AppFontSize
.medium
.sp
: AppFontSize
.small
.sp,
color: Colors
.grey[500],
),
),
),
],
),
],
],
),
),
if (customer
.addresses
.isNotEmpty) ...[
if (hasMultiAddress)
Icon(
isExpanded
? Icons
.keyboard_arrow_up_rounded
: Icons
.keyboard_arrow_down_rounded,
color: Colors.grey,
size: 7.w,
)
else
CheckboxCustomer(
isSelected: isSingleSelected,
),
],
],
),
),
),
if (hasMultiAddress && isExpanded) ...[
Container(
height: 1,
color: Colors.grey[100],
),
Padding(
padding: EdgeInsets.symmetric(
vertical: 1.h,
),
child: Column(
children: customer.addresses.map((
address,
) {
final bool isAddressSelected =
_selectedCustomerId ==
customer.id &&
_selectedCustomerAddress ==
address;
return InkWell(
onTap: () {
_selectCustomer(
SelectedCustomer(
id: customer.id,
outletId: ref.read(
currentOutletIdProvider,
)!,
name: customer.name,
phoneNumber:
customer.phoneNumber,
address: address,
),
customer.id,
address,
);
},
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 4.w,
vertical: 1.5.h,
),
child: Row(
children: [
SizedBox(width: 4.w),
CheckboxCustomer(
isSelected:
isAddressSelected,
),
SizedBox(width: 3.w),
Expanded(
child: Text(
address,
overflow:
TextOverflow.ellipsis,
style: TextStyle(
fontSize: isTablet
? AppFontSize
.medium
.sp
: AppFontSize
.small
.sp,
color: isAddressSelected
? Colors.black
: Colors.grey[700],
fontWeight:
isAddressSelected
? FontWeight.bold
: FontWeight.normal,
),
),
),
],
),
),
);
}).toList(),
),
),
],
],
),
);
},
);
}
},
error: (error, stackTrace) {
return CustomErrorScreen(
message: "Ups, terjadi kesalahan",
onRefresh: () {},
);
},
loading: () => const AlreadyCustomerShimmer(),
),
bottomNavigationBar: Container(
padding: EdgeInsets.all(5.w),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, -5),
),
],
),
child: SizedBox(
height: 6.5.h,
child: ElevatedButton(
onPressed: (_selectedCustomerId != null)
? _submitSelection
: null,
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2.5.w),
),
elevation: 0,
disabledBackgroundColor: Colors.grey.shade300,
),
child: Text(
"Pilih Pelanggan",
style: TextStyle(
color: Colors.white,
fontSize: AppFontSize.medium.sp,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
if (messageActivity != null)
PopupNotification(
isOwner: currentUserRole == UserRole.owner,
messages: messageActivity.messages,
type: messageActivity.type,
),
],
),
);
},
);
}
}