Resolve merge conflict between main and vypq

This commit is contained in:
anhtunz
2025-04-14 11:41:00 +07:00
64 changed files with 1001 additions and 237 deletions

View File

@@ -1,7 +1,13 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:sfm_app/product/cache/local_manager.dart';
import 'package:sfm_app/product/constant/app/api_path_constant.dart';
import 'package:sfm_app/product/constant/enums/local_keys_enums.dart';
import 'package:sfm_app/product/network/network_manager.dart';
import '../product/base/bloc/base_bloc.dart';
import '../product/services/api_services.dart';
import '../feature/bell/bell_model.dart';
@@ -41,4 +47,32 @@ class MainBloc extends BlocBase {
User user = User.fromJson(jsonDecode(data));
sinkUserProfile.add(user);
}
getFCMTokenAndPresentations() async {
String? firebaseAppToken = await FirebaseMessaging.instance.getToken();
if (firebaseAppToken != null) {
log("FCM TOKEN: $firebaseAppToken");
sendNotificationToken(firebaseAppToken);
} else {
log("FCM TOKEN: null");
}
}
Future<int> sendNotificationToken(String token) async{
String uid = await getUID();
Map<String,dynamic> body = {
"user_id": uid,
"app_token": token
};
int statusCode = await NetworkManager.instance!.updateDataInServer(
APIPathConstants.NOTIFICATION_TOKEN_PATH, body);
return statusCode;
}
Future<String> getUID() async {
String uid = LocaleManager.instance.getStringValue(PreferencesKeys.UID);
return uid;
}
}

View File

@@ -6,9 +6,11 @@ import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:go_router/go_router.dart';
import 'package:badges/badges.dart' as badges;
import 'package:persistent_bottom_nav_bar/persistent_bottom_nav_bar.dart';
import 'package:sfm_app/product/utils/permission_handler.dart';
import '../../product/permission/notification_permission.dart';
import '../../product/services/notification_services.dart';
import '../settings/profile/profile_model.dart';
@@ -46,20 +48,20 @@ class MainScreen extends StatefulWidget {
PersistentTabController controller = PersistentTabController(initialIndex: 0);
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
log("Full background message payload: ${message.toMap()}");
await Firebase.initializeApp();
final notificationServices = NotificationServices();
await notificationServices.initLocalNotifications(controller);
await notificationServices.showNotification(message);
log("Background message handled: ${message.data['title']}");
}
// @pragma('vm:entry-point')
// Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// log("Full background message payload: ${message.toMap()}");
// await Firebase.initializeApp();
// final notificationServices = NotificationServices();
// await notificationServices.initLocalNotifications(controller);
// await notificationServices.showNotification(message);
// log("Background message handled: ${message.data['title']}");
// }
class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
APIServices apiServices = APIServices();
final NotificationServices notificationServices = NotificationServices();
// final NotificationServices notificationServices = NotificationServices();
late MainBloc mainBloc;
bool isVN = true;
bool isLight = true;
@@ -86,7 +88,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
}
mainBloc.sinkIsVNIcon.add(isVN);
mainBloc.sinkThemeMode.add(isLight);
LocationPermissionRequest.instance.checkLocationPermission(context);
checkAndRequestPermission();
NotificationPermission.instance.checkNotificationPermission(context);
}
@@ -94,13 +96,38 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
void initState() {
super.initState();
mainBloc = BlocProvider.of(context);
mainBloc.getFCMTokenAndPresentations();
WidgetsBinding.instance.addObserver(this);
initialCheck();
getBellNotification();
mainBloc.getUserProfile();
notificationServices.initLocalNotifications(controller);
notificationServices.firebaseInit(context);
NotificationServices().setupInteractMessage(controller);
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
log("New FCM Token: $newToken");
// Gửi token mới lên server
mainBloc.sendNotificationToken(newToken);
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null ) {
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'your channel id', 'your channel name',
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker');
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
flutterLocalNotificationsPlugin.show(
notification.hashCode, notification.title, notification.body, platformChannelSpecifics,
);
}
});
// notificationServices.initLocalNotifications(controller);
// notificationServices.firebaseInit(context);
// NotificationServices().setupInteractMessage(controller);
}
@override
@@ -110,7 +137,6 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
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) {
@@ -220,7 +246,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
SizedBox(
width: context.lowValue,
),
Text(userSnapshot.data?.name ?? "")
Flexible( child: Text(userSnapshot.data?.name ?? ""))
],
);
}),

View File

