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