Files
Qlyra/lib/frontend/widgets/text_entity_actions.dart
sevenhill f74057ce9b
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
Rebrand application as Qlyra
2026-08-30 13:16:17 +03:00

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),
),
],
),
),
],
);
}
}