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