QueenFruits/Mobile Commerce/lib/features/home/presentation/screens/outlet_map_screen.dart

439 lines
17 KiB
Dart

import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:latlong2/latlong.dart';
import 'package:niogu_ecommerce_v1/core/constant/app_asset.dart';
import 'package:niogu_ecommerce_v1/core/constant/app_font_size.dart';
import 'package:niogu_ecommerce_v1/core/providers/app_provider.dart';
import 'package:niogu_ecommerce_v1/core/system/system_setting.dart';
import 'package:niogu_ecommerce_v1/core/widgets/custom_snackbar.dart';
import 'package:niogu_ecommerce_v1/core/widgets/triangle_painter.dart';
import 'package:niogu_ecommerce_v1/features/home/domain/entities/home.dart';
import 'package:niogu_ecommerce_v1/features/home/presentation/providers/home_provider.dart';
import 'package:sizer/sizer.dart';
import 'package:niogu_ecommerce_v1/core/constant/app_color.dart';
class OutletMapScreen extends ConsumerStatefulWidget {
final LatLng userLocation;
final List<OtherOutlet> outlets;
const OutletMapScreen({
super.key,
required this.userLocation,
required this.outlets,
});
@override
ConsumerState<OutletMapScreen> createState() => _OutletMapScreenState();
}
class _OutletMapScreenState extends ConsumerState<OutletMapScreen> {
final MapController _mapController = MapController();
late final LatLng _userLocation;
late final List<OtherOutlet> _outlets;
int _selectedOutletIndex = 0;
@override
void initState() {
super.initState();
_userLocation = widget.userLocation;
_outlets = widget.outlets;
WidgetsBinding.instance.addPostFrameCallback((_) {
_fitBounds();
});
}
double _calculateDistance(
double lat1,
double lon1,
double lat2,
double lon2,
) {
final p = 0.017453292519943295;
final c = cos;
final a =
0.5 -
c((lat2 - lat1) * p) / 2 +
c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p)) / 2;
return 12742 * asin(sqrt(a));
}
void _fitBounds() {
_mapController.fitCamera(
CameraFit.bounds(
bounds: LatLngBounds(
_userLocation,
_outlets[_selectedOutletIndex].coordinate!,
),
padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 10.w),
),
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return SafeArea(
top: false,
bottom: true,
right: false,
left: false,
child: Scaffold(
body: Stack(
children: [
FlutterMap(
mapController: _mapController,
options: MapOptions(
initialCenter: _outlets[0].coordinate!,
initialZoom: 16.0,
minZoom: 3.0,
maxZoom: 18.0,
),
children: [
TileLayer(
urlTemplate:
'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',
subdomains: const ['a', 'b', 'c', 'd'],
),
MarkerLayer(
markers: [
..._outlets.asMap().entries.map((entry) {
int idx = entry.key;
var data = entry.value;
final isSelected = _selectedOutletIndex == idx;
final distance = _calculateDistance(
_userLocation.latitude,
_userLocation.longitude,
data.coordinate!.latitude,
data.coordinate!.longitude,
);
var strDistance =
'${distance.toStringAsFixed(1)} Km dari lokasimu';
if (distance < 1) {
strDistance =
'${(distance * 1000).toStringAsFixed(0)} meter dari lokasimu';
}
return Marker(
point: data.coordinate!,
width: 70.w,
height: 25.h,
alignment: Alignment.topCenter,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedOpacity(
duration: const Duration(milliseconds: 300),
opacity: isSelected ? 1.0 : 0.0,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 3.w,
vertical: 0.8.h,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(2.w),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
border: Border.all(
color: isSelected
? AppColor.primaryColor.withOpacity(
0.5,
)
: Colors.grey.shade200,
),
),
child: Text(
strDistance,
style: TextStyle(
fontSize: (AppFontSize.small - 1).sp,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor,
),
),
),
),
if (isSelected)
CustomPaint(
size: Size(3.w, 1.5.w),
painter: TrianglePainter(
Colors.white,
), // Reuse painter
),
Icon(
Icons.location_on,
color: isSelected
? AppColor.primaryColor
: Colors.grey.shade400,
size: 10.w,
),
],
),
);
}).toList(),
],
),
],
),
Positioned(
top: 6.h,
left: 4.w,
child: CircleAvatar(
backgroundColor: Colors.white,
child: IconButton(
icon: Icon(
Icons.arrow_back,
color: Colors.black,
size: 7.w,
),
onPressed: () => Navigator.pop(context),
),
),
),
Positioned(
bottom: 12.h,
left: 0,
right: 0,
child: SizedBox(
height: 20.h,
child: PageView.builder(
controller: PageController(viewportFraction: 0.85),
itemCount: _outlets.length,
onPageChanged: (index) {
setState(() => _selectedOutletIndex = index);
_fitBounds();
},
itemBuilder: (context, index) {
final outlet = _outlets[index];
return CachedNetworkImage(
imageUrl: outlet.image ?? 'error',
imageBuilder: (context, imageProvider) {
return Container(
width: 75.w,
margin: EdgeInsets.only(right: 4.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2.5.w),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2.5.w),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black87,
],
),
),
padding: EdgeInsets.all(4.w),
child: _buildBranchInformation(
name: outlet.name,
location: outlet.location,
),
),
);
},
errorWidget: (context, url, error) {
return Container(
width: 75.w,
margin: EdgeInsets.only(right: 4.w),
decoration: BoxDecoration(
border: BoxBorder.all(
color: _selectedOutletIndex == index
? AppColor.primaryColor
: Colors.transparent,
),
borderRadius: BorderRadius.circular(2.5.w),
image: DecorationImage(
image: AssetImage(AppAsset.OUTLET_MOCK),
fit: BoxFit.cover,
),
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2.5.w),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black87,
],
),
),
padding: EdgeInsets.all(4.w),
child: _buildBranchInformation(
name: outlet.name,
location: outlet.location,
),
),
);
},
);
},
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
padding: EdgeInsets.all(4.w),
color: Colors.white,
child: SafeArea(
top: false,
child: ElevatedButton(
onPressed: () async {
final outlet = _outlets[_selectedOutletIndex];
await SystemSetting.switchOutlet(
outletId: outlet.id,
outletName: outlet.name,
outletPhone: outlet.phoneNumber,
outletLocation: outlet.location,
outletCoordinate: outlet.coordinate,
);
ref.read(currentOutletIdProvider.notifier).state =
outlet.id;
ref.read(currentOutletNameProvider.notifier).state =
outlet.name;
ref.read(currentOutletPhoneProvider.notifier).state =
outlet.phoneNumber;
ref
.read(currentOutletLocationProvider.notifier)
.state =
outlet.location;
ref
.read(currentOutletCoordinateProvider.notifier)
.state = outlet
.coordinate;
await ref
.read(homeControllerProvider.notifier)
.refresh();
CustomSnackbar.showSuccess(
context,
"Berhasil mengunjungi ${outlet.name}",
);
context.pop();
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
minimumSize: Size(double.infinity, 6.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2.5.w),
),
),
child: Text(
"Pilih Outlet Ini",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: AppFontSize.medium.sp,
),
),
),
),
),
),
],
),
),
);
},
);
}
Widget _buildBranchInformation({required String name, String? location}) {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: AppFontSize.small.sp,
),
),
if (location != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
children: [
Icon(Icons.location_on, size: 4.w, color: Colors.white70),
SizedBox(width: 0.75.w),
Expanded(
child: Text(
location,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white70,
fontSize: (AppFontSize.small - 2).sp,
),
),
),
],
),
),
/**
SizedBox(width: 2.5.w),
Row(
children: [
Icon(Icons.straighten, size: 4.w, color: Colors.white70),
SizedBox(width: 0.75.w),
Text(
"4km",
style: TextStyle(
color: Colors.white70,
fontSize: (AppFontSize.small - 2).sp,
),
),
],
),
*/
],
),
],
);
}
}