починил вход, выход с ака, сделал вход сразу после кода, задержку перед появлением менюшки с эмодзи чтобы мозги не ебало

This commit is contained in:
jganenok
2025-12-04 07:54:30 +07:00
parent 9e07617293
commit 4729c16a13
2 changed files with 158 additions and 41 deletions

View File

@@ -28,6 +28,7 @@ import 'package:gwid/utils/user_id_lookup_screen.dart';
import 'package:gwid/screens/music_library_screen.dart';
import 'package:gwid/widgets/message_preview_dialog.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/widgets/contact_name_widget.dart';
import 'package:gwid/widgets/contact_avatar_widget.dart';
@@ -1849,6 +1850,25 @@ class _ChatsScreenState extends State<ChatsScreen>
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) {
_showSferumWebView(context, webUrl);
}

View File

@@ -176,12 +176,7 @@ class _KometColoredSegment {
_KometColoredSegment(this.text, this.color);
}
enum _KometSegmentType {
normal,
colored,
galaxy,
pulse,
}
enum _KometSegmentType { normal, colored, galaxy, pulse }
class _KometSegment {
final String text;
@@ -232,19 +227,13 @@ class _GalaxyAnimatedTextState extends State<_GalaxyAnimatedText>
return LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
color,
Color.lerp(Colors.white, Colors.black, t)!,
],
colors: [color, Color.lerp(Colors.white, Colors.black, t)!],
).createShader(bounds);
},
blendMode: BlendMode.srcIn,
child: Text(
widget.text,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
);
},
@@ -330,7 +319,10 @@ class _PulseAnimatedTextState extends State<_PulseAnimatedText>
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;
return AnimatedBuilder(
@@ -1754,13 +1746,14 @@ class ChatMessageBubble extends StatelessWidget {
if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
if (isMobile) {
videoContent = GestureDetector(
onTapDown: (TapDownDetails details) {
_showMessageContextMenu(context, details.globalPosition);
},
// На мобильных: короткий тап по видео запускает видео,
// а панель появляется только при длинном удержании (~0.7 c).
videoContent = _LongPressContextMenuWrapper(
child: videoContent,
onShowMenu: (offset) => _showMessageContextMenu(context, offset),
);
} else {
// На десктопе оставляем контекстное меню по правому клику
videoContent = GestureDetector(
onSecondaryTapDown: (TapDownDetails details) {
_showMessageContextMenu(context, details.globalPosition);
@@ -1849,15 +1842,14 @@ class ChatMessageBubble extends StatelessWidget {
if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
if (isMobile) {
photoContent = GestureDetector(
onTapDown: (TapDownDetails details) {
_showMessageContextMenu(context, details.globalPosition);
},
// На мобильных: короткий тап открывает фото, панель только по долгому тапу.
photoContent = _LongPressContextMenuWrapper(
child: photoContent,
onShowMenu: (offset) => _showMessageContextMenu(context, offset),
);
} else {
photoContent = GestureDetector(
onTapDown: (TapDownDetails details) {
onSecondaryTapDown: (TapDownDetails details) {
_showMessageContextMenu(context, details.globalPosition);
},
child: photoContent,
@@ -1986,11 +1978,11 @@ class ChatMessageBubble extends StatelessWidget {
if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
if (isMobile) {
videoContent = GestureDetector(
onTapDown: (TapDownDetails details) {
_showMessageContextMenu(context, details.globalPosition);
},
// На мобильных: короткий тап по видео — воспроизведение,
// панель реакций/действий — только по долгому тапу.
videoContent = _LongPressContextMenuWrapper(
child: videoContent,
onShowMenu: (offset) => _showMessageContextMenu(context, offset),
);
} else {
videoContent = GestureDetector(
@@ -4048,7 +4040,12 @@ class ChatMessageBubble extends StatelessWidget {
),
)
else if (decryptedText != null)
_buildMixedMessageContent(decryptedText!, defaultTextStyle, linkStyle, onOpenLink)
_buildMixedMessageContent(
decryptedText!,
defaultTextStyle,
linkStyle,
onOpenLink,
)
else if (message.text.contains("welcome.saved.dialog.message"))
Linkify(
text:
@@ -4061,7 +4058,12 @@ class ChatMessageBubble extends StatelessWidget {
)
else if (message.text.contains("komet.cosmetic.") ||
message.text.contains("komet.color_"))
_buildMixedMessageContent(message.text, defaultTextStyle, linkStyle, onOpenLink)
_buildMixedMessageContent(
message.text,
defaultTextStyle,
linkStyle,
onOpenLink,
)
else
Linkify(
text: message.text,
@@ -4219,14 +4221,21 @@ class ChatMessageBubble extends StatelessWidget {
// Если маркер не найден, добавляем оставшийся текст как обычный
if (markerType == null) {
if (index < text.length) {
segments.add(_KometSegment(text.substring(index), _KometSegmentType.normal));
segments.add(
_KometSegment(text.substring(index), _KometSegmentType.normal),
);
}
break;
}
// Добавляем текст до маркера как обычный
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) {
final segmentText = afterHash.substring(textStart, secondQuote);
final color = _parseKometHexColor(hexStr, null);
segments.add(_KometSegment(segmentText, _KometSegmentType.pulse, color: color));
index = nextMarker + prefix.length + secondQuote + 2; // +2 для двух кавычек
segments.add(
_KometSegment(segmentText, _KometSegmentType.pulse, color: color),
);
index =
nextMarker +
prefix.length +
secondQuote +
2; // +2 для двух кавычек
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;
} else if (markerType == "galaxy") {
const prefix = "komet.cosmetic.galaxy'";
@@ -4260,7 +4280,12 @@ class ChatMessageBubble extends StatelessWidget {
continue;
}
// Если парсинг не удался, добавляем как обычный текст
segments.add(_KometSegment(text.substring(nextMarker, textStart + 10), _KometSegmentType.normal));
segments.add(
_KometSegment(
text.substring(nextMarker, textStart + 10),
_KometSegmentType.normal,
),
);
index = textStart + 10;
} else if (markerType == "color") {
const marker = 'komet.color_';
@@ -4273,13 +4298,24 @@ class ChatMessageBubble extends StatelessWidget {
if (secondQuote != -1) {
final segmentText = text.substring(textStart, secondQuote);
final color = _parseKometHexColor(colorStr, null);
segments.add(_KometSegment(segmentText, _KometSegmentType.colored, color: color));
segments.add(
_KometSegment(
segmentText,
_KometSegmentType.colored,
color: color,
),
);
index = secondQuote + 1;
continue;
}
}
// Если парсинг не удался, добавляем как обычный текст
segments.add(_KometSegment(text.substring(nextMarker, colorStart + 10), _KometSegmentType.normal));
segments.add(
_KometSegment(
text.substring(nextMarker, colorStart + 10),
_KometSegmentType.normal,
),
);
index = colorStart + 10;
}
}
@@ -4320,8 +4356,14 @@ class ChatMessageBubble extends StatelessWidget {
return _GalaxyAnimatedText(text: seg.text);
case _KometSegmentType.pulse:
// Создаем строку в правильном формате для _PulseAnimatedText
final hexStr = seg.color!.value.toRadixString(16).padLeft(8, '0').substring(2).toUpperCase();
return _PulseAnimatedText(text: "komet.cosmetic.pulse#$hexStr'${seg.text}'");
final hexStr = seg.color!.value
.toRadixString(16)
.padLeft(8, '0')
.substring(2)
.toUpperCase();
return _PulseAnimatedText(
text: "komet.cosmetic.pulse#$hexStr'${seg.text}'",
);
}
}).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 {
static final Map<String, Uint8List> _memory = {};
static final Map<String, ValueNotifier<double?>> _progress = {};