From ab44ad58153b9aea8802b1144ac8990821c504f7 Mon Sep 17 00:00:00 2001 From: SurendarSuri30 Date: Thu, 11 Jun 2026 09:25:55 +0530 Subject: [PATCH] SSO and chatbot --- android/app/build.gradle | 4 +- lib/config/environment.dart | 2 +- .../application/chatbot_controller.dart | 72 ++++++++++-- .../chatbot/domain/models/chatbot_models.dart | 4 + .../helpers/ecard_download_service_io.dart | 10 +- lib/pages/login.dart | 103 +++++++++--------- pubspec.yaml | 4 +- 7 files changed, 126 insertions(+), 73 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b9d1725..1aed202 100755 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -19,12 +19,12 @@ if (project.hasProperty('google-services.json')) { def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { - flutterVersionCode = '63' + flutterVersionCode = '65' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { - flutterVersionName = '2.0.25' + flutterVersionName = '2.0.27' } def keystoreProperties = new Properties() diff --git a/lib/config/environment.dart b/lib/config/environment.dart index 027fdeb..151210b 100755 --- a/lib/config/environment.dart +++ b/lib/config/environment.dart @@ -5,7 +5,7 @@ class Environment { static Flavor flavor = Flavor.dev; // overwritten by each main_*.dart /// Chatbot FAB + window. Set to `true` when ready to enable. - static const bool chatbotEnabled = true; + static const bool chatbotEnabled = false; static bool get isProd => flavor == Flavor.prod || flavor == Flavor.prod1; diff --git a/lib/features/chatbot/application/chatbot_controller.dart b/lib/features/chatbot/application/chatbot_controller.dart index 43037ed..39e30bc 100644 --- a/lib/features/chatbot/application/chatbot_controller.dart +++ b/lib/features/chatbot/application/chatbot_controller.dart @@ -1,9 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:nhance_app_pwa/features/chatbot/data/chatbot_api_service.dart'; import 'package:nhance_app_pwa/features/chatbot/data/chatbot_repository.dart'; import 'package:nhance_app_pwa/features/chatbot/domain/models/chatbot_models.dart'; +import 'package:nhance_app_pwa/pages/helpers/ecard_download_notification_service.dart'; +import 'package:nhance_app_pwa/pages/helpers/ecard_download_service.dart'; +import 'package:nhance_app_pwa/pages/service/TokenService.dart'; import 'package:url_launcher/url_launcher.dart'; final chatbotRepositoryProvider = Provider((ref) { @@ -98,6 +102,7 @@ class ChatbotUiState { class ChatbotController extends StateNotifier { final ChatbotRepository _repository; + final EcardDownloadService _ecardDownloadService = const EcardDownloadService(); List? _policiesCache; DateTime? _policiesCacheAt; static const Duration _cacheTtl = Duration(minutes: 10); @@ -201,7 +206,7 @@ class ChatbotController extends StateNotifier { showCoveredMembers(); return; case 'ecard_download': - await openEcard(); + await openEcard(context); return; case 'covered_hospital_list': await openHospitalList(); @@ -298,19 +303,31 @@ class ChatbotController extends StateNotifier { void selectPolicy(ChatbotPolicy policy) { _appendBotMessage('Selected ${policy.policyName}.'); + final isGmcPolicy = _isGmcPolicy(policy); + final actionOptions = [ + const ChatbotOption(id: 'covered_member_list', label: 'Covered Member List'), + if (isGmcPolicy) + const ChatbotOption(id: 'ecard_download', label: 'E-Card Download'), + if (isGmcPolicy) + const ChatbotOption( + id: 'covered_hospital_list', + label: 'Covered Hospital List', + ), + const ChatbotOption(id: 'back_to_policies', label: 'Back to Policies'), + ]; state = state.copyWith( selectedPolicy: policy, step: ChatbotFlowStep.policyActions, - options: const [ - ChatbotOption(id: 'covered_member_list', label: 'Covered Member List'), - ChatbotOption(id: 'ecard_download', label: 'E-Card Download'), - ChatbotOption(id: 'covered_hospital_list', label: 'Covered Hospital List'), - ChatbotOption(id: 'back_to_policies', label: 'Back to Policies'), - ], + options: actionOptions, clearError: true, ); } + bool _isGmcPolicy(ChatbotPolicy policy) { + final normalizedType = policy.policyType.trim().toUpperCase(); + return normalizedType.startsWith('GMC'); + } + void showCoveredMembers() { final policy = state.selectedPolicy; if (policy == null) { @@ -328,7 +345,7 @@ class ChatbotController extends StateNotifier { } } - Future openEcard() async { + Future openEcard(BuildContext context) async { final policy = state.selectedPolicy; if (policy == null) { _appendBotMessage('Please choose a policy first.'); @@ -347,12 +364,43 @@ class ChatbotController extends StateNotifier { return; } - final launched = await launchUrl(uri, mode: LaunchMode.externalApplication); - if (!launched) { - _appendBotMessage('Unable to open E-Card link.'); + final fileName = _chatbotEcardFileName(policy); + final postToken = await TokenService.getPostToken(); + final headers = { + 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', + if (postToken != null && postToken.isNotEmpty) + 'Authorization': 'Bearer $postToken', + }; + + if (!kIsWeb) { + _appendBotMessage('Downloading eCard...'); + } + + final result = await _ecardDownloadService.downloadEcard( + url: uri.toString(), + fileName: fileName, + headers: headers, + ); + + if (!result.success) { + _appendBotMessage(result.message ?? 'Unable to download E-Card.'); return; } - _appendBotMessage('Opened E-Card in browser.'); + + if (!kIsWeb && result.savedPath != null && result.savedPath!.isNotEmpty) { + await EcardDownloadNotificationService.showDownloadCompleted( + filePath: result.savedPath!, + fileName: fileName, + ); + } + + _appendBotMessage(result.message ?? 'E-Card downloaded successfully.'); + } + + String _chatbotEcardFileName(ChatbotPolicy policy) { + final sanitized = policy.clientPolicyId.replaceAll(RegExp(r'[^\w\-.]'), '_'); + if (sanitized.isEmpty) return 'ecard.pdf'; + return 'ecard_$sanitized.pdf'; } Future openHospitalList() async { diff --git a/lib/features/chatbot/domain/models/chatbot_models.dart b/lib/features/chatbot/domain/models/chatbot_models.dart index b103a22..cf26e72 100644 --- a/lib/features/chatbot/domain/models/chatbot_models.dart +++ b/lib/features/chatbot/domain/models/chatbot_models.dart @@ -21,6 +21,7 @@ class PolicyMember { class ChatbotPolicy { final String clientPolicyId; final String policyName; + final String policyType; final String eCardDownloadUrl; final String networkHospitalsUrl; final List members; @@ -28,6 +29,7 @@ class ChatbotPolicy { const ChatbotPolicy({ required this.clientPolicyId, required this.policyName, + required this.policyType, required this.eCardDownloadUrl, required this.networkHospitalsUrl, required this.members, @@ -38,6 +40,8 @@ class ChatbotPolicy { return ChatbotPolicy( clientPolicyId: (json['client_policy_id'] ?? '').toString(), policyName: (json['heading'] ?? 'Unnamed Policy').toString(), + policyType: + (json['policy_type'] ?? json['policy_type_long_name'] ?? '').toString(), eCardDownloadUrl: (json['eCardDownload'] ?? '').toString(), networkHospitalsUrl: (json['network_hospitals_url'] ?? '').toString(), members: membersRaw diff --git a/lib/pages/helpers/ecard_download_service_io.dart b/lib/pages/helpers/ecard_download_service_io.dart index f096295..31e469c 100644 --- a/lib/pages/helpers/ecard_download_service_io.dart +++ b/lib/pages/helpers/ecard_download_service_io.dart @@ -126,10 +126,12 @@ Future _downloadAndShareOnIos({ return DownloadResult.failure('Download failed'); } - await Share.shareXFiles( - [XFile(filePath)], - text: 'Save eCard to Files', - subject: safeFileName, + await SharePlus.instance.share( + ShareParams( + files: [XFile(filePath)], + text: 'Save eCard to Files', + subject: safeFileName, + ), ); return DownloadResult.success( diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 48f49db..5bf5857 100755 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -1877,64 +1877,63 @@ class _loginState extends State { ), ), ), - SizedBox(height: 15), - Container( - margin: Responsive.isDesktop(context) - ? const EdgeInsets.symmetric(horizontal: 150) - : const EdgeInsets.symmetric(horizontal: 0), - child: SizedBox( - width: double.infinity, - height: 45, - child: ElevatedButton( - onPressed: _isSsoButtonEnabled && - !_isLoadingSAML - ? _handleSsoLoginButtonTap - : null, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Colors.black, - disabledBackgroundColor: - const Color(0xFFF5F5F5), - disabledForegroundColor: - Colors.black38, - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 5, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18), - side: BorderSide( - color: _isSsoButtonEnabled - ? Colors.black - : Colors.black26, - width: 0.5, + if (_isSsoButtonEnabled) ...[ + SizedBox(height: 15), + Container( + margin: Responsive.isDesktop(context) + ? const EdgeInsets.symmetric(horizontal: 150) + : const EdgeInsets.symmetric(horizontal: 0), + child: SizedBox( + width: double.infinity, + height: 45, + child: ElevatedButton( + onPressed: !_isLoadingSAML + ? _handleSsoLoginButtonTap + : null, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + disabledBackgroundColor: + const Color(0xFFF5F5F5), + disabledForegroundColor: + Colors.black38, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 5, ), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Sign In With Microsoft", - style: GoogleFonts.poppins( - fontWeight: FontWeight.w500, - fontSize: 13, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(18), + side: const BorderSide( + color: Colors.black, + width: 0.5, ), ), - const SizedBox(width: 8), - Image.asset( - 'assets/images/login/microsoft.png', - width: 22, - height: 22, - fit: BoxFit.contain, - ), - ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Sign In With Microsoft", + style: GoogleFonts.poppins( + fontWeight: FontWeight.w500, + fontSize: 13, + ), + ), + const SizedBox(width: 8), + Image.asset( + 'assets/images/login/microsoft.png', + width: 22, + height: 22, + fit: BoxFit.contain, + ), + ], + ), ), ), ), - ), - SizedBox(height: 15), + SizedBox(height: 15), + ], // MouseRegion( // cursor: // SystemMouseCursors.click, diff --git a/pubspec.yaml b/pubspec.yaml index d52a144..ceb574e 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,8 +17,8 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. #version: 1.2.41+98 -version: 1.0.37+43 -#version: 2.0.25+63 +version: 1.0.38+44 +#version: 2.0.27+65 environment: sdk: '>=3.3.3 <4.0.0'