Accessibility, Tooltips and better Strings
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
Sarah Jamie Lewis 2021-04-06 13:33:44 -07:00
parent 9448529061
commit e1b9b0b3c1
12 changed files with 121 additions and 19 deletions

View File

@ -44,6 +44,9 @@
"deleteConfirmText": "LÖSCHEN", "deleteConfirmText": "LÖSCHEN",
"deleteProfileBtn": "Profil löschen", "deleteProfileBtn": "Profil löschen",
"deleteProfileConfirmBtn": "Profil wirklich löschen", "deleteProfileConfirmBtn": "Profil wirklich löschen",
"descriptionBlockUnknownConnections": "",
"descriptionExperiments": "",
"descriptionExperimentsGroups": "",
"displayNameLabel": "Angezeigter Name", "displayNameLabel": "Angezeigter Name",
"dmTooltip": "Klicken, um DM zu senden", "dmTooltip": "Klicken, um DM zu senden",
"dontSavePeerHistory": "Peer-Verlauf löschen", "dontSavePeerHistory": "Peer-Verlauf löschen",
@ -122,8 +125,13 @@
"smallTextLabel": "Klein", "smallTextLabel": "Klein",
"themeDark": "Dunkel", "themeDark": "Dunkel",
"themeLight": "Licht", "themeLight": "Licht",
"titleManageContacts": "",
"titleManageProfiles": "",
"titlePlaceholder": "Titel...", "titlePlaceholder": "Titel...",
"todoPlaceholder": "noch zu erledigen", "todoPlaceholder": "noch zu erledigen",
"tooltipAddContact": "",
"tooltipOpenSettings": "",
"tooltipUnlockProfiles": "",
"unblockBtn": "Peer entblockieren", "unblockBtn": "Peer entblockieren",
"unlock": "Entsperren", "unlock": "Entsperren",
"update": "", "update": "",

View File

@ -44,6 +44,9 @@
"deleteConfirmText": "DELETE", "deleteConfirmText": "DELETE",
"deleteProfileBtn": "Delete Profile", "deleteProfileBtn": "Delete Profile",
"deleteProfileConfirmBtn": "Really 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", "displayNameLabel": "Display Name",
"dmTooltip": "Click to DM", "dmTooltip": "Click to DM",
"dontSavePeerHistory": "Delete Peer History", "dontSavePeerHistory": "Delete Peer History",
@ -89,7 +92,7 @@
"passwordChangeError": "Error changing password: Supplied password rejected", "passwordChangeError": "Error changing password: Supplied password rejected",
"passwordErrorEmpty": "Password cannot be empty", "passwordErrorEmpty": "Password cannot be empty",
"passwordErrorMatch": "Passwords do not match", "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", "peerAddress": "Address",
"peerBlockedMessage": "Peer is blocked", "peerBlockedMessage": "Peer is blocked",
"peerName": "Name", "peerName": "Name",
@ -122,8 +125,13 @@
"smallTextLabel": "Small", "smallTextLabel": "Small",
"themeDark": "Dark", "themeDark": "Dark",
"themeLight": "Light", "themeLight": "Light",
"titleManageContacts": "Manage Contacts",
"titleManageProfiles": "Manage Cwtch Profiles",
"titlePlaceholder": "title...", "titlePlaceholder": "title...",
"todoPlaceholder": "Todo...", "todoPlaceholder": "Todo...",
"tooltipAddContact": "Add a new contact",
"tooltipOpenSettings": "Open the settings pane",
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"unblockBtn": "Unblock Peer", "unblockBtn": "Unblock Peer",
"unlock": "Unlock", "unlock": "Unlock",
"update": "Update", "update": "Update",

View File

