diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 6f42bcc..e5a0852 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -44,6 +44,9 @@ "deleteConfirmText": "LÖSCHEN", "deleteProfileBtn": "Profil löschen", "deleteProfileConfirmBtn": "Profil wirklich löschen", + "descriptionBlockUnknownConnections": "", + "descriptionExperiments": "", + "descriptionExperimentsGroups": "", "displayNameLabel": "Angezeigter Name", "dmTooltip": "Klicken, um DM zu senden", "dontSavePeerHistory": "Peer-Verlauf löschen", @@ -122,8 +125,13 @@ "smallTextLabel": "Klein", "themeDark": "Dunkel", "themeLight": "Licht", + "titleManageContacts": "", + "titleManageProfiles": "", "titlePlaceholder": "Titel...", "todoPlaceholder": "noch zu erledigen", + "tooltipAddContact": "", + "tooltipOpenSettings": "", + "tooltipUnlockProfiles": "", "unblockBtn": "Peer entblockieren", "unlock": "Entsperren", "update": "", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index ec84bf0..ef3c9da 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -44,6 +44,9 @@ "deleteConfirmText": "DELETE", "deleteProfileBtn": "Delete Profile", "deleteProfileConfirmBtn": "Really Delete Profile", + "descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.", + "descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.", + "descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.", "displayNameLabel": "Display Name", "dmTooltip": "Click to DM", "dontSavePeerHistory": "Delete Peer History", @@ -89,7 +92,7 @@ "passwordChangeError": "Error changing password: Supplied password rejected", "passwordErrorEmpty": "Password cannot be empty", "passwordErrorMatch": "Passwords do not match", - "pasteAddressToAddContact": "... paste an address here to add a contact...", + "pasteAddressToAddContact": "Paste a cwtch address here to add a new contact.", "peerAddress": "Address", "peerBlockedMessage": "Peer is blocked", "peerName": "Name", @@ -122,8 +125,13 @@ "smallTextLabel": "Small", "themeDark": "Dark", "themeLight": "Light", + "titleManageContacts": "Manage Contacts", + "titleManageProfiles": "Manage Cwtch Profiles", "titlePlaceholder": "title...", "todoPlaceholder": "Todo...", + "tooltipAddContact": "Add a new contact", + "tooltipOpenSettings": "Open the settings pane", + "tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.", "unblockBtn": "Unblock Peer", "unlock": "Unlock", "update": "Update", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 90c97bc..09e5602 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -44,6 +44,9 @@ "deleteConfirmText": "ELIMINAR", "deleteProfileBtn": "Eliminar Perfil", "deleteProfileConfirmBtn": "Confirmar eliminar perfil", + "descriptionBlockUnknownConnections": "", + "descriptionExperiments": "", + "descriptionExperimentsGroups": "", "displayNameLabel": "Nombre de Usuario", "dmTooltip": "Haz clic para enviar mensaje directo", "dontSavePeerHistory": "Eliminar historial de contacto", @@ -89,7 +92,7 @@ "passwordChangeError": "Hubo un error cambiando tu contraseña: la contraseña ingresada fue rechazada", "passwordErrorEmpty": "El campo de contraseña no puede estar vacío", "passwordErrorMatch": "Las contraseñas no coinciden", - "pasteAddressToAddContact": "...pegar una dirección aquí para añadir un contacto...", + "pasteAddressToAddContact": "...pegar una dirección aquí para añadir contacto...", "peerAddress": "Dirección", "peerBlockedMessage": "Contacto bloqueado", "peerName": "Nombre", @@ -122,8 +125,13 @@ "smallTextLabel": "Pequeño", "themeDark": "Oscuro", "themeLight": "Claro", + "titleManageContacts": "", + "titleManageProfiles": "", "titlePlaceholder": "título...", "todoPlaceholder": "Por hacer...", + "tooltipAddContact": "", + "tooltipOpenSettings": "", + "tooltipUnlockProfiles": "", "unblockBtn": "Desbloquear contacto", "unlock": "Desbloquear", "update": "Actualizar", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 71b3069..4836bee 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -44,6 +44,9 @@ "deleteConfirmText": "", "deleteProfileBtn": "", "deleteProfileConfirmBtn": "", + "descriptionBlockUnknownConnections": "", + "descriptionExperiments": "", + "descriptionExperimentsGroups": "", "displayNameLabel": "Pseudo", "dmTooltip": "Envoyer un message privé", "dontSavePeerHistory": "", @@ -122,8 +125,13 @@ "smallTextLabel": "Petit", "themeDark": "", "themeLight": "", + "titleManageContacts": "", + "titleManageProfiles": "", "titlePlaceholder": "titre...", "todoPlaceholder": "A faire...", + "tooltipAddContact": "", + "tooltipOpenSettings": "", + "tooltipUnlockProfiles": "", "unblockBtn": "", "unlock": "", "update": "", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index b388d01..f482ce2 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -44,6 +44,9 @@ "deleteConfirmText": "ELIMINA", "deleteProfileBtn": "Elimina profilo", "deleteProfileConfirmBtn": "Elimina realmente il profilo", + "descriptionBlockUnknownConnections": "", + "descriptionExperiments": "", + "descriptionExperimentsGroups": "", "displayNameLabel": "Nome visualizzato", "dmTooltip": "Clicca per inviare un Messagio Diretto", "dontSavePeerHistory": "Elimina cronologia dei peer", @@ -89,7 +92,7 @@ "passwordChangeError": "Errore durante la modifica della password: password fornita rifiutata", "passwordErrorEmpty": "La password non può essere vuota", "passwordErrorMatch": "Le password non corrispondono", - "pasteAddressToAddContact": "... incolla qui un indirizzo per aggiungere un contatto ...", + "pasteAddressToAddContact": "... incolla qui un indirizzo per aggiungere un contatto...", "peerAddress": "Indirizzo", "peerBlockedMessage": "Il peer è bloccato", "peerName": "Nome", @@ -122,8 +125,13 @@ "smallTextLabel": "Piccolo", "themeDark": "Scuro", "themeLight": "Chiaro", + "titleManageContacts": "", + "titleManageProfiles": "", "titlePlaceholder": "titolo...", "todoPlaceholder": "Da fare...", + "tooltipAddContact": "", + "tooltipOpenSettings": "", + "tooltipUnlockProfiles": "", "unblockBtn": "Sblocca il peer", "unlock": "Sblocca", "update": "Aggiornamento", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index df635c9..91aac23 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -44,6 +44,9 @@ "deleteConfirmText": "", "deleteProfileBtn": "", "deleteProfileConfirmBtn": "", + "descriptionBlockUnknownConnections": "", + "descriptionExperiments": "", + "descriptionExperimentsGroups": "", "displayNameLabel": "Nome de Exibição", "dmTooltip": "Clique para DM", "dontSavePeerHistory": "", @@ -122,8 +125,13 @@ "smallTextLabel": "Pequeno", "themeDark": "", "themeLight": "", + "titleManageContacts": "", + "titleManageProfiles": "", "titlePlaceholder": "título…", "todoPlaceholder": "Afazer…", + "tooltipAddContact": "", + "tooltipOpenSettings": "", + "tooltipUnlockProfiles": "", "unblockBtn": "", "unlock": "", "update": "", diff --git a/lib/settings.dart b/lib/settings.dart index ff49887..2696f78 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -19,6 +19,8 @@ class Settings extends ChangeNotifier { bool experimentsEnabled; HashMap experiments = HashMap.identity(); + bool blockUnknownConnections; + /// Set the dark theme. void setDark() { theme = Opaque.dark; @@ -49,6 +51,9 @@ class Settings extends ChangeNotifier { // Set Locale and notify listeners switchLocale(Locale(settings["Locale"])); + // Decide whether to enable Experiments + blockUnknownConnections = settings["BlockUnknownConnections"]; + // Decide whether to enable Experiments experimentsEnabled = settings["ExperimentsEnabled"]; @@ -67,12 +72,28 @@ class Settings extends ChangeNotifier { }); } - // Switch the Locale of the App + /// Switch the Locale of the App switchLocale(Locale newLocale) { locale = newLocale; notifyListeners(); } + /// Block Unknown Connections will autoblock connections if they authenticate with public key not in our contacts list. + /// This is one of the best tools we have to combat abuse, while it isn't ideal it does allow a user to curate their contacts + /// list without being bothered by spurious requests (either permanently, or as a short term measure). + /// Note: This is not an *appear offline* setting which would explicitly close the listen port, rather than simply auto disconnecting unknown attempts. + forbidUnknownConnections() { + blockUnknownConnections = true; + notifyListeners(); + } + + /// Allow Unknown Connections will allow new contact requires from unknown public keys + /// See above for more information. + allowUnknownConnections() { + blockUnknownConnections = false; + notifyListeners(); + } + /// Turn Experiments On, this will also have the side effect of enabling any /// Experiments that have been previously activated. enableExperiments() { @@ -113,6 +134,7 @@ class Settings extends ChangeNotifier { "Locale": this.locale.languageCode, "Theme": themeString, "PreviousPid": -1, + "BlockUnknownConnections": blockUnknownConnections, "ExperimentsEnabled": this.experimentsEnabled, "Experiments": experiments, "StateRootPane": 0, diff --git a/lib/views/addcontactview.dart b/lib/views/addcontactview.dart index 34fadfa..1a507f6 100644 --- a/lib/views/addcontactview.dart +++ b/lib/views/addcontactview.dart @@ -32,7 +32,7 @@ class _AddContactViewState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(AppLocalizations.of(context).newConnectionPaneTitle), + title: Text(AppLocalizations.of(context).titleManageContacts), ), body: _buildForm(), ); @@ -40,6 +40,7 @@ class _AddContactViewState extends State { Widget _buildForm() { ctrlrOnion.text = Provider.of(context).onion; + /// We display a different number of tabs dependening on the experiment setup bool groupsEnabled = Provider.of(context).experimentsEnabled && Provider.of(context).experiments[TapirGroupsExperiment]; return Consumer(builder: (context, globalErrorHandler, child) { @@ -116,14 +117,14 @@ class _AddContactViewState extends State { CwtchTextField( controller: ctrlrContact, validator: (value) { - if (value == "") { + if (value == "") { return null; - } if (globalErrorHandler.invalidImportStringError) { + } + if (globalErrorHandler.invalidImportStringError) { return AppLocalizations.of(context).invalidImportString; } else if (globalErrorHandler.contactAlreadyExistsError) { return AppLocalizations.of(context).contactAlreadyExists; - } else if (globalErrorHandler.explicitAddContactSuccess) { - } + } else if (globalErrorHandler.explicitAddContactSuccess) {} return null; }, onChanged: (String peerAddr) async { diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart index a82c2dc..3d34bd0 100644 --- a/lib/views/contactsview.dart +++ b/lib/views/contactsview.dart @@ -29,7 +29,7 @@ class _ContactsViewState extends State { ), floatingActionButton: FloatingActionButton( onPressed: _pushAddContact, - tooltip: AppLocalizations.of(context).newConnectionPaneTitle, + tooltip: AppLocalizations.of(context).tooltipAddContact, child: const Icon(Icons.person_add_sharp), ), body: _buildContactList(), diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 6de47f3..8f4c91e 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -74,8 +74,25 @@ class _GlobalSettingsViewState extends State { }, secondary: Icon(Icons.lightbulb_outline, color: settings.current().mainTextColor()), ), + SwitchListTile( + title: Text(AppLocalizations.of(context).blockUnknownLabel, style: TextStyle(color: settings.current().mainTextColor())), + subtitle: Text(AppLocalizations.of(context).descriptionBlockUnknownConnections), + value: settings.blockUnknownConnections, + onChanged: (bool value) { + if (value) { + settings.forbidUnknownConnections(); + } else { + settings.allowUnknownConnections(); + } + + // Save Settings... + saveSettings(context); + }, + secondary: Icon(Icons.app_blocking, color: settings.current().mainTextColor()), + ), SwitchListTile( title: Text(AppLocalizations.of(context).experimentsEnabled, style: TextStyle(color: settings.current().mainTextColor())), + subtitle: Text(AppLocalizations.of(context).descriptionExperiments), value: settings.experimentsEnabled, onChanged: (bool value) { if (value) { @@ -94,6 +111,7 @@ class _GlobalSettingsViewState extends State { children: [ SwitchListTile( title: Text(AppLocalizations.of(context).enableGroups, style: TextStyle(color: settings.current().mainTextColor())), + subtitle: Text(AppLocalizations.of(context).descriptionExperimentsGroups), value: settings.experiments.containsKey(TapirGroupsExperiment) && settings.experiments[TapirGroupsExperiment], onChanged: (bool value) { if (value) { diff --git a/lib/views/profilemgrview.dart b/lib/views/profilemgrview.dart index 5588a25..6df2d5f 100644 --- a/lib/views/profilemgrview.dart +++ b/lib/views/profilemgrview.dart @@ -29,20 +29,24 @@ class _ProfileMgrViewState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(AppLocalizations.of(context).profileName), + title: Text(AppLocalizations.of(context).titleManageProfiles), actions: [ IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _setLoggingLevelDebug), IconButton( icon: Icon(Icons.lock_open), + tooltip: AppLocalizations.of(context).tooltipUnlockProfiles, onPressed: _modalUnlockProfiles, ), - IconButton(icon: Icon(Icons.settings), onPressed: _pushGlobalSettings), + IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context).tooltipOpenSettings, onPressed: _pushGlobalSettings), ], ), floatingActionButton: FloatingActionButton( onPressed: _pushAddEditProfile, tooltip: AppLocalizations.of(context).addNewProfileBtn, - child: const Icon(Icons.add), + child: Icon( + Icons.add, + semanticLabel: AppLocalizations.of(context).addNewProfileBtn, + ), ), body: _buildProfileManager(), //_buildSuggestions(), ); @@ -105,7 +109,7 @@ class _ProfileMgrViewState extends State { ), ), ElevatedButton( - child: Text(AppLocalizations.of(context).unlock), + child: Text(AppLocalizations.of(context).unlock, semanticsLabel: AppLocalizations.of(context).unlock), onPressed: () { Provider.of(context, listen: false).cwtch.LoadProfiles(ctrlrPassword.value.text); Navigator.pop(context); diff --git a/lib/widgets/profilerow.dart b/lib/widgets/profilerow.dart index e3c3c91..764a0d7 100644 --- a/lib/widgets/profilerow.dart +++ b/lib/widgets/profilerow.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_app/views/addeditprofileview.dart'; import 'package:flutter_app/views/contactsview.dart'; import 'package:flutter_app/views/doublecolview.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../main.dart'; import '../model.dart'; @@ -30,23 +32,30 @@ class _ProfileRowState extends State { width: 60, height: 60, child: Image( + excludeFromSemantics: true, image: AssetImage("assets/" + profile.imagePath), width: 50, height: 50, ))), ), ), + + title: Text( + profile.nickname, + semanticsLabel: profile.nickname, + style: Provider.of(context).biggerFont, + ), + subtitle: ExcludeSemantics(child: Text(profile.onion)), + trailing: IconButton( + enableFeedback: true, + tooltip: AppLocalizations.of(context).editProfile + " " + profile.nickname, icon: Icon(Icons.create, color: Provider.of(context).current().mainTextColor()), onPressed: () { _pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath); }, ), //(nb: Icons.create is a pencil and we use it for "edit", not create) - title: Text( - profile.nickname, - style: Provider.of(context).biggerFont, - ), - subtitle: Text(profile.onion), + onTap: () { setState(() { var flwtch = Provider.of(context, listen: false);