You can group objects by date (start of day for example, considering your example) and then show whatever you want (without libs).
Example:
1 - We have some class with dates to group by:
class WalletAccountRecord {
String id;
String name;
DateTime date;
....
2 - We have list with such items (in our case coming from api, but any building way is ok for approach understanding)
List<WalletAccountRecord> records = response.records;
3 - Then we group this list by dates (by start of day)
Map<Jiffy, List<WalletAccountRecord>> recordsByDays = {};
for (var record in records) {
Jiffy date = Jiffy.parseFromDateTime(record.date).startOf(Unit.day);
recordsByDays.update(date, (List<WalletAccountRecord> value) => [record, ...value], ifAbsent: () => [record]);
}
_logger.d('Records by Dates: ${recordsByDays.length} days -> ${recordsByDays.keys}');
4 - now, considering all the data is grouped into Map we can display it in wanted way. Full example with our UI:
class WalletRecords extends StatelessWidget {
final AnalyticsService _analyticsService = Get.find();
final WalletRecordsStateController _walletRecordsStateController = Get.find();
final Logger _logger = LoggerHelper.logger();
@override
Widget build(BuildContext context) {
return Obx(() => _recordsItems());
}
Widget _recordsItems() {
RecordsSearchResultRx recordsRx = _walletRecordsStateController.recordsGet();
//TODO Revision flow. if no wallet (when??) then just show 'no records yet'
//TODO no wallet/loading handle
List<WalletAccountRecord> records = recordsRx.records;
Map<Jiffy, List<WalletAccountRecord>> recordsByDays = {};
for (var record in records) {
Jiffy date = Jiffy.parseFromDateTime(record.date).startOf(Unit.day);
recordsByDays.update(date, (List<WalletAccountRecord> value) => [record, ...value], ifAbsent: () => [record]);
}
_logger.d('Records by Dates: ${recordsByDays.length} days -> ${recordsByDays.keys}');
return _recordsByDates(recordsByDays);
}
Widget _recordsByDates(Map<Jiffy, List<WalletAccountRecord>> recordsByDays) {
return ListView.builder(
itemCount: recordsByDays.length,
shrinkWrap: true,
itemBuilder: (context, int index) {
var date = recordsByDays.keys.elementAt(index);
return _recordsByDate(date, recordsByDays[date] ?? []);
},
);
}
Widget _recordsByDate(Jiffy date, List<WalletAccountRecord> recordsByDay) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
child: Container(
decoration: BoxDecoration(
color: UiConstants.colorPrimaryDark.withOpacity(0.3),
border: Border.all(color: Colors.grey),
borderRadius: const BorderRadius.all(Radius.circular(15))),
child: Column(children: [
Padding(
padding: const EdgeInsets.only(left: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(date.format(pattern: 'd.MM.yy'),
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600))),
Wrap(children: [
IconButton(icon: const Icon(LineIcons.plus), onPressed: () => {}),
IconButton(icon: const Icon(LineIcons.pen), onPressed: () => {}),
]),
],
),
),
const Divider(height: 5, indent: 10, endIndent: 10),
_recordItems(recordsByDay),
]),
),
);
}
Widget _recordItems(List<WalletAccountRecord> records) {
return ListView.separated(
itemCount: records.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (BuildContext context, int index) => const Divider(height: 1, indent: 10, endIndent: 10),
itemBuilder: (context, index) {
return _recordOverview(records[index]);
},
);
}
Widget _recordOverview(WalletAccountRecord record) {
return ListTile(
dense: true,
horizontalTitleGap: 0,
title: Text(record.category!, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(record.account.accountName),
Text(record.operationType.name),
Text(record.note ?? ''),
],
),
contentPadding: const EdgeInsets.only(left: 3),
trailing: Text('${record.amount.amount} ${record.amount.currencyCode}'),
//todo dynamic based on account color per account
leading: Container(color: Colors.blue, width: 5, padding: EdgeInsets.zero, margin: EdgeInsets.zero),
);
}
}