@ -44,6 +44,9 @@
"deleteConfirmText": "ELIMINAR", "deleteConfirmText": "ELIMINAR",
"deleteProfileBtn": "Eliminar Perfil", "deleteProfileBtn": "Eliminar Perfil",
"deleteProfileConfirmBtn": "Confirmar eliminar perfil", "deleteProfileConfirmBtn": "Confirmar eliminar perfil",
"descriptionBlockUnknownConnections": "",
"descriptionExperiments": "",
"descriptionExperimentsGroups": "",
"displayNameLabel": "Nombre de Usuario", "displayNameLabel": "Nombre de Usuario",
"dmTooltip": "Haz clic para enviar mensaje directo", "dmTooltip": "Haz clic para enviar mensaje directo",
"dontSavePeerHistory": "Eliminar historial de contacto", "dontSavePeerHistory": "Eliminar historial de contacto",
@ -89,7 +92,7 @@
"passwordChangeError": "Hubo un error cambiando tu contraseña: la contraseña ingresada fue rechazada", "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", "passwordErrorEmpty": "El campo de contraseña no puede estar vacío",
"passwordErrorMatch": "Las contraseñas no coinciden", "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", "peerAddress": "Dirección",
"peerBlockedMessage": "Contacto bloqueado", "peerBlockedMessage": "Contacto bloqueado",
"peerName": "Nombre", "peerName": "Nombre",
@ -122,8 +125,13 @@
"smallTextLabel": "Pequeño", "smallTextLabel": "Pequeño",
"themeDark": "Oscuro", "themeDark": "Oscuro",
"themeLight": "Claro", "themeLight": "Claro",
"titleManageContacts": "",
"titleManageProfiles": "",
"titlePlaceholder": "título...", "titlePlaceholder": "título...",
"todoPlaceholder": "Por hacer...", "todoPlaceholder": "Por hacer...",
"tooltipAddContact": "",
"tooltipOpenSettings": "",
"tooltipUnlockProfiles": "",
"unblockBtn": "Desbloquear contacto", "unblockBtn": "Desbloquear contacto",
"unlock": "Desbloquear", "unlock": "Desbloquear",
"update": "Actualizar", "update": "Actualizar",

View File

@ -44,6 +44,9 @@
"deleteConfirmText": "", "deleteConfirmText": "",
"deleteProfileBtn": "", "deleteProfileBtn": "",
"deleteProfileConfirmBtn": "", "deleteProfileConfirmBtn": "",
"descriptionBlockUnknownConnections": "",
"descriptionExperiments": "",
"descriptionExperimentsGroups": "",
"displayNameLabel": "Pseudo", "displayNameLabel": "Pseudo",
"dmTooltip": "Envoyer un message privé", "dmTooltip": "Envoyer un message privé",
"dontSavePeerHistory": "", "dontSavePeerHistory": "",
@ -122,8 +125,13 @@
"smallTextLabel": "Petit", "smallTextLabel": "Petit",
"themeDark": "", "themeDark": "",
"themeLight": "", "themeLight": "",
"titleManageContacts": "",
"titleManageProfiles": "",
"titlePlaceholder": "titre...", "titlePlaceholder": "titre...",
"todoPlaceholder": "A faire...", "todoPlaceholder": "A faire...",
"tooltipAddContact": "",
"tooltipOpenSettings": "",
"tooltipUnlockProfiles": "",
"unblockBtn": "", "unblockBtn": "",
"unlock": "", "unlock": "",
"update": "", "update": "",

View File

