diff --git a/lib/feature/main/main_screen.dart b/lib/feature/main/main_screen.dart index ec8d258..f460c48 100644 --- a/lib/feature/main/main_screen.dart +++ b/lib/feature/main/main_screen.dart @@ -11,6 +11,7 @@ import 'package:sfm_app/feature/home/home_bloc.dart'; import 'package:sfm_app/product/constant/app/app_constants.dart'; import 'package:sfm_app/product/constant/enums/app_route_enums.dart'; import 'package:sfm_app/product/constant/enums/role_enums.dart'; +import 'package:sfm_app/product/permission/location_permission.dart'; import '../devices/devices_manager_bloc.dart'; import '../devices/devices_manager_screen.dart'; import '../home/home_screen.dart'; @@ -39,7 +40,7 @@ class MainScreen extends StatefulWidget { State createState() => _MainScreenState(); } -class _MainScreenState extends State { +class _MainScreenState extends State with WidgetsBindingObserver { APIServices apiServices = APIServices(); late MainBloc mainBloc; bool isVN = true; @@ -68,16 +69,39 @@ class _MainScreenState extends State { mainBloc.sinkIsVNIcon.add(isVN); mainBloc.sinkThemeMode.add(isLight); log("role: $role"); + LocationPermissionRequest.instance.checkLocationPermission(context); } @override void initState() { super.initState(); mainBloc = BlocProvider.of(context); + WidgetsBinding.instance.addObserver(this); initialCheck(); getBellNotification(); } + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + if (state == AppLifecycleState.inactive) { + log("App Inactive"); + } else if (state == AppLifecycleState.resumed) { + log("App Resumed"); + LocationPermissionRequest.instance.checkLocationPermission(context); + } else if (state == AppLifecycleState.paused) { + log("App paused"); + } else if (state == AppLifecycleState.detached) { + log("App detached"); + } + } + + @override + void dispose() { + super.dispose(); + WidgetsBinding.instance.removeObserver(this); + } + @override Widget build(BuildContext context) { ThemeNotifier themeNotifier = context.watch(); diff --git a/lib/feature/map/map_bloc.dart b/lib/feature/map/map_bloc.dart index 9cd7830..aa4a75f 100644 --- a/lib/feature/map/map_bloc.dart +++ b/lib/feature/map/map_bloc.dart @@ -17,8 +17,8 @@ class MapBloc extends BlocBase { void dispose() {} final mapType = StreamController.broadcast(); - StreamSink get sinkmapType => mapType.sink; - Stream get streammapType => mapType.stream; + StreamSink get sinkMapType => mapType.sink; + Stream get streamMapType => mapType.stream; final mapController = StreamController.broadcast(); StreamSink get sinkmapController => mapController.sink; @@ -71,4 +71,6 @@ class MapBloc extends BlocBase { context, "Không tìm thấy đường", Colors.orange, Colors.white); } } + + } diff --git a/lib/feature/map/map_screen.dart b/lib/feature/map/map_screen.dart index a7615cf..08566d8 100644 --- a/lib/feature/map/map_screen.dart +++ b/lib/feature/map/map_screen.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:developer'; import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; import 'package:google_maps_cluster_manager/google_maps_cluster_manager.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:sfm_app/feature/devices/device_model.dart'; @@ -10,6 +11,7 @@ import 'package:sfm_app/feature/map/map_bloc.dart'; import 'package:sfm_app/feature/map/widget/on_tap_marker_widget.dart'; import 'package:sfm_app/product/base/bloc/base_bloc.dart'; import 'package:sfm_app/product/constant/icon/icon_constants.dart'; +import 'package:sfm_app/product/permission/location_permission.dart'; import 'package:sfm_app/product/services/api_services.dart'; class MapScreen extends StatefulWidget { @@ -19,7 +21,7 @@ class MapScreen extends StatefulWidget { State createState() => _MapScreenState(); } -class _MapScreenState extends State { +class _MapScreenState extends State with WidgetsBindingObserver { late BitmapDescriptor normalIcon; late BitmapDescriptor offlineIcon; late BitmapDescriptor abnormalIcon; @@ -47,7 +49,8 @@ class _MapScreenState extends State { Set markersAll = {}; List markers = []; LatLng myLocation = const LatLng(213761, 123123); - + Position? position; + bool isAllowLocationPermission = false; @override void initState() { super.initState(); @@ -143,17 +146,27 @@ class _MapScreenState extends State { cluster.getId(), ), position: cluster.location, - onTap: () { - onTapMarker( + onTap: () async { + bool check = await checkLocationPermission(context); + if (check == true) { + Position position = await Geolocator.getCurrentPosition(); + // ignore: use_build_context_synchronously + onTapMarker( context, _controller, mapBloc, - myLocation, + LatLng(position.latitude, position.longitude), cluster.items, imageAssets, markers, hospitalIcon, - fireStationIcon); + fireStationIcon, + ); + } else { + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("Cannot get your Location"))); + } }, icon: getMarkerIcon(cluster), ); @@ -234,4 +247,10 @@ class _MapScreenState extends State { } } } + + Future checkLocationPermission(context) async { + bool check = await LocationPermissionRequest.instance + .checkLocationPermission(context); + return check; + } } diff --git a/lib/feature/map/widget/on_tap_marker_widget.dart b/lib/feature/map/widget/on_tap_marker_widget.dart index 2f39c06..ac429dd 100644 --- a/lib/feature/map/widget/on_tap_marker_widget.dart +++ b/lib/feature/map/widget/on_tap_marker_widget.dart @@ -1,7 +1,6 @@ // ignore_for_file: use_build_context_synchronously import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'show_direction_widget.dart'; @@ -26,7 +25,6 @@ onTapMarker( BitmapDescriptor hospitalIcon, BitmapDescriptor fireStationIcon, ) { - LatLng testLocation = const LatLng(20.985453, 105.738381); showModalBottomSheet( context: context, builder: (BuildContext modalBottomSheetContext) { @@ -77,7 +75,7 @@ onTapMarker( mapBloc.findTheWay( context, controller, - testLocation, + myLocation, destination, ); String deviceLocations = await DeviceUtils.instance diff --git a/lib/main.dart b/lib/main.dart index b869359..fd17086 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:sfm_app/product/services/language_services.dart'; +import 'product/services/language_services.dart'; import 'feature/main/main_bloc.dart'; import 'product/base/bloc/base_bloc.dart'; import 'product/constant/navigation/navigation_router.dart'; diff --git a/lib/product/base/widget/dialog/request_permission_dialog.dart b/lib/product/base/widget/dialog/request_permission_dialog.dart index 91e4d0d..0ea3395 100644 --- a/lib/product/base/widget/dialog/request_permission_dialog.dart +++ b/lib/product/base/widget/dialog/request_permission_dialog.dart @@ -1,11 +1,17 @@ import 'package:app_settings/app_settings.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../../../extention/context_extention.dart'; import '../../../services/language_services.dart'; class RequestPermissionDialog { - showRequestPermissionDialog(BuildContext context, IconData icon, - String dialogContent, AppSettingsType type) { + RequestPermissionDialog._init(); + static RequestPermissionDialog? _instance; + static RequestPermissionDialog get instance => + _instance ??= RequestPermissionDialog._init(); + + requestPermissionDialogAndroid(BuildContext context, IconData icon, + String content, AppSettingsType type) { showDialog( useRootNavigator: false, context: context, @@ -20,36 +26,39 @@ class RequestPermissionDialog { mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: context.paddingNormalVertical, + padding: dialogContext.paddingNormalVertical, child: Text( - dialogContent, + "Alow app to use $content permission", textAlign: TextAlign.center, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18), ), ), - Divider(height: context.lowValue), + Divider(height: dialogContext.lowValue), GestureDetector( onTap: () { AppSettings.openAppSettings(type: type); + Navigator.of(dialogContext).pop(); }, child: Padding( - padding: - context.paddingNormalVertical, // Cách giữa các phần tử + padding: dialogContext.paddingNormalVertical, child: Text(appLocalization(context).allow_message, style: const TextStyle(fontWeight: FontWeight.bold)), ), ), - Divider(height: context.lowValue), + Divider(height: dialogContext.lowValue), GestureDetector( onTap: () { - Navigator.of(dialogContext).pop(); // Đóng dialog + Navigator.of(dialogContext).pop(); }, child: Padding( - padding: - context.paddingNormalVertical, // Cách giữa các phần tử - child: Text(appLocalization(context).decline_message, - style: const TextStyle(fontWeight: FontWeight.bold)), + padding: dialogContext.paddingNormalVertical, + child: Text( + appLocalization(dialogContext).decline_message, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), ), ), ], @@ -58,4 +67,31 @@ class RequestPermissionDialog { }, ); } + + requestPermissionDialogIOS(context, AppSettingsType appSettingsType, content, + AppSettingsType type) => + showCupertinoDialog( + context: context, + builder: (showCupertinoDialogContext) => CupertinoAlertDialog( + title: Text(appLocalization(showCupertinoDialogContext) + .permission_deny_message), + content: Text("Alow app to use $content permission"), + actions: [ + CupertinoDialogAction( + child: Text(appLocalization(showCupertinoDialogContext) + .cancel_button_content), + onPressed: () => Navigator.pop(showCupertinoDialogContext), + ), + CupertinoDialogAction( + isDefaultAction: true, + child: Text( + appLocalization(showCupertinoDialogContext).allow_message), + onPressed: () { + Navigator.pop(showCupertinoDialogContext); + AppSettings.openAppSettings(type: appSettingsType); + }, + ), + ], + ), + ); } diff --git a/lib/product/lang/l10n/app_en.arb b/lib/product/lang/l10n/app_en.arb index 508bf46..959e94a 100644 --- a/lib/product/lang/l10n/app_en.arb +++ b/lib/product/lang/l10n/app_en.arb @@ -193,8 +193,9 @@ "warning_message": "Warning: ", "loading_message": "Loading...", "detail_message": "Detail", - "decline_message": "DECLINE", - "allow_message": "ALLOW", + "permission_deny_message": "Permission Denied", + "decline_message": "Decline", + "allow_message": "Allow", "add_button_content": "Add", "update_button_content": "Update", "change_button_content": "Change", diff --git a/lib/product/lang/l10n/app_vi.arb b/lib/product/lang/l10n/app_vi.arb index f395858..cf179c8 100644 --- a/lib/product/lang/l10n/app_vi.arb +++ b/lib/product/lang/l10n/app_vi.arb @@ -193,6 +193,7 @@ "warning_message": "Cảnh báo:", "loading_message": "Đang tải...", "detail_message": "Chi tiết", + "permission_deny_message":"Quyền bị từ chối", "decline_message": "TỪ CHỐI", "allow_message": "CHO PHÉP", "add_button_content": "Thêm", diff --git a/lib/product/permission/location_permission.dart b/lib/product/permission/location_permission.dart new file mode 100644 index 0000000..f5f47e0 --- /dev/null +++ b/lib/product/permission/location_permission.dart @@ -0,0 +1,57 @@ +import 'dart:developer'; +import 'dart:io'; + +import 'package:app_settings/app_settings.dart'; +import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:sfm_app/product/base/widget/dialog/request_permission_dialog.dart'; + +class LocationPermissionRequest { + LocationPermissionRequest._init(); + static LocationPermissionRequest? _instance; + static LocationPermissionRequest get instance => + _instance ??= LocationPermissionRequest._init(); + + Future checkLocationPermission(context) async { + var status = await Permission.location.status; + log("Status: $status"); + if (status.isDenied || status.isPermanentlyDenied) { + requestLocationPermisson(context, Icons.location_on_outlined, + Permission.location, AppSettingsType.location, "Location"); + return false; + } else { + return true; + } + } + + void requestLocationPermisson( + context, + IconData icon, + Permission service, + AppSettingsType appSettingsType, + String serviceName, + ) async { + var status = await service.status; + if (status.isDenied) { + await service.request(); + } else if (status.isPermanentlyDenied) { + if (Platform.isAndroid) { + RequestPermissionDialog.instance.requestPermissionDialogAndroid( + context, + icon, + serviceName, + appSettingsType, + ); + } else if (Platform.isIOS) { + RequestPermissionDialog.instance.requestPermissionDialogIOS( + context, + appSettingsType, + serviceName, + appSettingsType, + ); + } else { + log("Location Permission: Undefined Platform"); + } + } else {} + } +} diff --git a/lib/product/permission/notification_permission.dart b/lib/product/permission/notification_permission.dart index 23d5b75..f7a6dbf 100644 --- a/lib/product/permission/notification_permission.dart +++ b/lib/product/permission/notification_permission.dart @@ -24,7 +24,7 @@ class NotificationPermission { } else { log("NotificationsPermission: User denied permission"); // ignore: use_build_context_synchronously - RequestPermissionDialog().showRequestPermissionDialog(context, + RequestPermissionDialog.instance.requestPermissionDialogAndroid(context, Icons.location_on_outlined, "ABCDE", AppSettingsType.notification); } } diff --git a/lib/product/shared/shared_input_decoration.dart b/lib/product/shared/shared_input_decoration.dart index 0a9eaa7..857332d 100644 --- a/lib/product/shared/shared_input_decoration.dart +++ b/lib/product/shared/shared_input_decoration.dart @@ -5,7 +5,7 @@ InputDecoration borderRadiusTopLeftAndBottomRight( BuildContext context, String hintText) => InputDecoration( hintText: hintText, - border: OutlineInputBorder( + border: const OutlineInputBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(12), bottomRight: Radius.circular(12)), ));