From 453d4f3423e0b299c091e100469623df973a2817 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 15 Apr 2021 09:05:41 +0200 Subject: [PATCH] refactor: New private chat view --- lib/config/routes.dart | 2 +- .../new_private_chat_controller.dart | 116 +++++++++ lib/views/new_private_chat.dart | 229 ------------------ lib/views/new_private_chat_view.dart | 133 ++++++++++ 4 files changed, 250 insertions(+), 230 deletions(-) create mode 100644 lib/controllers/new_private_chat_controller.dart delete mode 100644 lib/views/new_private_chat.dart create mode 100644 lib/views/new_private_chat_view.dart diff --git a/lib/config/routes.dart b/lib/config/routes.dart index c72c5350..5348c0ca 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -16,7 +16,7 @@ import 'package:fluffychat/views/loading_view.dart'; import 'package:fluffychat/views/log_view.dart'; import 'package:fluffychat/views/login.dart'; import 'package:fluffychat/controllers/new_group_controller.dart'; -import 'package:fluffychat/views/new_private_chat.dart'; +import 'package:fluffychat/controllers/new_private_chat_controller.dart'; import 'package:fluffychat/views/search_view.dart'; import 'package:fluffychat/views/settings.dart'; import 'package:fluffychat/views/settings_3pid.dart'; diff --git a/lib/controllers/new_private_chat_controller.dart b/lib/controllers/new_private_chat_controller.dart new file mode 100644 index 00000000..2a99c5d1 --- /dev/null +++ b/lib/controllers/new_private_chat_controller.dart @@ -0,0 +1,116 @@ +import 'dart:async'; + +import 'package:adaptive_page_layout/adaptive_page_layout.dart'; +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/views/new_private_chat_view.dart'; +import 'package:future_loading_dialog/future_loading_dialog.dart'; +import 'package:fluffychat/views/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class NewPrivateChat extends StatefulWidget { + @override + NewPrivateChatController createState() => NewPrivateChatController(); +} + +class NewPrivateChatController extends State { + TextEditingController controller = TextEditingController(); + final formKey = GlobalKey(); + bool loading = false; + String currentSearchTerm; + List foundProfiles = []; + Timer coolDown; + Profile get foundProfile => + foundProfiles.firstWhere((user) => user.userId == '@$currentSearchTerm', + orElse: () => null); + bool get correctMxId => + foundProfiles + .indexWhere((user) => user.userId == '@$currentSearchTerm') != + -1; + + void submitAction([_]) async { + controller.text = controller.text.replaceAll('@', '').trim(); + if (controller.text.isEmpty) return; + if (!formKey.currentState.validate()) return; + final matrix = Matrix.of(context); + + if ('@' + controller.text == matrix.client.userID) return; + + final user = User( + '@' + controller.text, + room: Room(id: '', client: matrix.client), + ); + final roomID = await showFutureLoadingDialog( + context: context, + future: () => user.startDirectChat(), + ); + + if (roomID.error == null) { + await AdaptivePageLayout.of(context) + .popAndPushNamed('/rooms/${roomID.result}'); + } + } + + void searchUserWithCoolDown([_]) async { + coolDown?.cancel(); + coolDown = Timer( + Duration(milliseconds: 500), + () => searchUser(controller.text), + ); + } + + void searchUser(String text) async { + if (text.isEmpty) { + setState(() { + foundProfiles = []; + }); + } + currentSearchTerm = text; + if (currentSearchTerm.isEmpty) return; + if (loading) return; + setState(() => loading = true); + final matrix = Matrix.of(context); + UserSearchResult response; + try { + response = await matrix.client.searchUser(text, limit: 10); + } catch (_) {} + setState(() => loading = false); + if (response?.results?.isEmpty ?? true) return; + setState(() { + foundProfiles = List.from(response.results); + }); + } + + String validateForm(String value) { + if (value.isEmpty) { + return L10n.of(context).pleaseEnterAMatrixIdentifier; + } + final matrix = Matrix.of(context); + final mxid = '@' + controller.text.trim(); + if (mxid == matrix.client.userID) { + return L10n.of(context).youCannotInviteYourself; + } + if (!mxid.contains('@')) { + return L10n.of(context).makeSureTheIdentifierIsValid; + } + if (!mxid.contains(':')) { + return L10n.of(context).makeSureTheIdentifierIsValid; + } + return null; + } + + void inviteAction() => FluffyShare.share( + L10n.of(context).inviteText(Matrix.of(context).client.userID, + 'https://matrix.to/#/${Matrix.of(context).client.userID}'), + context, + ); + + void pickUser(Profile foundProfile) => setState( + () => controller.text = + currentSearchTerm = foundProfile.userId.substring(1), + ); + + @override + Widget build(BuildContext context) => NewPrivateChatView(this); +} diff --git a/lib/views/new_private_chat.dart b/lib/views/new_private_chat.dart deleted file mode 100644 index 1598dae0..00000000 --- a/lib/views/new_private_chat.dart +++ /dev/null @@ -1,229 +0,0 @@ -import 'dart:async'; - -import 'package:adaptive_page_layout/adaptive_page_layout.dart'; -import 'package:famedlysdk/famedlysdk.dart'; -import 'package:fluffychat/views/widgets/avatar.dart'; -import 'package:fluffychat/views/widgets/contacts_list.dart'; -import 'package:fluffychat/views/widgets/max_width_body.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:fluffychat/views/widgets/matrix.dart'; -import 'package:fluffychat/utils/fluffy_share.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -class NewPrivateChat extends StatefulWidget { - @override - _NewPrivateChatState createState() => _NewPrivateChatState(); -} - -class _NewPrivateChatState extends State { - TextEditingController controller = TextEditingController(); - final _formKey = GlobalKey(); - bool loading = false; - String currentSearchTerm; - List foundProfiles = []; - Timer coolDown; - Profile get foundProfile => - foundProfiles.firstWhere((user) => user.userId == '@$currentSearchTerm', - orElse: () => null); - bool get correctMxId => - foundProfiles - .indexWhere((user) => user.userId == '@$currentSearchTerm') != - -1; - - void submitAction(BuildContext context) async { - controller.text = controller.text.replaceAll('@', '').trim(); - if (controller.text.isEmpty) return; - if (!_formKey.currentState.validate()) return; - final matrix = Matrix.of(context); - - if ('@' + controller.text == matrix.client.userID) return; - - final user = User( - '@' + controller.text, - room: Room(id: '', client: matrix.client), - ); - final roomID = await showFutureLoadingDialog( - context: context, - future: () => user.startDirectChat(), - ); - - if (roomID.error == null) { - await AdaptivePageLayout.of(context) - .popAndPushNamed('/rooms/${roomID.result}'); - } - } - - void searchUserWithCoolDown(BuildContext context) async { - coolDown?.cancel(); - coolDown = Timer( - Duration(milliseconds: 500), - () => searchUser(context, controller.text), - ); - } - - void searchUser(BuildContext context, String text) async { - if (text.isEmpty) { - setState(() { - foundProfiles = []; - }); - } - currentSearchTerm = text; - if (currentSearchTerm.isEmpty) return; - if (loading) return; - setState(() => loading = true); - final matrix = Matrix.of(context); - UserSearchResult response; - try { - response = await matrix.client.searchUser(text, limit: 10); - } catch (_) {} - setState(() => loading = false); - if (response?.results?.isEmpty ?? true) return; - setState(() { - foundProfiles = List.from(response.results); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - leading: BackButton(), - title: Text(L10n.of(context).newChat), - elevation: 0, - actions: [ - TextButton( - onPressed: () => AdaptivePageLayout.of(context) - .pushNamedAndRemoveUntilIsFirst('/newgroup'), - child: Text( - L10n.of(context).createNewGroup, - style: TextStyle(color: Theme.of(context).accentColor), - ), - ) - ], - ), - body: MaxWidthBody( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(12.0), - child: Form( - key: _formKey, - child: TextFormField( - controller: controller, - //autofocus: true, - autocorrect: false, - onChanged: (String text) => searchUserWithCoolDown(context), - textInputAction: TextInputAction.go, - onFieldSubmitted: (s) => submitAction(context), - validator: (value) { - if (value.isEmpty) { - return L10n.of(context).pleaseEnterAMatrixIdentifier; - } - final matrix = Matrix.of(context); - final mxid = '@' + controller.text.trim(); - if (mxid == matrix.client.userID) { - return L10n.of(context).youCannotInviteYourself; - } - if (!mxid.contains('@')) { - return L10n.of(context).makeSureTheIdentifierIsValid; - } - if (!mxid.contains(':')) { - return L10n.of(context).makeSureTheIdentifierIsValid; - } - return null; - }, - decoration: InputDecoration( - labelText: L10n.of(context).enterAUsername, - prefixIcon: loading - ? Container( - padding: const EdgeInsets.all(8.0), - width: 12, - height: 12, - child: CircularProgressIndicator(), - ) - : correctMxId - ? Padding( - padding: const EdgeInsets.all(8.0), - child: Avatar( - foundProfile.avatarUrl, - foundProfile.displayname ?? - foundProfile.userId, - size: 12, - ), - ) - : Icon(Icons.account_circle_outlined), - prefixText: '@', - suffixIcon: IconButton( - onPressed: () => submitAction(context), - icon: Icon(Icons.arrow_forward_outlined), - ), - hintText: '${L10n.of(context).username.toLowerCase()}', - ), - ), - ), - ), - Divider(height: 1), - ListTile( - leading: CircleAvatar( - radius: Avatar.defaultSize / 2, - foregroundColor: Theme.of(context).accentColor, - backgroundColor: Theme.of(context).secondaryHeaderColor, - child: Icon(Icons.share_outlined), - ), - onTap: () => FluffyShare.share( - L10n.of(context).inviteText(Matrix.of(context).client.userID, - 'https://matrix.to/#/${Matrix.of(context).client.userID}'), - context), - title: Text('${L10n.of(context).yourOwnUsername}:'), - subtitle: Text( - Matrix.of(context).client.userID, - style: TextStyle(color: Theme.of(context).accentColor), - ), - ), - Divider(height: 1), - if (foundProfiles.isNotEmpty) - Expanded( - child: ListView.builder( - itemCount: foundProfiles.length, - itemBuilder: (BuildContext context, int i) { - final foundProfile = foundProfiles[i]; - return ListTile( - onTap: () { - setState(() { - controller.text = currentSearchTerm = - foundProfile.userId.substring(1); - }); - }, - leading: Avatar( - foundProfile.avatarUrl, - foundProfile.displayname ?? foundProfile.userId, - //size: 24, - ), - title: Text( - foundProfile.displayname ?? - foundProfile.userId.localpart, - style: TextStyle(), - maxLines: 1, - ), - subtitle: Text( - foundProfile.userId, - maxLines: 1, - style: TextStyle( - fontSize: 12, - ), - ), - ); - }, - ), - ), - if (foundProfiles.isEmpty) - Expanded( - child: ContactsList(searchController: controller), - ), - ], - ), - ), - ); - } -} diff --git a/lib/views/new_private_chat_view.dart b/lib/views/new_private_chat_view.dart new file mode 100644 index 00000000..2b57b80a --- /dev/null +++ b/lib/views/new_private_chat_view.dart @@ -0,0 +1,133 @@ +import 'package:adaptive_page_layout/adaptive_page_layout.dart'; +import 'package:fluffychat/controllers/new_private_chat_controller.dart'; +import 'package:fluffychat/views/widgets/avatar.dart'; +import 'package:fluffychat/views/widgets/contacts_list.dart'; +import 'package:fluffychat/views/widgets/max_width_body.dart'; +import 'package:fluffychat/views/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:famedlysdk/famedlysdk.dart'; + +class NewPrivateChatView extends StatelessWidget { + final NewPrivateChatController controller; + + const NewPrivateChatView(this.controller, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: BackButton(), + title: Text(L10n.of(context).newChat), + elevation: 0, + actions: [ + TextButton( + onPressed: () => AdaptivePageLayout.of(context) + .pushNamedAndRemoveUntilIsFirst('/newgroup'), + child: Text( + L10n.of(context).createNewGroup, + style: TextStyle(color: Theme.of(context).accentColor), + ), + ) + ], + ), + body: MaxWidthBody( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(12.0), + child: Form( + key: controller.formKey, + child: TextFormField( + controller: controller.controller, + autocorrect: false, + onChanged: controller.searchUserWithCoolDown, + textInputAction: TextInputAction.go, + onFieldSubmitted: controller.submitAction, + validator: controller.validateForm, + decoration: InputDecoration( + labelText: L10n.of(context).enterAUsername, + prefixIcon: controller.loading + ? Container( + padding: const EdgeInsets.all(8.0), + width: 12, + height: 12, + child: CircularProgressIndicator(), + ) + : controller.correctMxId + ? Padding( + padding: const EdgeInsets.all(8.0), + child: Avatar( + controller.foundProfile.avatarUrl, + controller.foundProfile.displayname ?? + controller.foundProfile.userId, + size: 12, + ), + ) + : Icon(Icons.account_circle_outlined), + prefixText: '@', + suffixIcon: IconButton( + onPressed: controller.submitAction, + icon: Icon(Icons.arrow_forward_outlined), + ), + hintText: '${L10n.of(context).username.toLowerCase()}', + ), + ), + ), + ), + Divider(height: 1), + ListTile( + leading: CircleAvatar( + radius: Avatar.defaultSize / 2, + foregroundColor: Theme.of(context).accentColor, + backgroundColor: Theme.of(context).secondaryHeaderColor, + child: Icon(Icons.share_outlined), + ), + onTap: controller.inviteAction, + title: Text('${L10n.of(context).yourOwnUsername}:'), + subtitle: Text( + Matrix.of(context).client.userID, + style: TextStyle(color: Theme.of(context).accentColor), + ), + ), + Divider(height: 1), + if (controller.foundProfiles.isNotEmpty) + Expanded( + child: ListView.builder( + itemCount: controller.foundProfiles.length, + itemBuilder: (BuildContext context, int i) { + final foundProfile = controller.foundProfiles[i]; + return ListTile( + onTap: () => controller.pickUser(foundProfile), + leading: Avatar( + foundProfile.avatarUrl, + foundProfile.displayname ?? foundProfile.userId, + //size: 24, + ), + title: Text( + foundProfile.displayname ?? + foundProfile.userId.localpart, + style: TextStyle(), + maxLines: 1, + ), + subtitle: Text( + foundProfile.userId, + maxLines: 1, + style: TextStyle( + fontSize: 12, + ), + ), + ); + }, + ), + ), + if (controller.foundProfiles.isEmpty) + Expanded( + child: ContactsList(searchController: controller.controller), + ), + ], + ), + ), + ); + } +}