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:flutter_gen/gen_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 0 : bool selected = Provider.of<AppState>(context).selectedConversation == contact.identifier;
56 0 : if (selected && widget.messageIndex != null) {
57 0 : if (selected && widget.messageIndex == Provider.of<SearchState>(context).selectedSearchMessage) {
58 : selected = true;
59 : } else {
60 : selected = false;
61 : }
62 : }
63 0 : return InkWell(
64 : enableFeedback: true,
65 : splashFactory: InkSplash.splashFactory,
66 0 : child: Ink(
67 0 : color: selected ? (Provider.of<Settings>(context).theme.backgroundHilightElementColor as Color).withOpacity(0.8) : Colors.transparent,
68 0 : child: Container(
69 0 : child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [
70 0 : Padding(
71 : padding: const EdgeInsets.all(6.0), //border size
72 0 : child: ProfileImage(
73 0 : badgeCount: contact.unreadMessages,
74 0 : badgeColor: Provider.of<Settings>(context).theme.portraitContactBadgeColor,
75 0 : badgeTextColor: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor,
76 : diameter: 64.0,
77 0 : imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? contact.imagePath : contact.defaultImagePath,
78 0 : disabled: !contact.isOnline(),
79 0 : border: contact.getBorderColor(Provider.of<Settings>(context).theme),
80 : )),
81 0 : Expanded(
82 0 : child: Padding(
83 0 : padding: EdgeInsets.all(6.0),
84 0 : child: Column(
85 : crossAxisAlignment: CrossAxisAlignment.start,
86 : mainAxisAlignment: MainAxisAlignment.spaceBetween,
87 : mainAxisSize: MainAxisSize.min,
88 0 : children: [
89 0 : Container(
90 : clipBehavior: Clip.hardEdge,
91 0 : height: Provider.of<Settings>(context).fontScaling * 14.0 + 5.0,
92 0 : decoration: BoxDecoration(),
93 0 : child: Text(
94 0 : contact.augmentedNickname(context).trim() + (contact.messageDraft.isEmpty() ? "" : "*"),
95 0 : style: TextStyle(
96 0 : fontSize: Provider.of<Settings>(context).fontScaling * 14.0,
97 : fontFamily: "Inter",
98 : fontWeight: FontWeight.bold,
99 0 : color: contact.isBlocked
100 0 : ? Provider.of<Settings>(context).theme.portraitBlockedTextColor
101 0 : : Provider.of<Settings>(context).theme.mainTextColor), //Provider.of<FlwtchState>(context).biggerFont,
102 : softWrap: true,
103 : overflow: TextOverflow.clip,
104 : maxLines: 1,
105 : )),
106 : syncStatus ??
107 0 : SizedBox(
108 : width: 0,
109 : height: 0,
110 : ),
111 : // we need to ignore the child widget in this context, otherwise gesture events will flow down...
112 0 : IgnorePointer(
113 : ignoring: true,
114 0 : child: Visibility(
115 0 : visible: this.cachedMessage != null,
116 : maintainSize: false,
117 : maintainInteractivity: false,
118 : maintainSemantics: false,
119 : maintainState: false,
120 0 : child: this.cachedMessage == null ? CircularProgressIndicator() : this.cachedMessage!.getPreviewWidget(context),
121 : )),
122 0 : Container(
123 0 : padding: EdgeInsets.all(0),
124 0 : height: contact.isInvitation ? Provider.of<Settings>(context).fontScaling * 14.0 + 35.0 : Provider.of<Settings>(context).fontScaling * 14.0 + 5.0,
125 0 : child: contact.isInvitation == true
126 0 : ? FittedBox(
127 : fit: BoxFit.scaleDown,
128 0 : child: Wrap(children: <Widget>[
129 0 : Padding(
130 0 : padding: EdgeInsets.all(2),
131 0 : child: TextButton.icon(
132 0 : label: Text(
133 0 : AppLocalizations.of(context)!.tooltipAcceptContactRequest,
134 0 : style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle),
135 : ),
136 0 : icon: Icon(
137 : Icons.favorite,
138 : size: 16,
139 0 : color: Provider.of<Settings>(context).theme.mainTextColor,
140 : ),
141 0 : onPressed: _btnApprove,
142 : )),
143 0 : Padding(
144 0 : padding: EdgeInsets.all(2),
145 0 : child: TextButton.icon(
146 0 : label: Text(
147 0 : AppLocalizations.of(context)!.tooltipRejectContactRequest,
148 0 : style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle),
149 : ),
150 0 : style: ButtonStyle(
151 0 : backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor),
152 0 : foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor)),
153 0 : icon: Icon(Icons.delete, size: 16, color: Provider.of<Settings>(context).theme.mainTextColor),
154 0 : onPressed: _btnReject,
155 : ))
156 : ]))
157 0 : : (contact.isBlocked
158 0 : ? IconButton(
159 0 : padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 0.0),
160 0 : splashRadius: Material.defaultSplashRadius / 2,
161 : iconSize: 16,
162 0 : icon: Icon(Icons.block, color: Provider.of<Settings>(context).theme.mainTextColor),
163 : onPressed: null,
164 : )
165 0 : : Text(prettyDateString(context, widget.messageIndex == null ? contact.lastMessageSentTime : (this.cachedMessage?.getMetadata().timestamp ?? DateTime.now())),
166 0 : style: Provider.of<Settings>(context).scaleFonts(TextStyle(
167 : fontSize: 12.0,
168 : fontFamily: "Inter",
169 : ))))),
170 0 : Visibility(
171 0 : visible: !Provider.of<Settings>(context).streamerMode,
172 0 : child: Container(
173 0 : padding: EdgeInsets.all(0),
174 0 : height: Provider.of<Settings>(context).fontScaling * 13.0 + 5.0,
175 0 : child: FittedBox(
176 : fit: BoxFit.scaleDown,
177 0 : child: Text(
178 0 : contact.onion,
179 : overflow: TextOverflow.ellipsis,
180 0 : style: Provider.of<Settings>(context).scaleFonts(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 0 : Visibility(
189 : // only allow pinning for non-blocked and accepted conversations,
190 0 : visible: contact.isAccepted() && (Platform.isAndroid || (!Platform.isAndroid && isHover) || contact.pinned),
191 0 : child: IconButton(
192 0 : tooltip: contact.pinned ? AppLocalizations.of(context)!.tooltipUnpinConversation : AppLocalizations.of(context)!.tooltipPinConversation,
193 0 : icon: Icon(
194 0 : contact.pinned ? Icons.push_pin : Icons.push_pin_outlined,
195 0 : color: Provider.of<Settings>(context).theme.mainTextColor,
196 : ),
197 0 : onPressed: () {
198 0 : if (contact.pinned) {
199 0 : contact.unpin(context);
200 : } else {
201 0 : contact.pin(context);
202 : }
203 0 : Provider.of<ContactListState>(context, listen: false).resort();
204 : },
205 : ))
206 : ]))),
207 0 : onTap: () {
208 0 : setState(() {
209 0 : selectConversation(context, contact.identifier, widget.messageIndex);
210 : });
211 : },
212 0 : onHover: (hover) {
213 0 : if (isHover != hover) {
214 0 : setState(() {
215 0 : isHover = hover;
216 : });
217 : }
218 : },
219 : );
220 : }
221 :
222 0 : void _btnApprove() {
223 0 : Provider.of<ContactInfoState>(context, listen: false).accepted = true;
224 0 : Provider.of<FlwtchState>(context, listen: false)
225 0 : .cwtch
226 0 : .AcceptContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier);
227 : }
228 :
229 0 : void _btnReject() {
230 0 : Provider.of<ContactInfoState>(context, listen: false).blocked = true;
231 0 : ContactInfoState contact = Provider.of<ContactInfoState>(context, listen: false);
232 0 : if (contact.isGroup == true) {
233 : // FIXME This flow is incorrect. Groups never just show up on the contact list anymore
234 0 : Provider.of<ProfileInfoState>(context, listen: false).removeContact(contact.onion);
235 : } else {
236 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.BlockContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, contact.identifier);
237 : }
238 : }
239 : }
|