import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/views/profileserversview.dart'; import 'package:flutter/material.dart'; import 'package:cwtch/views/torstatusview.dart'; import 'package:cwtch/widgets/contactrow.dart'; import 'package:cwtch/widgets/profileimage.dart'; import 'package:cwtch/widgets/textfield.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import '../main.dart'; import '../settings.dart'; import 'addcontactview.dart'; import '../model.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'messageview.dart'; class ContactsView extends StatefulWidget { const ContactsView({Key? key}) : super(key: key); @override _ContactsViewState createState() => _ContactsViewState(); } // selectConversation can be called from anywhere to set the active conversation void selectConversation(BuildContext context, int handle) { // requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts var initialIndex = Provider.of(context, listen: false).contactList.getContact(handle)!.unreadMessages; Provider.of(context, listen: false).contactList.getContact(handle)!.unreadMessages = 0; // triggers update in Double/TripleColumnView Provider.of(context, listen: false).initialScrollIndex = initialIndex; Provider.of(context, listen: false).selectedConversation = handle; Provider.of(context, listen: false).selectedIndex = null; Provider.of(context, listen: false).hoveredIndex = -1; // if in singlepane mode, push to the stack var isLandscape = Provider.of(context, listen: false).isLandscape(context); if (Provider.of(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(context, handle); } void _pushMessageView(BuildContext context, int handle) { var profileOnion = Provider.of(context, listen: false).onion; Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext builderContext) { // assert we have an actual profile... // We need to listen for updates to the profile in order to update things like invitation message bubbles. var profile = Provider.of(builderContext).profs.getProfile(profileOnion)!; return MultiProvider( providers: [ ChangeNotifierProvider.value(value: profile), ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)!), ], builder: (context, child) => MessageView(), ); }, ), ); } class _ContactsViewState extends State { late TextEditingController ctrlrFilter; bool showSearchBar = false; @override void initState() { super.initState(); ctrlrFilter = new TextEditingController(text: Provider.of(context, listen: false).filter); } @override Widget build(BuildContext context) { return Scaffold( endDrawerEnableOpenDragGesture: false, drawerEnableOpenDragGesture: false, appBar: AppBar( title: RepaintBoundary( child: Row(children: [ ProfileImage( imagePath: Provider.of(context).imagePath, diameter: 42, border: Provider.of(context).current().portraitOnlineBorderColor, badgeTextColor: Colors.red, badgeColor: Colors.red, ), SizedBox( width: 10, ), Expanded( child: Text("%1 ยป %2".replaceAll("%1", Provider.of(context).nickname).replaceAll("%2", AppLocalizations.of(context)!.titleManageContacts), overflow: TextOverflow.ellipsis, style: TextStyle(color: Provider.of(context).current().mainTextColor))), ])), actions: getActions(context), ), floatingActionButton: FloatingActionButton( onPressed: _pushAddContact, tooltip: AppLocalizations.of(context)!.tooltipAddContact, child: const Icon(CwtchIcons.person_add_alt_1_24px), ), body: showSearchBar || Provider.of(context).isFiltered ? _buildFilterable() : _buildContactList()); } List getActions(context) { var actions = List.empty(growable: true); if (Provider.of(context).blockUnknownConnections) { actions.add(Tooltip(message: AppLocalizations.of(context)!.blockUnknownConnectionsEnabledDescription, child: Icon(CwtchIcons.block_unknown))); } // Copy profile onion actions.add(IconButton( icon: Icon(CwtchIcons.address_copy_2), tooltip: AppLocalizations.of(context)!.copyAddress, onPressed: () { Clipboard.setData(new ClipboardData(text: Provider.of(context, listen: false).onion)); })); // Manage known Servers if (Provider.of(context, listen: false).isExperimentEnabled(TapirGroupsExperiment) || Provider.of(context, listen: false).isExperimentEnabled(ServerManagementExperiment)) { actions.add(IconButton( icon: Icon(CwtchIcons.dns_24px), tooltip: AppLocalizations.of(context)!.manageKnownServersButton, onPressed: () { _pushServers(); })); } // Search contacts actions.add(IconButton( // need both conditions for displaying initial empty textfield and also allowing filters to be cleared if this widget gets lost/reset icon: Icon(showSearchBar || Provider.of(context).isFiltered ? Icons.search_off : Icons.search), onPressed: () { Provider.of(context, listen: false).filter = ""; setState(() { showSearchBar = !showSearchBar; }); })); return actions; } Widget _buildFilterable() { Widget txtfield = CwtchTextField( controller: ctrlrFilter, hintText: AppLocalizations.of(context)!.search, onChanged: (newVal) { Provider.of(context, listen: false).filter = newVal; }, ); return Column(children: [Padding(padding: EdgeInsets.all(8), child: txtfield), Expanded(child: _buildContactList())]); } Widget _buildContactList() { final tiles = Provider.of(context).filteredList().map((ContactInfoState contact) { return ChangeNotifierProvider.value(key: ValueKey(contact.profileOnion + "" + contact.onion), value: contact, builder: (_, __) => RepaintBoundary(child: ContactRow())); }); final divided = ListTile.divideTiles( context: context, tiles: tiles, ).toList(); return RepaintBoundary(child: ListView(children: divided)); } void _pushAddContact() { Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext bcontext) { return MultiProvider( providers: [ ChangeNotifierProvider.value(value: Provider.of(context)), ], child: AddContactView(), ); }, )); } void _pushServers() { var profile = Provider.of(context); Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) { return MultiProvider( providers: [ChangeNotifierProvider(create: (context) => profile), Provider.value(value: Provider.of(context))], child: ProfileServersView(), ); }, )); } }