import 'package:flutter/material.dart'; enum BubbleNip { leftTop, rightBottom, none } class BubbleStyle { const BubbleStyle({ this.color, this.borderWidth = 0, this.borderColor, this.elevation = 0, this.margin = EdgeInsets.zero, this.padding = const EdgeInsets.all(8), this.alignment = Alignment.centerLeft, this.nip = BubbleNip.none, this.borderRadius = 12, }); final Color? color; final double borderWidth; final Color? borderColor; final double elevation; final EdgeInsets margin; final EdgeInsets padding; final Alignment alignment; final BubbleNip nip; final double borderRadius; BubbleStyle copyWith({double? borderWidth, Color? borderColor}) => BubbleStyle( color: color, borderWidth: borderWidth ?? this.borderWidth, borderColor: borderColor ?? this.borderColor, elevation: elevation, margin: margin, padding: padding, alignment: alignment, nip: nip, borderRadius: borderRadius, ); } /// The "nip" is faked by flattening one corner so the bubble 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, decoration: BoxDecoration( color: style.color, borderRadius: radius, border: style.borderWidth > 0 ? Border.all( color: style.borderColor ?? 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, child: child, ), ); } }