diff --git a/lib/app.dart b/lib/app.dart index c460bede..556759d9 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; @@ -115,7 +116,7 @@ class _AppMainWidgetState extends State { bool _isUserCreated = false; bool _showOnboarding = true; bool _isLoaded = false; - bool _skipBackup = false; + bool _skipBackup = kDebugMode; bool _isTwonlyLocked = true; (Future?, bool) _proofOfWork = (null, false); diff --git a/lib/src/providers/routing.provider.dart b/lib/src/providers/routing.provider.dart index d7138010..d7c83b3a 100644 --- a/lib/src/providers/routing.provider.dart +++ b/lib/src/providers/routing.provider.dart @@ -4,11 +4,11 @@ import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/visual/views/camera/camera_qr_scanner.view.dart'; import 'package:twonly/src/visual/views/camera/camera_send_to.view.dart'; -import 'package:twonly/src/visual/views/chats/add_new_user.view.dart'; import 'package:twonly/src/visual/views/chats/archived_chats.view.dart'; import 'package:twonly/src/visual/views/chats/chat_messages.view.dart'; import 'package:twonly/src/visual/views/chats/media_viewer.view.dart'; import 'package:twonly/src/visual/views/chats/start_new_chat.view.dart'; +import 'package:twonly/src/visual/views/contact/add_new_contact.view.dart'; import 'package:twonly/src/visual/views/contact/contact.view.dart'; import 'package:twonly/src/visual/views/groups/group.view.dart'; import 'package:twonly/src/visual/views/groups/group_create_select_members.view.dart'; diff --git a/lib/src/services/intent/links.intent.dart b/lib/src/services/intent/links.intent.dart index 193bb85d..39fa5a65 100644 --- a/lib/src/services/intent/links.intent.dart +++ b/lib/src/services/intent/links.intent.dart @@ -19,7 +19,8 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.utils.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; import 'package:twonly/src/visual/views/camera/share_image_editor.view.dart'; -import 'package:twonly/src/visual/views/chats/add_new_user.view.dart'; +import 'package:twonly/src/visual/views/contact/add_contact_via_qr_link.view.dart'; +import 'package:twonly/src/visual/views/contact/add_new_contact.view.dart'; Future handleIntentUrl(BuildContext context, Uri uri) async { if (!uri.scheme.startsWith('http')) return false; @@ -50,9 +51,9 @@ Future handleIntentUrl(BuildContext context, Uri uri) async { } } else { await context.navPush( - AddNewUserView( - username: profile.username, - publicKey: Uint8List.fromList(profile.publicIdentityKey), + AddContactViaQrLinkView( + profile: profile, + qrCodeLink: uri.toString(), ), ); } diff --git a/lib/src/utils/qr.utils.dart b/lib/src/utils/qr.utils.dart index 2f418ec5..c4f6f389 100644 --- a/lib/src/utils/qr.utils.dart +++ b/lib/src/utils/qr.utils.dart @@ -78,7 +78,7 @@ class QrCodeUtils { profile.userId.toInt(), ); - if (contact == null || !contact.accepted) { + if (contact == null) { if (profile.username == userService.currentUser.username) { return null; } diff --git a/lib/src/visual/views/contact/add_contact_via_qr_link.view.dart b/lib/src/visual/views/contact/add_contact_via_qr_link.view.dart new file mode 100644 index 00000000..cd23bc7e --- /dev/null +++ b/lib/src/visual/views/contact/add_contact_via_qr_link.view.dart @@ -0,0 +1,154 @@ +import 'dart:convert'; + +import 'package:drift/drift.dart' hide Column; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart' + as server; +import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart'; +import 'package:twonly/src/services/api/utils.api.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/utils/qr.utils.dart'; + +class AddContactViaQrLinkView extends StatefulWidget { + const AddContactViaQrLinkView({ + required this.profile, + this.qrCodeLink, + super.key, + }); + + final PublicProfile profile; + final String? qrCodeLink; + + @override + State createState() => + _AddContactViaQrLinkViewState(); +} + +class _AddContactViaQrLinkViewState extends State { + bool _isLoading = false; + + Future _sendFollowRequest() async { + setState(() { + _isLoading = true; + }); + + try { + final userData = server.Response_UserData( + userId: widget.profile.userId, + publicIdentityKey: widget.profile.publicIdentityKey, + signedPrekey: widget.profile.signedPrekey, + signedPrekeySignature: widget.profile.signedPrekeySignature, + signedPrekeyId: widget.profile.signedPrekeyId, + username: utf8.encode(widget.profile.username), + registrationId: widget.profile.registrationId, + ); + + final added = await twonlyDB.contactsDao.insertOnConflictUpdate( + ContactsCompanion( + username: Value(widget.profile.username), + userId: Value(widget.profile.userId.toInt()), + requested: const Value(false), + blocked: const Value(false), + deletedByUser: const Value(false), + ), + ); + + if (added > 0) { + await importSignalContactAndCreateRequest(userData); + if (widget.qrCodeLink != null) { + // As the user does now exist he can now be marked as verified + await QrCodeUtils.handleQrCodeLink(widget.qrCodeLink!); + } + } + + if (mounted) { + context.pop(); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e')), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.lang.addFriendTitle), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + CircleAvatar( + radius: 50, + backgroundColor: context.color.primaryContainer, + child: FaIcon( + FontAwesomeIcons.user, + size: 40, + color: context.color.onPrimaryContainer, + ), + ), + const SizedBox(height: 20), + Text( + widget.profile.username, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + color: context.color.onSurface, + ), + ), + const SizedBox(height: 10), + Text( + context.lang.userFoundBody, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: context.color.onSurfaceVariant, + ), + ), + const Spacer(), + const SizedBox(width: 16), + Center( + child: FilledButton( + onPressed: _isLoading ? null : _sendFollowRequest, + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : Text(context.lang.createContactRequest), + ), + ), + const SizedBox(height: 8), + Center( + child: OutlinedButton( + onPressed: _isLoading ? null : () => context.pop(), + child: Text(context.lang.cancel), + ), + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/visual/views/chats/add_new_user.view.dart b/lib/src/visual/views/contact/add_new_contact.view.dart similarity index 97% rename from lib/src/visual/views/chats/add_new_user.view.dart rename to lib/src/visual/views/contact/add_new_contact.view.dart index 31df9348..6808ce0c 100644 --- a/lib/src/visual/views/chats/add_new_user.view.dart +++ b/lib/src/visual/views/contact/add_new_contact.view.dart @@ -14,8 +14,8 @@ import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; -import 'package:twonly/src/visual/views/chats/add_new_user_components/friend_suggestions.comp.dart'; -import 'package:twonly/src/visual/views/chats/add_new_user_components/open_requests_list.comp.dart'; +import 'package:twonly/src/visual/views/contact/add_new_contact_components/friend_suggestions.comp.dart'; +import 'package:twonly/src/visual/views/contact/add_new_contact_components/open_requests_list.comp.dart'; class AddNewUserView extends StatefulWidget { const AddNewUserView({ diff --git a/lib/src/visual/views/chats/add_new_user_components/friend_suggestions.comp.dart b/lib/src/visual/views/contact/add_new_contact_components/friend_suggestions.comp.dart similarity index 100% rename from lib/src/visual/views/chats/add_new_user_components/friend_suggestions.comp.dart rename to lib/src/visual/views/contact/add_new_contact_components/friend_suggestions.comp.dart diff --git a/lib/src/visual/views/chats/add_new_user_components/open_requests_list.comp.dart b/lib/src/visual/views/contact/add_new_contact_components/open_requests_list.comp.dart similarity index 98% rename from lib/src/visual/views/chats/add_new_user_components/open_requests_list.comp.dart rename to lib/src/visual/views/contact/add_new_contact_components/open_requests_list.comp.dart index 8fd9a743..0d7ecf93 100644 --- a/lib/src/visual/views/chats/add_new_user_components/open_requests_list.comp.dart +++ b/lib/src/visual/views/contact/add_new_contact_components/open_requests_list.comp.dart @@ -11,7 +11,7 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; import 'package:twonly/src/visual/elements/headline.element.dart'; import 'package:twonly/src/visual/themes/light.dart'; -import 'package:twonly/src/visual/views/chats/add_new_user_components/friend_suggestions.comp.dart'; +import 'package:twonly/src/visual/views/contact/add_new_contact_components/friend_suggestions.comp.dart'; class OpenRequestsListComp extends StatelessWidget { const OpenRequestsListComp({ diff --git a/lib/src/visual/views/contact/contact.view.dart b/lib/src/visual/views/contact/contact.view.dart index 745441ef..0a6f51a2 100644 --- a/lib/src/visual/views/contact/contact.view.dart +++ b/lib/src/visual/views/contact/contact.view.dart @@ -18,7 +18,7 @@ import 'package:twonly/src/visual/components/flame_counter.comp.dart'; import 'package:twonly/src/visual/components/select_chat_deletion_time.comp.dart'; import 'package:twonly/src/visual/components/verification_badge.comp.dart'; import 'package:twonly/src/visual/elements/better_list_title.element.dart'; -import 'package:twonly/src/visual/views/contact/components/restore_flame.comp.dart'; +import 'package:twonly/src/visual/views/contact/contact_components/restore_flame.comp.dart'; import 'package:twonly/src/visual/views/groups/group.view.dart'; class ContactView extends StatefulWidget { diff --git a/lib/src/visual/views/contact/components/restore_flame.comp.dart b/lib/src/visual/views/contact/contact_components/restore_flame.comp.dart similarity index 100% rename from lib/src/visual/views/contact/components/restore_flame.comp.dart rename to lib/src/visual/views/contact/contact_components/restore_flame.comp.dart diff --git a/lib/src/visual/views/home.view.dart b/lib/src/visual/views/home.view.dart index 73a6fbf1..0db147ca 100644 --- a/lib/src/visual/views/home.view.dart +++ b/lib/src/visual/views/home.view.dart @@ -70,6 +70,13 @@ class HomeViewState extends State { unawaited(_mainCameraController.selectCamera(0, true)); unawaited(_initAsync()); + handleIntentUrl( + context, + Uri.parse( + 'https://me.twonly.eu/qr/#EAAauAEIgLDN0Nm7oKh0EghoYWhoaGhoaBohBRZQ8w_zpm1v7SRTdc8GEOMAxuf1caGDlBa-v0ZiTw9qIiEF05juEs1c3yw0STiSwQR7lowDX5hBaxN4YFR0HhkopGIoudTO5wIyQFQRtU1aO7P7O5s2ekB1ppAost3iQQizwhFObjOLgHQnpwcnwEONXZzSADYqCeEoNcvyE45w0v21z1Imhozk3Q44oI0GQhA9U_chIJwwZ7J9fpeXODZF', + ), + ); + // Subscribe to all events (initial link and further) _deepLinkSub = AppLinks().uriLinkStream.listen((uri) async { if (!mounted) return;