111 lines
2.7 KiB
Dart
111 lines
2.7 KiB
Dart
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;
|
|
}
|
|
|
|
/// 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.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,
|
|
),
|
|
);
|
|
}
|
|
}
|