@@ -15,6 +15,7 @@ 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';
import 'package:sfm_app/product/utils/permission_handler.dart';
import '../../product/constant/enums/app_theme_enums.dart';
class MapScreen extends StatefulWidget {
@@ -181,8 +182,8 @@ class _MapScreenState extends State<MapScreen> with WidgetsBindingObserver {
),
position: cluster.location,
onTap: () async {
bool check = await checkLocationPermission(context);
if (check == true) {
LocationPermission permission = await checkAndRequestPermission();
if (permission == LocationPermission.whileInUse || permission == LocationPermission.always) {
Position position = await Geolocator.getCurrentPosition();
onTapMarker(
// ignore: use_build_context_synchronously
@@ -284,9 +285,9 @@ class _MapScreenState extends State<MapScreen> with WidgetsBindingObserver {
}
}
Future<bool> checkLocationPermission(context) async {
bool check = await LocationPermissionRequest.instance
.checkLocationPermission(context);
return check;
}
// Future<bool> checkLocationPermission(context) async {
// bool check = await LocationPermissionRequest.instance
// .checkLocationPermission(context);
// return check;
// }
}

View File

@@ -52,6 +52,7 @@ showNearPlacesSideSheet(
padding: context.paddingLow,
width: screenWidth,
height: screenHeight / 3,
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [

View File

@@ -2,9 +2,11 @@ import 'dart:developer';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'feature/main/main_screen.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:sfm_app/firebase_options.dart';
import 'product/services/api_services.dart';
import 'product/services/notification_services.dart';
import 'product/services/theme_services.dart';
@@ -13,12 +15,89 @@ import 'bloc/main_bloc.dart';
import 'product/base/bloc/base_bloc.dart';
import 'product/constant/navigation/navigation_router.dart';
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await setupFlutterNotifications();
showFlutterNotification(message);
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
print('Handling a background message ${message.messageId}');
}
/// Create a [AndroidNotificationChannel] for heads up notifications
late AndroidNotificationChannel channel;
bool isFlutterLocalNotificationsInitialized = false;
Future<void> setupFlutterNotifications() async {
if (isFlutterLocalNotificationsInitialized) {
return;
}
channel = const AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
description:
'This channel is used for important notifications.', // description
importance: Importance.high,
);
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
/// Create an Android Notification Channel.
///
/// We use this channel in the `AndroidManifest.xml` file to override the
/// default FCM channel to enable heads up notifications.
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
/// Update the iOS foreground notification presentation options to allow
/// heads up notifications.
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
isFlutterLocalNotificationsInitialized = true;
}
void showFlutterNotification(RemoteMessage message) {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null && !kIsWeb) {
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channelDescription: channel.description,
// TODO add a proper drawable resource to android, for now using
// one that already exists in example app.
icon: 'launch_background',
),
),
);
}
}
/// Initialize the [FlutterLocalNotificationsPlugin] package.
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging
.onBackgroundMessage(firebaseMessagingBackgroundHandler);
// NotificationServices().setupInteractMessage();
// Set the background messaging handler early on, as a named top-level function
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
if (!kIsWeb) {
await setupFlutterNotifications();
}
runApp(
BlocProvider(
@@ -28,15 +107,7 @@ void main() async {
);
}
// @pragma('vm:entry-point')
// Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// log("Full background message payload: ${message.toMap()}");
// await Firebase.initializeApp();
// final notificationServices = NotificationServices();
// await notificationServices.initLocalNotifications();
// await notificationServices.showNotification(message);
// log("Background message handled: ${message.data['title']}");
// }
class MyApp extends StatefulWidget {
const MyApp({super.key});
@@ -59,7 +130,7 @@ class _MyAppState extends State<MyApp> {
late MainBloc mainBloc;
LanguageServices languageServices = LanguageServices();
ThemeServices themeServices = ThemeServices();
final NotificationServices notificationServices = NotificationServices();
// final NotificationServices notificationServices = NotificationServices();
APIServices apiServices = APIServices();
setLocale(Locale locale) {
_locale = locale;
@@ -70,23 +141,24 @@ class _MyAppState extends State<MyApp> {
_themeData = theme;
mainBloc.sinkTheme.add(_themeData);
}
@override
void initState() {
super.initState();
mainBloc = BlocProvider.of(context);
// notificationServices.initLocalNotifications();
// notificationServices.firebaseInit(context);
// notificationServices.setupInteractMessage();
notificationServices.getDeviceToken().then((token){
print("Firebase Token: $token");
sendNotificationToken(token);
});
// // notificationServices.initLocalNotifications();
// // notificationServices.firebaseInit(context);
// // notificationServices.setupInteractMessage();
// notificationServices.getDeviceToken().then((token){
// print("Firebase Token: $token");
// sendNotificationToken(token);
// });
}
void sendNotificationToken (String token) async {
int statusCode = await apiServices.sendNotificationToken(token);
log("Notification Send StatusCode : $statusCode");
}
// void sendNotificationToken (String token) async {
// int statusCode = await apiServices.sendNotificationToken(token);
// log("Notification Send StatusCode : $statusCode");
// }
@override
void didChangeDependencies() {

View File

@@ -87,8 +87,8 @@ class RequestPermissionDialog {
child: Text(
appLocalization(showCupertinoDialogContext).allow_message),
onPressed: () {
Navigator.pop(showCupertinoDialogContext);
AppSettings.openAppSettings(type: appSettingsType);
Navigator.pop(showCupertinoDialogContext);
},
),
],

View File

@@ -12,17 +12,20 @@ class LocationPermissionRequest {
static LocationPermissionRequest get instance =>
_instance ??= LocationPermissionRequest._init();
Future<bool> 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;
}
}
// Future<bool> checkLocationPermission(context) async {
// var status = await Permission.location.status;
// log("Status1: $status");
// log("Status2: ${status.isDenied}");
// log("Status3: ${status.isPermanentlyDenied}");
// log("Status4: ${status.isGranted}");
// if (status.isDenied || status.isPermanentlyDenied) {
// requestLocationPermisson(context, Icons.location_on_outlined,
// Permission.location, AppSettingsType.location, "Location");
// return false;
// } else {
// return true;
// }
// }
void requestLocationPermisson(
context,

View File

@@ -43,21 +43,22 @@ class MapServices {
List<LatLng> polylineCoordinates = [];
PolylinePoints polylinePoints = PolylinePoints();
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
ApplicationConstants.MAP_KEY,
PointLatLng(origin.latitude, origin.longitude),
PointLatLng(destination.latitude, destination.longitude),
travelMode: TravelMode.driving,
optimizeWaypoints: true);
if (result.points.isNotEmpty) {
for (var point in result.points) {
polylineCoordinates.add(LatLng(point.latitude, point.longitude));
}
return polylineCoordinates;
} else {
log("Lỗi khi tìm đường");
return [];
}
// PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
// ApplicationConstants.MAP_KEY,
// PointLatLng(origin.latitude, origin.longitude),
// PointLatLng(destination.latitude, destination.longitude),
// travelMode: TravelMode.driving,
// optimizeWaypoints: true);
// if (result.points.isNotEmpty) {
// for (var point in result.points) {
// polylineCoordinates.add(LatLng(point.latitude, point.longitude));
// }
// return polylineCoordinates;
// } else {
// log("Lỗi khi tìm đường");
// return [];
// }
return [];
}
}

View File

@@ -39,7 +39,9 @@ class NotificationServices {
}
Future<String> getDeviceToken() async {
String? token = await messaging.getToken();
print("GET FB TOKEN");
String? token = await messaging.getAPNSToken();
print("GET FB: ${token}");
return token!;
}
@@ -50,6 +52,9 @@ class NotificationServices {
}
Future<void> showNotification(RemoteMessage message) async {
dev.log(message.toString());
dev.log(message.data.toString());
dev.log(message.data["notification"].toString());
String? title = message.data['title'];
String? body = message.data['body'];
String type = message.data['type'] ?? "normal";

View File

@@ -0,0 +1,62 @@
import 'package:geolocator/geolocator.dart';
/// Kiểm tra xem dịch vụ vị trí có được bật không
Future<bool> isLocationServiceEnabled() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
print('Vui lòng bật dịch vụ vị trí');
return false;
}
return true;
}
/// Kiểm tra và yêu cầu quyền truy cập vị trí
Future<LocationPermission> checkAndRequestPermission() async {
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
print('Quyền truy cập vị trí bị từ chối');
return permission;
}
}
if (permission == LocationPermission.deniedForever) {
print('Quyền truy cập vị trí bị từ chối vĩnh viễn. Vui lòng cấp quyền trong cài đặt.');
return permission;
}
return permission;
}
/// Lấy vị trí hiện tại của người dùng
Future<Position?> getCurrentPosition() async {
try {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
print('Vị trí hiện tại: ${position.latitude}, ${position.longitude}');
return position;
} catch (e) {
print('Lỗi khi lấy vị trí: $e');
return null;
}
}
/// Hàm chính để xử lý toàn bộ quy trình yêu cầu vị trí
Future<void> requestLocationPermission() async {
// Bước 1: Kiểm tra dịch vụ vị trí
bool isServiceEnabled = await isLocationServiceEnabled();
if (!isServiceEnabled) {
return;
}
// Bước 2: Kiểm tra và yêu cầu quyền
LocationPermission permission = await checkAndRequestPermission();
// Bước 3: Nếu quyền được cấp, lấy vị trí
if (permission == LocationPermission.whileInUse || permission == LocationPermission.always) {
await getCurrentPosition();
}
}