diff --git a/.drone.yml b/.drone.yml index d1f9aeac..38c902ce 100644 --- a/.drone.yml +++ b/.drone.yml @@ -11,7 +11,7 @@ steps: image: cirrusci/flutter:dev environment: buildbot_key_b64: - from_secret: buildbot_key_b64 + from_secret: buildbot_key_b64 commands: - mkdir ~/.ssh - echo $buildbot_key_b64 > ~/.ssh/id_rsa.b64 @@ -68,8 +68,8 @@ steps: - name: test-build-android image: cirrusci/flutter:dev - when: - event: pull_request + when: + event: pull_request volumes: - name: deps path: /root/.pub-cache @@ -106,7 +106,7 @@ steps: - name: deps path: /root/.pub-cache commands: - # - flutter config --enable-linux-desktop + # - flutter config --enable-linux-desktop - flutter test --coverage - genhtml coverage/lcov.info -o coverage/html @@ -209,21 +209,10 @@ steps: - name: build-windows image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc - environment: - pfx: - from_secret: pfx - pfx_pass: - from_secret: pfx_pass commands: - flutter pub get - $Env:version += type .\VERSION - $Env:builddate += type .\BUILDDATE - - $Env:buildname = 'flwtch-win-' + $Env:version + '-' + $Env:builddate - - $Env:builddir = $Env:buildname - - $Env:zip = 'cwtch-' + $Env:version + '.zip' - - $Env:zipsha = $Env:zip + '.sha512' - - $Env:msix = 'cwtch-install-' + $Env:version + '.msix' - - $Env:msixsha = $Env:msix + '.sha512' - $Env:releasedir = "build\\windows\\runner\\Release\\" - flutter build windows --dart-define BUILD_VER=$Env:version --dart-define BUILD_DATE=$Env:builddate - copy windows\libCwtch.dll $Env:releasedir @@ -234,22 +223,46 @@ steps: - copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\vcruntime140_1.dll $Env:releasedir - copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\msvcp140.dll $Env:releasedir - powershell -command "Expand-Archive -Path tor.zip -DestinationPath $Env:releasedir\Tor" + + - name: package-windows + image: openpriv/nsis:latest + environment: + pfx: + from_secret: pfx + pfx_pass: + from_secret: pfx_pass + commands: + - $Env:version += type .\VERSION + - $Env:builddate += type .\BUILDDATE + - $Env:releasedir = "build\\windows\\runner\\Release\\" + - $Env:zip = 'cwtch-' + $Env:version + '.zip' + - $Env:zipsha = $Env:zip + '.sha512' + - $Env:msix = 'cwtch-install-' + $Env:version + '.msix' + - $Env:msixsha = $Env:msix + '.sha512' + - $Env:buildname = 'flwtch-win-' + $Env:version + '-' + $Env:builddate + - $Env:builddir = $Env:buildname - echo $Env:pfx > codesign.pfx.b64 - certutil -decode codesign.pfx.b64 codesign.pfx - - C:\MSIX-Toolkit\MSIX-Toolkit.x64\signtool sign /v /fd sha256 /a /f codesign.pfx /p $Env:pfx_pass /tr http://timestamp.digicert.com $Env:releasedir\cwtch.exe + - signtool sign /v /fd sha256 /a /f codesign.pfx /p $Env:pfx_pass /tr http://timestamp.digicert.com $Env:releasedir\cwtch.exe + - copy windows\runner\resources\knot_128.ico $Env:releasedir\cwtch.ico + - makensis windows\nsis\cwtch-installer.nsi + - move windows\nsis\cwtch-installer.exe cwtch-installer.exe + - signtool sign /v /fd sha256 /a /f codesign.pfx /p $Env:pfx_pass /tr http://timestamp.digicert.com cwtch-installer.exe + - powershell -command "(Get-FileHash cwtch-installer.exe -Algorithm sha512).Hash" > cwtch-installer.sha512 - mkdir deploy - mkdir deploy\$Env:builddir - move $Env:releasedir $Env:builddir - powershell -command "Compress-Archive -Path $Env:builddir -DestinationPath cwtch.zip" - powershell -command "(Get-FileHash cwtch.zip -Algorithm sha512).Hash" > $Env:zipsha + - move cwtch-installer.exe deploy\$Env:builddir\cwtch-installer.exe - move cwtch.zip deploy\$Env:builddir\$Env:zip - - move $Env:zipsha deploy\$Env:builddir + - move *.sha512 deploy\$Env:builddir - name: deploy-windows image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc when: - event: push - status: [ success ] + event: push + status: [ success ] environment: BUILDFILES_KEY: from_secret: buildfiles_key diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version index 271b7350..a3ef35ec 100644 --- a/LIBCWTCH-GO.version +++ b/LIBCWTCH-GO.version @@ -1 +1 @@ -v1.0.0-7-g520d35a-2021-06-25-16-34 \ No newline at end of file +v1.0.0-20-gf8eedca-2021-06-30-20-48 \ No newline at end of file diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt index 8fcf423b..fddf4372 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt @@ -178,7 +178,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : "CreateGroup" -> { val profile = (a.get("ProfileOnion") as? String) ?: "" val server = (a.get("server") as? String) ?: "" - val groupName = (a.get("groupname") as? String) ?: "" + val groupName = (a.get("groupName") as? String) ?: "" Cwtch.createGroup(profile, server, groupName) } "DeleteProfile" -> { diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index d5597674..7551526a 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -245,6 +245,7 @@ class CwtchNotifier { break; case "ServerStateChange": // Update the Server Cache + EnvironmentConfig.debugLog("server state changes $data"); 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"]) { diff --git a/lib/licenses.dart b/lib/licenses.dart index 36a9f577..a665eed0 100644 --- a/lib/licenses.dart +++ b/lib/licenses.dart @@ -115,6 +115,5 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''); - yield LicenseEntryWithLineBreaks(["flaticons"], "Icons made by Freepik (https://www.freepik.com) from Flaticon (www.flaticon.com)"); } diff --git a/lib/main.dart b/lib/main.dart index d066e39b..204c8695 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -190,7 +190,8 @@ class FlwtchState extends State { }, ), ); - } else { //dual pane + } else { + //dual pane Provider.of(navKey.currentContext!, listen: false).selectedProfile = args["ProfileOnion"]; Provider.of(navKey.currentContext!, listen: false).selectedConversation = args["Handle"]; } diff --git a/lib/notification_manager.dart b/lib/notification_manager.dart index ea4e9a78..fa5de8c3 100644 --- a/lib/notification_manager.dart +++ b/lib/notification_manager.dart @@ -22,10 +22,7 @@ class LinuxNotificationsManager implements NotificationsManager { } Future notify(String message) async { var iconPath = Uri.file(path.join(path.current, "cwtch.png")); - client.notify(message, appName: "cwtch", - appIcon: iconPath.toString(), - replacesId: this.previous_id).then((Notification value) => - previous_id = value.id); + client.notify(message, appName: "cwtch", appIcon: iconPath.toString(), replacesId: this.previous_id).then((Notification value) => previous_id = value.id); } } @@ -40,4 +37,4 @@ NotificationsManager newDesktopNotificationsManager() { print("Attempted to access DBUS for notifications but failed. Switching off notifications."); } return NullNotificationsManager(); -} \ No newline at end of file +} diff --git a/lib/opaque.dart b/lib/opaque.dart index b6ba50bf..fa09fc41 100644 --- a/lib/opaque.dart +++ b/lib/opaque.dart @@ -314,7 +314,6 @@ abstract class OpaqueThemeType { double contactOnionTextSize() { return 18; } - } class OpaqueDark extends OpaqueThemeType { @@ -1440,4 +1439,4 @@ class Opaque extends OpaqueThemeType { } } -*/ \ No newline at end of file +*/ diff --git a/lib/settings.dart b/lib/settings.dart index d63333dc..843076ba 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -161,26 +161,40 @@ class Settings extends ChangeNotifier { List uiColumns(bool isLandscape) { var m = (!isLandscape || uiColumnModeLandscape == DualpaneMode.CopyPortrait) ? uiColumnModePortrait : uiColumnModeLandscape; - switch(m) { - case DualpaneMode.Single: return [1]; - case DualpaneMode.Dual1to2: return [1, 2]; - case DualpaneMode.Dual1to4: return [1, 4]; + switch (m) { + case DualpaneMode.Single: + return [1]; + case DualpaneMode.Dual1to2: + return [1, 2]; + case DualpaneMode.Dual1to4: + return [1, 4]; } print("impossible column configuration: portrait/$uiColumnModePortrait landscape/$uiColumnModeLandscape"); return [1]; } static List uiColumnModeOptions(bool isLandscape) { - if (isLandscape) return [DualpaneMode.CopyPortrait, DualpaneMode.Single, DualpaneMode.Dual1to2, DualpaneMode.Dual1to4,]; - else return [DualpaneMode.Single, DualpaneMode.Dual1to2, DualpaneMode.Dual1to4]; + if (isLandscape) + return [ + DualpaneMode.CopyPortrait, + DualpaneMode.Single, + DualpaneMode.Dual1to2, + DualpaneMode.Dual1to4, + ]; + else + return [DualpaneMode.Single, DualpaneMode.Dual1to2, DualpaneMode.Dual1to4]; } static DualpaneMode uiColumnModeFromString(String m) { - switch(m) { - case "DualpaneMode.Single": return DualpaneMode.Single; - case "DualpaneMode.Dual1to2": return DualpaneMode.Dual1to2; - case "DualpaneMode.Dual1to4": return DualpaneMode.Dual1to4; - case "DualpaneMode.CopyPortrait": return DualpaneMode.CopyPortrait; + switch (m) { + case "DualpaneMode.Single": + return DualpaneMode.Single; + case "DualpaneMode.Dual1to2": + return DualpaneMode.Dual1to2; + case "DualpaneMode.Dual1to4": + return DualpaneMode.Dual1to4; + case "DualpaneMode.CopyPortrait": + return DualpaneMode.CopyPortrait; } print("Error: ui requested translation of column mode [$m] which doesn't exist"); return DualpaneMode.Single; @@ -188,11 +202,15 @@ class Settings extends ChangeNotifier { static String uiColumnModeToString(DualpaneMode m) { // todo: translate - switch(m) { - case DualpaneMode.Single: return "Single"; - case DualpaneMode.Dual1to2: return "Double (1:2)"; - case DualpaneMode.Dual1to4: return "Double (1:4)"; - case DualpaneMode.CopyPortrait: return "Same as portrait mode setting"; + switch (m) { + case DualpaneMode.Single: + return "Single"; + case DualpaneMode.Dual1to2: + return "Double (1:2)"; + case DualpaneMode.Dual1to4: + return "Double (1:4)"; + case DualpaneMode.CopyPortrait: + return "Same as portrait mode setting"; } } diff --git a/lib/views/addcontactview.dart b/lib/views/addcontactview.dart index 5947d3fb..a8a1a422 100644 --- a/lib/views/addcontactview.dart +++ b/lib/views/addcontactview.dart @@ -152,9 +152,14 @@ class _AddContactViewState extends State { Future.delayed(const Duration(milliseconds: 500), () { if (globalErrorHandler.importBundleSuccess) { - final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact + importBundle)); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - Navigator.pop(context); + // TODO: This isn't ideal, but because onChange can be fired during this future check + // and because the context can change after being popped we have this kind of double assertion... + // There is probably a better pattern to handle this... + if (AppLocalizations.of(context) != null) { + final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact + importBundle)); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + Navigator.popUntil(context, (route) => route.settings.name == "conversations"); + } } }); }, diff --git a/lib/views/addeditprofileview.dart b/lib/views/addeditprofileview.dart index 93a10ccb..26d2dcd7 100644 --- a/lib/views/addeditprofileview.dart +++ b/lib/views/addeditprofileview.dart @@ -166,7 +166,10 @@ class _AddEditProfileViewState extends State { autoFillHints: [AutofillHints.newPassword], validator: (value) { // Password field can be empty when just updating the profile, not on creation - if (Provider.of(context).isEncrypted && Provider.of(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) { + if (Provider.of(context).isEncrypted && + Provider.of(context, listen: false).onion.isEmpty && + value.isEmpty && + usePassword) { return AppLocalizations.of(context)!.passwordErrorEmpty; } if (Provider.of(context).deleteProfileError == true) { diff --git a/lib/views/doublecolview.dart b/lib/views/doublecolview.dart index 2737755e..e7d3a260 100644 --- a/lib/views/doublecolview.dart +++ b/lib/views/doublecolview.dart @@ -29,7 +29,7 @@ class _DoubleColumnViewState extends State { Flexible( flex: cols[1], child: flwtch.selectedConversation == null - ? Card(child:Center(child: Text(AppLocalizations.of(context)!.addContactFirst))) + ? Card(child: Center(child: Text(AppLocalizations.of(context)!.addContactFirst))) : //dev MultiProvider(providers: [ ChangeNotifierProvider.value(value: Provider.of(context)), diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 2329ea83..310db952 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -169,7 +169,7 @@ class _GlobalSettingsViewState extends State { )), AboutListTile( icon: Icon(Icons.info, color: settings.current().mainTextColor()), - applicationIcon: Padding(padding:EdgeInsets.all(5), child: Icon(CwtchIcons.cwtch_knott)), + applicationIcon: Padding(padding: EdgeInsets.all(5), child: Icon(CwtchIcons.cwtch_knott)), applicationName: "Cwtch (Flutter UI)", 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/messageview.dart b/lib/views/messageview.dart index da13f2c1..258f596e 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cwtch/views/peersettingsview.dart'; import 'package:cwtch/widgets/DropdownContacts.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -50,17 +51,19 @@ class _MessageViewState extends State { appBar: AppBar( // setting leading to null makes it do the default behaviour; container() hides it leading: Provider.of(context).uiColumns(appState.isLandscape(context)).length > 1 ? Container() : null, - title: Row(children: [ - ProfileImage( - imagePath: Provider.of(context).imagePath, - diameter: 42, - border: Provider.of(context).current().portraitOnlineBorderColor(), - badgeTextColor: Colors.red, - badgeColor: Colors.red, - ), - SizedBox( - width: 10, - ),Text(Provider.of(context).nickname)]), + title: Row(children: [ + ProfileImage( + imagePath: Provider.of(context).imagePath, + diameter: 42, + border: Provider.of(context).current().portraitOnlineBorderColor(), + badgeTextColor: Colors.red, + badgeColor: Colors.red, + ), + SizedBox( + width: 10, + ), + Text(Provider.of(context).nickname) + ]), actions: [ //IconButton(icon: Icon(Icons.chat), onPressed: _pushContactSettings), //IconButton(icon: Icon(Icons.list), onPressed: _pushContactSettings), @@ -100,11 +103,13 @@ class _MessageViewState extends State { } void _sendMessage([String? ignoredParam]) { - ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text); - Provider.of(context, listen: false) - .cwtch - .SendMessage(Provider.of(context, listen: false).profileOnion, Provider.of(context, listen: false).onion, jsonEncode(cm)); - _sendMessageHelper(); + if (ctrlrCompose.value.text.isNotEmpty) { + ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text); + Provider.of(context, listen: false) + .cwtch + .SendMessage(Provider.of(context, listen: false).profileOnion, Provider.of(context, listen: false).onion, jsonEncode(cm)); + _sendMessageHelper(); + } } void _sendInvitation([String? ignoredParam]) { @@ -135,12 +140,18 @@ class _MessageViewState extends State { Expanded( child: Container( decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of(context).theme.defaultButtonActiveColor()))), + child: RawKeyboardListener( + focusNode: FocusNode(), + onKey: handleKeyPress, child: TextFormField( key: Key('txtCompose'), controller: ctrlrCompose, - autofocus: !Platform.isAndroid, focusNode: focusNode, - textInputAction: TextInputAction.send, + autofocus: !Platform.isAndroid, + textInputAction: TextInputAction.newline, + keyboardType: TextInputType.multiline, + minLines: 1, + maxLines: null, onFieldSubmitted: _sendMessage, decoration: InputDecoration( enabledBorder: InputBorder.none, @@ -158,13 +169,26 @@ class _MessageViewState extends State { tooltip: AppLocalizations.of(context)!.sendMessage, onPressed: _sendMessage, ), - ))), + )))), ), ], ), ); } + // Send the message if enter is pressed without the shift key... + void handleKeyPress(event) { + var data = event.data as RawKeyEventData; + if (data.logicalKey == LogicalKeyboardKey.enter && !event.isShiftPressed) { + final messageWithoutNewLine = ctrlrCompose.value.text.trimRight(); + ctrlrCompose.value = TextEditingValue( + text: messageWithoutNewLine + ); + _sendMessage(); + + } + } + void placeHolder() => {}; // explicitly passing BuildContext ctx here is important, change at risk to own health diff --git a/lib/views/profilemgrview.dart b/lib/views/profilemgrview.dart index 2e65a2f8..80a037f4 100644 --- a/lib/views/profilemgrview.dart +++ b/lib/views/profilemgrview.dart @@ -40,45 +40,41 @@ class _ProfileMgrViewState extends State { // Prevents Android back button from closing the app on the profile manager screen // (which would shutdown connections and all kinds of other expensive to generate things) // TODO pop up a dialogue regarding closing the app? - builder: (context, settings, child) => - WillPopScope( - onWillPop: () async { - _modalShutdown(); - return Provider.of(context, listen: false).cwtchIsClosing; - }, - child: Scaffold( - backgroundColor: settings.theme.backgroundMainColor(), - appBar: AppBar( - title: Row(children: [ - Image( - image: AssetImage("assets/core/knott-white.png"), - filterQuality: FilterQuality.medium, - isAntiAlias: true, - width: 32, - height: 32, - colorBlendMode: BlendMode.dstIn, - color: Provider - .of(context) - .theme - .backgroundHilightElementColor(), - ), - SizedBox( - width: 10, - ), - Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor()))) - ]), - actions: getActions(), + builder: (context, settings, child) => WillPopScope( + onWillPop: () async { + _modalShutdown(); + return Provider.of(context, listen: false).cwtchIsClosing; + }, + child: Scaffold( + backgroundColor: settings.theme.backgroundMainColor(), + appBar: AppBar( + title: Row(children: [ + Image( + image: AssetImage("assets/core/knott-white.png"), + filterQuality: FilterQuality.medium, + isAntiAlias: true, + width: 32, + height: 32, + colorBlendMode: BlendMode.dstIn, + color: Provider.of(context).theme.backgroundHilightElementColor(), ), - floatingActionButton: FloatingActionButton( - onPressed: _pushAddEditProfile, - tooltip: AppLocalizations.of(context)!.addNewProfileBtn, - child: Icon( - Icons.add, - semanticLabel: AppLocalizations.of(context)!.addNewProfileBtn, - ), + SizedBox( + width: 10, ), - body: _buildProfileManager(), - )), + Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor()))) + ]), + actions: getActions(), + ), + floatingActionButton: FloatingActionButton( + onPressed: _pushAddEditProfile, + tooltip: AppLocalizations.of(context)!.addNewProfileBtn, + child: Icon( + Icons.add, + semanticLabel: AppLocalizations.of(context)!.addNewProfileBtn, + ), + ), + body: _buildProfileManager(), + )), ); } diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index 296ae406..d38d1df1 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -33,7 +33,11 @@ class _ContactRowState extends State { diameter: 64.0, imagePath: contact.imagePath, maskOut: !contact.isOnline(), - border: contact.isOnline() ? Provider.of(context).theme.portraitOnlineBorderColor() : contact.isBlocked ? Provider.of(context).theme.portraitBlockedBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), + border: contact.isOnline() + ? Provider.of(context).theme.portraitOnlineBorderColor() + : contact.isBlocked + ? Provider.of(context).theme.portraitBlockedBorderColor() + : Provider.of(context).theme.portraitOfflineBorderColor()), ), Expanded( child: Padding( @@ -44,13 +48,16 @@ class _ContactRowState extends State { Text( contact.nickname, //(contact.isInvitation ? "invite " : "non-invite ") + (contact.isBlocked ? "blokt" : "nonblokt"),// - style: TextStyle(fontSize: Provider.of(context).theme.contactOnionTextSize(), - color: contact.isBlocked ? Provider.of(context).theme.portraitBlockedTextColor() : Provider.of(context).theme.mainTextColor()), //Provider.of(context).biggerFont, + style: TextStyle( + fontSize: Provider.of(context).theme.contactOnionTextSize(), + color: contact.isBlocked + ? Provider.of(context).theme.portraitBlockedTextColor() + : Provider.of(context).theme.mainTextColor()), //Provider.of(context).biggerFont, softWrap: true, overflow: TextOverflow.visible, ), Text(contact.onion, - style: TextStyle(color: contact.isBlocked ? Provider.of(context).theme.portraitBlockedTextColor() : Provider.of(context).theme.mainTextColor())), + style: TextStyle(color: contact.isBlocked ? Provider.of(context).theme.portraitBlockedTextColor() : Provider.of(context).theme.mainTextColor())), ], ))), Padding( @@ -60,7 +67,10 @@ class _ContactRowState extends State { IconButton( padding: EdgeInsets.zero, iconSize: 16, - icon: Icon(Icons.favorite, color: Provider.of(context).theme.mainTextColor(),), + icon: Icon( + Icons.favorite, + color: Provider.of(context).theme.mainTextColor(), + ), tooltip: AppLocalizations.of(context)!.tooltipAcceptContactRequest, onPressed: _btnApprove, ), diff --git a/lib/widgets/invitationbubble.dart b/lib/widgets/invitationbubble.dart index ff86ffca..47057f74 100644 --- a/lib/widgets/invitationbubble.dart +++ b/lib/widgets/invitationbubble.dart @@ -66,18 +66,18 @@ class InvitationBubbleState extends State { return MalformedBubble(); } - var wdgMessage = isGroup && !showGroupInvite ? - Text(AppLocalizations.of(context)!.groupInviteSettingsWarning) : - fromMe - ? senderInviteChrome(AppLocalizations.of(context)!.sendAnInvitation, - isGroup ? Provider.of(context).contactList.getContact(Provider.of(context).inviteTarget)!.nickname : Provider.of(context).message, myKey) - : (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, Provider.of(context).inviteNick, - Provider.of(context).inviteTarget, myKey)); + var wdgMessage = isGroup && !showGroupInvite + ? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning) + : fromMe + ? senderInviteChrome(AppLocalizations.of(context)!.sendAnInvitation, + isGroup ? Provider.of(context).contactList.getContact(Provider.of(context).inviteTarget)!.nickname : Provider.of(context).message, myKey) + : (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, Provider.of(context).inviteNick, + Provider.of(context).inviteTarget, myKey)); Widget wdgDecorations; if (isGroup && !showGroupInvite) { wdgDecorations = Text('\u202F'); - } else if (fromMe) { + } else if (fromMe) { wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, prettyDate: prettyDate); } else if (isAccepted) { wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F'); @@ -113,7 +113,8 @@ class InvitationBubbleState extends State { child: Padding( padding: EdgeInsets.all(9.0), child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [ - Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(isGroup && !showGroupInvite ? CwtchIcons.enable_experiments : CwtchIcons.send_invite, size: 32))), + Center( + widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(isGroup && !showGroupInvite ? CwtchIcons.enable_experiments : CwtchIcons.send_invite, size: 32))), Center( widthFactor: 1.0, child: Column( diff --git a/lib/widgets/messagelist.dart b/lib/widgets/messagelist.dart index 67158294..5e985b7d 100644 --- a/lib/widgets/messagelist.dart +++ b/lib/widgets/messagelist.dart @@ -16,16 +16,15 @@ class _MessageListState extends State { @override Widget build(BuildContext outerContext) { - - bool isP2P = !Provider.of(context).isGroup; + bool isP2P = !Provider.of(context).isGroup; bool isGroupAndSyncing = Provider.of(context).isGroup == true && Provider.of(context).status == "Authenticated"; bool isGroupAndSynced = Provider.of(context).isGroup && Provider.of(context).status == "Synced"; - bool isGroupAndNotAuthenticated= Provider.of(context).isGroup && Provider.of(context).status != "Authenticated"; + bool isGroupAndNotAuthenticated = Provider.of(context).isGroup && Provider.of(context).status != "Authenticated"; bool showEphemeralWarning = (isP2P && Provider.of(context).savePeerHistory != "SaveHistory"); bool showOfflineWarning = Provider.of(context).isOnline() == false; - bool showMessageWarning = showEphemeralWarning || showOfflineWarning; bool showSyncing = isGroupAndSyncing; + bool showMessageWarning = showEphemeralWarning || showOfflineWarning || showSyncing; // Only load historical messages when the conversation is with a p2p contact OR the conversation is a server and *not* syncing. bool loadMessages = isP2P || (isGroupAndSynced || isGroupAndNotAuthenticated); @@ -37,18 +36,17 @@ class _MessageListState extends State { child: Container( padding: EdgeInsets.all(5.0), color: Provider.of(context).theme.defaultButtonActiveColor(), - child: showSyncing ? - Text(AppLocalizations.of(context)!.serverNotSynced, - textAlign: TextAlign.center) - : showOfflineWarning - ? Text(Provider.of(context).isGroup ? AppLocalizations.of(context)!.serverConnectivityDisconnected : AppLocalizations.of(context)!.peerOfflineMessage, - textAlign: TextAlign.center) - // Only show the ephemeral status for peer conversations, not for groups... - : (showEphemeralWarning - ? Text(AppLocalizations.of(context)!.chatHistoryDefault, textAlign: TextAlign.center) - : - // We are not allowed to put null here, so put an empty text widge - Text("")), + child: showSyncing + ? Text(AppLocalizations.of(context)!.serverNotSynced, textAlign: TextAlign.center) + : showOfflineWarning + ? Text(Provider.of(context).isGroup ? AppLocalizations.of(context)!.serverConnectivityDisconnected : AppLocalizations.of(context)!.peerOfflineMessage, + textAlign: TextAlign.center) + // Only show the ephemeral status for peer conversations, not for groups... + : (showEphemeralWarning + ? Text(AppLocalizations.of(context)!.chatHistoryDefault, textAlign: TextAlign.center) + : + // We are not allowed to put null here, so put an empty text widge + Text("")), )), Expanded( child: Scrollbar( @@ -63,30 +61,32 @@ class _MessageListState extends State { alignment: Alignment.center, image: AssetImage("assets/core/negative_heart_512px.png"), colorFilter: ColorFilter.mode(Provider.of(context).theme.hilightElementTextColor(), BlendMode.srcIn))), - // Don't load messages for syncing server... - child: loadMessages ? ListView.builder( - controller: ctrlr1, - itemCount: Provider.of(outerContext).totalMessages, - reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction... - itemBuilder: (itemBuilderContext, index) { - var trueIndex = Provider.of(outerContext).totalMessages - index - 1; - return ChangeNotifierProvider( - key: ValueKey(trueIndex), - 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, - ), - builder: (bcontext, child) { - String idx = Provider.of(outerContext).isGroup == true && Provider.of(bcontext).signature.isEmpty == false - ? Provider.of(bcontext).signature - : trueIndex.toString(); - return RepaintBoundary(child: MessageRow(key: Provider.of(bcontext).getMessageKey(idx))); - }); - }, - ) : null ))) + // Don't load messages for syncing server... + child: loadMessages + ? ListView.builder( + controller: ctrlr1, + itemCount: Provider.of(outerContext).totalMessages, + reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction... + itemBuilder: (itemBuilderContext, index) { + var trueIndex = Provider.of(outerContext).totalMessages - index - 1; + return ChangeNotifierProvider( + key: ValueKey(trueIndex), + 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, + ), + builder: (bcontext, child) { + String idx = Provider.of(outerContext).isGroup == true && Provider.of(bcontext).signature.isEmpty == false + ? Provider.of(bcontext).signature + : trueIndex.toString(); + return RepaintBoundary(child: MessageRow(key: Provider.of(bcontext).getMessageKey(idx))); + }); + }, + ) + : null))) ]))); } } diff --git a/lib/widgets/profilerow.dart b/lib/widgets/profilerow.dart index dfa79f69..de865c8e 100644 --- a/lib/widgets/profilerow.dart +++ b/lib/widgets/profilerow.dart @@ -30,43 +30,29 @@ class _ProfileRowState extends State { padding: const EdgeInsets.all(2.0), //border size child: ProfileImage( badgeCount: 0, - badgeColor: Provider - .of(context) - .theme - .portraitProfileBadgeColor(), - badgeTextColor: Provider - .of(context) - .theme - .portraitProfileBadgeTextColor(), + badgeColor: Provider.of(context).theme.portraitProfileBadgeColor(), + badgeTextColor: Provider.of(context).theme.portraitProfileBadgeTextColor(), diameter: 64.0, imagePath: profile.imagePath, - border: profile.isOnline ? Provider - .of(context) - .theme - .portraitOnlineBorderColor() : Provider - .of(context) - .theme - .portraitOfflineBorderColor())), + border: profile.isOnline ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor())), Expanded( child: Column( - children: [ - Text( - profile.nickname, - semanticsLabel: profile.nickname, - style: Provider - .of(context) - .biggerFont, - softWrap: true, - overflow: TextOverflow.ellipsis, - ), - ExcludeSemantics( - child: Text( - profile.onion, - softWrap: true, - overflow: TextOverflow.ellipsis, - )) - ], - )), + children: [ + Text( + profile.nickname, + semanticsLabel: profile.nickname, + style: Provider.of(context).biggerFont, + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + ExcludeSemantics( + child: Text( + profile.onion, + softWrap: true, + overflow: TextOverflow.ellipsis, + )) + ], + )), IconButton( enableFeedback: true, tooltip: AppLocalizations.of(context)!.editProfile + " " + profile.nickname, @@ -83,7 +69,7 @@ class _ProfileRowState extends State { appState.selectedProfile = profile.onion; appState.selectedConversation = null; - _pushContactList(profile, appState.isLandscape(context));//orientation == Orientation.landscape); + _pushContactList(profile, appState.isLandscape(context)); //orientation == Orientation.landscape); }); }, )); @@ -94,19 +80,17 @@ class _ProfileRowState extends State { MaterialPageRoute( settings: RouteSettings(name: "conversations"), builder: (BuildContext buildcontext) { - return OrientationBuilder( - builder: (orientationBuilderContext, orientation) { - return MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: profile), - ChangeNotifierProvider.value(value: profile.contactList), - ], - builder: (innercontext, widget) { - var appState = Provider.of(context); - var settings = Provider.of(context); - return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? DoubleColumnView() : ContactsView(); - } - ); + return OrientationBuilder(builder: (orientationBuilderContext, orientation) { + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: profile), + ChangeNotifierProvider.value(value: profile.contactList), + ], + builder: (innercontext, widget) { + var appState = Provider.of(context); + var settings = Provider.of(context); + return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? DoubleColumnView() : ContactsView(); + }); }); }, ), diff --git a/pubspec.lock b/pubspec.lock index ec4202b9..58f991e0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "1.2.0" clock: dependency: transitive description: @@ -70,7 +70,7 @@ packages: name: dbus url: "https://pub.dartlang.org" source: hosted - version: "0.5.1" + version: "0.5.2" desktop_notifications: dependency: "direct main" description: @@ -91,14 +91,14 @@ packages: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.2" file: dependency: transitive description: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.1.1" + version: "6.1.2" flutter: dependency: "direct main" description: flutter @@ -125,7 +125,7 @@ packages: name: glob url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.1" http: dependency: transitive description: @@ -189,20 +189,6 @@ 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_config: dependency: transitive description: @@ -216,7 +202,7 @@ packages: name: package_info_plus url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.3" package_info_plus_linux: dependency: transitive description: @@ -244,14 +230,14 @@ packages: name: package_info_plus_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" path: dependency: transitive description: @@ -265,7 +251,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.2" path_provider_linux: dependency: transitive description: @@ -300,14 +286,14 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.11.0" + version: "1.11.1" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "4.2.0" platform: dependency: transitive description: @@ -382,7 +368,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.1" + version: "0.4.0" typed_data: dependency: transitive description: @@ -403,7 +389,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.2.4" xdg_directories: dependency: transitive description: diff --git a/windows/nsis/brand_side.bmp b/windows/nsis/brand_side.bmp new file mode 100644 index 00000000..3d03803d Binary files /dev/null and b/windows/nsis/brand_side.bmp differ diff --git a/windows/nsis/cwtch-installer.nsi b/windows/nsis/cwtch-installer.nsi new file mode 100644 index 00000000..75a59515 --- /dev/null +++ b/windows/nsis/cwtch-installer.nsi @@ -0,0 +1,92 @@ +; USAGE: Run in ui/deploy, requires the output be in 'windows' directory + +!include "MUI2.nsh" + +; General settings ---------------------------- +Name "Cwtch" +; !define MUI_BRANDINGTEXT "SIG Beta Ver. 1.0" + +Unicode True + +# define the name of the installer +Outfile "cwtch-installer.exe" + +# For removing Start Menu shortcut in Windows 7 +#RequestExecutionLevel user +RequestExecutionLevel admin ;Require admin rights on NT6+ (When UAC is turned on) + +# define the directory to install to, the desktop in this case as specified +# by the predefined $DESKTOP variable +InstallDir "$PROGRAMFILES\Cwtch" + +;Get installation folder from registry if available +InstallDirRegKey HKCU "Software\Cwtch" "installLocation" + +; MUI Interface ----------------------------- + +!define MUI_INSTALLCOLORS "DFB9DE 281831" + +; 128x128, 32bit +!define MUI_ICON "../runner/resources/knot_128.ico" + +!define MUI_HEADERIMAGE +!define MUI_HEADERIMAGE_BITMAP "cwtch_title.bmp" + +!define MUI_TEXTCOLOR "350052" + +!define MUI_WELCOMEFINISHPAGE_BITMAP "brand_side.bmp" +!define MUI_WELCOMEFINISHPAGE_BITMAP_STRETCH NoStretchNoCrop + +!define MUI_INSTFILESPAGE_COLORS "DFB9DE 281831" +!define MUI_INSTFILESPAGE_PROGRESSBAR "colored" + +!define MUI_FINISHPAGE_NOAUTOCLOSE + + +ShowInstDetails show + +; Pages -------- + + +!define MUI_WELCOMEPAGE_TITLE "Welcome to the Cwtch installer" +!define MUI_WELCOMEPAGE_TEXT "Cwtch (pronounced: kutch) is a Welsh word roughly meaning 'a hug that creates a safe space'$\n$\n\ + Cwtch is a platform for building consentful, decentralized, untrusted infrastructure using metadata resistant group communication applications. Currently there is a selfnamed instant messaging prototype app that is driving development and testing. Many Further apps are planned as the platform matures." + +!define MUI_FINISHPAGE_TITLE "Enjoy Cwtch" +!define MUI_FINISHPAGE_RUN $INSTDIR/cwtch.exe +!define MUI_FINISHPAGE_TEXT "You can keep up-to-date on Cwtch and report any issues you have at https://cwtch.im" +!define MUI_FINISHPAGE_LINK "https://cwtch.im" +!define MUI_FINISHPAGE_LINK_LOCATION "https://cwtch.im" +!define MUI_FINISHPAGE_LINK_COLOR "D01972" + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_LICENSE "../../LICENSE" +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +; Languages -------------------------------- + +!insertmacro MUI_LANGUAGE "English" + +# default section +Section + + # define the output path for this file + SetOutPath $INSTDIR + + # define what to install and place it in the output path + # Filler for .sh to populate with contents of deploy/windows + #FILESLISTSTART + FILE /r "..\..\build\windows\runner\Release\" + #FILESLISTEND + + + # create a shortcut in the start menu programs directory + CreateDirectory "$SMPROGRAMS\Cwtch" + CreateShortcut "$SMPROGRAMS\Cwtch\Cwtch.lnk" "$INSTDIR\cwtch.exe" "" "$INSTDIR\cwtch.ico" + + ;Store installation folder + WriteRegStr HKCU "Software\Cwtch" "installLocation" $INSTDIR + +SectionEnd diff --git a/windows/nsis/cwtch_title.bmp b/windows/nsis/cwtch_title.bmp new file mode 100644 index 00000000..8acc80af Binary files /dev/null and b/windows/nsis/cwtch_title.bmp differ diff --git a/windows/runner/run_loop.cpp b/windows/runner/run_loop.cpp index 2d6636ab..818b6ae2 100644 --- a/windows/runner/run_loop.cpp +++ b/windows/runner/run_loop.cpp @@ -9,7 +9,14 @@ RunLoop::RunLoop() {} RunLoop::~RunLoop() {} void RunLoop::Run() { - bool keep_running = true; + // Fix/workaround for Windows high CPU usage + // https://github.com/flutter/flutter/issues/78517#issuecomment-814843107 + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + /*bool keep_running = true; TimePoint next_flutter_event_time = TimePoint::clock::now(); while (keep_running) { std::chrono::nanoseconds wait_duration = @@ -40,7 +47,7 @@ void RunLoop::Run() { next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } - } + }*/ } void RunLoop::RegisterFlutterInstance(