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 : }
|