LCOV - code coverage report
Current view: top level - lib/widgets - contactrow.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 121 0.0 %
Date: 2026-07-15 19:28:30 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:io';
       2             : 
       3             : import 'package:cwtch/models/appstate.dart';
       4             : import 'package:cwtch/models/contact.dart';
       5             : import 'package:cwtch/models/contactlist.dart';
       6             : import 'package:cwtch/models/profile.dart';
       7             : import 'package:cwtch/models/redaction.dart';
       8             : import 'package:cwtch/models/search.dart';
       9             : import 'package:cwtch/themes/opaque.dart';
      10             : import 'package:cwtch/views/contactsview.dart';
      11             : import 'package:flutter/material.dart';
      12             : import 'package:cwtch/widgets/profileimage.dart';
      13             : import 'package:provider/provider.dart';
      14             : import 'package:cwtch/l10n/app_localizations.dart';
      15             : import '../main.dart';
      16             : import '../models/message.dart';
      17             : import '../settings.dart';
      18             : 
      19             : class ContactRow extends StatefulWidget {
      20             :   int? messageIndex;
      21             : 
      22           0 :   ContactRow({this.messageIndex});
      23           0 :   @override
      24           0 :   _ContactRowState createState() => _ContactRowState();
      25             : }
      26             : 
      27             : class _ContactRowState extends State<ContactRow> {
      28             :   bool isHover = false;
      29             :   Message? cachedMessage;
      30             : 
      31           0 :   @override
      32             :   Widget build(BuildContext context) {
      33           0 :     var contact = Provider.of<ContactInfoState>(context);
      34             : 
      35           0 :     if (widget.messageIndex != null && this.cachedMessage == null) {
      36           0 :       messageHandler(context, Provider.of<ProfileInfoState>(context, listen: false).onion, contact.identifier, ByIndex(widget.messageIndex!)).then((value) {
      37           0 :         setState(() {
      38           0 :           this.cachedMessage = value;
      39             :         });
      40             :       });
      41             :     }
      42             : 
      43             :     // Only groups have a sync status
      44             :     Widget? syncStatus;
      45           0 :     if (contact.isGroup) {
      46           0 :       syncStatus = Visibility(
      47           0 :         visible: contact.isGroup && contact.status == "Authenticated",
      48           0 :         child: LinearProgressIndicator(
      49           0 :           color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
      50           0 :           backgroundColor: Provider.of<Settings>(context).theme.defaultButtonDisabledColor,
      51           0 :           value: Provider.of<ProfileInfoState>(context).serverList.getServer(contact.server ?? "")?.syncProgress,
      52             :         ),
      53             :       );
      54             :     }
      55             : 
      56           0 :     bool selected = Provider.of<AppState>(context).selectedConversation == contact.identifier;
      57           0 :     if (selected && widget.messageIndex != null) {
      58           0 :       if (selected && widget.messageIndex == Provider.of<SearchState>(context).selectedSearchMessage) {
      59             :         selected = true;
      60             :       } else {
      61             :         selected = false;
      62             :       }
      63             :     }
      64           0 :     return InkWell(
      65             :       enableFeedback: true,
      66             :       splashFactory: InkSplash.splashFactory,
      67           0 :       child: Ink(
      68           0 :         color: selected ? (Provider.of<Settings>(context).theme.backgroundHilightElementColor as Color).withOpacity(0.8) : Colors.transparent,
      69           0 :         child: Container(
      70           0 :           child: Row(
      71             :             mainAxisAlignment: MainAxisAlignment.spaceBetween,
      72             :             crossAxisAlignment: CrossAxisAlignment.center,
      73           0 :             children: [
      74           0 :               Padding(
      75             :                 padding: const EdgeInsets.all(6.0), //border size
      76           0 :                 child: ProfileImage(
      77           0 :                   badgeCount: contact.unreadMessages,
      78           0 :                   badgeColor: Provider.of<Settings>(context).theme.portraitContactBadgeColor,
      79           0 :                   badgeTextColor: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor,
      80             :                   diameter: 64.0,
      81           0 :                   imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? contact.imagePath : contact.defaultImagePath,
      82           0 :                   disabled: !contact.isOnline(),
      83           0 :                   border: contact.getBorderColor(Provider.of<Settings>(context).theme),
      84             :                 ),
      85             :               ),
      86           0 :               Expanded(
      87           0 :                 child: Padding(
      88           0 :                   padding: EdgeInsets.all(6.0),
      89           0 :                   child: Column(
      90             :                     crossAxisAlignment: CrossAxisAlignment.start,
      91             :                     mainAxisAlignment: MainAxisAlignment.spaceBetween,
      92             :                     mainAxisSize: MainAxisSize.min,
      93           0 :                     children: [
      94           0 :                       Container(
      95             :                         clipBehavior: Clip.hardEdge,
      96           0 :                         height: Provider.of<Settings>(context).fontScaling * 14.0 + 5.0,
      97           0 :                         decoration: BoxDecoration(),
      98           0 :                         child: Text(
      99           0 :                           contact.augmentedNickname(context).trim() + (contact.messageDraft.isEmpty() ? "" : "*"),
     100           0 :                           style: TextStyle(
     101           0 :                             fontSize: Provider.of<Settings>(context).fontScaling * 14.0,
     102             :                             fontFamily: "Inter",
     103             :                             fontWeight: FontWeight.bold,
     104           0 :                             color: contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor : Provider.of<Settings>(context).theme.mainTextColor,
     105             :                           ), //Provider.of<FlwtchState>(context).biggerFont,
     106             :                           softWrap: true,
     107             :                           overflow: TextOverflow.clip,
     108             :                           maxLines: 1,
     109             :                         ),
     110             :                       ),
     111           0 :                       syncStatus ?? SizedBox(width: 0, height: 0),
     112             :                       // we need to ignore the child widget in this context, otherwise gesture events will flow down...
     113           0 :                       IgnorePointer(
     114             :                         ignoring: true,
     115           0 :                         child: Visibility(
     116           0 :                           visible: this.cachedMessage != null,
     117             :                           maintainSize: false,
     118             :                           maintainInteractivity: false,
     119             :                           maintainSemantics: false,
     120             :                           maintainState: false,
     121           0 :                           child: this.cachedMessage == null ? CircularProgressIndicator() : this.cachedMessage!.getPreviewWidget(context),
     122             :                         ),
     123             :                       ),
     124           0 :                       Container(
     125           0 :                         padding: EdgeInsets.all(0),
     126           0 :                         height: contact.isInvitation ? Provider.of<Settings>(context).fontScaling * 14.0 + 35.0 : Provider.of<Settings>(context).fontScaling * 14.0 + 5.0,
     127           0 :                         child: contact.isInvitation == true
     128           0 :                             ? FittedBox(
     129             :                                 fit: BoxFit.scaleDown,
     130           0 :                                 child: Wrap(
     131           0 :                                   children: <Widget>[
     132           0 :                                     Padding(
     133           0 :                                       padding: EdgeInsets.all(2),
     134           0 :                                       child: TextButton.icon(
     135           0 :                                         label: Text(AppLocalizations.of(context)!.tooltipAcceptContactRequest, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)),
     136           0 :                                         icon: Icon(Icons.favorite, size: 16, color: Provider.of<Settings>(context).theme.mainTextColor),
     137           0 :                                         onPressed: _btnApprove,
     138             :                                       ),
     139             :                                     ),
     140           0 :                                     Padding(
     141           0 :                                       padding: EdgeInsets.all(2),
     142           0 :                                       child: TextButton.icon(
     143           0 :                                         label: Text(AppLocalizations.of(context)!.tooltipRejectContactRequest, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)),
     144           0 :                                         style: ButtonStyle(
     145           0 :                                           backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor),
     146           0 :                                           foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor),
     147             :                                         ),
     148           0 :                                         icon: Icon(Icons.delete, size: 16, color: Provider.of<Settings>(context).theme.mainTextColor),
     149           0 :                                         onPressed: _btnReject,
     150             :                                       ),
     151             :                                     ),
     152             :                                   ],
     153             :                                 ),
     154             :                               )
     155           0 :                             : (contact.isBlocked
     156           0 :                                   ? IconButton(
     157           0 :                                       padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 0.0),
     158           0 :                                       splashRadius: Material.defaultSplashRadius / 2,
     159             :                                       iconSize: 16,
     160           0 :                                       icon: Icon(Icons.block, color: Provider.of<Settings>(context).theme.mainTextColor),
     161             :                                       onPressed: null,
     162             :                                     )
     163           0 :                                   : Text(
     164           0 :                                       prettyDateString(context, widget.messageIndex == null ? contact.lastMessageSentTime : (this.cachedMessage?.getMetadata().timestamp ?? DateTime.now())),
     165           0 :                                       style: Provider.of<Settings>(context).scaleFonts(TextStyle(fontSize: 12.0, fontFamily: "Inter")),
     166             :                                     )),
     167             :                       ),
     168           0 :                       Visibility(
     169           0 :                         visible: !Provider.of<Settings>(context).streamerMode,
     170           0 :                         child: Container(
     171           0 :                           padding: EdgeInsets.all(0),
     172           0 :                           height: Provider.of<Settings>(context).fontScaling * 13.0 + 5.0,
     173           0 :                           child: FittedBox(
     174             :                             fit: BoxFit.scaleDown,
     175           0 :                             child: Text(
     176           0 :                               contact.onion,
     177             :                               overflow: TextOverflow.ellipsis,
     178           0 :                               style: Provider.of<Settings>(context).scaleFonts(
     179           0 :                                 TextStyle(
     180             :                                   fontSize: 13.0,
     181             :                                   fontFamily: "RobotoMono",
     182           0 :                                   color: ((contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor : Provider.of<Settings>(context).theme.mainTextColor) as Color)
     183           0 :                                       .withOpacity(0.8),
     184             :                                 ),
     185             :                               ),
     186             :                             ),
     187             :                           ),
     188             :                         ),
     189             :                       ),
     190             :                     ],
     191             :                   ),
     192             :                 ),
     193             :               ),
     194           0 :               Visibility(
     195             :                 // only allow pinning for non-blocked and accepted conversations,
     196           0 :                 visible: contact.isAccepted() && (Platform.isAndroid || (!Platform.isAndroid && isHover) || contact.pinned),
     197           0 :                 child: IconButton(
     198           0 :                   tooltip: contact.pinned ? AppLocalizations.of(context)!.tooltipUnpinConversation : AppLocalizations.of(context)!.tooltipPinConversation,
     199           0 :                   icon: Icon(contact.pinned ? Icons.push_pin : Icons.push_pin_outlined, color: Provider.of<Settings>(context).theme.mainTextColor),
     200           0 :                   onPressed: () {
     201           0 :                     if (contact.pinned) {
     202           0 :                       contact.unpin(context);
     203             :                     } else {
     204           0 :                       contact.pin(context);
     205             :                     }
     206           0 :                     Provider.of<ProfileInfoState>(context, listen: false).resortContacts();
     207             :                   },
     208             :                 ),
     209             :               ),
     210             :             ],
     211             :           ),
     212             :         ),
     213             :       ),
     214           0 :       onTap: () {
     215           0 :         setState(() {
     216           0 :           selectConversation(context, contact.identifier, widget.messageIndex);
     217             :         });
     218             :       },
     219           0 :       onHover: (hover) {
     220           0 :         if (isHover != hover) {
     221           0 :           setState(() {
     222           0 :             isHover = hover;
     223             :           });
     224             :         }
     225             :       },
     226             :     );
     227             :   }
     228             : 
     229           0 :   void _btnApprove() {
     230           0 :     Provider.of<ContactInfoState>(context, listen: false).accepted = true;
     231           0 :     Provider.of<FlwtchState>(
     232           0 :       context,
     233             :       listen: false,
     234           0 :     ).cwtch.AcceptContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier);
     235             :   }
     236             : 
     237           0 :   void _btnReject() {
     238           0 :     Provider.of<ContactInfoState>(context, listen: false).blocked = true;
     239           0 :     ContactInfoState contact = Provider.of<ContactInfoState>(context, listen: false);
     240           0 :     if (contact.isGroup == true) {
     241             :       // FIXME This flow is incorrect. Groups never just show up on the contact list anymore
     242           0 :       Provider.of<ProfileInfoState>(context, listen: false).removeContact(contact.onion);
     243             :     } else {
     244           0 :       Provider.of<FlwtchState>(context, listen: false).cwtch.BlockContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, contact.identifier);
     245             :     }
     246             :   }
     247             : }

Generated by: LCOV version 1.14