SSO and chatbot
This commit is contained in:
parent
a157694897
commit
ab44ad5815
@ -19,12 +19,12 @@ if (project.hasProperty('google-services.json')) {
|
|||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
if (flutterVersionCode == null) {
|
if (flutterVersionCode == null) {
|
||||||
flutterVersionCode = '63'
|
flutterVersionCode = '65'
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
if (flutterVersionName == null) {
|
if (flutterVersionName == null) {
|
||||||
flutterVersionName = '2.0.25'
|
flutterVersionName = '2.0.27'
|
||||||
}
|
}
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
|
|||||||
@ -5,7 +5,7 @@ class Environment {
|
|||||||
static Flavor flavor = Flavor.dev; // overwritten by each main_*.dart
|
static Flavor flavor = Flavor.dev; // overwritten by each main_*.dart
|
||||||
|
|
||||||
/// Chatbot FAB + window. Set to `true` when ready to enable.
|
/// 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;
|
static bool get isProd => flavor == Flavor.prod || flavor == Flavor.prod1;
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.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_api_service.dart';
|
||||||
import 'package:nhance_app_pwa/features/chatbot/data/chatbot_repository.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/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';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
final chatbotRepositoryProvider = Provider<ChatbotRepository>((ref) {
|
final chatbotRepositoryProvider = Provider<ChatbotRepository>((ref) {
|
||||||
@ -98,6 +102,7 @@ class ChatbotUiState {
|
|||||||
|
|
||||||
class ChatbotController extends StateNotifier<ChatbotUiState> {
|
class ChatbotController extends StateNotifier<ChatbotUiState> {
|
||||||
final ChatbotRepository _repository;
|
final ChatbotRepository _repository;
|
||||||
|
final EcardDownloadService _ecardDownloadService = const EcardDownloadService();
|
||||||
List<ChatbotPolicy>? _policiesCache;
|
List<ChatbotPolicy>? _policiesCache;
|
||||||
DateTime? _policiesCacheAt;
|
DateTime? _policiesCacheAt;
|
||||||
static const Duration _cacheTtl = Duration(minutes: 10);
|
static const Duration _cacheTtl = Duration(minutes: 10);
|
||||||
@ -201,7 +206,7 @@ class ChatbotController extends StateNotifier<ChatbotUiState> {
|
|||||||
showCoveredMembers();
|
showCoveredMembers();
|
||||||
return;
|
return;
|
||||||
case 'ecard_download':
|
case 'ecard_download':
|
||||||
await openEcard();
|
await openEcard(context);
|
||||||
return;
|
return;
|
||||||
case 'covered_hospital_list':
|
case 'covered_hospital_list':
|
||||||
await openHospitalList();
|
await openHospitalList();
|
||||||
@ -298,19 +303,31 @@ class ChatbotController extends StateNotifier<ChatbotUiState> {
|
|||||||
|
|
||||||
void selectPolicy(ChatbotPolicy policy) {
|
void selectPolicy(ChatbotPolicy policy) {
|
||||||
_appendBotMessage('Selected ${policy.policyName}.');
|
_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(
|
state = state.copyWith(
|
||||||
selectedPolicy: policy,
|
selectedPolicy: policy,
|
||||||
step: ChatbotFlowStep.policyActions,
|
step: ChatbotFlowStep.policyActions,
|
||||||
options: const [
|
options: actionOptions,
|
||||||
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'),
|
|
||||||
],
|
|
||||||
clearError: true,
|
clearError: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isGmcPolicy(ChatbotPolicy policy) {
|
||||||
|
final normalizedType = policy.policyType.trim().toUpperCase();
|
||||||
|
return normalizedType.startsWith('GMC');
|
||||||
|
}
|
||||||
|
|
||||||
void showCoveredMembers() {
|
void showCoveredMembers() {
|
||||||
final policy = state.selectedPolicy;
|
final policy = state.selectedPolicy;
|
||||||
if (policy == null) {
|
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;
|
final policy = state.selectedPolicy;
|
||||||
if (policy == null) {
|
if (policy == null) {
|
||||||
_appendBotMessage('Please choose a policy first.');
|
_appendBotMessage('Please choose a policy first.');
|
||||||
@ -347,12 +364,43 @@ class ChatbotController extends StateNotifier<ChatbotUiState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final launched = await launchUrl(uri, mode: LaunchMode.externalApplication);
|
final fileName = _chatbotEcardFileName(policy);
|
||||||
if (!launched) {
|
final postToken = await TokenService.getPostToken();
|
||||||
_appendBotMessage('Unable to open E-Card link.');
|
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;
|
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 {
|
Future<void> openHospitalList() async {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ class PolicyMember {
|
|||||||
class ChatbotPolicy {
|
class ChatbotPolicy {
|
||||||
final String clientPolicyId;
|
final String clientPolicyId;
|
||||||
final String policyName;
|
final String policyName;
|
||||||
|
final String policyType;
|
||||||
final String eCardDownloadUrl;
|
final String eCardDownloadUrl;
|
||||||
final String networkHospitalsUrl;
|
final String networkHospitalsUrl;
|
||||||
final List<PolicyMember> members;
|
final List<PolicyMember> members;
|
||||||
@ -28,6 +29,7 @@ class ChatbotPolicy {
|
|||||||
const ChatbotPolicy({
|
const ChatbotPolicy({
|
||||||
required this.clientPolicyId,
|
required this.clientPolicyId,
|
||||||
required this.policyName,
|
required this.policyName,
|
||||||
|
required this.policyType,
|
||||||
required this.eCardDownloadUrl,
|
required this.eCardDownloadUrl,
|
||||||
required this.networkHospitalsUrl,
|
required this.networkHospitalsUrl,
|
||||||
required this.members,
|
required this.members,
|
||||||
@ -38,6 +40,8 @@ class ChatbotPolicy {
|
|||||||
return ChatbotPolicy(
|
return ChatbotPolicy(
|
||||||
clientPolicyId: (json['client_policy_id'] ?? '').toString(),
|
clientPolicyId: (json['client_policy_id'] ?? '').toString(),
|
||||||
policyName: (json['heading'] ?? 'Unnamed Policy').toString(),
|
policyName: (json['heading'] ?? 'Unnamed Policy').toString(),
|
||||||
|
policyType:
|
||||||
|
(json['policy_type'] ?? json['policy_type_long_name'] ?? '').toString(),
|
||||||
eCardDownloadUrl: (json['eCardDownload'] ?? '').toString(),
|
eCardDownloadUrl: (json['eCardDownload'] ?? '').toString(),
|
||||||
networkHospitalsUrl: (json['network_hospitals_url'] ?? '').toString(),
|
networkHospitalsUrl: (json['network_hospitals_url'] ?? '').toString(),
|
||||||
members: membersRaw
|
members: membersRaw
|
||||||
|
|||||||
@ -126,10 +126,12 @@ Future<DownloadResult> _downloadAndShareOnIos({
|
|||||||
return DownloadResult.failure('Download failed');
|
return DownloadResult.failure('Download failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
await Share.shareXFiles(
|
await SharePlus.instance.share(
|
||||||
[XFile(filePath)],
|
ShareParams(
|
||||||
text: 'Save eCard to Files',
|
files: [XFile(filePath)],
|
||||||
subject: safeFileName,
|
text: 'Save eCard to Files',
|
||||||
|
subject: safeFileName,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return DownloadResult.success(
|
return DownloadResult.success(
|
||||||
|
|||||||
@ -1877,64 +1877,63 @@ class _loginState extends State<login> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 15),
|
if (_isSsoButtonEnabled) ...[
|
||||||
Container(
|
SizedBox(height: 15),
|
||||||
margin: Responsive.isDesktop(context)
|
Container(
|
||||||
? const EdgeInsets.symmetric(horizontal: 150)
|
margin: Responsive.isDesktop(context)
|
||||||
: const EdgeInsets.symmetric(horizontal: 0),
|
? const EdgeInsets.symmetric(horizontal: 150)
|
||||||
child: SizedBox(
|
: const EdgeInsets.symmetric(horizontal: 0),
|
||||||
width: double.infinity,
|
child: SizedBox(
|
||||||
height: 45,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
height: 45,
|
||||||
onPressed: _isSsoButtonEnabled &&
|
child: ElevatedButton(
|
||||||
!_isLoadingSAML
|
onPressed: !_isLoadingSAML
|
||||||
? _handleSsoLoginButtonTap
|
? _handleSsoLoginButtonTap
|
||||||
: null,
|
: null,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
foregroundColor: Colors.black,
|
foregroundColor: Colors.black,
|
||||||
disabledBackgroundColor:
|
disabledBackgroundColor:
|
||||||
const Color(0xFFF5F5F5),
|
const Color(0xFFF5F5F5),
|
||||||
disabledForegroundColor:
|
disabledForegroundColor:
|
||||||
Colors.black38,
|
Colors.black38,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 24,
|
horizontal: 24,
|
||||||
vertical: 5,
|
vertical: 5,
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(18),
|
|
||||||
side: BorderSide(
|
|
||||||
color: _isSsoButtonEnabled
|
|
||||||
? Colors.black
|
|
||||||
: Colors.black26,
|
|
||||||
width: 0.5,
|
|
||||||
),
|
),
|
||||||
),
|
shape: RoundedRectangleBorder(
|
||||||
),
|
borderRadius: BorderRadius.circular(18),
|
||||||
child: Row(
|
side: const BorderSide(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
color: Colors.black,
|
||||||
mainAxisSize: MainAxisSize.min,
|
width: 0.5,
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Sign In With Microsoft",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 13,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
),
|
||||||
Image.asset(
|
child: Row(
|
||||||
'assets/images/login/microsoft.png',
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
width: 22,
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: 22,
|
children: [
|
||||||
fit: BoxFit.contain,
|
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(
|
// MouseRegion(
|
||||||
// cursor:
|
// cursor:
|
||||||
// SystemMouseCursors.click,
|
// SystemMouseCursors.click,
|
||||||
|
|||||||
@ -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
|
# 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.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
#version: 1.2.41+98
|
#version: 1.2.41+98
|
||||||
version: 1.0.37+43
|
version: 1.0.38+44
|
||||||
#version: 2.0.25+63
|
#version: 2.0.27+65
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.3 <4.0.0'
|
sdk: '>=3.3.3 <4.0.0'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user