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:flutter_gen/gen_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 0 : body: _buildSettingsList(),
57 : );
58 : }
59 :
60 0 : Widget _buildSettingsList() {
61 0 : return Consumer<Settings>(builder: (context, settings, child) {
62 0 : return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
63 0 : String? acnCircuit = Provider.of<ContactInfoState>(context).acnCircuit;
64 :
65 0 : Widget path = Text(Provider.of<ContactInfoState>(context).status);
66 :
67 : if (acnCircuit != null) {
68 0 : var hops = acnCircuit.split(",");
69 0 : if (hops.length == 3) {
70 0 : List<Widget> paths = hops.map((String countryCodeAndIp) {
71 0 : var parts = countryCodeAndIp.split(":");
72 0 : var country = parts[0];
73 0 : var ip = parts[1];
74 0 : return RichText(
75 : textAlign: TextAlign.left,
76 0 : text: TextSpan(
77 : text: country,
78 0 : style: TextStyle(fontWeight: FontWeight.bold, fontSize: 10, fontFamily: "RobotoMono"),
79 0 : children: [TextSpan(text: " ($ip)", style: TextStyle(fontSize: 8, fontWeight: FontWeight.normal))]));
80 0 : }).toList(growable: true);
81 :
82 0 : paths.add(RichText(text: TextSpan(text: AppLocalizations.of(context)!.labelTorNetwork, style: TextStyle(fontWeight: FontWeight.normal, fontSize: 8, fontFamily: "monospace"))));
83 :
84 0 : path = Column(
85 : children: paths,
86 : );
87 : }
88 : }
89 :
90 0 : List<Widget> evWidgets = Provider.of<ContactInfoState>(context, listen: false).contactEvents.map((ev) {
91 0 : return ListTile(
92 0 : title: Text(ev.summary, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)),
93 0 : subtitle: Text(ev.timestamp.toLocal().toIso8601String(), style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)),
94 : );
95 0 : }).toList();
96 :
97 0 : return Scrollbar(
98 : trackVisibility: true,
99 0 : controller: peerSettingsScrollController,
100 0 : child: SingleChildScrollView(
101 : clipBehavior: Clip.antiAlias,
102 0 : controller: peerSettingsScrollController,
103 0 : child: ConstrainedBox(
104 0 : constraints: BoxConstraints(
105 0 : minHeight: viewportConstraints.maxHeight,
106 : ),
107 0 : child: Container(
108 0 : color: settings.theme.backgroundPaneColor,
109 0 : padding: EdgeInsets.all(12),
110 0 : child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
111 0 : Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
112 0 : ProfileImage(
113 0 : imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
114 0 : ? Provider.of<ContactInfoState>(context).imagePath
115 0 : : Provider.of<ContactInfoState>(context).defaultImagePath,
116 : diameter: 120,
117 : maskOut: false,
118 0 : border: settings.theme.portraitOnlineBorderColor,
119 0 : badgeTextColor: settings.theme.portraitContactBadgeTextColor,
120 0 : badgeColor: settings.theme.portraitContactBadgeColor,
121 : badgeEdit: false),
122 0 : SizedBox(
123 0 : width: MediaQuery.of(context).size.width / 2,
124 0 : child: Column(children: [
125 0 : Padding(
126 0 : padding: EdgeInsets.all(1),
127 0 : child: SelectableText(
128 0 : Provider.of<ContactInfoState>(context, listen: false).attributes[0] ?? "",
129 : textAlign: TextAlign.center,
130 : ),
131 : ),
132 0 : Padding(
133 0 : padding: EdgeInsets.all(1),
134 0 : child: SelectableText(
135 0 : Provider.of<ContactInfoState>(context, listen: false).attributes[1] ?? "",
136 : textAlign: TextAlign.center,
137 : ),
138 : ),
139 0 : Padding(
140 0 : padding: EdgeInsets.all(1),
141 0 : child: SelectableText(
142 0 : Provider.of<ContactInfoState>(context, listen: false).attributes[2] ?? "",
143 : textAlign: TextAlign.center,
144 : ),
145 : )
146 : ]))
147 : ]),
148 :
149 0 : Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
150 0 : CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
151 0 : SizedBox(
152 : height: 20,
153 : ),
154 0 : CwtchButtonTextField(
155 0 : controller: ctrlrNick,
156 : readonly: false,
157 0 : onPressed: () {
158 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
159 0 : var conversation = Provider.of<ContactInfoState>(context, listen: false).identifier;
160 0 : Provider.of<ContactInfoState>(context, listen: false).localNickname = ctrlrNick.text;
161 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, conversation, "profile.name", ctrlrNick.text);
162 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess));
163 0 : ScaffoldMessenger.of(context).showSnackBar(snackBar);
164 : },
165 0 : icon: Icon(Icons.save),
166 0 : tooltip: AppLocalizations.of(context)!.saveBtn,
167 : )
168 : ]),
169 :
170 : // Address Copy Button
171 0 : Visibility(
172 0 : visible: settings.streamerMode == false,
173 0 : child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
174 0 : SizedBox(
175 : height: 20,
176 : ),
177 0 : CwtchLabel(label: AppLocalizations.of(context)!.addressLabel),
178 0 : SizedBox(
179 : height: 20,
180 : ),
181 0 : CwtchButtonTextField(
182 0 : controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).onion),
183 0 : onPressed: _copyOnion,
184 0 : icon: Icon(CwtchIcons.address_copy),
185 0 : tooltip: AppLocalizations.of(context)!.copyBtn,
186 : )
187 : ])),
188 0 : Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
189 0 : SizedBox(
190 : height: 20,
191 : ),
192 0 : CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings),
193 0 : SizedBox(
194 : height: 20,
195 : ),
196 0 : ListTile(
197 0 : leading: Icon(CwtchIcons.onion_on, color: settings.current().mainTextColor),
198 : isThreeLine: true,
199 0 : title: Text(AppLocalizations.of(context)!.labelACNCircuitInfo),
200 0 : subtitle: Text(AppLocalizations.of(context)!.descriptionACNCircuitInfo),
201 : trailing: path,
202 : ),
203 0 : SizedBox(
204 : height: 20,
205 : ),
206 0 : SwitchListTile(
207 0 : title: Text(AppLocalizations.of(context)!.blockBtn, style: TextStyle(color: settings.current().mainTextColor)),
208 0 : value: Provider.of<ContactInfoState>(context).isBlocked,
209 0 : onChanged: (bool blocked) {
210 0 : Provider.of<ContactInfoState>(context, listen: false).blocked = blocked;
211 :
212 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
213 0 : var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
214 :
215 : if (blocked) {
216 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.BlockContact(profileOnion, identifier);
217 : } else {
218 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.UnblockContact(profileOnion, identifier);
219 : }
220 : },
221 0 : activeTrackColor: settings.theme.defaultButtonColor,
222 0 : inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
223 0 : secondary: Icon(CwtchIcons.block_peer, color: settings.current().mainTextColor),
224 : ),
225 0 : ListTile(
226 0 : title: Text(AppLocalizations.of(context)!.savePeerHistory, style: TextStyle(color: settings.current().mainTextColor)),
227 0 : subtitle: Text(AppLocalizations.of(context)!.savePeerHistoryDescription),
228 0 : leading: Icon(CwtchIcons.peer_history, color: settings.current().mainTextColor),
229 0 : trailing: DropdownButton(
230 : value:
231 0 : (Provider.of<ContactInfoState>(context).savePeerHistory == "DefaultDeleteHistory" || Provider.of<ContactInfoState>(context).savePeerHistory == "HistoryDefault")
232 0 : ? AppLocalizations.of(context)!.conversationNotificationPolicyDefault
233 0 : : (Provider.of<ContactInfoState>(context).savePeerHistory == "SaveHistory"
234 0 : ? AppLocalizations.of(context)!.savePeerHistory
235 0 : : AppLocalizations.of(context)!.dontSavePeerHistory),
236 0 : onChanged: (newValue) {
237 0 : setState(() {
238 : // Set whether or not to dave the Contact History...
239 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
240 0 : var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
241 : const SaveHistoryKey = "profile.SavePeerHistory";
242 : const SaveHistory = "SaveHistory";
243 : const DelHistory = "DeleteHistoryConfirmed";
244 : const HistoryDefault = "HistoryDefault";
245 :
246 : // NOTE: .savePeerHistory is used to show ephemeral warnings so it's state is important to update.
247 0 : if (newValue == AppLocalizations.of(context)!.conversationNotificationPolicyDefault) {
248 0 : Provider.of<ContactInfoState>(context, listen: false).savePeerHistory = HistoryDefault;
249 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, SaveHistoryKey, HistoryDefault);
250 0 : } else if (newValue == AppLocalizations.of(context)!.savePeerHistory) {
251 0 : Provider.of<ContactInfoState>(context, listen: false).savePeerHistory = SaveHistory;
252 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, SaveHistoryKey, SaveHistory);
253 : } else {
254 0 : Provider.of<ContactInfoState>(context, listen: false).savePeerHistory = DelHistory;
255 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, SaveHistoryKey, DelHistory);
256 : }
257 : });
258 : },
259 0 : items: [
260 0 : AppLocalizations.of(context)!.conversationNotificationPolicyDefault,
261 0 : AppLocalizations.of(context)!.savePeerHistory,
262 0 : AppLocalizations.of(context)!.dontSavePeerHistory
263 0 : ].map<DropdownMenuItem<String>>((String value) {
264 0 : return DropdownMenuItem<String>(
265 : value: value,
266 0 : child: Text(value, style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)),
267 : );
268 0 : }).toList())),
269 0 : ListTile(
270 0 : title: Text(AppLocalizations.of(context)!.conversationNotificationPolicySettingLabel, style: TextStyle(color: settings.current().mainTextColor)),
271 0 : subtitle: Text(AppLocalizations.of(context)!.conversationNotificationPolicySettingDescription),
272 0 : leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor),
273 0 : trailing: DropdownButton(
274 0 : value: Provider.of<ContactInfoState>(context).notificationsPolicy,
275 0 : items: ConversationNotificationPolicy.values.map<DropdownMenuItem<ConversationNotificationPolicy>>((ConversationNotificationPolicy value) {
276 0 : return DropdownMenuItem<ConversationNotificationPolicy>(
277 : value: value,
278 0 : child: Text(value.toName(context), style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)),
279 : );
280 0 : }).toList(),
281 0 : onChanged: (ConversationNotificationPolicy? newVal) {
282 0 : Provider.of<ContactInfoState>(context, listen: false).notificationsPolicy = newVal!;
283 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
284 0 : var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
285 : const NotificationPolicyKey = "profile.notification-policy";
286 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationPolicyKey, newVal.toString());
287 : },
288 : )),
289 : ]),
290 0 : Column(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [
291 0 : SizedBox(
292 : height: 20,
293 : ),
294 0 : Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
295 0 : Tooltip(
296 0 : message: AppLocalizations.of(context)!.archiveConversation,
297 0 : child: ElevatedButton.icon(
298 0 : onPressed: () {
299 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
300 0 : var handle = Provider.of<ContactInfoState>(context, listen: false).identifier;
301 : // locally update cache...
302 0 : Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
303 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.ArchiveConversation(profileOnion, handle);
304 0 : Future.delayed(Duration(milliseconds: 500), () {
305 0 : Provider.of<AppState>(context, listen: false).selectedConversation = null;
306 0 : Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
307 : });
308 : },
309 0 : icon: Icon(Icons.archive),
310 0 : label: Text(AppLocalizations.of(context)!.archiveConversation),
311 : ))
312 : ]),
313 0 : SizedBox(
314 : height: 20,
315 : ),
316 0 : Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
317 0 : Tooltip(
318 0 : message: AppLocalizations.of(context)!.leaveConversation,
319 0 : child: TextButton.icon(
320 0 : onPressed: () {
321 0 : showAlertDialog(context);
322 : },
323 0 : style: ButtonStyle(
324 0 : backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor),
325 0 : foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor)),
326 0 : icon: Icon(CwtchIcons.leave_group),
327 0 : label: Text(
328 0 : AppLocalizations.of(context)!.leaveConversation,
329 0 : style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)),
330 : ),
331 : ))
332 : ]),
333 : ]),
334 0 : Visibility(
335 0 : visible: EnvironmentConfig.BUILD_VER == dev_version,
336 : maintainSize: false,
337 0 : child: Column(
338 : children: evWidgets,
339 : ))
340 : ])))));
341 : });
342 : });
343 : }
344 :
345 0 : void _copyOnion() {
346 0 : Clipboard.setData(new ClipboardData(text: Provider.of<ContactInfoState>(context, listen: false).onion));
347 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
348 0 : ScaffoldMessenger.of(context).showSnackBar(snackBar);
349 : }
350 :
351 0 : showAlertDialog(BuildContext context) {
352 : // set up the buttons
353 0 : Widget cancelButton = ElevatedButton(
354 0 : child: Text(AppLocalizations.of(context)!.cancel),
355 0 : style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
356 0 : onPressed: () {
357 0 : Navigator.of(context).pop(); // dismiss dialog
358 : },
359 : );
360 0 : Widget continueButton = ElevatedButton(
361 0 : style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
362 0 : child: Text(AppLocalizations.of(context)!.yesLeave),
363 0 : onPressed: () {
364 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
365 0 : var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
366 : // locally update cache...
367 0 : Provider.of<ProfileInfoState>(context, listen: false).contactList.removeContact(identifier);
368 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, identifier);
369 0 : Future.delayed(Duration(milliseconds: 500), () {
370 0 : Provider.of<AppState>(context, listen: false).selectedConversation = null;
371 0 : Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
372 : });
373 : },
374 : );
375 :
376 : // set up the AlertDialog
377 0 : AlertDialog alert = AlertDialog(
378 0 : title: Text(AppLocalizations.of(context)!.reallyLeaveThisGroupPrompt),
379 0 : actions: [
380 : cancelButton,
381 : continueButton,
382 : ],
383 : );
384 :
385 : // show the dialog
386 0 : showDialog(
387 : context: context,
388 0 : builder: (BuildContext context) {
389 : return alert;
390 : },
391 : );
392 : }
393 : }
|