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:dio/dio.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.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 'package:tm_app/data/services/model/login_response/login_response_model.dart';
|
||||||
|
|
||||||
import '../config/app_config.dart';
|
import '../config/app_config.dart';
|
||||||
@@ -305,16 +306,25 @@ class NetworkManager {
|
|||||||
return 'Unknown error';
|
return 'Unknown error';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
if (data is Map<String, dynamic>) {
|
if (data is Map<String, dynamic>) {
|
||||||
return data['message'] ??
|
final Object? nested = data['error'];
|
||||||
data['error'] ??
|
final Map<String, dynamic> json =
|
||||||
data['detail'] ??
|
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';
|
'Unknown error';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data is String) {
|
if (data is String) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// fall through to default message
|
||||||
|
}
|
||||||
|
|
||||||
return 'Unknown error';
|
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);
|
_handleMessage(initialMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kIsWeb) {
|
// if (kIsWeb) {
|
||||||
FirebaseMessaging.onMessage.listen((event) {
|
FirebaseMessaging.onMessage.listen((event) {
|
||||||
final overlayCtx = rootNavigatorKey.currentContext;
|
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 != null) {
|
||||||
if (overlayCtx.mounted) {
|
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(
|
AppToast.notification(
|
||||||
overlayCtx,
|
overlayCtx,
|
||||||
event.notification?.title ?? 'New Notification',
|
event.notification?.title ?? 'New Notification',
|
||||||
@@ -76,12 +113,38 @@ class _MyAppState extends State<MyApp> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
// }
|
||||||
|
|
||||||
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
|
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleMessage(RemoteMessage message) {
|
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(
|
appRouter.go(
|
||||||
const SplashScreenRoute(navigateToNotification: true).location,
|
const SplashScreenRoute(navigateToNotification: true).location,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -147,7 +147,11 @@ class AuthViewModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> localLogout() async {
|
Future<void> localLogout() async {
|
||||||
|
try {
|
||||||
await _authRepository.localLogout();
|
await _authRepository.localLogout();
|
||||||
|
} catch (e) {
|
||||||
|
// Handle local logout exceptions gracefully
|
||||||
|
}
|
||||||
_loggedInUser = null;
|
_loggedInUser = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -160,7 +164,7 @@ class AuthViewModel with ChangeNotifier {
|
|||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case Ok<LogoutResponse>():
|
case Ok<LogoutResponse>():
|
||||||
localLogout();
|
await localLogout();
|
||||||
break;
|
break;
|
||||||
case Error<LogoutResponse>():
|
case Error<LogoutResponse>():
|
||||||
_errorMessage = result.error.toString();
|
_errorMessage = result.error.toString();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
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/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_data/company_profile_data.dart';
|
||||||
import 'package:tm_app/data/services/model/company_profile_response/company_profile_response.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;
|
CompanyProfileData? _companyProfileData;
|
||||||
bool _loggedOut = false;
|
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 isLoading => _isLoading;
|
||||||
bool get loggedOut => _loggedOut;
|
bool get loggedOut => _loggedOut;
|
||||||
String? get errorMessage => _errorMessage;
|
String? get errorMessage => _errorMessage;
|
||||||
ProfileData? get profileData => _profileData;
|
ProfileData? get profileData => _profileData;
|
||||||
CompanyProfileData? get companyProfileData => _companyProfileData;
|
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 {
|
Future<void> getProfile() async {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
@@ -98,4 +152,28 @@ class ProfileViewModel with ChangeNotifier {
|
|||||||
_loggedOut = false;
|
_loggedOut = false;
|
||||||
notifyListeners();
|
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:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:tm_app/core/theme/colors.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/core/utils/result.dart';
|
||||||
import 'package:tm_app/data/repositories/your_tenders_repository.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';
|
import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart';
|
||||||
@@ -109,7 +110,7 @@ class YourTendersViewModel with ChangeNotifier {
|
|||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
break;
|
break;
|
||||||
case Error<TenderApprovalsResponse>():
|
case Error<TenderApprovalsResponse>():
|
||||||
errorMessage = result.error.toString();
|
errorMessage = ErrorUtils.getErrorMessage(result.error);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +176,7 @@ class YourTendersViewModel with ChangeNotifier {
|
|||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
break;
|
break;
|
||||||
case Error<LikedTendersResponse>():
|
case Error<LikedTendersResponse>():
|
||||||
errorMessage = result.error.toString();
|
errorMessage = ErrorUtils.getErrorMessage(result.error);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
viewModel = context.read<ProfileViewModel>();
|
viewModel = context.read<ProfileViewModel>();
|
||||||
viewModel.addListener(_profileListener);
|
viewModel.addListener(_profileListener);
|
||||||
|
// Load notification info saved in SharedPreferences
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
viewModel.loadNotificationInfoFromPrefs();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _profileListener() {
|
void _profileListener() {
|
||||||
@@ -75,6 +79,30 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 36.0.h()),
|
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(
|
TitleDescription(
|
||||||
title: ProfileStrings.companyName,
|
title: ProfileStrings.companyName,
|
||||||
description: viewModel.companyProfileData!.name!,
|
description: viewModel.companyProfileData!.name!,
|
||||||
|
|||||||
Reference in New Issue
Block a user