Line data Source code
1 : import 'package:cwtch/config.dart';
2 : import 'package:cwtch/cwtch_icons_icons.dart';
3 : import 'package:cwtch/models/appstate.dart';
4 : import 'package:cwtch/models/contact.dart';
5 : import 'package:cwtch/models/profile.dart';
6 : import 'package:cwtch/widgets/profileimage.dart';
7 : import 'package:flutter/services.dart';
8 : import 'package:cwtch/widgets/buttontextfield.dart';
9 : import 'package:cwtch/widgets/cwtchlabel.dart';
10 : import 'package:flutter/material.dart';
11 : import 'package:cwtch/settings.dart';
12 : import 'package:provider/provider.dart';
13 : import 'package:cwtch/l10n/app_localizations.dart';
14 :
15 : import '../main.dart';
16 : import '../themes/opaque.dart';
17 :
18 : /// Peer Settings View Provides way to Configure .
19 : class PeerSettingsView extends StatefulWidget {
20 0 : @override
21 0 : _PeerSettingsViewState createState() => _PeerSettingsViewState();
22 : }
23 :
24 : class _PeerSettingsViewState extends State<PeerSettingsView> {
25 0 : @override
26 : void dispose() {
27 0 : super.dispose();
28 : }
29 :
30 : final ctrlrNick = TextEditingController(text: "");
31 : ScrollController peerSettingsScrollController = ScrollController();
32 :
33 0 : @override
34 : void initState() {
35 0 : super.initState();
36 0 : final nickname = Provider.of<ContactInfoState>(context, listen: false).nickname;
37 0 : if (nickname.isNotEmpty) {
38 0 : ctrlrNick.text = nickname;
39 : }
40 : }
41 :
42 0 : @override
43 : Widget build(BuildContext context) {
44 0 : var handle = Provider.of<ContactInfoState>(context).nickname;
45 0 : if (handle.isEmpty) {
46 0 : handle = Provider.of<ContactInfoState>(context).onion;
47 : }
48 0 : return Scaffold(
49 0 : appBar: AppBar(
50 0 : title: Container(
51 0 : height: Provider.of<Settings>(context).fontScaling * 24.0,
52 : clipBehavior: Clip.hardEdge,
53 0 : decoration: BoxDecoration(),
54 0 : child: Text(handle + " " + AppLocalizations.of(context)!.conversationSettings),
55 : ),
56 : ),
57 0 : body: _buildSettingsList(),
58 : );
59 : }
60 :
61 0 : Widget _buildSettingsList() {
62 0 : return Consumer<Settings>(
63 0 : builder: (context, settings, child) {
64 0 : return LayoutBuilder(
65 0 : builder: (BuildContext context, BoxConstraints viewportConstraints) {
66 0 : String? acnCircuit = Provider.of<ContactInfoState>(context).acnCircuit;
67 :
68 0 : Widget path = Text(Provider.of<ContactInfoState>(context).status);
69 :
70 : if (acnCircuit != null) {
71 0 : var hops = acnCircuit.split(",");
72 0 : if (hops.length == 3) {
73 : List<Widget> paths = hops
74 0 : .map((String countryCodeAndIp) {
75 0 : var parts = countryCodeAndIp.split(":");
76 0 : var country = parts[0];
77 0 : var ip = parts[1];
78 0 : return RichText(
79 : textAlign: TextAlign.left,
80 0 : text: TextSpan(
81 : text: country,
82 0 : style: TextStyle(fontWeight: FontWeight.bold, fontSize: 10, fontFamily: "RobotoMono"),
83 0 : children: [
84 0 : TextSpan(
85 0 : text: " ($ip)",
86 0 : style: TextStyle(fontSize: 8, fontWeight: FontWeight.normal),
87 : ),
88 : ],
89 : ),
90 : );
91 : })
92 0 : .toList(growable: true);
93 :
94 0 : paths.add(
95 0 : RichText(
96 0 : text: TextSpan(
97 0 : text: AppLocalizations.of(context)!.labelTorNetwork,
98 0 : style: TextStyle(fontWeight: FontWeight.normal, fontSize: 8, fontFamily: "monospace"),
99 : ),
100 : ),
101 : );
102 :
103 0 : path = Column(children: paths);
104 : }
105 : }
106 :
107 0 : List<Widget> evWidgets = Provider.of<ContactInfoState>(context, listen: false).contactEvents.map((ev) {
108 0 : return ListTile(
109 0 : title: Text(ev.summary, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)),
110 0 : subtitle: Text(ev.timestamp.toLocal().toIso8601String(), style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)),
111 : );
112 0 : }).toList();
113 :
114 0 : bool canCopy = !Provider.of<ContactInfoState>(context, listen: false).isManaged || Provider.of<ContactInfoState>(context, listen: false).modeLine.contains('o');
115 :
116 0 : return Scrollbar(
117 : trackVisibility: true,
118 0 : controller: peerSettingsScrollController,
119 0 : child: SingleChildScrollView(
120 : clipBehavior: Clip.antiAlias,
121 0 : controller: peerSettingsScrollController,
122 0 : child: ConstrainedBox(
123 0 : constraints: BoxConstraints(minHeight: viewportConstraints.maxHeight),
124 0 : child: Container(
125 0 : color: settings.theme.backgroundPaneColor,
126 0 : padding: EdgeInsets.all(12),
127 0 : child: Column(
128 : mainAxisAlignment: MainAxisAlignment.start,
129 : crossAxisAlignment: CrossAxisAlignment.center,
130 0 : children: [
131 0 : Column(
132 : crossAxisAlignment: CrossAxisAlignment.center,
133 0 : children: [
134 0 : ProfileImage(
135 0 : imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
136 0 : ? Provider.of<ContactInfoState>(context).imagePath
137 0 : : Provider.of<ContactInfoState>(context).defaultImagePath,
138 : diameter: 120,
139 : maskOut: false,
140 0 : border: settings.theme.portraitOnlineBorderColor,
141 0 : badgeTextColor: settings.theme.portraitContactBadgeTextColor,
142 0 : badgeColor: settings.theme.portraitContactBadgeColor,
143 : badgeEdit: false,
144 : ),
145 0 : SizedBox(
146 0 : width: MediaQuery.of(context).size.width / 2,
147 0 : child: Column(
148 0 : children: [
149 0 : Padding(
150 0 : padding: EdgeInsets.all(1),
151 0 : child: SelectableText(Provider.of<ContactInfoState>(context, listen: false).attributes[0] ?? "", textAlign: TextAlign.center),
152 : ),
153 0 : Padding(
154 0 : padding: EdgeInsets.all(1),
155 0 : child: SelectableText(Provider.of<ContactInfoState>(context, listen: false).attributes[1] ?? "", textAlign: TextAlign.center),
156 : ),
157 0 : Padding(
158 0 : padding: EdgeInsets.all(1),
159 0 : child: SelectableText(Provider.of<ContactInfoState>(context, listen: false).attributes[2] ?? "", textAlign: TextAlign.center),
160 : ),
161 : ],
162 : ),
163 : ),
164 : ],
165 : ),
166 :
167 0 : Column(
168 : mainAxisAlignment: MainAxisAlignment.start,
169 : crossAxisAlignment: CrossAxisAlignment.start,
170 0 : children: [
171 0 : CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
172 0 : SizedBox(height: 20),
173 0 : CwtchButtonTextField(
174 0 : controller: ctrlrNick,
175 : readonly: false,
176 0 : onPressed: () {
177 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
178 0 : var conversation = Provider.of<ContactInfoState>(context, listen: false).identifier;
179 0 : Provider.of<ContactInfoState>(context, listen: false).localNickname = ctrlrNick.text;
180 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, conversation, "profile.name", ctrlrNick.text);
181 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess));
182 0 : ScaffoldMessenger.of(context).showSnackBar(snackBar);
183 : },
184 0 : icon: Icon(Icons.save),
185 0 : tooltip: AppLocalizations.of(context)!.saveBtn,
186 : ),
187 : ],
188 : ),
189 :
190 : // Address Copy Button
191 0 : Visibility(
192 0 : visible: settings.streamerMode == false,
193 0 : child: Column(
194 : mainAxisAlignment: MainAxisAlignment.start,
195 : crossAxisAlignment: CrossAxisAlignment.start,
196 0 : children: [
197 0 : SizedBox(height: 20),
198 0 : CwtchLabel(label: AppLocalizations.of(context)!.addressLabel),
199 0 : SizedBox(height: 20),
200 0 : CwtchButtonTextField(
201 0 : controller: TextEditingController(
202 0 : text: (Provider.of<ContactInfoState>(context, listen: false).isManaged ? "grwp-" : "") + Provider.of<ContactInfoState>(context, listen: false).onion,
203 : ),
204 0 : onPressed: canCopy ? _copyOnion : null,
205 0 : icon: canCopy ? Icon(CwtchIcons.copy_address) : null,
206 0 : tooltip: AppLocalizations.of(context)!.copyBtn,
207 : ),
208 : ],
209 : ),
210 : ),
211 0 : Visibility(visible: Provider.of<ContactInfoState>(context, listen: false).isManaged && canCopy, child: Text(AppLocalizations.of(context)!.anyoneCanJoin)),
212 0 : Visibility(visible: Provider.of<ContactInfoState>(context, listen: false).isManaged && !canCopy, child: Text(AppLocalizations.of(context)!.inviteRequired)),
213 0 : Column(
214 : mainAxisAlignment: MainAxisAlignment.start,
215 : crossAxisAlignment: CrossAxisAlignment.start,
216 0 : children: [
217 0 : SizedBox(height: 20),
218 0 : CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings),
219 0 : SizedBox(height: 20),
220 0 : ListTile(
221 0 : leading: Icon(CwtchIcons.onion_on, color: settings.current().mainTextColor),
222 : isThreeLine: true,
223 0 : title: Text(AppLocalizations.of(context)!.labelACNCircuitInfo),
224 0 : subtitle: Text(AppLocalizations.of(context)!.descriptionACNCircuitInfo),
225 : trailing: path,
226 : ),
227 0 : SizedBox(height: 20),
228 0 : Material(
229 0 : child: SwitchListTile(
230 0 : title: Text(AppLocalizations.of(context)!.blockBtn, style: TextStyle(color: settings.current().mainTextColor)),
231 0 : value: Provider.of<ContactInfoState>(context).isBlocked,
232 0 : onChanged: (bool blocked) {
233 0 : Provider.of<ContactInfoState>(context, listen: false).blocked = blocked;
234 :
235 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
236 0 : var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
237 :
238 : if (blocked) {
239 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.BlockContact(profileOnion, identifier);
240 : } else {
241 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.UnblockContact(profileOnion, identifier);
242 : }
243 : },
244 0 : activeTrackColor: settings.theme.defaultButtonColor,
245 0 : inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
246 0 : secondary: Icon(CwtchIcons.block_peer, color: settings.current().mainTextColor),
247 : ),
248 : ),
249 0 : Material(
250 0 : child: ListTile(
251 0 : title: Text(AppLocalizations.of(context)!.savePeerHistory, style: TextStyle(color: settings.current().mainTextColor)),
252 0 : subtitle: Text(AppLocalizations.of(context)!.savePeerHistoryDescription),
253 0 : leading: Icon(CwtchIcons.peer_history, color: settings.current().mainTextColor),
254 0 : trailing: DropdownButton(
255 : value:
256 0 : (Provider.of<ContactInfoState>(context).savePeerHistory == "DefaultDeleteHistory" || Provider.of<ContactInfoState>(context).savePeerHistory == "HistoryDefault")
257 0 : ? AppLocalizations.of(context)!.conversationNotificationPolicyDefault
258 0 : : (Provider.of<ContactInfoState>(context).savePeerHistory == "SaveHistory"
259 0 : ? AppLocalizations.of(context)!.savePeerHistory
260 0 : : AppLocalizations.of(context)!.dontSavePeerHistory),
261 0 : onChanged: (newValue) {
262 0 : setState(() {
263 : // Set whether or not to dave the Contact History...
264 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
265 0 : var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
266 : const SaveHistoryKey = "profile.SavePeerHistory";
267 : const SaveHistory = "SaveHistory";
268 : const DelHistory = "DeleteHistoryConfirmed";
269 : const HistoryDefault = "HistoryDefault";
270 :
271 : // NOTE: .savePeerHistory is used to show ephemeral warnings so it's state is important to update.
272 0 : if (newValue == AppLocalizations.of(context)!.conversationNotificationPolicyDefault) {
273 0 : Provider.of<ContactInfoState>(context, listen: false).savePeerHistory = HistoryDefault;
274 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, SaveHistoryKey, HistoryDefault);
275 0 : } else if (newValue == AppLocalizations.of(context)!.savePeerHistory) {
276 0 : Provider.of<ContactInfoState>(context, listen: false).savePeerHistory = SaveHistory;
277 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, SaveHistoryKey, SaveHistory);
278 : } else {
279 0 : Provider.of<ContactInfoState>(context, listen: false).savePeerHistory = DelHistory;
280 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, SaveHistoryKey, DelHistory);
281 : }
282 : });
283 : },
284 : items:
285 0 : [
286 0 : AppLocalizations.of(context)!.conversationNotificationPolicyDefault,
287 0 : AppLocalizations.of(context)!.savePeerHistory,
288 0 : AppLocalizations.of(context)!.dontSavePeerHistory,
289 0 : ].map<DropdownMenuItem<String>>((String value) {
290 0 : return DropdownMenuItem<String>(
291 : value: value,
292 0 : child: Text(value, style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)),
293 : );
294 0 : }).toList(),
295 : ),
296 : ),
297 : ),
298 0 : Material(
299 0 : child: ListTile(
300 0 : title: Text(AppLocalizations.of(context)!.conversationNotificationPolicySettingLabel, style: TextStyle(color: settings.current().mainTextColor)),
301 0 : subtitle: Text(AppLocalizations.of(context)!.conversationNotificationPolicySettingDescription),
302 0 : leading: Icon(CwtchIcons.chat_bubble_empy, color: settings.current().mainTextColor),
303 0 : trailing: DropdownButton(
304 0 : value: Provider.of<ContactInfoState>(context).notificationsPolicy,
305 0 : items: ConversationNotificationPolicy.values.map<DropdownMenuItem<ConversationNotificationPolicy>>((ConversationNotificationPolicy value) {
306 0 : return DropdownMenuItem<ConversationNotificationPolicy>(
307 : value: value,
308 0 : child: Text(value.toName(context), style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)),
309 : );
310 0 : }).toList(),
311 0 : onChanged: (ConversationNotificationPolicy? newVal) {
312 0 : Provider.of<ContactInfoState>(context, listen: false).notificationsPolicy = newVal!;
313 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
314 0 : var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
315 : const NotificationPolicyKey = "profile.notification-policy";
316 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationPolicyKey, newVal.toString());
317 : },
318 : ),
319 : ),
320 : ),
321 : ],
322 : ),
323 0 : Column(
324 : mainAxisAlignment: MainAxisAlignment.end,
325 : crossAxisAlignment: CrossAxisAlignment.end,
326 0 : children: [
327 0 : SizedBox(height: 20),
328 0 : Row(
329 : crossAxisAlignment: CrossAxisAlignment.center,
330 : mainAxisAlignment: MainAxisAlignment.end,
331 0 : children: [
332 0 : Tooltip(
333 0 : message: AppLocalizations.of(context)!.archiveConversation,
334 0 : child: OutlinedButton.icon(
335 0 : onPressed: () {
336 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
337 0 : var handle = Provider.of<ContactInfoState>(context, listen: false).identifier;
338 : // locally update cache...
339 0 : Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
340 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.ArchiveConversation(profileOnion, handle);
341 0 : Future.delayed(Duration(milliseconds: 500), () {
342 0 : Provider.of<AppState>(context, listen: false).selectedConversation = null;
343 0 : Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
344 : });
345 : },
346 0 : icon: Icon(Icons.archive),
347 0 : label: Text(AppLocalizations.of(context)!.archiveConversation),
348 : ),
349 : ),
350 : ],
351 : ),
352 0 : SizedBox(height: 20),
353 0 : Row(
354 : crossAxisAlignment: CrossAxisAlignment.center,
355 : mainAxisAlignment: MainAxisAlignment.end,
356 0 : children: [
357 0 : Tooltip(
358 0 : message: AppLocalizations.of(context)!.leaveConversation,
359 0 : child: OutlinedButton.icon(
360 0 : onPressed: () {
361 0 : showAlertDialog(context);
362 : },
363 0 : icon: Icon(CwtchIcons.leave_group),
364 0 : label: Text(AppLocalizations.of(context)!.leaveConversation),
365 : ),
366 : ),
367 : ],
368 : ),
369 : ],
370 : ),
371 0 : Visibility(
372 0 : visible: EnvironmentConfig.BUILD_VER == dev_version,
373 : maintainSize: false,
374 0 : child: Column(children: evWidgets),
375 : ),
376 : ],
377 : ),
378 : ),
379 : ),
380 : ),
381 : );
382 : },
383 : );
384 : },
385 : );
386 : }
387 :
388 0 : void _copyOnion() {
389 0 : String prefix = Provider.of<ContactInfoState>(context, listen: false).isManaged ? "grwp-" : "";
390 0 : Clipboard.setData(new ClipboardData(text: prefix + Provider.of<ContactInfoState>(context, listen: false).onion));
391 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
392 0 : ScaffoldMessenger.of(context).showSnackBar(snackBar);
393 : }
394 :
395 0 : showAlertDialog(BuildContext context) {
396 : // set up the buttons
397 0 : Widget cancelButton = ElevatedButton(
398 0 : child: Text(AppLocalizations.of(context)!.cancel),
399 0 : style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
400 0 : onPressed: () {
401 0 : Navigator.of(context).pop(); // dismiss dialog
402 : },
403 : );
404 0 : Widget continueButton = ElevatedButton(
405 0 : style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
406 0 : child: Text(AppLocalizations.of(context)!.yesLeave),
407 0 : onPressed: () {
408 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
409 0 : var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
410 0 : if (Provider.of<ContactInfoState>(context, listen: false).isManaged) {
411 : // remove ourselves from the group
412 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.KickMember(profileOnion, identifier, profileOnion);
413 : }
414 : // locally update cache...
415 0 : Provider.of<ProfileInfoState>(context, listen: false).contactList.removeContact(identifier);
416 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, identifier);
417 0 : Future.delayed(Duration(milliseconds: 500), () {
418 0 : Provider.of<AppState>(context, listen: false).selectedConversation = null;
419 0 : Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
420 : });
421 : },
422 : );
423 :
424 : // set up the AlertDialog
425 0 : AlertDialog alert = AlertDialog(title: Text(AppLocalizations.of(context)!.reallyLeaveThisGroupPrompt), actions: [cancelButton, continueButton]);
426 :
427 : // show the dialog
428 0 : showDialog(
429 : context: context,
430 0 : builder: (BuildContext context) {
431 : return alert;
432 : },
433 : );
434 : }
435 : }
|