123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- import 'package:cloud_firestore/cloud_firestore.dart';
- import 'package:easy_localization/easy_localization.dart';
- import 'package:easy_refresh/easy_refresh.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:provider/provider.dart';
- import 'package:selectable_autolink_text/selectable_autolink_text.dart';
- import 'package:telnow_mobile_new/src/api/api_auth_provider.dart';
- import 'package:telnow_mobile_new/src/layouts/functions/message.dart';
- import 'package:telnow_mobile_new/src/layouts/components/template.dart';
- import 'package:telnow_mobile_new/src/utils/U.dart';
- import 'package:telnow_mobile_new/src/utils/provider.dart';
- import 'package:translator/translator.dart';
- import 'package:url_launcher/url_launcher.dart';
- class WebMessageListPage extends StatefulWidget {
- Map<String, dynamic>? user;
- WebMessageListPage(this.user, {super.key});
- @override
- State<WebMessageListPage> createState() => _WebMessageListPageState();
- }
- class _WebMessageListPageState extends State<WebMessageListPage> {
- final MessageFunction messageFunc = MessageFunction();
- final ApiAuthProvider apiAuthProvider = ApiAuthProvider();
- final translator = GoogleTranslator();
- String? idChat;
- String tenant = '';
- String opponent = '';
- ScrollController scrollController = ScrollController();
- List messageData = [];
- int page = 0;
- bool isAfterLoad = false;
- String username = '';
- bool isLoad = false;
- bool stopLoad = false;
- bool scrollBottom = false;
- bool isReverse = false;
- bool askBroadcast = false;
- @override
- void initState() {
- Provider.of<MessageModule>(context, listen: false).reset();
- if(widget.user == null){
- messageFunc.getUser(context);
- }
- else{
- Provider.of<MessageModule>(context, listen: false).setUser(widget.user!);
- messageFunc.getDataMessages(context);
- }
- // TODO: implement initState
- super.initState();
- }
- selectMessage(data, isMe) async{
- if(idChat != data['chatId']){
- setState(() {
- idChat = data['chatId'];
- username = Provider.of<MessageModule>(context, listen: false).user()['userId'];
- askBroadcast = isMe;
- opponent = isMe ? data['recipientName'] : data['senderName'];
- tenant = data['recipient'];
- messageData = [];
- page = 0;
- isAfterLoad = false;
- isLoad = false;
- stopLoad = false;
- scrollBottom = false;
- isReverse = false;
- });
- scrollController.addListener(() => scrollListener());
- if (!isMe) {
- await apiAuthProvider.patchData('/api/messages/' + data['id'].toString(), {'readStatus': 'READ'}, context);
- }
- getMessage();
- getCollectionData();
- }
- }
- getMessage() async {
- String url = 'myMessages/$idChat';
- String filter = '{"f":["uniqueId","LIKE","%-%"]}';
- if (askBroadcast) {
- url = 'myBroadcast';
- filter = '{"f":["1","EQ","1"]}';
- }
- if (!isLoad && !stopLoad) {
- setState(() => isLoad = true);
- var mymess = await apiAuthProvider.getData('/api/messages/search/$url', {'isPaged': 'true', 'page': page.toString(), 'size': '20', 'filter': filter, 'tenant': tenant}, context);
- if (mymess.containsKey('_embedded') && mounted) {
- List data = mymess['_embedded']['myMessages'];
- for (int i = 0; i < data.length; i++) {
- if (username == data[i]['from']['user']) {
- data[i]['selected'] = false;
- }
- else{
- if(U.autoTranslate()){
- data[i]['translate'] = '';
- }
- }
- setState(() => messageData.insert(0, data[i]));
- }
- setState(() {
- if (page == 0) {
- scrollBottom = true;
- }
- isLoad = false;
- page++;
- if (messageData.length >= mymess['page']['totalElements']) {
- stopLoad = true;
- }
- });
- if(U.autoTranslate()){
- translateMessage();
- }
- } else {
- setState(() {
- isLoad = false;
- stopLoad = true;
- });
- }
- }
- }
- getCollectionData() {
- FirebaseFirestore.instance.collection("tmMessages").doc('messages').collection(idChat!).snapshots().listen((querySnapshot) {
- setState(() {
- querySnapshot.docChanges.forEach((result) async {
- var data = result.doc.data();
- if (result.type == DocumentChangeType.added && isAfterLoad) {
- if ((username == data!['from']['user'] && messageData.where((element) => element['uniqueId'] == data['uniqueId']).length > 0) || (username != data['from']['user'] && data['readStatus'] == 'DELETED')) {
- int index = messageData.indexWhere((element) => element['uniqueId'] == data['uniqueId']);
- messageData[index]['read'] = data['read'];
- messageData[index]['readStatus'] = data['readStatus'];
- messageData[index]['imageUrl'] = data['imageUrl'];
- } else {
- if(U.autoTranslate()){
- var locale = context.locale.toString() == 'zh' ? 'zh-cn' : context.locale.toString();
- var translate = await translator.translate(data['msg']??'', to: locale);
- data['translate'] = translate.text;
- }
- messageData.add(data);
- }
- } else if (result.type == DocumentChangeType.modified && isAfterLoad) {
- // print("modified");
- if (messageData.where((element) => element['uniqueId'] == data!['uniqueId']).length > 0) {
- int index = messageData.indexWhere((element) => element['uniqueId'] == data!['uniqueId']);
- messageData[index]['read'] = data!['read'];
- messageData[index]['readStatus'] = data['readStatus'];
- }
- } else if (result.type == DocumentChangeType.removed && isAfterLoad) {
- // print("removed");
- }
- });
- isAfterLoad = true;
- });
- });
- }
- translateMessage(){
- var locale = context.locale.toString() == 'zh' ? 'zh-cn' : context.locale.toString();
- messageData.forEach((element) async{
- if (username != element['from']['user'] && element['translate'] == '') {
- var translate = await translator.translate(element['msg']??'', to: locale);
- setState(() {
- element['translate'] = translate.text;
- });
- }
- });
- }
- deleteCollection() {
- try{
- FirebaseFirestore.instance.collection("tmMessages").doc('messages').collection(idChat!).get().then((value) {
- for (DocumentSnapshot ds in value.docs) {
- ds.reference.delete();
- }
- });
- } catch(e){}
- }
- scrollToBottom() {
- scrollController.animateTo(scrollController.position.minScrollExtent, duration: Duration(milliseconds: 1), curve: Curves.decelerate);
- scrollBottom = false;
- }
- scrollListener() {
- if (scrollController.offset >= scrollController.position.maxScrollExtent) {
- // print('loadMessage');
- getMessage();
- }
- }
- @override
- Widget build(BuildContext context) {
- if (messageData.length > 0) {
- if (scrollController.position.maxScrollExtent > 0 && !isReverse) {
- setState(() => isReverse = true);
- }
- if (isAfterLoad && scrollBottom && isReverse) {
- scrollToBottom();
- }
- }
- return Scaffold(
- backgroundColor: backgroundColor,
- appBar: PreferredSize(preferredSize: Size.fromHeight(0), child: AppBar(elevation: 0, backgroundColor: primaryColor)),
- body: Column(
- children: [
- Container(
- padding: EdgeInsets.symmetric(vertical: 25, horizontal: 100),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text('message'.tr(), style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis),
- GestureDetector(
- child: Text('buttonBack'.tr(), style: TextStyle(color: primaryColor, fontSize: 14)),
- onTap: (){
- deleteCollection();
- navigateBack(context);
- },
- )
- ],
- ),
- ),
- divider(),
- Expanded(
- child: Container(
- width: double.infinity, height: double.infinity,
- padding: EdgeInsets.symmetric(vertical: 25, horizontal: 100),
- child: Row(
- children: [
- Expanded(
- child: Stack(
- children: [
- Container(
- width: double.infinity, height: double.infinity,
- child: EasyRefresh(
- header: MaterialHeader(clamping: true, color: primaryColor),
- onRefresh: () => messageFunc.onRefresh(context),
- child: Provider.of<MessageModule>(context).data().isEmpty && !Provider.of<MessageModule>(context).firstLoad()? loadingTemplate(() {},) : Provider.of<MessageModule>(context).data().isEmpty ? Center(
- child: Text('noMessageText').tr(),
- ) : SingleChildScrollView(
- padding: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
- child: Column(
- mainAxisSize: MainAxisSize.max,
- children: List.generate(Provider.of<MessageModule>(context).data().length, (i) {
- bool _isMe = Provider.of<MessageModule>(context, listen: false).data()[i]['userId'] == Provider.of<MessageModule>(context, listen: false).user()['userId'] ? true : false;
- return GestureDetector(
- child: Container(
- padding: EdgeInsets.all(10),
- child: Row(
- children: [
- Provider.of<MessageModule>(context).data()[i]['senderAvatar'] != null && Provider.of<MessageModule>(context).data()[i]['senderAvatar'] != '' ? CircleAvatar(
- backgroundImage: NetworkImage(Provider.of<MessageModule>(context).data()[i]['senderAvatar']), radius: 24,
- ) : Container(
- height: 48, width: 48,
- child: Center(child: Text(_isMe ? Provider.of<MessageModule>(context).data()[i]['recipientName'] == "all_informants" ? "allInformants".tr()[0] : Provider.of<MessageModule>(context).data()[i]['recipientName'][0] : Provider.of<MessageModule>(context).data()[i]['senderName'][0], style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 18))),
- decoration: BoxDecoration(shape: BoxShape.circle, color: Color(U.getColor(_isMe ? Provider.of<MessageModule>(context).data()[i]['recipient'] : Provider.of<MessageModule>(context).data()[i]['recipientId']))),
- ),
- SizedBox(width: 20),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(_isMe ? Provider.of<MessageModule>(context).data()[i]['recipientName'] == "all_informants" ? "allInformants".tr() : Provider.of<MessageModule>(context).data()[i]['recipientName'] : Provider.of<MessageModule>(context).data()[i]['senderName']),
- SizedBox(height: 5),
- Row(
- children: [
- _isMe ? Padding(
- padding: EdgeInsets.only(right: 5),
- child: Icon(Icons.done_all, size: 18, color: Colors.blueGrey),
- ) : Container(),
- Provider.of<MessageModule>(context).data()[i]['isImage'] ? Align(
- alignment: Alignment.centerLeft,
- child: Row(
- children: [
- Icon(Provider.of<MessageModule>(context).data()[i]['fileType'] == 'pdf' ? Icons.picture_as_pdf : Icons.image, color: Colors.black45, size: 16),
- SizedBox(width: 6),
- Provider.of<MessageModule>(context).data()[i]['lastText'] == '' ? Provider.of<MessageModule>(context).data()[i]['fileType'] == 'pdf' ? Text('pdfFile'.tr()) : Text('photo'.tr()) : Container()
- ],
- ),
- ) : Container(),
- Expanded(
- child: Text(Provider.of<MessageModule>(context).data()[i]['lastText'], style: TextStyle(fontSize: 13), maxLines: 1, overflow: TextOverflow.ellipsis),
- )
- ],
- )
- ],
- ),
- ),
- SizedBox(width: 20),
- Column(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- Text(messageFunc.timeSet(Provider.of<MessageModule>(context).data()[i]['lastDateTimeSend']), style: TextStyle(fontSize: 11, color: Provider.of<MessageModule>(context).data()[i]['lastReadStatus'] == 'UNREAD' && !_isMe ? Colors.green : Colors.black45)),
- SizedBox(height: 4),
- Icon(Icons.circle, color: Colors.green.withValues(alpha: Provider.of<MessageModule>(context).data()[i]['lastReadStatus'] == 'UNREAD' && !_isMe ? 1 : 0))
- ],
- )
- ],
- ),
- decoration: BoxDecoration(color: idChat != null && idChat == Provider.of<MessageModule>(context, listen: false).data()[i]['chatId'] ? Color(0xff26DA17).withValues(alpha: 0.2) : Colors.white, borderRadius: BorderRadius.all(Radius.circular(12))),
- ),
- onTap: ()=>selectMessage(Provider.of<MessageModule>(context, listen: false).data()[i], _isMe),
- );
- }),
- ),
- ),
- ),
- decoration: BoxDecoration(color: Colors.white, border: Border.all(color: textColor.withValues(alpha: 0.15)), borderRadius: BorderRadius.all(Radius.circular(12))),
- ),
- Provider.of<MessageModule>(context).user().isNotEmpty && Provider.of<MessageModule>(context).user()['canSendMessage'] ? Align(
- alignment: Alignment.bottomRight,
- child: GestureDetector(
- child: Container(
- margin: EdgeInsets.only(right: 20, bottom: 20),
- width: 50, height: 50, alignment: Alignment.center,
- child: U.iconsax('message', color: Colors.white),
- decoration: BoxDecoration(
- color: primaryColor,
- borderRadius: BorderRadius.all(Radius.circular(50))
- ),
- ),
- onTap: ()=>messageFunc.createMessage(context),
- ),
- ) : Container()
- ],
- ),
- ),
- SizedBox(width: 30),
- Expanded(
- child: Container(
- child: idChat != null ? Column(
- children: [
- Container(
- width: double.infinity, padding: EdgeInsets.all(20),
- child: Text(opponent != 'all_informants' ? opponent : "allInformants".tr(), style: TextStyle(color: textColor, fontSize: 16)),
- ),
- divider(),
- Expanded(
- child: messageData.isEmpty && !isAfterLoad ? loadingTemplate(() {},) : SingleChildScrollView(
- padding: const EdgeInsets.fromLTRB(10, 10, 10, 7),
- controller: scrollController,
- reverse: isReverse,
- child: Column(
- children: List.generate(messageData.length, (i) {
- bool hideDate = i == 0 ? false : checkDate(messageData[i]['datetime'], messageData[i - 1]['datetime']);
- bool isNip = i == 0 ? true : !hideDate ? true : messageData[i]['from']['user'] == messageData[i - 1]['from']['user'] ? false : true;
- return Column(
- children: [
- !hideDate ? Center(
- child: Padding(
- padding: const EdgeInsets.all(10),
- child: bubble_chat(Text(convertDate(messageData[i]['datetime'], context.locale.toString()), textAlign: TextAlign.center, style: TextStyle(fontSize: 12)), null, false, false),
- ),
- ) : Container(),
- Builder(builder: (context) {
- var isMe = messageData[i]['from']['user'] == Provider.of<MessageModule>(context, listen: false).user()['userId'];
- var isFile = messageData[i]['imageUrl'] != null && messageData[i]['imageUrl'] != '' && messageData[i]['readStatus'] != 'DELETED';
- var isPdf = isFile && messageData[i]['imageUrl'].split('.').last == 'pdf';
- var isImg = isFile && !isPdf;
- var isTranslate = U.autoTranslate() && !isMe && messageData[i]['msg'] != messageData[i]['translate'];
- var wdg = messageData[i]['readStatus'] == 'DELETED' ? Text('deletedMessage'.tr(), style: TextStyle(fontSize: 14, color: Colors.black54, fontStyle: FontStyle.italic)) : Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- isPdf ? GestureDetector(
- child: Container(
- child: Builder(builder: (context) {
- var pdfContainer = Container(
- padding: EdgeInsets.all(6),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(Icons.picture_as_pdf),
- SizedBox(width: 6),
- Flexible(child: Text(getImageName(messageData[i]['imageUrl'])))
- ],
- ),
- decoration: BoxDecoration(color: Colors.black12.withValues(alpha: 0.1), borderRadius: BorderRadius.all(Radius.circular(4))),
- );
- return pdfContainer;
- }),
- ),
- onTap: () async {
- await launchUrl( messageData[i]['imageUrl']);
- },
- ) : isImg ? GestureDetector(
- child: Container(
- margin: const EdgeInsets.only(bottom: 5),
- child: Builder(builder: (context) {
- return Image.network(messageData[i]['imageUrl'], fit: BoxFit.cover, width: 200, height: 200);
- })
- ),
- onTap: () => navigateTo(context, PhotoPreview(opponent, messageData[i]['imageUrl'], true)),
- ) : SizedBox(height: 1),
- messageData[i]['msg'] != null && messageData[i]['msg'] != '' ? Column(
- crossAxisAlignment: !isMe?CrossAxisAlignment.start:CrossAxisAlignment.end,
- children: [
- SizedBox(height: 4),
- SelectableAutoLinkText(
- messageData[i]['msg'],
- style: TextStyle(fontSize: 14, color: Colors.black),
- linkStyle: TextStyle(color: Colors.blueAccent),
- highlightedLinkStyle: TextStyle(color: Colors.blueAccent, backgroundColor: Colors.blueAccent.withAlpha(0x33)),
- onTap: (link) async{
- if (await canLaunchUrl(Uri.parse(link))) {
- await launchUrl(Uri.parse(link));
- }
- },
- onLongPress: (link) {
- Clipboard.setData(new ClipboardData(text: link)).then((value){
- showSuccess('link_copied'.tr(), context);
- });
- },
- enableInteractiveSelection: false,
- ),
- isTranslate ? Container(
- margin: EdgeInsets.only(top: 2), decoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.2)))),
- child: Text(messageData[i]['translate']!=''?'(${messageData[i]['translate']})':'...', style: TextStyle(fontSize: 14, color: Colors.black.withValues(alpha: 0.65), fontStyle: FontStyle.italic)),
- ) : Container(),
- ],
- ) : Container()
- ],
- );
- var timeBubble = Positioned(
- bottom: 0, right: isNip ? 6 : 0,
- child: Text(DateFormat('HH:mm').format(DateTime.parse(messageData[i]['datetime'])), style: TextStyle(fontSize: 10, color: Colors.black45)),
- );
- var expander = Expanded(
- flex: 8,
- child: Column(
- children: [
- bubble_chat(wdg, timeBubble, isNip, isMe),
- SizedBox(height: 5),
- ],
- crossAxisAlignment: isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
- )
- );
- return isMe ? Row(
- children: [
- Expanded(flex: 2, child: Container()),
- expander,
- ],
- ) : Row(
- children: [
- expander,
- Expanded(flex: 2, child: Container()),
- ],
- );
- }),
- ],
- );
- }),
- ),
- ),
- )
- ],
- ) : Container(),
- decoration: BoxDecoration(color: Colors.white, border: Border.all(color: textColor.withValues(alpha: 0.15)), borderRadius: BorderRadius.all(Radius.circular(12))),
- ),
- )
- ],
- ),
- ),
- )
- ],
- ),
- );
- }
- getImageName(imageUrl) {
- var imgSplit = imageUrl.toString().split('/');
- return imgSplit[imgSplit.length - 1];
- }
- Widget bubble_chat(Widget child, Widget? time, bool isNip, bool isMe) {
- bool isDate = false;
- if (time == null) {
- isDate = true;
- time = Container();
- }
- Color color = isMe ? primaryColor.withValues(alpha: 0.3) : isDate ? Color(0xffD5F5FF) : Color(0xffECECEC);
- var clipPath = ClipPath(
- child: ConstrainedBox(
- constraints: BoxConstraints(minWidth: 50),
- child: Container(
- decoration: BoxDecoration(
- color: color,
- ),
- child: Padding(padding: EdgeInsets.all(8),
- child: isDate ? child : Stack(
- children: [
- Padding(
- padding: EdgeInsets.only(
- bottom: 15,
- right: isNip && isMe ? 6 : 0,
- left: isNip && !isMe ? 6 : 0,
- ),
- child: child,
- ),
- time
- ],
- ),
- ),
- ),
- ),
- clipper: isMe ? MyClipper(isNip) : YourClipper(isNip),
- );
- return Padding(
- padding: EdgeInsets.only(
- right: isNip && isMe ? 0 : 6,
- left: isNip && !isMe ? 0 : 6
- ),
- child: clipPath
- );
- }
- bool checkDate(date1, date2) {
- final dateToCheck1 = DateTime(DateTime.parse(date1).year, DateTime.parse(date1).month, DateTime.parse(date1).day);
- final dateToCheck2 = DateTime(DateTime.parse(date2).year, DateTime.parse(date2).month, DateTime.parse(date2).day);
- return dateToCheck1 == dateToCheck2 ? true : false;
- }
- String convertDate(date, locale) {
- final dateToCheck = DateTime.parse(date);
- final now = DateTime.now();
- final today = DateTime(now.year, now.month, now.day);
- final yesterday = DateTime(now.year, now.month, now.day - 1);
- final aDate = DateTime(dateToCheck.year, dateToCheck.month, dateToCheck.day);
- if (aDate == today) {
- return 'today'.tr();
- } else if (aDate == yesterday) {
- return 'yesterday'.tr();
- } else {
- return DateFormat('dd MMMM yyyy', locale).format(DateTime.parse(date));
- }
- }
- }
|