LCOV - code coverage report
Current view: top level - lib/widgets - messagerow.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 280 0.0 %
Date: 2024-08-22 18:05:30 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'dart:io';
       3             : 
       4             : import 'package:cwtch/config.dart';
       5             : import 'package:cwtch/cwtch_icons_icons.dart';
       6             : import 'package:cwtch/models/contact.dart';
       7             : import 'package:cwtch/models/message.dart';
       8             : import 'package:cwtch/models/profile.dart';
       9             : import 'package:cwtch/themes/opaque.dart';
      10             : import 'package:cwtch/third_party/base32/base32.dart';
      11             : import 'package:cwtch/views/contactsview.dart';
      12             : import 'package:cwtch/widgets/messageloadingbubble.dart';
      13             : import 'package:cwtch/widgets/staticmessagebubble.dart';
      14             : import 'package:flutter/material.dart';
      15             : import 'package:cwtch/widgets/profileimage.dart';
      16             : import 'package:flutter/physics.dart';
      17             : import 'package:provider/provider.dart';
      18             : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
      19             : 
      20             : import '../main.dart';
      21             : import '../models/messagecache.dart';
      22             : import '../models/redaction.dart';
      23             : import '../settings.dart';
      24             : 
      25             : class MessageRow extends StatefulWidget {
      26             :   final Widget child;
      27             :   final int index;
      28             : 
      29           0 :   MessageRow(this.child, this.index, {Key? key}) : super(key: key);
      30             : 
      31           0 :   @override
      32           0 :   MessageRowState createState() => MessageRowState();
      33             : }
      34             : 
      35             : class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMixin {
      36             :   bool showBlockedMessage = false;
      37             :   late AnimationController _controller;
      38             :   late Animation<Alignment> _animation;
      39             :   late Alignment _dragAlignment = Alignment.center;
      40             :   Alignment _dragAffinity = Alignment.center;
      41             : 
      42           0 :   @override
      43             :   void initState() {
      44           0 :     super.initState();
      45           0 :     _controller = AnimationController(vsync: this);
      46           0 :     _controller.addListener(() {
      47           0 :       setState(() {
      48           0 :         _dragAlignment = _animation.value;
      49             :       });
      50             :     });
      51             :   }
      52             : 
      53           0 :   @override
      54             :   void dispose() {
      55           0 :     _controller.dispose();
      56           0 :     super.dispose();
      57             :   }
      58             : 
      59           0 :   @override
      60             :   Widget build(BuildContext context) {
      61             :     // message cache will return a malformed metadata object
      62             :     // if for whatever reason this message doesn't exist
      63           0 :     if (Provider.of<MessageMetadata>(context).senderHandle == "") {
      64           0 :       EnvironmentConfig.debugLog("error, cache returned malformed message. This is likely a programming bug.");
      65           0 :       return MessageLoadingBubble();
      66             :     }
      67           0 :     var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
      68           0 :     var isContact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle) != null;
      69           0 :     var isGroup = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context, listen: false).conversationIdentifier)!.isGroup;
      70           0 :     var isBlocked = isContact ? Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle)!.isBlocked : false;
      71           0 :     var actualMessage = Flexible(flex: Platform.isAndroid ? 10 : 100, fit: FlexFit.loose, child: widget.child);
      72             : 
      73           0 :     _dragAffinity = fromMe ? Alignment.centerRight : Alignment.centerLeft;
      74           0 :     _dragAlignment = fromMe ? Alignment.centerRight : Alignment.centerLeft;
      75             : 
      76             :     var senderDisplayStr = "";
      77             :     if (!fromMe) {
      78           0 :       ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
      79             :       if (contact != null) {
      80           0 :         senderDisplayStr = redactedNick(context, contact.onion, contact.nickname);
      81             :       } else {
      82           0 :         senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
      83             :       }
      84             :     }
      85             : 
      86           0 :     Widget wdgReply = Platform.isAndroid
      87           0 :         ? SizedBox.shrink()
      88           0 :         : Visibility(
      89           0 :             visible: EnvironmentConfig.TEST_MODE || Provider.of<ContactInfoState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID,
      90             :             maintainSize: true,
      91             :             maintainAnimation: true,
      92             :             maintainState: true,
      93             :             maintainInteractivity: false,
      94           0 :             child: IconButton(
      95           0 :                 tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage,
      96           0 :                 splashRadius: Material.defaultSplashRadius / 2,
      97           0 :                 onPressed: () {
      98           0 :                   Provider.of<ContactInfoState>(context, listen: false).messageDraft.quotedReference = Provider.of<MessageMetadata>(context, listen: false).messageID;
      99           0 :                   Provider.of<ContactInfoState>(context, listen: false).notifyMessageDraftUpdate();
     100           0 :                   setState(() {});
     101             :                 },
     102           0 :                 icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.chatReactionIconColor)));
     103             : 
     104           0 :     var settings = Provider.of<Settings>(context);
     105           0 :     var pis = Provider.of<ProfileInfoState>(context);
     106           0 :     var cis = Provider.of<ContactInfoState>(context);
     107           0 :     var borderColor = Provider.of<Settings>(context).theme.portraitOnlineBorderColor;
     108           0 :     var messageID = Provider.of<MessageMetadata>(context).messageID;
     109           0 :     var cache = Provider.of<ContactInfoState>(context).messageCache;
     110             : 
     111           0 :     Widget wdgSeeReplies = Platform.isAndroid
     112           0 :         ? SizedBox.shrink()
     113           0 :         : Visibility(
     114           0 :             visible: EnvironmentConfig.TEST_MODE || Provider.of<ContactInfoState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID,
     115             :             maintainSize: true,
     116             :             maintainAnimation: true,
     117             :             maintainState: true,
     118             :             maintainInteractivity: false,
     119           0 :             child: IconButton(
     120           0 :                 tooltip: AppLocalizations.of(context)!.viewReplies,
     121           0 :                 splashRadius: Material.defaultSplashRadius / 2,
     122           0 :                 onPressed: () {
     123           0 :                   modalShowReplies(context, AppLocalizations.of(context)!.headingReplies, AppLocalizations.of(context)!.messageNoReplies, settings, pis, cis, borderColor, cache, messageID);
     124             :                 },
     125           0 :                 icon: Icon(CwtchIcons.view_replies, color: Provider.of<Settings>(context).theme.chatReactionIconColor)));
     126             : 
     127           0 :     var profile = Provider.of<ProfileInfoState>(context, listen: false);
     128           0 :     var conversation = Provider.of<ContactInfoState>(context, listen: false);
     129           0 :     var message = Provider.of<MessageMetadata>(context, listen: false);
     130             : 
     131           0 :     Widget wdgTranslateMessage = Platform.isAndroid
     132           0 :         ? SizedBox.shrink()
     133           0 :         : Visibility(
     134           0 :             visible: Provider.of<FlwtchState>(context, listen: false).cwtch.IsBlodeuweddSupported() &&
     135           0 :                 Provider.of<Settings>(context).isExperimentEnabled(BlodeuweddExperiment) &&
     136           0 :                 (EnvironmentConfig.TEST_MODE || Provider.of<ContactInfoState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID),
     137             :             maintainSize: true,
     138             :             maintainAnimation: true,
     139             :             maintainState: true,
     140             :             maintainInteractivity: false,
     141           0 :             child: IconButton(
     142           0 :                 tooltip: AppLocalizations.of(context)!.blodeuweddTranslate,
     143           0 :                 splashRadius: Material.defaultSplashRadius / 2,
     144           0 :                 onPressed: () {
     145           0 :                   Provider.of<MessageMetadata>(context, listen: false).translation = "";
     146           0 :                   Provider.of<FlwtchState>(context, listen: false).cwtch.TranslateMessage(profile.onion, conversation.identifier, message.messageID, "French");
     147           0 :                   modalShowTranslation(context, profile, settings);
     148             :                 },
     149           0 :                 icon: Icon(Icons.translate, color: Provider.of<Settings>(context).theme.chatReactionIconColor)));
     150             : 
     151           0 :     Widget wdgSpacer = Flexible(flex: 1, child: SizedBox(width: Platform.isAndroid ? 20 : 60, height: 10));
     152           0 :     var widgetRow = <Widget>[];
     153             : 
     154             :     if (fromMe) {
     155           0 :       widgetRow = <Widget>[
     156             :         wdgSpacer,
     157             :         wdgTranslateMessage,
     158             :         wdgSeeReplies,
     159             :         wdgReply,
     160             :         actualMessage,
     161             :       ];
     162           0 :     } else if (isBlocked && !showBlockedMessage) {
     163           0 :       Color blockedMessageBackground = Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor;
     164           0 :       Widget wdgPortrait = Padding(padding: EdgeInsets.all(4.0), child: Icon(CwtchIcons.account_blocked));
     165           0 :       widgetRow = <Widget>[
     166             :         wdgPortrait,
     167           0 :         Container(
     168           0 :             padding: EdgeInsets.all(2.0),
     169           0 :             decoration: BoxDecoration(
     170             :                 color: blockedMessageBackground,
     171           0 :                 border: Border.all(color: blockedMessageBackground, width: 2),
     172           0 :                 borderRadius: BorderRadius.only(
     173           0 :                   topLeft: Radius.circular(15.0),
     174           0 :                   topRight: Radius.circular(15.0),
     175           0 :                   bottomLeft: Radius.circular(15.0),
     176           0 :                   bottomRight: Radius.circular(15.0),
     177             :                 )),
     178           0 :             child: Padding(
     179           0 :                 padding: EdgeInsets.all(9.0),
     180           0 :                 child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
     181           0 :                   SelectableText(
     182           0 :                     AppLocalizations.of(context)!.blockedMessageMessage,
     183             :                     //key: Key(myKey),
     184           0 :                     style: TextStyle(
     185           0 :                       color: Provider.of<Settings>(context).theme.messageFromOtherTextColor,
     186             :                     ),
     187             :                     textAlign: TextAlign.center,
     188             :                     textWidthBasis: TextWidthBasis.longestLine,
     189             :                   ),
     190           0 :                   Padding(
     191           0 :                       padding: EdgeInsets.all(1.0),
     192           0 :                       child: TextButton(
     193           0 :                           style: ButtonStyle(
     194           0 :                             backgroundColor: MaterialStateProperty.all(blockedMessageBackground),
     195             :                           ),
     196           0 :                           child: Text(
     197           0 :                             AppLocalizations.of(context)!.showMessageButton + '\u202F',
     198           0 :                             style: TextStyle(decoration: TextDecoration.underline),
     199             :                           ),
     200           0 :                           onPressed: () {
     201           0 :                             setState(() {
     202           0 :                               this.showBlockedMessage = true;
     203             :                             });
     204             :                           })),
     205             :                 ]))),
     206             :         wdgSpacer,
     207             :       ];
     208             :     } else {
     209           0 :       var contact = Provider.of<ContactInfoState>(context);
     210           0 :       ContactInfoState? sender = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
     211             : 
     212           0 :       String imagePath = Provider.of<MessageMetadata>(context).senderImage!;
     213             :       if (sender != null) {
     214           0 :         imagePath = Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? sender.imagePath : sender.defaultImagePath;
     215             :       } else {
     216           0 :         imagePath = RandomProfileImage(Provider.of<MessageMetadata>(context).senderHandle);
     217             :       }
     218           0 :       Widget wdgPortrait = GestureDetector(
     219             :           onTap: !isGroup
     220             :               ? null
     221             :               : isContact
     222           0 :                   ? _btnGoto
     223           0 :                   : _btnAdd,
     224           0 :           child: Padding(
     225           0 :               padding: EdgeInsets.all(4.0),
     226           0 :               child: ProfileImage(
     227             :                 diameter: 48.0,
     228             :                 // default to the contact image...otherwise use a derived sender image...
     229             :                 imagePath: imagePath,
     230           0 :                 border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor : Provider.of<Settings>(context).theme.portraitOfflineBorderColor,
     231             :                 badgeTextColor: Colors.red,
     232             :                 badgeColor: Colors.red,
     233             :                 tooltip: !isGroup
     234             :                     ? ""
     235             :                     : isContact
     236           0 :                         ? AppLocalizations.of(context)!.contactGoto.replaceFirst("%1", senderDisplayStr)
     237           0 :                         : AppLocalizations.of(context)!.addContact,
     238             :               )));
     239             : 
     240           0 :       widgetRow = <Widget>[
     241             :         wdgPortrait,
     242             :         actualMessage,
     243             :         wdgReply,
     244             :         wdgSeeReplies,
     245             :         wdgTranslateMessage,
     246             :         wdgSpacer,
     247             :       ];
     248             :     }
     249           0 :     var size = MediaQuery.of(context).size;
     250           0 :     var mr = MouseRegion(
     251             :         // For desktop...
     252           0 :         onHover: (event) {
     253           0 :           if (Provider.of<ContactInfoState>(context, listen: false).hoveredIndex != Provider.of<MessageMetadata>(context, listen: false).messageID) {
     254           0 :             Provider.of<ContactInfoState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context, listen: false).messageID;
     255             :           }
     256             :         },
     257           0 :         onExit: (event) {
     258           0 :           Provider.of<ContactInfoState>(context, listen: false).hoveredIndex = -1;
     259             :         },
     260           0 :         child: GestureDetector(
     261           0 :             onPanUpdate: (details) {
     262           0 :               setState(() {
     263           0 :                 _dragAlignment += Alignment(
     264           0 :                   details.delta.dx / (size.width * 0.5),
     265             :                   0,
     266             :                 );
     267             :               });
     268             :             },
     269           0 :             onPanDown: (details) {
     270           0 :               _controller.stop();
     271             :             },
     272           0 :             onPanEnd: (details) {
     273           0 :               _runAnimation(details.velocity.pixelsPerSecond, size);
     274           0 :               if (Platform.isAndroid) {
     275           0 :                 Provider.of<ContactInfoState>(context, listen: false).messageDraft.quotedReference = Provider.of<MessageMetadata>(context, listen: false).messageID;
     276           0 :                 Provider.of<ContactInfoState>(context, listen: false).notifyMessageDraftUpdate();
     277           0 :                 setState(() {});
     278             :               }
     279             :             },
     280           0 :             onLongPress: () async {
     281           0 :               if (Platform.isAndroid) {
     282           0 :                 modalShowReplies(context, AppLocalizations.of(context)!.headingReplies, AppLocalizations.of(context)!.messageNoReplies, settings, pis, cis, borderColor, cache, messageID);
     283             :               }
     284             :             },
     285           0 :             child: Padding(
     286           0 :                 padding: EdgeInsets.all(2),
     287           0 :                 child: Align(
     288             :                     widthFactor: 1,
     289           0 :                     alignment: _dragAlignment,
     290           0 :                     child: Row(
     291             :                       mainAxisSize: MainAxisSize.min,
     292             :                       mainAxisAlignment: MainAxisAlignment.center,
     293             :                       children: widgetRow,
     294             :                     )))));
     295             : 
     296           0 :     if (Provider.of<ContactInfoState>(context).newMarkerMsgIndex == widget.index) {
     297           0 :       return Column(
     298             :           crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
     299           0 :           children: [Align(alignment: Alignment.center, child: Padding(padding: EdgeInsets.all(5.0), child: _bubbleNew())), mr]);
     300             :     } else {
     301             :       return mr;
     302             :     }
     303             :   }
     304             : 
     305           0 :   Widget _bubbleNew() {
     306           0 :     return Container(
     307           0 :         decoration: BoxDecoration(
     308           0 :           color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
     309           0 :           border: Border.all(color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor, width: 1),
     310           0 :           borderRadius: BorderRadius.only(
     311           0 :             topLeft: Radius.circular(8),
     312           0 :             topRight: Radius.circular(8),
     313           0 :             bottomLeft: Radius.circular(8),
     314           0 :             bottomRight: Radius.circular(8),
     315             :           ),
     316             :         ),
     317           0 :         child: Padding(padding: EdgeInsets.all(9.0), child: Text(AppLocalizations.of(context)!.newMessagesLabel, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle))));
     318             :   }
     319             : 
     320           0 :   void _runAnimation(Offset pixelsPerSecond, Size size) {
     321           0 :     _animation = _controller.drive(
     322           0 :       AlignmentTween(
     323           0 :         begin: _dragAlignment,
     324           0 :         end: _dragAffinity,
     325             :       ),
     326             :     );
     327             :     // Calculate the velocity relative to the unit interval, [0,1],
     328             :     // used by the animation controller.
     329           0 :     final unitsPerSecondX = pixelsPerSecond.dx / size.width;
     330           0 :     final unitsPerSecondY = pixelsPerSecond.dy / size.height;
     331           0 :     final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
     332           0 :     final unitVelocity = unitsPerSecond.distance;
     333             : 
     334             :     const spring = SpringDescription(
     335             :       mass: 30,
     336             :       stiffness: 1,
     337             :       damping: 1,
     338             :     );
     339             : 
     340           0 :     final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
     341           0 :     _controller.animateWith(simulation);
     342             :   }
     343             : 
     344           0 :   void _btnGoto() {
     345           0 :     var id = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context, listen: false).senderHandle)?.identifier;
     346             :     if (id == null) {
     347             :       // Can't happen
     348             :     } else {
     349           0 :       selectConversation(context, id, null);
     350           0 :       var contactIndex = Provider.of<ProfileInfoState>(context, listen: false).contactList.filteredList().indexWhere((element) => element.identifier == id);
     351           0 :       Provider.of<ProfileInfoState>(context, listen: false).contactListScrollController.jumpTo(index: contactIndex);
     352             :     }
     353             :   }
     354             : 
     355           0 :   void _btnAdd() {
     356           0 :     var sender = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
     357           0 :     if (sender == "") {
     358           0 :       print("sender not yet loaded");
     359             :       return;
     360             :     }
     361           0 :     var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
     362             : 
     363           0 :     showAddContactConfirmAlertDialog(context, profileOnion, sender);
     364             :   }
     365             : 
     366           0 :   showAddContactConfirmAlertDialog(BuildContext context, String profileOnion, String senderOnion) {
     367             :     // set up the buttons
     368           0 :     Widget cancelButton = ElevatedButton(
     369           0 :       child: Text(AppLocalizations.of(context)!.cancel),
     370           0 :       style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
     371           0 :       onPressed: () {
     372           0 :         Navigator.of(context).pop(); // dismiss dialog
     373             :       },
     374             :     );
     375           0 :     Widget continueButton = ElevatedButton(
     376           0 :       style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
     377           0 :       child: Text(AppLocalizations.of(context)!.addContact),
     378           0 :       onPressed: () {
     379           0 :         Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, senderOnion);
     380           0 :         final snackBar = SnackBar(
     381           0 :           content: Text(AppLocalizations.of(context)!.successfullAddedContact),
     382           0 :           duration: Duration(seconds: 2),
     383             :         );
     384           0 :         ScaffoldMessenger.of(context).showSnackBar(snackBar);
     385           0 :         Navigator.of(context).pop(); // dismiss dialog
     386             :       },
     387             :     );
     388             : 
     389             :     // set up the AlertDialog
     390           0 :     AlertDialog alert = AlertDialog(
     391           0 :       title: Text(AppLocalizations.of(context)!.addContactConfirm.replaceFirst("%1", senderOnion)),
     392           0 :       actions: [
     393             :         cancelButton,
     394             :         continueButton,
     395             :       ],
     396             :     );
     397             : 
     398             :     // show the dialog
     399           0 :     showDialog(
     400             :       context: context,
     401           0 :       builder: (BuildContext context) {
     402             :         return alert;
     403             :       },
     404             :     );
     405             :   }
     406             : }
     407             : 
     408           0 : void modalShowReplies(
     409             :     BuildContext ctx, String replyHeader, String noRepliesText, Settings settings, ProfileInfoState profile, ContactInfoState cis, Color borderColor, MessageCache cache, int messageID,
     410             :     {bool showImage = true}) {
     411           0 :   showModalBottomSheet<void>(
     412             :       context: ctx,
     413           0 :       builder: (BuildContext bcontext) {
     414           0 :         List<Message> replies = getReplies(cache, messageID);
     415           0 :         ScrollController controller = ScrollController();
     416             : 
     417           0 :         return ChangeNotifierProvider.value(
     418             :             value: profile,
     419           0 :             builder: (bcontext, child) {
     420           0 :               return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
     421           0 :                 var replyWidgets = replies.map((e) {
     422           0 :                   var fromMe = e.getMetadata().senderHandle == profile.onion;
     423             : 
     424           0 :                   var bubble = StaticMessageBubble(profile, settings, e.getMetadata(), Row(children: [Flexible(child: e.getPreviewWidget(context))]));
     425             : 
     426           0 :                   String imagePath = e.getMetadata().senderImage!;
     427           0 :                   var sender = profile.contactList.findContact(e.getMetadata().senderHandle);
     428             :                   if (sender != null) {
     429           0 :                     imagePath = showImage ? sender.imagePath : sender.defaultImagePath;
     430             :                   } else {
     431           0 :                     imagePath = RandomProfileImage(e.getMetadata().senderHandle);
     432             :                   }
     433             : 
     434             :                   if (fromMe) {
     435           0 :                     imagePath = profile.imagePath;
     436             :                   }
     437             : 
     438           0 :                   var image = Padding(
     439           0 :                       padding: EdgeInsets.all(4.0),
     440           0 :                       child: ProfileImage(
     441             :                         imagePath: imagePath,
     442             :                         diameter: 48.0,
     443             :                         border: borderColor,
     444             :                         badgeTextColor: Colors.red,
     445             :                         badgeColor: Colors.red,
     446             :                       ));
     447             : 
     448           0 :                   return Padding(
     449           0 :                       padding: EdgeInsets.all(10.0),
     450           0 :                       child: Row(
     451             :                         mainAxisSize: MainAxisSize.min,
     452           0 :                         children: [image, Flexible(child: bubble)],
     453             :                       ));
     454           0 :                 }).toList();
     455             : 
     456             :                 var withHeader = replyWidgets;
     457             : 
     458             :                 var original =
     459           0 :                     StaticMessageBubble(profile, settings, cache.cache[messageID]!.metadata, Row(children: [Flexible(child: compileOverlay(cache.cache[messageID]!).getPreviewWidget(context))]));
     460             : 
     461           0 :                 withHeader.insert(0, Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Center(child: original)));
     462             : 
     463           0 :                 withHeader.insert(
     464             :                     1,
     465           0 :                     Padding(
     466           0 :                         padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0),
     467           0 :                         child: Divider(
     468           0 :                           color: settings.theme.mainTextColor,
     469             :                         )));
     470             : 
     471           0 :                 if (replies.isNotEmpty) {
     472           0 :                   withHeader.insert(2, Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Text(replyHeader, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold))));
     473             :                 } else {
     474           0 :                   withHeader.insert(
     475           0 :                       2, Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Center(child: Text(noRepliesText, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)))));
     476             :                 }
     477             : 
     478           0 :                 return Scrollbar(
     479             :                     trackVisibility: true,
     480             :                     controller: controller,
     481           0 :                     child: SingleChildScrollView(
     482             :                         clipBehavior: Clip.antiAlias,
     483           0 :                         child: ConstrainedBox(
     484           0 :                             constraints: BoxConstraints(
     485           0 :                               minHeight: viewportConstraints.maxHeight,
     486             :                             ),
     487           0 :                             child: Padding(
     488           0 :                                 padding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 20.0),
     489           0 :                                 child: Column(
     490             :                                   crossAxisAlignment: CrossAxisAlignment.start,
     491             :                                   children: withHeader,
     492             :                                 )))));
     493             :               });
     494             :             });
     495             :       });
     496             : }
     497             : 
     498           0 : void modalShowTranslation(BuildContext context, ProfileInfoState profile, Settings settings) async {
     499           0 :   showModalBottomSheet<void>(
     500           0 :       builder: (
     501             :         BuildContext bcontext,
     502             :       ) {
     503           0 :         return StatefulBuilder(builder: (BuildContext scontext, StateSetter setState /*You can rename this!*/) {
     504           0 :           if (scontext.mounted) {
     505           0 :             new Timer.periodic(Duration(seconds: 1), (Timer t) {
     506           0 :               if (scontext.mounted) {
     507           0 :                 setState(() {});
     508             :               }
     509             :             });
     510             :           }
     511             : 
     512           0 :           var bubble = StaticMessageBubble(
     513             :               profile,
     514             :               settings,
     515           0 :               MessageMetadata(profile.onion, Provider.of<ContactInfoState>(context, listen: false).identifier, 1, DateTime.now(), "blodeuwedd", null, null, null, true, false, false, ""),
     516           0 :               Row(children: [
     517           0 :                 Provider.of<MessageMetadata>(context).translation == ""
     518           0 :                     ? Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
     519           0 :                         CircularProgressIndicator(color: settings.theme.defaultButtonActiveColor),
     520           0 :                         Padding(padding: EdgeInsets.all(5.0), child: Text(AppLocalizations.of(context)!.blodeuweddProcessing))
     521             :                       ])
     522           0 :                     : Flexible(child: SelectableText(Provider.of<MessageMetadata>(context).translation))
     523             :               ]));
     524             : 
     525           0 :           var image = Padding(
     526           0 :               padding: EdgeInsets.all(4.0),
     527           0 :               child: ProfileImage(
     528             :                 imagePath: "assets/blodeuwedd.png",
     529             :                 diameter: 48.0,
     530           0 :                 border: settings.theme.portraitOnlineBorderColor,
     531             :                 badgeTextColor: Colors.red,
     532             :                 badgeColor: Colors.red,
     533             :               ));
     534             : 
     535           0 :           return Container(
     536             :               height: 300, // bespoke value courtesy of the [TextField] docs
     537           0 :               child: Container(
     538             :                   alignment: Alignment.center,
     539           0 :                   child: Padding(
     540           0 :                       padding: EdgeInsets.all(10.0),
     541           0 :                       child: Padding(
     542           0 :                           padding: EdgeInsets.all(10.0),
     543           0 :                           child: Row(
     544             :                             mainAxisSize: MainAxisSize.min,
     545           0 :                             children: [image, Flexible(child: bubble)],
     546             :                           )))));
     547             :         });
     548             :       },
     549             :       context: context);
     550             : }
     551             : 
     552             : // temporary until we do real picture selection
     553           0 : String RandomProfileImage(String onion) {
     554           0 :   var choices = [
     555             :     "001-centaur",
     556             :     "002-kraken",
     557             :     "003-dinosaur",
     558             :     "004-tree-1",
     559             :     "005-hand",
     560             :     "006-echidna",
     561             :     "007-robot",
     562             :     "008-mushroom",
     563             :     "009-harpy",
     564             :     "010-phoenix",
     565             :     "011-dragon-1",
     566             :     "012-devil",
     567             :     "013-troll",
     568             :     "014-alien",
     569             :     "015-minotaur",
     570             :     "016-madre-monte",
     571             :     "017-satyr",
     572             :     "018-karakasakozou",
     573             :     "019-pirate",
     574             :     "020-werewolf",
     575             :     "021-scarecrow",
     576             :     "022-valkyrie",
     577             :     "023-curupira",
     578             :     "024-loch-ness-monster",
     579             :     "025-tree",
     580             :     "026-cerberus",
     581             :     "027-gryphon",
     582             :     "028-mermaid",
     583             :     "029-vampire",
     584             :     "030-goblin",
     585             :     "031-yeti",
     586             :     "032-leprechaun",
     587             :     "033-medusa",
     588             :     "034-chimera",
     589             :     "035-elf",
     590             :     "036-hydra",
     591             :     "037-cyclops",
     592             :     "038-pegasus",
     593             :     "039-narwhal",
     594             :     "040-woodcutter",
     595             :     "041-zombie",
     596             :     "042-dragon",
     597             :     "043-frankenstein",
     598             :     "044-witch",
     599             :     "045-fairy",
     600             :     "046-genie",
     601             :     "047-pinocchio",
     602             :     "048-ghost",
     603             :     "049-wizard",
     604             :     "050-unicorn"
     605             :   ];
     606           0 :   var encoding = base32.decode(onion.toUpperCase());
     607           0 :   return "assets/profiles/" + choices[encoding[33] % choices.length] + ".png";
     608             : }

Generated by: LCOV version 1.14