LCOV - code coverage report
Current view: top level - lib/widgets - messagelist.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 102 0.0 %
Date: 2024-10-28 21:12:34 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:cwtch/models/appstate.dart';
       2             : import 'package:cwtch/models/contact.dart';
       3             : import 'package:cwtch/models/message.dart';
       4             : import 'package:cwtch/models/messagecache.dart';
       5             : import 'package:cwtch/models/profile.dart';
       6             : import 'package:cwtch/widgets/messageloadingbubble.dart';
       7             : import 'package:flutter/material.dart';
       8             : import 'package:provider/provider.dart';
       9             : import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
      10             : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
      11             : import '../main.dart';
      12             : import '../settings.dart';
      13             : 
      14             : class MessageList extends StatefulWidget {
      15             :   ItemPositionsListener scrollListener;
      16           0 :   MessageList(this.scrollListener);
      17             : 
      18           0 :   @override
      19           0 :   _MessageListState createState() => _MessageListState();
      20             : }
      21             : 
      22             : class _MessageListState extends State<MessageList> {
      23           0 :   @override
      24             :   Widget build(BuildContext outerContext) {
      25             :     // On Android we can have unsynced messages at the front of the index from when the UI was asleep, if there are some, kick off sync of those first
      26           0 :     if (Provider.of<ContactInfoState>(context).messageCache.indexUnsynced != 0) {
      27           0 :       var conversationId = Provider.of<AppState>(outerContext, listen: false).selectedConversation!;
      28           0 :       MessageCache? cache = Provider.of<ProfileInfoState>(outerContext, listen: false).contactList.getContact(conversationId)?.messageCache;
      29           0 :       ByIndex(0).loadUnsynced(Provider.of<FlwtchState>(context, listen: false).cwtch, Provider.of<AppState>(outerContext, listen: false).selectedProfile!, conversationId, cache!);
      30             :     }
      31           0 :     if (Provider.of<ContactInfoState>(outerContext, listen: false).everFetched == false) {
      32           0 :       messageHandler(outerContext,
      33           0 :                      Provider.of<ProfileInfoState>(outerContext, listen: false).onion,
      34           0 :                      Provider.of<ContactInfoState>(outerContext, listen: false).identifier,
      35           0 :                      ByIndex(Provider.of<ContactInfoState>(outerContext, listen: false).uiLoadedMessages));
      36           0 :       Provider.of<ContactInfoState>(outerContext, listen: false).everFetched = true;
      37             :     }
      38           0 :     var initi = Provider.of<AppState>(outerContext, listen: false).initialScrollIndex;
      39           0 :     bool isP2P = !Provider.of<ContactInfoState>(context).isGroup;
      40           0 :     bool isGroupAndSyncing = Provider.of<ContactInfoState>(context).isGroup == true && Provider.of<ContactInfoState>(context).status == "Authenticated";
      41             : 
      42           0 :     bool preserveHistoryByDefault = Provider.of<Settings>(context, listen: false).preserveHistoryByDefault;
      43           0 :     bool showEphemeralWarning = (isP2P && (!preserveHistoryByDefault && Provider.of<ContactInfoState>(context).savePeerHistory != "SaveHistory"));
      44           0 :     bool showOfflineWarning = Provider.of<ContactInfoState>(context).isOnline() == false;
      45             :     bool showSyncing = isGroupAndSyncing;
      46             :     bool showMessageWarning = showEphemeralWarning || showOfflineWarning || showSyncing;
      47             :     // We used to only load historical messages when the conversation is with a p2p contact OR the conversation is a server and *not* syncing.
      48             :     // With the message cache in place this is no longer necessary
      49             :     bool loadMessages = true;
      50             : 
      51             :     // if it's been more than 2 minutes since we last clicked the button let users click the button again
      52             :     // OR if users have never clicked the button AND they appear offline, then they can click the button
      53             :     // NOTE: all these listeners are false...this is not ideal, but if they were true we would end up rebuilding the message view every tick (which would kill performance)
      54             :     // any significant changes in state e.g. peer offline or button clicks will trigger a rebuild anyway
      55           0 :     bool canReconnect = DateTime.now().difference(Provider.of<ContactInfoState>(context, listen: false).lastRetryTime).abs() > Duration(seconds: 30) ||
      56           0 :         (Provider.of<ProfileInfoState>(context, listen: false).appearOffline &&
      57           0 :             (Provider.of<ContactInfoState>(context, listen: false).lastRetryTime == Provider.of<ContactInfoState>(context, listen: false).loaded));
      58             : 
      59           0 :     var reconnectButton = Padding(
      60           0 :         padding: EdgeInsets.all(2),
      61             :         child: canReconnect
      62           0 :             ? Tooltip(
      63           0 :                 message: AppLocalizations.of(context)!.retryConnectionTooltip,
      64           0 :                 child: ElevatedButton(
      65           0 :                   style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
      66           0 :                   child: Text(AppLocalizations.of(context)!.retryConnection),
      67           0 :                   onPressed: () {
      68           0 :                     if (Provider.of<ContactInfoState>(context, listen: false).isGroup) {
      69           0 :                       Provider.of<FlwtchState>(context, listen: false)
      70           0 :                           .cwtch
      71           0 :                           .AttemptReconnectionServer(Provider.of<ProfileInfoState>(context, listen: false).onion, Provider.of<ContactInfoState>(context, listen: false).server!);
      72             :                     } else {
      73           0 :                       Provider.of<FlwtchState>(context, listen: false)
      74           0 :                           .cwtch
      75           0 :                           .AttemptReconnection(Provider.of<ProfileInfoState>(context, listen: false).onion, Provider.of<ContactInfoState>(context, listen: false).onion);
      76             :                     }
      77           0 :                     Provider.of<ContactInfoState>(context, listen: false).lastRetryTime = DateTime.now();
      78           0 :                     Provider.of<ContactInfoState>(context, listen: false).contactEvents.add(ContactEvent("Actively Retried Connection"));
      79           0 :                     setState(() {
      80             :                       // force update of this view...otherwise the button won't be removed fast enough...
      81             :                     });
      82             :                   },
      83             :                 ))
      84           0 :             : CircularProgressIndicator(color: Provider.of<Settings>(context).theme.hilightElementColor));
      85             : 
      86           0 :     return RepaintBoundary(
      87           0 :         child: Container(
      88           0 :             color: Provider.of<Settings>(context).theme.backgroundMainColor,
      89           0 :             child: Column(children: [
      90           0 :               Visibility(
      91             :                   visible: showMessageWarning,
      92           0 :                   child: Container(
      93           0 :                       padding: EdgeInsets.all(5.0),
      94           0 :                       color: Provider.of<Settings>(context).theme.backgroundHilightElementColor,
      95           0 :                       child: DefaultTextStyle(
      96           0 :                         style: TextStyle(color: Provider.of<Settings>(context).theme.hilightElementColor),
      97             :                         child: showSyncing
      98           0 :                             ? Text(AppLocalizations.of(context)!.serverNotSynced, textAlign: TextAlign.center)
      99             :                             : showOfflineWarning
     100           0 :                                 ? Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
     101           0 :                                     Text(
     102           0 :                                         Provider.of<ContactInfoState>(context).isGroup
     103           0 :                                             ? AppLocalizations.of(context)!.serverConnectivityDisconnected
     104           0 :                                             : AppLocalizations.of(context)!.peerOfflineMessage,
     105             :                                         textAlign: TextAlign.center),
     106             :                                     reconnectButton
     107             :                                   ])
     108             :                                 // Only show the ephemeral status for peer conversations, not for groups...
     109             :                                 : (showEphemeralWarning
     110           0 :                                     ? Text(AppLocalizations.of(context)!.chatHistoryDefault, textAlign: TextAlign.center)
     111             :                                     :
     112             :                                     // We are not allowed to put null here, so put an empty text widget
     113           0 :                                     Text("")),
     114             :                       ))),
     115           0 :               Expanded(
     116           0 :                   child: Container(
     117             :                       // Only show broken heart is the contact is offline...
     118           0 :                       decoration: BoxDecoration(
     119           0 :                           image: Provider.of<ContactInfoState>(outerContext).isOnline()
     120           0 :                               ? (Provider.of<Settings>(context).themeImages && Provider.of<Settings>(context).theme.chatImage != null)
     121           0 :                                   ? DecorationImage(
     122             :                                       repeat: ImageRepeat.repeat,
     123           0 :                                       image: Provider.of<Settings>(context, listen: false).theme.loadImage(Provider.of<Settings>(context, listen: false).theme.chatImage, context: context),
     124           0 :                                       colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.chatImageColor, BlendMode.srcIn))
     125             :                                   : null
     126           0 :                               : DecorationImage(
     127             :                                   fit: BoxFit.scaleDown,
     128             :                                   alignment: Alignment.center,
     129           0 :                                   image: AssetImage("assets/core/negative_heart_512px.png"),
     130           0 :                                   colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementColor.withOpacity(0.15), BlendMode.srcIn))),
     131             :                       // Don't load messages for syncing server...
     132           0 :                       child: Padding(
     133           0 :                           padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 20.0),
     134             :                           child: loadMessages
     135           0 :                               ? ScrollablePositionedList.builder(
     136           0 :                                   itemPositionsListener: widget.scrollListener,
     137           0 :                                   itemScrollController: Provider.of<ContactInfoState>(outerContext).messageScrollController,
     138           0 :                                   initialScrollIndex: initi > 4 ? initi - 4 : 0,
     139           0 :                                   itemCount: Provider.of<ContactInfoState>(outerContext).uiLoadedMessages + 1,
     140             :                                   reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
     141             :                                   shrinkWrap: true,
     142           0 :                                   itemBuilder: (itemBuilderContext, index) {
     143           0 :                                     var profileOnion = Provider.of<ProfileInfoState>(itemBuilderContext, listen: false).onion;
     144           0 :                                     var contactHandle = Provider.of<ContactInfoState>(itemBuilderContext, listen: false).identifier;
     145             :                                     var messageIndex = index;
     146           0 :                                     var loadedMessages = Provider.of<ContactInfoState>(itemBuilderContext, listen: false).uiLoadedMessages;
     147           0 :                                     var endOfMessages = Provider.of<ContactInfoState>(itemBuilderContext, listen: false).totalMessages + 1;
     148           0 :                                     if (index == endOfMessages - 1) {
     149           0 :                                       return ListTile(
     150           0 :                                         title: Text(AppLocalizations.of(itemBuilderContext)!.messageHistoryEndOfHistory,
     151             :                                                     textAlign: TextAlign.center),
     152             :                                         dense: true,
     153             :                                       );
     154           0 :                                     } else if (index >= loadedMessages) {
     155           0 :                                       return ElevatedButton(
     156           0 :                                         child: Text(AppLocalizations.of(itemBuilderContext)!.messageHistoryLoadOlderMessages),
     157           0 :                                         key: Key("loadAdditionalMessages"),
     158           0 :                                         onPressed: () {
     159           0 :                                           messageHandler(itemBuilderContext, profileOnion, contactHandle, ByIndex(messageIndex));
     160             :                                         },
     161           0 :                                         style: ButtonStyle(
     162           0 :                                           backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(itemBuilderContext).theme.messageFromMeBackgroundColor)
     163             :                                         ),
     164             :                                       );
     165             :                                     }
     166           0 :                                     return FutureBuilder(
     167           0 :                                       future: messageHandler(itemBuilderContext, profileOnion, contactHandle, ByIndex(messageIndex)),
     168           0 :                                       builder: (fbcontext, snapshot) {
     169           0 :                                         if (snapshot.hasData) {
     170           0 :                                           var message = snapshot.data as Message;
     171             :                                           // here we create an index key for the contact and assign it to the row. Indexes are unique so we can
     172             :                                           // reliably use this without running into duplicate keys...it isn't ideal as it means keys need to be re-built
     173             :                                           // when new messages are added...however it is better than the alternative of not having widget keys at all.
     174           0 :                                           var key = Provider.of<ContactInfoState>(itemBuilderContext, listen: false).getMessageKey(contactHandle, messageIndex);
     175           0 :                                           return message.getWidget(fbcontext, key, messageIndex);
     176             :                                         } else {
     177           0 :                                           return MessageLoadingBubble();
     178             :                                         }
     179             :                                       },
     180             :                                     );
     181             :                                   },
     182             :                                 )
     183             :                               : null)))
     184             :             ])));
     185             :   }
     186             : }

Generated by: LCOV version 1.14