diff --git a/lib/src/model/contacts_model.dart b/lib/src/model/contacts_model.dart index a6d4157..2d44f68 100644 --- a/lib/src/model/contacts_model.dart +++ b/lib/src/model/contacts_model.dart @@ -96,6 +96,19 @@ class DbContacts extends CvModelBase { ); } + static Future acceptUser(int userId) async { + Map valuesToUpdate = { + columnAccepted: 1, + columnRequested: 0, + }; + await dbProvider.db!.update( + tableName, + valuesToUpdate, + where: "$columnUserId = ?", + whereArgs: [userId], + ); + } + static Future deleteUser(int userId) async { await dbProvider.db!.delete( tableName, diff --git a/lib/src/model/json/message.dart b/lib/src/model/json/message.dart index 70ea9a2..bd5f90b 100644 --- a/lib/src/model/json/message.dart +++ b/lib/src/model/json/message.dart @@ -3,7 +3,14 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:twonly/src/utils/json.dart'; part 'message.g.dart'; -enum MessageKind { textMessage, image, video, contactRequest, rejectRequest } +enum MessageKind { + textMessage, + image, + video, + contactRequest, + rejectRequest, + acceptRequest +} // so _$MessageKindEnumMap gets generated @JsonSerializable() diff --git a/lib/src/model/json/message.g.dart b/lib/src/model/json/message.g.dart index c46c6b3..ee41642 100644 --- a/lib/src/model/json/message.g.dart +++ b/lib/src/model/json/message.g.dart @@ -20,6 +20,7 @@ const _$MessageKindEnumMap = { MessageKind.video: 'video', MessageKind.contactRequest: 'contactRequest', MessageKind.rejectRequest: 'rejectRequest', + MessageKind.acceptRequest: 'acceptRequest', }; Message _$MessageFromJson(Map json) => Message( diff --git a/lib/src/providers/api_provider.dart b/lib/src/providers/api_provider.dart index cf147d7..ffab719 100644 --- a/lib/src/providers/api_provider.dart +++ b/lib/src/providers/api_provider.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'dart:math'; import 'package:fixnum/fixnum.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:logging/logging.dart'; import 'package:twonly/src/model/contacts_model.dart'; @@ -12,7 +11,6 @@ import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client; import 'package:twonly/src/proto/api/client_to_server.pbserver.dart'; import 'package:twonly/src/proto/api/error.pb.dart'; import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; // ignore: library_prefixes @@ -176,6 +174,10 @@ class ApiProvider { DbContacts.deleteUser(fromUserId.toInt()); updateNotifier(); break; + case MessageKind.acceptRequest: + DbContacts.acceptUser(fromUserId.toInt()); + updateNotifier(); + break; default: log.shout("Got unknown MessageKind $message"); } @@ -253,39 +255,6 @@ class ApiProvider { return ClientToServer()..v0 = v0; } - static String getLocalizedString(BuildContext context, ErrorCode code) { - switch (code.toString()) { - case "Unknown": - return AppLocalizations.of(context)!.errorUnknown; - case "BadRequest": - return AppLocalizations.of(context)!.errorBadRequest; - case "TooManyRequests": - return AppLocalizations.of(context)!.errorTooManyRequests; - case "InternalError": - return AppLocalizations.of(context)!.errorInternalError; - case "InvalidInvitationCode": - return AppLocalizations.of(context)!.errorInvalidInvitationCode; - case "UsernameAlreadyTaken": - return AppLocalizations.of(context)!.errorUsernameAlreadyTaken; - case "SignatureNotValid": - return AppLocalizations.of(context)!.errorSignatureNotValid; - case "UsernameNotFound": - return AppLocalizations.of(context)!.errorUsernameNotFound; - case "UsernameNotValid": - return AppLocalizations.of(context)!.errorUsernameNotValid; - case "InvalidPublicKey": - return AppLocalizations.of(context)!.errorInvalidPublicKey; - case "SessionAlreadyAuthenticated": - return AppLocalizations.of(context)!.errorSessionAlreadyAuthenticated; - case "SessionNotAuthenticated": - return AppLocalizations.of(context)!.errorSessionNotAuthenticated; - case "OnlyOneSessionAllowed": - return AppLocalizations.of(context)!.errorOnlyOneSessionAllowed; - default: - return code.toString(); // Fallback for unrecognized keys - } - } - Result _asResult(server.ServerToClient msg) { if (msg.v0.response.hasOk()) { return Result.success(msg.v0.response.ok); diff --git a/lib/src/utils/api.dart b/lib/src/utils/api.dart index 11ac634..0e5bf5d 100644 --- a/lib/src/utils/api.dart +++ b/lib/src/utils/api.dart @@ -64,6 +64,12 @@ Future rejectUserRequest(Int64 userId) async { return encryptAndSendMessage(userId, msg); } +Future acceptUserRequest(Int64 userId) async { + Message msg = + Message(kind: MessageKind.acceptRequest, timestamp: DateTime.now()); + return encryptAndSendMessage(userId, msg); +} + Future createNewUser(String username, String inviteCode) async { final storage = getSecureStorage(); diff --git a/lib/src/utils/misc.dart b/lib/src/utils/misc.dart index cf959e7..0fbd9a3 100644 --- a/lib/src/utils/misc.dart +++ b/lib/src/utils/misc.dart @@ -1,10 +1,13 @@ import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; +import 'package:flutter/material.dart'; import 'package:gal/gal.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:twonly/src/proto/api/error.pb.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; Future writeLogToFile(LogRecord record) async { final directory = await getApplicationDocumentsDirectory(); @@ -49,3 +52,36 @@ Uint8List getRandomUint8List(int length) { return randomBytes; } + +String errorCodeToText(BuildContext context, ErrorCode code) { + switch (code.toString()) { + case "Unknown": + return AppLocalizations.of(context)!.errorUnknown; + case "BadRequest": + return AppLocalizations.of(context)!.errorBadRequest; + case "TooManyRequests": + return AppLocalizations.of(context)!.errorTooManyRequests; + case "InternalError": + return AppLocalizations.of(context)!.errorInternalError; + case "InvalidInvitationCode": + return AppLocalizations.of(context)!.errorInvalidInvitationCode; + case "UsernameAlreadyTaken": + return AppLocalizations.of(context)!.errorUsernameAlreadyTaken; + case "SignatureNotValid": + return AppLocalizations.of(context)!.errorSignatureNotValid; + case "UsernameNotFound": + return AppLocalizations.of(context)!.errorUsernameNotFound; + case "UsernameNotValid": + return AppLocalizations.of(context)!.errorUsernameNotValid; + case "InvalidPublicKey": + return AppLocalizations.of(context)!.errorInvalidPublicKey; + case "SessionAlreadyAuthenticated": + return AppLocalizations.of(context)!.errorSessionAlreadyAuthenticated; + case "SessionNotAuthenticated": + return AppLocalizations.of(context)!.errorSessionNotAuthenticated; + case "OnlyOneSessionAllowed": + return AppLocalizations.of(context)!.errorOnlyOneSessionAllowed; + default: + return code.toString(); // Fallback for unrecognized keys + } +} diff --git a/lib/src/views/new_message_view.dart b/lib/src/views/new_message_view.dart index cf00ba1..69498bb 100644 --- a/lib/src/views/new_message_view.dart +++ b/lib/src/views/new_message_view.dart @@ -24,7 +24,8 @@ class _NewMessageView extends State { } Future _loadUsers() async { - final users = await DbContacts.getUsers(); + final users = + (await DbContacts.getUsers()).where((c) => c.accepted).toList(); setState(() { _knownUsers = users; _filteredUsers = List.from(_knownUsers); diff --git a/lib/src/views/register_view.dart b/lib/src/views/register_view.dart index efd8e01..cfe0d28 100644 --- a/lib/src/views/register_view.dart +++ b/lib/src/views/register_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:twonly/src/utils/api.dart'; +import 'package:twonly/src/utils/misc.dart'; class RegisterView extends StatefulWidget { const RegisterView({super.key, required this.callbackOnSuccess}); @@ -131,8 +132,7 @@ class _RegisterViewState extends State { return; } if (context.mounted) { - final errMsg = - ApiProvider.getLocalizedString(context, res.error); + final errMsg = errorCodeToText(context, res.error); showAlertDialog(context, "Oh no!", errMsg); } }, diff --git a/lib/src/views/search_username_view.dart b/lib/src/views/search_username_view.dart index 14b972c..2d84d68 100644 --- a/lib/src/views/search_username_view.dart +++ b/lib/src/views/search_username_view.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/model/contacts_model.dart'; @@ -93,7 +92,11 @@ class _SearchUsernameView extends State { Text(AppLocalizations.of(context)!.searchUsernameQrCodeBtn), ), SizedBox(height: 30), - if (context.read().allContacts.isNotEmpty) + if (context + .read() + .allContacts + .where((contact) => !contact.accepted) + .isNotEmpty) Container( alignment: Alignment.centerLeft, padding: EdgeInsets.symmetric(horizontal: 4.0, vertical: 10), @@ -131,7 +134,11 @@ class ContactsListView extends StatefulWidget { class _ContactsListViewState extends State { @override Widget build(BuildContext context) { - List contacts = context.read().allContacts; + List contacts = context + .read() + .allContacts + .where((contact) => !contact.accepted) + .toList(); return ListView.builder( itemCount: contacts.length, itemBuilder: (context, index) { @@ -172,9 +179,12 @@ class _ContactsListViewState extends State { ), IconButton( icon: Icon(Icons.check, color: Colors.green), - onPressed: () { - // Handle accept action - print('Accepted ${contact.displayName}'); + onPressed: () async { + await DbContacts.acceptUser(contact.userId.toInt()); + if (context.mounted) { + context.read().update(); + } + acceptUserRequest(contact.userId); }, ), ],