@ -44,6 +44,9 @@
"deleteConfirmText": "ELIMINA", "deleteConfirmText": "ELIMINA",
"deleteProfileBtn": "Elimina profilo", "deleteProfileBtn": "Elimina profilo",
"deleteProfileConfirmBtn": "Elimina realmente il profilo", "deleteProfileConfirmBtn": "Elimina realmente il profilo",
"descriptionBlockUnknownConnections": "",
"descriptionExperiments": "",
"descriptionExperimentsGroups": "",
"displayNameLabel": "Nome visualizzato", "displayNameLabel": "Nome visualizzato",
"dmTooltip": "Clicca per inviare un Messagio Diretto", "dmTooltip": "Clicca per inviare un Messagio Diretto",
"dontSavePeerHistory": "Elimina cronologia dei peer", "dontSavePeerHistory": "Elimina cronologia dei peer",
@ -89,7 +92,7 @@
"passwordChangeError": "Errore durante la modifica della password: password fornita rifiutata", "passwordChangeError": "Errore durante la modifica della password: password fornita rifiutata",
"passwordErrorEmpty": "La password non può essere vuota", "passwordErrorEmpty": "La password non può essere vuota",
"passwordErrorMatch": "Le password non corrispondono", "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", "peerAddress": "Indirizzo",
"peerBlockedMessage": "Il peer è bloccato", "peerBlockedMessage": "Il peer è bloccato",
"peerName": "Nome", "peerName": "Nome",
@ -122,8 +125,13 @@
"smallTextLabel": "Piccolo", "smallTextLabel": "Piccolo",
"themeDark": "Scuro", "themeDark": "Scuro",
"themeLight": "Chiaro", "themeLight": "Chiaro",
"titleManageContacts": "",
"titleManageProfiles": "",
"titlePlaceholder": "titolo...", "titlePlaceholder": "titolo...",
"todoPlaceholder": "Da fare...", "todoPlaceholder": "Da fare...",
"tooltipAddContact": "",
"tooltipOpenSettings": "",
"tooltipUnlockProfiles": "",
"unblockBtn": "Sblocca il peer", "unblockBtn": "Sblocca il peer",
"unlock": "Sblocca", "unlock": "Sblocca",
"update": "Aggiornamento", "update": "Aggiornamento",

View File

@ -44,6 +44,9 @@
"deleteConfirmText": "", "deleteConfirmText": "",
"deleteProfileBtn": "", "deleteProfileBtn": "",
"deleteProfileConfirmBtn": "", "deleteProfileConfirmBtn": "",
"descriptionBlockUnknownConnections": "",
"descriptionExperiments": "",
"descriptionExperimentsGroups": "",
"displayNameLabel": "Nome de Exibição", "displayNameLabel": "Nome de Exibição",
"dmTooltip": "Clique para DM", "dmTooltip": "Clique para DM",
"dontSavePeerHistory": "", "dontSavePeerHistory": "",
@ -122,8 +125,13 @@
"smallTextLabel": "Pequeno", "smallTextLabel": "Pequeno",
"themeDark": "", "themeDark": "",
"themeLight": "", "themeLight": "",
"titleManageContacts": "",
"titleManageProfiles": "",
"titlePlaceholder": "título…", "titlePlaceholder": "título…",
"todoPlaceholder": "Afazer…", "todoPlaceholder": "Afazer…",
"tooltipAddContact": "",
"tooltipOpenSettings": "",
"tooltipUnlockProfiles": "",
"unblockBtn": "", "unblockBtn": "",
"unlock": "", "unlock": "",
"update": "", "update": "",

View File

