From b212be19c6ee6d8fb5ec75fb7e90cda90fda3263 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 9 Aug 2026 14:47:01 +0300 Subject: [PATCH] structure improvements. --- lib/controllers/api_routes/urls.dart | 11 +- lib/modules/cx_module/chat/chat_page.dart | 177 ++++++++++++------ lib/modules/cx_module/chat/chat_provider.dart | 15 ++ lib/views/widgets/dialogs/dialog.dart | 85 +++++---- 4 files changed, 184 insertions(+), 104 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 14d81867..ac159911 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -4,17 +4,18 @@ class URLs { static const String appReleaseBuildNumber = "44"; // static const host1 = "https://atomsm.hmg.com"; // production url - // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url - static const host1 = "https://atomsmuat.hmg.com"; // local UAT url + static const host1 = "https://atomsmdev.hmg.com"; // local DEV url + // static const host1 = "https://atomsmuat.hmg.com"; // local UAT url - // static final String _baseUrl = "$_host/mobile"; // host local UAT - static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis + static final String _baseUrl = "$_host/mobile"; // host local UAT + // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module - static const String chatHubUrl = "https://apiderichat.hmg.com/chathub"; + // static const String chatHubUrl = "https://apiderichat.hmg.com/chathub"; + static const String chatHubUrl = "$host1/api/DeriChat"; static const String chatHubUrlApi = "$chatHubUrl/api"; // new V2 apis static const String chatHubUrlChat = "$chatHubUrl/hubs/chat"; // new V2 apis diff --git a/lib/modules/cx_module/chat/chat_page.dart b/lib/modules/cx_module/chat/chat_page.dart index 047b8ff1..6d69ffa8 100644 --- a/lib/modules/cx_module/chat/chat_page.dart +++ b/lib/modules/cx_module/chat/chat_page.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; - +import 'package:permission_handler/permission_handler.dart' as PermissionHandler; import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; @@ -26,6 +26,7 @@ import 'package:test_sa/modules/cx_module/chat/view_all_attachment_page.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/dialogs/dialog.dart'; import 'package:test_sa/views/widgets/sound/sound_player.dart'; import 'helper/chat_audio_player.dart'; @@ -206,66 +207,126 @@ class _ChatPageState extends State { appBar: DefaultAppBar( title: widget.title, actions: [ - // NEW: Call readiness indicator - shows if device can receive calls - if (kDebugMode) - Selector( - selector: (_, provider) => provider.areCallHandlersRegistered, - builder: (context, handlersRegistered, _) => Padding( - padding: const EdgeInsets.only(right: 8), - child: Center( - child: Container( - width: 8, - height: 8, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: handlersRegistered ? Colors.green : Colors.red, - ), + Selector( + selector: (_, provider) => _ChatConnectionState( + isLoading: provider.chatLoginTokenLoading, + loginResponse: provider.chatLoginResponse, ), - ), - ), - ), - Selector( - selector: (_, provider) => provider.recipient, - builder: (context, recipient, _) => IconButton( - icon: "calling_icon".toSvgAsset(width: 24, height: 24), - onPressed: () { - log('recipient: ${recipient?.userName}'); - if (recipient != null) { - chatProvider.startCall(recipient, CallType.audio); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AudioCallPage(), - ), - ); - } - }, - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - ), - ), - Selector( - selector: (_, provider) => provider.recipient, - builder: (context, recipient, _) => IconButton( - icon: "video_call_icon".toSvgAsset( - width: 24, - height: 24, - ), - onPressed: () { - if (recipient != null) { - chatProvider.startCall(recipient, CallType.video); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const VideoCallPage(), - ), + builder: (context, connectionState, child) { + if (!connectionState.isLoading && connectionState.loginResponse == null) { + return SizedBox(); + } else { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (kDebugMode) + Selector( + selector: (_, provider) => provider.areCallHandlersRegistered, + builder: (context, handlersRegistered, _) => Padding( + padding: const EdgeInsets.only(right: 8), + child: Center( + child: Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: handlersRegistered ? Colors.green : Colors.red, + ), + ), + ), + ), + ), + Selector( + selector: (_, provider) => provider.recipient, + builder: (context, recipient, _) => IconButton( + icon: "calling_icon".toSvgAsset(width: 24, height: 24), + onPressed: () async{ + bool audioPermission = await Provider.of(context, listen: false).checkAudioPermissions(); + if (!audioPermission) { + context.showConfirmDialog("To perform audio call, we need permission to access microphone.", title: "Permission Required", onTap: () async { + Navigator.pop(context); + audioPermission = await Provider.of(context, listen: false).requestAudioPermissions(); + if (!audioPermission) { + bool openSetting = await showDialog( + context: context, + builder: (_) => const AAlertDialog( + title: "Permission denied", + content: "Permission is denied. Please go to application Setting to enable it.", + button1Text: "Cancel", + button2Text: "Open Setting", + ), + ); + if (!openSetting) PermissionHandler.openAppSettings(); + return; + } else { + if (recipient != null) { + chatProvider.startCall(recipient, CallType.audio); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AudioCallPage(), + ), + ); + } + } + }); + } else { + if (recipient != null) { + chatProvider.startCall(recipient, CallType.audio); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AudioCallPage(), + ), + ); + } + } + }, + + // { + // log('recipient: ${recipient?.userName}'); + // + // + // if (recipient != null) { + // chatProvider.startCall(recipient, CallType.audio); + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => const AudioCallPage(), + // ), + // ); + // } + // }, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + ), + Selector( + selector: (_, provider) => provider.recipient, + builder: (context, recipient, _) => IconButton( + icon: "video_call_icon".toSvgAsset( + width: 24, + height: 24, + ), + onPressed: () { + if (recipient != null) { + chatProvider.startCall(recipient, CallType.video); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const VideoCallPage(), + ), + ); + } + }, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + ), + ], ); } - }, - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - ), - ), + }), ], ), // OPTIMIZATION: Using Selector for connection state (rarely changes) diff --git a/lib/modules/cx_module/chat/chat_provider.dart b/lib/modules/cx_module/chat/chat_provider.dart index a7535937..15b481a5 100644 --- a/lib/modules/cx_module/chat/chat_provider.dart +++ b/lib/modules/cx_module/chat/chat_provider.dart @@ -199,6 +199,21 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { // } // } + + Future checkAudioPermissions() => Permission.microphone.isGranted; + + Future checkVideoPermissions() async => (await Permission.microphone.isGranted) && (await Permission.camera.isGranted); + + Future requestAudioPermissions() async { + var result = await [Permission.microphone].request(); + return (result[Permission.microphone] == PermissionStatus.granted); + } + + Future requestVideoPermissions() async { + var result = await [Permission.microphone, Permission.camera].request(); + return (result[Permission.microphone] == PermissionStatus.granted) && (result[Permission.camera] == PermissionStatus.granted); + } + Future getUserAutoLoginTokenSilent(int moduleId, int requestId, String title, String myId, String assigneeEmployeeNumber, {bool isMounted = true}) async { // OPTIMIZATION: Use async reset for proper cleanup await reset(); diff --git a/lib/views/widgets/dialogs/dialog.dart b/lib/views/widgets/dialogs/dialog.dart index 0cdfa87c..ecb8ab10 100644 --- a/lib/views/widgets/dialogs/dialog.dart +++ b/lib/views/widgets/dialogs/dialog.dart @@ -9,53 +9,56 @@ import 'package:test_sa/new_views/app_style/app_color.dart'; class AAlertDialog extends StatelessWidget { final String? title; final String? content; + final String? button1Text; + final String? button2Text; - const AAlertDialog({Key? key, this.title, this.content}) : super(key: key); + const AAlertDialog({Key? key, this.title, this.content, this.button1Text, this.button2Text}) : super(key: key); @override Widget build(BuildContext context) { return Platform.isIOS ? CupertinoAlertDialog( - title: title != null ? Text(title!) : null, - content: content != null ? Text(content!) : null, - actions: [ - TextButton( - // child: Text(_subtitle.confirm), - child: Text(context.translation.confirm), - onPressed: () { - Navigator.of(context).pop(true); - }, - ), - TextButton( - // child: Text(_subtitle.cancel), - child: Text(context.translation.cancel), - onPressed: () { - Navigator.of(context).pop(false); - }, - ), - ], - ) + title: title != null ? Text(title!) : null, + content: content != null ? Text(content!) : null, + actions: [ + TextButton( + // child: Text(_subtitle.confirm), + child: Text(button1Text ?? context.translation.confirm), + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + TextButton( + // child: Text(_subtitle.cancel), + child: Text(button2Text ?? context.translation.cancel), + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + ], + ) : AlertDialog( - backgroundColor: AppColor.background(context), - title: title != null ? Text(title!) : null, - content: content != null ? Text(content!) : null, - // contentTextStyle: AppTextStyles.bodyText.copyWith(color: (context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - actions: [ - TextButton( - // child: Text(_subtitle.confirm), - child: Text(context.translation.confirm, style: AppTextStyles.bodyText.copyWith(color: AppColor.loadingColor(context))), - onPressed: () { - Navigator.of(context).pop(true); - }, - ), - TextButton( - // child: Text(_subtitle.cancel), - child: Text(context.translation.cancel, style: AppTextStyles.bodyText.copyWith(color: AppColor.loadingColor(context))), - onPressed: () { - Navigator.of(context).pop(false); - }, - ), - ], - ); + backgroundColor: AppColor.background(context), + actionsPadding: const EdgeInsets.only(bottom: 12, left: 24, right: 24), + title: title != null ? Text(title!) : null, + content: content != null ? Text(content!) : null, + // contentTextStyle: AppTextStyles.bodyText.copyWith(color: (context.isDark ? AppColor.neutral30 : AppColor.neutral50)), + actions: [ + TextButton( + // child: Text(_subtitle.confirm), + child: Text(button1Text ?? context.translation.confirm, style: AppTextStyles.bodyText.copyWith(color: AppColor.loadingColor(context))), + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + TextButton( + // child: Text(_subtitle.cancel), + child: Text(button2Text ?? context.translation.cancel, style: AppTextStyles.bodyText.copyWith(color: AppColor.loadingColor(context))), + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + ], + ); } }