Line data Source code
1 : import 'dart:io';
2 :
3 : import 'package:cwtch/cwtch/cwtch.dart';
4 : import 'package:cwtch/cwtch_icons_icons.dart';
5 : import 'package:cwtch/models/appstate.dart';
6 : import 'package:cwtch/models/contact.dart';
7 : import 'package:cwtch/models/contactlist.dart';
8 : import 'package:cwtch/models/profile.dart';
9 : import 'package:cwtch/models/profilelist.dart';
10 : import 'package:cwtch/models/search.dart';
11 : import 'package:cwtch/views/globalsettingsview.dart';
12 : import 'package:cwtch/views/profileserversview.dart';
13 : import 'package:flutter/material.dart';
14 : import 'package:cwtch/widgets/contactrow.dart';
15 : import 'package:cwtch/widgets/profileimage.dart';
16 : import 'package:cwtch/widgets/textfield.dart';
17 : import 'package:flutter/services.dart';
18 : import 'package:provider/provider.dart';
19 : import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
20 : import '../config.dart';
21 : import '../main.dart';
22 : import '../models/redaction.dart';
23 : import '../settings.dart';
24 : import '../themes/opaque.dart';
25 : import 'addcontactview.dart';
26 : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
27 : import 'package:qr_flutter/qr_flutter.dart';
28 :
29 : import 'addeditprofileview.dart';
30 : import 'messageview.dart';
31 :
32 : enum ShareMenu { copyCode, qrcode }
33 :
34 : enum ProfileStatusMenu { available, away, busy, appearOnline, appearOffline, allowUnknownContacts, blockUnknownContacts, enableProfile, disableProfile, editProfile }
35 :
36 : class ContactsView extends StatefulWidget {
37 0 : const ContactsView({Key? key}) : super(key: key);
38 :
39 0 : @override
40 0 : _ContactsViewState createState() => _ContactsViewState();
41 : }
42 :
43 : // selectConversation can be called from anywhere to set the active conversation
44 0 : void selectConversation(BuildContext context, int handle, int? messageIndex) {
45 : int? index;
46 :
47 : if (messageIndex != null) {
48 : // this message is loaded
49 0 : Provider.of<SearchState>(context, listen: false).selectedSearchMessage = messageIndex;
50 0 : Provider.of<AppState>(context, listen: false).initialScrollIndex = messageIndex;
51 0 : EnvironmentConfig.debugLog("Looked up index $messageIndex");
52 : }
53 :
54 0 : if (handle == Provider.of<AppState>(context, listen: false).selectedConversation) {
55 : if (messageIndex != null) {
56 0 : Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: messageIndex, duration: Duration(milliseconds: 100));
57 : }
58 : return;
59 : }
60 :
61 : // requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
62 0 : var unread = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages;
63 0 : var previouslySelected = Provider.of<AppState>(context, listen: false).selectedConversation;
64 : if (previouslySelected != null) {
65 0 : Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(previouslySelected)!.unselected();
66 : }
67 0 : Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.selected();
68 :
69 : // triggers update in Double/TripleColumnView
70 0 : Provider.of<ContactInfoState>(context, listen: false).hoveredIndex = -1;
71 0 : Provider.of<AppState>(context, listen: false).selectedConversation = handle;
72 :
73 : // if in singlepane mode, push to the stack
74 0 : var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
75 0 : if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(context, handle);
76 :
77 : // Set last message seen time in backend
78 0 : Provider.of<FlwtchState>(context, listen: false)
79 0 : .cwtch
80 0 : .SetConversationAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, handle, LastMessageSeenTimeKey, DateTime.now().toUtc().toIso8601String());
81 : }
82 :
83 0 : void _pushMessageView(BuildContext context, int handle) {
84 0 : var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
85 :
86 0 : Navigator.of(context).push(
87 0 : PageRouteBuilder(
88 0 : settings: RouteSettings(name: "messages"),
89 0 : pageBuilder: (builderContext, a1, a2) {
90 0 : var profile = Provider.of<FlwtchState>(builderContext).profs.getProfile(profileOnion)!;
91 0 : return MultiProvider(
92 0 : providers: [
93 0 : ChangeNotifierProvider.value(value: profile),
94 0 : ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)!),
95 0 : ChangeNotifierProvider.value(value: profile.searchState),
96 : ],
97 0 : builder: (context, child) => MessageView(),
98 : );
99 : },
100 0 : transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
101 0 : transitionDuration: Duration(milliseconds: 200),
102 : ),
103 : );
104 : }
105 :
106 : class _ContactsViewState extends State<ContactsView> {
107 : late TextEditingController ctrlrFilter;
108 : final scaffoldKey = GlobalKey<ScaffoldMessengerState>();
109 :
110 0 : @override
111 : void initState() {
112 0 : super.initState();
113 0 : ctrlrFilter = new TextEditingController(text: Provider.of<SearchState>(context, listen: false).filter);
114 : }
115 :
116 0 : @override
117 : Widget build(BuildContext context) {
118 0 : var enabled = Provider.of<ProfileInfoState>(context, listen: false).enabled;
119 0 : var appearOffline = Provider.of<ProfileInfoState>(context, listen: false).appearOffline;
120 0 : var settings = Provider.of<Settings>(context, listen: false);
121 0 : if (!Provider.of<SearchState>(context).searchActive) {
122 0 : ctrlrFilter.text = "";
123 : }
124 0 : return ScaffoldMessenger(
125 0 : key: scaffoldKey,
126 0 : child: Scaffold(
127 0 : backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
128 : endDrawerEnableOpenDragGesture: false,
129 : drawerEnableOpenDragGesture: false,
130 0 : appBar: AppBar(
131 0 : leading: Stack(children: [
132 0 : Align(
133 : alignment: Alignment.center,
134 0 : child: IconButton(
135 0 : icon: Icon(Icons.arrow_back),
136 0 : tooltip: MaterialLocalizations.of(context).backButtonTooltip,
137 0 : onPressed: () {
138 0 : if (Provider.of<SearchState>(context, listen: false).searchActive) {
139 0 : Provider.of<SearchState>(context, listen: false).clearSearch();
140 : } else {
141 0 : Provider.of<ProfileInfoState>(context, listen: false).recountUnread();
142 0 : Provider.of<AppState>(context, listen: false).selectedProfile = "";
143 0 : Navigator.of(context).pop();
144 : }
145 : },
146 : )),
147 0 : Positioned(
148 : bottom: 5.0,
149 : right: 5.0,
150 0 : child: StreamBuilder<bool>(
151 0 : stream: Provider.of<AppState>(context).getUnreadProfileNotifyStream(),
152 0 : builder: (BuildContext context, AsyncSnapshot<bool> unreadCountSnapshot) {
153 0 : int unreadCount = Provider.of<ProfileListState>(context).generateUnreadCount(Provider.of<AppState>(context).selectedProfile ?? "");
154 :
155 0 : return Visibility(
156 0 : visible: unreadCount > 0,
157 0 : child: CircleAvatar(
158 : radius: 10.0,
159 0 : backgroundColor: Provider.of<Settings>(context).theme.portraitProfileBadgeColor,
160 : child:
161 0 : Text(unreadCount > 99 ? "99+" : unreadCount.toString(), style: TextStyle(color: Provider.of<Settings>(context).theme.portraitProfileBadgeTextColor, fontSize: 8.0)),
162 : ));
163 : }),
164 : )
165 : ]),
166 0 : title: Row(children: [
167 0 : PopupMenuButton<ProfileStatusMenu>(
168 0 : icon: ProfileImage(
169 0 : imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
170 0 : ? Provider.of<ProfileInfoState>(context).imagePath
171 0 : : Provider.of<ProfileInfoState>(context).defaultImagePath,
172 : diameter: 42,
173 0 : border: Provider.of<ProfileInfoState>(context).getBorderColor(Provider.of<Settings>(context).theme),
174 : badgeTextColor: Colors.red,
175 : badgeColor: Colors.red,
176 : ),
177 : iconSize: 42,
178 0 : tooltip: AppLocalizations.of(context)!.availabilityStatusTooltip,
179 0 : splashRadius: Material.defaultSplashRadius / 2,
180 0 : onSelected: (ProfileStatusMenu item) {
181 0 : String onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
182 : switch (item) {
183 0 : case ProfileStatusMenu.available:
184 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-status", "available");
185 : break;
186 0 : case ProfileStatusMenu.away:
187 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-status", "away");
188 : break;
189 0 : case ProfileStatusMenu.busy:
190 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-status", "busy");
191 : break;
192 0 : case ProfileStatusMenu.appearOffline:
193 0 : Provider.of<ProfileInfoState>(context, listen: false).appearOffline = true;
194 0 : Provider.of<ProfileInfoState>(context, listen: false).deactivatePeerEngine(context);
195 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.ConfigureConnections(onion, false, false, false);
196 : break;
197 0 : case ProfileStatusMenu.editProfile:
198 0 : Navigator.of(context).push(
199 0 : PageRouteBuilder(
200 0 : pageBuilder: (bcontext, a1, a2) {
201 0 : return MultiProvider(
202 0 : providers: [
203 0 : ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context, listen: false)),
204 : ],
205 0 : builder: (context, widget) => AddEditProfileView(key: Key('addprofile')),
206 : );
207 : },
208 0 : transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
209 0 : transitionDuration: Duration(milliseconds: 200),
210 : ),
211 : );
212 : break;
213 0 : case ProfileStatusMenu.appearOnline:
214 0 : Provider.of<ProfileInfoState>(context, listen: false).appearOffline = false;
215 : // we only need to toggle all connections on here..
216 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.ConfigureConnections(onion, true, true, true);
217 : break;
218 0 : case ProfileStatusMenu.allowUnknownContacts:
219 0 : settings.blockUnknownConnections = false;
220 0 : saveSettings(context);
221 : break;
222 0 : case ProfileStatusMenu.blockUnknownContacts:
223 0 : settings.blockUnknownConnections = true;
224 0 : saveSettings(context);
225 : break;
226 0 : case ProfileStatusMenu.enableProfile:
227 0 : Provider.of<ProfileInfoState>(context, listen: false).enabled = true;
228 0 : if (Provider.of<ProfileInfoState>(context, listen: false).appearOffline) {
229 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.ConfigureConnections(onion, false, false, false);
230 : } else {
231 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.ConfigureConnections(onion, true, true, true);
232 : }
233 : break;
234 0 : case ProfileStatusMenu.disableProfile:
235 0 : Provider.of<ProfileInfoState>(context, listen: false).enabled = false;
236 0 : Provider.of<ProfileInfoState>(context, listen: false).deactivatePeerEngine(context);
237 : break;
238 : }
239 : },
240 0 : itemBuilder: (BuildContext context) => <PopupMenuEntry<ProfileStatusMenu>>[
241 0 : PopupMenuItem<ProfileStatusMenu>(
242 : value: ProfileStatusMenu.available,
243 : enabled: enabled,
244 0 : child: Row(children: [
245 0 : Icon(
246 : CwtchIcons.account_circle_24px,
247 : color: Colors.white,
248 : ),
249 0 : Expanded(
250 0 : child: Text(AppLocalizations.of(context)!.availabilityStatusAvailable,
251 0 : textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
252 : ]),
253 : ),
254 0 : PopupMenuItem<ProfileStatusMenu>(
255 : value: ProfileStatusMenu.away,
256 : enabled: enabled,
257 0 : child: Row(children: [
258 0 : Icon(
259 : CwtchIcons.account_circle_24px,
260 : color: Colors.yellowAccent,
261 : ),
262 0 : Expanded(
263 0 : child: Text(AppLocalizations.of(context)!.availabilityStatusAway,
264 0 : textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
265 : ]),
266 : ),
267 0 : PopupMenuItem<ProfileStatusMenu>(
268 : value: ProfileStatusMenu.busy,
269 : enabled: enabled,
270 0 : child: Row(children: [
271 0 : Icon(
272 : CwtchIcons.account_circle_24px,
273 : color: Colors.redAccent,
274 : ),
275 0 : Expanded(
276 0 : child: Text(AppLocalizations.of(context)!.availabilityStatusBusy,
277 0 : textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
278 : ]),
279 : ),
280 0 : PopupMenuDivider(),
281 0 : PopupMenuItem<ProfileStatusMenu>(
282 : value: ProfileStatusMenu.appearOffline,
283 : enabled: enabled && !appearOffline,
284 0 : child: Row(children: [
285 0 : Icon(CwtchIcons.disconnect_from_contact),
286 0 : Expanded(
287 0 : child: Text(AppLocalizations.of(context)!.profileAppearOffline,
288 0 : textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
289 : ]),
290 : ),
291 0 : PopupMenuItem<ProfileStatusMenu>(
292 : value: ProfileStatusMenu.appearOnline,
293 : enabled: enabled && appearOffline,
294 0 : child: Row(children: [
295 0 : Icon(CwtchIcons.disconnect_from_contact),
296 0 : Expanded(
297 0 : child: Text(AppLocalizations.of(context)!.profileAppearOnline,
298 0 : textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
299 : ]),
300 : ),
301 0 : PopupMenuDivider(),
302 0 : PopupMenuItem<ProfileStatusMenu>(
303 0 : value: !settings.blockUnknownConnections ? ProfileStatusMenu.blockUnknownContacts : ProfileStatusMenu.allowUnknownContacts,
304 0 : child: Row(children: [
305 0 : Icon(
306 : CwtchIcons.block_unknown,
307 0 : color: settings.theme.mainTextColor,
308 : ),
309 0 : Expanded(
310 0 : child: Text((settings.blockUnknownConnections ? AppLocalizations.of(context)!.profileAllowUnknownContacts : AppLocalizations.of(context)!.profileBlockUnknownContacts),
311 0 : textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
312 : ]),
313 : ),
314 0 : PopupMenuDivider(),
315 0 : PopupMenuItem<ProfileStatusMenu>(
316 : value: enabled ? ProfileStatusMenu.disableProfile : ProfileStatusMenu.enableProfile,
317 0 : child: Row(children: [
318 0 : Icon(CwtchIcons.favorite_24dp, color: settings.theme.mainTextColor),
319 0 : Expanded(
320 0 : child: Text((enabled ? AppLocalizations.of(context)!.profileDisableProfile : AppLocalizations.of(context)!.profileEnableProfile),
321 0 : textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
322 : ]),
323 : ),
324 0 : PopupMenuItem<ProfileStatusMenu>(
325 : value: ProfileStatusMenu.editProfile,
326 : enabled: true,
327 0 : child: Row(children: [
328 0 : Icon(
329 : CwtchIcons.edit_24px,
330 0 : color: settings.theme.mainTextColor,
331 : ),
332 0 : Expanded(
333 0 : child: Text(AppLocalizations.of(context)!.editProfile, textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
334 : ]),
335 : ),
336 : ],
337 : ),
338 0 : SizedBox(
339 : width: 10,
340 : ),
341 0 : Expanded(
342 0 : child: Text(
343 : "%1 ยป %2"
344 0 : .replaceAll("%1", redactedNick(context, Provider.of<ProfileInfoState>(context).onion, Provider.of<ProfileInfoState>(context).nickname))
345 0 : .replaceAll("%2", AppLocalizations.of(context)!.titleManageContacts),
346 : overflow: TextOverflow.ellipsis,
347 0 : style: TextStyle(
348 0 : color: Provider.of<Settings>(context).current().mainTextColor,
349 : fontFamily: "Inter",
350 : fontWeight: FontWeight.bold,
351 0 : fontSize: 14.0 * Provider.of<Settings>(context).fontScaling))),
352 : ]),
353 0 : actions: getActions(context),
354 : ),
355 0 : floatingActionButton: FloatingActionButton(
356 0 : onPressed: _modalAddImportChoice,
357 0 : tooltip: AppLocalizations.of(context)!.tooltipAddContact,
358 0 : child: Icon(
359 : CwtchIcons.person_add_alt_1_24px,
360 0 : color: Provider.of<Settings>(context).theme.defaultButtonTextColor,
361 : ),
362 : ),
363 0 : body: Provider.of<SearchState>(context).searchOrFiltered ? _buildFilterable() : _buildContactList()));
364 : }
365 :
366 0 : List<Widget> getActions(context) {
367 0 : var actions = List<Widget>.empty(growable: true);
368 0 : if (Provider.of<Settings>(context).blockUnknownConnections) {
369 0 : actions.add(Tooltip(message: AppLocalizations.of(context)!.blockUnknownConnectionsEnabledDescription, child: Icon(CwtchIcons.block_unknown)));
370 : }
371 :
372 0 : if (Provider.of<Settings>(context, listen: false).isExperimentEnabled(QRCodeExperiment)) {
373 0 : actions.add(PopupMenuButton<ShareMenu>(
374 0 : icon: Icon(CwtchIcons.address_copy),
375 0 : tooltip: AppLocalizations.of(context)!.shareProfileMenuTooltop,
376 0 : splashRadius: Material.defaultSplashRadius / 2,
377 0 : onSelected: (ShareMenu item) {
378 : switch (item) {
379 0 : case ShareMenu.copyCode:
380 : {
381 0 : Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
382 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
383 0 : scaffoldKey.currentState?.showSnackBar(snackBar);
384 : }
385 : break;
386 0 : case ShareMenu.qrcode:
387 : {
388 0 : _showQRCode("cwtch:" + Provider.of<ProfileInfoState>(context, listen: false).onion);
389 : }
390 : break;
391 : }
392 : },
393 0 : itemBuilder: (BuildContext context) => <PopupMenuEntry<ShareMenu>>[
394 0 : PopupMenuItem<ShareMenu>(
395 : value: ShareMenu.copyCode,
396 0 : child: Text(AppLocalizations.of(context)!.copyAddress, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)),
397 : ),
398 0 : PopupMenuItem<ShareMenu>(
399 : value: ShareMenu.qrcode,
400 0 : child: Text(AppLocalizations.of(context)!.shareMenuQRCode, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)),
401 : ),
402 : ],
403 : ));
404 : } else {
405 0 : actions.add(IconButton(
406 0 : icon: Icon(CwtchIcons.address_copy),
407 0 : tooltip: AppLocalizations.of(context)!.copyAddress,
408 0 : splashRadius: Material.defaultSplashRadius / 2,
409 0 : onPressed: () {
410 0 : Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
411 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
412 0 : scaffoldKey.currentState?.showSnackBar(snackBar);
413 : }));
414 : }
415 :
416 : // Manage known Servers
417 0 : if (Provider.of<Settings>(context, listen: false).isExperimentEnabled(TapirGroupsExperiment) || Provider.of<Settings>(context, listen: false).isExperimentEnabled(ServerManagementExperiment)) {
418 0 : actions.add(IconButton(
419 0 : icon: Icon(CwtchIcons.dns_24px),
420 0 : tooltip: AppLocalizations.of(context)!.manageKnownServersButton,
421 0 : splashRadius: Material.defaultSplashRadius / 2,
422 0 : onPressed: () {
423 0 : _pushServers();
424 : }));
425 : }
426 :
427 : // Search contacts
428 0 : actions.add(IconButton(
429 : // need both conditions for displaying initial empty textfield and also allowing filters to be cleared if this widget gets lost/reset
430 0 : icon: Icon(Provider.of<SearchState>(context).searchOrFiltered ? Icons.search_off : Icons.search),
431 0 : splashRadius: Material.defaultSplashRadius / 2,
432 0 : onPressed: () {
433 0 : Provider.of<SearchState>(context, listen: false).toggleSearch();
434 : }));
435 : return actions;
436 : }
437 :
438 0 : Widget _buildFilterable() {
439 0 : Widget txtfield = CwtchTextField(
440 0 : controller: ctrlrFilter,
441 0 : hintText: AppLocalizations.of(context)!.search,
442 : autofocus: true,
443 0 : onChanged: (newVal) {
444 0 : String profileHandle = Provider.of<ProfileInfoState>(context, listen: false).onion;
445 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SearchConversations(profileHandle, newVal).then((value) {
446 0 : Provider.of<SearchState>(context, listen: false).newSearch(value);
447 : });
448 0 : Provider.of<SearchState>(context, listen: false).filter = newVal;
449 : },
450 : );
451 0 : return Column(children: [Padding(padding: EdgeInsets.all(8), child: txtfield), Expanded(child: _buildContactList())]);
452 : }
453 :
454 0 : Widget _buildContactList() {
455 0 : var tilesSearchResult = Provider.of<ProfileInfoState>(context).filteredList().map((ContactInfoState contact) {
456 0 : return MultiProvider(
457 0 : providers: [
458 0 : ChangeNotifierProvider.value(value: contact),
459 0 : ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).serverList),
460 : ],
461 0 : builder: (context, child) => ContactRow(),
462 : );
463 : });
464 :
465 : var initialScroll =
466 0 : Provider.of<ProfileInfoState>(context, listen: false).filteredList().indexWhere((element) => element.identifier == Provider.of<AppState>(context).selectedConversation);
467 0 : if (initialScroll < 0) {
468 : initialScroll = 0;
469 : }
470 :
471 0 : if (Provider.of<SearchState>(context).searchActive) {
472 0 : List<SearchResult> searchResults = Provider.of<SearchState>(context).activeSearchResults;
473 0 : tilesSearchResult = searchResults.map((SearchResult searchResult) {
474 0 : return MultiProvider(
475 0 : providers: [
476 0 : ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(searchResult.conversationIdentifier)),
477 0 : ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).serverList),
478 : ],
479 0 : builder: (context, child) => ContactRow(messageIndex: searchResult.messageIndex),
480 : );
481 : });
482 : } else {}
483 :
484 0 : var contactList = ScrollablePositionedList.separated(
485 0 : itemScrollController: Provider.of<ProfileInfoState>(context).contactListScrollController,
486 0 : itemCount: tilesSearchResult.length,
487 : initialScrollIndex: initialScroll,
488 : shrinkWrap: true,
489 0 : physics: BouncingScrollPhysics(),
490 0 : semanticChildCount: tilesSearchResult.length,
491 0 : itemBuilder: (context, index) {
492 0 : if (tilesSearchResult.length > index) {
493 0 : return tilesSearchResult.elementAt(index);
494 : }
495 0 : return Container();
496 : },
497 0 : separatorBuilder: (BuildContext context, int index) {
498 0 : return Divider(height: 1);
499 : },
500 : );
501 :
502 : return contactList;
503 : }
504 :
505 0 : void _pushAddContact(bool newGroup) {
506 : // close modal
507 0 : Navigator.popUntil(context, (route) => route.settings.name == "conversations");
508 :
509 0 : Navigator.of(context).push(
510 0 : PageRouteBuilder(
511 0 : settings: RouteSettings(name: "addcontact"),
512 0 : pageBuilder: (builderContext, a1, a2) {
513 0 : return MultiProvider(
514 0 : providers: [
515 0 : ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
516 : ],
517 0 : child: AddContactView(newGroup: newGroup),
518 : );
519 : },
520 0 : transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
521 0 : transitionDuration: Duration(milliseconds: 200),
522 : ),
523 : );
524 : }
525 :
526 0 : void _pushServers() {
527 0 : var profileInfoState = Provider.of<ProfileInfoState>(context, listen: false);
528 :
529 0 : Navigator.of(context).push(
530 0 : PageRouteBuilder(
531 0 : settings: RouteSettings(name: "profileremoteservers"),
532 0 : pageBuilder: (bcontext, a1, a2) {
533 0 : return MultiProvider(
534 0 : providers: [ChangeNotifierProvider.value(value: profileInfoState), Provider.value(value: Provider.of<FlwtchState>(context))],
535 0 : child: ProfileServersView(),
536 : );
537 : },
538 0 : transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
539 0 : transitionDuration: Duration(milliseconds: 200),
540 : ),
541 : );
542 : }
543 :
544 0 : void _modalAddImportChoice() {
545 0 : bool groupsEnabled = Provider.of<Settings>(context, listen: false).isExperimentEnabled(TapirGroupsExperiment);
546 :
547 0 : showModalBottomSheet<void>(
548 0 : context: context,
549 : isScrollControlled: true,
550 0 : builder: (BuildContext context) {
551 0 : return Padding(
552 0 : padding: MediaQuery.of(context).viewInsets,
553 0 : child: RepaintBoundary(
554 0 : child: Container(
555 0 : height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
556 0 : child: Center(
557 0 : child: Padding(
558 0 : padding: EdgeInsets.all(2.0),
559 0 : child: Column(
560 : mainAxisAlignment: MainAxisAlignment.center,
561 : crossAxisAlignment: CrossAxisAlignment.center,
562 : mainAxisSize: MainAxisSize.max,
563 0 : children: <Widget>[
564 0 : SizedBox(
565 : height: 20,
566 : ),
567 0 : Expanded(
568 0 : child: Tooltip(
569 0 : message: AppLocalizations.of(context)!.tooltipAddContact,
570 0 : child: ElevatedButton(
571 0 : style: ElevatedButton.styleFrom(
572 0 : minimumSize: Size.fromWidth(399),
573 0 : maximumSize: Size.fromWidth(400),
574 0 : shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
575 : ),
576 0 : child: Text(
577 0 : AppLocalizations.of(context)!.addContact,
578 0 : semanticsLabel: AppLocalizations.of(context)!.addContact,
579 : textAlign: TextAlign.center,
580 0 : style: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, fontWeight: FontWeight.bold),
581 : ),
582 0 : onPressed: () {
583 0 : _pushAddContact(false);
584 : },
585 : ))),
586 0 : SizedBox(
587 : height: 20,
588 : ),
589 0 : Expanded(
590 0 : child: Tooltip(
591 0 : message: groupsEnabled ? AppLocalizations.of(context)!.addServerTooltip : AppLocalizations.of(context)!.thisFeatureRequiresGroupExpermientsToBeEnabled,
592 0 : child: ElevatedButton(
593 0 : style: ElevatedButton.styleFrom(
594 0 : minimumSize: Size.fromWidth(399),
595 0 : maximumSize: Size.fromWidth(400),
596 0 : shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
597 : ),
598 0 : child: Text(
599 0 : AppLocalizations.of(context)!.addServerTitle,
600 0 : semanticsLabel: AppLocalizations.of(context)!.addServerTitle,
601 : textAlign: TextAlign.center,
602 0 : style: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, fontWeight: FontWeight.bold),
603 : ),
604 : onPressed: groupsEnabled
605 0 : ? () {
606 0 : _pushAddContact(false);
607 : }
608 : : null,
609 : )),
610 : ),
611 0 : SizedBox(
612 : height: 20,
613 : ),
614 0 : Expanded(
615 0 : child: Tooltip(
616 0 : message: groupsEnabled ? AppLocalizations.of(context)!.createGroupTitle : AppLocalizations.of(context)!.thisFeatureRequiresGroupExpermientsToBeEnabled,
617 0 : child: ElevatedButton(
618 0 : style: ElevatedButton.styleFrom(
619 0 : minimumSize: Size.fromWidth(399),
620 0 : maximumSize: Size.fromWidth(400),
621 0 : shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
622 : ),
623 0 : child: Text(
624 0 : AppLocalizations.of(context)!.createGroupTitle,
625 0 : semanticsLabel: AppLocalizations.of(context)!.createGroupTitle,
626 : textAlign: TextAlign.center,
627 0 : style: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, fontWeight: FontWeight.bold),
628 : ),
629 : onPressed: groupsEnabled
630 0 : ? () {
631 0 : _pushAddContact(true);
632 : }
633 : : null,
634 : ))),
635 0 : SizedBox(
636 : height: 20,
637 : ),
638 : ],
639 : ))),
640 : )));
641 : });
642 : }
643 :
644 0 : void _showQRCode(String profileCode) {
645 0 : showModalBottomSheet<dynamic>(
646 0 : context: context,
647 0 : builder: (BuildContext context) {
648 0 : return Wrap(children: <Widget>[
649 0 : Center(
650 0 : child: QrImageView(
651 : data: profileCode,
652 : version: QrVersions.auto,
653 : size: 400.0,
654 0 : backgroundColor: Provider.of<Settings>(context).theme.backgroundPaneColor,
655 0 : foregroundColor: Provider.of<Settings>(context).theme.mainTextColor,
656 : gapless: false,
657 : ),
658 : )
659 : ]);
660 : },
661 : );
662 : }
663 : }
|