implemented chat long-polling and optimistic updates, centralized notification management, optimized avatar caching
This commit is contained in:
@@ -37,40 +37,10 @@ class AppointmentTile extends StatelessWidget {
|
||||
borderRadius: _radius,
|
||||
color: color,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
_AdaptiveTitle(
|
||||
text: appointment.subject,
|
||||
fontSize: kAppointmentTitleFontSize,
|
||||
minFontSize: kAppointmentTitleMinFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
if (isCustom) ...[
|
||||
if (description.isNotEmpty)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 1),
|
||||
child: _WrappingBody(
|
||||
text: description,
|
||||
fontSize: kAppointmentBodyFontSize,
|
||||
lineHeight: kAppointmentBodyLineHeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
for (final line
|
||||
in description
|
||||
.split('\n')
|
||||
.where((p) => p.isNotEmpty)
|
||||
.take(2))
|
||||
_ScaledLine(
|
||||
text: line,
|
||||
fontSize: kAppointmentBodyFontSize,
|
||||
),
|
||||
],
|
||||
],
|
||||
child: _TileContent(
|
||||
title: appointment.subject,
|
||||
description: description,
|
||||
isCustom: isCustom,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -96,6 +66,91 @@ class AppointmentTile extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Picks how many lines fit into the calendar slot's height. Title gets
|
||||
/// first dibs; if not even one minimum-size title line fits, the column
|
||||
/// collapses to keep the slot from overflowing.
|
||||
class _TileContent extends StatelessWidget {
|
||||
final String title;
|
||||
final String description;
|
||||
final bool isCustom;
|
||||
|
||||
const _TileContent({
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.isCustom,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scaler = MediaQuery.textScalerOf(context);
|
||||
final titleLineHeight = scaler.scale(kAppointmentTitleMinFontSize) * 1.1;
|
||||
final bodyLineHeight = scaler.scale(kAppointmentBodyFontSize) * 1.1;
|
||||
|
||||
final titleWidget = _AdaptiveTitle(
|
||||
text: title,
|
||||
fontSize: kAppointmentTitleFontSize,
|
||||
minFontSize: kAppointmentTitleMinFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
);
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final available = constraints.maxHeight;
|
||||
// Slot too short for even one min-size title line — drop text
|
||||
// entirely; the coloured rectangle is enough.
|
||||
if (available < titleLineHeight) return const SizedBox.shrink();
|
||||
|
||||
final remaining =
|
||||
(available - titleLineHeight).clamp(0.0, double.infinity);
|
||||
final bodyLineCapacity = (remaining / bodyLineHeight).floor();
|
||||
|
||||
if (isCustom) {
|
||||
if (description.isEmpty || bodyLineCapacity <= 0) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [titleWidget],
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
titleWidget,
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 1),
|
||||
child: _WrappingBody(
|
||||
text: description,
|
||||
fontSize: kAppointmentBodyFontSize,
|
||||
lineHeight: kAppointmentBodyLineHeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final maxBodyLines = bodyLineCapacity.clamp(0, 2);
|
||||
final lines = description
|
||||
.split('\n')
|
||||
.where((p) => p.isNotEmpty)
|
||||
.take(maxBodyLines)
|
||||
.toList(growable: false);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
titleWidget,
|
||||
for (final line in lines)
|
||||
_ScaledLine(text: line, fontSize: kAppointmentBodyFontSize),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the appointment title. Scales down to fit the available width via
|
||||
/// [FittedBox], but never below [minFontSize] — when even the minimum size
|
||||
/// overflows, the text is rendered at [minFontSize] with an ellipsis.
|
||||
|
||||
Reference in New Issue
Block a user