SSO and chatbot

This commit is contained in:
Surendiran 2026-06-11 09:25:55 +05:30
parent a157694897
commit ab44ad5815
7 changed files with 126 additions and 73 deletions

View File

@ -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()

View File

@ -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;

View File

@ -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<ChatbotRepository>((ref) {
@ -98,6 +102,7 @@ class ChatbotUiState {
class ChatbotController extends StateNotifier<ChatbotUiState> {
final ChatbotRepository _repository;
final EcardDownloadService _ecardDownloadService = const EcardDownloadService();
List<ChatbotPolicy>? _policiesCache;
DateTime? _policiesCacheAt;
static const Duration _cacheTtl = Duration(minutes: 10);
@ -201,7 +206,7 @@ class ChatbotController extends StateNotifier<ChatbotUiState> {
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<ChatbotUiState> {
void selectPolicy(ChatbotPolicy policy) {
_appendBotMessage('Selected ${policy.policyName}.');
final isGmcPolicy = _isGmcPolicy(policy);
final actionOptions = <ChatbotOption>[
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<ChatbotUiState> {
}
}
Future<void> openEcard() async {
Future<void> 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<ChatbotUiState> {
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 = <String, String>{
'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<void> openHospitalList() async {

View File

@ -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<PolicyMember> 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

View File

@ -126,10 +126,12 @@ Future<DownloadResult> _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(

View File

@ -1877,64 +1877,63 @@ class _loginState extends State<login> {
),
),
),
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,

View File

@ -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'