diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 39414f4f820..7d75840ee01 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -14,6 +14,7 @@
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 4e0f1ae15a6..5f94c9bdd87 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -58,9 +58,11 @@
We need access to your microphone to record it
LSSupportsOpeningDocumentsInPlace
- CADisableMinimumFrameDurationOnPhone
-
- UIApplicationSupportsIndirectInputEvents
-
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+ NSContactsUsageDescription
+ We need access to your contacts to import them.
diff --git a/lib/domain/model/application_settings.dart b/lib/domain/model/application_settings.dart
index 22f84743ca9..d76831ff702 100644
--- a/lib/domain/model/application_settings.dart
+++ b/lib/domain/model/application_settings.dart
@@ -81,6 +81,10 @@ class ApplicationSettings extends HiveObject {
/// [CustomNavigationBar] of [HomeView].
@HiveField(9)
bool workWithUsTabEnabled;
+
+ /// Indicator whether contacts from device's contacts book was imported.
+ @HiveField(10)
+ bool? contactsImported;
}
/// Possible call buttons position.
diff --git a/lib/domain/repository/contact.dart b/lib/domain/repository/contact.dart
index fab36f630a0..1a557239c70 100644
--- a/lib/domain/repository/contact.dart
+++ b/lib/domain/repository/contact.dart
@@ -52,9 +52,14 @@ abstract class AbstractContactRepository {
/// Clears the stored [paginated].
Future clearCache();
- /// Creates a new [ChatContact] with the specified [User] in the current
- /// [MyUser]'s address book.
- Future createChatContact(UserName name, UserId id);
+ /// Creates a new [ChatContact] with the specified [User], [UserPhone]s and
+ /// [UserEmail]s in the current [MyUser]'s address book.
+ Future createChatContact(
+ UserName name, {
+ UserId? userId,
+ List emails = const [],
+ List phones = const [],
+ });
/// Deletes the specified [ChatContact] from the authenticated [MyUser]'s
/// address book.
diff --git a/lib/domain/repository/settings.dart b/lib/domain/repository/settings.dart
index cbcd77172c1..c006e669a06 100644
--- a/lib/domain/repository/settings.dart
+++ b/lib/domain/repository/settings.dart
@@ -87,4 +87,7 @@ abstract class AbstractSettingsRepository {
/// Sets the [ApplicationSettings.workWithUsTabEnabled] value.
Future setWorkWithUsTabEnabled(bool enabled);
+
+ /// Sets the [ApplicationSettings.contactsImported] value.
+ Future setContactsImported(bool val);
}
diff --git a/lib/domain/service/contact.dart b/lib/domain/service/contact.dart
index 3ba74ac231f..7f634e8dae0 100644
--- a/lib/domain/service/contact.dart
+++ b/lib/domain/service/contact.dart
@@ -17,23 +17,34 @@
import 'dart:async';
+import 'package:device_region/device_region.dart';
+import 'package:fast_contacts/fast_contacts.dart';
import 'package:get/get.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:phone_numbers_parser/phone_numbers_parser.dart';
+import '/domain/model/application_settings.dart';
import '/domain/model/contact.dart';
import '/domain/model/user.dart';
import '/domain/repository/contact.dart';
import '/domain/repository/paginated.dart';
+import '/domain/repository/settings.dart';
import '/util/log.dart';
import '/util/obs/obs.dart';
+import '/util/permission.dart';
+import '/util/platform_utils.dart';
import 'disposable_service.dart';
/// Service responsible for [ChatContact]s related functionality.
class ContactService extends DisposableService {
- ContactService(this._contactRepository);
+ ContactService(this._contactRepository, this._settingsRepository);
/// Repository to fetch [ChatContact]s from.
final AbstractContactRepository _contactRepository;
+ /// Settings repository updating the [ApplicationSettings.contactsImported].
+ final AbstractSettingsRepository _settingsRepository;
+
/// Returns the [RxStatus] of the [paginated] initialization.
Rx get status => _contactRepository.status;
@@ -51,6 +62,20 @@ class ContactService extends DisposableService {
RxObsMap get contacts =>
_contactRepository.contacts;
+ @override
+ void onInit() {
+ Log.debug('onInit()', '$runtimeType');
+
+ if (PlatformUtils.isMobile &&
+ !PlatformUtils.isWeb &&
+ _settingsRepository.applicationSettings.value?.contactsImported !=
+ true) {
+ _importContacts();
+ }
+
+ super.onInit();
+ }
+
/// Fetches the next [paginated] page.
FutureOr next() {
Log.debug('next()', '$runtimeType');
@@ -63,7 +88,7 @@ class ContactService extends DisposableService {
return _contactRepository.createChatContact(
user.name ?? UserName(user.num.toString()),
- user.id,
+ userId: user.id,
);
}
@@ -112,4 +137,85 @@ class ContactService extends DisposableService {
phone: phone,
);
}
+
+ /// Imports contacts from the device's contact list.
+ Future _importContacts() async {
+ Log.debug('_importContacts()', '$runtimeType');
+
+ PermissionStatus status = await Permission.contacts.status;
+
+ if (status.isPermanentlyDenied || status.isRestricted) {
+ return;
+ }
+
+ if (!status.isGranted) {
+ status = await PermissionUtils.contacts();
+
+ if (!status.isGranted) {
+ return;
+ }
+ }
+
+ final List futures = [];
+ final List contacts = await FastContacts.getAllContacts();
+
+ IsoCode? isoCode;
+ final String? countryCode = await DeviceRegion.getSIMCountryCode();
+ if (countryCode != null) {
+ isoCode = IsoCode.fromJson(countryCode.toUpperCase());
+ }
+
+ for (final Contact contact in contacts) {
+ final List phones = [];
+ final List emails = [];
+
+ for (var e in contact.phones) {
+ try {
+ final PhoneNumber phone =
+ PhoneNumber.parse(e.number, callerCountry: isoCode);
+
+ if (!phone.isValid(type: PhoneNumberType.mobile)) {
+ throw const FormatException('Not valid');
+ }
+
+ phones.add(UserPhone('+${phone.countryCode}${phone.nsn}'));
+ } catch (ex) {
+ Log.warning(
+ 'Failed to parse ${e.number} into UserPhone with $ex',
+ '$runtimeType',
+ );
+ }
+ }
+
+ for (var e in contact.emails) {
+ try {
+ emails.add(UserEmail(e.address));
+ } catch (ex) {
+ Log.warning(
+ 'Failed to parse ${e.address} into UserEmail with $ex',
+ '$runtimeType',
+ );
+ }
+ }
+
+ futures.add(
+ Future(() async {
+ try {
+ if (phones.isNotEmpty || emails.isNotEmpty) {
+ await _contactRepository.createChatContact(
+ UserName(contact.displayName.padRight(2, '_')),
+ phones: phones,
+ emails: emails,
+ );
+ }
+ } catch (_) {
+ // No-op.
+ }
+ }),
+ );
+ }
+
+ await Future.wait(futures);
+ await _settingsRepository.setContactsImported(true);
+ }
}
diff --git a/lib/domain/service/notification.dart b/lib/domain/service/notification.dart
index d5043d871cc..8c4d9e5e87e 100644
--- a/lib/domain/service/notification.dart
+++ b/lib/domain/service/notification.dart
@@ -36,6 +36,7 @@ import '/ui/worker/cache.dart';
import '/util/android_utils.dart';
import '/util/audio_utils.dart';
import '/util/log.dart';
+import '/util/permission.dart';
import '/util/platform_utils.dart';
import '/util/web/web_utils.dart';
import 'disposable_service.dart';
@@ -445,16 +446,14 @@ class NotificationService extends DisposableService {
Log.error(e.toString(), '$runtimeType');
}
}
-
- NotificationSettings settings =
- await FirebaseMessaging.instance.requestPermission();
+ NotificationSettings settings = await PermissionUtils.notifications();
// On Android first attempt is always [AuthorizationStatus.denied] due to
// notifications request popping while invoking a
// [AndroidUtils.createNotificationChannel], so try again on failure.
if (PlatformUtils.isAndroid &&
settings.authorizationStatus != AuthorizationStatus.authorized) {
- settings = await FirebaseMessaging.instance.requestPermission();
+ settings = await PermissionUtils.notifications();
}
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
diff --git a/lib/provider/hive/application_settings.dart b/lib/provider/hive/application_settings.dart
index 11632c2f793..cfc6379680a 100644
--- a/lib/provider/hive/application_settings.dart
+++ b/lib/provider/hive/application_settings.dart
@@ -150,4 +150,13 @@ class ApplicationSettingsHiveProvider
(box.get(0) ?? ApplicationSettings())..workWithUsTabEnabled = enabled,
);
}
+
+ /// Stores a new [ApplicationSettings.contactsImported] to [Hive].
+ Future setContactsImported(bool val) async {
+ Log.trace('setContactsImported($val)', '$runtimeType');
+ await putSafe(
+ 0,
+ (box.get(0) ?? ApplicationSettings())..contactsImported = val,
+ );
+ }
}
diff --git a/lib/provider/hive/session_data.dart b/lib/provider/hive/session_data.dart
index 6fa2e3b8d83..8d8cf40229b 100644
--- a/lib/provider/hive/session_data.dart
+++ b/lib/provider/hive/session_data.dart
@@ -125,7 +125,7 @@ class SessionDataHiveProvider extends HiveBaseProvider {
/// Stores a new [SessionData.blocklistSynchronized] to [Hive].
Future setBlocklistSynchronized(bool val) async {
- Log.trace('setBlocklistSynchronized()', '$runtimeType');
+ Log.trace('setBlocklistSynchronized($val)', '$runtimeType');
await putSafe(
0,
(box.get(0) ?? SessionData())..blocklistSynchronized = val,
diff --git a/lib/routes.dart b/lib/routes.dart
index 3c00d533418..6d1ec5b138d 100644
--- a/lib/routes.dart
+++ b/lib/routes.dart
@@ -593,7 +593,7 @@ class AppRouterDelegate extends RouterDelegate
deps.put(MyUserService(Get.find(), myUserRepository));
deps.put(UserService(userRepository));
- deps.put(ContactService(contactRepository));
+ deps.put(ContactService(contactRepository, settingsRepository));
ChatService chatService =
deps.put(ChatService(chatRepository, Get.find()));
deps.put(CallService(
@@ -747,7 +747,7 @@ class AppRouterDelegate extends RouterDelegate
MyUserService myUserService =
deps.put(MyUserService(Get.find(), myUserRepository));
deps.put(UserService(userRepository));
- deps.put(ContactService(contactRepository));
+ deps.put(ContactService(contactRepository, settingsRepository));
ChatService chatService =
deps.put(ChatService(chatRepository, Get.find()));
CallService callService = deps.put(CallService(
diff --git a/lib/store/contact.dart b/lib/store/contact.dart
index 4a4b415dcf0..9f62de47e22 100644
--- a/lib/store/contact.dart
+++ b/lib/store/contact.dart
@@ -159,12 +159,24 @@ class ContactRepository extends DisposableInterface
// TODO: Forbid creating multiple ChatContacts with the same User?
@override
- Future createChatContact(UserName name, UserId id) async {
- Log.debug('createChatContact($name, $id)', '$runtimeType');
+ Future createChatContact(
+ UserName name, {
+ UserId? userId,
+ List emails = const [],
+ List phones = const [],
+ }) async {
+ Log.debug(
+ 'createChatContact($name, $userId, $emails, $phones)',
+ '$runtimeType',
+ );
final response = await _graphQlProvider.createChatContact(
name: name,
- records: [ChatContactRecord(userId: id)],
+ records: [
+ if (userId != null) ChatContactRecord(userId: userId),
+ ...emails.map((e) => ChatContactRecord(email: e)),
+ ...phones.map((e) => ChatContactRecord(phone: e)),
+ ],
);
final events = ChatContactsEventsEvent(
diff --git a/lib/store/settings.dart b/lib/store/settings.dart
index 087ab3c8c81..d6cee65a394 100644
--- a/lib/store/settings.dart
+++ b/lib/store/settings.dart
@@ -202,6 +202,12 @@ class SettingsRepository extends DisposableInterface
await _settingsLocal.setWorkWithUsTabEnabled(enabled);
}
+ @override
+ Future setContactsImported(bool val) async {
+ Log.debug('setContactsImported($val)', '$runtimeType');
+ await _settingsLocal.setContactsImported(val);
+ }
+
/// Initializes [MediaSettingsHiveProvider.boxEvents] subscription.
Future _initMediaSubscription() async {
Log.debug('_initMediaSubscription()', '$runtimeType');
diff --git a/lib/ui/page/call/search/controller.dart b/lib/ui/page/call/search/controller.dart
index 688722ab045..32f0af42253 100644
--- a/lib/ui/page/call/search/controller.dart
+++ b/lib/ui/page/call/search/controller.dart
@@ -585,11 +585,11 @@ class SearchController extends GetxController {
// Predicates to filter the [allContacts] by.
bool isMember(RxChatContact c) =>
- chat?.members.items.containsKey(c.user.value!.id) ?? false;
- bool inRecent(RxChatContact c) => recent.containsKey(c.user.value!.id);
+ chat?.members.items.containsKey(c.user.value?.id) ?? false;
+ bool inRecent(RxChatContact c) => recent.containsKey(c.user.value?.id);
bool inChats(RxChatContact c) => chats.values.any((chat) =>
chat.chat.value.isDialog &&
- chat.members.items.containsKey(c.user.value!.id));
+ chat.members.items.containsKey(c.user.value?.id));
bool matchesQuery(RxChatContact c) => _matchesQuery(user: c.user.value);
final List filtered = allContacts
diff --git a/lib/ui/page/home/tab/chats/view.dart b/lib/ui/page/home/tab/chats/view.dart
index 4d2a4167a54..a3f1b715322 100644
--- a/lib/ui/page/home/tab/chats/view.dart
+++ b/lib/ui/page/home/tab/chats/view.dart
@@ -308,6 +308,8 @@ class ChatsTabView extends StatelessWidget {
c.search.value?.search.clear();
c.search.value?.query.value = '';
c.search.value?.search.focus.requestFocus();
+ } else {
+ c.closeSearch(true);
}
} else if (c.selecting.value) {
c.toggleSelecting();
diff --git a/lib/ui/page/home/tab/contacts/controller.dart b/lib/ui/page/home/tab/contacts/controller.dart
index 0c533922e77..c70d9406474 100644
--- a/lib/ui/page/home/tab/contacts/controller.dart
+++ b/lib/ui/page/home/tab/contacts/controller.dart
@@ -19,6 +19,7 @@ import 'dart:async';
import 'package:async/async.dart';
import 'package:back_button_interceptor/back_button_interceptor.dart';
+import 'package:collection/collection.dart';
import 'package:flutter/material.dart' hide SearchController;
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
@@ -140,6 +141,7 @@ class ContactsTabController extends GetxController {
scrollController.addListener(_scrollListener);
contacts.value = _contactService.paginated.values
+ .where((e) => e.contact.value.users.isNotEmpty)
.map((e) => ContactEntry(e))
.toList()
..sort();
@@ -427,10 +429,12 @@ class ContactsTabController extends GetxController {
_contactsSubscription = _contactService.paginated.changes.listen((e) {
switch (e.op) {
case OperationKind.added:
- final entry = ContactEntry(e.value!);
- contacts.add(entry);
- contacts.sort();
- listen(entry);
+ if (e.value!.contact.value.users.isNotEmpty) {
+ final entry = ContactEntry(e.value!);
+ contacts.add(entry);
+ contacts.sort();
+ listen(entry);
+ }
break;
case OperationKind.removed:
@@ -440,7 +444,14 @@ class ContactsTabController extends GetxController {
break;
case OperationKind.updated:
- contacts.sort();
+ if (e.value!.contact.value.users.isNotEmpty) {
+ if (contacts.none((c) => c.id == e.key)) {
+ final entry = ContactEntry(e.value!);
+ contacts.add(entry);
+ }
+
+ contacts.sort();
+ }
break;
}
});
@@ -474,7 +485,7 @@ class ContactsTabController extends GetxController {
if (!_scrollIsInvoked) {
_scrollIsInvoked = true;
- SchedulerBinding.instance.addPostFrameCallback((_) {
+ SchedulerBinding.instance.addPostFrameCallback((_) async {
_scrollIsInvoked = false;
if (scrollController.hasClients &&
@@ -482,7 +493,8 @@ class ContactsTabController extends GetxController {
_contactService.nextLoading.isFalse &&
scrollController.position.pixels >
scrollController.position.maxScrollExtent - 500) {
- _contactService.next();
+ await _contactService.next();
+ _scrollListener();
}
});
}
@@ -500,14 +512,12 @@ class ContactsTabController extends GetxController {
return;
}
- if (!scrollController.hasClients) {
- return await _ensureScrollable();
- }
-
// If the fetched initial page contains less elements than required to
// fill the view and there's more pages available, then fetch those pages.
- if (scrollController.position.maxScrollExtent < 50 &&
- _contactService.nextLoading.isFalse) {
+ if ((!scrollController.hasClients ||
+ scrollController.position.maxScrollExtent < 50) &&
+ _contactService.nextLoading.isFalse &&
+ hasNext.isTrue) {
await _contactService.next();
_ensureScrollable();
}
diff --git a/lib/ui/page/home/tab/contacts/view.dart b/lib/ui/page/home/tab/contacts/view.dart
index 6b17401595e..207759643ba 100644
--- a/lib/ui/page/home/tab/contacts/view.dart
+++ b/lib/ui/page/home/tab/contacts/view.dart
@@ -182,6 +182,8 @@ class ContactsTabView extends StatelessWidget {
c.search.value?.search.clear();
c.search.value?.query.value = '';
c.search.value?.search.focus.requestFocus();
+ } else {
+ c.toggleSearch(false);
}
} else if (c.selecting.value) {
c.toggleSelecting();
diff --git a/lib/util/permission.dart b/lib/util/permission.dart
new file mode 100644
index 00000000000..17715b7a00d
--- /dev/null
+++ b/lib/util/permission.dart
@@ -0,0 +1,42 @@
+// Copyright © 2022-2024 IT ENGINEERING MANAGEMENT INC,
+//
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License v3.0 as published by the
+// Free Software Foundation, either version 3 of the License, or (at your
+// option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License v3.0 for
+// more details.
+//
+// You should have received a copy of the GNU Affero General Public License v3.0
+// along with this program. If not, see
+// .
+
+import 'package:firebase_messaging/firebase_messaging.dart';
+import 'package:mutex/mutex.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+/// Utility class for requesting permissions.
+class PermissionUtils {
+ /// Mutex for synchronized access to permissions requesting.
+ ///
+ /// Ensures that only one permission is requested at the same time.
+ static final Mutex _permissionMutex = Mutex();
+
+ /// Requests the notifications permission.
+ static Future notifications() {
+ return _permissionMutex.protect(() {
+ return FirebaseMessaging.instance.requestPermission();
+ });
+ }
+
+ /// Requests the contacts permission.
+ static Future contacts() {
+ return _permissionMutex.protect(() async {
+ return Permission.contacts.request();
+ });
+ }
+}
diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc
index 1c2650094ca..4919611db43 100644
--- a/linux/flutter/generated_plugin_registrant.cc
+++ b/linux/flutter/generated_plugin_registrant.cc
@@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h"
#include
+#include
#include
#include
#include
@@ -21,6 +22,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
+ g_autoptr(FlPluginRegistrar) device_region_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "DeviceRegionPlugin");
+ device_region_plugin_register_with_registrar(device_region_registrar);
g_autoptr(FlPluginRegistrar) flutter_custom_cursor_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterCustomCursorPlugin");
flutter_custom_cursor_plugin_register_with_registrar(flutter_custom_cursor_registrar);
diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake
index 579ffe84607..e02a1b3f960 100644
--- a/linux/flutter/generated_plugins.cmake
+++ b/linux/flutter/generated_plugins.cmake
@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
+ device_region
flutter_custom_cursor
medea_flutter_webrtc
medea_jason
diff --git a/pubspec.lock b/pubspec.lock
index a70350f37c3..9508621b0e4 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -329,6 +329,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
+ device_region:
+ dependency: "direct main"
+ description:
+ name: device_region
+ sha256: d04cc40a445f8c8405e557ec0732e43e18e1a0d2109ddfb191911b15846780c2
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
dio:
dependency: "direct main"
description:
@@ -370,6 +378,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
+ fast_contacts:
+ dependency: "direct main"
+ description:
+ name: fast_contacts
+ sha256: cdc0091af580db3fe848decf7c7fc50ae2e08ac2d57694491801cd5eab6fcf4e
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.3"
ffi:
dependency: "direct main"
description:
@@ -1398,6 +1414,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.2"
+ phone_numbers_parser:
+ dependency: "direct main"
+ description:
+ name: phone_numbers_parser
+ sha256: ebe08725e63218a6ae2bf9129b7130332cb2ababa4988f07d6ddce48b0c21e06
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.2.1"
photo_view:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 22eda1092e9..7949329e087 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -17,12 +17,14 @@ dependencies:
crypto: ^3.0.3
desktop_drop: ^0.4.1
device_info_plus: ^9.0.2
+ device_region: ^1.4.0
dio: ^5.1.2
dough:
git:
url: https://github.com/krida2000/dough
path: packages/dough/
email_validator: ^2.1.17
+ fast_contacts: ^3.1.3
ffi: ^2.0.2
file_picker: ^6.1.1
firebase_core: ^2.25.4
@@ -81,6 +83,7 @@ dependencies:
path_provider: ^2.1.0
path_provider_android: ^2.1.0
permission_handler: ^11.3.0
+ phone_numbers_parser: ^8.2.1
photo_view: ^0.14.0
platform_detect: ^2.0.7
scrollable_positioned_list:
diff --git a/test/e2e/features/home/contacts/dismissing/.feature b/test/e2e/features/home/contacts/dismissing/.feature
index 1c6552e60d1..2f8efec6fbd 100644
--- a/test/e2e/features/home/contacts/dismissing/.feature
+++ b/test/e2e/features/home/contacts/dismissing/.feature
@@ -19,6 +19,7 @@ Feature: Contacts dismissing
Scenario: Contacts can be dismissed and restored
Given I am Alice
+ And users Bob and Charlie
And contacts Bob and Charlie
And I tap `ContactsButton` button
And I wait until "Bob" contact is present
diff --git a/test/e2e/steps/has_contact.dart b/test/e2e/steps/has_contact.dart
index 43b2fc8fe95..129b329ef0f 100644
--- a/test/e2e/steps/has_contact.dart
+++ b/test/e2e/steps/has_contact.dart
@@ -21,6 +21,7 @@ import 'package:messenger/domain/model/contact.dart';
import 'package:messenger/domain/model/user.dart';
import 'package:messenger/provider/gql/graphql.dart';
+import '../configuration.dart';
import '../parameters/users.dart';
import '../world/custom_world.dart';
@@ -38,9 +39,13 @@ final StepDefinitionGeneric hasContacts = given2(
for (int i = 0; i < count; i++) {
futures.add(
- provider.createChatContact(
- name: UserName(i.toString().padLeft(2, '0')),
- ),
+ Future(() async {
+ final CustomUser user = await createUser();
+ await provider.createChatContact(
+ name: UserName(i.toString().padLeft(2, '0')),
+ records: [ChatContactRecord(userId: user.userId)],
+ );
+ }),
);
}
@@ -68,8 +73,12 @@ final StepDefinitionGeneric hasFavoriteContacts =
for (int i = 0; i < count; i++) {
Future future = Future(() async {
- final ChatContactEventsVersionedMixin result = await provider
- .createChatContact(name: UserName(i.toString().padLeft(2, '0')));
+ final CustomUser user = await createUser();
+ final ChatContactEventsVersionedMixin result =
+ await provider.createChatContact(
+ name: UserName(i.toString().padLeft(2, '0')),
+ records: [ChatContactRecord(userId: user.userId)],
+ );
final ChatContactId contactId = (result.events.first
as ChatContactEventsVersionedMixin$Events$EventChatContactCreated)
.contactId;
diff --git a/test/unit/contact_rename_test.dart b/test/unit/contact_rename_test.dart
index 87c58c6b127..8132f51ebff 100644
--- a/test/unit/contact_rename_test.dart
+++ b/test/unit/contact_rename_test.dart
@@ -23,17 +23,23 @@ import 'package:messenger/api/backend/schema.dart';
import 'package:messenger/domain/model/contact.dart';
import 'package:messenger/domain/model/user.dart';
import 'package:messenger/domain/repository/contact.dart';
+import 'package:messenger/domain/repository/settings.dart';
import 'package:messenger/domain/service/contact.dart';
import 'package:messenger/provider/gql/exceptions.dart';
import 'package:messenger/provider/gql/graphql.dart';
+import 'package:messenger/provider/hive/application_settings.dart';
+import 'package:messenger/provider/hive/background.dart';
+import 'package:messenger/provider/hive/call_rect.dart';
import 'package:messenger/provider/hive/chat.dart';
import 'package:messenger/provider/hive/contact.dart';
import 'package:messenger/provider/hive/contact_sorting.dart';
import 'package:messenger/provider/hive/credentials.dart';
import 'package:messenger/provider/hive/favorite_contact.dart';
+import 'package:messenger/provider/hive/media_settings.dart';
import 'package:messenger/provider/hive/session_data.dart';
import 'package:messenger/provider/hive/user.dart';
import 'package:messenger/store/contact.dart';
+import 'package:messenger/store/settings.dart';
import 'package:messenger/store/user.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@@ -60,6 +66,16 @@ void main() async {
await favoriteContactHiveProvider.init();
var contactSortingHiveProvider = Get.put(ContactSortingHiveProvider());
await contactSortingHiveProvider.init();
+ var settingsProvider = MediaSettingsHiveProvider();
+ await settingsProvider.init();
+ await settingsProvider.clear();
+ var applicationSettingsProvider = ApplicationSettingsHiveProvider();
+ await applicationSettingsProvider.init();
+ var backgroundProvider = BackgroundHiveProvider();
+ await backgroundProvider.init();
+ var callRectProvider = CallRectHiveProvider();
+ await callRectProvider.init();
+
final graphQlProvider = Get.put(MockGraphQlProvider());
when(graphQlProvider.disconnect()).thenAnswer((_) => () {});
when(graphQlProvider.favoriteChatsEvents(any))
@@ -146,8 +162,16 @@ void main() async {
sessionDataHiveProvider,
),
);
+ AbstractSettingsRepository settingsRepository = Get.put(
+ SettingsRepository(
+ settingsProvider,
+ applicationSettingsProvider,
+ backgroundProvider,
+ callRectProvider,
+ ),
+ );
- return Get.put(ContactService(contactRepository));
+ return Get.put(ContactService(contactRepository, settingsRepository));
}
test('ContactService successfully renames contact', () async {
diff --git a/test/widget/chat_attachment_test.dart b/test/widget/chat_attachment_test.dart
index 4d92f0585c1..0c54600e446 100644
--- a/test/widget/chat_attachment_test.dart
+++ b/test/widget/chat_attachment_test.dart
@@ -556,7 +556,7 @@ void main() async {
sessionProvider,
),
);
- Get.put(ContactService(contactRepository));
+ Get.put(ContactService(contactRepository, settingsRepository));
Get.put(UserService(userRepository));
ChatService chatService = Get.put(ChatService(chatRepository, authService));
diff --git a/test/widget/chat_direct_link_chat_test.dart b/test/widget/chat_direct_link_chat_test.dart
index 15f6fa2eadf..fb4d954861b 100644
--- a/test/widget/chat_direct_link_chat_test.dart
+++ b/test/widget/chat_direct_link_chat_test.dart
@@ -409,7 +409,7 @@ void main() async {
sessionProvider,
);
- Get.put(ContactService(contactRepository));
+ Get.put(ContactService(contactRepository, settingsRepository));
Get.put(UserService(userRepository));
ChatService chatService = Get.put(ChatService(chatRepository, authService));
Get.put(CallService(authService, chatService, callRepository));
diff --git a/test/widget/chat_edit_message_test.dart b/test/widget/chat_edit_message_test.dart
index 6d8ed06ac6a..20e190b8715 100644
--- a/test/widget/chat_edit_message_test.dart
+++ b/test/widget/chat_edit_message_test.dart
@@ -509,7 +509,7 @@ void main() async {
sessionProvider,
),
);
- Get.put(ContactService(contactRepository));
+ Get.put(ContactService(contactRepository, settingsRepository));
MyUserRepository myUserRepository = MyUserRepository(
graphQlProvider,
diff --git a/test/widget/chat_hide_test.dart b/test/widget/chat_hide_test.dart
index 53233c26845..402fb42f59f 100644
--- a/test/widget/chat_hide_test.dart
+++ b/test/widget/chat_hide_test.dart
@@ -401,6 +401,15 @@ void main() async {
);
Get.put(UserService(userRepository));
+ AbstractSettingsRepository settingsRepository = Get.put(
+ SettingsRepository(
+ settingsProvider,
+ applicationSettingsProvider,
+ backgroundProvider,
+ callRectProvider,
+ ),
+ );
+
Get.put(
ContactService(
Get.put(
@@ -413,6 +422,7 @@ void main() async {
sessionProvider,
),
),
+ settingsRepository,
),
);
@@ -424,15 +434,6 @@ void main() async {
);
Get.put(MyUserService(authService, myUserRepository));
- AbstractSettingsRepository settingsRepository = Get.put(
- SettingsRepository(
- settingsProvider,
- applicationSettingsProvider,
- backgroundProvider,
- callRectProvider,
- ),
- );
-
final callRepository = CallRepository(
graphQlProvider,
userRepository,
diff --git a/test/widget/chat_reply_message_test.dart b/test/widget/chat_reply_message_test.dart
index 7761c96147d..bddb31a113d 100644
--- a/test/widget/chat_reply_message_test.dart
+++ b/test/widget/chat_reply_message_test.dart
@@ -584,7 +584,7 @@ void main() async {
sessionProvider,
),
);
- Get.put(ContactService(contactRepository));
+ Get.put(ContactService(contactRepository, settingsRepository));
MyUserRepository myUserRepository = MyUserRepository(
graphQlProvider,
diff --git a/test/widget/chat_update_attachments_test.dart b/test/widget/chat_update_attachments_test.dart
index a36f698f744..e9b934000b9 100644
--- a/test/widget/chat_update_attachments_test.dart
+++ b/test/widget/chat_update_attachments_test.dart
@@ -601,7 +601,7 @@ void main() async {
Get.put(CacheWorker(cacheInfoProvider, null));
- Get.put(ContactService(contactRepository));
+ Get.put(ContactService(contactRepository, settingsRepository));
await tester.pumpWidget(createWidgetForTesting(
child: const ChatView(ChatId('0d72d245-8425-467a-9ebd-082d4f47850b')),
diff --git a/test/widget/user_profile_test.dart b/test/widget/user_profile_test.dart
index bcaf49693be..9d79359a863 100644
--- a/test/widget/user_profile_test.dart
+++ b/test/widget/user_profile_test.dart
@@ -488,7 +488,7 @@ void main() async {
sessionProvider,
),
);
- Get.put(ContactService(contactRepository));
+ Get.put(ContactService(contactRepository, settingsRepository));
userRepository.getContact = contactRepository.get;
final callRepository = Get.put(
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index a5752618fa9..66a2e4628e9 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -8,6 +8,7 @@
#include
#include
+#include
#include
#include
#include
@@ -29,6 +30,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
DesktopDropPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
+ DeviceRegionPluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("DeviceRegionPluginCApi"));
FirebaseCorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FlutterCustomCursorPluginRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 9e44701950c..1f98013289f 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
desktop_drop
+ device_region
firebase_core
flutter_custom_cursor
medea_flutter_webrtc