QueenFruits/Mobile Commerce/lib/features/account/presentation/screens/map_address_screen.dart

384 lines
12 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:go_router/go_router.dart';
import 'package:latlong2/latlong.dart';
import 'package:http/http.dart' as http;
import 'package:niogu_ecommerce_v1/core/constant/app_color.dart';
import 'package:niogu_ecommerce_v1/core/constant/app_font_size.dart';
import 'package:niogu_ecommerce_v1/core/router/app_route.dart';
import 'package:niogu_ecommerce_v1/core/utils/log_message.dart';
import 'package:niogu_ecommerce_v1/core/widgets/triangle_painter.dart';
import 'package:niogu_ecommerce_v1/features/account/domain/entities/account.dart';
import 'package:niogu_ecommerce_v1/features/account/presentation/providers/account_provider.dart';
import 'package:sizer/sizer.dart';
class MapAddressScreen extends ConsumerStatefulWidget {
const MapAddressScreen({super.key});
@override
ConsumerState<MapAddressScreen> createState() => _MapAddressScreenState();
}
class _MapAddressScreenState extends ConsumerState<MapAddressScreen> {
final MapController _mapController = MapController();
LatLng _selectedLocation = const LatLng(-6.2000, 106.8166);
String _fullAddress = "";
bool _isSearching = false;
bool _isLoadingMap = false;
List<dynamic> _suggestions = [];
Timer? _debounce;
@override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_fetchSelectedAddress();
});
}
void _fetchSelectedAddress() async {
final selectedAddress = ref.read(selectedAddressProvider);
if (selectedAddress != null) {
await _updateLocation(
LatLng(selectedAddress.latitude, selectedAddress.longitude),
);
}
}
void _onSearchChanged(String query) {
if (_debounce?.isActive ?? false) _debounce?.cancel();
if (query.isEmpty) {
setState(() => _suggestions = []);
return;
}
_debounce = Timer(const Duration(milliseconds: 800), () async {
setState(() => _isSearching = true);
try {
final url = Uri.parse(
'https://nominatim.openstreetmap.org/search?q=$query&format=json&limit=5&countrycodes=id',
);
final response = await http.get(
url,
headers: {
'User-Agent': 'NioguEcommerceApp/1.0 (niaganusantara@gmail.com)',
'Accept-Language': 'id',
},
);
if (response.statusCode == 200) {
setState(() {
_suggestions = json.decode(response.body);
_isSearching = false;
});
}
} catch (e, st) {
LogMessage.log.e(e.toString(), error: e, stackTrace: st);
setState(() => _isSearching = false);
}
});
}
Future<void> _getCurrentPosition() async {
setState(() => _isLoadingMap = true);
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
Position position = await Geolocator.getCurrentPosition();
final newLatLng = LatLng(position.latitude, position.longitude);
await _updateLocation(newLatLng);
}
Future<void> _updateLocation(LatLng point) async {
setState(() {
_selectedLocation = point;
_isLoadingMap = true;
});
_mapController.move(point, 16.0);
try {
List<Placemark> placemarks = await placemarkFromCoordinates(
point.latitude,
point.longitude,
);
if (placemarks.isNotEmpty) {
final place = placemarks[0];
setState(() {
_fullAddress =
"${place.street}, ${place.subLocality}, ${place.locality}, ${place.subAdministrativeArea}";
_isLoadingMap = false;
_suggestions = [];
});
}
} catch (e) {
setState(() => _isLoadingMap = false);
}
}
void _selectedAddress() {
ref.read(selectedAddressProvider.notifier).state = SelectedAddress(
fullAddress: _fullAddress,
latitude: _selectedLocation.latitude,
longitude: _selectedLocation.longitude,
);
}
@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: _selectedLocation,
initialZoom: 16.0,
onTap: (_, point) => _updateLocation(point),
),
children: [
TileLayer(
urlTemplate:
'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',
subdomains: const ['a', 'b', 'c', 'd'],
),
MarkerLayer(
markers: [
Marker(
point: _selectedLocation,
width: 80.w,
height: 15.h,
alignment: Alignment.topCenter,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_fullAddress.isNotEmpty) ...[
_buildAddressBubble(),
CustomPaint(
size: Size(5.w, 2.5.w),
painter: TrianglePainter(Colors.white),
),
Icon(
Icons.location_on,
color: AppColor.primaryColor,
size: 10.w,
),
],
],
),
),
],
),
],
),
Positioned(
top: 6.h,
left: 5.w,
right: 5.w,
child: Column(
children: [
_buildSearchBar(),
if (_suggestions.isNotEmpty || _isSearching)
_buildSuggestionList(),
],
),
),
Positioned(
bottom: 4.h,
left: 5.w,
right: 5.w,
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: FloatingActionButton(
mini: true,
backgroundColor: Colors.white,
onPressed: _getCurrentPosition,
child: Icon(
Icons.my_location,
color: AppColor.primaryColor,
),
),
),
SizedBox(height: 2.h),
ElevatedButton(
onPressed: _fullAddress.isEmpty
? null
: () {
_selectedAddress();
context.pushReplacementNamed(
AppRoute.saveAddressScreen,
);
},
style: ElevatedButton.styleFrom(
minimumSize: Size(double.infinity, 6.h),
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2.5.w),
),
),
child: Text(
"Pilih Lokasi Ini",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: AppFontSize.medium.sp,
),
),
),
],
),
),
if (_isLoadingMap)
Center(
child: CircularProgressIndicator(
color: AppColor.primaryColor,
),
),
],
),
),
);
},
);
}
Widget _buildSearchBar() {
return Row(
children: [
GestureDetector(
onTap: () => context.pop(),
child: Padding(
padding: EdgeInsets.all(2.w),
child: CircleAvatar(
maxRadius: 5.w,
minRadius: 5.w,
backgroundColor: Colors.white.withOpacity(0.9),
child: Center(
child: Icon(Icons.arrow_back, color: Colors.black, size: 6.w),
),
),
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(2.5.w),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: TextField(
onChanged: _onSearchChanged,
style: TextStyle(fontSize: AppFontSize.small.sp),
decoration: InputDecoration(
hintText: "Cari Lokasi...",
hintStyle: TextStyle(fontSize: AppFontSize.small.sp),
prefixIcon: Icon(Icons.search, size: 5.w),
suffixIcon: _isSearching
? Transform.scale(
scale: 0.5,
child: CircularProgressIndicator(
color: AppColor.primaryColor,
),
)
: null,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 2.h),
),
),
),
),
],
);
}
Widget _buildSuggestionList() {
return Container(
margin: EdgeInsets.only(top: 1.h),
constraints: BoxConstraints(maxHeight: 30.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(2.5.w),
),
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: _suggestions.length,
itemBuilder: (context, index) {
final item = _suggestions[index];
return ListTile(
leading: Icon(Icons.location_on_outlined, size: 5.w),
title: Text(
item['display_name'],
style: TextStyle(fontSize: (AppFontSize.small - 1.25).sp),
),
onTap: () {
final lat = double.parse(item['lat']);
final lon = double.parse(item['lon']);
_updateLocation(LatLng(lat, lon));
},
);
},
),
);
}
Widget _buildAddressBubble() {
return Container(
padding: EdgeInsets.all(2.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(2.w),
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)],
),
child: Text(
_fullAddress,
maxLines: 2,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: (AppFontSize.small - 1.25).sp,
fontWeight: FontWeight.w500,
),
),
);
}
}