LCOV - code coverage report
Current view: top level - lib/views - addcontactview.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 112 0.0 %
Date: 2026-07-15 19:28:30 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:cwtch/cwtch_icons_icons.dart';
       2             : import 'package:cwtch/models/profile.dart';
       3             : import 'package:cwtch/models/remoteserver.dart';
       4             : import 'package:flutter/material.dart';
       5             : import 'package:flutter/services.dart';
       6             : import 'package:cwtch/errorHandler.dart';
       7             : import 'package:cwtch/settings.dart';
       8             : import 'package:cwtch/widgets/buttontextfield.dart';
       9             : import 'package:cwtch/widgets/cwtchlabel.dart';
      10             : import 'package:cwtch/widgets/textfield.dart';
      11             : import 'package:cwtch/l10n/app_localizations.dart';
      12             : import 'package:provider/provider.dart';
      13             : 
      14             : import '../main.dart';
      15             : 
      16             : /// Add Contact View is the one-stop shop for adding public keys to a Profiles contact list.
      17             : /// We support both Peers and Groups (experiment-pending).
      18             : /// NOTE: This view makes use of the global Error Handler to receive events from the Cwtch Library (for validating
      19             : /// error states caused by incorrect import string or duplicate requests to add a specific contact)
      20             : class AddContactView extends StatefulWidget {
      21             :   final newGroup;
      22             : 
      23           0 :   const AddContactView({Key? key, this.newGroup}) : super(key: key);
      24             : 
      25           0 :   @override
      26           0 :   _AddContactViewState createState() => _AddContactViewState();
      27             : }
      28             : 
      29             : class _AddContactViewState extends State<AddContactView> {
      30             :   final _formKey = GlobalKey<FormState>();
      31             :   final _createGroupFormKey = GlobalKey<FormState>();
      32             :   final ctrlrOnion = TextEditingController(text: "");
      33             :   final ctrlrContact = TextEditingController(text: "");
      34             :   final ctrlrGroupName = TextEditingController(text: "");
      35             :   String server = "";
      36             :   // flutter textfield onChange often fires twice and since we need contexts, we can't easily use a controler/listener
      37             :   String lastContactValue = "";
      38             :   bool failedImport = false;
      39             : 
      40           0 :   @override
      41             :   Widget build(BuildContext context) {
      42             :     //  if we haven't picked a server yet, pick the first one in the list...
      43           0 :     if (server.isEmpty && Provider.of<ProfileInfoState>(context).serverList.servers.isNotEmpty) {
      44           0 :       server = Provider.of<ProfileInfoState>(context).serverList.servers.first.onion;
      45             :     }
      46             : 
      47           0 :     return Scaffold(
      48           0 :       appBar: AppBar(title: Text(AppLocalizations.of(context)!.titleManageContacts)),
      49           0 :       backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
      50           0 :       body: _buildForm(),
      51             :     );
      52             :   }
      53             : 
      54           0 :   Widget _buildForm() {
      55           0 :     ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
      56             : 
      57             :     /// We display a different number of tabs depending on the experiment setup
      58           0 :     bool groupsEnabled = Provider.of<Settings>(context, listen: false).isExperimentEnabled(TapirGroupsExperiment);
      59           0 :     return Consumer<ErrorHandler>(
      60           0 :       builder: (bcontext, globalErrorHandler, child) {
      61           0 :         return DefaultTabController(
      62           0 :           initialIndex: widget.newGroup && groupsEnabled ? 1 : 0,
      63             :           length: groupsEnabled ? 2 : 1,
      64           0 :           child: Column(
      65           0 :             children: [
      66           0 :               (groupsEnabled ? getTabBarWithGroups() : getTabBarWithAddPeerOnly()),
      67           0 :               Expanded(child: TabBarView(children: (groupsEnabled ? [addPeerTab(bcontext), addGroupTab(bcontext)] : [addPeerTab(bcontext)]))),
      68             :             ],
      69             :           ),
      70             :         );
      71             :       },
      72             :     );
      73             :   }
      74             : 
      75           0 :   void _copyOnion() {
      76           0 :     Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
      77           0 :     final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
      78           0 :     ScaffoldMessenger.of(context).showSnackBar(snackBar);
      79             :   }
      80             : 
      81             :   /// A Tab Bar with only the Add Peer Tab
      82           0 :   TabBar getTabBarWithAddPeerOnly() {
      83           0 :     return TabBar(
      84           0 :       tabs: [Tab(icon: Icon(CwtchIcons.add_peer), text: AppLocalizations.of(context)!.addPeer)],
      85             :     );
      86             :   }
      87             : 
      88             :   /// The full tab bar with Join and Add Groups
      89           0 :   TabBar getTabBarWithGroups() {
      90           0 :     return TabBar(
      91           0 :       tabs: [
      92           0 :         Tab(icon: Icon(CwtchIcons.add_peer), text: AppLocalizations.of(context)!.tooltipAddContact),
      93             :         //Tab(icon: Icon(Icons.backup), text: AppLocalizations.of(context)!.titleManageServers),
      94           0 :         Tab(icon: Icon(CwtchIcons.add_group), text: AppLocalizations.of(context)!.createGroup),
      95             :       ],
      96             :     );
      97             :   }
      98             : 
      99             :   /// The Add Peer Tab allows a peer to add a specific non-group peer to their contact lists
     100             :   /// We also provide a convenient way to copy their onion.
     101           0 :   Widget addPeerTab(bcontext) {
     102           0 :     ScrollController controller = ScrollController();
     103           0 :     return Scrollbar(
     104             :       controller: controller,
     105           0 :       child: SingleChildScrollView(
     106             :         clipBehavior: Clip.antiAlias,
     107             :         controller: controller,
     108           0 :         child: Container(
     109           0 :           margin: EdgeInsets.all(30),
     110           0 :           padding: EdgeInsets.all(20),
     111           0 :           child: Form(
     112             :             autovalidateMode: AutovalidateMode.always,
     113           0 :             key: _formKey,
     114           0 :             child: Column(
     115             :               crossAxisAlignment: CrossAxisAlignment.start,
     116           0 :               children: [
     117           0 :                 CwtchLabel(label: AppLocalizations.of(context)!.profileOnionLabel),
     118           0 :                 SizedBox(height: 20),
     119           0 :                 CwtchButtonTextField(controller: ctrlrOnion, onPressed: _copyOnion, readonly: true, icon: Icon(CwtchIcons.address_copy, size: 32), tooltip: AppLocalizations.of(context)!.copyBtn),
     120           0 :                 SizedBox(height: 20),
     121           0 :                 CwtchLabel(label: AppLocalizations.of(context)!.pasteAddressToAddContact),
     122           0 :                 SizedBox(height: 20),
     123           0 :                 CwtchTextField(
     124           0 :                   key: Key("txtAddP2P"),
     125           0 :                   controller: ctrlrContact,
     126           0 :                   validator: (value) {
     127           0 :                     if (value == "") {
     128             :                       return null;
     129             :                     }
     130           0 :                     if (failedImport) {
     131           0 :                       return AppLocalizations.of(context)!.invalidImportString;
     132             :                     }
     133             :                     return null;
     134             :                   },
     135           0 :                   onChanged: (String importBundle) async {
     136           0 :                     if (lastContactValue != importBundle) {
     137           0 :                       lastContactValue = importBundle;
     138           0 :                       var profileOnion = Provider.of<ProfileInfoState>(bcontext, listen: false).onion;
     139           0 :                       Provider.of<FlwtchState>(bcontext, listen: false).cwtch.ImportBundle(profileOnion, importBundle.replaceFirst("cwtch:", "")).then((result) {
     140           0 :                         if (result == "importBundle.success") {
     141           0 :                           failedImport = false;
     142           0 :                           if (AppLocalizations.of(bcontext) != null) {
     143           0 :                             final snackBar = SnackBar(content: Text(AppLocalizations.of(bcontext)!.successfullAddedContact + importBundle));
     144           0 :                             ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
     145           0 :                             Navigator.popUntil(bcontext, (route) => route.settings.name == "conversations");
     146             :                           }
     147             :                         } else {
     148           0 :                           failedImport = true;
     149             :                         }
     150             :                       });
     151             :                     }
     152             :                   },
     153             :                   hintText: '',
     154             :                 ),
     155             :               ],
     156             :             ),
     157             :           ),
     158             :         ),
     159             :       ),
     160             :     );
     161             :   }
     162             : 
     163             :   /// TODO Add Group Pane
     164           0 :   Widget addGroupTab(bcontext) {
     165             :     // TODO We should replace with with a "Paste in Server Key Bundle"
     166           0 :     if (Provider.of<ProfileInfoState>(bcontext).serverList.servers.isEmpty) {
     167           0 :       return Text(AppLocalizations.of(bcontext)!.addServerFirst);
     168             :     }
     169           0 :     ScrollController controller = ScrollController();
     170           0 :     return Scrollbar(
     171             :       controller: controller,
     172           0 :       child: SingleChildScrollView(
     173             :         clipBehavior: Clip.antiAlias,
     174             :         controller: controller,
     175           0 :         child: Container(
     176           0 :           margin: EdgeInsets.all(30),
     177           0 :           padding: EdgeInsets.all(20),
     178           0 :           child: Form(
     179             :             autovalidateMode: AutovalidateMode.always,
     180           0 :             key: _createGroupFormKey,
     181           0 :             child: Column(
     182             :               mainAxisAlignment: MainAxisAlignment.start,
     183             :               crossAxisAlignment: CrossAxisAlignment.start,
     184           0 :               children: [
     185           0 :                 CwtchLabel(label: AppLocalizations.of(context)!.server),
     186           0 :                 SizedBox(height: 20),
     187           0 :                 DropdownButton(
     188           0 :                   onChanged: (String? newServer) {
     189           0 :                     setState(() {
     190           0 :                       server = newServer!;
     191             :                     });
     192             :                   },
     193             :                   isExpanded: true, // magic property
     194           0 :                   value: server,
     195           0 :                   items: Provider.of<ProfileInfoState>(bcontext).serverList.servers.map<DropdownMenuItem<String>>((RemoteServerInfoState serverInfo) {
     196           0 :                     return DropdownMenuItem<String>(
     197           0 :                       value: serverInfo.onion,
     198           0 :                       child: Text(serverInfo.description.isNotEmpty ? serverInfo.description : serverInfo.onion, overflow: TextOverflow.ellipsis),
     199             :                     );
     200           0 :                   }).toList(),
     201             :                 ),
     202           0 :                 SizedBox(height: 20),
     203           0 :                 CwtchLabel(label: AppLocalizations.of(bcontext)!.groupNameLabel),
     204           0 :                 SizedBox(height: 20),
     205           0 :                 CwtchTextField(
     206           0 :                   controller: ctrlrGroupName,
     207           0 :                   hintText: AppLocalizations.of(bcontext)!.groupNameLabel,
     208           0 :                   onChanged: (newValue) {},
     209           0 :                   validator: (value) {
     210             :                     return null;
     211             :                   },
     212             :                 ),
     213           0 :                 SizedBox(height: 20),
     214           0 :                 ElevatedButton(
     215           0 :                   onPressed: () {
     216           0 :                     var profileOnion = Provider.of<ProfileInfoState>(bcontext, listen: false).onion;
     217           0 :                     Provider.of<FlwtchState>(bcontext, listen: false).cwtch.CreateGroup(profileOnion, server, ctrlrGroupName.text);
     218           0 :                     Future.delayed(const Duration(milliseconds: 500), () {
     219           0 :                       final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact + " " + ctrlrGroupName.text));
     220           0 :                       ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
     221           0 :                       Navigator.pop(bcontext);
     222             :                     });
     223             :                   },
     224           0 :                   child: Text(AppLocalizations.of(context)!.createGroupBtn),
     225             :                 ),
     226             :               ],
     227             :             ),
     228             :           ),
     229             :         ),
     230             :       ),
     231             :     );
     232             :   }
     233             : }

Generated by: LCOV version 1.14