import 'package:cwtch/config.dart'; import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/profile.dart'; import 'package:cwtch/widgets/profileimage.dart'; import 'package:flutter/services.dart'; import 'package:cwtch/widgets/buttontextfield.dart'; import 'package:cwtch/widgets/cwtchlabel.dart'; import 'package:flutter/material.dart'; import 'package:cwtch/settings.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../main.dart'; import '../themes/opaque.dart'; /// Peer Settings View Provides way to Configure . class PeerSettingsView extends StatefulWidget { @override _PeerSettingsViewState createState() => _PeerSettingsViewState(); } class _PeerSettingsViewState extends State { @override void dispose() { super.dispose(); } final ctrlrNick = TextEditingController(text: ""); ScrollController peerSettingsScrollController = ScrollController(); @override void initState() { super.initState(); final nickname = Provider.of(context, listen: false).nickname; if (nickname.isNotEmpty) { ctrlrNick.text = nickname; } } @override Widget build(BuildContext context) { var handle = Provider.of(context).nickname; if (handle.isEmpty) { handle = Provider.of(context).onion; } return Scaffold( appBar: AppBar( title: Container( height: Provider.of(context).fontScaling * 24.0, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: Text(handle + " " + AppLocalizations.of(context)!.conversationSettings)), ), body: _buildSettingsList(), ); } Widget _buildSettingsList() { return Consumer(builder: (context, settings, child) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { String? acnCircuit = Provider.of(context).acnCircuit; Widget path = Text(Provider.of(context).status); if (acnCircuit != null) { var hops = acnCircuit.split(","); if (hops.length == 3) { List paths = hops.map((String countryCodeAndIp) { var parts = countryCodeAndIp.split(":"); var country = parts[0]; var ip = parts[1]; return RichText( textAlign: TextAlign.left, text: TextSpan( text: country, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 10, fontFamily: "RobotoMono"), children: [TextSpan(text: " ($ip)", style: TextStyle(fontSize: 8, fontWeight: FontWeight.normal))])); }).toList(growable: true); paths.add(RichText(text: TextSpan(text: AppLocalizations.of(context)!.labelTorNetwork, style: TextStyle(fontWeight: FontWeight.normal, fontSize: 8, fontFamily: "monospace")))); path = Column( children: paths, ); } } List evWidgets = Provider.of(context, listen: false).contactEvents.map((ev) { return ListTile( title: Text(ev.summary, style: Provider.of(context).scaleFonts(defaultTextStyle)), subtitle: Text(ev.timestamp.toLocal().toIso8601String(), style: Provider.of(context).scaleFonts(defaultTextStyle)), ); }).toList(); return Scrollbar( trackVisibility: true, controller: peerSettingsScrollController, child: SingleChildScrollView( clipBehavior: Clip.antiAlias, controller: peerSettingsScrollController, child: ConstrainedBox( constraints: BoxConstraints( minHeight: viewportConstraints.maxHeight, ), child: Container( color: settings.theme.backgroundPaneColor, padding: EdgeInsets.all(12), child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ ProfileImage( imagePath: Provider.of(context).isExperimentEnabled(ImagePreviewsExperiment) ? Provider.of(context).imagePath : Provider.of(context).defaultImagePath, diameter: 120, maskOut: false, border: settings.theme.portraitOnlineBorderColor, badgeTextColor: settings.theme.portraitContactBadgeTextColor, badgeColor: settings.theme.portraitContactBadgeColor, badgeEdit: false), SizedBox( width: MediaQuery.of(context).size.width / 2, child: Column(children: [ Padding( padding: EdgeInsets.all(1), child: SelectableText( Provider.of(context, listen: false).attributes[0] ?? "", textAlign: TextAlign.center, ), ), Padding( padding: EdgeInsets.all(1), child: SelectableText( Provider.of(context, listen: false).attributes[1] ?? "", textAlign: TextAlign.center, ), ), Padding( padding: EdgeInsets.all(1), child: SelectableText( Provider.of(context, listen: false).attributes[2] ?? "", textAlign: TextAlign.center, ), ) ])) ]), Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel), SizedBox( height: 20, ), CwtchButtonTextField( controller: ctrlrNick, readonly: false, onPressed: () { var profileOnion = Provider.of(context, listen: false).profileOnion; var conversation = Provider.of(context, listen: false).identifier; Provider.of(context, listen: false).localNickname = ctrlrNick.text; Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, conversation, "profile.name", ctrlrNick.text); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess)); ScaffoldMessenger.of(context).showSnackBar(snackBar); }, icon: Icon(Icons.save), tooltip: AppLocalizations.of(context)!.saveBtn, ) ]), // Address Copy Button Visibility( visible: settings.streamerMode == false, child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( height: 20, ), CwtchLabel(label: AppLocalizations.of(context)!.addressLabel), SizedBox( height: 20, ), CwtchButtonTextField( controller: TextEditingController(text: Provider.of(context, listen: false).onion), onPressed: _copyOnion, icon: Icon(CwtchIcons.address_copy), tooltip: AppLocalizations.of(context)!.copyBtn, ) ])), Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( height: 20, ), CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings), SizedBox( height: 20, ), ListTile( leading: Icon(CwtchIcons.onion_on, color: settings.current().mainTextColor), isThreeLine: true, title: Text(AppLocalizations.of(context)!.labelACNCircuitInfo), subtitle: Text(AppLocalizations.of(context)!.descriptionACNCircuitInfo), trailing: path, ), SizedBox( height: 20, ), SwitchListTile( title: Text(AppLocalizations.of(context)!.blockBtn, style: TextStyle(color: settings.current().mainTextColor)), value: Provider.of(context).isBlocked, onChanged: (bool blocked) { Provider.of(context, listen: false).blocked = blocked; var profileOnion = Provider.of(context, listen: false).profileOnion; var identifier = Provider.of(context, listen: false).identifier; if (blocked) { Provider.of(context, listen: false).cwtch.BlockContact(profileOnion, identifier); } else { Provider.of(context, listen: false).cwtch.UnblockContact(profileOnion, identifier); } }, activeTrackColor: settings.theme.defaultButtonColor, inactiveTrackColor: settings.theme.defaultButtonDisabledColor, secondary: Icon(CwtchIcons.block_peer, color: settings.current().mainTextColor), ), ListTile( title: Text(AppLocalizations.of(context)!.savePeerHistory, style: TextStyle(color: settings.current().mainTextColor)), subtitle: Text(AppLocalizations.of(context)!.savePeerHistoryDescription), leading: Icon(CwtchIcons.peer_history, color: settings.current().mainTextColor), trailing: DropdownButton( value: (Provider.of(context).savePeerHistory == "DefaultDeleteHistory" || Provider.of(context).savePeerHistory == "HistoryDefault") ? AppLocalizations.of(context)!.conversationNotificationPolicyDefault : (Provider.of(context).savePeerHistory == "SaveHistory" ? AppLocalizations.of(context)!.savePeerHistory : AppLocalizations.of(context)!.dontSavePeerHistory), onChanged: (newValue) { setState(() { // Set whether or not to dave the Contact History... var profileOnion = Provider.of(context, listen: false).profileOnion; var identifier = Provider.of(context, listen: false).identifier; const SaveHistoryKey = "profile.SavePeerHistory"; const SaveHistory = "SaveHistory"; const DelHistory = "DeleteHistoryConfirmed"; const HistoryDefault = "HistoryDefault"; // NOTE: .savePeerHistory is used to show ephemeral warnings so it's state is important to update. if (newValue == AppLocalizations.of(context)!.conversationNotificationPolicyDefault) { Provider.of(context, listen: false).savePeerHistory = HistoryDefault; Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, SaveHistoryKey, HistoryDefault); } else if (newValue == AppLocalizations.of(context)!.savePeerHistory) { Provider.of(context, listen: false).savePeerHistory = SaveHistory; Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, SaveHistoryKey, SaveHistory); } else { Provider.of(context, listen: false).savePeerHistory = DelHistory; Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, SaveHistoryKey, DelHistory); } }); }, items: [ AppLocalizations.of(context)!.conversationNotificationPolicyDefault, AppLocalizations.of(context)!.savePeerHistory, AppLocalizations.of(context)!.dontSavePeerHistory ].map>((String value) { return DropdownMenuItem( value: value, child: Text(value, style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)), ); }).toList())), ListTile( title: Text(AppLocalizations.of(context)!.conversationNotificationPolicySettingLabel, style: TextStyle(color: settings.current().mainTextColor)), subtitle: Text(AppLocalizations.of(context)!.conversationNotificationPolicySettingDescription), leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor), trailing: DropdownButton( value: Provider.of(context).notificationsPolicy, items: ConversationNotificationPolicy.values.map>((ConversationNotificationPolicy value) { return DropdownMenuItem( value: value, child: Text(value.toName(context), style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)), ); }).toList(), onChanged: (ConversationNotificationPolicy? newVal) { Provider.of(context, listen: false).notificationsPolicy = newVal!; var profileOnion = Provider.of(context, listen: false).profileOnion; var identifier = Provider.of(context, listen: false).identifier; const NotificationPolicyKey = "profile.notification-policy"; Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationPolicyKey, newVal.toString()); }, )), ]), Column(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ SizedBox( height: 20, ), Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [ Tooltip( message: AppLocalizations.of(context)!.archiveConversation, child: ElevatedButton.icon( onPressed: () { var profileOnion = Provider.of(context, listen: false).profileOnion; var handle = Provider.of(context, listen: false).identifier; // locally update cache... Provider.of(context, listen: false).isArchived = true; Provider.of(context, listen: false).cwtch.ArchiveConversation(profileOnion, handle); Future.delayed(Duration(milliseconds: 500), () { Provider.of(context, listen: false).selectedConversation = null; Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog }); }, icon: Icon(Icons.archive), label: Text(AppLocalizations.of(context)!.archiveConversation), )) ]), SizedBox( height: 20, ), Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [ Tooltip( message: AppLocalizations.of(context)!.leaveConversation, child: TextButton.icon( onPressed: () { showAlertDialog(context); }, style: ButtonStyle( backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.backgroundPaneColor), foregroundColor: MaterialStateProperty.all(Provider.of(context).theme.mainTextColor)), icon: Icon(CwtchIcons.leave_group), label: Text( AppLocalizations.of(context)!.leaveConversation, style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)), ), )) ]), ]), Visibility( visible: EnvironmentConfig.BUILD_VER == dev_version, maintainSize: false, child: Column( children: evWidgets, )) ]))))); }); }); } void _copyOnion() { Clipboard.setData(new ClipboardData(text: Provider.of(context, listen: false).onion)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification)); ScaffoldMessenger.of(context).showSnackBar(snackBar); } showAlertDialog(BuildContext context) { // set up the buttons Widget cancelButton = ElevatedButton( child: Text(AppLocalizations.of(context)!.cancel), style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), onPressed: () { Navigator.of(context).pop(); // dismiss dialog }, ); Widget continueButton = ElevatedButton( style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), child: Text(AppLocalizations.of(context)!.yesLeave), onPressed: () { var profileOnion = Provider.of(context, listen: false).profileOnion; var identifier = Provider.of(context, listen: false).identifier; // locally update cache... Provider.of(context, listen: false).contactList.removeContact(identifier); Provider.of(context, listen: false).cwtch.DeleteContact(profileOnion, identifier); Future.delayed(Duration(milliseconds: 500), () { Provider.of(context, listen: false).selectedConversation = null; Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog }); }, ); // set up the AlertDialog AlertDialog alert = AlertDialog( title: Text(AppLocalizations.of(context)!.reallyLeaveThisGroupPrompt), actions: [ cancelButton, continueButton, ], ); // show the dialog showDialog( context: context, builder: (BuildContext context) { return alert; }, ); } }