LCOV - code coverage report
Current view: top level - lib/widgets - contactrow.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 122 0.0 %
Date: 2026-09-01 20:55:37 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           0 :                   managed: contact.isManaged,
      85             :                 ),
      86             :               ),
      87           0 :               Expanded(
      88           0 :                 child: Padding(
      89           0 :                   padding: EdgeInsets.all(6.0),
      90           0 :                   child: Column(
      91             :                     crossAxisAlignment: CrossAxisAlignment.start,
      92             :                     mainAxisAlignment: MainAxisAlignment.spaceBetween,
      93             :                     mainAxisSize: MainAxisSize.min,
      94           0 :                     children: [
      95           0 :                       Container(
      96             :                         clipBehavior: Clip.hardEdge,
      97           0 :                         height: Provider.of<Settings>(context).fontScaling * 14.0 + 5.0,
      98           0 :                         decoration: BoxDecoration(),
      99           0 :                         child: Text(
     100           0 :                           (contact.isShadowed ? "s:" : "") + contact.augmentedNickname(context).trim() + (contact.messageDraft.isEmpty() ? "" : "*"),
     101           0 :                           style: TextStyle(
     102           0 :                             fontSize: Provider.of<Settings>(context).fontScaling * 14.0,
     103             :                             fontFamily: "Inter",
     104             :                             fontWeight: FontWeight.bold,
     105           0 :                             color: contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor : Provider.of<Settings>(context).theme.mainTextColor,
     106             :                           ), //Provider.of<FlwtchState>(context).biggerFont,
     107             :                           softWrap: true,
     108             :                           overflow: TextOverflow.clip,
     109             :                           maxLines: 1,
     110             :                         ),
     111             :                       ),
     112           0 :                       syncStatus ?? SizedBox(width: 0, height: 0),
     113             :                       // we need to ignore the child widget in this context, otherwise gesture events will flow down...
     114           0 :                       IgnorePointer(
     115             :                         ignoring: true,
     116           0 :                         child: Visibility(
     117           0 :                           visible: this.cachedMessage != null,
     118             :                           maintainSize: false,
     119             :                           maintainInteractivity: false,
     120             :                           maintainSemantics: false,
     121             :                           maintainState: false,
     122           0 :                           child: this.cachedMessage == null ? CircularProgressIndicator() : this.cachedMessage!.getPreviewWidget(context),
     123             :                         ),
     124             :                       ),
     125           0 :                       Container(
     126           0 :                         padding: EdgeInsets.all(0),
     127           0 :                         height: contact.isInvitation ? Provider.of<Settings>(context).fontScaling * 14.0 + 35.0 : Provider.of<Settings>(context).fontScaling * 14.0 + 5.0,
     128           0 :                         child: contact.isInvitation == true
     129           0 :                             ? FittedBox(
     130             :                                 fit: BoxFit.scaleDown,
     131           0 :                                 child: Wrap(
     132           0 :                                   children: <Widget>[
     133           0 :                                     Padding(
     134           0 :                                       padding: EdgeInsets.all(2),
     135           0 :                                       child: TextButton.icon(
     136           0 :                                         label: Text(AppLocalizations.of(context)!.tooltipAcceptContactRequest, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)),
     137           0 :                                         icon: Icon(Icons.favorite, size: 16, color: Provider.of<Settings>(context).theme.mainTextColor),
     138           0 :                                         onPressed: _btnApprove,
     139             :                                       ),
     140             :                                     ),
     141           0 :                                     Padding(
     142           0 :                                       padding: EdgeInsets.all(2),
     143           0 :                                       child: TextButton.icon(
     144           0 :                                         label: Text(AppLocalizations.of(context)!.tooltipRejectContactRequest, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)),
     145           0 :                                         style: ButtonStyle(
     146           0 :                                           backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor),
     147           0 :                                           foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor),
     148             :                                         ),
     149           0 :                                         icon: Icon(Icons.delete, size: 16, color: Provider.of<Settings>(context).theme.mainTextColor),
     150           0 :                                         onPressed: _btnReject,
     151             :                                       ),
     152             :                                     ),
     153             :                                   ],
     154             :                                 ),
     155             :                               )
     156           0 :                             : (contact.isBlocked
     157           0 :                                   ? IconButton(
     158           0 :                                       padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 0.0),
     159           0 :                                       splashRadius: Material.defaultSplashRadius / 2,
     160             :                                       iconSize: 16,
     161           0 :                                       icon: Icon(Icons.block, color: Provider.of<Settings>(context).theme.mainTextColor),
     162             :                                       onPressed: null,
     163             :                                     )
     164           0 :                                   : Text(
     165           0 :                                       prettyDateString(context, widget.messageIndex == null ? contact.lastMessageSentTime : (this.cachedMessage?.getMetadata().timestamp ?? DateTime.now())),
     166           0 :                                       style: Provider.of<Settings>(context).scaleFonts(TextStyle(fontSize: 12.0, fontFamily: "Inter")),
     167             :                                     )),
     168             :                       ),
     169           0 :                       Visibility(
     170           0 :                         visible: !Provider.of<Settings>(context).streamerMode,
     171           0 :                         child: Container(
     172           0 :                           padding: EdgeInsets.all(0),
     173           0 :                           height: Provider.of<Settings>(context).fontScaling * 13.0 + 5.0,
     174           0 :                           child: FittedBox(
     175             :                             fit: BoxFit.scaleDown,
     176           0 :                             child: Text(
     177           0 :                               contact.onion,
     178             :                               overflow: TextOverflow.ellipsis,
     179           0 :                               style: Provider.of<Settings>(context).scaleFonts(
     180           0 :                                 TextStyle(
     181             :                                   fontSize: 13.0,
     182             :                                   fontFamily: "RobotoMono",
     183           0 :                                   color: ((contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor : Provider.of<Settings>(context).theme.mainTextColor) as Color)
     184           0 :                                       .withOpacity(0.8),
     185             :                                 ),
     186             :                               ),
     187             :                             ),
     188             :                           ),
     189             :                         ),
     190             :                       ),
     191             :                     ],
     192             :                   ),
     193             :                 ),
     194             :               ),
     195           0 :               Visibility(
     196             :                 // only allow pinning for non-blocked and accepted conversations,
     197           0 :                 visible: contact.isAccepted() && (Platform.isAndroid || (!Platform.isAndroid && isHover) || contact.pinned),
     198           0 :                 child: IconButton(
     199           0 :                   tooltip: contact.pinned ? AppLocalizations.of(context)!.tooltipUnpinConversation : AppLocalizations.of(context)!.tooltipPinConversation,
     200           0 :                   icon: Icon(contact.pinned ? Icons.push_pin : Icons.push_pin_outlined, color: Provider.of<Settings>(context).theme.mainTextColor),
     201           0 :                   onPressed: () {
     202           0 :                     if (contact.pinned) {
     203           0 :                       contact.unpin(context);
     204             :                     } else {
     205           0 :                       contact.pin(context);
     206             :                     }
     207           0 :                     Provider.of<ProfileInfoState>(context, listen: false).resortContacts();
     208             :                   },
     209             :                 ),
     210             :               ),
     211             :             ],
     212             :           ),
     213             :         ),
     214             :       ),
     215           0 :       onTap: () {
     216           0 :         setState(() {
     217           0 :           selectConversation(context, contact.identifier, widget.messageIndex);
     218             :         });
     219             :       },
     220           0 :       onHover: (hover) {
     221           0 :         if (isHover != hover) {
     222           0 :           setState(() {
     223           0 :             isHover = hover;
     224             :           });
     225             :         }
     226             :       },
     227             :     );
     228             :   }
     229             : 
     230           0 :   void _btnApprove() {
     231           0 :     Provider.of<ContactInfoState>(context, listen: false).accepted = true;
     232           0 :     Provider.of<FlwtchState>(
     233           0 :       context,
     234             :       listen: false,
     235           0 :     ).cwtch.AcceptContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier);
     236             :   }
     237             : 
     238           0 :   void _btnReject() {
     239           0 :     Provider.of<ContactInfoState>(context, listen: false).blocked = true;
     240           0 :     ContactInfoState contact = Provider.of<ContactInfoState>(context, listen: false);
     241           0 :     if (contact.isGroup == true) {
     242             :       // FIXME This flow is incorrect. Groups never just show up on the contact list anymore
     243           0 :       Provider.of<ProfileInfoState>(context, listen: false).removeContact(contact.onion);
     244             :     } else {
     245           0 :       Provider.of<FlwtchState>(context, listen: false).cwtch.BlockContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, contact.identifier);
     246             :     }
     247             :   }
     248             : }

Generated by: LCOV version 1.14