feat: я забыл а хули я сделал

This commit is contained in:
Jganenokk
2026-07-09 22:18:29 +07:00
parent 3a7ec23d03
commit 53bf892f04
2 changed files with 125 additions and 7 deletions
+23 -5
View File
@@ -529,6 +529,14 @@ class _ChatScreenState extends State<ChatScreen>
final p = await AppDatabase.loadActiveProfile();
if (!mounted) return;
_myId = p?.id ?? 0;
if (p != null && p.id != 0) {
final myName = [
p.firstName,
p.lastName,
].whereType<String>().where((s) => s.isNotEmpty).join(' ');
if (myName.isNotEmpty) ContactCache.put(p.id, myName);
ContactCache.putAvatar(p.id, p.baseUrl);
}
_restoreDraft();
unawaited(_loadPeerKind());
unawaited(_loadWallpaper());
@@ -4208,6 +4216,8 @@ class _ChatScreenState extends State<ChatScreen>
onReplyTap: _jumpToMessage,
onAvatarTap: _openSenderProfile,
onStickerTap: _openStickerPack,
peerName: widget.name,
peerAvatarUrl: widget.imageUrl,
);
final canReport = !isMe && !message.isControl;
@@ -5669,12 +5679,20 @@ class _SelectableMessageRowState extends State<_SelectableMessageRow> {
void _handleTap() {
if (widget.isSelectionActive()) {
widget.onToggleSelection();
} else {
_openTimer?.cancel();
_openTimer = Timer(const Duration(milliseconds: 200), () {
if (mounted && !widget.isSelectionActive()) _openMenu();
});
return;
}
final react = widget.onReact;
if (react != null && (_openTimer?.isActive ?? false)) {
_openTimer?.cancel();
_openTimer = null;
Haptics.tap();
react('❤️');
return;
}
_openTimer?.cancel();
_openTimer = Timer(const Duration(milliseconds: 200), () {
if (mounted && !widget.isSelectionActive()) _openMenu();
});
}
void _handleLongPress() {
+102 -2
View File
@@ -49,6 +49,61 @@ class _RenderZeroIntrinsicWidth extends RenderProxyBox {
double computeMaxIntrinsicWidth(double height) => 0;
}
/// A [Wrap] that reports its single-line width as the max intrinsic width, so an
/// enclosing [IntrinsicWidth] grows the bubble to fit the chips on one line
/// instead of collapsing to the widest single chip (which makes them stack).
/// It still wraps to multiple lines when the available width is smaller.
class _ReactionsWrap extends Wrap {
const _ReactionsWrap({
super.spacing,
super.runSpacing,
required super.children,
});
@override
RenderWrap createRenderObject(BuildContext context) {
return _RenderReactionsWrap(
direction: direction,
alignment: alignment,
spacing: spacing,
runAlignment: runAlignment,
runSpacing: runSpacing,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection ?? Directionality.maybeOf(context),
verticalDirection: verticalDirection,
clipBehavior: clipBehavior,
);
}
}
class _RenderReactionsWrap extends RenderWrap {
_RenderReactionsWrap({
super.direction,
super.alignment,
super.spacing,
super.runAlignment,
super.runSpacing,
super.crossAxisAlignment,
super.textDirection,
super.verticalDirection,
super.clipBehavior,
});
@override
double computeMaxIntrinsicWidth(double height) {
var total = 0.0;
var count = 0;
RenderBox? child = firstChild;
while (child != null) {
total += child.getMaxIntrinsicWidth(double.infinity);
count++;
child = childAfter(child);
}
if (count > 1) total += spacing * (count - 1);
return total;
}
}
class MessageBubble extends StatelessWidget {
static final Color _reactionChipBg = Colors.black.withValues(alpha: 0.18);
static const BorderRadius _reactionChipRadius = BorderRadius.all(
@@ -73,6 +128,8 @@ class MessageBubble extends StatelessWidget {
final void Function(String messageId)? onReplyTap;
final void Function(int senderId)? onAvatarTap;
final void Function(StickerAttachment sticker)? onStickerTap;
final String? peerName;
final String? peerAvatarUrl;
const MessageBubble({
super.key,
@@ -89,6 +146,8 @@ class MessageBubble extends StatelessWidget {
this.onReplyTap,
this.onAvatarTap,
this.onStickerTap,
this.peerName,
this.peerAvatarUrl,
});
bool _computeHasPhotoWithCaption() {
@@ -778,13 +837,27 @@ class MessageBubble extends StatelessWidget {
List<Widget> _buildReactionChipsFor(ColorScheme cs, ReactionInfo? info) {
if (info == null) return const [];
final yourReaction = info.yourReaction;
final isDialog = chatType == 'DIALOG';
final chips = <Widget>[];
for (final c in info.counters) {
final isYours = yourReaction == c.reaction;
Widget? avatar;
if (isDialog) {
final peerReacted = (c.count - (isYours ? 1 : 0)) >= 1;
avatar = peerReacted
? _reactionAvatar(cs, peerAvatarUrl, peerName)
: _reactionAvatar(
cs,
ContactCache.getAvatar(myId),
ContactCache.get(myId),
);
}
chips.add(
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
padding: EdgeInsets.fromLTRB(7, 2, avatar != null ? 3 : 7, 2),
decoration: BoxDecoration(
color: isYours
? cs.primary.withValues(alpha: 0.22)
@@ -806,6 +879,7 @@ class MessageBubble extends StatelessWidget {
),
),
],
if (avatar != null) ...[const SizedBox(width: 5), avatar],
],
),
),
@@ -814,6 +888,32 @@ class MessageBubble extends StatelessWidget {
return chips;
}
Widget _reactionAvatar(ColorScheme cs, String? url, String? name) {
const double diameter = 17;
if (url != null && url.isNotEmpty) {
return CircleAvatar(
radius: diameter / 2,
backgroundColor: cs.primaryContainer,
backgroundImage: CachedNetworkImageProvider(
url,
maxWidth: 64,
maxHeight: 64,
),
);
}
final letter = (name != null && name.isNotEmpty)
? name[0].toUpperCase()
: '?';
return CircleAvatar(
radius: diameter / 2,
backgroundColor: cs.primaryContainer,
child: Text(
letter,
style: TextStyle(fontSize: 9, color: cs.onPrimaryContainer),
),
);
}
Widget _buildControlContent(ColorScheme cs) {
final attachments = message.attachments;
if (attachments == null || attachments.isEmpty) {
@@ -921,7 +1021,7 @@ class MessageBubble extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Wrap(
child: _ReactionsWrap(
spacing: 4,
runSpacing: 4,
children: reactionChips,