diff --git a/.github/workflows/flutter-workflow.yaml b/.github/workflows/flutter-workflow.yaml index 9502928..5411b4e 100644 --- a/.github/workflows/flutter-workflow.yaml +++ b/.github/workflows/flutter-workflow.yaml @@ -171,8 +171,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: v2.0.2 - release_name: Release v2.0.2 + tag_name: v2.0.3 + release_name: Release v2.0.3 draft: false prerelease: false diff --git a/lib/models/conversation_model.dart b/lib/models/conversation_model.dart index d7e3ace..4810d43 100644 --- a/lib/models/conversation_model.dart +++ b/lib/models/conversation_model.dart @@ -15,6 +15,7 @@ class Conversation { final DateTime lastUpdatedAt; final List participantIds; final ChatModel sender; + final bool read; final ChatModel receiver; Conversation({ @@ -22,6 +23,7 @@ class Conversation { required this.lastUpdatedAt, required this.participantIds, required this.sender, + required this.read, required this.receiver, }); @@ -29,6 +31,7 @@ class Conversation { sender: ChatModel.fromJson(json["sender"]), receiver: ChatModel.fromJson(json["receiver"]), lastMessage: json["lastMessage"], + read: json["read"], lastUpdatedAt: json["lastUpdatedAt"] is Timestamp ? (json["lastUpdatedAt"] as Timestamp).toDate() : DateTime.parse(json["lastUpdatedAt"]), @@ -39,6 +42,7 @@ class Conversation { "sender": sender.toJson(), "receiver": receiver.toJson(), "lastMessage": lastMessage, + "read": read, "lastUpdatedAt": lastUpdatedAt.toIso8601String(), "participantIds": List.from(participantIds.map((x) => x)), }; diff --git a/lib/services/message_service.dart b/lib/services/message_service.dart index 9da7d96..7894557 100644 --- a/lib/services/message_service.dart +++ b/lib/services/message_service.dart @@ -41,6 +41,7 @@ class MessageService { 'messages/${getConversationID(message.receiver.uid, senderId: senderId)}/chat') .add(message.toJson()); } + // Retrieve messages between two users Stream> getMessagesBetweenUsers(String friendUid) { @@ -67,8 +68,24 @@ class MessageService { "lastMessage": message.content, "lastUpdatedAt": message.timestamp, "participantIds": [message.sender.uid, message.receiver.uid], + "read": false, "sender": message.sender.toJson(), "receiver": message.receiver.toJson() }).onError((e, _) => log("Error writing document: $e")); } + + markChatAsRead(receiverUid) async { + DocumentSnapshot doc = await _databaseService.store + .collection('messages') + .doc(getConversationID(receiverUid)) + .get(); + if (doc.get("sender") == receiverUid) { + await _databaseService.store + .collection('messages') + .doc(getConversationID(receiverUid)) + .set({ + "read": true, + }).onError((e, _) => log("Error writing document: $e")); + } + } } diff --git a/lib/ui/views/ai_chat/ai_chat_view.dart b/lib/ui/views/ai_chat/ai_chat_view.dart index 62dd4de..146ae49 100644 --- a/lib/ui/views/ai_chat/ai_chat_view.dart +++ b/lib/ui/views/ai_chat/ai_chat_view.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:shimmer_animation/shimmer_animation.dart'; import 'package:stacked/stacked.dart'; import '../../../models/message_model.dart'; import '../../../utils/app_colors.dart'; @@ -67,7 +68,28 @@ class AiChatView extends StackedView { child: StreamBuilder( stream: viewModel.getMessagesStream(viewModel.AI_ID), builder: (context, snapshot) { - if (!snapshot.hasData) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Shimmer( + duration: const Duration(seconds: 4), + //Default value + interval: const Duration(seconds: 1), + //Default value: Duration(seconds: 0) + color: Colors.grey, + //Default value + colorOpacity: 0.01, + //Default value + enabled: true, + //Default value + direction: const ShimmerDirection.fromLTRB(), + child: Container( + decoration: BoxDecoration( + color: Colors.grey.withOpacity(.01), + borderRadius: BorderRadius.circular(8)), + width: double.infinity, + height: 100, + ), + ); + } else if (!snapshot.hasData) { log("no messages"); return const Center( diff --git a/lib/ui/views/chat/chat_view.dart b/lib/ui/views/chat/chat_view.dart index 5fc9b34..5a25ed7 100644 --- a/lib/ui/views/chat/chat_view.dart +++ b/lib/ui/views/chat/chat_view.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:shimmer_animation/shimmer_animation.dart'; import 'package:soro_soke/ui/common/custom_text_form_field.dart'; import 'package:soro_soke/ui/common/string_utils.dart'; import 'package:soro_soke/ui/views/chat/widgets/message_box.dart'; @@ -62,7 +63,28 @@ class ChatView extends StackedView { child: StreamBuilder( stream: viewModel.getMessagesStream(friend.uid), builder: (context, snapshot) { - if (!snapshot.hasData) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Shimmer( + duration: const Duration(seconds: 4), + //Default value + interval: const Duration(seconds: 1), + //Default value: Duration(seconds: 0) + color: Colors.grey, + //Default value + colorOpacity: 0.01, + //Default value + enabled: true, + //Default value + direction: const ShimmerDirection.fromLTRB(), + child: Container( + decoration: BoxDecoration( + color: Colors.grey.withOpacity(.01), + borderRadius: BorderRadius.circular(8)), + width: double.infinity, + height: 100, + ), + ); + } else if (!snapshot.hasData) { log("no messages"); return const Center( @@ -170,6 +192,12 @@ class ChatView extends StackedView { viewModel.initializeUser(); } + @override + void onDispose(ChatViewModel viewModel) { + super.onDispose(viewModel); + viewModel.markAsRead(friend.uid); + } + @override ChatViewModel viewModelBuilder( BuildContext context, diff --git a/lib/ui/views/chat/chat_viewmodel.dart b/lib/ui/views/chat/chat_viewmodel.dart index c15cb8a..702d949 100644 --- a/lib/ui/views/chat/chat_viewmodel.dart +++ b/lib/ui/views/chat/chat_viewmodel.dart @@ -47,6 +47,10 @@ class ChatViewModel extends BaseViewModel { return _messageService.getMessagesBetweenUsers(friendID); } + void markAsRead(String friendID) { + _messageService.markChatAsRead(friendID); + } + void goBack() { _navigationService.back(); } diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index df42047..ee16358 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -138,6 +138,9 @@ class HomeView extends StackedView { borderRadius: BorderRadius.circular(10)), tileColor: const Color(0xFF261C2C), onTap: () { + sender == true + ? null + : viewModel.markChatAsRead(receiver.uid); viewModel.goToChat(receiver); }, leading: CircleAvatar( @@ -158,12 +161,33 @@ class HomeView extends StackedView { fontStyle: FontStyle.italic, overflow: TextOverflow.ellipsis), ), - trailing: Text( - "${convos[index].lastUpdatedAt.hour}: ${convos[index].lastUpdatedAt.minute}", - style: const TextStyle( - fontSize: 10, - color: Colors.white70, - overflow: TextOverflow.ellipsis), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${convos[index].lastUpdatedAt.hour}: ${convos[index].lastUpdatedAt.minute}", + style: const TextStyle( + fontSize: 10, + color: Colors.white70, + overflow: TextOverflow.ellipsis), + ), + horizontalSpaceSmall, + Visibility( + visible: sender == false, + replacement: const SizedBox( + width: 6, + ), + child: Container( + width: 6, + height: 6, + decoration: BoxDecoration( + color: convos[index].read == false + ? Colors.pinkAccent + : Colors.transparent, + shape: BoxShape.circle), + ), + ) + ], ), ); }, diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index ae7c602..8aece66 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -6,12 +6,10 @@ import 'package:soro_soke/services/message_service.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import '../../../models/chat_model.dart'; -import '../../../services/friend_service.dart'; import '../../../services/user_service.dart'; class HomeViewModel extends BaseViewModel { final _userService = locator(); - final _friendService = locator(); final _messageService = locator(); final _navigationService = locator(); @@ -27,6 +25,10 @@ class HomeViewModel extends BaseViewModel { rebuildUi(); } + markChatAsRead(receiverUid) async { + _messageService.markChatAsRead(receiverUid); + } + void goToChat(ChatModel user) { _navigationService.navigateToChatView(friend: user); }