From 400990efe158d5839b31718f8b5744967cbee47c Mon Sep 17 00:00:00 2001 From: Remigijus Kiminas Date: Wed, 2 Feb 2022 05:35:30 -0500 Subject: [PATCH] 1.7v --- android/app/build.gradle | 4 +- changelog.txt | 5 - .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin lib/bloc/chats_list/chatslist_bloc.dart | 34 +++ lib/bloc/chats_list/chatslist_state.dart | 12 +- lib/bloc/fcm/fcmtoken_bloc.dart | 9 + lib/model/chat.dart | 29 ++- lib/model/server.dart | 26 ++ lib/pages/chat/chat_page.dart | 7 - lib/pages/lists/chat_list_bot.dart | 232 ++++++++++++++++++ lib/pages/lists/chat_list_subject.dart | 232 ++++++++++++++++++ lib/pages/main_page.dart | 67 ++++- lib/pages/pages.dart | 2 + lib/services/server_api_client.dart | 24 ++ lib/utils/local_notification.dart | 25 +- lib/widget/chat_item_widget.dart | 93 ++++++- lib/widget/chat_number_indicator.dart | 28 ++- pubspec.lock | 0 pubspec.yaml | 2 +- 36 files changed, 795 insertions(+), 36 deletions(-) mode change 100755 => 100644 android/app/build.gradle mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png mode change 100755 => 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 lib/pages/lists/chat_list_bot.dart create mode 100644 lib/pages/lists/chat_list_subject.dart mode change 100755 => 100644 pubspec.lock mode change 100755 => 100644 pubspec.yaml diff --git a/android/app/build.gradle b/android/app/build.gradle old mode 100755 new mode 100644 index 9e6900f..08b0217 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -45,8 +45,8 @@ android { applicationId "com.livehelperchat.chat" minSdkVersion 16 targetSdkVersion 30 - versionCode 36 - versionName "1.7" + versionCode 38 + versionName "1.8" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/changelog.txt b/changelog.txt index 7bddacf..906906c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,8 +1,3 @@ -1.2 - -1. Chat between operators -2. Notifications fixes. - 1.1 To see online visitor status it's require 3.42v diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png old mode 100755 new mode 100644 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png old mode 100755 new mode 100644 diff --git a/lib/bloc/chats_list/chatslist_bloc.dart b/lib/bloc/chats_list/chatslist_bloc.dart index b15f471..05deead 100644 --- a/lib/bloc/chats_list/chatslist_bloc.dart +++ b/lib/bloc/chats_list/chatslist_bloc.dart @@ -65,6 +65,8 @@ class ChatslistBloc extends Bloc { transferChatList: server.transferChatList, twilioChatList: server.twilioChatList, closedChatList: server.closedChatList, + botChatList: server.botChatList, + subjectChatList: server.subjectChatList, operatorsChatList: server.operatorsChatList ); } catch (ex) { @@ -80,6 +82,8 @@ class ChatslistBloc extends Bloc { List transferChats = List.from(currentState.transferChatList); List twilioChats = List.from(currentState.twilioChatList); List closedChats = List.from(currentState.closedChatList); + List botChats = List.from(currentState.botChatList); + List subjectChats = List.from(currentState.subjectChatList); List operatorsChatChats = List.from(currentState.operatorsChatList); List activeList = @@ -88,6 +92,12 @@ class ChatslistBloc extends Bloc { List closedList = await _cleanList(ChatListName.closed, server, closedChats); + List botList = + await _cleanList(ChatListName.bot, server, botChats); + + List subjectList = + await _cleanList(ChatListName.subject, server, subjectChats); + List operatorsList = await _cleanListOperator(ChatListName.operators, server, operatorsChatChats); @@ -106,6 +116,8 @@ class ChatslistBloc extends Bloc { transferChatList: transferList, twilioChatList: _sortByLastMessageTime(twilioList), closedChatList: _sortById(closedList), + botChatList: _sortByLastMessageTime(botList), + subjectChatList: _sortByLastMessageTime(subjectList), operatorsChatList: _sortByLastOperatorMessageTime(operatorsList) ); } @@ -157,6 +169,28 @@ class ChatslistBloc extends Bloc { } return listToClean; + case ChatListName.bot: + if (server.botChatList.length == 0) { + if (listToClean.length > 0) { + listToClean.removeWhere((chat) => chat.serverid == server.id); + } + } else { + listToClean = + await _updateChatList(listToClean, server.botChatList); + } + return listToClean; + + case ChatListName.subject: + if (server.subjectChatList.length == 0) { + if (listToClean.length > 0) { + listToClean.removeWhere((chat) => chat.serverid == server.id); + } + } else { + listToClean = + await _updateChatList(listToClean, server.subjectChatList); + } + return listToClean; + case ChatListName.closed: if (server.closedChatList.length == 0) { if (listToClean.length > 0) { diff --git a/lib/bloc/chats_list/chatslist_state.dart b/lib/bloc/chats_list/chatslist_state.dart index befe408..afaa6f3 100644 --- a/lib/bloc/chats_list/chatslist_state.dart +++ b/lib/bloc/chats_list/chatslist_state.dart @@ -1,6 +1,6 @@ part of 'chatslist_bloc.dart'; -enum ChatListName { active, pending, transfer, twilio, closed, operators } +enum ChatListName { active, pending, transfer, twilio, closed, subject, bot, operators } abstract class ChatListState extends Equatable { ChatListState(); @@ -26,6 +26,8 @@ class ChatListLoaded extends ChatListState { final List transferChatList; final List twilioChatList; final List closedChatList; + final List botChatList; + final List subjectChatList; final List operatorsChatList; final bool isLoading; @@ -35,6 +37,8 @@ class ChatListLoaded extends ChatListState { this.transferChatList = const [], this.twilioChatList = const [], this.closedChatList = const [], + this.botChatList = const [], + this.subjectChatList = const [], this.operatorsChatList = const [], this.isLoading = false}); @@ -44,6 +48,8 @@ class ChatListLoaded extends ChatListState { List transferChatList, List twilioChatList, List closedChatList, + List botChatList, + List subjectChatList, List operatorsChatList, bool isLoading = false}) { return ChatListLoaded( @@ -52,6 +58,8 @@ class ChatListLoaded extends ChatListState { transferChatList: transferChatList ?? this.transferChatList, twilioChatList: twilioChatList ?? this.twilioChatList, closedChatList: closedChatList ?? this.closedChatList, + botChatList: botChatList ?? this.botChatList, + subjectChatList: subjectChatList ?? this.subjectChatList, operatorsChatList: operatorsChatList ?? this.operatorsChatList, isLoading: isLoading ?? this.isLoading); } @@ -63,6 +71,8 @@ class ChatListLoaded extends ChatListState { transferChatList, twilioChatList, closedChatList, + botChatList, + subjectChatList, operatorsChatList, isLoading ]; diff --git a/lib/bloc/fcm/fcmtoken_bloc.dart b/lib/bloc/fcm/fcmtoken_bloc.dart index 0c6289b..55ca74c 100644 --- a/lib/bloc/fcm/fcmtoken_bloc.dart +++ b/lib/bloc/fcm/fcmtoken_bloc.dart @@ -158,6 +158,15 @@ class FcmTokenBloc extends Bloc { body: data['msg'].toString()); } + if (data["chat_type"].toString() == "subject") { + return ReceivedNotification( + server: server, + chat: Chat.fromJson(chat), + type: NotificationType.SUBJECT, + title: "New subject", + body: data['msg'].toString()); + } + if (data["chat_type"].toString() == "unread") { return ReceivedNotification( server: server, diff --git a/lib/model/chat.dart b/lib/model/chat.dart index 9956512..95f59a4 100644 --- a/lib/model/chat.dart +++ b/lib/model/chat.dart @@ -26,6 +26,8 @@ class Chat extends Equatable { "db_last_user_msg_time": "last_user_msg_time", "db_phone": "phone", "db_user_status_front": "user_status_front", + "db_subject_front": "subject_front", + "db_aicon_front": "aicon_front" }; final int id, @@ -48,7 +50,9 @@ class Chat extends Equatable { department_name, user_typing_txt, owner, - phone; + phone, + subject_front, + aicon_front; int get last_msg_time { if (last_op_msg_time != null && last_user_msg_time != null) { @@ -85,7 +89,10 @@ class Chat extends Equatable { this.last_user_msg_time, this.last_op_msg_time, this.phone, - this.user_status_front}); + this.user_status_front, + this.subject_front, + this.aicon_front, + }); Chat copyWith( {int id, @@ -108,7 +115,10 @@ class Chat extends Equatable { last_user_msg_time, last_op_msg_time, String phone, - String user_status_front}) { + String user_status_front, + String subject_front, + String aicon_front + }) { return Chat( id: id ?? this.id, serverid: serverid ?? this.serverid, @@ -130,7 +140,10 @@ class Chat extends Equatable { last_user_msg_time: last_user_msg_time ?? this.last_user_msg_time, last_op_msg_time: last_op_msg_time ?? this.last_op_msg_time, phone: phone ?? this.phone, - user_status_front: user_status_front ?? this.user_status_front); + user_status_front: user_status_front ?? this.user_status_front, + subject_front: subject_front ?? this.subject_front, + aicon_front: aicon_front ?? this.aicon_front + ); } static int checkInt(dynamic value) { @@ -161,6 +174,8 @@ class Chat extends Equatable { last_op_msg_time: checkInt(map['last_op_msg_time']), phone: map['phone'] ?? "", user_status_front: map['user_status_front'] ?? 0, + subject_front: map['subject_front'] ?? "", + aicon_front: map['aicon_front'] ?? "", ); Map toJson() { @@ -185,6 +200,8 @@ class Chat extends Equatable { 'last_msg_time': last_msg_time, columns['db_phone']: phone, columns['db_user_status_front']: user_status_front, + columns['db_subject_front']: subject_front, + columns['db_aicon_front']: aicon_front, }; } @@ -210,6 +227,8 @@ class Chat extends Equatable { last_user_msg_time, last_op_msg_time, phone, - user_status_front + user_status_front, + subject_front, + aicon_front, ]; } diff --git a/lib/model/server.dart b/lib/model/server.dart index 6d6e6f7..0626fab 100644 --- a/lib/model/server.dart +++ b/lib/model/server.dart @@ -73,6 +73,8 @@ class Server { List transferChatList; List twilioChatList; List closedChatList; + List botChatList; + List subjectChatList; List operatorsChatList; Server( @@ -104,6 +106,8 @@ class Server { transferChatList = List(); twilioChatList = List(); closedChatList = List(); + botChatList = List(); + subjectChatList = List(); operatorsChatList = List(); } @@ -180,6 +184,16 @@ class Server { this.closedChatList = _cleanUpLists(this.closedChatList, newChatList); this.closedChatList.sort((a, b) => a.id.compareTo(b.id)); break; + case "bot": + this.botChatList ??= new List(); + this.botChatList = _cleanUpLists(this.botChatList, newChatList); + this.botChatList.sort((a, b) => a.id.compareTo(b.id)); + break; + case "subject": + this.subjectChatList ??= new List(); + this.subjectChatList = _cleanUpLists(this.subjectChatList, newChatList); + this.subjectChatList.sort((a, b) => a.id.compareTo(b.id)); + break; case "twilio": this.twilioChatList ??= new List(); this.twilioChatList = _cleanUpLists(this.twilioChatList, newChatList); @@ -278,6 +292,12 @@ class Server { case 'transfer': this.transferChatList?.clear(); break; + case 'bot': + this.botChatList?.clear(); + break; + case 'subject': + this.subjectChatList?.clear(); + break; case 'closed': this.closedChatList?.clear(); break; @@ -300,6 +320,12 @@ class Server { case 'pending': this.pendingChatList.removeWhere((chat) => chat.id == id); break; + case 'subject': + this.subjectChatList.removeWhere((chat) => chat.id == id); + break; + case 'bot': + this.botChatList.removeWhere((chat) => chat.id == id); + break; case 'transfer': this.transferChatList.removeWhere((chat) => chat.id == id); break; diff --git a/lib/pages/chat/chat_page.dart b/lib/pages/chat/chat_page.dart index aa414a0..1d5a97f 100644 --- a/lib/pages/chat/chat_page.dart +++ b/lib/pages/chat/chat_page.dart @@ -307,13 +307,6 @@ class ChatPageState extends State onPressed: () { _isActionLoading = true; _acceptChat(); - // Use timer to accept the chat. To handle problematic network connection - /* _acceptTimer = new Timer.periodic( - new Duration(seconds: 10), (Timer timer){ - if (!_isOwnerOfChat)_acceptChat(); - else _cancelAccept(); - }); - */ }, )), new IconButton( diff --git a/lib/pages/lists/chat_list_bot.dart b/lib/pages/lists/chat_list_bot.dart new file mode 100644 index 0000000..09d856c --- /dev/null +++ b/lib/pages/lists/chat_list_bot.dart @@ -0,0 +1,232 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:livehelp/bloc/bloc.dart'; + +import 'package:livehelp/model/model.dart'; +import 'package:livehelp/services/server_repository.dart'; +import 'package:livehelp/widget/widget.dart'; +import 'package:livehelp/utils/utils.dart'; + +import 'package:livehelp/utils/routes.dart' as LHCRouter; + +class BotListWidget extends StatefulWidget { + final List listOfServers; + final VoidCallback refreshList; + final Function(Server, Chat) callbackCloseChat; + final Function(Server, Chat) callBackDeleteChat; + + BotListWidget( + {Key key, + this.listOfServers, + this.refreshList, + @required this.callbackCloseChat, + @required this.callBackDeleteChat}) + : super(key: key); + + @override + _BotListWidgetState createState() => new _BotListWidgetState(); +} + +class _BotListWidgetState extends State { + ServerRepository _serverRepository; + + @override + void initState() { + super.initState(); + _serverRepository = context.repository(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + if (state is ChatslistInitial) { + return Center(child: CircularProgressIndicator()); + } + + if (state is ChatListLoaded) { + if (state.isLoading) { + return Center( + child: CircularProgressIndicator(), + ); + } else { + return ListView.builder( + itemCount: state.botChatList.length, + itemBuilder: (BuildContext context, int index) { + if (state.botChatList.isNotEmpty) { + Chat chat = state.botChatList[index]; + Server server = widget.listOfServers.firstWhere( + (srvr) => srvr.id == chat.serverid, + orElse: () => null); + + return server == null + ? Text("No server found") + : new GestureDetector( + child: new ChatItemWidget( + server: server, + chat: chat, + menuBuilder: _itemMenuBuilder(), + onMenuSelected: (selectedOption) { + onItemSelected( + context, server, chat, selectedOption); + }, + ), + onTap: () { + /* var route = new FadeRoute( + settings: + new RouteSettings(name: AppRoutes.chatPage), + builder: (BuildContext context) => ChatPage( + server: server, + chat: chat, + isNewChat: false, + refreshList: widget.refreshList, + ), + ); */ + // RouteArguments is used to track chatpage widget + // foreground state + final routeArgs = RouteArguments(chatId: chat.id); + final routeSettings = RouteSettings( + name: AppRoutes.chatPage, arguments: routeArgs); + var route = LHCRouter.Router.generateRouteChatPage( + routeSettings, + chat, + server, + true, + widget.refreshList); + Navigator.of(context).push(route); + }, + ); + } else + return Container(); + }); + } + } + + if (state is ChatListLoadError) { + return ErrorReloadButton( + child: Text("An error occurred: ${state.message}"), + actionText: 'Reload', + onButtonPress: () { + context.bloc().add(ChatListInitialise()); + }, + ); + } + return ListView.builder( + itemCount: 1, + itemBuilder: (BuildContext context, int index) { + return Text("No list available"); + }); + }); + } + + List> _itemMenuBuilder() { + return >[ + const PopupMenuItem( + value: ChatItemMenuOption.CLOSE, + child: const Text('Close'), + ), + const PopupMenuItem( + value: ChatItemMenuOption.REJECT, + child: const Text('Delete'), + ), + const PopupMenuItem( + value: ChatItemMenuOption.TRANSFER, + child: const Text('Transfer'), + ), + ]; + } + + void onItemSelected( + BuildContext ctx, Server srv, Chat chat, ChatItemMenuOption result) { + switch (result) { + case ChatItemMenuOption.CLOSE: + widget.callbackCloseChat(srv, chat); + break; + case ChatItemMenuOption.REJECT: + widget.callBackDeleteChat(srv, chat); + break; + case ChatItemMenuOption.TRANSFER: + // widget.loadingState(true); + _showOperatorList(ctx, srv, chat); + //_getOperatorList(ctx,srv,chat); + break; + default: + break; + } + } + + Future> _getOperatorList( + BuildContext context, Server srvr, Chat chat) async { + return await _serverRepository.getOperatorsList(srvr); + } + + void _showOperatorList(BuildContext context, Server srvr, Chat chat) { + var futureBuilder = new FutureBuilder( + future: _getOperatorList(context, srvr, chat), + builder: (BuildContext context, AsyncSnapshot snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.none: + case ConnectionState.waiting: + return new Text('loading...'); + default: + if (snapshot.hasError) + return new Text('Error: ${snapshot.error}'); + else + return createListView(context, snapshot, srvr, chat); + } + }, + ); + + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return new Container( + child: new Padding( + padding: const EdgeInsets.all(4.0), + child: new Column( + mainAxisSize: MainAxisSize.max, + children: [ + new Text( + "Select online operator", + style: new TextStyle( + fontWeight: FontWeight.bold, fontSize: 16.0), + ), + new Divider(), + Expanded( + child: futureBuilder, + ) + ], + ), + )); + }); + } + + Future _transferToUser(Server srvr, Chat chat, int userid) async { + return _serverRepository.transferChatUser(srvr, chat, userid); + } + + Widget createListView( + BuildContext context, AsyncSnapshot snapshot, Server srvr, Chat chat) { + List listOP = snapshot.data; + + return listOP != null + ? new ListView.builder( + reverse: false, + padding: new EdgeInsets.all(6.0), + itemCount: listOP.length, + itemBuilder: (_, int index) { + Map operator = listOP[index]; + return new ListTile( + title: new Text( + 'Name: ${operator["name"]} ${operator["surname"]}'), + subtitle: new Text('Title: ${operator["job_title"]}'), + onTap: () async { + await _transferToUser(srvr, chat, int.parse(operator['id'])); + Navigator.of(context).pop(); + }, + ); + }, + ) + : new Text('No online operator found!'); + } +} diff --git a/lib/pages/lists/chat_list_subject.dart b/lib/pages/lists/chat_list_subject.dart new file mode 100644 index 0000000..d5296d2 --- /dev/null +++ b/lib/pages/lists/chat_list_subject.dart @@ -0,0 +1,232 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:livehelp/bloc/bloc.dart'; + +import 'package:livehelp/model/model.dart'; +import 'package:livehelp/services/server_repository.dart'; +import 'package:livehelp/widget/widget.dart'; +import 'package:livehelp/utils/utils.dart'; + +import 'package:livehelp/utils/routes.dart' as LHCRouter; + +class SubjectListWidget extends StatefulWidget { + final List listOfServers; + final VoidCallback refreshList; + final Function(Server, Chat) callbackCloseChat; + final Function(Server, Chat) callBackDeleteChat; + + SubjectListWidget( + {Key key, + this.listOfServers, + this.refreshList, + @required this.callbackCloseChat, + @required this.callBackDeleteChat}) + : super(key: key); + + @override + _SubjectListWidgetState createState() => new _SubjectListWidgetState(); +} + +class _SubjectListWidgetState extends State { + ServerRepository _serverRepository; + + @override + void initState() { + super.initState(); + _serverRepository = context.repository(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + if (state is ChatslistInitial) { + return Center(child: CircularProgressIndicator()); + } + + if (state is ChatListLoaded) { + if (state.isLoading) { + return Center( + child: CircularProgressIndicator(), + ); + } else { + return ListView.builder( + itemCount: state.subjectChatList.length, + itemBuilder: (BuildContext context, int index) { + if (state.subjectChatList.isNotEmpty) { + Chat chat = state.subjectChatList[index]; + Server server = widget.listOfServers.firstWhere( + (srvr) => srvr.id == chat.serverid, + orElse: () => null); + + return server == null + ? Text("No server found") + : new GestureDetector( + child: new ChatItemWidget( + server: server, + chat: chat, + menuBuilder: _itemMenuBuilder(), + onMenuSelected: (selectedOption) { + onItemSelected( + context, server, chat, selectedOption); + }, + ), + onTap: () { + /* var route = new FadeRoute( + settings: + new RouteSettings(name: AppRoutes.chatPage), + builder: (BuildContext context) => ChatPage( + server: server, + chat: chat, + isNewChat: false, + refreshList: widget.refreshList, + ), + ); */ + // RouteArguments is used to track chatpage widget + // foreground state + final routeArgs = RouteArguments(chatId: chat.id); + final routeSettings = RouteSettings( + name: AppRoutes.chatPage, arguments: routeArgs); + var route = LHCRouter.Router.generateRouteChatPage( + routeSettings, + chat, + server, + true, + widget.refreshList); + Navigator.of(context).push(route); + }, + ); + } else + return Container(); + }); + } + } + + if (state is ChatListLoadError) { + return ErrorReloadButton( + child: Text("An error occurred: ${state.message}"), + actionText: 'Reload', + onButtonPress: () { + context.bloc().add(ChatListInitialise()); + }, + ); + } + return ListView.builder( + itemCount: 1, + itemBuilder: (BuildContext context, int index) { + return Text("No list available"); + }); + }); + } + + List> _itemMenuBuilder() { + return >[ + const PopupMenuItem( + value: ChatItemMenuOption.CLOSE, + child: const Text('Close'), + ), + const PopupMenuItem( + value: ChatItemMenuOption.REJECT, + child: const Text('Delete'), + ), + const PopupMenuItem( + value: ChatItemMenuOption.TRANSFER, + child: const Text('Transfer'), + ), + ]; + } + + void onItemSelected( + BuildContext ctx, Server srv, Chat chat, ChatItemMenuOption result) { + switch (result) { + case ChatItemMenuOption.CLOSE: + widget.callbackCloseChat(srv, chat); + break; + case ChatItemMenuOption.REJECT: + widget.callBackDeleteChat(srv, chat); + break; + case ChatItemMenuOption.TRANSFER: + // widget.loadingState(true); + _showOperatorList(ctx, srv, chat); + //_getOperatorList(ctx,srv,chat); + break; + default: + break; + } + } + + Future> _getOperatorList( + BuildContext context, Server srvr, Chat chat) async { + return await _serverRepository.getOperatorsList(srvr); + } + + void _showOperatorList(BuildContext context, Server srvr, Chat chat) { + var futureBuilder = new FutureBuilder( + future: _getOperatorList(context, srvr, chat), + builder: (BuildContext context, AsyncSnapshot snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.none: + case ConnectionState.waiting: + return new Text('loading...'); + default: + if (snapshot.hasError) + return new Text('Error: ${snapshot.error}'); + else + return createListView(context, snapshot, srvr, chat); + } + }, + ); + + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return new Container( + child: new Padding( + padding: const EdgeInsets.all(4.0), + child: new Column( + mainAxisSize: MainAxisSize.max, + children: [ + new Text( + "Select online operator", + style: new TextStyle( + fontWeight: FontWeight.bold, fontSize: 16.0), + ), + new Divider(), + Expanded( + child: futureBuilder, + ) + ], + ), + )); + }); + } + + Future _transferToUser(Server srvr, Chat chat, int userid) async { + return _serverRepository.transferChatUser(srvr, chat, userid); + } + + Widget createListView( + BuildContext context, AsyncSnapshot snapshot, Server srvr, Chat chat) { + List listOP = snapshot.data; + + return listOP != null + ? new ListView.builder( + reverse: false, + padding: new EdgeInsets.all(6.0), + itemCount: listOP.length, + itemBuilder: (_, int index) { + Map operator = listOP[index]; + return new ListTile( + title: new Text( + 'Name: ${operator["name"]} ${operator["surname"]}'), + subtitle: new Text('Title: ${operator["job_title"]}'), + onTap: () async { + await _transferToUser(srvr, chat, int.parse(operator['id'])); + Navigator.of(context).pop(); + }, + ); + }, + ) + : new Text('No online operator found!'); + } +} diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index 285f77e..b8007d2 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -45,6 +45,8 @@ class _MainPageState extends State List pendingChatStore = new List(); List transferChatStore = new List(); List closedChatStore = new List(); + List botChatStore = new List(); + List subjectChatStore = new List(); List operatorsStore = new List(); Timer _timerChatList; @@ -184,6 +186,40 @@ class _MainPageState extends State ); }), ), + Tab( + child: BlocBuilder( + builder: (context, state) { + if (state is ChatListLoaded) { + return ChatNumberIndcator( + title: "Bot", + offstage: state.botChatList.length == 0, + number: state.botChatList.length.toString(), + ); + } + return ChatNumberIndcator( + title: "Bot", + offstage: true, + number: "0", + ); + }), + ), + Tab( + child: BlocBuilder( + builder: (context, state) { + if (state is ChatListLoaded) { + return ChatNumberIndcator( + title: "Subject", + offstage: state.subjectChatList.length == 0, + number: state.subjectChatList.length.toString(), + ); + } + return ChatNumberIndcator( + title: "Subject", + offstage: true, + number: "0", + ); + }), + ), Tab( child: BlocBuilder( builder: (context, state) { @@ -240,7 +276,7 @@ class _MainPageState extends State builder: (context, state) { if (state is ChatListLoaded) { return ChatNumberIndcator( - title: "Ops", + title: "Operators", offstage: state.operatorsChatList.length == 0, number: state.operatorsChatList.length.toString(), ); @@ -267,6 +303,30 @@ class _MainPageState extends State .add(DeleteChatMainPage(server: server, chat: chat)); }, ), + BotListWidget( + listOfServers: listServers, + refreshList: _loadChatList, + callbackCloseChat: (server, chat) { + _chatListBloc + .add(CloseChatMainPage(server: server, chat: chat)); + }, + callBackDeleteChat: (server, chat) { + _chatListBloc + .add(DeleteChatMainPage(server: server, chat: chat)); + }, + ), + SubjectListWidget( + listOfServers: listServers, + refreshList: _loadChatList, + callbackCloseChat: (server, chat) { + _chatListBloc + .add(CloseChatMainPage(server: server, chat: chat)); + }, + callBackDeleteChat: (server, chat) { + _chatListBloc + .add(DeleteChatMainPage(server: server, chat: chat)); + }, + ), PendingListWidget( listOfServers: listServers, refreshList: _loadChatList, @@ -616,7 +676,10 @@ class _MainPageState extends State notification.chat, notification.server, isNewChat, _loadChatList); if (notification.type == NotificationType.NEW_MESSAGE || - notification.type == NotificationType.UNREAD) { + notification.type == NotificationType.UNREAD || + notification.type == NotificationType.SUBJECT + ) { + Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.home)); SchedulerBinding.instance.addPostFrameCallback((_) async { Navigator.of(context).pushRouteIfNotCurrent(routeChat); }); diff --git a/lib/pages/pages.dart b/lib/pages/pages.dart index a4c84d9..1c9d9f1 100644 --- a/lib/pages/pages.dart +++ b/lib/pages/pages.dart @@ -10,3 +10,5 @@ export 'lists/chat_list_active.dart'; export 'lists/chat_list_pending.dart'; export 'lists/chat_list_transferred.dart'; export 'lists/chat_list_closed.dart'; +export 'lists/chat_list_bot.dart'; +export 'lists/chat_list_subject.dart'; diff --git a/lib/services/server_api_client.dart b/lib/services/server_api_client.dart index cadc633..e13a811 100644 --- a/lib/services/server_api_client.dart +++ b/lib/services/server_api_client.dart @@ -214,6 +214,30 @@ class ServerApiClient { } else server.clearList('closed'); + if (response.body['bot_chats'] != null) { + int botSize = response.body['bot_chats']['size']; + if (botSize > 0) { + Map botJson = response.body['bot_chats']['rows']; + List newBotList = + chatListToMap(server.id, botJson.values.toList()); + if (newBotList != null && newBotList.length > 0) + server.addChatsToList(newBotList, 'bot'); + } else + server.clearList('bot'); + } + + if (response.body['subject_chats'] != null) { + int subjectSize = response.body['subject_chats']['size']; + if (subjectSize > 0) { + Map subjectJson = response.body['subject_chats']['rows']; + List newSubjectList = + chatListToMap(server.id, subjectJson.values.toList()); + if (newSubjectList != null && newSubjectList.length > 0) + server.addChatsToList(newSubjectList, 'subject'); + } else + server.clearList('subject'); + } + if (response.body['operators_chats'] != null) { int operatorsSize = response.body['operators_chats']['size']; if (operatorsSize > 0) { diff --git a/lib/utils/local_notification.dart b/lib/utils/local_notification.dart index 7140b1d..1e621d9 100644 --- a/lib/utils/local_notification.dart +++ b/lib/utils/local_notification.dart @@ -56,6 +56,15 @@ class LocalNotificationPlugin { playSound: true, ); + const AndroidNotificationChannel subjectMessage = AndroidNotificationChannel( + 'com.livehelperchat.chat.channel.SUBJECT', // id + 'New subject', // title + 'New subject notifications while app is in the background', // description + importance: Importance.High, + enableVibration: true, + playSound: true, + ); + flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() @@ -70,6 +79,11 @@ class LocalNotificationPlugin { .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(channelGroupMessage); + + flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(subjectMessage); } initializePlatformSpecifics(); @@ -112,6 +126,12 @@ class LocalNotificationPlugin { description: "New group messages notifications while app is open", number: 4444); + static final NotificationChannel channelSubject = NotificationChannel( + id: "com.livehelperchat.chat.channel.SUBJECT", + name: "New subject (open app)", + description: "New subject while app is open", + number: 5555); + initializePlatformSpecifics() { var initializationSettingsAndroid = AndroidInitializationSettings('icon'); var initializationSettingsIOS = IOSInitializationSettings( @@ -183,6 +203,9 @@ class LocalNotificationPlugin { case NotificationType.UNREAD: channel = channelUnreadMsg; break; + case NotificationType.SUBJECT: + channel = channelSubject; + break; default: break; } @@ -311,4 +334,4 @@ class ReceivedNotification { } } -enum NotificationType { INFO, NEW_MESSAGE, PENDING, UNREAD, NEW_GROUP_MESSAGE } +enum NotificationType { INFO, NEW_MESSAGE, PENDING, UNREAD, NEW_GROUP_MESSAGE, SUBJECT} diff --git a/lib/widget/chat_item_widget.dart b/lib/widget/chat_item_widget.dart index 106cade..c178659 100644 --- a/lib/widget/chat_item_widget.dart +++ b/lib/widget/chat_item_widget.dart @@ -5,6 +5,9 @@ import 'package:flutter/material.dart'; import 'package:livehelp/model/model.dart'; import 'package:livehelp/utils/utils.dart'; +import 'dart:developer' as developer; +import 'dart:convert'; + class ChatItemWidget extends StatelessWidget { ChatItemWidget( {Key key, @@ -37,6 +40,63 @@ class ChatItemWidget extends StatelessWidget { return menuBuilder; }); + List subjects = []; + + if (chat.subject_front != "") { + List subjectsList = chat.subject_front.split("||"); + subjectsList.forEach((element) { + subjects.add(Container( + color: Colors.transparent, + margin: const EdgeInsets.only(right: 4.0,), + child: new Container( + decoration: new BoxDecoration( + color: Colors.lightGreen, + borderRadius: new BorderRadius.only( + topLeft: const Radius.circular(5.0), + topRight: const Radius.circular(5.0), + bottomRight: const Radius.circular(5.0), + bottomLeft: const Radius.circular(5.0), + ) + ), + child: Container( + margin: const EdgeInsets.all(4.0), + child: new Center( + child: new Text(element, style: TextStyle(fontSize: 12.0, color: Colors.white)), + ) + ) + ), + )); + }); + + } + + if (chat.aicon_front != "") { + List aicons = chat.aicon_front.split("||"); + aicons.forEach((element) { + subjects.add(Container( + color: Colors.transparent, + margin: const EdgeInsets.only(right: 4.0,), + child: new Container( + decoration: new BoxDecoration( + color: Colors.grey, + borderRadius: new BorderRadius.only( + topLeft: const Radius.circular(5.0), + topRight: const Radius.circular(5.0), + bottomRight: const Radius.circular(5.0), + bottomLeft: const Radius.circular(5.0), + ) + ), + child: Container( + margin: const EdgeInsets.all(4.0), + child: new Center( + child: new Text(element, style: TextStyle(fontSize: 12.0, color: Colors.white)), + ) + ) + ), + )); + }); + } + return new SizedBox( height: 160.0, child: new Container( @@ -82,14 +142,29 @@ class ChatItemWidget extends StatelessWidget { fontSize: 18.0, fontWeight: FontWeight.bold)), ), - new AnimatedDefaultTextStyle( - duration: kThemeChangeDuration, - child: Text(chat.country_name ?? ""), - textAlign: TextAlign.left, - style: styling.copyWith( - color: Colors.indigo.shade400, - fontSize: 14.0, - )), + new Row( + children: [ + AnimatedDefaultTextStyle( + duration: kThemeChangeDuration, + child: Text(chat.country_name ?? ""), + textAlign: TextAlign.left, + style: styling.copyWith( + color: Colors.grey.shade400, + fontSize: 14.0, + )), + new Expanded( + child: new Container( + height: 20.0, + child: ListView( + scrollDirection: Axis.horizontal, + children: subjects + ), + ), + ) + ] + ), + + ])), Align( alignment: Alignment.topRight, @@ -132,7 +207,7 @@ class ChatItemWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ new Icon( - Icons.people, + (chat.status == 5 && chat.user_id == null ? Icons.android : Icons.people), size: 14, color: Theme.of(context).primaryColor, ), diff --git a/lib/widget/chat_number_indicator.dart b/lib/widget/chat_number_indicator.dart index 63af435..ba166b3 100644 --- a/lib/widget/chat_number_indicator.dart +++ b/lib/widget/chat_number_indicator.dart @@ -10,14 +10,36 @@ class ChatNumberIndcator extends StatelessWidget{ @override Widget build(BuildContext context) { + + Map icons = { + 'Operators' : Icons.support_agent, + 'Bot' : Icons.android, + 'Transfer' : Icons.transfer_within_a_station, + 'Subject' : Icons.label, + }; + IconData icon = icons.containsKey(title) ? icons[title] : Icons.message; + + Map colors = { + 'Operators' : Colors.green.shade400, + 'Bot' : Colors.green.shade400, + 'Transfer' : Colors.green.shade400, + 'Subject' :Colors.green.shade400, + 'Pending' :Colors.yellow.shade400, + 'Closed' :Colors.red.shade400, + }; + + Color colorIcon = colors.containsKey(title) ? colors[title] : Colors.green.shade400; + return Stack( children: [ Align( alignment: Alignment.bottomCenter, - child:Text(title,textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12.0),), + child: new Icon( + icon, + size: 14, + color: colorIcon + ), ), Offstage( offstage: offstage, diff --git a/pubspec.lock b/pubspec.lock old mode 100755 new mode 100644 diff --git a/pubspec.yaml b/pubspec.yaml old mode 100755 new mode 100644 index 1ca409b..2d3abd9 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: livehelp description: A messenger for Live Helper Chat -version: 1.0.13+13 +version: 1.0.14+14 dependencies: flutter: sdk: flutter