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:flutter_gen/gen_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(
49 0 : title: Text(AppLocalizations.of(context)!.titleManageContacts),
50 : ),
51 0 : backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
52 0 : body: _buildForm(),
53 : );
54 : }
55 :
56 0 : Widget _buildForm() {
57 0 : ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
58 :
59 : /// We display a different number of tabs depending on the experiment setup
60 0 : bool groupsEnabled = Provider.of<Settings>(context, listen: false).isExperimentEnabled(TapirGroupsExperiment);
61 0 : return Consumer<ErrorHandler>(builder: (bcontext, globalErrorHandler, child) {
62 0 : return DefaultTabController(
63 0 : initialIndex: widget.newGroup && groupsEnabled ? 1 : 0,
64 : length: groupsEnabled ? 2 : 1,
65 0 : child: Column(children: [
66 0 : (groupsEnabled ? getTabBarWithGroups() : getTabBarWithAddPeerOnly()),
67 0 : Expanded(
68 0 : child: TabBarView(
69 : children: (groupsEnabled
70 0 : ? [
71 0 : addPeerTab(bcontext),
72 0 : addGroupTab(bcontext),
73 : ]
74 0 : : [addPeerTab(bcontext)]),
75 : )),
76 : ]));
77 : });
78 : }
79 :
80 0 : void _copyOnion() {
81 0 : Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
82 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
83 0 : ScaffoldMessenger.of(context).showSnackBar(snackBar);
84 : }
85 :
86 : /// A Tab Bar with only the Add Peer Tab
87 0 : TabBar getTabBarWithAddPeerOnly() {
88 0 : return TabBar(
89 0 : tabs: [
90 0 : Tab(
91 0 : icon: Icon(CwtchIcons.add_peer),
92 0 : text: AppLocalizations.of(context)!.addPeer,
93 : ),
94 : ],
95 : );
96 : }
97 :
98 : /// The full tab bar with Join and Add Groups
99 0 : TabBar getTabBarWithGroups() {
100 0 : return TabBar(
101 0 : tabs: [
102 0 : Tab(
103 0 : icon: Icon(CwtchIcons.add_peer),
104 0 : text: AppLocalizations.of(context)!.tooltipAddContact,
105 : ),
106 : //Tab(icon: Icon(Icons.backup), text: AppLocalizations.of(context)!.titleManageServers),
107 0 : Tab(icon: Icon(CwtchIcons.add_group), text: AppLocalizations.of(context)!.createGroup),
108 : ],
109 : );
110 : }
111 :
112 : /// The Add Peer Tab allows a peer to add a specific non-group peer to their contact lists
113 : /// We also provide a convenient way to copy their onion.
114 0 : Widget addPeerTab(bcontext) {
115 0 : ScrollController controller = ScrollController();
116 0 : return Scrollbar(
117 : controller: controller,
118 0 : child: SingleChildScrollView(
119 : clipBehavior: Clip.antiAlias,
120 : controller: controller,
121 0 : child: Container(
122 0 : margin: EdgeInsets.all(30),
123 0 : padding: EdgeInsets.all(20),
124 0 : child: Form(
125 : autovalidateMode: AutovalidateMode.always,
126 0 : key: _formKey,
127 0 : child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
128 0 : CwtchLabel(label: AppLocalizations.of(context)!.profileOnionLabel),
129 0 : SizedBox(
130 : height: 20,
131 : ),
132 0 : CwtchButtonTextField(
133 0 : controller: ctrlrOnion,
134 0 : onPressed: _copyOnion,
135 : readonly: true,
136 0 : icon: Icon(
137 : CwtchIcons.address_copy,
138 : size: 32,
139 : ),
140 0 : tooltip: AppLocalizations.of(context)!.copyBtn,
141 : ),
142 0 : SizedBox(
143 : height: 20,
144 : ),
145 0 : CwtchLabel(label: AppLocalizations.of(context)!.pasteAddressToAddContact),
146 0 : SizedBox(
147 : height: 20,
148 : ),
149 0 : CwtchTextField(
150 0 : key: Key("txtAddP2P"),
151 0 : controller: ctrlrContact,
152 0 : validator: (value) {
153 0 : if (value == "") {
154 : return null;
155 : }
156 0 : if (failedImport) {
157 0 : return AppLocalizations.of(context)!.invalidImportString;
158 : }
159 : return null;
160 : },
161 0 : onChanged: (String importBundle) async {
162 0 : if (lastContactValue != importBundle) {
163 0 : lastContactValue = importBundle;
164 0 : var profileOnion = Provider.of<ProfileInfoState>(bcontext, listen: false).onion;
165 0 : Provider.of<FlwtchState>(bcontext, listen: false).cwtch.ImportBundle(profileOnion, importBundle.replaceFirst("cwtch:", "")).then((result) {
166 0 : if (result == "importBundle.success") {
167 0 : failedImport = false;
168 0 : if (AppLocalizations.of(bcontext) != null) {
169 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(bcontext)!.successfullAddedContact + importBundle));
170 0 : ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
171 0 : Navigator.popUntil(bcontext, (route) => route.settings.name == "conversations");
172 : }
173 : } else {
174 0 : failedImport = true;
175 : }
176 : });
177 : }
178 : },
179 : hintText: '',
180 : )
181 : ])))));
182 : }
183 :
184 : /// TODO Add Group Pane
185 0 : Widget addGroupTab(bcontext) {
186 : // TODO We should replace with with a "Paste in Server Key Bundle"
187 0 : if (Provider.of<ProfileInfoState>(bcontext).serverList.servers.isEmpty) {
188 0 : return Text(AppLocalizations.of(bcontext)!.addServerFirst);
189 : }
190 0 : ScrollController controller = ScrollController();
191 0 : return Scrollbar(
192 : controller: controller,
193 0 : child: SingleChildScrollView(
194 : clipBehavior: Clip.antiAlias,
195 : controller: controller,
196 0 : child: Container(
197 0 : margin: EdgeInsets.all(30),
198 0 : padding: EdgeInsets.all(20),
199 0 : child: Form(
200 : autovalidateMode: AutovalidateMode.always,
201 0 : key: _createGroupFormKey,
202 0 : child: Column(
203 : mainAxisAlignment: MainAxisAlignment.start,
204 : crossAxisAlignment: CrossAxisAlignment.start,
205 0 : children: [
206 0 : CwtchLabel(label: AppLocalizations.of(context)!.server),
207 0 : SizedBox(
208 : height: 20,
209 : ),
210 0 : DropdownButton(
211 0 : onChanged: (String? newServer) {
212 0 : setState(() {
213 0 : server = newServer!;
214 : });
215 : },
216 : isExpanded: true, // magic property
217 0 : value: server,
218 0 : items: Provider.of<ProfileInfoState>(bcontext).serverList.servers.map<DropdownMenuItem<String>>((RemoteServerInfoState serverInfo) {
219 0 : return DropdownMenuItem<String>(
220 0 : value: serverInfo.onion,
221 0 : child: Text(
222 0 : serverInfo.description.isNotEmpty ? serverInfo.description : serverInfo.onion,
223 : overflow: TextOverflow.ellipsis,
224 : ),
225 : );
226 0 : }).toList()),
227 0 : SizedBox(
228 : height: 20,
229 : ),
230 0 : CwtchLabel(label: AppLocalizations.of(bcontext)!.groupNameLabel),
231 0 : SizedBox(
232 : height: 20,
233 : ),
234 0 : CwtchTextField(
235 0 : controller: ctrlrGroupName,
236 0 : hintText: AppLocalizations.of(bcontext)!.groupNameLabel,
237 0 : onChanged: (newValue) {},
238 0 : validator: (value) {
239 : return null;
240 : },
241 : ),
242 0 : SizedBox(
243 : height: 20,
244 : ),
245 0 : ElevatedButton(
246 0 : onPressed: () {
247 0 : var profileOnion = Provider.of<ProfileInfoState>(bcontext, listen: false).onion;
248 0 : Provider.of<FlwtchState>(bcontext, listen: false).cwtch.CreateGroup(profileOnion, server, ctrlrGroupName.text);
249 0 : Future.delayed(const Duration(milliseconds: 500), () {
250 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact + " " + ctrlrGroupName.text));
251 0 : ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
252 0 : Navigator.pop(bcontext);
253 : });
254 : },
255 0 : child: Text(AppLocalizations.of(context)!.createGroupBtn),
256 : ),
257 : ],
258 : )))));
259 : }
260 : }
|