part of '../activity_page.dart'; /// Reminders surface for the Reminders filter — due/pending NIP-ER /// reminders that deep-link to their target message. class _RemindersList extends ConsumerWidget { final ScrollController scrollController; final void Function(Reminder reminder) onOpen; final Future Function() onRefresh; const _RemindersList({ required this.scrollController, required this.onOpen, required this.onRefresh, }); @override Widget build(BuildContext context, WidgetRef ref) { final remindersAsync = ref.watch(remindersProvider); final reminders = [ for (final r in remindersAsync.value ?? const []) if (r.status == 'pending') r, ]; if (remindersAsync.isLoading && reminders.isEmpty) { return Center( child: BuzzLoadingIndicator( size: 44, semanticLabel: context.l10n.loadingReminders, ), ); } if (reminders.isEmpty) { return _EmptySurface( icon: LucideIcons.clock, message: context.l10n.noReminders, detail: context.l10n.remindersHint, ); } final now = DateTime.now().millisecondsSinceEpoch ~/ 1000; return BeeRefreshIndicator( onRefresh: onRefresh, child: ListView.builder( controller: scrollController, padding: _activityScrollPadding(context), itemCount: reminders.length, itemBuilder: (context, index) { final reminder = reminders[index]; final due = reminder.isDue(now); final title = reminder.note?.trim().isNotEmpty == true ? reminder.note!.trim() : reminder.target?.preview ?? context.l10n.reminderLabel; return ListTile( key: ValueKey('reminder-row-${reminder.id}'), leading: Icon( due ? LucideIcons.bellRing : LucideIcons.clock, size: 20, color: due ? context.colors.primary : context.colors.onSurfaceVariant, ), title: Text(title, maxLines: 2, overflow: TextOverflow.ellipsis), subtitle: reminder.notBefore != null ? Text( due ? context.l10n.dueNow : context.l10n.dueAt( _inboxTimestamp(context, reminder.notBefore!), ), ) : null, onTap: () => onOpen(reminder), ); }, ), ); } } /// Drafts surface for the Drafts filter — locally saved unsent composer /// text that reopens the target composer. class _DraftsList extends StatelessWidget { final List drafts; final ScrollController scrollController; final Map channelById; final String? myPubkey; final void Function(ComposeDraft draft) onOpen; final void Function(ComposeDraft draft) onDelete; const _DraftsList({ required this.drafts, required this.scrollController, required this.channelById, required this.myPubkey, required this.onOpen, required this.onDelete, }); @override Widget build(BuildContext context) { if (drafts.isEmpty) { return _EmptySurface( icon: LucideIcons.filePen, message: context.l10n.noDrafts, detail: context.l10n.draftsHint, ); } return ListView.builder( controller: scrollController, padding: _activityScrollPadding(context), itemCount: drafts.length, itemBuilder: (context, index) { final draft = drafts[index]; final channel = channelById[draft.channelId]; final destination = channel == null ? context.l10n.unavailableChannel : channel.isDm ? resolveDmChannelDisplayLabel(channel, currentPubkey: myPubkey) : '#${channel.name}'; return ListTile( key: ValueKey('draft-row-${draft.key}'), leading: Icon( LucideIcons.filePen, size: 20, color: context.colors.onSurfaceVariant, ), title: Text(draft.text, maxLines: 2, overflow: TextOverflow.ellipsis), subtitle: Text( draft.threadHeadId != null ? context.l10n.threadIn(destination) : destination, ), trailing: IconButton( icon: const Icon(LucideIcons.trash2, size: 18), tooltip: context.l10n.deleteDraft, onPressed: () => onDelete(draft), ), onTap: () => onOpen(draft), ); }, ); } } /// Inbox list timestamp — desktop's `formatInboxTimestamp`: /// time today, "Yesterday", short date this year, dated otherwise. String _inboxTimestamp( BuildContext context, int unixSeconds, { DateTime? now, }) { final current = now ?? DateTime.now(); final date = DateTime.fromMillisecondsSinceEpoch(unixSeconds * 1000); final today = DateTime(current.year, current.month, current.day); final day = DateTime(date.year, date.month, date.day); final dayDiff = today.difference(day).inDays; if (dayDiff == 0) { final hour = date.hour % 12 == 0 ? 12 : date.hour % 12; final minute = date.minute.toString().padLeft(2, '0'); final period = date.hour < 12 ? context.l10n.timeAm : context.l10n.timePm; return '$hour:$minute $period'; } if (dayDiff == 1) return context.l10n.timeYesterday; final months = [ context.l10n.monthJan, context.l10n.monthFeb, context.l10n.monthMar, context.l10n.monthApr, context.l10n.monthMay, context.l10n.monthJun, context.l10n.monthJul, context.l10n.monthAug, context.l10n.monthSep, context.l10n.monthOct, context.l10n.monthNov, context.l10n.monthDec, ]; final month = months[date.month - 1]; return date.year == current.year ? context.l10n.shortDate(month, date.day) : context.l10n.shortDateYear(month, date.day, date.year); }