From b5c3d7b1e3d8cd6b812da4c7f900d7828a4e4881 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Sun, 22 Aug 2021 21:09:05 +0200 Subject: [PATCH] feat: New design for new chat page --- assets/l10n/intl_en.arb | 4 + lib/pages/new_private_chat.dart | 103 +++++---------- lib/pages/views/new_private_chat_view.dart | 138 +++++++++------------ pubspec.lock | 14 +++ pubspec.yaml | 1 + 5 files changed, 113 insertions(+), 147 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 62677b58..84848a41 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -1428,6 +1428,10 @@ "server2": {} } }, + "createNewChatExplaination": "Just scan the QR code or share your invite link if you are not next to each other.", + "shareYourInviteLink": "Share your invite link", + "typeInInviteLinkManually": "Type in invite link manually...", + "scanQrCode": "Scan QR code", "noMegolmBootstrap": "Please turn on online key backup from within Element instead.", "@noMegolmBootstrap": { "type": "text", diff --git a/lib/pages/new_private_chat.dart b/lib/pages/new_private_chat.dart index 29291577..975fed9a 100644 --- a/lib/pages/new_private_chat.dart +++ b/lib/pages/new_private_chat.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:matrix/matrix.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/pages/views/new_private_chat_view.dart'; @@ -18,84 +16,56 @@ 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; + + static const Set supportedSigils = {'@', '!', '#'}; void submitAction([_]) async { - controller.text = controller.text.replaceAll('@', '').trim(); - if (controller.text.isEmpty) return; + controller.text = controller.text.trim(); if (!formKey.currentState.validate()) return; - final matrix = Matrix.of(context); + final client = Matrix.of(context).client; - if ('@' + controller.text == matrix.client.userID) return; + LoadingDialogResult roomIdResult; - final user = User( - '@' + controller.text, - room: Room(id: '', client: matrix.client), - ); - final roomID = await showFutureLoadingDialog( - context: context, - future: () => user.startDirectChat(), - ); - - if (roomID.error == null) { - VRouter.of(context).toSegments(['rooms', roomID.result]); + switch (controller.text.sigil) { + case '@': + roomIdResult = await showFutureLoadingDialog( + context: context, + future: () => client.startDirectChat(controller.text), + ); + break; + case '#': + case '!': + roomIdResult = await showFutureLoadingDialog( + context: context, + future: () async { + final roomId = await client.joinRoom(controller.text); + if (client.getRoomById(roomId) == null) { + await client.onSync.stream + .where((s) => s.rooms.join.containsKey(roomId)) + .first; + } + return roomId; + }, + ); + break; } - } - void searchUserWithCoolDown([_]) async { - coolDown?.cancel(); - coolDown = Timer( - Duration(milliseconds: 500), - () => searchUser(controller.text), - ); - } - - void searchUser(String text) async { - if (text.isEmpty) { - setState(() { - foundProfiles = []; - }); + if (roomIdResult.error == null) { + VRouter.of(context).toSegments(['rooms', roomIdResult.result]); } - currentSearchTerm = text; - if (currentSearchTerm.isEmpty) return; - if (loading) return; - setState(() => loading = true); - final matrix = Matrix.of(context); - SearchUserDirectoryResponse response; - try { - response = await matrix.client.searchUserDirectory(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) { + if (!controller.text.isValidMatrixId || + !supportedSigils.contains(controller.text.sigil)) { + return L10n.of(context).makeSureTheIdentifierIsValid; + } + if (controller.text == Matrix.of(context).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; } @@ -105,11 +75,6 @@ class NewPrivateChatController extends State { context, ); - void pickUser(Profile foundProfile) => setState( - () => controller.text = - currentSearchTerm = foundProfile.userId.substring(1), - ); - @override Widget build(BuildContext context) => NewPrivateChatView(this); } diff --git a/lib/pages/views/new_private_chat_view.dart b/lib/pages/views/new_private_chat_view.dart index e7787663..9465cfe3 100644 --- a/lib/pages/views/new_private_chat_view.dart +++ b/lib/pages/views/new_private_chat_view.dart @@ -1,11 +1,12 @@ +import 'dart:math'; + +import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/new_private_chat.dart'; -import 'package:fluffychat/widgets/avatar.dart'; -import 'package:fluffychat/widgets/contacts_list.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; +import 'package:qr_flutter/qr_flutter.dart'; import 'package:vrouter/vrouter.dart'; class NewPrivateChatView extends StatelessWidget { @@ -13,8 +14,12 @@ class NewPrivateChatView extends StatelessWidget { const NewPrivateChatView(this.controller, {Key key}) : super(key: key); + static const double _qrCodePadding = 8; + @override Widget build(BuildContext context) { + final qrCodeSize = min(AppConfig.columnWidth - _qrCodePadding * 6, + MediaQuery.of(context).size.width - _qrCodePadding * 6); return Scaffold( appBar: AppBar( leading: BackButton(), @@ -31,103 +36,80 @@ class NewPrivateChatView extends StatelessWidget { ], ), body: MaxWidthBody( - child: Column( - children: [ + child: ListView( + children: [ Padding( - padding: const EdgeInsets.all(12.0), + padding: const EdgeInsets.symmetric( + horizontal: _qrCodePadding * 2, + ), + child: Text( + L10n.of(context).createNewChatExplaination, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + ), + Container( + margin: EdgeInsets.all(_qrCodePadding), + alignment: Alignment.center, + padding: EdgeInsets.all(_qrCodePadding * 2), + child: Material( + borderRadius: BorderRadius.circular(12), + elevation: 4, + child: QrImage( + data: + 'https://matrix.to/#/${Matrix.of(context).client.userID}', + version: QrVersions.auto, + size: qrCodeSize, + ), + ), + ), + Divider(), + ListTile( + title: Text(L10n.of(context).shareYourInviteLink), + trailing: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon(Icons.share_outlined), + ), + onTap: controller.inviteAction, + ), + Divider(), + Padding( + padding: EdgeInsets.all(12), 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: '@', + labelText: L10n.of(context).typeInInviteLinkManually, + hintText: '@username', + prefixText: 'https://matrix.to/#/', suffixIcon: IconButton( + icon: Icon(Icons.send_outlined), 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).colorScheme.secondary, - 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).colorScheme.secondary), + Center( + child: Image.asset( + 'assets/private_chat_wallpaper.png', + width: qrCodeSize, + height: qrCodeSize, ), ), - 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), - ), ], ), ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () {}, + label: Text(L10n.of(context).scanQrCode), + icon: Icon(Icons.camera_alt_outlined), + ), ); } } diff --git a/pubspec.lock b/pubspec.lock index e6c109cc..390d8525 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1012,6 +1012,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + qr: + dependency: transitive + description: + name: qr + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" quiver: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 68e600b9..db0f3f5a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,6 +56,7 @@ dependencies: pin_code_text_field: ^1.8.0 provider: ^5.0.0 punycode: ^1.0.0 + qr_flutter: ^4.0.0 receive_sharing_intent: ^1.4.5 record: ^3.0.0 scroll_to_index: ^2.0.0