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:
a.ghabeli
2025-10-06 10:33:38 +03:30
7 changed files with 228 additions and 25 deletions
+13 -3
View File
@@ -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';
}
+19
View File
@@ -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
View File
@@ -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,
);
+5 -1
View File
@@ -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();
+78
View File
@@ -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();
}
}
+3 -2
View File
@@ -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!,