From bee3ae6e7bb879ee6bebbfad98e17e4a33891dad Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 12 Jan 2022 15:28:33 -0800 Subject: [PATCH 1/3] Fix Debug Layout Issue in AddContact --- lib/views/addcontactview.dart | 290 +++++++++++++++++----------------- lib/views/torstatusview.dart | 23 +-- lib/widgets/textfield.dart | 10 +- 3 files changed, 155 insertions(+), 168 deletions(-) diff --git a/lib/views/addcontactview.dart b/lib/views/addcontactview.dart index 0d920f6c..f8acf20c 100644 --- a/lib/views/addcontactview.dart +++ b/lib/views/addcontactview.dart @@ -52,26 +52,22 @@ class _AddContactViewState extends State { /// We display a different number of tabs depending on the experiment setup bool groupsEnabled = Provider.of(context).isExperimentEnabled(TapirGroupsExperiment); - return Scrollbar( - isAlwaysShown: true, - child: SingleChildScrollView( - clipBehavior: Clip.antiAlias, - child: Consumer(builder: (context, globalErrorHandler, child) { - return DefaultTabController( - length: groupsEnabled ? 2 : 1, - child: Column(children: [ - (groupsEnabled ? getTabBarWithGroups() : getTabBarWithAddPeerOnly()), - Expanded( - child: TabBarView( - children: (groupsEnabled - ? [ - addPeerTab(), - addGroupTab(), - ] - : [addPeerTab()]), - )), - ])); - }))); + return Consumer(builder: (context, globalErrorHandler, child) { + return DefaultTabController( + length: groupsEnabled ? 2 : 1, + child: Column(children: [ + (groupsEnabled ? getTabBarWithGroups() : getTabBarWithAddPeerOnly()), + Expanded( + child: TabBarView( + children: (groupsEnabled + ? [ + addPeerTab(), + addGroupTab(), + ] + : [addPeerTab()]), + )), + ])); + }); } void _copyOnion() { @@ -109,67 +105,70 @@ class _AddContactViewState extends State { /// The Add Peer Tab allows a peer to add a specific non-group peer to their contact lists /// We also provide a convenient way to copy their onion. Widget addPeerTab() { - return Container( - margin: EdgeInsets.all(30), - padding: EdgeInsets.all(20), - child: Form( - autovalidateMode: AutovalidateMode.always, - key: _formKey, - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - CwtchLabel(label: AppLocalizations.of(context)!.profileOnionLabel), - SizedBox( - height: 20, - ), - CwtchButtonTextField( - controller: ctrlrOnion, - onPressed: _copyOnion, - readonly: true, - icon: Icon( - CwtchIcons.address_copy_2, - size: 32, - ), - tooltip: AppLocalizations.of(context)!.copyBtn, - ), - SizedBox( - height: 20, - ), - CwtchLabel(label: AppLocalizations.of(context)!.pasteAddressToAddContact), - SizedBox( - height: 20, - ), - CwtchTextField( - controller: ctrlrContact, - validator: (value) { - if (value == "") { - return null; - } - if (globalErrorHandler.invalidImportStringError) { - return AppLocalizations.of(context)!.invalidImportString; - } else if (globalErrorHandler.contactAlreadyExistsError) { - return AppLocalizations.of(context)!.contactAlreadyExists; - } else if (globalErrorHandler.explicitAddContactSuccess) {} - return null; - }, - onChanged: (String importBundle) async { - var profileOnion = Provider.of(context, listen: false).onion; - Provider.of(context, listen: false).cwtch.ImportBundle(profileOnion, importBundle); + return Scrollbar( + child: SingleChildScrollView( + clipBehavior: Clip.antiAlias, + child: Container( + margin: EdgeInsets.all(30), + padding: EdgeInsets.all(20), + child: Form( + autovalidateMode: AutovalidateMode.always, + key: _formKey, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + CwtchLabel(label: AppLocalizations.of(context)!.profileOnionLabel), + SizedBox( + height: 20, + ), + CwtchButtonTextField( + controller: ctrlrOnion, + onPressed: _copyOnion, + readonly: true, + icon: Icon( + CwtchIcons.address_copy_2, + size: 32, + ), + tooltip: AppLocalizations.of(context)!.copyBtn, + ), + SizedBox( + height: 20, + ), + CwtchLabel(label: AppLocalizations.of(context)!.pasteAddressToAddContact), + SizedBox( + height: 20, + ), + CwtchTextField( + controller: ctrlrContact, + validator: (value) { + if (value == "") { + return null; + } + if (globalErrorHandler.invalidImportStringError) { + return AppLocalizations.of(context)!.invalidImportString; + } else if (globalErrorHandler.contactAlreadyExistsError) { + return AppLocalizations.of(context)!.contactAlreadyExists; + } else if (globalErrorHandler.explicitAddContactSuccess) {} + return null; + }, + onChanged: (String importBundle) async { + var profileOnion = Provider.of(context, listen: false).onion; + Provider.of(context, listen: false).cwtch.ImportBundle(profileOnion, importBundle); - Future.delayed(const Duration(milliseconds: 500), () { - if (globalErrorHandler.importBundleSuccess) { - // 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"); - } - } - }); - }, - hintText: '', - ) - ]))); + Future.delayed(const Duration(milliseconds: 500), () { + if (globalErrorHandler.importBundleSuccess) { + // 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"); + } + } + }); + }, + hintText: '', + ) + ]))))); } /// TODO Add Group Pane @@ -179,71 +178,74 @@ class _AddContactViewState extends State { return Text(AppLocalizations.of(context)!.addServerFirst); } - return Container( - margin: EdgeInsets.all(30), - padding: EdgeInsets.all(20), - child: Form( - autovalidateMode: AutovalidateMode.always, - key: _createGroupFormKey, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CwtchLabel(label: AppLocalizations.of(context)!.server), - SizedBox( - height: 20, - ), - DropdownButton( - onChanged: (String? newServer) { - setState(() { - server = newServer!; - }); - }, - isExpanded: true, // magic property - value: server, - items: Provider.of(context) - .serverList - .servers - .where((serverInfo) => serverInfo.status == "Synced") - .map>((RemoteServerInfoState serverInfo) { - return DropdownMenuItem( - value: serverInfo.onion, - child: Text( - serverInfo.description.isNotEmpty ? serverInfo.description : serverInfo.onion, - overflow: TextOverflow.ellipsis, + return Scrollbar( + child: SingleChildScrollView( + clipBehavior: Clip.antiAlias, + child: Container( + margin: EdgeInsets.all(30), + padding: EdgeInsets.all(20), + child: Form( + autovalidateMode: AutovalidateMode.always, + key: _createGroupFormKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CwtchLabel(label: AppLocalizations.of(context)!.server), + SizedBox( + height: 20, ), - ); - }).toList()), - SizedBox( - height: 20, - ), - CwtchLabel(label: AppLocalizations.of(context)!.groupName), - SizedBox( - height: 20, - ), - CwtchTextField( - controller: ctrlrGroupName, - hintText: AppLocalizations.of(context)!.groupNameLabel, - onChanged: (newValue) {}, - validator: (value) {}, - ), - SizedBox( - height: 20, - ), - ElevatedButton( - onPressed: () { - var profileOnion = Provider.of(context, listen: false).onion; - Provider.of(context, listen: false).cwtch.CreateGroup(profileOnion, server, ctrlrGroupName.text); - Future.delayed(const Duration(milliseconds: 500), () { - final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact + " " + ctrlrGroupName.text)); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - Navigator.pop(context); - }); - }, - child: Text(AppLocalizations.of(context)!.createGroupBtn), - ), - ], - ))); + DropdownButton( + onChanged: (String? newServer) { + setState(() { + server = newServer!; + }); + }, + isExpanded: true, // magic property + value: server, + items: Provider.of(context) + .serverList + .servers + .where((serverInfo) => serverInfo.status == "Synced") + .map>((RemoteServerInfoState serverInfo) { + return DropdownMenuItem( + value: serverInfo.onion, + child: Text( + serverInfo.description.isNotEmpty ? serverInfo.description : serverInfo.onion, + overflow: TextOverflow.ellipsis, + ), + ); + }).toList()), + SizedBox( + height: 20, + ), + CwtchLabel(label: AppLocalizations.of(context)!.groupName), + SizedBox( + height: 20, + ), + CwtchTextField( + controller: ctrlrGroupName, + hintText: AppLocalizations.of(context)!.groupNameLabel, + onChanged: (newValue) {}, + validator: (value) {}, + ), + SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: () { + var profileOnion = Provider.of(context, listen: false).onion; + Provider.of(context, listen: false).cwtch.CreateGroup(profileOnion, server, ctrlrGroupName.text); + Future.delayed(const Duration(milliseconds: 500), () { + final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact + " " + ctrlrGroupName.text)); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + Navigator.pop(context); + }); + }, + child: Text(AppLocalizations.of(context)!.createGroupBtn), + ), + ], + ))))); } /// TODO Manage Servers Tab diff --git a/lib/views/torstatusview.dart b/lib/views/torstatusview.dart index 1a63899c..982bfbaa 100644 --- a/lib/views/torstatusview.dart +++ b/lib/views/torstatusview.dart @@ -106,14 +106,10 @@ class _TorStatusView extends State { if (port > 0 && port < 65536) { return null; } else { - return AppLocalizations.of( - context)! - .torSettingsErrorSettingPort; + return AppLocalizations.of(context)!.torSettingsErrorSettingPort; } - }catch (e) { - return AppLocalizations.of( - context)! - .torSettingsErrorSettingPort; + } catch (e) { + return AppLocalizations.of(context)!.torSettingsErrorSettingPort; } }, onChanged: (String socksPort) { @@ -141,22 +137,17 @@ class _TorStatusView extends State { if (port > 0 && port < 65536) { return null; } else { - return AppLocalizations.of( - context)! - .torSettingsErrorSettingPort; + return AppLocalizations.of(context)!.torSettingsErrorSettingPort; } - }catch (e) { - return AppLocalizations.of( - context)! - .torSettingsErrorSettingPort; + } catch (e) { + return AppLocalizations.of(context)!.torSettingsErrorSettingPort; } }, onChanged: (String controlPort) { try { var port = int.parse(controlPort); if (port > 0 && port < 65536) { - settings.controlPort = - int.parse(controlPort); + settings.controlPort = int.parse(controlPort); saveSettings(context); } } catch (e) {} diff --git a/lib/widgets/textfield.dart b/lib/widgets/textfield.dart index 9f5ff620..b62c84c8 100644 --- a/lib/widgets/textfield.dart +++ b/lib/widgets/textfield.dart @@ -51,9 +51,7 @@ class _CwtchTextFieldState extends State { : widget.number ? TextInputType.number : TextInputType.text, - inputFormatters: widget.number ? [ - FilteringTextInputFormatter.digitsOnly - ] : null, + inputFormatters: widget.number ? [FilteringTextInputFormatter.digitsOnly] : null, maxLines: widget.multiLine ? null : 1, scrollController: _scrollController, enableIMEPersonalizedLearning: false, @@ -66,11 +64,7 @@ class _CwtchTextFieldState extends State { focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor, width: 3.0)), focusedErrorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor, width: 3.0)), errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor, width: 3.0)), - errorStyle: TextStyle( - color: theme.current().textfieldErrorColor, - fontWeight: FontWeight.bold, - overflow: TextOverflow.visible - ), + errorStyle: TextStyle(color: theme.current().textfieldErrorColor, fontWeight: FontWeight.bold, overflow: TextOverflow.visible), fillColor: theme.current().textfieldBackgroundColor, contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor, width: 3.0))), From 9d3d5b06e5712c20e2cebde05d5bc00480c59d1a Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 12 Jan 2022 15:54:07 -0800 Subject: [PATCH 2/3] Upgrade Android Dependencies. Remove references to jCenter --- android/app/build.gradle | 3 ++- android/build.gradle | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 5ac09ca3..4899ea9e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -89,7 +89,8 @@ dependencies { implementation project(':cwtch') implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2" - implementation "com.airbnb.android:lottie:3.5.0" + implementation "com.airbnb.android:lottie:4.2.1" + implementation "androidx.localbroadcastmanager:localbroadcastmanager:1.0.0" implementation "com.android.support.constraint:constraint-layout:2.0.4" // WorkManager diff --git a/android/build.gradle b/android/build.gradle index 5a12dade..0827984d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,8 @@ buildscript { ext.kotlin_version = '1.3.50' repositories { google() - jcenter() + // jCenter() no longer exists... https://blog.gradle.org/jcenter-shutdown + mavenCentral() } dependencies { @@ -15,7 +16,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } From a3e2da8469ffe9f8c70424db55551009bc8d2dec Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 12 Jan 2022 16:05:58 -0800 Subject: [PATCH 3/3] update text field golden --- test/textfield_form_alpha.png | Bin 8123 -> 8237 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/textfield_form_alpha.png b/test/textfield_form_alpha.png index 531bb36ab74c681fd472f67e044e5a16a8910eb2..1eff8ae81debab26d982a6df3bdc93395a36ec09 100644 GIT binary patch literal 8237 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 literal 8123 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