убрал скрол к непрочитанныm GET OUT, статус отправки реакций, более заметная кнопка сохранения фото, возможность выбрать деррикторию для сохранения файлов на пк и телiфоне, совместил отправку файлов и отправку медиав одну кнопку, добавил функционал кнопкам 'добавить в контакты' и написать сообщение который там в этом ну этом ну вы поняли, возможность написать человеку прям с чата(добавил кнопки в то меню где можно редактировать локально контакт), выход не чекал
This commit is contained in:
@@ -379,6 +379,8 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
final int? chatId;
|
||||
final bool isEncryptionPasswordSet;
|
||||
final String? decryptedText;
|
||||
// Идёт ли сейчас отправка/удаление реакции для этого сообщения
|
||||
final bool isReactionSending;
|
||||
|
||||
const ChatMessageBubble({
|
||||
super.key,
|
||||
@@ -414,6 +416,7 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
this.chatId,
|
||||
this.isEncryptionPasswordSet = false,
|
||||
this.decryptedText,
|
||||
this.isReactionSending = false,
|
||||
});
|
||||
|
||||
String _formatMessageTime(BuildContext context, int timestamp) {
|
||||
@@ -1166,17 +1169,32 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
: textColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
'$emoji $count',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: isUserReaction
|
||||
? FontWeight.w600
|
||||
: FontWeight.w500,
|
||||
color: isUserReaction
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: textColor.withOpacity(0.9),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'$emoji $count',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: isUserReaction
|
||||
? FontWeight.w600
|
||||
: FontWeight.w500,
|
||||
color: isUserReaction
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: textColor.withOpacity(0.9),
|
||||
),
|
||||
),
|
||||
if (isUserReaction && isReactionSending) ...[
|
||||
const SizedBox(width: 4),
|
||||
_RotatingIcon(
|
||||
icon: Icons.watch_later_outlined,
|
||||
size: 12,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? const Color(0xFF9bb5c7)
|
||||
: const Color(0xFF6b7280),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -3340,10 +3358,10 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
String? token,
|
||||
int? chatId,
|
||||
}) async {
|
||||
// Initialize progress
|
||||
FileDownloadProgressService().updateProgress(fileId, 0.0);
|
||||
|
||||
try {
|
||||
// Initialize progress
|
||||
FileDownloadProgressService().updateProgress(fileId, 0.0);
|
||||
|
||||
// Get Downloads directory using helper
|
||||
final downloadDir = await DownloadPathHelper.getDownloadDirectory();
|
||||
|
||||
@@ -5634,10 +5652,22 @@ class _FullScreenPhotoViewerState extends State<FullScreenPhotoViewer> {
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
if (widget.attach != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.download, color: Colors.white),
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueAccent,
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
icon: const Icon(Icons.download),
|
||||
label: const Text(
|
||||
'Скачать',
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
onPressed: _downloadPhoto,
|
||||
tooltip: 'Скачать фото',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -41,7 +41,10 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
|
||||
final ScrollController _nameScrollController = ScrollController();
|
||||
String? _localDescription;
|
||||
StreamSubscription? _changesSubscription;
|
||||
StreamSubscription? _wsSubscription;
|
||||
bool _isOpeningChat = false;
|
||||
bool _isInContacts = false;
|
||||
bool _isAddingToContacts = false;
|
||||
|
||||
String get _displayName {
|
||||
final displayName = getContactDisplayName(
|
||||
@@ -64,20 +67,50 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadLocalDescription();
|
||||
_checkIfInContacts();
|
||||
|
||||
_changesSubscription = ContactLocalNamesService().changes.listen((
|
||||
contactId,
|
||||
) {
|
||||
if (contactId == widget.userId && mounted) {
|
||||
_loadLocalDescription();
|
||||
_checkIfInContacts();
|
||||
}
|
||||
});
|
||||
|
||||
_wsSubscription = ApiService.instance.messages.listen((msg) {
|
||||
try {
|
||||
if (msg['opcode'] == 34 &&
|
||||
msg['cmd'] == 1 &&
|
||||
msg['payload'] != null &&
|
||||
msg['payload']['contact'] != null) {
|
||||
final contactJson = msg['payload']['contact'] as Map<String, dynamic>;
|
||||
final id = contactJson['id'] as int?;
|
||||
if (id == widget.userId && mounted) {
|
||||
final contact = Contact.fromJson(contactJson);
|
||||
ApiService.instance.updateContactCache([contact]);
|
||||
setState(() {
|
||||
_isInContacts = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkNameLength();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _checkIfInContacts() async {
|
||||
final cached = ApiService.instance.getCachedContact(widget.userId);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isInContacts = cached != null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadLocalDescription() async {
|
||||
final localData = await ContactLocalNamesService().getContactData(
|
||||
widget.userId,
|
||||
@@ -92,6 +125,7 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
|
||||
@override
|
||||
void dispose() {
|
||||
_changesSubscription?.cancel();
|
||||
_wsSubscription?.cancel();
|
||||
_nameScrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -234,14 +268,24 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
|
||||
_buildActionButton(
|
||||
icon: Icons.phone,
|
||||
label: 'Позвонить',
|
||||
onPressed: null,
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Звонков пока нету'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
},
|
||||
colors: colors,
|
||||
),
|
||||
_buildActionButton(
|
||||
icon: Icons.person_add,
|
||||
label: 'В контакты',
|
||||
onPressed: null,
|
||||
label: _isInContacts ? 'В контактах' : 'В контакты',
|
||||
onPressed: _isInContacts || _isAddingToContacts
|
||||
? null
|
||||
: _handleAddToContacts,
|
||||
colors: colors,
|
||||
isLoading: _isAddingToContacts,
|
||||
),
|
||||
_buildActionButton(
|
||||
icon: Icons.message,
|
||||
@@ -386,4 +430,46 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleAddToContacts() async {
|
||||
if (_isAddingToContacts || _isInContacts) return;
|
||||
|
||||
setState(() {
|
||||
_isAddingToContacts = true;
|
||||
});
|
||||
|
||||
try {
|
||||
// Отправляем opcode=34 с action="ADD"
|
||||
await ApiService.instance.addContact(widget.userId);
|
||||
|
||||
// Пытаемся сразу подтянуть обновлённые данные контакта
|
||||
await ApiService.instance.requestContactsByIds([widget.userId]);
|
||||
|
||||
await _checkIfInContacts();
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Запрос на добавление в контакты отправлен'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Ошибка при добавлении в контакты: $e'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isAddingToContacts = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user