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