Merge pull request 'auth_view_model' (#205) from auth_view_model into main
Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/205
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tm_app/data/services/model/error/error_model.dart';
|
||||
import 'package:tm_app/data/services/model/login_response/login_response_model.dart';
|
||||
|
||||
import '../config/app_config.dart';
|
||||
@@ -305,16 +306,25 @@ class NetworkManager {
|
||||
return 'Unknown error';
|
||||
}
|
||||
|
||||
try {
|
||||
if (data is Map<String, dynamic>) {
|
||||
return data['message'] ??
|
||||
data['error'] ??
|
||||
data['detail'] ??
|
||||
final Object? nested = data['error'];
|
||||
final Map<String, dynamic> json =
|
||||
nested is Map<String, dynamic> ? nested : data;
|
||||
final ErrorModel model = ErrorModel.fromJson(json);
|
||||
return model.message ??
|
||||
model.details ??
|
||||
model.code ??
|
||||
(data['detail'] as String?) ??
|
||||
'Unknown error';
|
||||
}
|
||||
|
||||
if (data is String) {
|
||||
return data;
|
||||
}
|
||||
} catch (_) {
|
||||
// fall through to default message
|
||||
}
|
||||
|
||||
return 'Unknown error';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:tm_app/core/network/app_exceptions.dart';
|
||||
|
||||
/// Utility class for handling error messages
|
||||
class ErrorUtils {
|
||||
/// Extracts a clean error message from an Exception
|
||||
static String getErrorMessage(Exception error) {
|
||||
if (error is AppException) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
// For plain Exception, remove "Exception: " prefix
|
||||
final errorString = error.toString();
|
||||
if (errorString.startsWith('Exception: ')) {
|
||||
return errorString.substring(11);
|
||||
}
|
||||
|
||||
return errorString;
|
||||
}
|
||||
}
|
||||
+65
-2
@@ -63,11 +63,48 @@ class _MyAppState extends State<MyApp> {
|
||||
_handleMessage(initialMessage);
|
||||
}
|
||||
|
||||
if (kIsWeb) {
|
||||
// if (kIsWeb) {
|
||||
FirebaseMessaging.onMessage.listen((event) {
|
||||
final overlayCtx = rootNavigatorKey.currentContext;
|
||||
AppLogger().error('Notification received: ${event.notification?.web}');
|
||||
AppLogger().error('web link: ${event.notification?.title}');
|
||||
AppLogger().error('web link: ${event.notification?.web?.link}');
|
||||
print('Notification received1: ${event.notification?.web.toString()}');
|
||||
print(
|
||||
'Notification received1: ${event.notification?.web?.analyticsLabel}',
|
||||
);
|
||||
|
||||
print('web link: ${event.notification?.web?.link}');
|
||||
print('Notification received: ${event.notification?.title}');
|
||||
AppLogger().error('notif data: ${event.data}');
|
||||
AppLogger().error('notif from: ${event.from}');
|
||||
AppLogger().error('notif type: ${event.messageType}');
|
||||
AppLogger().error('notif keys: ${event.data.keys}');
|
||||
AppLogger().info('notif title: ${event.notification?.title}');
|
||||
AppLogger().info('notif body: ${event.notification?.body}');
|
||||
AppLogger().info('notif data: ${event.data}');
|
||||
AppLogger().info('notif from: ${event.from}');
|
||||
AppLogger().info('notif type: ${event.messageType}');
|
||||
AppLogger().info('notif keys: ${event.data.keys}');
|
||||
if (overlayCtx != null) {
|
||||
if (overlayCtx.mounted) {
|
||||
AppLogger().error(
|
||||
'Notification received: ${event.notification?.web}',
|
||||
);
|
||||
AppLogger().error(
|
||||
' link android: ${event.notification?.android?.link}',
|
||||
);
|
||||
AppLogger().error(
|
||||
'android link image: ${event.notification?.android?.imageUrl}',
|
||||
);
|
||||
AppLogger().error(
|
||||
'android link click action: ${event.notification?.android?.clickAction}',
|
||||
);
|
||||
AppLogger().error(
|
||||
'web link image: ${event.notification?.web?.image}',
|
||||
);
|
||||
print('Notification received: ${event.notification?.web}');
|
||||
print('web link: ${event.notification?.web?.link}');
|
||||
AppToast.notification(
|
||||
overlayCtx,
|
||||
event.notification?.title ?? 'New Notification',
|
||||
@@ -76,12 +113,38 @@ class _MyAppState extends State<MyApp> {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// }
|
||||
|
||||
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
|
||||
}
|
||||
|
||||
void _handleMessage(RemoteMessage message) {
|
||||
SharedPreferences.getInstance().then((value) {
|
||||
value.setString('notif_data', message.data.toString());
|
||||
value.setString('notif_from', message.from.toString());
|
||||
value.setString('notif_type', message.messageType.toString());
|
||||
value.setString('notif_keys', message.data.keys.toString());
|
||||
value.setString('notif_title', message.notification?.title ?? '');
|
||||
value.setString('notif_body', message.notification?.body ?? '');
|
||||
value.setString('notif_data', message.contentAvailable.toString());
|
||||
value.setString('notif_web_link', message.notification?.web?.link ?? '');
|
||||
value.setString(
|
||||
'notif_android_link',
|
||||
message.notification?.android?.link ?? '',
|
||||
);
|
||||
value.setString(
|
||||
'notif_web_image',
|
||||
message.notification?.web?.image ?? '',
|
||||
);
|
||||
value.setString(
|
||||
'notif_android_image',
|
||||
message.notification?.android?.imageUrl ?? '',
|
||||
);
|
||||
value.setString(
|
||||
'notif_web_click_action',
|
||||
message.notification?.web?.link ?? '',
|
||||
);
|
||||
});
|
||||
appRouter.go(
|
||||
const SplashScreenRoute(navigateToNotification: true).location,
|
||||
);
|
||||
|
||||
@@ -147,7 +147,11 @@ class AuthViewModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> localLogout() async {
|
||||
try {
|
||||
await _authRepository.localLogout();
|
||||
} catch (e) {
|
||||
// Handle local logout exceptions gracefully
|
||||
}
|
||||
_loggedInUser = null;
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -160,7 +164,7 @@ class AuthViewModel with ChangeNotifier {
|
||||
|
||||
switch (result) {
|
||||
case Ok<LogoutResponse>():
|
||||
localLogout();
|
||||
await localLogout();
|
||||
break;
|
||||
case Error<LogoutResponse>():
|
||||
_errorMessage = result.error.toString();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tm_app/data/repositories/auth_repository.dart';
|
||||
import 'package:tm_app/data/services/model/company_profile_data/company_profile_data.dart';
|
||||
import 'package:tm_app/data/services/model/company_profile_response/company_profile_response.dart';
|
||||
@@ -29,12 +30,65 @@ class ProfileViewModel with ChangeNotifier {
|
||||
CompanyProfileData? _companyProfileData;
|
||||
bool _loggedOut = false;
|
||||
|
||||
// Notification info saved from FCM message in SharedPreferences
|
||||
String? _notifData;
|
||||
String? _notifFrom;
|
||||
String? _notifType;
|
||||
String? _notifKeys;
|
||||
String? _notifTitle;
|
||||
String? _notifBody;
|
||||
String? _notifWebLink;
|
||||
String? _notifAndroidLink;
|
||||
String? _notifWebImage;
|
||||
String? _notifAndroidImage;
|
||||
String? _notifWebClickAction;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
bool get loggedOut => _loggedOut;
|
||||
String? get errorMessage => _errorMessage;
|
||||
ProfileData? get profileData => _profileData;
|
||||
CompanyProfileData? get companyProfileData => _companyProfileData;
|
||||
|
||||
// Expose notification info
|
||||
String? get notifData => _notifData;
|
||||
String? get notifFrom => _notifFrom;
|
||||
String? get notifType => _notifType;
|
||||
String? get notifKeys => _notifKeys;
|
||||
String? get notifTitle => _notifTitle;
|
||||
String? get notifBody => _notifBody;
|
||||
String? get notifWebLink => _notifWebLink;
|
||||
String? get notifAndroidLink => _notifAndroidLink;
|
||||
String? get notifWebImage => _notifWebImage;
|
||||
String? get notifAndroidImage => _notifAndroidImage;
|
||||
String? get notifWebClickAction => _notifWebClickAction;
|
||||
|
||||
String get notifSummaryText {
|
||||
final parts = <String>[];
|
||||
if (_notifTitle != null && _notifTitle!.isNotEmpty)
|
||||
parts.add('Title: ${_notifTitle!}');
|
||||
if (_notifBody != null && _notifBody!.isNotEmpty)
|
||||
parts.add('Body: ${_notifBody!}');
|
||||
if (_notifType != null && _notifType!.isNotEmpty)
|
||||
parts.add('Type: ${_notifType!}');
|
||||
if (_notifFrom != null && _notifFrom!.isNotEmpty)
|
||||
parts.add('From: ${_notifFrom!}');
|
||||
if (_notifKeys != null && _notifKeys!.isNotEmpty)
|
||||
parts.add('Keys: ${_notifKeys!}');
|
||||
if (_notifWebLink != null && _notifWebLink!.isNotEmpty)
|
||||
parts.add('Web: ${_notifWebLink!}');
|
||||
if (_notifAndroidLink != null && _notifAndroidLink!.isNotEmpty)
|
||||
parts.add('Android: ${_notifAndroidLink!}');
|
||||
if (_notifWebImage != null && _notifWebImage!.isNotEmpty)
|
||||
parts.add('WebImg: ${_notifWebImage!}');
|
||||
if (_notifAndroidImage != null && _notifAndroidImage!.isNotEmpty)
|
||||
parts.add('AndroidImg: ${_notifAndroidImage!}');
|
||||
if (_notifWebClickAction != null && _notifWebClickAction!.isNotEmpty)
|
||||
parts.add('Click: ${_notifWebClickAction!}');
|
||||
if (_notifData != null && _notifData!.isNotEmpty)
|
||||
parts.add('Data: ${_notifData!}');
|
||||
return parts.isEmpty ? '-' : parts.join(' | ');
|
||||
}
|
||||
|
||||
Future<void> getProfile() async {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
@@ -98,4 +152,28 @@ class ProfileViewModel with ChangeNotifier {
|
||||
_loggedOut = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> loadNotificationInfoFromPrefs() async {
|
||||
// Do not toggle global loading; this is a lightweight local load
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_notifData = prefs.getString('notif_data');
|
||||
_notifFrom = prefs.getString('notif_from');
|
||||
_notifType = prefs.getString('notif_type');
|
||||
_notifKeys = prefs.getString('notif_keys');
|
||||
_notifTitle = prefs.getString('notif_title');
|
||||
_notifBody = prefs.getString('notif_body');
|
||||
_notifWebLink = prefs.getString('notif_web_link');
|
||||
_notifAndroidLink = prefs.getString('notif_android_link');
|
||||
_notifWebImage = prefs.getString('notif_web_image');
|
||||
_notifAndroidImage = prefs.getString('notif_android_image');
|
||||
_notifWebClickAction = prefs.getString('notif_web_click_action');
|
||||
} catch (e) {
|
||||
// Non-fatal; surface as errorMessage so UI can toast if listening
|
||||
_errorMessage = e.toString();
|
||||
}
|
||||
notifyListeners();
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/error_utils.dart';
|
||||
import 'package:tm_app/core/utils/result.dart';
|
||||
import 'package:tm_app/data/repositories/your_tenders_repository.dart';
|
||||
import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart';
|
||||
@@ -109,7 +110,7 @@ class YourTendersViewModel with ChangeNotifier {
|
||||
errorMessage = null;
|
||||
break;
|
||||
case Error<TenderApprovalsResponse>():
|
||||
errorMessage = result.error.toString();
|
||||
errorMessage = ErrorUtils.getErrorMessage(result.error);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -175,7 +176,7 @@ class YourTendersViewModel with ChangeNotifier {
|
||||
errorMessage = null;
|
||||
break;
|
||||
case Error<LikedTendersResponse>():
|
||||
errorMessage = result.error.toString();
|
||||
errorMessage = ErrorUtils.getErrorMessage(result.error);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
|
||||
super.initState();
|
||||
viewModel = context.read<ProfileViewModel>();
|
||||
viewModel.addListener(_profileListener);
|
||||
// Load notification info saved in SharedPreferences
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
viewModel.loadNotificationInfoFromPrefs();
|
||||
});
|
||||
}
|
||||
|
||||
void _profileListener() {
|
||||
@@ -75,6 +79,30 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 36.0.h()),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Text(
|
||||
'Last Notification',
|
||||
style: TextStyle(
|
||||
fontSize: 18.0.sp(),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.0.h()),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Text(
|
||||
viewModel.notifSummaryText,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey60,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24.0.h()),
|
||||
TitleDescription(
|
||||
title: ProfileStrings.companyName,
|
||||
description: viewModel.companyProfileData!.name!,
|
||||
|
||||
Reference in New Issue
Block a user