Marker
A note in a conversation that nobody said: a day, someone joining, thinking.
import 'package:ashlar_icons/ashlar_icons.dart';
import 'package:flutter/widgets.dart';
import 'ui/marker.dart';
import 'ui/spinner.dart';
class MarkerDemo extends StatelessWidget {
const MarkerDemo({super.key});
@override
Widget build(BuildContext context) => const SizedBox(
width: 400,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Marker(variant: MarkerVariant.separator, child: Text('Today')),
SizedBox(height: 24),
Marker(
icon: AshlarIcon(AshlarIconsRoundedStroke.userPlus),
child: Text('Kofi joined the conversation'),
),
SizedBox(height: 24),
Marker(icon: Spinner(size: 16), child: Text('Searching the docs…')),
SizedBox(height: 24),
Marker(variant: MarkerVariant.border, child: Text('Earlier messages')),
],
),
);
}
Try its options
Each option its constructor takes, to change and see; the code below it is what you chose, to copy.
Add it
In your Flutter project:
ashlar add marker
It writes theme.dart, palette.dart and marker.dart into lib/ui, keeping any you already have.
Examples
Other ways to use it, each running live, with its code.
New messages
A line where the unread begin.
// New messages: A line where the unread begin.
import 'package:flutter/widgets.dart';
import 'ui/marker.dart';
class MarkerUnreadExample extends StatelessWidget {
const MarkerUnreadExample({super.key});
@override
Widget build(BuildContext context) => const SizedBox(
width: 420,
child: Marker(variant: MarkerVariant.separator, child: Text('3 new messages')),
);
}
API
Read from the source above: each parameter with its type, its default, and what it is for.
MarkerVariantenum
How a Marker sets itself apart.
| Value | What it is for |
|---|---|
plain | Faint text, nothing more. |
separator | Centred between two lines: a day changing, someone joining. |
border | A line under it. |
Markerclass
A note in a conversation that nobody said: "Today", "Ama joined", "Thinking…". Faint, with an optional icon.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
childrequired |
Widget |
The note: Text('Today'). | |
variant |
MarkerVariant |
MarkerVariant.plain |
Faint text, between two lines, or with a line under it. |
icon |
Widget? |
16 square, before the text. Hidden from screen readers: the text says it. |
Source
What lands in your app. It is yours from then on: change it as you would your own code, and ashlar add
will not write over it.
lib/ui/marker.dart 101 lines
// Ashlar UI: Marker. Copied into your app by `ashlar add marker`; it is yours to change.
import 'package:flutter/widgets.dart';
import 'theme.dart';
/// How a [Marker] sets itself apart.
enum MarkerVariant {
/// Faint text, nothing more.
plain,
/// Centred between two lines: a day changing, someone joining.
separator,
/// A line under it.
border,
}
/// A note in a conversation that nobody said: "Today", "Ama joined", "Thinking…". Faint,
/// with an optional icon.
///
/// ```dart
/// Marker(variant: MarkerVariant.separator, child: Text('Today'))
/// Marker(icon: Spinner(size: 16), child: Text('Searching the docs…'))
/// ```
class Marker extends StatelessWidget {
const Marker({
super.key,
required this.child,
this.variant = MarkerVariant.plain,
this.icon,
});
/// The note: Text('Today').
final Widget child;
/// Faint text, between two lines, or with a line under it.
final MarkerVariant variant;
/// 16 square, before the text. Hidden from screen readers: the text says it.
final Widget? icon;
@override
Widget build(BuildContext context) {
final t = AshlarTheme.of(context);
final c = t.colors;
final text = DefaultTextStyle.merge(
style: t.text.sm.copyWith(color: c.mutedForeground),
textAlign: variant == MarkerVariant.separator ? TextAlign.center : null,
child: child,
);
final content = [
if (icon != null) ...[
ExcludeSemantics(
child: IconTheme.merge(
data: IconThemeData(color: c.mutedForeground, size: 16),
child: SizedBox.square(dimension: 16, child: Center(child: icon)),
),
),
const SizedBox(width: 8),
],
];
Widget line() => Expanded(child: Container(height: 1, color: c.border));
final body = switch (variant) {
// The text takes what it needs, up to most of the width; the lines share the rest.
MarkerVariant.separator => LayoutBuilder(
builder: (context, box) => Row(
children: [
line(),
const SizedBox(width: 12),
...content,
ConstrainedBox(
constraints: BoxConstraints(maxWidth: box.maxWidth * .7),
child: text,
),
const SizedBox(width: 12),
line(),
],
),
),
_ => Row(
children: [
...content,
Expanded(child: text),
],
),
};
return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 16),
child: variant == MarkerVariant.border
? Container(
padding: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: c.border)),
),
child: body,
)
: body,
);
}
}