diff --git a/.drone.yml b/.drone.yml index e0b7eee..2a873a0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -53,13 +53,18 @@ steps: - name: deps path: /root/.pub-cache commands: - - flutter build linux + - flutter build linux --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE` - mkdir deploy/linux - cp -r build/linux/x64/release/bundle/* deploy/linux - cp linux/cwtch.desktop deploy/linux - cp linux/cwtch.png deploy/linux - cp linux/libCwtch.so deploy/linux/lib/ - cp /sdks/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat deploy/linux + - cp tor deploy/linux + - cd deploy + - mv linux cwtch + - tar -czf cwtch-`cat ../VERSION`.tar.gz cwtch + - rm -r cwtch - name: build-android image: cirrusci/flutter:dev @@ -77,13 +82,12 @@ steps: - echo $upload_jks_file_b64 > upload-keystore.jks.b64 - base64 -i --decode upload-keystore.jks.b64 > android/app/upload-keystore.jks - sed -i "s/%jks-password%/$upload_jks_pass/g" android/key.properties - - flutter build appbundle + - flutter build appbundle --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE` # cant do debug for final release, this is just a stop gap - - flutter build apk + - flutter build apk --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE` # or build apk --split-per-abi ? - - mkdir deploy/android - - cp build/app/outputs/bundle/release/app-release.aab deploy/android - - cp build/app/outputs/apk/release/app-release.apk deploy/android + - cp build/app/outputs/bundle/release/app-release.aab deploy/ + - cp build/app/outputs/apk/release/app-release.apk deploy/ #- cp build/app/outputs/flutter-apk/app-debug.apk deploy/android - name: widget-tests @@ -179,7 +183,7 @@ clone: steps: - name: clone - image: openpriv/flutter-desktop:windows-sdk30-fdev2.2rc + image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc environment: buildbot_key_b64: from_secret: buildbot_key_b64 @@ -197,7 +201,7 @@ steps: - git checkout $DRONE_COMMIT - name: fetch - image: openpriv/flutter-desktop:windows-sdk30-fdev2.2rc + image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc commands: - powershell -command "Invoke-WebRequest -Uri https://www.torproject.org/dist/torbrowser/10.0.16/tor-win32-0.4.5.7.zip -OutFile tor.zip" - powershell -command "if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '2b7d683f036d0fec149f1d2bdfcf5b7ef4c337005a2b685c056b00047fdb2b57d4c25b8559ad7ef5c7a030b273934be82a9f83ef6e391f5d7d13d8d6c83e8048' ) { Write-Error 'tor.zip sha512sum mismatch' }" @@ -206,19 +210,23 @@ steps: - .\fetch-libcwtch-go.ps1 - - name: build-windows - image: openpriv/flutter-desktop:windows-beta + image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc commands: - flutter pub get - - flutter build windows # flwtch-`cat VERSION`-`cat BUILDDATE` - $Env:buildname = 'flwtch-win-' - - $Env:buildname += type .\VERSION + - $Env:version += type .\VERSION + - $Env:buildname += $Env:version - $Env:buildname += '-' - - $Env:buildname += type .\BUILDDATE + - $Env:builddate += type .\BUILDDATE + - $Env:buildname += $Env:builddate - $Env:builddir += $Env:buildname - - $Env:zip = 'flwtch.zip' + - $Env:zip = 'cwtch-' + - $Env:zip += $Env:version + - $Env:zip += '.zip' - $Env:sha = $Env:zip - $Env:sha += '.sha512' + - flutter build windows --dart-define BUILD_VER=$Env:version --dart-define BUILD_DATE=$Env:builddate - mkdir deploy - move build\\windows\\runner\\Release $Env:builddir - copy windows\libCwtch.dll $Env:builddir @@ -230,7 +238,7 @@ steps: - move $Env:sha deploy\$Env:builddir - name: deploy-windows - image: openpriv/flutter-desktop:windows-sdk30-fdev2.2rc + image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc when: event: push status: [ success ] diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version index efa5c08..ce58061 100644 --- a/LIBCWTCH-GO.version +++ b/LIBCWTCH-GO.version @@ -1 +1 @@ -v0.0.2-43-ga98b5de-2021-05-28-21-21 \ No newline at end of file +v0.0.2-45-g4f625c7-2021-05-31-23-30 \ No newline at end of file diff --git a/lib/config.dart b/lib/config.dart new file mode 100644 index 0000000..7d0c4b8 --- /dev/null +++ b/lib/config.dart @@ -0,0 +1,6 @@ +const dev_version = "development"; + +class EnvironmentConfig { + static const BUILD_VER = String.fromEnvironment('BUILD_VER', defaultValue: dev_version); + static const BUILD_DATE = String.fromEnvironment('BUILD_DATE', defaultValue: "now"); +} diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 62af116..89565b4 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:cwtch/models/servers.dart'; import 'package:cwtch/notification_manager.dart'; import 'package:provider/provider.dart'; @@ -40,18 +41,26 @@ class CwtchNotifier { imagePath: data["picture"], isBlocked: data["authorization"] == "blocked", isInvitation: data["authorization"] == "unknown", - savePeerHistory: data["saveConversationHistory"], + savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"], numMessages: int.parse(data["numMessages"]), numUnread: int.parse(data["unread"]), - isGroup: data["isGroup"], + isGroup: data["isGroup"] == true, server: data["groupServer"], lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet )); break; case "GroupCreated": + + // Retrieve Server Status from Cache... + String status = ""; + ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]); + if (serverInfoState != null) { + status = serverInfoState.status; + } + if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) == null) { profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["GroupID"], - isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now())); + isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], status: status, server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now())); profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now()); } break; @@ -175,9 +184,23 @@ class CwtchNotifier { String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5))); dynamic groupInvite = jsonDecode(inviteJson); print("new group invite: $groupInvite"); + + // Retrieve Server Status from Cache... + String status = ""; + ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(groupInvite["ServerHost"]); + if (serverInfoState != null) { + status = serverInfoState.status; + } + if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) { profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"], - isInvitation: true, imagePath: data["PicturePath"], nickname: groupInvite["GroupName"], server: groupInvite["ServerHost"], isGroup: true, lastMessageTime: DateTime.now())); + isInvitation: true, + imagePath: data["PicturePath"], + nickname: groupInvite["GroupName"], + server: groupInvite["ServerHost"], + status: status, + isGroup: true, + lastMessageTime: DateTime.now())); profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.now()); } } @@ -189,6 +212,8 @@ class CwtchNotifier { break; case "ServerStateChange": print("server state change: $data"); + // Update the Server Cache + profileCN.getProfile(data["ProfileOnion"])?.updateServerStatusCache(data["GroupServer"], data["ConnectionState"]); profileCN.getProfile(data["ProfileOnion"])?.contactList.contacts.forEach((contact) { if (contact.isGroup == true && contact.server == data["GroupServer"]) { contact.status = data["ConnectionState"]; @@ -205,6 +230,16 @@ class CwtchNotifier { print("unhandled set group attribute event: $type $data"); } break; + case "NewRetValMessageFromPeer": + if (data["Path"] == "name") { + // Update locally on the UI... + if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]) != null) { + profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.nickname = data["Data"]; + } + } else { + print("unhandled peer attribute event: $type $data"); + } + break; default: print("unhandled event: $type $data"); } diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index e61c093..89af1e5 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -9,6 +9,8 @@ import 'package:path/path.dart' as path; import 'package:ffi/ffi.dart'; import 'package:cwtch/cwtch/cwtch.dart'; +import '../config.dart'; + ///////////////////// /// Cwtch API /// ///////////////////// @@ -84,7 +86,10 @@ class CwtchFfi implements Cwtch { home = (envVars['UserProfile'])!; bundledTor = "Tor\\Tor\\tor.exe"; } - var cwtchDir = path.join(home, ".cwtch/dev/"); + var cwtchDir = path.join(home, ".cwtch"); + if (EnvironmentConfig.BUILD_VER == dev_version) { + cwtchDir = path.join(cwtchDir, "dev"); + } print("cwtchDir $cwtchDir"); var startCwtchC = library.lookup>("c_StartCwtch"); diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index 97a0684..210eb42 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; +import 'package:cwtch/config.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:async'; @@ -44,7 +45,10 @@ class CwtchGomobile implements Cwtch { // ignore: non_constant_identifier_names Future Start() async { print("gomobile.dart: Start()..."); - var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch/dev/"); + var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch"); + if (EnvironmentConfig.BUILD_VER == dev_version) { + cwtchDir = path.join(cwtchDir, "dev"); + } String torPath = path.join(await androidLibraryDir, "libtor.so"); print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)..."); cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath}); diff --git a/lib/errorHandler.dart b/lib/errorHandler.dart index ee474e3..cf26cde 100644 --- a/lib/errorHandler.dart +++ b/lib/errorHandler.dart @@ -29,6 +29,7 @@ class ErrorHandler extends ChangeNotifier { break; case importBundleErrorPrefix: handleImportBundleError(errorType); + break; } notifyListeners(); @@ -49,6 +50,7 @@ class ErrorHandler extends ChangeNotifier { break; case successErrorType: explicitAddContactSuccess = true; + importBundleSuccess = true; break; } } diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index f8cdf6c..3d1a360 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -75,10 +75,12 @@ "invitation": "Einladung", "invitationLabel": "Einladung", "inviteBtn": "Einladen", + "inviteToGroup": "", "inviteToGroupLabel": "In die Gruppe einladen", "joinGroup": "Gruppe beitreten", "joinGroupTab": "Einer Gruppe beitreten", "largeTextLabel": "Groß", + "leaveGroup": "", "listsBtn": "Listen", "loadingTor": "Tor wird geladen...", "localeDe": "Deutsche", @@ -95,6 +97,7 @@ "newBulletinLabel": "Neue Meldung", "newConnectionPaneTitle": "Neue Verbindung", "newGroupBtn": "Neue Gruppe anlegen", + "newPassword": "", "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", @@ -116,6 +119,7 @@ "puzzleGameBtn": "Puzzlespiel", "radioNoPassword": "Unverschlüsselt (kein Passwort)", "radioUsePassword": "Passwort", + "reallyLeaveThisGroupPrompt": "", "rejectGroupBtn": "Ablehnen", "saveBtn": "Speichern", "savePeerHistory": "Peer-Verlauf speichern", @@ -153,6 +157,7 @@ "versionTor": "Version %1 mit tor %2", "viewGroupMembershipTooltip": "Gruppenmitgliedschaft anzeigen", "viewServerInfo": "", + "yesLeave": "", "yourDisplayName": "Ihr Anzeigename", "yourProfiles": "Ihre Profile", "yourServers": "Ihre Server", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 78fecfd..8ce8f99 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -75,10 +75,12 @@ "invitation": "Invitation", "invitationLabel": "Invitation", "inviteBtn": "Invite", + "inviteToGroup": "You have been invited to join a group:", "inviteToGroupLabel": "Invite to group", "joinGroup": "Join group", "joinGroupTab": "Join a group", "largeTextLabel": "Large", + "leaveGroup": "Leave This Conversation", "listsBtn": "Lists", "loadingTor": "Loading tor...", "localeDe": "Deutsche", @@ -95,6 +97,7 @@ "newBulletinLabel": "New Bulletin", "newConnectionPaneTitle": "New Connection", "newGroupBtn": "Create new group", + "newPassword": "New Password", "newProfile": "New Profile", "noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted", "password": "Password", @@ -103,7 +106,7 @@ "passwordChangeError": "Error changing password: Supplied password rejected", "passwordErrorEmpty": "Password cannot be empty", "passwordErrorMatch": "Passwords do not match", - "pasteAddressToAddContact": "Paste a cwtch address here to add a new contact.", + "pasteAddressToAddContact": "Paste a cwtch address, invitation or key bundle here to add a new conversation", "peerAddress": "Address", "peerBlockedMessage": "Peer is blocked", "peerName": "Name", @@ -116,6 +119,7 @@ "puzzleGameBtn": "Puzzle Game", "radioNoPassword": "Unencrypted (No password)", "radioUsePassword": "Password", + "reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.", "rejectGroupBtn": "Reject", "saveBtn": "Save", "savePeerHistory": "Save Peer History", @@ -137,12 +141,12 @@ "successfullAddedContact": "Successfully added ", "themeDark": "Dark", "themeLight": "Light", - "titleManageContacts": "Manage Contacts", + "titleManageContacts": "Conversations", "titleManageProfiles": "Manage Cwtch Profiles", "titleManageServers": "Manage Servers", "titlePlaceholder": "title...", "todoPlaceholder": "Todo...", - "tooltipAddContact": "Add a new contact", + "tooltipAddContact": "Add a new contact or conversation", "tooltipOpenSettings": "Open the settings pane", "tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.", "unblockBtn": "Unblock Peer", @@ -153,6 +157,7 @@ "versionTor": "Version %1 with tor %2", "viewGroupMembershipTooltip": "View Group Membership", "viewServerInfo": "Server Info", + "yesLeave": "Yes, Leave This Conversation", "yourDisplayName": "Your Display Name", "yourProfiles": "Your Profiles", "yourServers": "Your Servers", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 0234709..02694e9 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -75,10 +75,12 @@ "invitation": "Invitación", "invitationLabel": "Invitación", "inviteBtn": "Invitar", + "inviteToGroup": "", "inviteToGroupLabel": "Invitar al grupo", "joinGroup": "Únete al grupo", "joinGroupTab": "Únete a un grupo", "largeTextLabel": "Grande", + "leaveGroup": "", "listsBtn": "Listas", "loadingTor": "Cargando tor...", "localeDe": "Alemán", @@ -95,6 +97,7 @@ "newBulletinLabel": "Nuevo Boletín", "newConnectionPaneTitle": "Nueva conexión", "newGroupBtn": "Crear un nuevo grupo de chat", + "newPassword": "", "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", @@ -116,6 +119,7 @@ "puzzleGameBtn": "Juego de rompecabezas", "radioNoPassword": "Sin cifrado (sin contraseña)", "radioUsePassword": "Contraseña", + "reallyLeaveThisGroupPrompt": "", "rejectGroupBtn": "Rechazar", "saveBtn": "Guardar", "savePeerHistory": "Guardar el historial con contacto", @@ -153,6 +157,7 @@ "versionTor": "Versión %1 con tor %2", "viewGroupMembershipTooltip": "Ver membresía del grupo", "viewServerInfo": "Información del servidor", + "yesLeave": "", "yourDisplayName": "Tu nombre de usuario", "yourProfiles": "Tus perfiles", "yourServers": "Tus servidores", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 2c5fa91..59bee35 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -75,10 +75,12 @@ "invitation": "", "invitationLabel": "Invitation", "inviteBtn": "Invitation", + "inviteToGroup": "", "inviteToGroupLabel": "Inviter quelqu'un", "joinGroup": "", "joinGroupTab": "", "largeTextLabel": "Large", + "leaveGroup": "", "listsBtn": "Listes", "loadingTor": "", "localeDe": "", @@ -95,6 +97,7 @@ "newBulletinLabel": "Nouveau bulletin", "newConnectionPaneTitle": "", "newGroupBtn": "Créer un nouveau groupe", + "newPassword": "", "newProfile": "", "noPasswordWarning": "", "password": "", @@ -116,6 +119,7 @@ "puzzleGameBtn": "Puzzle", "radioNoPassword": "", "radioUsePassword": "", + "reallyLeaveThisGroupPrompt": "", "rejectGroupBtn": "Refuser", "saveBtn": "Sauvegarder", "savePeerHistory": "", @@ -153,6 +157,7 @@ "versionTor": "", "viewGroupMembershipTooltip": "", "viewServerInfo": "", + "yesLeave": "", "yourDisplayName": "", "yourProfiles": "", "yourServers": "", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index e306073..fd1d432 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -75,10 +75,12 @@ "invitation": "Invito", "invitationLabel": "Invito", "inviteBtn": "Invitare", + "inviteToGroup": "", "inviteToGroupLabel": "Invitare nel gruppo", "joinGroup": "Unisciti al gruppo", "joinGroupTab": "Unisciti a un gruppo", "largeTextLabel": "Grande", + "leaveGroup": "", "listsBtn": "Liste", "loadingTor": "Caricamento di tor...", "localeDe": "Tedesco", @@ -95,6 +97,7 @@ "newBulletinLabel": "Nuovo bollettino", "newConnectionPaneTitle": "Nuova connessione", "newGroupBtn": "Crea un nuovo gruppo", + "newPassword": "", "newProfile": "Nuovo profilo", "noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati", "password": "Password", @@ -116,6 +119,7 @@ "puzzleGameBtn": "Gioco di puzzle", "radioNoPassword": "Non criptato (senza password)", "radioUsePassword": "Password", + "reallyLeaveThisGroupPrompt": "", "rejectGroupBtn": "Rifiuta", "saveBtn": "Salva", "savePeerHistory": "Salva cronologia peer", @@ -153,6 +157,7 @@ "versionTor": "Versione %1 con tor %2", "viewGroupMembershipTooltip": "Visualizza i membri del gruppo", "viewServerInfo": "Informazioni sul server", + "yesLeave": "", "yourDisplayName": "Il tuo nome visualizzato", "yourProfiles": "I tuoi profili", "yourServers": "I tuoi server", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index c5ec9f8..ef4ab54 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -75,10 +75,12 @@ "invitation": "", "invitationLabel": "Convite", "inviteBtn": "Convidar", + "inviteToGroup": "", "inviteToGroupLabel": "Convidar ao grupo", "joinGroup": "", "joinGroupTab": "", "largeTextLabel": "Grande", + "leaveGroup": "", "listsBtn": "Listas", "loadingTor": "", "localeDe": "", @@ -95,6 +97,7 @@ "newBulletinLabel": "Novo Boletim", "newConnectionPaneTitle": "", "newGroupBtn": "Criar novo grupo", + "newPassword": "", "newProfile": "", "noPasswordWarning": "", "password": "", @@ -116,6 +119,7 @@ "puzzleGameBtn": "Jogo de Adivinhação", "radioNoPassword": "", "radioUsePassword": "", + "reallyLeaveThisGroupPrompt": "", "rejectGroupBtn": "Recusar", "saveBtn": "Salvar", "savePeerHistory": "", @@ -153,6 +157,7 @@ "versionTor": "", "viewGroupMembershipTooltip": "", "viewServerInfo": "", + "yesLeave": "", "yourDisplayName": "", "yourProfiles": "", "yourServers": "", diff --git a/lib/main.dart b/lib/main.dart index 13731a7..bfde1c1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:cwtch/notification_manager.dart'; +import 'package:cwtch/widgets/rightshiftfixer.dart'; import 'package:flutter/foundation.dart'; import 'package:cwtch/cwtch/ffi.dart'; import 'package:cwtch/cwtch/gomobile.dart'; @@ -42,7 +43,6 @@ class FlwtchState extends State { String selectedConversation = ""; var columns = [1]; // default or 'single column' mode //var columns = [1, 1, 2]; - late AppModel appStatus; late ProfileListState profs; @override @@ -68,8 +68,6 @@ class FlwtchState extends State { cwtchInit = true; }); }); - - appStatus = AppModel(cwtch: cwtch); } ChangeNotifierProvider getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus); @@ -101,7 +99,7 @@ class FlwtchState extends State { theme: mkThemeData(settings), // from dan: home: cwtchInit == true ? ProfileMgrView(cwtch) : SplashView(), // from erinn: home: columns.length == 3 ? TripleColumnView() : ProfileMgrView(), - home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ProfileMgrView()) : SplashView(), + home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ShiftRightFixer(child: ProfileMgrView())) : SplashView(), ), ); }, diff --git a/lib/model.dart b/lib/model.dart index 977639d..f5f80d0 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -190,9 +190,16 @@ class ProfileInfoState extends ChangeNotifier { // TODO Keys... return ServerInfoState(onion: server["onion"], status: server["status"]); })); + notifyListeners(); } } + // + void updateServerStatusCache(String server, String status) { + this._servers.updateServerCache(server, status); + notifyListeners(); + } + // Getters and Setters for Online Status bool get isOnline => this._online; set isOnline(bool newValue) { @@ -253,7 +260,7 @@ class ContactInfoState extends ChangeNotifier { // todo: a nicer way to model contacts, groups and other "entities" late bool _isGroup; - late String _server; + String? _server; ContactInfoState( this.profileOnion, @@ -268,7 +275,7 @@ class ContactInfoState extends ChangeNotifier { numMessages = 0, numUnread = 0, lastMessageTime, - server = "", + server, }) { this._nickname = nickname; this._isGroup = isGroup; @@ -470,37 +477,3 @@ class MessageState extends ChangeNotifier { }); } } - -///////////// -/// ACN /// -///////////// - -class AppModel { - final Cwtch cwtch; - AppModel({required this.cwtch}); - - Stream contactEvents() async* { - while (true) { - String event = await cwtch.ContactEvents(); - if (event != "") { - print(event); - yield event; - } else { - print("TEST TEST FAIL TEST FAIL 123"); - await Future.delayed(Duration(seconds: 1)); - } - } - } - - Stream torStatus() async* { - while (true) { - String event = await cwtch.ACNEvents(); - if (event != "") { - yield event; - } else { - print("TOR TEST TEST FAIL TEST FAIL 123"); - await Future.delayed(Duration(seconds: 1)); - } - } - } -} diff --git a/lib/models/servers.dart b/lib/models/servers.dart index 3bd62b7..6996aed 100644 --- a/lib/models/servers.dart +++ b/lib/models/servers.dart @@ -14,6 +14,16 @@ class ServerListState extends ChangeNotifier { return idx >= 0 ? _servers[idx] : null; } + void updateServerCache(String onion, String status) { + int idx = _servers.indexWhere((element) => element.onion == onion); + if (idx >= 0) { + _servers[idx] = ServerInfoState(onion: onion, status: status); + } else { + print("Tried to update server cache without a starting state...this is probably an error"); + } + notifyListeners(); + } + List get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier } diff --git a/lib/views/addcontactview.dart b/lib/views/addcontactview.dart index fa30972..2ab248c 100644 --- a/lib/views/addcontactview.dart +++ b/lib/views/addcontactview.dart @@ -34,7 +34,7 @@ class _AddContactViewState extends State { @override Widget build(BuildContext context) { // if we haven't picked a server yet, pick the first one in the list... - if (server.isEmpty) { + if (server.isEmpty && Provider.of(context).serverList.servers.isNotEmpty) { server = Provider.of(context).serverList.servers.first.onion; } diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart index 647dfff..1c5c077 100644 --- a/lib/views/contactsview.dart +++ b/lib/views/contactsview.dart @@ -47,7 +47,7 @@ class _ContactsViewState extends State { width: 10, ), Expanded( - child: Text("%1 » %2".replaceAll("%1", Provider.of(context).nickname).replaceAll("%2", "Contacts"), + child: Text("%1 » %2".replaceAll("%1", Provider.of(context).nickname).replaceAll("%2", AppLocalizations.of(context)!.titleManageContacts), overflow: TextOverflow.ellipsis, style: TextStyle(color: Provider.of(context).current().mainTextColor()))), //todo ])), actions: [ diff --git a/lib/views/doublecolview.dart b/lib/views/doublecolview.dart index fba9d8a..5abaa1b 100644 --- a/lib/views/doublecolview.dart +++ b/lib/views/doublecolview.dart @@ -31,7 +31,7 @@ class _DoubleColumnViewState extends State { : //dev MultiProvider(providers: [ ChangeNotifierProvider.value(value: Provider.of(context)), - ChangeNotifierProvider.value(value: Provider.of(context).contactList.getContact(flwtch.selectedConversation)), + ChangeNotifierProvider.value(value: Provider.of(context).contactList.getContact(flwtch.selectedConversation)!), ], child: Container(child: MessageView())), ), ], diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 6648d79..143bc90 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -7,6 +7,7 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../main.dart'; +import '../config.dart'; /// Global Settings View provides access to modify all the Globally Relevant Settings including Locale, Theme and Experiments. class GlobalSettingsView extends StatefulWidget { @@ -156,7 +157,7 @@ class _GlobalSettingsViewState extends State { height: 128, )), applicationName: "Cwtch (Flutter UI)", - applicationVersion: AppLocalizations.of(context)!.version.replaceAll("%1", constructVersionString(Provider.of(context).packageInfo)), + applicationVersion: AppLocalizations.of(context)!.versionBuilddate.replaceAll("%1", EnvironmentConfig.BUILD_VER).replaceAll("%2", EnvironmentConfig.BUILD_DATE), applicationLegalese: '\u{a9} 2021 Open Privacy Research Society', ), ])))); diff --git a/lib/views/groupsettingsview.dart b/lib/views/groupsettingsview.dart index 1ed6ff7..c4efe5d 100644 --- a/lib/views/groupsettingsview.dart +++ b/lib/views/groupsettingsview.dart @@ -141,7 +141,7 @@ class _GroupSettingsViewState extends State { showAlertDialog(context); }, icon: Icon(Icons.delete), - label: Text(AppLocalizations.of(context)!.deleteBtn), + label: Text(AppLocalizations.of(context)!.leaveGroup), )) ]) ]))))); @@ -166,7 +166,7 @@ class _GroupSettingsViewState extends State { ); Widget continueButton = TextButton( style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), - child: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn), + child: Text(AppLocalizations.of(context)!.yesLeave), onPressed: () { var profileOnion = Provider.of(context, listen: false).profileOnion; var handle = Provider.of(context, listen: false).onion; @@ -179,7 +179,7 @@ class _GroupSettingsViewState extends State { // set up the AlertDialog AlertDialog alert = AlertDialog( - title: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn), + title: Text(AppLocalizations.of(context)!.reallyLeaveThisGroupPrompt), actions: [ cancelButton, continueButton, diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index acf8a34..4727262 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -93,7 +93,8 @@ class _ContactRowState extends State { MaterialPageRoute( builder: (BuildContext builderContext) { // assert we have an actual profile... - var profile = Provider.of(builderContext, listen: false).profs.getProfile(profileOnion)!; + // We need to listen for updates to the profile in order to update things like invitation message bubbles. + var profile = Provider.of(builderContext).profs.getProfile(profileOnion)!; return MultiProvider( providers: [ ChangeNotifierProvider.value(value: profile), diff --git a/lib/widgets/invitationbubble.dart b/lib/widgets/invitationbubble.dart index 24b9d7c..cca741b 100644 --- a/lib/widgets/invitationbubble.dart +++ b/lib/widgets/invitationbubble.dart @@ -5,6 +5,7 @@ import 'package:provider/provider.dart'; import '../main.dart'; import '../model.dart'; import 'package:intl/intl.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../settings.dart'; @@ -34,11 +35,17 @@ class InvitationBubbleState extends State { prettyDate = DateFormat.yMd().add_jm().format(Provider.of(context).timestamp); } + // If the sender is not us, then we want to give them a nickname... var senderDisplayStr = ""; - if (Provider.of(context).senderOnion != null) { - var contact = Provider.of(context).contactList.getContact(Provider.of(context).senderOnion); - senderDisplayStr = contact!.nickname; + if (!fromMe && Provider.of(context).senderOnion != null) { + ContactInfoState? contact = Provider.of(context).contactList.getContact(Provider.of(context).senderOnion); + if (contact != null) { + senderDisplayStr = contact.nickname; + } else { + senderDisplayStr = Provider.of(context).senderOnion; + } } + var wdgSender = Center( widthFactor: 1, child: SelectableText(senderDisplayStr + '\u202F', @@ -50,9 +57,8 @@ class InvitationBubbleState extends State { //todo: get group name? messageStr = "You sent an invitation for " + (isGroup ? "a group" : Provider.of(context).message ?? ""); } else { - messageStr = (isGroup ? "You have been invited to join " + (Provider.of(context).inviteNick ?? "") : "This is a contact suggestion for:") + - "\n" + - (Provider.of(context).inviteTarget ?? ""); + String joinGroup = AppLocalizations.of(context)!.inviteToGroup; + messageStr = (isGroup ? joinGroup + (Provider.of(context).inviteNick ?? "") : "This is a contact suggestion for:") + "\n" + (Provider.of(context).inviteTarget ?? ""); } var wdgMessage = Center( widthFactor: 1, @@ -79,9 +85,13 @@ class InvitationBubbleState extends State { textAlign: fromMe ? TextAlign.right : TextAlign.left), !fromMe ? SizedBox(width: 1, height: 1) - : Provider.of(context).ackd - ? Icon(Icons.check_circle_outline, color: Provider.of(context).theme.messageFromMeTextColor(), size: 12) - : Icon(Icons.hourglass_bottom_outlined, color: Provider.of(context).theme.messageFromMeTextColor(), size: 12) + : Padding( + padding: EdgeInsets.all(1.0), + child: Provider.of(context).ackd == true + ? Icon(Icons.check_circle_outline, color: Provider.of(context).theme.messageFromMeTextColor(), size: 16) + : (Provider.of(context).error == true + ? Icon(Icons.error_outline, color: Provider.of(context).theme.messageFromMeTextColor(), size: 16) + : Icon(Icons.hourglass_bottom_outlined, color: Provider.of(context).theme.messageFromMeTextColor(), size: 16))) ], )); } else if (isAccepted) { diff --git a/lib/widgets/messagebubble.dart b/lib/widgets/messagebubble.dart index 84415f3..08b1224 100644 --- a/lib/widgets/messagebubble.dart +++ b/lib/widgets/messagebubble.dart @@ -1,3 +1,4 @@ +import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../model.dart'; @@ -63,23 +64,32 @@ class MessageBubbleState extends State { textAlign: fromMe ? TextAlign.right : TextAlign.left), !fromMe ? SizedBox(width: 1, height: 1) - : Provider.of(context).ackd == true - ? Icon(Icons.check_circle_outline, color: Provider.of(context).theme.messageFromMeTextColor(), size: 12) - : (Provider.of(context).error == true - ? Icon(Icons.error_outline, color: Provider.of(context).theme.messageFromMeTextColor(), size: 12) - : Icon(Icons.hourglass_bottom_outlined, color: Provider.of(context).theme.messageFromMeTextColor(), size: 12)) + : Padding( + padding: EdgeInsets.all(1.0), + child: Provider.of(context).ackd == true + ? Icon(Icons.check_circle_outline, color: Provider.of(context).theme.messageFromMeTextColor(), size: 16) + : (Provider.of(context).error == true + ? Icon(Icons.error_outline, color: Provider.of(context).theme.messageFromMeTextColor(), size: 16) + : Icon(Icons.hourglass_bottom_outlined, color: Provider.of(context).theme.messageFromMeTextColor(), size: 16))) ], )); + var error = Provider.of(context).error; + return LayoutBuilder(builder: (context, constraints) { //print(constraints.toString()+", "+constraints.maxWidth.toString()); return RepaintBoundary( child: Container( child: Container( decoration: BoxDecoration( - color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor() : Provider.of(context).theme.messageFromOtherBackgroundColor(), + color: error + ? malformedColor + : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor() : Provider.of(context).theme.messageFromOtherBackgroundColor()), border: Border.all( - color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor() : Provider.of(context).theme.messageFromOtherBackgroundColor(), width: 1), + color: error + ? malformedColor + : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor() : Provider.of(context).theme.messageFromOtherBackgroundColor()), + width: 1), borderRadius: BorderRadius.only( topLeft: Radius.circular(borderRadiousEh), topRight: Radius.circular(borderRadiousEh), diff --git a/lib/widgets/messagelist.dart b/lib/widgets/messagelist.dart index fc5b728..dd89b23 100644 --- a/lib/widgets/messagelist.dart +++ b/lib/widgets/messagelist.dart @@ -40,6 +40,7 @@ class _MessageListState extends State { create: (x) => MessageState( context: itemBuilderContext, profileOnion: Provider.of(outerContext, listen: false).onion, + // We don't want to listen for updates to the contact handle... contactHandle: Provider.of(x, listen: false).onion, messageIndex: trueIndex, ), diff --git a/lib/widgets/passwordfield.dart b/lib/widgets/passwordfield.dart index d7931ef..7b8804d 100644 --- a/lib/widgets/passwordfield.dart +++ b/lib/widgets/passwordfield.dart @@ -39,7 +39,9 @@ class _CwtchTextFieldState extends State { decoration: InputDecoration( suffixIcon: IconButton( onPressed: () { - obscureText = !obscureText; + setState(() { + obscureText = !obscureText; + }); }, icon: Icon((obscureText ? Icons.remove_red_eye : Icons.remove_red_eye_outlined), semanticLabel: label), tooltip: label, diff --git a/lib/widgets/rightshiftfixer.dart b/lib/widgets/rightshiftfixer.dart new file mode 100644 index 0000000..cf4c6da --- /dev/null +++ b/lib/widgets/rightshiftfixer.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +// From https://github.com/flutter/flutter/issues/75675#issuecomment-846601115 +// necessary to fix bug in flutter engine on Windows. +// todo: hopefully we can remove this soon +class ShiftRightFixer extends StatefulWidget { + ShiftRightFixer({required this.child}); + final Widget child; + @override + State createState() => _ShiftRightFixerState(); +} + +class _ShiftRightFixerState extends State { + final FocusNode focus = FocusNode(skipTraversal: true, canRequestFocus: false); + @override + Widget build(BuildContext context) { + return Focus( + focusNode: focus, + onKey: (_, RawKeyEvent event) { + return event.physicalKey == PhysicalKeyboardKey.shiftRight ? KeyEventResult.handled : KeyEventResult.ignored; + }, + child: widget.child, + ); + } +} diff --git a/lib/widgets/torstatuslabel.dart b/lib/widgets/torstatuslabel.dart deleted file mode 100644 index 92c14fb..0000000 --- a/lib/widgets/torstatuslabel.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../main.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -class TorStatusLabel extends StatefulWidget { - @override - _TorStatusState createState() => _TorStatusState(); -} - -class _TorStatusState extends State { - String status = ""; - - @override - Widget build(BuildContext context) { - return Builder( - builder: (context2) => StreamBuilder( - stream: Provider.of(context).appStatus.torStatus(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - return Text( - snapshot.hasData ? snapshot.data! : AppLocalizations.of(context)!.loadingTor, - style: Theme.of(context).textTheme.headline4, - ); - }, - )); - } -} diff --git a/pubspec.lock b/pubspec.lock index 58bca12..d43c28e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -14,7 +14,7 @@ packages: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" async: dependency: transitive description: @@ -77,7 +77,7 @@ packages: name: dbus url: "https://pub.dartlang.org" source: hosted - version: "0.5.0" + version: "0.5.1" desktop_notifications: dependency: "direct main" description: @@ -142,7 +142,7 @@ packages: name: glob url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "1.2.0" http: dependency: transitive description: @@ -199,6 +199,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + node_interop: + dependency: transitive + description: + name: node_interop + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" package_info_plus: dependency: "direct main" description: