починил вход, выход с ака, сделал вход сразу после кода, задержку перед появлением менюшки с эмодзи чтобы мозги не ебало
This commit is contained in:
@@ -28,6 +28,7 @@ import 'package:gwid/utils/user_id_lookup_screen.dart';
|
|||||||
import 'package:gwid/screens/music_library_screen.dart';
|
import 'package:gwid/screens/music_library_screen.dart';
|
||||||
import 'package:gwid/widgets/message_preview_dialog.dart';
|
import 'package:gwid/widgets/message_preview_dialog.dart';
|
||||||
import 'package:gwid/services/chat_read_settings_service.dart';
|
import 'package:gwid/services/chat_read_settings_service.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:gwid/services/local_profile_manager.dart';
|
import 'package:gwid/services/local_profile_manager.dart';
|
||||||
import 'package:gwid/widgets/contact_name_widget.dart';
|
import 'package:gwid/widgets/contact_name_widget.dart';
|
||||||
import 'package:gwid/widgets/contact_avatar_widget.dart';
|
import 'package:gwid/widgets/contact_avatar_widget.dart';
|
||||||
@@ -1849,6 +1850,25 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
|
|
||||||
print('🌐 URL веб-приложения: $webUrl');
|
print('🌐 URL веб-приложения: $webUrl');
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
// На десктопах WebView ведёт себя нестабильно (чёрный экран),
|
||||||
|
// поэтому открываем Сферум во внешнем браузере.
|
||||||
|
if (!Platform.isAndroid && !Platform.isIOS) {
|
||||||
|
final uri = Uri.tryParse(webUrl);
|
||||||
|
if (uri != null && await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Не удалось открыть Сферум: $webUrl'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_showSferumWebView(context, webUrl);
|
_showSferumWebView(context, webUrl);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,12 +176,7 @@ class _KometColoredSegment {
|
|||||||
_KometColoredSegment(this.text, this.color);
|
_KometColoredSegment(this.text, this.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum _KometSegmentType {
|
enum _KometSegmentType { normal, colored, galaxy, pulse }
|
||||||
normal,
|
|
||||||
colored,
|
|
||||||
galaxy,
|
|
||||||
pulse,
|
|
||||||
}
|
|
||||||
|
|
||||||
class _KometSegment {
|
class _KometSegment {
|
||||||
final String text;
|
final String text;
|
||||||
@@ -232,19 +227,13 @@ class _GalaxyAnimatedTextState extends State<_GalaxyAnimatedText>
|
|||||||
return LinearGradient(
|
return LinearGradient(
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
colors: [
|
colors: [color, Color.lerp(Colors.white, Colors.black, t)!],
|
||||||
color,
|
|
||||||
Color.lerp(Colors.white, Colors.black, t)!,
|
|
||||||
],
|
|
||||||
).createShader(bounds);
|
).createShader(bounds);
|
||||||
},
|
},
|
||||||
blendMode: BlendMode.srcIn,
|
blendMode: BlendMode.srcIn,
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.text,
|
widget.text,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -330,7 +319,10 @@ class _PulseAnimatedTextState extends State<_PulseAnimatedText>
|
|||||||
return Text(text);
|
return Text(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
final messageText = afterHash.substring(quoteIndex + 1, afterHash.length - 1);
|
final messageText = afterHash.substring(
|
||||||
|
quoteIndex + 1,
|
||||||
|
afterHash.length - 1,
|
||||||
|
);
|
||||||
final baseColor = _pulseColor ?? Colors.red;
|
final baseColor = _pulseColor ?? Colors.red;
|
||||||
|
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
@@ -1754,13 +1746,14 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
|
|
||||||
if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
|
if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
videoContent = GestureDetector(
|
// На мобильных: короткий тап по видео запускает видео,
|
||||||
onTapDown: (TapDownDetails details) {
|
// а панель появляется только при длинном удержании (~0.7 c).
|
||||||
_showMessageContextMenu(context, details.globalPosition);
|
videoContent = _LongPressContextMenuWrapper(
|
||||||
},
|
|
||||||
child: videoContent,
|
child: videoContent,
|
||||||
|
onShowMenu: (offset) => _showMessageContextMenu(context, offset),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// На десктопе оставляем контекстное меню по правому клику
|
||||||
videoContent = GestureDetector(
|
videoContent = GestureDetector(
|
||||||
onSecondaryTapDown: (TapDownDetails details) {
|
onSecondaryTapDown: (TapDownDetails details) {
|
||||||
_showMessageContextMenu(context, details.globalPosition);
|
_showMessageContextMenu(context, details.globalPosition);
|
||||||
@@ -1849,15 +1842,14 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
|
|
||||||
if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
|
if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
photoContent = GestureDetector(
|
// На мобильных: короткий тап открывает фото, панель только по долгому тапу.
|
||||||
onTapDown: (TapDownDetails details) {
|
photoContent = _LongPressContextMenuWrapper(
|
||||||
_showMessageContextMenu(context, details.globalPosition);
|
|
||||||
},
|
|
||||||
child: photoContent,
|
child: photoContent,
|
||||||
|
onShowMenu: (offset) => _showMessageContextMenu(context, offset),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
photoContent = GestureDetector(
|
photoContent = GestureDetector(
|
||||||
onTapDown: (TapDownDetails details) {
|
onSecondaryTapDown: (TapDownDetails details) {
|
||||||
_showMessageContextMenu(context, details.globalPosition);
|
_showMessageContextMenu(context, details.globalPosition);
|
||||||
},
|
},
|
||||||
child: photoContent,
|
child: photoContent,
|
||||||
@@ -1986,11 +1978,11 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
|
|
||||||
if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
|
if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
videoContent = GestureDetector(
|
// На мобильных: короткий тап по видео — воспроизведение,
|
||||||
onTapDown: (TapDownDetails details) {
|
// панель реакций/действий — только по долгому тапу.
|
||||||
_showMessageContextMenu(context, details.globalPosition);
|
videoContent = _LongPressContextMenuWrapper(
|
||||||
},
|
|
||||||
child: videoContent,
|
child: videoContent,
|
||||||
|
onShowMenu: (offset) => _showMessageContextMenu(context, offset),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
videoContent = GestureDetector(
|
videoContent = GestureDetector(
|
||||||
@@ -4048,7 +4040,12 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
else if (decryptedText != null)
|
else if (decryptedText != null)
|
||||||
_buildMixedMessageContent(decryptedText!, defaultTextStyle, linkStyle, onOpenLink)
|
_buildMixedMessageContent(
|
||||||
|
decryptedText!,
|
||||||
|
defaultTextStyle,
|
||||||
|
linkStyle,
|
||||||
|
onOpenLink,
|
||||||
|
)
|
||||||
else if (message.text.contains("welcome.saved.dialog.message"))
|
else if (message.text.contains("welcome.saved.dialog.message"))
|
||||||
Linkify(
|
Linkify(
|
||||||
text:
|
text:
|
||||||
@@ -4061,7 +4058,12 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
else if (message.text.contains("komet.cosmetic.") ||
|
else if (message.text.contains("komet.cosmetic.") ||
|
||||||
message.text.contains("komet.color_"))
|
message.text.contains("komet.color_"))
|
||||||
_buildMixedMessageContent(message.text, defaultTextStyle, linkStyle, onOpenLink)
|
_buildMixedMessageContent(
|
||||||
|
message.text,
|
||||||
|
defaultTextStyle,
|
||||||
|
linkStyle,
|
||||||
|
onOpenLink,
|
||||||
|
)
|
||||||
else
|
else
|
||||||
Linkify(
|
Linkify(
|
||||||
text: message.text,
|
text: message.text,
|
||||||
@@ -4219,14 +4221,21 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
// Если маркер не найден, добавляем оставшийся текст как обычный
|
// Если маркер не найден, добавляем оставшийся текст как обычный
|
||||||
if (markerType == null) {
|
if (markerType == null) {
|
||||||
if (index < text.length) {
|
if (index < text.length) {
|
||||||
segments.add(_KometSegment(text.substring(index), _KometSegmentType.normal));
|
segments.add(
|
||||||
|
_KometSegment(text.substring(index), _KometSegmentType.normal),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем текст до маркера как обычный
|
// Добавляем текст до маркера как обычный
|
||||||
if (nextMarker > index) {
|
if (nextMarker > index) {
|
||||||
segments.add(_KometSegment(text.substring(index, nextMarker), _KometSegmentType.normal));
|
segments.add(
|
||||||
|
_KometSegment(
|
||||||
|
text.substring(index, nextMarker),
|
||||||
|
_KometSegmentType.normal,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обрабатываем найденный маркер
|
// Обрабатываем найденный маркер
|
||||||
@@ -4241,13 +4250,24 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
if (secondQuote != -1) {
|
if (secondQuote != -1) {
|
||||||
final segmentText = afterHash.substring(textStart, secondQuote);
|
final segmentText = afterHash.substring(textStart, secondQuote);
|
||||||
final color = _parseKometHexColor(hexStr, null);
|
final color = _parseKometHexColor(hexStr, null);
|
||||||
segments.add(_KometSegment(segmentText, _KometSegmentType.pulse, color: color));
|
segments.add(
|
||||||
index = nextMarker + prefix.length + secondQuote + 2; // +2 для двух кавычек
|
_KometSegment(segmentText, _KometSegmentType.pulse, color: color),
|
||||||
|
);
|
||||||
|
index =
|
||||||
|
nextMarker +
|
||||||
|
prefix.length +
|
||||||
|
secondQuote +
|
||||||
|
2; // +2 для двух кавычек
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Если парсинг не удался, добавляем как обычный текст
|
// Если парсинг не удался, добавляем как обычный текст
|
||||||
segments.add(_KometSegment(text.substring(nextMarker, nextMarker + prefix.length + 10), _KometSegmentType.normal));
|
segments.add(
|
||||||
|
_KometSegment(
|
||||||
|
text.substring(nextMarker, nextMarker + prefix.length + 10),
|
||||||
|
_KometSegmentType.normal,
|
||||||
|
),
|
||||||
|
);
|
||||||
index = nextMarker + prefix.length + 10;
|
index = nextMarker + prefix.length + 10;
|
||||||
} else if (markerType == "galaxy") {
|
} else if (markerType == "galaxy") {
|
||||||
const prefix = "komet.cosmetic.galaxy'";
|
const prefix = "komet.cosmetic.galaxy'";
|
||||||
@@ -4260,7 +4280,12 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Если парсинг не удался, добавляем как обычный текст
|
// Если парсинг не удался, добавляем как обычный текст
|
||||||
segments.add(_KometSegment(text.substring(nextMarker, textStart + 10), _KometSegmentType.normal));
|
segments.add(
|
||||||
|
_KometSegment(
|
||||||
|
text.substring(nextMarker, textStart + 10),
|
||||||
|
_KometSegmentType.normal,
|
||||||
|
),
|
||||||
|
);
|
||||||
index = textStart + 10;
|
index = textStart + 10;
|
||||||
} else if (markerType == "color") {
|
} else if (markerType == "color") {
|
||||||
const marker = 'komet.color_';
|
const marker = 'komet.color_';
|
||||||
@@ -4273,13 +4298,24 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
if (secondQuote != -1) {
|
if (secondQuote != -1) {
|
||||||
final segmentText = text.substring(textStart, secondQuote);
|
final segmentText = text.substring(textStart, secondQuote);
|
||||||
final color = _parseKometHexColor(colorStr, null);
|
final color = _parseKometHexColor(colorStr, null);
|
||||||
segments.add(_KometSegment(segmentText, _KometSegmentType.colored, color: color));
|
segments.add(
|
||||||
|
_KometSegment(
|
||||||
|
segmentText,
|
||||||
|
_KometSegmentType.colored,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
);
|
||||||
index = secondQuote + 1;
|
index = secondQuote + 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Если парсинг не удался, добавляем как обычный текст
|
// Если парсинг не удался, добавляем как обычный текст
|
||||||
segments.add(_KometSegment(text.substring(nextMarker, colorStart + 10), _KometSegmentType.normal));
|
segments.add(
|
||||||
|
_KometSegment(
|
||||||
|
text.substring(nextMarker, colorStart + 10),
|
||||||
|
_KometSegmentType.normal,
|
||||||
|
),
|
||||||
|
);
|
||||||
index = colorStart + 10;
|
index = colorStart + 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4320,8 +4356,14 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
return _GalaxyAnimatedText(text: seg.text);
|
return _GalaxyAnimatedText(text: seg.text);
|
||||||
case _KometSegmentType.pulse:
|
case _KometSegmentType.pulse:
|
||||||
// Создаем строку в правильном формате для _PulseAnimatedText
|
// Создаем строку в правильном формате для _PulseAnimatedText
|
||||||
final hexStr = seg.color!.value.toRadixString(16).padLeft(8, '0').substring(2).toUpperCase();
|
final hexStr = seg.color!.value
|
||||||
return _PulseAnimatedText(text: "komet.cosmetic.pulse#$hexStr'${seg.text}'");
|
.toRadixString(16)
|
||||||
|
.padLeft(8, '0')
|
||||||
|
.substring(2)
|
||||||
|
.toUpperCase();
|
||||||
|
return _PulseAnimatedText(
|
||||||
|
text: "komet.cosmetic.pulse#$hexStr'${seg.text}'",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
@@ -4493,6 +4535,61 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Обёртка, которая показывает контекстное меню только при долгом удержании.
|
||||||
|
///
|
||||||
|
/// - Короткий тап пропускается к дочерним жестам (открытие фото/видео и т.п.).
|
||||||
|
/// - Долгое удержание (~0.7 секунды) открывает панель реакций/действий.
|
||||||
|
class _LongPressContextMenuWrapper extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
final void Function(Offset globalPosition) onShowMenu;
|
||||||
|
|
||||||
|
const _LongPressContextMenuWrapper({
|
||||||
|
required this.child,
|
||||||
|
required this.onShowMenu,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_LongPressContextMenuWrapper> createState() =>
|
||||||
|
_LongPressContextMenuWrapperState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LongPressContextMenuWrapperState
|
||||||
|
extends State<_LongPressContextMenuWrapper> {
|
||||||
|
static const Duration _longPressDuration = Duration(milliseconds: 700);
|
||||||
|
|
||||||
|
Timer? _timer;
|
||||||
|
bool _isLongPressTriggered = false;
|
||||||
|
|
||||||
|
void _onPointerDown(PointerDownEvent event) {
|
||||||
|
_isLongPressTriggered = false;
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = Timer(_longPressDuration, () {
|
||||||
|
_isLongPressTriggered = true;
|
||||||
|
widget.onShowMenu(event.position);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPointerUpOrCancel(PointerEvent event) {
|
||||||
|
_timer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Listener(
|
||||||
|
onPointerDown: _onPointerDown,
|
||||||
|
onPointerUp: _onPointerUpOrCancel,
|
||||||
|
onPointerCancel: _onPointerUpOrCancel,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class GlobalImageStore {
|
class GlobalImageStore {
|
||||||
static final Map<String, Uint8List> _memory = {};
|
static final Map<String, Uint8List> _memory = {};
|
||||||
static final Map<String, ValueNotifier<double?>> _progress = {};
|
static final Map<String, ValueNotifier<double?>> _progress = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user