782 lines
25 KiB
Dart
782 lines
25 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'dart:math';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
|
import 'package:onufitness/constants/api_enum_constant.dart';
|
|
import 'package:onufitness/constants/data_constant.dart';
|
|
import 'package:onufitness/controller/get_agora_token_controller.dart';
|
|
import 'package:onufitness/screens/streamming/controllers/get_api_live_streams_controller.dart';
|
|
import 'package:onufitness/services/local_storage_services/shared_services.dart';
|
|
import 'package:onufitness/services/logger_service.dart';
|
|
import 'package:onufitness/utils/custom_sneakbar.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import 'package:agora_chat_sdk/agora_chat_sdk.dart';
|
|
|
|
// Live Stream Controller-----------------------------------------------------------------------
|
|
|
|
class LiveStreamController extends GetxController {
|
|
final logger = LoggerService();
|
|
|
|
late RtcEngine _engine;
|
|
get engine => _engine;
|
|
|
|
// Timer related variables---------------------------------------------------------------------
|
|
Timer? _streamTimer;
|
|
static const int _streamDurationMinutes = streamEndsTime;
|
|
RxInt remainingSeconds = (_streamDurationMinutes * 60).obs;
|
|
RxString timeDisplay =
|
|
"${_streamDurationMinutes.toString().padLeft(2, '0')}:00".obs;
|
|
|
|
// Observable variables------------------------------------------------------------------------
|
|
RxBool localUserJoined = false.obs;
|
|
RxInt remoteUid = 0.obs;
|
|
RxBool isHost = false.obs;
|
|
RxBool isJoined = false.obs;
|
|
RxList<LiveChatMessage> chatMessages = <LiveChatMessage>[].obs;
|
|
RxBool showChat = true.obs;
|
|
RxBool chatRoomJoined = false.obs;
|
|
RxInt memberCount = 0.obs;
|
|
|
|
// Controllers---------------------------------------------------------------------------------
|
|
final TextEditingController commentController = TextEditingController();
|
|
final ScrollController chatScrollController = ScrollController();
|
|
|
|
// Agora Configuration -------------------------------------------------------------------------
|
|
static const appId = agoraAppId;
|
|
static const String chatAppKey = agoraChatAppKey;
|
|
|
|
// Chat Room Configuration---------------------------------------------------------------------
|
|
RxString chatRoomId = "".obs;
|
|
|
|
RxString token = "".obs;
|
|
RxString channel = "".obs;
|
|
// User info------------------------------------------------------------------------------------
|
|
RxString hostName = "".obs;
|
|
RxString hostProfilePic = "".obs;
|
|
RxString currentUserId = "".obs;
|
|
RxString currentUserName = "".obs;
|
|
RxString currentUserProfilePic = "".obs;
|
|
|
|
// Add flag to track if engine needs reinitialization
|
|
bool _engineNeedsReinit = false;
|
|
|
|
//------------------------------------------------------------------------------------------------
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_initializeChatSDK();
|
|
_initializeAgoraVideoSDK();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
stopStreamTimer();
|
|
_cleanupEverything();
|
|
commentController.dispose();
|
|
chatScrollController.dispose();
|
|
super.onClose();
|
|
}
|
|
|
|
// Start the 10-minute countdown timer.................................................................
|
|
void _startStreamTimer() {
|
|
stopStreamTimer();
|
|
|
|
remainingSeconds.value = _streamDurationMinutes * 60;
|
|
updateTimeDisplay();
|
|
|
|
_streamTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
if (remainingSeconds.value > 0) {
|
|
remainingSeconds.value--;
|
|
updateTimeDisplay();
|
|
|
|
// Show warnings at specific intervals
|
|
if (remainingSeconds.value == 300) {
|
|
showTimeWarning("5 minutes remaining before spark auto-ends");
|
|
} else if (remainingSeconds.value == 180) {
|
|
showTimeWarning("3 minutes remaining before spark auto-ends");
|
|
} else if (remainingSeconds.value == 60) {
|
|
showTimeWarning("1 minute remaining before spark auto-ends");
|
|
} else if (remainingSeconds.value == 30) {
|
|
showTimeWarning("30 seconds remaining before spark auto-ends");
|
|
}
|
|
} else {
|
|
timer.cancel();
|
|
autoEndStream();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Stop the stream timer.......................................................................................
|
|
void stopStreamTimer() {
|
|
_streamTimer?.cancel();
|
|
_streamTimer = null;
|
|
}
|
|
|
|
// Update the time display format (MM:SS)......................................................................
|
|
void updateTimeDisplay() {
|
|
int minutes = remainingSeconds.value ~/ 60;
|
|
int seconds = remainingSeconds.value % 60;
|
|
timeDisplay.value =
|
|
'${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
|
}
|
|
|
|
// Show time warning to users..................................................................................
|
|
void showTimeWarning(String message) {
|
|
customSnackbar(title: 'Spark Timer', message: message, duration: 3);
|
|
|
|
// Add system message to chat
|
|
addChatMessage(
|
|
LiveChatMessage(
|
|
userId: "system",
|
|
username: "System",
|
|
message: "⏰ $message",
|
|
timestamp: DateTime.now(),
|
|
isSystemMessage: true,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Auto-end stream when timer expires..................................................................................
|
|
Future<void> autoEndStream() async {
|
|
try {
|
|
// Show final warning
|
|
customSnackbar(
|
|
title: 'Spark Ended',
|
|
message: 'Spark automatically ended after 10 minutes',
|
|
duration: 5,
|
|
);
|
|
|
|
// Add system message to chat
|
|
addChatMessage(
|
|
LiveChatMessage(
|
|
userId: "system",
|
|
username: "System",
|
|
message:
|
|
"⏰ Spark automatically ended after 10 minutes. Thank you for watching!",
|
|
timestamp: DateTime.now(),
|
|
isSystemMessage: true,
|
|
),
|
|
);
|
|
|
|
// Wait a moment for the message to be sent
|
|
await Future.delayed(const Duration(seconds: 2));
|
|
|
|
// End the stream
|
|
await endStream();
|
|
|
|
// Make API call to end stream on backend
|
|
if (!Get.isRegistered<GetLiveStreamsController>()) {
|
|
Get.put(GetLiveStreamsController());
|
|
}
|
|
final apiController = Get.find<GetLiveStreamsController>();
|
|
await apiController.endLiveStream(streamID: channel.value);
|
|
} catch (e, stackTrace) {
|
|
logger.error(
|
|
"Failed to end stream or make API call to end stream on backend",
|
|
error: e,
|
|
stackTrace: stackTrace,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Initialize Agora Chat SDK-------------------------------------------------------------------------
|
|
Future<void> _initializeChatSDK() async {
|
|
try {
|
|
ChatOptions options = ChatOptions(
|
|
appKey: chatAppKey,
|
|
deleteMessagesAsExitChatRoom: false,
|
|
);
|
|
|
|
await ChatClient.getInstance.init(options);
|
|
await loginChatUser();
|
|
_setupChatEventHandlers();
|
|
} catch (e) {
|
|
Get.snackbar('Chat Error', 'Failed to initialize chat: $e');
|
|
}
|
|
}
|
|
|
|
// Chat Login------------------------------------------------------------------------------------------------------------
|
|
Future<bool> loginChatUser() async {
|
|
try {
|
|
final loginDetails = SharedServices.getLoginDetails();
|
|
if (loginDetails?.data?.userId == null) {
|
|
return false;
|
|
}
|
|
final tokenSuccess = SharedServices.getAgoraUserAndRtmTokens();
|
|
if (tokenSuccess?.data == null ||
|
|
tokenSuccess?.data?.agoraUserToken == null) {
|
|
return false;
|
|
}
|
|
final userToken = tokenSuccess!.data!.agoraUserToken.toString();
|
|
final userId = loginDetails!.data!.userId.toString();
|
|
final agoraUserId = userId.replaceAll("-", "");
|
|
|
|
await ChatClient.getInstance.loginWithToken(agoraUserId, userToken);
|
|
|
|
return true;
|
|
} on ChatError catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Setup chat room event handlers-------------------------------------------------------------------------
|
|
void _setupChatEventHandlers() {
|
|
ChatClient.getInstance.chatRoomManager.addEventHandler(
|
|
"chat_event_handeller",
|
|
ChatRoomEventHandler(
|
|
// When a new member joins the chat room
|
|
onMemberJoinedFromChatRoom: (roomId, participant, ext) async {
|
|
await updateMemberCount();
|
|
},
|
|
|
|
// When a member leaves the chat room
|
|
onMemberExitedFromChatRoom: (roomId, roomName, participant) async {
|
|
await updateMemberCount();
|
|
},
|
|
|
|
// When chat room is destroyed - UPDATED WITH STREAM ENDED MESSAGE
|
|
onChatRoomDestroyed: (roomId, roomName) {
|
|
addChatMessage(
|
|
LiveChatMessage(
|
|
userId: "system",
|
|
username: "System",
|
|
message: "🔴 The Spark has ended. Thank you for watching!",
|
|
timestamp: DateTime.now(),
|
|
isSystemMessage: true,
|
|
),
|
|
);
|
|
|
|
chatRoomJoined.value = false;
|
|
customSnackbar(
|
|
title: 'Live Stream ended',
|
|
message: 'The Spark has ended. Thank you for watching!',
|
|
duration: 2,
|
|
);
|
|
},
|
|
|
|
// When member count changes
|
|
onSpecificationChanged: (room) async {
|
|
await updateMemberCount();
|
|
},
|
|
),
|
|
);
|
|
|
|
// Setup message event handler for receiving messages
|
|
ChatClient.getInstance.chatManager.addEventHandler(
|
|
"receive_message_handler",
|
|
ChatEventHandler(
|
|
onMessagesReceived: (messages) {
|
|
for (var message in messages) {
|
|
if (message.chatType == ChatType.ChatRoom &&
|
|
message.conversationId == chatRoomId.value) {
|
|
_handleReceivedMessage(message);
|
|
}
|
|
}
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
// Handle received chat messages-------------------------------------------------------------------------
|
|
void _handleReceivedMessage(ChatMessage message) {
|
|
String messageText = "";
|
|
String senderName = message.attributes!['fullName'] ?? "Unknown";
|
|
|
|
if (message.body.type == MessageType.TXT) {
|
|
ChatTextMessageBody body = message.body as ChatTextMessageBody;
|
|
messageText = body.content;
|
|
}
|
|
|
|
addChatMessage(
|
|
LiveChatMessage(
|
|
userId: message.from ?? "unknown",
|
|
username: senderName,
|
|
message: messageText,
|
|
timestamp: DateTime.fromMillisecondsSinceEpoch(message.serverTime),
|
|
isSystemMessage: false,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Update member count-------------------------------------------------------------------------
|
|
Future<void> updateMemberCount() async {
|
|
try {
|
|
// ChatRoom? room = await ChatClient.getInstance.chatRoomManager
|
|
// .getChatRoomWithId(chatRoomId.value);
|
|
ChatRoom? room = await ChatClient.getInstance.chatRoomManager
|
|
.fetchChatRoomInfoFromServer(chatRoomId.value);
|
|
|
|
memberCount.value = room.memberCount ?? 0;
|
|
} catch (e, stackTrace) {
|
|
logger.error(
|
|
"Failed to update member count from chat room",
|
|
error: e,
|
|
stackTrace: stackTrace,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Initialize the Agora RTC engine instance-------------------------------------------------------------------------
|
|
Future<void> _initializeAgoraVideoSDK() async {
|
|
try {
|
|
_engine = createAgoraRtcEngine();
|
|
await _engine.initialize(
|
|
const RtcEngineContext(
|
|
appId: appId,
|
|
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
|
|
),
|
|
);
|
|
|
|
_setupEventHandlers();
|
|
await _requestPermissions();
|
|
_engineNeedsReinit = false;
|
|
} catch (e) {
|
|
Get.snackbar('Error', 'Failed to initialize Agora SDK: $e');
|
|
}
|
|
}
|
|
|
|
// Request microphone and camera permissions-------------------------------------------------------------------------
|
|
Future<void> _requestPermissions() async {
|
|
if (Platform.isAndroid) {
|
|
await [Permission.microphone, Permission.camera].request();
|
|
}
|
|
}
|
|
|
|
// Register event handlers for Agora RTC-------------------------------------------------------------------------
|
|
void _setupEventHandlers() {
|
|
_engine.registerEventHandler(
|
|
RtcEngineEventHandler(
|
|
onJoinChannelSuccess: (RtcConnection connection, int elapsed) async {
|
|
localUserJoined.value = true;
|
|
isJoined.value = true;
|
|
|
|
// Start the 10-minute timer when successfully joined (only for host)
|
|
if (isHost.value) {
|
|
_startStreamTimer();
|
|
}
|
|
// Join chat room after successfully joining video channel
|
|
await joinChatRoom();
|
|
},
|
|
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
|
|
this.remoteUid.value = remoteUid;
|
|
},
|
|
onUserOffline: (
|
|
RtcConnection connection,
|
|
int remoteUid,
|
|
UserOfflineReasonType reason,
|
|
) {
|
|
this.remoteUid.value = 0;
|
|
},
|
|
onError: (ErrorCodeType err, String msg) {},
|
|
),
|
|
);
|
|
}
|
|
|
|
// Setup local video.....................................................................
|
|
Future<void> _setupLocalVideo() async {
|
|
await _engine.enableVideo();
|
|
await _engine.startPreview();
|
|
}
|
|
|
|
// Join channel as host.....................................................................
|
|
Future joinAsHost() async {
|
|
try {
|
|
// Reinitialize engine if needed
|
|
if (_engineNeedsReinit) {
|
|
await _initializeAgoraVideoSDK();
|
|
}
|
|
|
|
isHost.value = true;
|
|
await _setupLocalVideo();
|
|
if (!Get.isRegistered<AgoraTokenController>()) {
|
|
Get.put(AgoraTokenController());
|
|
}
|
|
|
|
final agoraTokenController = Get.find<AgoraTokenController>();
|
|
|
|
// Generate a random 32-bit integer
|
|
final random = Random();
|
|
final tempId = random.nextInt(4294967296); // 2^32
|
|
|
|
await agoraTokenController
|
|
.getRTCtoken(
|
|
channelName: channel.value,
|
|
role: ApiEnum.SUBSCRIBER,
|
|
tempId: tempId.toString(),
|
|
)
|
|
.then((value) {
|
|
if (value) {
|
|
String tokenValue = agoraTokenController.rTCtoken.value;
|
|
token.value = tokenValue;
|
|
}
|
|
});
|
|
// Enabling the chat..................................................................
|
|
chatRoomJoined.value = true;
|
|
//......................................................................................
|
|
await _engine.joinChannel(
|
|
token: token.value,
|
|
channelId: channel.value,
|
|
options: const ChannelMediaOptions(
|
|
autoSubscribeVideo: true,
|
|
autoSubscribeAudio: true,
|
|
publishCameraTrack: true,
|
|
publishMicrophoneTrack: true,
|
|
clientRoleType: ClientRoleType.clientRoleBroadcaster,
|
|
audienceLatencyLevel:
|
|
AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency,
|
|
),
|
|
uid: tempId,
|
|
);
|
|
} catch (e, stackTrace) {
|
|
logger.error(
|
|
"Failed to join channel as broadcaster",
|
|
error: e,
|
|
stackTrace: stackTrace,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Join channel as audience.....................................................................
|
|
Future joinAsAudience() async {
|
|
// Reinitialize engine if needed
|
|
if (_engineNeedsReinit) {
|
|
await _initializeAgoraVideoSDK();
|
|
}
|
|
|
|
if (!Get.isRegistered<AgoraTokenController>()) {
|
|
Get.put(AgoraTokenController());
|
|
}
|
|
|
|
final agoraTokenController = Get.find<AgoraTokenController>();
|
|
|
|
// Generate a random 32-bit integer
|
|
final random = Random();
|
|
final tempId = random.nextInt(4294967296); // 2^32
|
|
|
|
await agoraTokenController
|
|
.getRTCtoken(
|
|
channelName: channel.value,
|
|
role: ApiEnum.SUBSCRIBER,
|
|
tempId: tempId.toString(),
|
|
)
|
|
.then((value) {
|
|
if (value) {
|
|
String tokenValue = agoraTokenController.rTCtoken.value;
|
|
token.value = tokenValue;
|
|
}
|
|
});
|
|
|
|
try {
|
|
isHost.value = false;
|
|
// Enabling the chat..................................................................
|
|
chatRoomJoined.value = true;
|
|
//......................................................................................
|
|
await _engine.joinChannel(
|
|
token: token.value,
|
|
channelId: channel.value,
|
|
options: const ChannelMediaOptions(
|
|
autoSubscribeVideo: true,
|
|
autoSubscribeAudio: true,
|
|
publishCameraTrack: false,
|
|
publishMicrophoneTrack: false,
|
|
clientRoleType: ClientRoleType.clientRoleAudience,
|
|
audienceLatencyLevel:
|
|
AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency,
|
|
),
|
|
uid: tempId,
|
|
);
|
|
} catch (e, stackTrace) {
|
|
logger.error(
|
|
"Failed to join channel as audience",
|
|
error: e,
|
|
stackTrace: stackTrace,
|
|
);
|
|
}
|
|
}
|
|
|
|
// End stream (host only)------------------------------------------------------------------------
|
|
Future<void> endStream() async {
|
|
try {
|
|
// Stop the timer first
|
|
stopStreamTimer();
|
|
|
|
// Step 1: Destroy chat room if host (this will kick out all members)
|
|
if (isHost.value && chatRoomJoined.value) {
|
|
try {
|
|
await ChatClient.getInstance.chatRoomManager.destroyChatRoom(
|
|
chatRoomId.value,
|
|
);
|
|
} catch (e) {
|
|
// If destroy fails, at least leave the room
|
|
await _leaveChatRoom();
|
|
}
|
|
} else {
|
|
// If not host, just leave the chat room
|
|
await _leaveChatRoom();
|
|
}
|
|
|
|
// Step 2: Clean up video engine
|
|
await _cleanupAgoraEngine();
|
|
|
|
// Step 3: Reset all state variables
|
|
_resetStreamState();
|
|
|
|
// Step 4: Navigate back
|
|
Get.back();
|
|
} catch (e, stackTrace) {
|
|
logger.error(
|
|
"Failed to end stream and cleanup",
|
|
error: e,
|
|
stackTrace: stackTrace,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Leave stream(For audience) --------------------------------------------------------------------------
|
|
Future<void> leaveStream() async {
|
|
try {
|
|
// Stop timer if running (shouldn't be for audience, but just in case)
|
|
stopStreamTimer();
|
|
// Send system message..............................
|
|
await sendJoinedAndLeaveMessage(false);
|
|
// Leave chat room first..............................
|
|
await _leaveChatRoom();
|
|
|
|
// Clean up video engine..............................
|
|
await _cleanupAgoraEngine();
|
|
|
|
// Reset state..............................
|
|
_resetStreamState();
|
|
|
|
// Navigate back..............................
|
|
Get.back();
|
|
} catch (e, stackTrace) {
|
|
logger.error(
|
|
"Failed to leave stream and cleanup",
|
|
error: e,
|
|
stackTrace: stackTrace,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Reset stream state-------------------------------------------------------------------------
|
|
void _resetStreamState() {
|
|
// Reset observable variables
|
|
localUserJoined.value = false;
|
|
isJoined.value = false;
|
|
remoteUid.value = 0;
|
|
chatRoomJoined.value = false;
|
|
memberCount.value = 0;
|
|
|
|
// Reset timer state
|
|
remainingSeconds.value = _streamDurationMinutes * 60;
|
|
timeDisplay.value =
|
|
"${_streamDurationMinutes.toString().padLeft(2, '0')}:00";
|
|
|
|
// Clear chat messages
|
|
chatMessages.clear();
|
|
|
|
// Clear controllers
|
|
commentController.clear();
|
|
|
|
// Reset tokens and channel info
|
|
token.value = "";
|
|
// Don't reset channel.value as it might be needed for rejoining
|
|
}
|
|
|
|
// Cleanup Agora engine -------------------------------------------------------------------------
|
|
Future<void> _cleanupAgoraEngine() async {
|
|
try {
|
|
if (isJoined.value) {
|
|
await _engine.leaveChannel();
|
|
}
|
|
await _engine.release();
|
|
_engineNeedsReinit = true; // Mark that engine needs reinitialization
|
|
} catch (e) {
|
|
_engineNeedsReinit = true; // Still mark for reinit even if cleanup failed
|
|
}
|
|
}
|
|
|
|
// Complete cleanup - for onClose-------------------------------------------------------------------------
|
|
Future<void> _cleanupEverything() async {
|
|
try {
|
|
// First cleanup chat
|
|
await _leaveChatRoom();
|
|
|
|
// Then cleanup video engine
|
|
if (!_engineNeedsReinit) {
|
|
await _cleanupAgoraEngine();
|
|
}
|
|
|
|
// Remove all event handlers
|
|
ChatClient.getInstance.chatRoomManager.removeEventHandler(
|
|
"chat_event_handeller",
|
|
);
|
|
ChatClient.getInstance.chatManager.removeEventHandler(
|
|
"receive_message_handler",
|
|
);
|
|
} catch (e, stackTrace) {
|
|
logger.error(
|
|
"Failed to cleanup Agora engine or remove event handlers",
|
|
error: e,
|
|
stackTrace: stackTrace,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Join chat room -------------------------------------------------------------------------
|
|
Future<void> joinChatRoom() async {
|
|
try {
|
|
await ChatClient.getInstance.chatRoomManager.joinChatRoom(
|
|
chatRoomId.value,
|
|
leaveOtherRooms: false,
|
|
ext: currentUserId.value,
|
|
);
|
|
|
|
chatRoomJoined.value = true;
|
|
|
|
await updateMemberCount();
|
|
|
|
// Add welcome message
|
|
addChatMessage(
|
|
LiveChatMessage(
|
|
userId: "system",
|
|
username: "System",
|
|
message:
|
|
"Welcome to the live chat! Stream will auto-end after 10 minutes.",
|
|
timestamp: DateTime.now(),
|
|
isSystemMessage: true,
|
|
),
|
|
);
|
|
Future.delayed(Duration(milliseconds: 500), () async {
|
|
await sendJoinedAndLeaveMessage(true);
|
|
});
|
|
} catch (e, stackTrace) {
|
|
logger.error(
|
|
"Failed to join chat room or send welcome message",
|
|
error: e,
|
|
stackTrace: stackTrace,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Leave chat room - IMPROVED VERSION----------------------------------------------------------------------------------------
|
|
Future<void> _leaveChatRoom() async {
|
|
try {
|
|
if (chatRoomJoined.value && chatRoomId.value.isNotEmpty) {
|
|
await ChatClient.getInstance.chatRoomManager.leaveChatRoom(
|
|
chatRoomId.value,
|
|
);
|
|
chatRoomJoined.value = false;
|
|
}
|
|
} catch (e) {
|
|
// Don't show error to user as this might happen during normal cleanup
|
|
}
|
|
}
|
|
|
|
// Send comment to chat room-------------------------------------------------------------------------
|
|
void sendComment() {
|
|
if (commentController.text.trim().isNotEmpty && chatRoomJoined.value) {
|
|
_sendChatMessage(commentController.text);
|
|
commentController.clear();
|
|
} else if (!chatRoomJoined.value) {
|
|
Get.snackbar('Chat Error', 'Not connected to chat room');
|
|
}
|
|
}
|
|
|
|
// Send message to chat room-------------------------------------------------------------------------
|
|
Future<void> _sendChatMessage(String messageText) async {
|
|
try {
|
|
// Create text message
|
|
ChatMessage message = ChatMessage.createTxtSendMessage(
|
|
targetId: chatRoomId.value,
|
|
content: messageText,
|
|
chatType: ChatType.ChatRoom,
|
|
);
|
|
message.attributes = {
|
|
"fullName": currentUserName.value,
|
|
"profilePicture": currentUserProfilePic.value,
|
|
};
|
|
// Send message
|
|
await ChatClient.getInstance.chatManager.sendMessage(message);
|
|
|
|
// Add to local chat immediately for better UX
|
|
addChatMessage(
|
|
LiveChatMessage(
|
|
userId: currentUserId.value,
|
|
username: 'You',
|
|
message: messageText,
|
|
timestamp: DateTime.now(),
|
|
isSystemMessage: false,
|
|
),
|
|
);
|
|
} catch (e) {
|
|
Get.snackbar('Chat Error', 'Failed to send message: $e');
|
|
}
|
|
}
|
|
|
|
//.... Show System message When user Join the room and Left the Room................................................
|
|
Future<void> sendJoinedAndLeaveMessage(bool isJoined) async {
|
|
try {
|
|
// Create text message
|
|
ChatMessage message = ChatMessage.createTxtSendMessage(
|
|
targetId: chatRoomId.value,
|
|
content:
|
|
isJoined
|
|
? "${currentUserName.value} Joined the chat"
|
|
: "${currentUserName.value} left the chat",
|
|
chatType: ChatType.ChatRoom,
|
|
);
|
|
message.attributes = {
|
|
"fullName": "System",
|
|
"profilePicture": "",
|
|
"isSystemMessage": true,
|
|
};
|
|
// Send message
|
|
await ChatClient.getInstance.chatManager.sendMessage(message);
|
|
} catch (e) {
|
|
Get.snackbar('Chat Error', 'Failed to send message: $e');
|
|
}
|
|
}
|
|
|
|
// Add chat message-------------------------------------------------------------------------
|
|
void addChatMessage(LiveChatMessage message) {
|
|
chatMessages.add(message);
|
|
|
|
// Auto scroll to bottom
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (chatScrollController.hasClients) {
|
|
chatScrollController.animateTo(
|
|
chatScrollController.position.maxScrollExtent,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeOut,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Toggle chat visibility-------------------------------------------------------------------------
|
|
void toggleChat() {
|
|
showChat.value = !showChat.value;
|
|
}
|
|
}
|
|
|
|
// Live Chat Message Model-------------------------------------------------------------------------
|
|
class LiveChatMessage {
|
|
final String userId;
|
|
final String username;
|
|
final String message;
|
|
final DateTime timestamp;
|
|
final bool isSystemMessage;
|
|
|
|
LiveChatMessage({
|
|
required this.userId,
|
|
required this.username,
|
|
required this.message,
|
|
required this.timestamp,
|
|
this.isSystemMessage = false,
|
|
});
|
|
}
|