Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
214 lines
6.0 KiB
Dart
214 lines
6.0 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:material_symbols_icons/symbols.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
import '../../backend/modules/contacts.dart';
|
|
import '../../core/utils/text_entities.dart';
|
|
import '../../main.dart' show api;
|
|
import 'chat_menu_overlay.dart';
|
|
import 'custom_notification.dart';
|
|
import 'qlyra_avatar.dart';
|
|
import 'max_link_handler.dart';
|
|
import 'small_spinner.dart';
|
|
|
|
Future<void> openMentionProfile(BuildContext context, String nickname) async {
|
|
final handled = await tryHandleMaxLink(context, 'https://max.ru/$nickname');
|
|
if (handled || !context.mounted) return;
|
|
showCustomNotification(context, 'Профиль @$nickname не найден');
|
|
}
|
|
|
|
Future<void> copyTextEntity(
|
|
BuildContext context,
|
|
String value,
|
|
String message,
|
|
) async {
|
|
await Clipboard.setData(ClipboardData(text: value));
|
|
if (!context.mounted) return;
|
|
showCustomNotification(context, message);
|
|
}
|
|
|
|
void showPhoneEntityMenu(
|
|
BuildContext context,
|
|
String phone, {
|
|
required Offset at,
|
|
}) {
|
|
showChatMenu(
|
|
context: context,
|
|
anchorRect: Rect.fromLTWH(at.dx, at.dy, 0, 0),
|
|
compact: true,
|
|
header: _PhoneOwnerHeader(phone: phone),
|
|
items: [
|
|
ChatMenuItem(
|
|
icon: Symbols.content_copy,
|
|
label: 'Скопировать номер телефона',
|
|
onTap: () => copyTextEntity(context, phone, 'Номер скопирован'),
|
|
),
|
|
if (defaultTargetPlatform == TargetPlatform.android ||
|
|
defaultTargetPlatform == TargetPlatform.iOS)
|
|
ChatMenuItem(
|
|
icon: Symbols.call,
|
|
label: 'Позвонить',
|
|
onTap: () => _dial(context, phone),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
void showCardEntityMenu(
|
|
BuildContext context,
|
|
String digits, {
|
|
required Offset at,
|
|
}) {
|
|
showChatMenu(
|
|
context: context,
|
|
anchorRect: Rect.fromLTWH(at.dx, at.dy, 0, 0),
|
|
compact: true,
|
|
items: [
|
|
ChatMenuItem(
|
|
icon: Symbols.content_copy,
|
|
label: 'Скопировать номер карты',
|
|
onTap: () => copyTextEntity(context, digits, 'Номер карты скопирован'),
|
|
),
|
|
],
|
|
footer: _CardFooter(digits: digits),
|
|
);
|
|
}
|
|
|
|
Future<void> _dial(BuildContext context, String phone) async {
|
|
final uri = Uri(scheme: 'tel', path: phone);
|
|
var launched = false;
|
|
try {
|
|
launched = await launchUrl(uri, mode: LaunchMode.externalApplication);
|
|
} catch (_) {
|
|
launched = false;
|
|
}
|
|
if (launched || !context.mounted) return;
|
|
showCustomNotification(context, 'Не удалось открыть приложение звонков');
|
|
}
|
|
|
|
class _CardFooter extends StatelessWidget {
|
|
final String digits;
|
|
|
|
const _CardFooter({required this.digits});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = Theme.of(context).colorScheme;
|
|
final title = cardBrandTitle(digits);
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(14, 10, 14, 11),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
cardMask(digits),
|
|
style: TextStyle(color: cs.onSurface, fontSize: 14),
|
|
),
|
|
if (title != null) ...[
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
title,
|
|
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _PhoneOwnerHeader extends StatefulWidget {
|
|
final String phone;
|
|
|
|
const _PhoneOwnerHeader({required this.phone});
|
|
|
|
@override
|
|
State<_PhoneOwnerHeader> createState() => _PhoneOwnerHeaderState();
|
|
}
|
|
|
|
class _PhoneOwnerHeaderState extends State<_PhoneOwnerHeader> {
|
|
late final Future<PhoneLookupResult?> _lookup = ContactsModule.findByPhone(
|
|
api,
|
|
widget.phone,
|
|
silent: true,
|
|
);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = Theme.of(context).colorScheme;
|
|
return FutureBuilder<PhoneLookupResult?>(
|
|
future: _lookup,
|
|
builder: (context, snapshot) {
|
|
final Widget content;
|
|
if (snapshot.connectionState != ConnectionState.done) {
|
|
content = Row(
|
|
children: [
|
|
SmallSpinner(size: 18, color: cs.onSurfaceVariant),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
widget.phone,
|
|
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14),
|
|
),
|
|
],
|
|
);
|
|
} else {
|
|
final found = snapshot.data;
|
|
content = found == null
|
|
? Text(
|
|
'Человека ещё нет в MAX',
|
|
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14),
|
|
)
|
|
: _OwnerRow(found: found, phone: widget.phone);
|
|
}
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(14, 11, 14, 10),
|
|
child: content,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _OwnerRow extends StatelessWidget {
|
|
final PhoneLookupResult found;
|
|
final String phone;
|
|
|
|
const _OwnerRow({required this.found, required this.phone});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = Theme.of(context).colorScheme;
|
|
final resolved = found.name;
|
|
final name = (resolved == null || resolved.isEmpty) ? phone : resolved;
|
|
return Row(
|
|
children: [
|
|
QlyraAvatar(name: name, size: 36, imageUrl: found.avatarUrl),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
name,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
color: cs.onSurface,
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
Text(
|
|
phone,
|
|
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|