diff --git a/README.md b/README.md index be32d48..6007306 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,46 @@ -# flutter_app +# flwtch A new Flutter application. ## Getting Started -This project is a starting point for a Flutter application. +click the play button in android studio -A few resources to get you started if this is your first Flutter project: +## l10n -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) +### Adding a new string -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +Strings are managed directly from our Lokalise(url?) project. +Keys should be valid Dart variable names in lowerCamelCase. +After adding a new key and providing/obtaining translations for it, follow the next step to update your local copy. + +### Updating translations + +Only Open Privacy staff members can update translations automatically: + +``` +flutter pub run flutter_lokalise download -v --api-token "" --project-id "" +``` + +This will download a bundle of translations from Lokalise and convert it to resource files in `lib/l10n/intl_*.arb`. +The next time Flwtch is built, Flutter will notice the changes and update `app_localizations.dart` accordingly (thanks to `generate:true` in `pubspec.yaml`). + +### Using a string + +Any widget underneath the main MaterialApp should be able to: + +``` +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +``` + +and then use: + +``` +Text(AppLocalizations.of(context).stringIdentifer), +``` + +### Configuration + +API tokens are only available to Open Privacy staff at this time, who will perform the translation updates for you as part of merging your PRs. + +With `generate: true` in `pubspec.yaml`, the Flutter build process checks `l10n.yaml` for input/output filenames. \ No newline at end of file diff --git a/android/cwtch/cwtch.aar b/android/cwtch/cwtch.aar index ffdf628..ec095ea 100644 Binary files a/android/cwtch/cwtch.aar and b/android/cwtch/cwtch.aar differ diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..4254456 --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: intl_en.arb +output-localization-file: app_localizations.dart diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index e4a45cd..729fef1 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -6,7 +6,6 @@ import 'package:flutter_app/model.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:async'; import 'package:path/path.dart' as path; -import 'package:provider/provider.dart'; import 'cwtch.dart'; import 'cwtchNotifier.dart'; diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb new file mode 100644 index 0000000..0f59776 --- /dev/null +++ b/lib/l10n/intl_de.arb @@ -0,0 +1,134 @@ +{ + "@@locale": "de", + "acceptGroupBtn": "Annehmen", + "acceptGroupInviteLabel": "Möchtest Du die Einladung annehmen", + "acknowledgedLabel": "bestätigt", + "addListItem": "Liste hinzufügen", + "addListItemBtn": "Element hinzufügen", + "addNewItem": "Ein neues Element zur Liste hinzufügen", + "addNewProfileBtn": "Neues Profil hinzufügen", + "addPeer": "Peer hinzufügen", + "addPeerTab": "Einen Peer hinzufügen", + "addProfileTitle": "Neues Profil hinzufügen", + "addressLabel": "Adresse", + "blockBtn": "Peer blockieren", + "blocked": "Blockiert", + "blockUnknownLabel": "Unbekannte Peers blockieren", + "builddate": "Aufgebaut auf: %2", + "bulletinsBtn": "Meldungen", + "chatBtn": "Chat", + "copiedClipboardNotification": "in die Zwischenablage kopiert", + "copiedToClipboardNotification": "in die Zwischenablage kopiert", + "copyBtn": "Kopieren", + "couldNotSendMsgError": "Nachricht konnte nicht gesendet werden", + "createGroup": "Gruppe erstellen", + "createGroupBtn": "Anlegen", + "createGroupTab": "Eine Gruppe erstellen", + "createGroupTitle": "Gruppe Anlegen", + "createProfileBtn": "Profil speichern", + "currentPasswordLabel": "derzeitiges Passwort", + "cwtchSettingsTitle": "Cwtch Einstellungen", + "cycleCatsAndroid": "", + "cycleCatsDesktop": "", + "cycleColoursAndroid": "", + "cycleColoursDesktop": "", + "cycleMorphsAndroid": "", + "cycleMorphsDesktop": "", + "defaultGroupName": "Tolle Gruppe", + "defaultProfileName": "Alice", + "defaultScalingText": "defaultmäßige Textgröße (Skalierungsfaktor:", + "deleteBtn": "Löschen", + "deleteConfirmLabel": "Geben Sie LÖSCHEN zur Bestätigung ein", + "deleteConfirmText": "LÖSCHEN", + "deleteProfileBtn": "Profil löschen", + "deleteProfileConfirmBtn": "Profil wirklich löschen", + "displayNameLabel": "Angezeigter Name", + "dmTooltip": "Klicken, um DM zu senden", + "dontSavePeerHistory": "Peer-Verlauf löschen", + "editProfile": "Profil bearbeiten", + "editProfileTitle": "Profil bearbeiten", + "enterProfilePassword": "Geben Sie ein Passwort ein, um Ihre Profile anzuzeigen", + "error0ProfilesLoadedForPassword": "0 Profile mit diesem Passwort geladen", + "experimentsEnabled": "Experimente aktiviert", + "groupAddr": "Adresse", + "groupName": "Gruppenname", + "groupNameLabel": "Gruppenname", + "invitation": "Einladung", + "invitationLabel": "Einladung", + "inviteBtn": "Einladen", + "inviteToGroupLabel": "In die Gruppe einladen", + "joinGroup": "Gruppe beitreten", + "joinGroupTab": "Einer Gruppe beitreten", + "largeTextLabel": "Groß", + "listsBtn": "Listen", + "loadingTor": "Tor wird geladen...", + "localeDe": "Deutsche", + "localeEn": "", + "localeEs": "", + "localeFr": "", + "localeIt": "", + "localePt": "", + "membershipDescription": "Unten steht eine Liste der Benutzer, die Nachrichten an die Gruppe gesendet haben. Möglicherweise enthält diese Benutzerzliste nicht alle, die Zugang zur Gruppe haben.", + "networkStatusAttemptingTor": "Versuche, eine Verbindung mit dem Tor-Netzwerk herzustellen", + "networkStatusConnecting": "Verbinde zu Netzwerk und Peers ...", + "networkStatusDisconnected": "Vom Internet getrennt, überprüfen Sie Ihre Verbindung", + "networkStatusOnline": "Online", + "newBulletinLabel": "Neue Meldung", + "newConnectionPaneTitle": "Neue Verbindung", + "newGroupBtn": "Neue Gruppe anlegen", + "newProfile": "Neues Profil", + "noPasswordWarning": "Wenn für dieses Konto kein Passwort verwendet wird, bedeutet dies, dass alle lokal gespeicherten Daten nicht verschlüsselt werden.", + "password": "Passwort", + "password1Label": "Passwort", + "password2Label": "Passwort erneut eingeben", + "passwordChangeError": "Fehler beim Ändern des Passworts: Das Passwort wurde abgelehnt", + "passwordErrorEmpty": "Passwort kann nicht leer sein", + "passwordErrorMatch": "Passwörter stimmen nicht überein", + "pasteAddressToAddContact": "Adresse hier hinzufügen, um einen Kontakt aufzunehmen", + "peerAddress": "Adresse", + "peerBlockedMessage": "Peer ist blockiert", + "peerName": "Namen", + "peerNotOnline": "", + "peerOfflineMessage": "Peer ist offline, Nachrichten können derzeit nicht zugestellt werden", + "pendingLabel": "Bestätigung ausstehend", + "postNewBulletinLabel": "Neue Meldung veröffentlichen", + "profileName": "Anzeigename", + "profileOnionLabel": "Senden Sie diese Adresse an Peers, mit denen Sie sich verbinden möchten", + "puzzleGameBtn": "Puzzlespiel", + "radioNoPassword": "Unverschlüsselt (kein Passwort)", + "radioUsePassword": "Passwort", + "rejectGroupBtn": "Ablehnen", + "saveBtn": "Speichern", + "savePeerHistory": "Peer-Verlauf speichern", + "savePeerHistoryDescription": "Legt fest, ob ein mit dem Peer verknüpfter Verlauf gelöscht werden soll oder nicht.", + "saveProfileBtn": "Profil speichern", + "search": "Suche...", + "searchList": "", + "server": "Server", + "serverConnectivityConnected": "Server verbunden", + "serverConnectivityDisconnected": "Server getrennt", + "serverInfo": "Server-Informationen", + "serverLabel": "Server", + "serverNotSynced": "", + "serverSynced": "", + "settingInterfaceZoom": "Zoomstufe", + "settingLanguage": "Sprache", + "settingTheme": "Thema", + "smallTextLabel": "Klein", + "themeDark": "Dunkel", + "themeLight": "Licht", + "titlePlaceholder": "Titel...", + "todoPlaceholder": "noch zu erledigen", + "unblockBtn": "Peer entblockieren", + "unlock": "Entsperren", + "update": "", + "version": "Version %1", + "versionBuilddate": "Version: %1 Aufgebaut auf: %2", + "versionTor": "Version %1 mit tor %2", + "viewGroupMembershipTooltip": "Gruppenmitgliedschaft anzeigen", + "viewServerInfo": "", + "yourDisplayName": "Ihr Anzeigename", + "yourProfiles": "Ihre Profile", + "yourServers": "Ihre Server", + "zoomLabel": "Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)" +} \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb new file mode 100644 index 0000000..8704392 --- /dev/null +++ b/lib/l10n/intl_en.arb @@ -0,0 +1,134 @@ +{ + "@@locale": "en", + "acceptGroupBtn": "Accept", + "acceptGroupInviteLabel": "Do you want to accept the invitation to", + "acknowledgedLabel": "Acknowledged", + "addListItem": "Add a New List Item", + "addListItemBtn": "Add Item", + "addNewItem": "Add a new item to the list", + "addNewProfileBtn": "Add new profile", + "addPeer": "Add Peer", + "addPeerTab": "Add a peer", + "addProfileTitle": "Add new profile", + "addressLabel": "Address", + "blockBtn": "Block Peer", + "blocked": "Blocked", + "blockUnknownLabel": "Block Unknown Peers", + "builddate": "Built on: %2", + "bulletinsBtn": "Bulletins", + "chatBtn": "Chat", + "copiedClipboardNotification": "Copied to clipboard", + "copiedToClipboardNotification": "Copied to Clipboard", + "copyBtn": "Copy", + "couldNotSendMsgError": "Could not send this message", + "createGroup": "Create group", + "createGroupBtn": "Create", + "createGroupTab": "Create a group", + "createGroupTitle": "Create Group", + "createProfileBtn": "Create Profile", + "currentPasswordLabel": "Current Password", + "cwtchSettingsTitle": "Cwtch Settings", + "cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.", + "cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.", + "cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.", + "cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.", + "cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.", + "cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.", + "defaultGroupName": "Awesome Group", + "defaultProfileName": "Alice", + "defaultScalingText": "Default size text (scale factor:", + "deleteBtn": "Delete", + "deleteConfirmLabel": "Type DELETE to confirm", + "deleteConfirmText": "DELETE", + "deleteProfileBtn": "Delete Profile", + "deleteProfileConfirmBtn": "Really Delete Profile", + "displayNameLabel": "Display Name", + "dmTooltip": "Click to DM", + "dontSavePeerHistory": "Delete Peer History", + "editProfile": "Edit Profille", + "editProfileTitle": "Edit Profile", + "enterProfilePassword": "Enter a password to view your profiles", + "error0ProfilesLoadedForPassword": "0 profiles loaded with that password", + "experimentsEnabled": "Experiments enabled", + "groupAddr": "Address", + "groupName": "Group name", + "groupNameLabel": "Group Name", + "invitation": "Invitation", + "invitationLabel": "Invitation", + "inviteBtn": "Invite", + "inviteToGroupLabel": "Invite to group", + "joinGroup": "Join group", + "joinGroupTab": "Join a group", + "largeTextLabel": "Large", + "listsBtn": "Lists", + "loadingTor": "Loading tor...", + "localeDe": "Deutsche", + "localeEn": "English", + "localeEs": "Espanol", + "localeFr": "Frances", + "localeIt": "Italiana", + "localePt": "Portuguesa", + "membershipDescription": "Below is a list of users who have sent messages to the group. This list may not reflect all users who have access to the group.", + "networkStatusAttemptingTor": "Attempting to connect to Tor network", + "networkStatusConnecting": "Connecting to network and peers...", + "networkStatusDisconnected": "Disconnected from the internet, check your connection", + "networkStatusOnline": "Online", + "newBulletinLabel": "New Bulletin", + "newConnectionPaneTitle": "New Connection", + "newGroupBtn": "Create new group", + "newProfile": "New Profile", + "noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted", + "password": "Password", + "password1Label": "Password", + "password2Label": "Reenter password", + "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...", + "peerAddress": "Address", + "peerBlockedMessage": "Peer is blocked", + "peerName": "Name", + "peerNotOnline": "Peer is Offline. Applications cannot be used right now.", + "peerOfflineMessage": "Peer is offline, messages can't be delivered right now", + "pendingLabel": "Pending", + "postNewBulletinLabel": "Post new bulletin", + "profileName": "Display name", + "profileOnionLabel": "Send this address to peers you want to connect with", + "puzzleGameBtn": "Puzzle Game", + "radioNoPassword": "Unencrypted (No password)", + "radioUsePassword": "Password", + "rejectGroupBtn": "Reject", + "saveBtn": "Save", + "savePeerHistory": "Save Peer History", + "savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.", + "saveProfileBtn": "Save Profile", + "search": "Search...", + "searchList": "Search List", + "server": "Server", + "serverConnectivityConnected": "Server Connected", + "serverConnectivityDisconnected": "Server Disconnected", + "serverInfo": "Server Information", + "serverLabel": "Server", + "serverNotSynced": "Out of Sync", + "serverSynced": "Synced", + "settingInterfaceZoom": "Zoom level", + "settingLanguage": "Language", + "settingTheme": "Theme", + "smallTextLabel": "Small", + "themeDark": "Dark", + "themeLight": "Light", + "titlePlaceholder": "title...", + "todoPlaceholder": "Todo...", + "unblockBtn": "Unblock Peer", + "unlock": "Unlock", + "update": "Update", + "version": "Version %1", + "versionBuilddate": "Version: %1 Built on: %2", + "versionTor": "Version %1 with tor %2", + "viewGroupMembershipTooltip": "View Group Membership", + "viewServerInfo": "Server Info", + "yourDisplayName": "Your Display Name", + "yourProfiles": "Your Profiles", + "yourServers": "Your Servers", + "zoomLabel": "Interface zoom (mostly affects text and button sizes)" +} \ No newline at end of file diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb new file mode 100644 index 0000000..023d7eb --- /dev/null +++ b/lib/l10n/intl_es.arb @@ -0,0 +1,134 @@ +{ + "@@locale": "es", + "acceptGroupBtn": "Aceptar", + "acceptGroupInviteLabel": "¿Quieres aceptar la invitación a ", + "acknowledgedLabel": "Reconocido", + "addListItem": "Añadir un nuevo elemento a la lista", + "addListItemBtn": "Agregar artículo", + "addNewItem": "Añadir un nuevo elemento a la lista", + "addNewProfileBtn": "Agregar nuevo perfil", + "addPeer": "Agregar Contacto", + "addPeerTab": "Agregar Contacto", + "addProfileTitle": "Agregar nuevo perfil", + "addressLabel": "Dirección", + "blockBtn": "Bloquear contacto", + "blocked": "Bloqueado", + "blockUnknownLabel": "Bloquear conexiones desconocidas", + "builddate": "Basado en: %2", + "bulletinsBtn": "Boletines", + "chatBtn": "Chat", + "copiedClipboardNotification": "Copiado al portapapeles", + "copiedToClipboardNotification": "Copiado al portapapeles", + "copyBtn": "Copiar", + "couldNotSendMsgError": "No se pudo enviar este mensaje", + "createGroup": "Crear perfil", + "createGroupBtn": "Crear", + "createGroupTab": "Crear un grupo", + "createGroupTitle": "Crear un grupo", + "createProfileBtn": "Crear perfil", + "currentPasswordLabel": "Contraseña actual", + "cwtchSettingsTitle": "Configuración de Cwtch", + "cycleCatsAndroid": "Click para cambiar categoría. Mantenga pulsado para reiniciar.", + "cycleCatsDesktop": "Click para cambiar categoría. Click derecho para reiniciar.", + "cycleColoursAndroid": "Click para cambiar colores. Mantenga pulsado para reiniciar.", + "cycleColoursDesktop": "Click para cambiar colores. Click derecho para reiniciar.", + "cycleMorphsAndroid": "Click para cambiar transformaciones. Mantenga pulsado para reiniciar.", + "cycleMorphsDesktop": "Click para cambiar transformaciones. Click derecho para reiniciar.", + "defaultGroupName": "El Grupo Asombroso", + "defaultProfileName": "Alicia", + "defaultScalingText": "Tamaño predeterminado de texto (factor de escala:", + "deleteBtn": "Eliminar", + "deleteConfirmLabel": "Escribe ELIMINAR para confirmar", + "deleteConfirmText": "ELIMINAR", + "deleteProfileBtn": "Eliminar Perfil", + "deleteProfileConfirmBtn": "Confirmar eliminar perfil", + "displayNameLabel": "Nombre de Usuario", + "dmTooltip": "Haz clic para enviar mensaje directo", + "dontSavePeerHistory": "Eliminar historial de contacto", + "editProfile": "Editar perfil", + "editProfileTitle": "Editar perfil", + "enterProfilePassword": "Ingresa tu contraseña para ver tus perfiles", + "error0ProfilesLoadedForPassword": "0 perfiles cargados con esa contraseña", + "experimentsEnabled": "Experimentos habilitados", + "groupAddr": "Dirección", + "groupName": "Nombre del grupo", + "groupNameLabel": "Nombre del grupo", + "invitation": "Invitación", + "invitationLabel": "Invitación", + "inviteBtn": "Invitar", + "inviteToGroupLabel": "Invitar al grupo", + "joinGroup": "Únete al grupo", + "joinGroupTab": "Únete a un grupo", + "largeTextLabel": "Grande", + "listsBtn": "Listas", + "loadingTor": "Cargando tor...", + "localeDe": "Alemán", + "localeEn": "Inglés", + "localeEs": "Español", + "localeFr": "Francés", + "localeIt": "Italiano", + "localePt": "Portugués", + "membershipDescription": "La lista a continuación solo muestra los miembros que han enviado mensajes al grupo, no incluye a todos los usuarios dentro del grupo", + "networkStatusAttemptingTor": "Intentando conectarse a la red Tor", + "networkStatusConnecting": "Conectando a la red y a los contactos...", + "networkStatusDisconnected": "Sin conexión, comprueba tu conexión", + "networkStatusOnline": "En línea", + "newBulletinLabel": "Nuevo Boletín", + "newConnectionPaneTitle": "Nueva conexión", + "newGroupBtn": "Crear un nuevo grupo de chat", + "newProfile": "Nuevo perfil", + "noPasswordWarning": "No usar una contraseña para esta cuenta significa que los datos almacenados localmente no serán encriptados", + "password": "Contraseña", + "password1Label": "Contraseña", + "password2Label": "Vuelve a ingresar tu contraseña", + "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...", + "peerAddress": "Dirección", + "peerBlockedMessage": "Contacto bloqueado", + "peerName": "Nombre", + "peerNotOnline": "Este contacto no está en línea, la aplicación no puede ser usada en este momento", + "peerOfflineMessage": "Este contacto no está en línea, los mensajes no pueden ser entregados en este momento", + "pendingLabel": "Pendiente", + "postNewBulletinLabel": "Publicar nuevo boletín", + "profileName": "Nombre de Usuario", + "profileOnionLabel": "Envía esta dirección a los contactos con los que quieras conectarte", + "puzzleGameBtn": "Juego de rompecabezas", + "radioNoPassword": "Sin cifrado (sin contraseña)", + "radioUsePassword": "Contraseña", + "rejectGroupBtn": "Rechazar", + "saveBtn": "Guardar", + "savePeerHistory": "Guardar el historial con contacto", + "savePeerHistoryDescription": "Determina si eliminar o no el historial asociado con el contacto.", + "saveProfileBtn": "Guardar perfil", + "search": "Búsqueda...", + "searchList": "Buscar en la lista", + "server": "Servidor", + "serverConnectivityConnected": "Servidor conectado", + "serverConnectivityDisconnected": "Servidor desconectado", + "serverInfo": "Información del servidor", + "serverLabel": "Servidor", + "serverNotSynced": "Fuera de sincronización con el servidor", + "serverSynced": "Sincronizado", + "settingInterfaceZoom": "Nivel de zoom", + "settingLanguage": "Idioma", + "settingTheme": "Tema", + "smallTextLabel": "Pequeño", + "themeDark": "Oscuro", + "themeLight": "Claro", + "titlePlaceholder": "título...", + "todoPlaceholder": "Por hacer...", + "unblockBtn": "Desbloquear contacto", + "unlock": "Desbloquear", + "update": "Actualizar", + "version": "Versión %1", + "versionBuilddate": "Versión: %1 Basado en %2", + "versionTor": "Versión %1 con tor %2", + "viewGroupMembershipTooltip": "Ver membresía del grupo", + "viewServerInfo": "Información del servidor", + "yourDisplayName": "Tu nombre de usuario", + "yourProfiles": "Tus perfiles", + "yourServers": "Tus servidores", + "zoomLabel": "Zoom de la interfaz (afecta principalmente el tamaño del texto y de los botones)" +} \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb new file mode 100644 index 0000000..ea7b94f --- /dev/null +++ b/lib/l10n/intl_fr.arb @@ -0,0 +1,134 @@ +{ + "@@locale": "fr", + "acceptGroupBtn": "Accepter", + "acceptGroupInviteLabel": "Voulez-vous accepter l'invitation au groupe", + "acknowledgedLabel": "Confirmé", + "addListItem": "Ajouter un nouvel élément", + "addListItemBtn": "", + "addNewItem": "Ajouter un nouvel élément à la liste", + "addNewProfileBtn": "", + "addPeer": "", + "addPeerTab": "", + "addProfileTitle": "", + "addressLabel": "Adresse", + "blockBtn": "", + "blocked": "", + "blockUnknownLabel": "", + "builddate": "", + "bulletinsBtn": "Bulletins", + "chatBtn": "Discuter", + "copiedClipboardNotification": "Copié dans le presse-papier", + "copiedToClipboardNotification": "Copié dans le presse-papier", + "copyBtn": "Copier", + "couldNotSendMsgError": "Impossible d'envoyer ce message", + "createGroup": "", + "createGroupBtn": "Créer", + "createGroupTab": "", + "createGroupTitle": "Créer un groupe", + "createProfileBtn": "", + "currentPasswordLabel": "", + "cwtchSettingsTitle": "Préférences Cwtch", + "cycleCatsAndroid": "", + "cycleCatsDesktop": "", + "cycleColoursAndroid": "", + "cycleColoursDesktop": "", + "cycleMorphsAndroid": "", + "cycleMorphsDesktop": "", + "defaultGroupName": "Un super groupe", + "defaultProfileName": "", + "defaultScalingText": "Taille par défaut du texte (échelle:", + "deleteBtn": "Effacer", + "deleteConfirmLabel": "", + "deleteConfirmText": "", + "deleteProfileBtn": "", + "deleteProfileConfirmBtn": "", + "displayNameLabel": "Pseudo", + "dmTooltip": "Envoyer un message privé", + "dontSavePeerHistory": "", + "editProfile": "", + "editProfileTitle": "", + "enterProfilePassword": "", + "error0ProfilesLoadedForPassword": "", + "experimentsEnabled": "", + "groupAddr": "", + "groupName": "", + "groupNameLabel": "Nom du groupe", + "invitation": "", + "invitationLabel": "Invitation", + "inviteBtn": "Invitation", + "inviteToGroupLabel": "Inviter quelqu'un", + "joinGroup": "", + "joinGroupTab": "", + "largeTextLabel": "Large", + "listsBtn": "Listes", + "loadingTor": "", + "localeDe": "", + "localeEn": "", + "localeEs": "", + "localeFr": "", + "localeIt": "", + "localePt": "", + "membershipDescription": "Liste des utilisateurs ayant envoyés un ou plusieurs messages au groupe. Cette liste peut ne pas être representatives de l'ensemble des membres du groupe.", + "networkStatusAttemptingTor": "", + "networkStatusConnecting": "", + "networkStatusDisconnected": "", + "networkStatusOnline": "", + "newBulletinLabel": "Nouveau bulletin", + "newConnectionPaneTitle": "", + "newGroupBtn": "Créer un nouveau groupe", + "newProfile": "", + "noPasswordWarning": "", + "password": "", + "password1Label": "", + "password2Label": "", + "passwordChangeError": "", + "passwordErrorEmpty": "", + "passwordErrorMatch": "", + "pasteAddressToAddContact": "... coller une adresse ici pour ajouter un contact...", + "peerAddress": "", + "peerBlockedMessage": "", + "peerName": "", + "peerNotOnline": "", + "peerOfflineMessage": "", + "pendingLabel": "En attente", + "postNewBulletinLabel": "Envoyer un nouveau bulletin", + "profileName": "", + "profileOnionLabel": "", + "puzzleGameBtn": "Puzzle", + "radioNoPassword": "", + "radioUsePassword": "", + "rejectGroupBtn": "Refuser", + "saveBtn": "Sauvegarder", + "savePeerHistory": "", + "savePeerHistoryDescription": "", + "saveProfileBtn": "", + "search": "", + "searchList": "", + "server": "", + "serverConnectivityConnected": "", + "serverConnectivityDisconnected": "", + "serverInfo": "", + "serverLabel": "Serveur", + "serverNotSynced": "", + "serverSynced": "", + "settingInterfaceZoom": "", + "settingLanguage": "", + "settingTheme": "", + "smallTextLabel": "Petit", + "themeDark": "", + "themeLight": "", + "titlePlaceholder": "titre...", + "todoPlaceholder": "A faire...", + "unblockBtn": "", + "unlock": "", + "update": "", + "version": "", + "versionBuilddate": "", + "versionTor": "", + "viewGroupMembershipTooltip": "", + "viewServerInfo": "", + "yourDisplayName": "", + "yourProfiles": "", + "yourServers": "", + "zoomLabel": "Interface zoom (essentiellement la taille du texte et des composants de l'interface)" +} \ No newline at end of file diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb new file mode 100644 index 0000000..5526c92 --- /dev/null +++ b/lib/l10n/intl_it.arb @@ -0,0 +1,134 @@ +{ + "@@locale": "it", + "acceptGroupBtn": "Accetta", + "acceptGroupInviteLabel": "Vuoi accettare l'invito a", + "acknowledgedLabel": "Riconosciuto", + "addListItem": "Aggiungi un nuovo elemento alla lista", + "addListItemBtn": "Aggiungi elemento", + "addNewItem": "Aggiungi un nuovo elemento alla lista", + "addNewProfileBtn": "Aggiungi nuovo profilo", + "addPeer": "Aggiungi peer", + "addPeerTab": "Aggiungi un peer", + "addProfileTitle": "Aggiungi nuovo profilo", + "addressLabel": "Indirizzo", + "blockBtn": "Blocca il peer", + "blocked": "Bloccato", + "blockUnknownLabel": "Blocca peer sconosciuti", + "builddate": "Costruito il: %2", + "bulletinsBtn": "Bollettini", + "chatBtn": "Chat", + "copiedClipboardNotification": "Copiato negli Appunti", + "copiedToClipboardNotification": "Copiato negli Appunti", + "copyBtn": "Copia", + "couldNotSendMsgError": "Impossibile inviare questo messaggio", + "createGroup": "Crea un gruppo", + "createGroupBtn": "Crea", + "createGroupTab": "Crea un gruppo", + "createGroupTitle": "Crea un gruppo", + "createProfileBtn": "Crea un profilo", + "currentPasswordLabel": "Password corrente", + "cwtchSettingsTitle": "Impostazioni di Cwtch", + "cycleCatsAndroid": "Fare clic per scorrere le categorie.\nPressione lunga per resettare.", + "cycleCatsDesktop": "Fare clic per scorrere le categorie.\nCliccare con il tasto destro per resettare.", + "cycleColoursAndroid": "Fare clic per scorrere i colori.\nPressione lunga per resettare.", + "cycleColoursDesktop": "Fare clic per scorrere i colori.\nCliccare con il tasto destro per resettare.", + "cycleMorphsAndroid": "Fare clic per scorrere i morph.\nPressione lunga per resettare.", + "cycleMorphsDesktop": "Fare clic per scorrere i morph.\nCliccare con il tasto destro per resettare.", + "defaultGroupName": "Gruppo fantastico", + "defaultProfileName": "Alice", + "defaultScalingText": "Testo di dimensioni predefinite (fattore di scala:", + "deleteBtn": "Elimina", + "deleteConfirmLabel": "Digita ELIMINA per confermare", + "deleteConfirmText": "ELIMINA", + "deleteProfileBtn": "Elimina profilo", + "deleteProfileConfirmBtn": "Elimina realmente il profilo", + "displayNameLabel": "Nome visualizzato", + "dmTooltip": "Clicca per inviare un Messagio Diretto", + "dontSavePeerHistory": "Elimina cronologia dei peer", + "editProfile": "Modifica profilo", + "editProfileTitle": "Modifica profilo", + "enterProfilePassword": "Inserisci una password per visualizzare i tuoi profili", + "error0ProfilesLoadedForPassword": "0 profili caricati con quella password", + "experimentsEnabled": "Esperimenti abilitati", + "groupAddr": "Indirizzo", + "groupName": "Nome del gruppo", + "groupNameLabel": "Nome del gruppo", + "invitation": "Invito", + "invitationLabel": "Invito", + "inviteBtn": "Invitare", + "inviteToGroupLabel": "Invitare nel gruppo", + "joinGroup": "Unisciti al gruppo", + "joinGroupTab": "Unisciti a un gruppo", + "largeTextLabel": "Grande", + "listsBtn": "Liste", + "loadingTor": "Caricamento di tor...", + "localeDe": "Tedesco", + "localeEn": "Inglese", + "localeEs": "Spagnolo", + "localeFr": "Francese", + "localeIt": "Italiano", + "localePt": "Portoghese", + "membershipDescription": "Di seguito è riportato un elenco di utenti che hanno inviato messaggi al gruppo. Questo elenco potrebbe non corrispondere a tutti gli utenti che hanno accesso al gruppo.", + "networkStatusAttemptingTor": "Tentativo di connessione alla rete Tor", + "networkStatusConnecting": "Connessione alla rete e ai peer ...", + "networkStatusDisconnected": "Disconnesso da Internet, controlla la tua connessione", + "networkStatusOnline": "Online", + "newBulletinLabel": "Nuovo bollettino", + "newConnectionPaneTitle": "Nuova connessione", + "newGroupBtn": "Crea un nuovo gruppo", + "newProfile": "Nuovo profilo", + "noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati", + "password": "Password", + "password1Label": "Password", + "password2Label": "Reinserire la password", + "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 ...", + "peerAddress": "Indirizzo", + "peerBlockedMessage": "Il peer è bloccato", + "peerName": "Nome", + "peerNotOnline": "Il peer è offline. Le applicazioni non possono essere utilizzate in questo momento.", + "peerOfflineMessage": "Il peer è offline, i messaggi non possono essere recapitati in questo momento", + "pendingLabel": "In corso", + "postNewBulletinLabel": "Pubblica un nuovo bollettino", + "profileName": "Nome visualizzato", + "profileOnionLabel": "Inviare questo indirizzo ai peer con cui si desidera connettersi", + "puzzleGameBtn": "Gioco di puzzle", + "radioNoPassword": "Non criptato (senza password)", + "radioUsePassword": "Password", + "rejectGroupBtn": "Rifiuta", + "saveBtn": "Salva", + "savePeerHistory": "Salva cronologia peer", + "savePeerHistoryDescription": "Determina se eliminare o meno ogni cronologia eventualmente associata al peer.", + "saveProfileBtn": "Salva il profilo", + "search": "Ricerca...", + "searchList": "Cerca nella lista", + "server": "Server", + "serverConnectivityConnected": "Server connesso", + "serverConnectivityDisconnected": "Server disconnesso", + "serverInfo": "Informazioni sul server", + "serverLabel": "Server", + "serverNotSynced": "Non sincronizzato", + "serverSynced": "Sincronizzato", + "settingInterfaceZoom": "Livello di zoom", + "settingLanguage": "Lingua", + "settingTheme": "Tema", + "smallTextLabel": "Piccolo", + "themeDark": "Scuro", + "themeLight": "Chiaro", + "titlePlaceholder": "titolo...", + "todoPlaceholder": "Da fare...", + "unblockBtn": "Sblocca il peer", + "unlock": "Sblocca", + "update": "Aggiornamento", + "version": "Versione %1", + "versionBuilddate": "Versione: %1 Costruito il: %2", + "versionTor": "Versione %1 con tor %2", + "viewGroupMembershipTooltip": "Visualizza i membri del gruppo", + "viewServerInfo": "Informazioni sul server", + "yourDisplayName": "Il tuo nome visualizzato", + "yourProfiles": "I tuoi profili", + "yourServers": "I tuoi server", + "zoomLabel": "Zoom dell'interfaccia (influisce principalmente sulle dimensioni del testo e dei pulsanti)" +} \ No newline at end of file diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb new file mode 100644 index 0000000..4812797 --- /dev/null +++ b/lib/l10n/intl_pt.arb @@ -0,0 +1,134 @@ +{ + "@@locale": "pt", + "acceptGroupBtn": "Aceitar", + "acceptGroupInviteLabel": "Você quer aceitar o convite para", + "acknowledgedLabel": "Confirmada", + "addListItem": "Adicionar Item à Lista", + "addListItemBtn": "", + "addNewItem": "Adicionar novo item à lista", + "addNewProfileBtn": "", + "addPeer": "", + "addPeerTab": "", + "addProfileTitle": "", + "addressLabel": "Endereço", + "blockBtn": "", + "blocked": "", + "blockUnknownLabel": "", + "builddate": "", + "bulletinsBtn": "Boletins", + "chatBtn": "Chat", + "copiedClipboardNotification": "Copiado", + "copiedToClipboardNotification": "Copiado", + "copyBtn": "Copiar", + "couldNotSendMsgError": "Não deu para enviar esta mensagem", + "createGroup": "", + "createGroupBtn": "Criar", + "createGroupTab": "", + "createGroupTitle": "Criar Grupo", + "createProfileBtn": "", + "currentPasswordLabel": "", + "cwtchSettingsTitle": "Configurações do Cwtch", + "cycleCatsAndroid": "", + "cycleCatsDesktop": "", + "cycleColoursAndroid": "", + "cycleColoursDesktop": "", + "cycleMorphsAndroid": "", + "cycleMorphsDesktop": "", + "defaultGroupName": "Grupo incrível", + "defaultProfileName": "", + "defaultScalingText": "Texto tamanho padrão (fator de escala: ", + "deleteBtn": "Deletar", + "deleteConfirmLabel": "", + "deleteConfirmText": "", + "deleteProfileBtn": "", + "deleteProfileConfirmBtn": "", + "displayNameLabel": "Nome de Exibição", + "dmTooltip": "Clique para DM", + "dontSavePeerHistory": "", + "editProfile": "", + "editProfileTitle": "", + "enterProfilePassword": "", + "error0ProfilesLoadedForPassword": "", + "experimentsEnabled": "", + "groupAddr": "", + "groupName": "", + "groupNameLabel": "Nome do Grupo", + "invitation": "", + "invitationLabel": "Convite", + "inviteBtn": "Convidar", + "inviteToGroupLabel": "Convidar ao grupo", + "joinGroup": "", + "joinGroupTab": "", + "largeTextLabel": "Grande", + "listsBtn": "Listas", + "loadingTor": "", + "localeDe": "", + "localeEn": "", + "localeEs": "", + "localeFr": "", + "localeIt": "", + "localePt": "", + "membershipDescription": "A lista abaixo é de usuários que enviaram mensagens ao grupo. Essa lista pode não refletir todos os usuários que têm acesso ao grupo.", + "networkStatusAttemptingTor": "", + "networkStatusConnecting": "", + "networkStatusDisconnected": "", + "networkStatusOnline": "", + "newBulletinLabel": "Novo Boletim", + "newConnectionPaneTitle": "", + "newGroupBtn": "Criar novo grupo", + "newProfile": "", + "noPasswordWarning": "", + "password": "", + "password1Label": "", + "password2Label": "", + "passwordChangeError": "", + "passwordErrorEmpty": "", + "passwordErrorMatch": "", + "pasteAddressToAddContact": "… cole um endereço aqui para adicionar um contato…", + "peerAddress": "", + "peerBlockedMessage": "", + "peerName": "", + "peerNotOnline": "", + "peerOfflineMessage": "", + "pendingLabel": "Pendente", + "postNewBulletinLabel": "Postar novo boletim", + "profileName": "", + "profileOnionLabel": "", + "puzzleGameBtn": "Jogo de Adivinhação", + "radioNoPassword": "", + "radioUsePassword": "", + "rejectGroupBtn": "Recusar", + "saveBtn": "Salvar", + "savePeerHistory": "", + "savePeerHistoryDescription": "", + "saveProfileBtn": "", + "search": "", + "searchList": "", + "server": "", + "serverConnectivityConnected": "", + "serverConnectivityDisconnected": "", + "serverInfo": "", + "serverLabel": "Servidor", + "serverNotSynced": "", + "serverSynced": "", + "settingInterfaceZoom": "", + "settingLanguage": "", + "settingTheme": "", + "smallTextLabel": "Pequeno", + "themeDark": "", + "themeLight": "", + "titlePlaceholder": "título…", + "todoPlaceholder": "Afazer…", + "unblockBtn": "", + "unlock": "", + "update": "", + "version": "", + "versionBuilddate": "", + "versionTor": "", + "viewGroupMembershipTooltip": "", + "viewServerInfo": "", + "yourDisplayName": "", + "yourProfiles": "", + "yourServers": "", + "zoomLabel": "Zoom da interface (afeta principalmente tamanho de texto e botões)" +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 76b4646..0ab3ad9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,3 @@ -import 'dart:collection'; import 'package:flutter_app/cwtch/ffi.dart'; import 'package:flutter_app/cwtch/gomobile.dart'; import 'package:flutter/material.dart'; @@ -11,6 +10,7 @@ import 'views/profilemgrview.dart'; import 'views/splashView.dart'; import 'dart:io' show Platform; import 'opaque.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; void main() => runApp(Flwtch()); @@ -72,6 +72,9 @@ class FlwtchState extends State { builder: (context, widget) { return Consumer( builder: (context, opaque, child) => MaterialApp( + locale: Locale("es",''), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, title: 'Cwtch', theme: ThemeData( visualDensity: VisualDensity.adaptivePlatformDensity, diff --git a/lib/model.dart b/lib/model.dart index d04d225..aa5162a 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -1,17 +1,16 @@ import 'dart:convert'; -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; + import 'package:flutter/cupertino.dart'; import 'dart:async'; import 'dart:collection'; import 'cwtch/cwtch.dart'; -import 'main.dart'; //////////////////// /// UI State /// //////////////////// +//todo: delete class ProfileModel { String onion; String nickname; @@ -20,6 +19,7 @@ class ProfileModel { HashMap contacts; } +//todo: delete class ContactModel { String onion; String nickname; @@ -32,9 +32,13 @@ class ContactModel { ContactModel({this.onion, this.nickname, this.status, this.isInvitation, this.isBlocked, this.imagePath}); } +//todo: delete class DanMessageModel { + // ignore: non_constant_identifier_names String Timestamp; + // ignore: non_constant_identifier_names bool Acknowledged; + // ignore: non_constant_identifier_names String Message; } @@ -81,6 +85,27 @@ class ContactListState extends ChangeNotifier { List _onions = []; int get num => _onions.length; + ContactListState(Cwtch cwtch, String profileOnion) { + cwtch.GetContacts(profileOnion).then((jsonStr) { + if (jsonStr == null) return; + + print("contacts: " + jsonStr); + List contacts = jsonDecode(jsonStr); + contacts.forEach((c) { + add(ContactInfoState( + profileOnion: profileOnion, + onion: c["onion"], + nickname: c["name"], + isGroup: false, + isInvitation: false, + isBlocked: false, + status: c["status"], + imagePath: "", + )); + }); + }); + } + void addAll(Iterable newOnions) { _onions.addAll(newOnions); notifyListeners(); @@ -115,7 +140,7 @@ class ProfileInfoState extends ChangeNotifier { String get nickname => this._nickname; set nickname(String newValue) { - this.nickname = newValue; + this._nickname = newValue; notifyListeners(); } @@ -130,6 +155,12 @@ class ProfileInfoState extends ChangeNotifier { this._unreadMessages = newVal; notifyListeners(); } + + @override + void dispose() { + super.dispose(); + print("profileinfostate.dispose()"); + } } class ContactInfoState extends ChangeNotifier { @@ -158,6 +189,18 @@ class ContactInfoState extends ChangeNotifier { notifyListeners(); } + get isInvitation => this._isInvitation; + set isInvitation(bool newVal) { + this._isInvitation = newVal; + notifyListeners(); + } + + get status => this._status; + set status(String newVal) { + this._status = newVal; + notifyListeners(); + } + get unreadMessages => this._unreadMessages; set unreadMessages(int newVal) { this._unreadMessages = newVal; diff --git a/lib/views/addcontactview.dart b/lib/views/addcontactview.dart index 9315fe5..48300cf 100644 --- a/lib/views/addcontactview.dart +++ b/lib/views/addcontactview.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class AddContactView extends StatefulWidget { @override @@ -10,7 +11,7 @@ class _AddContactViewState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Add Contact'), + title: Text(AppLocalizations.of(context).newConnectionPaneTitle), ), body: _buildForm(), ); @@ -22,9 +23,9 @@ class _AddContactViewState extends State { spacing: 20.0, runSpacing: 20.0, children: [ - Text("display name"), - Text("peer handle or group invite or server bundle"), - Text("Create/save"), + Text(AppLocalizations.of(context).profileName), + Text("peer handle or group invite or server bundle"),//todo + Text(AppLocalizations.of(context).createGroupBtn), ], )); } diff --git a/lib/views/addeditprofileview.dart b/lib/views/addeditprofileview.dart index 049d455..8b6363a 100644 --- a/lib/views/addeditprofileview.dart +++ b/lib/views/addeditprofileview.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../main.dart'; @@ -26,7 +27,7 @@ class _AddEditProfileViewState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text((widget.profileOnion == "" ? 'Add' : 'Edit') + ' Profile'), + title: Text(widget.profileOnion == "" ? AppLocalizations.of(context).addProfileTitle : AppLocalizations.of(context).editProfileTitle), ), body: _buildForm(), ); @@ -38,15 +39,16 @@ class _AddEditProfileViewState extends State { spacing: 20.0, runSpacing: 20.0, children: [ - Text("Display name"), + Text(AppLocalizations.of(context).displayNameLabel), SizedBox(width:200, height: 60, child: TextField(controller: ctrlrNick,)), - widget.profileOnion == "" ? SizedBox(width:1,height:1,) : Text("Cwtch Address"), + widget.profileOnion == "" ? SizedBox(width:1,height:1,) : Text(AppLocalizations.of(context).addressLabel), widget.profileOnion == "" ? SizedBox(width:1,height:1,) : SizedBox(width:200,height:60,child:TextField(controller: ctrlrOnion)), - Text("Password/unencrypted"), - Text("Password"), + Text(AppLocalizations.of(context).radioUsePassword), + Text(AppLocalizations.of(context).radioNoPassword), + Text(AppLocalizations.of(context).password1Label), SizedBox(width:200, height: 60, child: TextField(controller: ctrlrPass,)), - Text("Confirm"), - ElevatedButton(onPressed: _createPressed, child: Text("do the thing"),), + Text(AppLocalizations.of(context).password2Label), + ElevatedButton(onPressed: _createPressed, child: Text(widget.profileOnion == "" ? AppLocalizations.of(context).addNewProfileBtn : AppLocalizations.of(context).saveProfileBtn),), ], )); } diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart index c8a4317..41c9706 100644 --- a/lib/views/contactsview.dart +++ b/lib/views/contactsview.dart @@ -1,16 +1,13 @@ -import 'dart:collection'; -import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_app/widgets/contactrow.dart'; import 'package:provider/provider.dart'; import '../main.dart'; -import '../opaque.dart'; import 'addcontactview.dart'; -import 'messageview.dart'; import '../model.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ContactsView extends StatefulWidget { - const ContactsView({Key key, this.profile}) : super(key: key); - final ProfileInfoState profile; + const ContactsView({Key key}) : super(key: key); @override _ContactsViewState createState() => _ContactsViewState(); @@ -18,35 +15,38 @@ class ContactsView extends StatefulWidget { class _ContactsViewState extends State { _ContactsViewState(); - Map _contacts = new HashMap(); + // Map _contacts = new HashMap(); - @override - void didChangeDependencies() { - super.didChangeDependencies(); - - Provider.of(context).cwtch.GetContacts(widget.profile.onion).then((jsonContacts) { - print("got contact: $jsonContacts"); - setState(() { - List contacts = jsonDecode(jsonContacts); - contacts.forEach((onion) { - _contacts.putIfAbsent(onion['onion'], () => ContactModel(onion: onion['onion'], nickname: onion['name'], status: onion['status'])); - }); - }); - }); - } + // @override + // void didChangeDependencies() { + // super.didChangeDependencies(); + // + // Provider.of(context).onions.forEach((contact) { + // _contacts.putIfAbsent(contact.onion, () => ContactModel(contact); + // }); + // .cwtch.GetContacts(widget.profile.onion).then((jsonContacts) { + // print("got contact: $jsonContacts"); + // setState(() { + // List contacts = jsonDecode(jsonContacts); + // contacts.forEach((onion) { + // _contacts.putIfAbsent(onion['onion'], () => ContactModel(onion: onion['onion'], nickname: onion['name'], status: onion['status'])); + // }); + // }); + // }); + // } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(widget.profile.nickname + '\'s contacts'), + title: Text("%1's contacts".replaceAll("%1", Provider.of(context).nickname ?? Provider.of(context).onion ?? '')),//todo actions: [ IconButton(icon: Icon(Icons.copy), onPressed: _copyOnion,), ], ), floatingActionButton: FloatingActionButton( onPressed: _pushAddContact, - tooltip: 'New Contact', + tooltip: AppLocalizations.of(context).newConnectionPaneTitle, child: const Icon(Icons.person_add_sharp), ), body: _buildContactList(), @@ -57,32 +57,11 @@ class _ContactsViewState extends State { return StreamBuilder( stream: Provider.of(context).appStatus.contactEvents(), builder: (BuildContext context, AsyncSnapshot snapshot) { - final tiles = _contacts.values.map( - (ContactModel contact) { - return ListTile( - leading: SizedBox( - width: 60, - height: 60, - child: ClipOval( - child: SizedBox(width:60, height:60, child:Container(color:Colors.white, width: 60, height: 60, child: Image(image: AssetImage("assets/profiles/001-centaur.png"), width:50,height:50,))), - //child: SizedBox(width:60, height:60, child:Container(color:Colors.white, width: 60, height: 60, child: Image(image: AssetImage(contact.imagePath), width:50,height:50,))), - ), - ), - trailing: contact.isInvitation != null && contact.isInvitation ? Column(children:[Icon(Icons.favorite, color: Opaque.current().mainTextColor()),Icon(Icons.delete, color: Opaque.current().mainTextColor())]) : Text("99+"),//(nb: Icons.create is a pencil and we use it for "edit", not create) - title: Text( - contact.nickname, - style: Provider.of(context).biggerFont, - ), - subtitle: Text(contact.status), - onTap: () { - setState(() { - var flwtch = Provider.of(context, listen:false); - flwtch.setState(() => flwtch.selectedConversation = contact.onion); - - // case 2/3 handled by Double/TripleColumnView respectively - if (flwtch.columns.length == 1) _pushMessageView(contact.onion); - }); - }, + final tiles = Provider.of(context).onions.map( + (ContactInfoState contact) { + return ChangeNotifierProvider( + create: (context) => contact, + builder: (context, child) => ContactRow(), ); }, ); @@ -97,19 +76,6 @@ class _ContactsViewState extends State { ); } - void _pushMessageView(String handle) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return Provider( - create: (_) => Provider.of(context), - child: MessageView(profile: widget.profile, conversationHandle: handle), - ); - }, - ), - ); - } - void _pushAddContact() { Navigator.of(context).push( MaterialPageRoute( @@ -124,7 +90,7 @@ class _ContactsViewState extends State { } void _copyOnion() { - final snackBar = SnackBar(content: Text('NYI:( Copied profile address to clipboard'));//todo + final snackBar = SnackBar(content: Text(AppLocalizations.of(context).copiedClipboardNotification));//todo // Find the Scaffold in the widget tree and use it to show a SnackBar. ScaffoldMessenger.of(context).showSnackBar(snackBar); } diff --git a/lib/views/doublecolview.dart b/lib/views/doublecolview.dart index 5b260c0..c84cb4f 100644 --- a/lib/views/doublecolview.dart +++ b/lib/views/doublecolview.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_app/views/profilemgrview.dart'; import 'package:provider/provider.dart'; -import 'package:flutter/material.dart'; import '../main.dart'; import 'contactsview.dart'; @@ -21,11 +19,13 @@ class _DoubleColumnViewState extends State { children: [ Flexible( flex: flwtch.columns[0], - child: ContactsView(profile: flwtch.selectedProfile), + child: ContactsView(), ), Flexible( flex: flwtch.columns[1], - child: flwtch.selectedConversation == "" ? Center(child:Text("pick a contact")) : Container(child:MessageView(profile:flwtch.selectedProfile, conversationHandle:flwtch.selectedConversation)), + child: flwtch.selectedConversation == "" ? + Center(child:Text("pick a contact")) : //dev + Container(child:MessageView(profile:flwtch.selectedProfile, conversationHandle:flwtch.selectedConversation)), ), ], ); diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 9f1602d..c5c3b9f 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_app/main.dart'; import 'package:flutter_app/opaque.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class GlobalSettingsView extends StatefulWidget { @override @@ -21,7 +21,7 @@ class _GlobalSettingsViewState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Cwtch Settings'), + title: Text(AppLocalizations.of(context).cwtchSettingsTitle), ), body: _buildSettingsList(), ); @@ -32,14 +32,14 @@ class _GlobalSettingsViewState extends State { builder: (context, theme, child) { return Center(child: Column( children: [ - Text("Language"), + Text(AppLocalizations.of(context).settingLanguage), TextField( controller: myController, onChanged: (text) { print("First text field: $text"); }, ), - Text("Zoom"), + Text(AppLocalizations.of(context).settingInterfaceZoom), SwitchListTile( title: Text('Theme', style: TextStyle(color: theme.current().mainTextColor())), @@ -54,11 +54,11 @@ class _GlobalSettingsViewState extends State { secondary: Icon(Icons.lightbulb_outline, color: theme.current().mainTextColor()), ), - Text("Experiments enabled"), - Text("Text magnification reference"), - Text("Acknowledgements"), - Text("Version: xxx"), - Text("Built on: xxx"), + Text(AppLocalizations.of(context).experimentsEnabled), + Text("Text magnification reference"),//dev + Text("Acknowledgements"),//todo + Text(AppLocalizations.of(context).version), + Text(AppLocalizations.of(context).builddate), ] )); } diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 8436ae4..ccc896b 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -1,11 +1,6 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:flutter_app/cwtch/cwtch.dart'; -import 'package:provider/provider.dart'; -import '../main.dart'; import '../model.dart'; import '../opaque.dart'; import '../widgets/messagelist.dart'; @@ -64,27 +59,27 @@ class _MessageViewState extends State { ), SizedBox( width: 100, - height: 80, - child: Column( - children: [ - ElevatedButton( - child: Icon(Icons.send, color: Opaque.current().mainTextColor()), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()), - ), onPressed: _sendMessage, - ), - Row ( - children: [ - SizedBox(width:45, child:ElevatedButton( - child: Icon(Icons.emoji_emotions_outlined, color: Opaque.current().mainTextColor()) - )), - SizedBox(width:45, child:ElevatedButton( - child: Icon(Icons.attach_file, color: Opaque.current().mainTextColor()) - )), - ] - ) - ] - ), + height: 80, + child: Column( + children: [ + ElevatedButton( + child: Icon(Icons.send, color: Opaque.current().mainTextColor()), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()), + ), onPressed: _sendMessage, + ), + Row ( + children: [ + SizedBox(width:45, child:ElevatedButton( + child: Icon(Icons.emoji_emotions_outlined, color: Opaque.current().mainTextColor()) + )), + SizedBox(width:45, child:ElevatedButton( + child: Icon(Icons.attach_file, color: Opaque.current().mainTextColor()) + )), + ] + ) + ] + ), ), ], ), diff --git a/lib/views/profilemgrview.dart b/lib/views/profilemgrview.dart index 0b85620..599cff2 100644 --- a/lib/views/profilemgrview.dart +++ b/lib/views/profilemgrview.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_app/widgets/profilerow.dart'; import 'package:provider/provider.dart'; import '../main.dart'; @@ -26,21 +27,26 @@ class _ProfileMgrViewState extends State { Widget build(BuildContext context) { return Scaffold ( appBar: AppBar( - title: Text('Profiles'), + title: Text(AppLocalizations.of(context).profileName), actions: [ + IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _testChangingContactInfo), IconButton(icon: Icon(Icons.lock_open), onPressed: _modalUnlockProfiles,), IconButton(icon: Icon(Icons.settings), onPressed: _pushGlobalSettings), ], ), floatingActionButton: FloatingActionButton( onPressed: _pushAddEditProfile, - tooltip: 'New Profile', + tooltip: AppLocalizations.of(context).addNewProfileBtn, child: const Icon(Icons.add), ), body: _buildProfileManager(),//_buildSuggestions(), ); } + void _testChangingContactInfo() { + Provider.of(context, listen:false).onions.first.nickname = "yay!"; + } + void _pushGlobalSettings() { Navigator.of(context).push( MaterialPageRoute( @@ -79,17 +85,17 @@ class _ProfileMgrViewState extends State { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - const Text('Enter a password to view your profiles'), + Text(AppLocalizations.of(context).enterProfilePassword), TextField( obscureText: true, controller: ctrlrPassword, decoration: InputDecoration( border: OutlineInputBorder(), - labelText: 'Password', + labelText: AppLocalizations.of(context).password1Label, ), ), ElevatedButton( - child: const Text('Unlock'), + child: Text(AppLocalizations.of(context).unlock), onPressed: () { Provider.of(context, listen: false).cwtch.LoadProfiles(ctrlrPassword.value.text); Navigator.pop(context); @@ -105,9 +111,9 @@ class _ProfileMgrViewState extends State { Widget _buildProfileManager() { final tiles = Provider.of(context).onions.map( (ProfileInfoState profile) { - return ChangeNotifierProvider( - create: (context) => profile, - builder: (context, child) => ProfileRow(profile), + return ChangeNotifierProvider.value( + value: profile, + builder: (context, child) => ProfileRow(), ); }, ); diff --git a/lib/views/triplecolview.dart b/lib/views/triplecolview.dart index 9b1905d..99f22f4 100644 --- a/lib/views/triplecolview.dart +++ b/lib/views/triplecolview.dart @@ -24,11 +24,13 @@ class _TripleColumnViewState extends State { ), Flexible( flex: flwtch.columns[1], - child: flwtch.selectedProfile == null ? Center(child:Text("pick a profile")) : ContactsView(profile:flwtch.selectedProfile), + child: flwtch.selectedProfile == null ? Center(child:Text("pick a profile")) : ContactsView(),//dev ), Flexible( flex: flwtch.columns[2], - child: flwtch.selectedConversation == "" ? Center(child:Text("pick a contact")) : Container(child:MessageView(profile:flwtch.selectedProfile, conversationHandle:flwtch.selectedConversation)), + child: flwtch.selectedConversation == "" ? + Center(child:Text("pick a contact")) : //dev + Container(child:MessageView(profile:flwtch.selectedProfile, conversationHandle:flwtch.selectedConversation)), ), ] ); diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart new file mode 100644 index 0000000..29fe637 --- /dev/null +++ b/lib/widgets/contactrow.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_app/views/messageview.dart'; +import 'package:provider/provider.dart'; + +import '../main.dart'; +import '../model.dart'; +import '../opaque.dart'; + +class ContactRow extends StatefulWidget { + @override + _ContactRowState createState() => _ContactRowState(); +} + +class _ContactRowState extends State { + @override + Widget build(BuildContext context) { + var contact = Provider.of(context); + return ListTile( + leading: SizedBox( + width: 60, + height: 60, + child: ClipOval( + child: SizedBox(width:60, height:60, child:Container(color:Colors.white, width: 60, height: 60, child: Image(image: AssetImage("assets/profiles/001-centaur.png"), width:50,height:50,))), + //child: SizedBox(width:60, height:60, child:Container(color:Colors.white, width: 60, height: 60, child: Image(image: AssetImage(contact.imagePath), width:50,height:50,))), + ), + ), + trailing: contact.isInvitation != null && contact.isInvitation ? Column(children:[Icon(Icons.favorite, color: Opaque.current().mainTextColor()),Icon(Icons.delete, color: Opaque.current().mainTextColor())]) : Text("99+"),//(nb: Icons.create is a pencil and we use it for "edit", not create) + title: Text( + contact.nickname, + style: Provider.of(context).biggerFont, + ), + subtitle: Text(contact.status), + onTap: () { + setState(() { + var flwtch = Provider.of(context, listen:false); + flwtch.setState(() => flwtch.selectedConversation = contact.onion); + + // case 2/3 handled by Double/TripleColumnView respectively + if (flwtch.columns.length == 1) _pushMessageView(contact.onion); + }); + }, + ); + } + + void _pushMessageView(String handle) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext builderContext) { + return MultiProvider( + providers: [ChangeNotifierProvider(create: (_) => Provider.of(context)),], + child: MessageView(conversationHandle: handle), + ); + }, + ), + ); + } +} diff --git a/lib/widgets/messagelist.dart b/lib/widgets/messagelist.dart index 7354364..b2697a6 100644 --- a/lib/widgets/messagelist.dart +++ b/lib/widgets/messagelist.dart @@ -35,7 +35,7 @@ class _MessageListState extends State { itemCount: conversationNumMessages, itemBuilder: (context, index) { return MessageBubble( - profile: widget.profile, + profile: Provider.of(context), contactOnion: widget.conversationHandle, messageIndex: index, ); @@ -50,7 +50,7 @@ class _MessageListState extends State { return; } - Provider.of(context, listen: false).cwtch.NumMessages(widget.profile.onion, widget.conversationHandle).then((n) { + Provider.of(context, listen: false).cwtch.NumMessages(Provider.of(context, listen: false).onion, widget.conversationHandle).then((n) { if (n != conversationNumMessages) setState(() => conversationNumMessages = n); }); } diff --git a/lib/widgets/profilerow.dart b/lib/widgets/profilerow.dart index fe49728..5e83ec8 100644 --- a/lib/widgets/profilerow.dart +++ b/lib/widgets/profilerow.dart @@ -9,9 +9,6 @@ import '../model.dart'; import '../opaque.dart'; class ProfileRow extends StatefulWidget { - final ProfileInfoState profile; - ProfileRow(this.profile); - @override _ProfileRowState createState() => _ProfileRowState(); } @@ -19,35 +16,36 @@ class ProfileRow extends StatefulWidget { class _ProfileRowState extends State { @override Widget build(BuildContext context) { + var profile = Provider.of(context); return ListTile( leading: SizedBox( width: 60, height: 60, child: ClipOval( - child: SizedBox(width:60, height:60, child:Container(color:Colors.white, width: 60, height: 60, child: Image(image: AssetImage("assets/" + widget.profile.imagePath), width:50,height:50,))), + child: SizedBox(width:60, height:60, child:Container(color:Colors.white, width: 60, height: 60, child: Image(image: AssetImage("assets/" + profile.imagePath), width:50,height:50,))), ), ) , trailing: IconButton( icon: Icon(Icons.create, color: Provider.of(context).current().mainTextColor()), - onPressed: () { _pushAddEditProfile(onion: widget.profile.onion); }, + onPressed: () { _pushAddEditProfile(onion: profile.onion); }, ),//(nb: Icons.create is a pencil and we use it for "edit", not create) title: Text( - widget.profile.nickname, + profile.nickname, style: Provider.of(context).biggerFont, ), - subtitle: Text(widget.profile.onion), + subtitle: Text(profile.onion), onTap: () { setState(() { var flwtch = Provider.of(context, listen:false); - flwtch.cwtch.SelectProfile(widget.profile.onion); + flwtch.cwtch.SelectProfile(profile.onion); flwtch.setState(() { - flwtch.selectedProfile = widget.profile; + flwtch.selectedProfile = profile; flwtch.selectedConversation = ""; }); switch (flwtch.columns.length) { - case 1: _pushContactList(widget.profile, false); break; - case 2: _pushContactList(widget.profile, true); break; + case 1: _pushContactList(profile, false); break; + case 2: _pushContactList(profile, true); break; } // case 3: handled by TripleColumnView }); }, @@ -57,10 +55,13 @@ class _ProfileRowState extends State { void _pushContactList(ProfileInfoState profile, bool includeDoublePane) { Navigator.of(context).push( MaterialPageRoute( - builder: (BuildContext context) { - return Provider( - create: (_) => Provider.of(context), - child: includeDoublePane ? DoubleColumnView() : ContactsView(profile:profile), + builder: (BuildContext buildcontext) { + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: profile), + ChangeNotifierProvider(create: (_) => ContactListState(Provider.of(buildcontext).cwtch, profile.onion),), + ], + builder: (context, widget) => includeDoublePane ? DoubleColumnView() : ContactsView(), ); }, ), diff --git a/lib/widgets/torstatuslabel.dart b/lib/widgets/torstatuslabel.dart index daef03d..67927c7 100644 --- a/lib/widgets/torstatuslabel.dart +++ b/lib/widgets/torstatuslabel.dart @@ -1,10 +1,7 @@ -import 'dart:ffi'; - import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; - import '../main.dart'; -import '../model.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class TorStatusLabel extends StatefulWidget { @override @@ -22,7 +19,7 @@ class _TorStatusState extends State { builder: (BuildContext context, AsyncSnapshot snapshot) { return Text( snapshot.hasData ? - snapshot.data : "Tor not yet Connected", + snapshot.data : AppLocalizations.of(context).loadingTor, style: Theme .of(context) .textTheme diff --git a/pubspec.lock b/pubspec.lock index 1edac19..e96e324 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,20 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" async: dependency: transitive description: @@ -43,6 +57,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0-nullsafety.5" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" cupertino_icons: dependency: "direct main" description: @@ -70,24 +98,71 @@ packages: name: file url: "https://pub.dartlang.org" source: hosted - version: "5.2.1" + version: "6.0.0-nullsafety.4" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lokalise: + dependency: "direct dev" + description: + name: flutter_lokalise + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.1" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.2" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" intl: dependency: transitive description: name: intl url: "https://pub.dartlang.org" source: hosted - version: "0.16.1" + version: "0.17.0-nullsafety.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.4" matcher: dependency: transitive description: @@ -151,13 +226,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.4+3" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" platform: dependency: transitive description: name: platform url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.0.0" plugin_platform_interface: dependency: transitive description: @@ -171,7 +253,7 @@ packages: name: process url: "https://pub.dartlang.org" source: hosted - version: "3.0.13" + version: "4.0.0-nullsafety.4" provider: dependency: "direct main" description: @@ -179,6 +261,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.3.2+3" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" sky_engine: dependency: transitive description: flutter @@ -212,6 +301,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0-nullsafety.3" + string_unescape: + dependency: transitive + description: + name: string_unescape + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" term_glyph: dependency: transitive description: @@ -254,6 +350,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" sdks: dart: ">=2.12.0-0.0 <3.0.0" - flutter: ">=1.16.0 <2.0.0" + flutter: ">=1.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 61c15f8..e93ce47 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,9 @@ dependencies: flutter: sdk: flutter provider: "4.3.2+3" - + #intl_translation: any + flutter_localizations: + sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. @@ -35,12 +37,26 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + flutter_lokalise: any + +# alternatively: flutter pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/intl/app_localizations.dart lib/l10n/intl_*.arb --api-token X --project-id Y +#flutter_lokalise: +# project_id: "" +# api_token: "" +# include_tags: +# - tag1 +# - tag2 + +flutter_intl: + enabled: true # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: + # makes flutter build/run generate app_localizations.dart (per l10n.yaml) + generate: true # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in