skip to Main Content

I’m trying to develop a chat application where users can "like" messages by double tapping a message. I want the heart icon to appear at the top right of the card. For reference, I’m sort of going off of how iMessage reactions look like.

The problem is that the corner of the icon is being clipped by the container constraints and I can’t figure out how to not make it look like this:
enter image description here

class ChatBubble extends StatefulWidget {
  final String? messageText;
  final DateTime? timeStamp;

  // I want to use this as a way to identify individual messages
  final String? messageId;
  final bool? isSelf;
  bool? isLiked;
  ChatBubble(
      {super.key,
      required this.messageText,
      required this.isSelf,
      this.isLiked = false,
      this.timeStamp,
      this.messageId});

  @override
  State<ChatBubble> createState() => _ChatBubbleState();
}

class _ChatBubbleState extends State<ChatBubble> {
  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: !widget.isSelf! ? Alignment.centerLeft : Alignment.centerRight,
      child: Padding(
        padding: !widget.isSelf!
            ? EdgeInsets.only(left: AppPadding.globalSidePadding.scale(context))
            : EdgeInsets.only(
                right: AppPadding.globalSidePadding.scale(context)),
        child: GestureDetector(
          onDoubleTap: () {
            if (widget.isSelf == false) {
              setState(() {
                widget.isLiked ??= false;
                widget.isLiked = !widget.isLiked!;
              });
            }
          },
          child: LayoutBuilder(builder: (context, constraints) {
            return Padding(
              padding: EdgeInsets.only(
                  top: AppPadding.transactionCardTopPadding.scale(context)),
              child: Stack(
                children: [
                  Card(
                    color: !widget.isSelf! ? Colors.white : AppColors.pink,
                    shape: const RoundedRectangleBorder(
                        borderRadius: BorderRadius.all(Radius.circular(
                            AppNums.globalElevatedButtonBorderRadius))),
                    child: Container(
                      constraints: BoxConstraints(
                          maxWidth: constraints.maxWidth * 0.7),
                      child: Padding(
                        padding: EdgeInsets.symmetric(
                            vertical: AppPadding.cardTopPadding
                                
                            horizontal: AppPadding.chatMessageCardSidePadding
                                
                        child: Text(widget.messageText!,
                            style: Fonts.notoSansSerif
                                .copyWith(
                                    fontSize: 10,
                                    fontWeight: FontWeight.normal,
                                    color: !widget.isSelf!
                                        ? Colors.black
                                        : Colors.white)
                                ),
                      ),
                    ),
                  ),
                  if (widget.isLiked == true)
                    Positioned(
                        top: -5,
                        right: -5,
                        child: LikedMessage(
                            reactionCameFromSelf:
                                widget.isLiked! && !widget.isSelf!)),
                ],
              ),
            );
          }),
        ),
      ),
    );
  }
}

class LikedMessage extends StatelessWidget {
  final bool? reactionCameFromSelf;
  const LikedMessage({super.key, required this.reactionCameFromSelf});

  @override
  Widget build(BuildContext context) {
    return CircleAvatar(
        radius: 20,
        backgroundColor: reactionCameFromSelf! ? Colors.grey : AppColors.pink,
        foregroundColor: reactionCameFromSelf! ? AppColors.pink : Colors.white,
        child: const Icon(Icons.favorite));
  }
}

The tricky part here is that each bubble is wrapped in an Align to control whether the cards show on the left or right side of the screen, depending on who sent the message.

I tried different combinations of using the Stack widget, but again because technically the underlying widgets of each card span the entire width of the screen, the heart icon would appear way off the card.

Where am I going wrong here?

2

Answers


  1. Set the clipBehavior property of the Stack to Clip.none.

    Stack(
      clipBehavior: Clip.none,
      // ...
    )
    

    enter image description here

    Login or Signup to reply.
  2. For this:

    enter image description here

    Use this:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: ListView.builder(
              itemCount: 20,
              itemBuilder: (context, index) => ChatBubble(isLeft: index % 2 == 0),
            ),
          ),
        );
      }
    }
    
    class ChatBubble extends StatefulWidget {
      final bool isLeft;
      const ChatBubble({required this.isLeft});
    
      @override
      _ChatBubbleState createState() => _ChatBubbleState();
    }
    
    class _ChatBubbleState extends State<ChatBubble> {
      bool isLiked = false;
    
      @override
      Widget build(BuildContext context) {
        return Align(
          alignment: widget.isLeft ? Alignment.centerRight : Alignment.centerLeft,
          child: Container(
            margin: EdgeInsets.all(12),
            padding: EdgeInsets.only(top: 2, bottom: 8, right: 2, left: 12),
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(12),
              color: Colors.grey[300],
            ),
            child: ConstrainedBox(
              constraints:
                  BoxConstraints(maxWidth: MediaQuery.of(context).size.width / 2),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Align(
                    alignment: Alignment.topRight,
                    child: IconButton(
                      constraints: BoxConstraints(),
                      padding: EdgeInsets.all(8),
                      icon: Icon(
                        Icons.favorite,
                        color: isLiked ? Colors.pink : Colors.grey,
                      ),
                      onPressed: () {
                        setState(() {
                          isLiked = !isLiked;
                        });
                      },
                    ),
                  ),
                  Text(
                    'Hello! This is a chat bubble. This is long message askldajsdlksajldsjdsljglksdgf',
                    style: TextStyle(fontSize: 16),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search