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() } } 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))), diff --git a/test/textfield_form_alpha.png b/test/textfield_form_alpha.png index 531bb36a..1eff8ae8 100644 Binary files a/test/textfield_form_alpha.png and b/test/textfield_form_alpha.png differ