LCOV - code coverage report
Current view: top level - lib/widgets - profileimage.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 43 83 51.8 %
Date: 2026-08-31 22:32:18 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:io';
       2             : import 'dart:math';
       3             : 
       4             : import 'package:flutter/material.dart';
       5             : import 'package:provider/provider.dart';
       6             : 
       7             : import 'package:cwtch/cwtch_icons_icons.dart';
       8             : import 'package:cwtch/models/contact.dart';
       9             : import 'package:cwtch/models/profile.dart';
      10             : import 'package:cwtch/themes/opaque.dart';
      11             : 
      12             : import '../settings.dart';
      13             : 
      14             : class ProfileImage extends StatefulWidget {
      15           1 :   ProfileImage({
      16             :     required this.imagePath,
      17             :     required this.diameter,
      18             :     required this.border,
      19             :     this.badgeCount = 0,
      20             :     required this.badgeColor,
      21             :     required this.badgeTextColor,
      22             :     this.maskOut = false,
      23             :     this.tooltip = "",
      24             :     this.disabled = false,
      25             :     this.badgeEdit = false,
      26             :     this.badgeIcon,
      27             :     this.defaultImagePath = "",
      28             :     this.managed = false,
      29             :   });
      30             :   final double diameter;
      31             :   final String imagePath;
      32             :   final String defaultImagePath;
      33             :   final Color border;
      34             :   final int badgeCount;
      35             :   final Color badgeColor;
      36             :   final Color badgeTextColor;
      37             :   final bool maskOut;
      38             :   final bool disabled;
      39             :   final bool badgeEdit;
      40             :   final String tooltip;
      41             :   final Widget? badgeIcon;
      42             :   final bool managed;
      43             : 
      44           1 :   @override
      45           1 :   _ProfileImageState createState() => _ProfileImageState();
      46             : 
      47           0 :   static ProfileImage fromContact(Settings settings, ContactInfoState contact, double diam) {
      48           0 :     return ProfileImage(
      49           0 :       imagePath: contact.imagePath,
      50           0 :       defaultImagePath: contact.defaultImagePath,
      51             :       diameter: diam,
      52           0 :       border: contact.getBorderColor(settings.theme),
      53           0 :       badgeColor: settings.theme.portraitContactBadgeColor,
      54           0 :       badgeTextColor: settings.theme.portraitContactBadgeTextColor,
      55             :     );
      56             :   }
      57             : 
      58           0 :   static ProfileImage fromProfile(Settings settings, ProfileInfoState profile, double diam) {
      59           0 :     return ProfileImage(
      60           0 :       imagePath: profile.imagePath,
      61           0 :       defaultImagePath: profile.defaultImagePath,
      62             :       diameter: diam,
      63           0 :       border: profile.getBorderColor(settings.theme),
      64           0 :       badgeColor: settings.theme.portraitContactBadgeColor,
      65           0 :       badgeTextColor: settings.theme.portraitContactBadgeTextColor,
      66             :     );
      67             :   }
      68             : }
      69             : 
      70             : class _ProfileImageState extends State<ProfileImage> {
      71           1 :   @override
      72             :   Widget build(BuildContext context) {
      73           2 :     var path = widget.imagePath;
      74             :     // preserve original behaviour where callees checked ImagePreviewsExperiment themselves
      75           5 :     if (!Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) && !widget.defaultImagePath.isEmpty) {
      76           0 :       path = widget.defaultImagePath;
      77             :     }
      78             : 
      79           3 :     var file = new File(widget.imagePath);
      80           1 :     var image = Image.file(
      81             :       file,
      82           4 :       cacheWidth: (4 * widget.diameter.floor()),
      83             :       filterQuality: FilterQuality.medium,
      84             :       fit: BoxFit.cover,
      85             :       alignment: Alignment.center,
      86             :       // We need some theme specific blending here...we might want to consider making this a theme level attribute
      87           2 :       colorBlendMode: !widget.maskOut
      88           4 :           ? Provider.of<Settings>(context).theme.mode == mode_dark
      89             :                 ? BlendMode.softLight
      90             :                 : BlendMode.darken
      91             :           : BlendMode.srcOut,
      92           3 :       color: Provider.of<Settings>(context).theme.portraitBackgroundColor,
      93             :       isAntiAlias: false,
      94           2 :       width: widget.diameter,
      95           2 :       height: widget.diameter,
      96           0 :       errorBuilder: (context, error, stackTrace) {
      97             :         // on android the above will fail for asset images, in which case try to load them the original way
      98           0 :         return Image.asset(
      99           0 :           widget.imagePath,
     100             :           filterQuality: FilterQuality.medium,
     101             :           // We need some theme specific blending here...we might want to consider making this a theme level attribute
     102           0 :           colorBlendMode: !widget.maskOut
     103           0 :               ? Provider.of<Settings>(context).theme.mode == mode_dark
     104             :                     ? BlendMode.softLight
     105             :                     : BlendMode.darken
     106             :               : BlendMode.srcOut,
     107           0 :           color: Provider.of<Settings>(context).theme.portraitBackgroundColor,
     108             :           isAntiAlias: true,
     109           0 :           width: widget.diameter,
     110           0 :           height: widget.diameter,
     111             :         );
     112             :       },
     113             :     );
     114             : 
     115           1 :     return RepaintBoundary(
     116           1 :       child: Stack(
     117           1 :         children: [
     118           2 :           widget.managed
     119           0 :               ? ClipRRect(
     120           0 :                   borderRadius: BorderRadiusGeometry.all(Radius.circular(8)),
     121             :                   clipBehavior: Clip.antiAlias,
     122           0 :                   child: Container(
     123           0 :                     width: widget.diameter,
     124           0 :                     height: widget.diameter,
     125           0 :                     color: widget.border,
     126           0 :                     foregroundDecoration: widget.disabled
     127           0 :                         ? BoxDecoration(
     128           0 :                             color: Provider.of<Settings>(context).theme.portraitBackgroundColor, //Colors.grey,
     129             :                             backgroundBlendMode: BlendMode.color, //saturation,
     130             :                           )
     131             :                         : null,
     132           0 :                     child: Padding(
     133             :                       padding: const EdgeInsets.all(2.0), //border size
     134           0 :                       child: ClipRRect(
     135           0 :                         borderRadius: BorderRadiusGeometry.all(Radius.circular(7)),
     136             :                         clipBehavior: Clip.antiAlias,
     137           0 :                         child: widget.tooltip == "" ? image : Tooltip(message: widget.tooltip, child: image),
     138             :                       ),
     139             :                     ),
     140             :                   ),
     141             :                 )
     142           1 :               : ClipOval(
     143             :                   clipBehavior: Clip.antiAlias,
     144           1 :                   child: Container(
     145           2 :                     width: widget.diameter,
     146           2 :                     height: widget.diameter,
     147           2 :                     color: widget.border,
     148           2 :                     foregroundDecoration: widget.disabled
     149           0 :                         ? BoxDecoration(
     150           0 :                             color: Provider.of<Settings>(context).theme.portraitBackgroundColor, //Colors.grey,
     151             :                             backgroundBlendMode: BlendMode.color, //saturation,
     152             :                           )
     153             :                         : null,
     154           1 :                     child: Padding(
     155             :                       padding: const EdgeInsets.all(2.0), //border size
     156           1 :                       child: ClipOval(
     157             :                         clipBehavior: Clip.antiAlias,
     158           3 :                         child: widget.tooltip == "" ? image : Tooltip(message: widget.tooltip, child: image),
     159             :                       ),
     160             :                     ),
     161             :                   ),
     162             :                 ),
     163             :           // badge
     164           1 :           Visibility(
     165           7 :             visible: widget.badgeIcon != null || widget.badgeEdit || widget.badgeCount > 0,
     166           1 :             child: Positioned(
     167             :               bottom: 0.0,
     168             :               right: 0.0,
     169           1 :               child: CircleAvatar(
     170           4 :                 radius: max(10.0, widget.diameter / 6.0),
     171           2 :                 backgroundColor: widget.badgeColor,
     172           2 :                 child: widget.badgeEdit
     173           0 :                     ? Icon(Icons.edit, color: widget.badgeTextColor)
     174           2 :                     : (widget.badgeIcon != null
     175           0 :                           ? widget.badgeIcon
     176          10 :                           : Text(widget.badgeCount > 99 ? "99+" : widget.badgeCount.toString(), style: TextStyle(color: widget.badgeTextColor, fontSize: 8.0))),
     177             :               ),
     178             :             ),
     179             :           ),
     180             :           // disabled center icon
     181           1 :           Visibility(
     182           2 :             visible: widget.disabled,
     183           1 :             child: Container(
     184           2 :               width: widget.diameter,
     185           2 :               height: widget.diameter,
     186           1 :               child: Center(
     187           7 :                 child: Icon(CwtchIcons.negative_heart_24px, size: widget.diameter / 1.5, color: Provider.of<Settings>(context).theme.portraitOfflineBorderColor),
     188             :               ),
     189             :             ),
     190             :           ),
     191             :         ],
     192             :       ),
     193             :     );
     194             :   }
     195             : }

Generated by: LCOV version 1.14