|
|
|
|
@ -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<ChatPage> {
|
|
|
|
|
appBar: DefaultAppBar(
|
|
|
|
|
title: widget.title,
|
|
|
|
|
actions: [
|
|
|
|
|
// NEW: Call readiness indicator - shows if device can receive calls
|
|
|
|
|
if (kDebugMode)
|
|
|
|
|
Selector<ChatProvider, bool>(
|
|
|
|
|
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<ChatProvider, _ChatConnectionState>(
|
|
|
|
|
selector: (_, provider) => _ChatConnectionState(
|
|
|
|
|
isLoading: provider.chatLoginTokenLoading,
|
|
|
|
|
loginResponse: provider.chatLoginResponse,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Selector<ChatProvider, Participants?>(
|
|
|
|
|
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<ChatProvider, Participants?>(
|
|
|
|
|
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<ChatProvider, bool>(
|
|
|
|
|
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<ChatProvider, Participants?>(
|
|
|
|
|
selector: (_, provider) => provider.recipient,
|
|
|
|
|
builder: (context, recipient, _) => IconButton(
|
|
|
|
|
icon: "calling_icon".toSvgAsset(width: 24, height: 24),
|
|
|
|
|
onPressed: () async{
|
|
|
|
|
bool audioPermission = await Provider.of<ChatProvider>(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<ChatProvider>(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<ChatProvider, Participants?>(
|
|
|
|
|
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)
|
|
|
|
|
|