feat(contacts): отправка контактов вложением
This commit is contained in:
@@ -1613,6 +1613,26 @@ class MessagesModule {
|
|||||||
return _sentMessageMap(response);
|
return _sentMessageMap(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>?> sendContactMessage(
|
||||||
|
int chatId,
|
||||||
|
int contactId, {
|
||||||
|
bool notify = true,
|
||||||
|
}) async {
|
||||||
|
final payload = {
|
||||||
|
'chatId': chatId,
|
||||||
|
'message': {
|
||||||
|
'cid': DateTime.now().millisecondsSinceEpoch * -1,
|
||||||
|
'attaches': [
|
||||||
|
{'_type': 'CONTACT', 'contactId': contactId},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'notify': notify,
|
||||||
|
};
|
||||||
|
|
||||||
|
final response = await _api.sendRequest(Opcode.msgSend, payload);
|
||||||
|
return _sentMessageMap(response);
|
||||||
|
}
|
||||||
|
|
||||||
static const int _pollAnonymousFlag = 4;
|
static const int _pollAnonymousFlag = 4;
|
||||||
static const int _pollMultipleFlag = 1;
|
static const int _pollMultipleFlag = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -5785,6 +5785,9 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
onCreatePoll: _encryptionEnabled
|
onCreatePoll: _encryptionEnabled
|
||||||
? () => _refuseUnencrypted('Опросы')
|
? () => _refuseUnencrypted('Опросы')
|
||||||
: _createPoll,
|
: _createPoll,
|
||||||
|
onSendContact: _encryptionEnabled
|
||||||
|
? (_) => _refuseUnencrypted('Контакты')
|
||||||
|
: _sendContact,
|
||||||
);
|
);
|
||||||
if (!mounted || !hadKeyboard) return;
|
if (!mounted || !hadKeyboard) return;
|
||||||
_messageFocusNode.requestFocus();
|
_messageFocusNode.requestFocus();
|
||||||
@@ -6190,6 +6193,22 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _sendContact(CachedContact contact) async {
|
||||||
|
final last = contact.lastName;
|
||||||
|
final fullName = (last != null && last.isNotEmpty)
|
||||||
|
? '${contact.firstName} $last'
|
||||||
|
: contact.firstName;
|
||||||
|
await _sendAttachMessage([
|
||||||
|
ContactAttachment(
|
||||||
|
contactId: contact.id,
|
||||||
|
firstName: contact.firstName,
|
||||||
|
lastName: last,
|
||||||
|
name: fullName,
|
||||||
|
photoUrl: contact.baseUrl,
|
||||||
|
),
|
||||||
|
], () => messagesModule.sendContactMessage(widget.chatId, contact.id));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _createPoll() async {
|
Future<void> _createPoll() async {
|
||||||
final draft = await showCreatePollSheet(context);
|
final draft = await showCreatePollSheet(context);
|
||||||
if (draft == null || !mounted) return;
|
if (draft == null || !mounted) return;
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import 'package:komet/backend/modules/contacts.dart';
|
||||||
import 'package:komet/core/config/app_frost.dart';
|
import 'package:komet/core/config/app_frost.dart';
|
||||||
import 'package:komet/core/config/app_nav_pill_style.dart';
|
import 'package:komet/core/config/app_nav_pill_style.dart';
|
||||||
import 'package:komet/core/config/app_visual_style.dart';
|
import 'package:komet/core/config/app_visual_style.dart';
|
||||||
import 'package:komet/core/media/gallery_source.dart';
|
import 'package:komet/core/media/gallery_source.dart';
|
||||||
import 'package:komet/core/utils/format.dart';
|
import 'package:komet/core/utils/format.dart';
|
||||||
|
import 'package:komet/frontend/widgets/attachment/contact_picker_page.dart';
|
||||||
import 'package:komet/frontend/widgets/attachment/media_preview_screen.dart';
|
import 'package:komet/frontend/widgets/attachment/media_preview_screen.dart';
|
||||||
import 'package:komet/frontend/widgets/attachment/photo_editor.dart';
|
import 'package:komet/frontend/widgets/attachment/photo_editor.dart';
|
||||||
import 'package:komet/frontend/widgets/custom_notification.dart';
|
import 'package:komet/frontend/widgets/custom_notification.dart';
|
||||||
@@ -25,7 +27,7 @@ List<PillNavItem> _buildNavItems(AppLocalizations l10n) => [
|
|||||||
PillNavItem(icon: Symbols.description, label: l10n.scheduledAttachFile),
|
PillNavItem(icon: Symbols.description, label: l10n.scheduledAttachFile),
|
||||||
PillNavItem(icon: Symbols.location_on, label: l10n.scheduledAttachLocation),
|
PillNavItem(icon: Symbols.location_on, label: l10n.scheduledAttachLocation),
|
||||||
PillNavItem(icon: Symbols.bar_chart, label: l10n.attachSheetPoll),
|
PillNavItem(icon: Symbols.bar_chart, label: l10n.attachSheetPoll),
|
||||||
PillNavItem(icon: Symbols.person, label: l10n.nfcPeerFirstNameFallback),
|
PillNavItem(icon: Symbols.person, label: l10n.attachSheetContact),
|
||||||
];
|
];
|
||||||
|
|
||||||
Future<void> showAttachmentSheet(
|
Future<void> showAttachmentSheet(
|
||||||
@@ -35,6 +37,7 @@ Future<void> showAttachmentSheet(
|
|||||||
VoidCallback? onPickFile,
|
VoidCallback? onPickFile,
|
||||||
VoidCallback? onShareLocation,
|
VoidCallback? onShareLocation,
|
||||||
VoidCallback? onCreatePoll,
|
VoidCallback? onCreatePoll,
|
||||||
|
ValueChanged<CachedContact>? onSendContact,
|
||||||
}) {
|
}) {
|
||||||
return showModalBottomSheet<void>(
|
return showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -48,6 +51,7 @@ Future<void> showAttachmentSheet(
|
|||||||
onPickFile: onPickFile,
|
onPickFile: onPickFile,
|
||||||
onShareLocation: onShareLocation,
|
onShareLocation: onShareLocation,
|
||||||
onCreatePoll: onCreatePoll,
|
onCreatePoll: onCreatePoll,
|
||||||
|
onSendContact: onSendContact,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -58,6 +62,7 @@ class AttachmentSheet extends StatefulWidget {
|
|||||||
final VoidCallback? onPickFile;
|
final VoidCallback? onPickFile;
|
||||||
final VoidCallback? onShareLocation;
|
final VoidCallback? onShareLocation;
|
||||||
final VoidCallback? onCreatePoll;
|
final VoidCallback? onCreatePoll;
|
||||||
|
final ValueChanged<CachedContact>? onSendContact;
|
||||||
|
|
||||||
const AttachmentSheet({
|
const AttachmentSheet({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -66,6 +71,7 @@ class AttachmentSheet extends StatefulWidget {
|
|||||||
this.onPickFile,
|
this.onPickFile,
|
||||||
this.onShareLocation,
|
this.onShareLocation,
|
||||||
this.onCreatePoll,
|
this.onCreatePoll,
|
||||||
|
this.onSendContact,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -327,11 +333,20 @@ class _AttachmentSheetState extends State<AttachmentSheet> {
|
|||||||
buttonLabel: l10n.attachSheetCreatePoll,
|
buttonLabel: l10n.attachSheetCreatePoll,
|
||||||
onTap: widget.onCreatePoll,
|
onTap: widget.onCreatePoll,
|
||||||
),
|
),
|
||||||
_buildPlaceholderPage(cs, bottomReserve),
|
_buildContactPage(cs, bottomReserve),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildContactPage(ColorScheme cs, double bottomReserve) {
|
||||||
|
final onSendContact = widget.onSendContact;
|
||||||
|
if (onSendContact == null) return _buildPlaceholderPage(cs, bottomReserve);
|
||||||
|
return ContactPickerPage(
|
||||||
|
bottomReserve: bottomReserve,
|
||||||
|
onPick: onSendContact,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildActionPage(
|
Widget _buildActionPage(
|
||||||
ColorScheme cs,
|
ColorScheme cs,
|
||||||
double bottomReserve, {
|
double bottomReserve, {
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import 'package:komet/backend/modules/contacts.dart';
|
||||||
|
import 'package:komet/core/config/debug_test.dart';
|
||||||
|
import 'package:komet/core/contacts/device_contacts_service.dart';
|
||||||
|
import 'package:komet/core/storage/app_database.dart';
|
||||||
|
import 'package:komet/frontend/widgets/komet_avatar.dart';
|
||||||
|
import 'package:komet/frontend/widgets/small_spinner.dart';
|
||||||
|
import 'package:komet/frontend/widgets/springy_tap.dart';
|
||||||
|
import 'package:komet/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class ContactPickerPage extends StatefulWidget {
|
||||||
|
final double bottomReserve;
|
||||||
|
final ValueChanged<CachedContact> onPick;
|
||||||
|
|
||||||
|
const ContactPickerPage({
|
||||||
|
super.key,
|
||||||
|
required this.bottomReserve,
|
||||||
|
required this.onPick,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ContactPickerPage> createState() => _ContactPickerPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContactPickerPageState extends State<ContactPickerPage> {
|
||||||
|
final TextEditingController _queryCtrl = TextEditingController();
|
||||||
|
|
||||||
|
List<CachedContact> _contacts = const [];
|
||||||
|
String _query = '';
|
||||||
|
bool _loading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_load();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_queryCtrl.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _load() async {
|
||||||
|
final loaded = await _fetch();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_contacts = loaded;
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<CachedContact>> _fetch() async {
|
||||||
|
if (DebugTest.enabled) {
|
||||||
|
return ContactsModule.debugContacts()
|
||||||
|
..sort((a, b) => a.firstName.compareTo(b.firstName));
|
||||||
|
}
|
||||||
|
final profile = await AppDatabase.loadActiveProfile();
|
||||||
|
if (profile == null) return const [];
|
||||||
|
final contacts = await ContactsModule.getContacts(profile.id);
|
||||||
|
contacts.sort((a, b) => _displayName(a).compareTo(_displayName(b)));
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _displayName(CachedContact contact) {
|
||||||
|
final book = DeviceContactsService.nameForPhone(contact.phone);
|
||||||
|
if (book != null && book.isNotEmpty) return book;
|
||||||
|
final last = contact.lastName;
|
||||||
|
final full = (last != null && last.isNotEmpty)
|
||||||
|
? '${contact.firstName} $last'
|
||||||
|
: contact.firstName;
|
||||||
|
final trimmed = full.trim();
|
||||||
|
return trimmed.isEmpty ? '+${contact.phone}' : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CachedContact> get _visible {
|
||||||
|
final query = _query.trim().toLowerCase();
|
||||||
|
if (query.isEmpty) return _contacts;
|
||||||
|
return _contacts
|
||||||
|
.where(
|
||||||
|
(c) =>
|
||||||
|
_displayName(c).toLowerCase().contains(query) ||
|
||||||
|
c.phone.toString().contains(query),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: widget.bottomReserve),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 4, 16, 8),
|
||||||
|
child: TextField(
|
||||||
|
controller: _queryCtrl,
|
||||||
|
onChanged: (value) => setState(() => _query = value),
|
||||||
|
style: TextStyle(color: cs.onSurface, fontSize: 15),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: l10n.attachSheetContactSearchHint,
|
||||||
|
hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 15),
|
||||||
|
prefixIcon: Icon(
|
||||||
|
Symbols.search,
|
||||||
|
color: cs.onSurfaceVariant,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
isDense: true,
|
||||||
|
filled: true,
|
||||||
|
fillColor: cs.surfaceContainerHighest,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: _buildBody(cs, l10n)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBody(ColorScheme cs, AppLocalizations l10n) {
|
||||||
|
if (_loading) {
|
||||||
|
return Center(child: SmallSpinner(size: 36, color: cs.primary));
|
||||||
|
}
|
||||||
|
final visible = _visible;
|
||||||
|
if (visible.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.person_off, size: 48, color: cs.onSurfaceVariant),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
_contacts.isEmpty
|
||||||
|
? l10n.attachSheetNoContacts
|
||||||
|
: l10n.attachSheetNoContactsFound,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 15),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
itemCount: visible.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final contact = visible[index];
|
||||||
|
return _buildTile(cs, contact);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTile(ColorScheme cs, CachedContact contact) {
|
||||||
|
final name = _displayName(contact);
|
||||||
|
return SpringyTap(
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
widget.onPick(contact);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
KometAvatar(name: name, imageUrl: contact.baseUrl, size: 44),
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurface,
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'+${contact.phone}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurfaceVariant,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -847,6 +847,10 @@
|
|||||||
"attachSheetNoImagesFound": "No images found",
|
"attachSheetNoImagesFound": "No images found",
|
||||||
"attachSheetLimitedAccessInfo": "Not all photos are accessible",
|
"attachSheetLimitedAccessInfo": "Not all photos are accessible",
|
||||||
"attachSheetSectionInProgress": "Section under development",
|
"attachSheetSectionInProgress": "Section under development",
|
||||||
|
"attachSheetContact": "Contact",
|
||||||
|
"attachSheetContactSearchHint": "Search contacts",
|
||||||
|
"attachSheetNoContacts": "You have no contacts yet",
|
||||||
|
"attachSheetNoContactsFound": "No contacts found",
|
||||||
"attachSheetNoGalleryAccessTitle": "No access to the gallery",
|
"attachSheetNoGalleryAccessTitle": "No access to the gallery",
|
||||||
"attachSheetNoGalleryAccessSubtitle": "Allow access to photos to pick them from here",
|
"attachSheetNoGalleryAccessSubtitle": "Allow access to photos to pick them from here",
|
||||||
"attachSheetAllow": "Allow",
|
"attachSheetAllow": "Allow",
|
||||||
|
|||||||
@@ -3764,6 +3764,30 @@ abstract class AppLocalizations {
|
|||||||
/// **'Section under development'**
|
/// **'Section under development'**
|
||||||
String get attachSheetSectionInProgress;
|
String get attachSheetSectionInProgress;
|
||||||
|
|
||||||
|
/// No description provided for @attachSheetContact.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Contact'**
|
||||||
|
String get attachSheetContact;
|
||||||
|
|
||||||
|
/// No description provided for @attachSheetContactSearchHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Search contacts'**
|
||||||
|
String get attachSheetContactSearchHint;
|
||||||
|
|
||||||
|
/// No description provided for @attachSheetNoContacts.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'You have no contacts yet'**
|
||||||
|
String get attachSheetNoContacts;
|
||||||
|
|
||||||
|
/// No description provided for @attachSheetNoContactsFound.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No contacts found'**
|
||||||
|
String get attachSheetNoContactsFound;
|
||||||
|
|
||||||
/// No description provided for @attachSheetNoGalleryAccessTitle.
|
/// No description provided for @attachSheetNoGalleryAccessTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -1956,6 +1956,18 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get attachSheetSectionInProgress => 'Section under development';
|
String get attachSheetSectionInProgress => 'Section under development';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get attachSheetContact => 'Contact';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get attachSheetContactSearchHint => 'Search contacts';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get attachSheetNoContacts => 'You have no contacts yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get attachSheetNoContactsFound => 'No contacts found';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get attachSheetNoGalleryAccessTitle => 'No access to the gallery';
|
String get attachSheetNoGalleryAccessTitle => 'No access to the gallery';
|
||||||
|
|
||||||
|
|||||||
@@ -1970,6 +1970,18 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get attachSheetSectionInProgress => 'Раздел в разработке';
|
String get attachSheetSectionInProgress => 'Раздел в разработке';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get attachSheetContact => 'Контакт';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get attachSheetContactSearchHint => 'Поиск по контактам';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get attachSheetNoContacts => 'У вас пока нет контактов';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get attachSheetNoContactsFound => 'Контакты не найдены';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get attachSheetNoGalleryAccessTitle => 'Нет доступа к галерее';
|
String get attachSheetNoGalleryAccessTitle => 'Нет доступа к галерее';
|
||||||
|
|
||||||
|
|||||||
@@ -636,6 +636,10 @@
|
|||||||
"attachSheetNoImagesFound": "Изображений не найдено",
|
"attachSheetNoImagesFound": "Изображений не найдено",
|
||||||
"attachSheetLimitedAccessInfo": "Доступны не все фото",
|
"attachSheetLimitedAccessInfo": "Доступны не все фото",
|
||||||
"attachSheetSectionInProgress": "Раздел в разработке",
|
"attachSheetSectionInProgress": "Раздел в разработке",
|
||||||
|
"attachSheetContact": "Контакт",
|
||||||
|
"attachSheetContactSearchHint": "Поиск по контактам",
|
||||||
|
"attachSheetNoContacts": "У вас пока нет контактов",
|
||||||
|
"attachSheetNoContactsFound": "Контакты не найдены",
|
||||||
"attachSheetNoGalleryAccessTitle": "Нет доступа к галерее",
|
"attachSheetNoGalleryAccessTitle": "Нет доступа к галерее",
|
||||||
"attachSheetNoGalleryAccessSubtitle": "Разрешите доступ к фото, чтобы выбрать их отсюда",
|
"attachSheetNoGalleryAccessSubtitle": "Разрешите доступ к фото, чтобы выбрать их отсюда",
|
||||||
"attachSheetAllow": "Разрешить",
|
"attachSheetAllow": "Разрешить",
|
||||||
|
|||||||
+1
-2
@@ -35,8 +35,7 @@ dependencies:
|
|||||||
intl: any
|
intl: any
|
||||||
|
|
||||||
# Rust networking core (kolibri) — FFI plugin, replaces the Dart transport.
|
# Rust networking core (kolibri) — FFI plugin, replaces the Dart transport.
|
||||||
# DEV: local path for fast iteration; re-pin to third_party/kolibri submodule
|
# Pinned to the third_party/kolibri submodule.
|
||||||
# (path: third_party/kolibri/kolibri-dart) before merge.
|
|
||||||
kolibri:
|
kolibri:
|
||||||
path: third_party/kolibri/kolibri-dart
|
path: third_party/kolibri/kolibri-dart
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user