@ -19,6 +19,8 @@ class Settings extends ChangeNotifier {
bool experimentsEnabled; bool experimentsEnabled;
HashMap<String, bool> experiments = HashMap.identity(); HashMap<String, bool> experiments = HashMap.identity();
bool blockUnknownConnections;
/// Set the dark theme. /// Set the dark theme.
void setDark() { void setDark() {
theme = Opaque.dark; theme = Opaque.dark;
@ -49,6 +51,9 @@ class Settings extends ChangeNotifier {
// Set Locale and notify listeners // Set Locale and notify listeners
switchLocale(Locale(settings["Locale"])); switchLocale(Locale(settings["Locale"]));
// Decide whether to enable Experiments
blockUnknownConnections = settings["BlockUnknownConnections"];
// Decide whether to enable Experiments // Decide whether to enable Experiments
experimentsEnabled = settings["ExperimentsEnabled"]; 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) { switchLocale(Locale newLocale) {
locale = newLocale; locale = newLocale;
notifyListeners(); 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 /// Turn Experiments On, this will also have the side effect of enabling any
/// Experiments that have been previously activated. /// Experiments that have been previously activated.
enableExperiments() { enableExperiments() {
@ -113,6 +134,7 @@ class Settings extends ChangeNotifier {
"Locale": this.locale.languageCode, "Locale": this.locale.languageCode,
"Theme": themeString, "Theme": themeString,
"PreviousPid": -1, "PreviousPid": -1,
"BlockUnknownConnections": blockUnknownConnections,
"ExperimentsEnabled": this.experimentsEnabled, "ExperimentsEnabled": this.experimentsEnabled,
"Experiments": experiments, "Experiments": experiments,
"StateRootPane": 0, "StateRootPane": 0,

View File

@ -32,7 +32,7 @@ class _AddContactViewState extends State<AddContactView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context).newConnectionPaneTitle), title: Text(AppLocalizations.of(context).titleManageContacts),
), ),
body: _buildForm(), body: _buildForm(),
); );
@ -40,6 +40,7 @@ class _AddContactViewState extends State<AddContactView> {
Widget _buildForm() { Widget _buildForm() {
ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion; ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
/// We display a different number of tabs dependening on the experiment setup /// We display a different number of tabs dependening on the experiment setup
bool groupsEnabled = Provider.of<Settings>(context).experimentsEnabled && Provider.of<Settings>(context).experiments[TapirGroupsExperiment]; bool groupsEnabled = Provider.of<Settings>(context).experimentsEnabled && Provider.of<Settings>(context).experiments[TapirGroupsExperiment];
return Consumer<ErrorHandler>(builder: (context, globalErrorHandler, child) { return Consumer<ErrorHandler>(builder: (context, globalErrorHandler, child) {
@ -116,14 +117,14 @@ class _AddContactViewState extends State<AddContactView> {
CwtchTextField( CwtchTextField(
controller: ctrlrContact, controller: ctrlrContact,
validator: (value) { validator: (value) {
if (value == "") { if (value == "") {
return null; return null;
} if (globalErrorHandler.invalidImportStringError) { }
if (globalErrorHandler.invalidImportStringError) {
return AppLocalizations.of(context).invalidImportString; return AppLocalizations.of(context).invalidImportString;
} else if (globalErrorHandler.contactAlreadyExistsError) { } else if (globalErrorHandler.contactAlreadyExistsError) {
return AppLocalizations.of(context).contactAlreadyExists; return AppLocalizations.of(context).contactAlreadyExists;
} else if (globalErrorHandler.explicitAddContactSuccess) { } else if (globalErrorHandler.explicitAddContactSuccess) {}
}
return null; return null;
}, },
onChanged: (String peerAddr) async { onChanged: (String peerAddr) async {

View File

@ -29,7 +29,7 @@ class _ContactsViewState extends State<ContactsView> {
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: _pushAddContact, onPressed: _pushAddContact,
tooltip: AppLocalizations.of(context).newConnectionPaneTitle, tooltip: AppLocalizations.of(context).tooltipAddContact,
child: const Icon(Icons.person_add_sharp), child: const Icon(Icons.person_add_sharp),
), ),
body: _buildContactList(), body: _buildContactList(),

View File

@ -74,8 +74,25 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
}, },
secondary: Icon(Icons.lightbulb_outline, color: settings.current().mainTextColor()), 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( SwitchListTile(
title: Text(AppLocalizations.of(context).experimentsEnabled, style: TextStyle(color: settings.current().mainTextColor())), title: Text(AppLocalizations.of(context).experimentsEnabled, style: TextStyle(color: settings.current().mainTextColor())),
subtitle: Text(AppLocalizations.of(context).descriptionExperiments),
value: settings.experimentsEnabled, value: settings.experimentsEnabled,
onChanged: (bool value) { onChanged: (bool value) {
if (value) { if (value) {
@ -94,6 +111,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
children: [ children: [
SwitchListTile( SwitchListTile(
title: Text(AppLocalizations.of(context).enableGroups, style: TextStyle(color: settings.current().mainTextColor())), 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], value: settings.experiments.containsKey(TapirGroupsExperiment) && settings.experiments[TapirGroupsExperiment],
onChanged: (bool value) { onChanged: (bool value) {
if (value) { if (value) {

View File

@ -29,20 +29,24 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context).profileName), title: Text(AppLocalizations.of(context).titleManageProfiles),
actions: [ actions: [
IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _setLoggingLevelDebug), IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _setLoggingLevelDebug),
IconButton( IconButton(
icon: Icon(Icons.lock_open), icon: Icon(Icons.lock_open),
tooltip: AppLocalizations.of(context).tooltipUnlockProfiles,
onPressed: _modalUnlockProfiles, onPressed: _modalUnlockProfiles,
), ),
IconButton(icon: Icon(Icons.settings), onPressed: _pushGlobalSettings), IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context).tooltipOpenSettings, onPressed: _pushGlobalSettings),
], ],
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: _pushAddEditProfile, onPressed: _pushAddEditProfile,
tooltip: AppLocalizations.of(context).addNewProfileBtn, tooltip: AppLocalizations.of(context).addNewProfileBtn,
child: const Icon(Icons.add), child: Icon(
Icons.add,
semanticLabel: AppLocalizations.of(context).addNewProfileBtn,
),
), ),
body: _buildProfileManager(), //_buildSuggestions(), body: _buildProfileManager(), //_buildSuggestions(),
); );
@ -105,7 +109,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
), ),
), ),
ElevatedButton( ElevatedButton(
child: Text(AppLocalizations.of(context).unlock), child: Text(AppLocalizations.of(context).unlock, semanticsLabel: AppLocalizations.of(context).unlock),
onPressed: () { onPressed: () {
Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(ctrlrPassword.value.text); Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(ctrlrPassword.value.text);
Navigator.pop(context); Navigator.pop(context);

View File

@ -1,8 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_app/views/addeditprofileview.dart'; import 'package:flutter_app/views/addeditprofileview.dart';
import 'package:flutter_app/views/contactsview.dart'; import 'package:flutter_app/views/contactsview.dart';
import 'package:flutter_app/views/doublecolview.dart'; import 'package:flutter_app/views/doublecolview.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../main.dart'; import '../main.dart';
import '../model.dart'; import '../model.dart';
@ -30,23 +32,30 @@ class _ProfileRowState extends State<ProfileRow> {
width: 60, width: 60,
height: 60, height: 60,
child: Image( child: Image(
excludeFromSemantics: true,
image: AssetImage("assets/" + profile.imagePath), image: AssetImage("assets/" + profile.imagePath),
width: 50, width: 50,
height: 50, height: 50,
))), ))),
), ),
), ),
title: Text(
profile.nickname,
semanticsLabel: profile.nickname,
style: Provider.of<FlwtchState>(context).biggerFont,
),
subtitle: ExcludeSemantics(child: Text(profile.onion)),
trailing: IconButton( trailing: IconButton(
enableFeedback: true,
tooltip: AppLocalizations.of(context).editProfile + " " + profile.nickname,
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()), icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
onPressed: () { onPressed: () {
_pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath); _pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath);
}, },
), //(nb: Icons.create is a pencil and we use it for "edit", not create) ), //(nb: Icons.create is a pencil and we use it for "edit", not create)
title: Text(
profile.nickname,
style: Provider.of<FlwtchState>(context).biggerFont,
),
subtitle: Text(profile.onion),
onTap: () { onTap: () {
setState(() { setState(() {
var flwtch = Provider.of<FlwtchState>(context, listen: false); var flwtch = Provider.of<FlwtchState>(context, listen: false);