import 'package:flutter/material.dart'; enum BubbleNip { leftTop, rightBottom, none } class BubbleEdges { const BubbleEdges.only({this.top = 0, this.bottom = 0, this.left = 0, this.right = 0}); const BubbleEdges.all(double value) : top = value, bottom = value, left = value, right = value; final double top; final double bottom; final double left; final double right; EdgeInsets toEdgeInsets() => EdgeInsets.fromLTRB(left, top, right, bottom); } class BubbleStyle { const BubbleStyle({ this.color, this.borderWidth = 0, this.elevation = 0, this.margin = const BubbleEdges.only(), this.padding = const BubbleEdges.all(8), this.alignment = Alignment.centerLeft, this.nip = BubbleNip.none, this.borderRadius = 12, }); final Color? color; final double borderWidth; final double elevation; final BubbleEdges margin; final BubbleEdges padding; final Alignment alignment; final BubbleNip nip; final double borderRadius; } /// Lightweight chat bubble. Replaces the abandoned `bubble` package: renders a /// rounded container with optional shadow / border. The nip is conveyed by /// flattening one corner so the bubble visually anchors to the speaker side. class Bubble extends StatelessWidget { const Bubble({required this.child, required this.style, super.key}); final Widget child; final BubbleStyle style; BorderRadius _radius() { final r = Radius.circular(style.borderRadius); final flat = Radius.zero; switch (style.nip) { case BubbleNip.leftTop: return BorderRadius.only(topLeft: flat, topRight: r, bottomLeft: r, bottomRight: r); case BubbleNip.rightBottom: return BorderRadius.only(topLeft: r, topRight: r, bottomLeft: r, bottomRight: flat); case BubbleNip.none: return BorderRadius.all(r); } } @override Widget build(BuildContext context) { final radius = _radius(); return Align( alignment: style.alignment, child: Container( margin: style.margin.toEdgeInsets(), decoration: BoxDecoration( color: style.color, borderRadius: radius, border: style.borderWidth > 0 ? Border.all(color: Theme.of(context).dividerColor, width: style.borderWidth) : null, boxShadow: style.elevation > 0 ? [BoxShadow(color: Colors.black26, blurRadius: style.elevation * 2, offset: Offset(0, style.elevation))] : null, ), padding: style.padding.toEdgeInsets(), child: child, ), ); } }