part of 'people_view.dart'; class _PersonDetail extends ConsumerWidget { const _PersonDetail({ required this.person, required this.data, required this.onEdit, required this.onDelete, required this.onQuickAction, this.compact = false, }); final PersonProfile person; final LocalDataState data; final VoidCallback onEdit; final VoidCallback onDelete; final ValueChanged<_PersonQuickAction> onQuickAction; final bool compact; @override Widget build(BuildContext context, WidgetRef ref) { final List moments = data.moments .where((RelationshipMoment moment) => moment.personId == person.id) .toList(growable: false) ..sort( (RelationshipMoment a, RelationshipMoment b) => b.at.compareTo(a.at), ); final List ideas = data.ideas .where((RelationshipIdea idea) => idea.personId == person.id) .toList(growable: false) ..sort( (RelationshipIdea a, RelationshipIdea b) => b.createdAt.compareTo(a.createdAt), ); final List reminders = data.reminders .where((ReminderRule reminder) => reminder.personId == person.id) .toList(growable: false) ..sort( (ReminderRule a, ReminderRule b) => a.nextAt.compareTo(b.nextAt), ); final List sharedMessages = data.sharedMessages .where((SharedMessageEntry entry) => entry.profileId == person.id) .toList(growable: false) ..sort( (SharedMessageEntry a, SharedMessageEntry b) => b.sharedAt.compareTo(a.sharedAt), ); final List sourceLinks = data.sourceLinks .where((SourceProfileLink link) => link.profileId == person.id) .toList(growable: false) ..sort( (SourceProfileLink a, SourceProfileLink b) => b.lastSeenAt.compareTo(a.lastSeenAt), ); final List preferenceSignals = data.preferenceSignals .where( (PersonPreferenceSignal signal) => signal.personId == person.id, ) .toList(growable: false) ..sort(_peoplePreferenceSignalSort); final List personFacts = data.personFacts .where((PersonFact fact) => fact.personId == person.id) .toList(growable: false) ..sort( (PersonFact a, PersonFact b) => b.updatedAt.compareTo(a.updatedAt), ); final List importantDates = data.importantDates .where((PersonImportantDate value) => value.personId == person.id) .toList(growable: false) ..sort( (PersonImportantDate a, PersonImportantDate b) => a.date.compareTo(b.date), ); final bool hasNotes = person.notes.trim().isNotEmpty; final String? location = person.location?.trim().isEmpty ?? true ? null : person.location!.trim(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _PersonWorkspaceHero( person: person, compact: compact, sourceLinks: sourceLinks, onEdit: onEdit, onDelete: onDelete, onQuickAction: onQuickAction, ), SizedBox(height: compact ? 12 : 14), Wrap( spacing: 10, runSpacing: 10, children: [ _PersonStatTile( label: 'Affinity', value: '${person.affinityScore}%', accent: const Color(0xFF1AB6C8), compact: compact, ), _PersonStatTile( label: 'Next moment', value: _dateTimeLabel(person.nextMoment), accent: const Color(0xFF2274E0), compact: compact, ), _PersonStatTile( label: 'Captures', value: '${moments.length}', accent: const Color(0xFF23A26D), compact: compact, ), _PersonStatTile( label: 'Ideas', value: '${ideas.where((RelationshipIdea e) => !e.isArchived).length}', accent: const Color(0xFFF4A524), compact: compact, ), ], ), const SizedBox(height: 12), _PeopleInsightSectionCard( title: 'Profile context', icon: Icons.person_outline_rounded, children: [ Wrap( spacing: 8, runSpacing: 8, children: [ _InlineMetaPill( icon: Icons.favorite_outline_rounded, label: person.relationship, ), if (location != null) _InlineMetaPill( icon: Icons.location_on_outlined, label: location, ), if (sourceLinks.isNotEmpty) _InlineMetaPill( icon: Icons.link_rounded, label: '${sourceLinks.length} linked source${sourceLinks.length == 1 ? '' : 's'}', ), ...person.aliases.map( (String alias) => _InlineMetaPill( icon: Icons.alternate_email_rounded, label: alias, ), ), ...person.tags.map( (String tag) => _InlineMetaPill(icon: Icons.sell_outlined, label: tag), ), ], ), const SizedBox(height: 10), _PeopleInsightBodyBlock( text: hasNotes ? person.notes.trim() : 'Add notes about preferences, boundaries, and what matters so future follow-ups stay relevant.', muted: !hasNotes, ), ], ), const SizedBox(height: 12), _PeopleInsightSectionCard( title: 'Captured facts', icon: Icons.fact_check_outlined, children: personFacts.isEmpty ? const [ _PeopleSectionEmpty( label: 'No structured facts yet. Shared captures can become notes, likes, dislikes, and ideas here.', ), ] : personFacts .take(compact ? 4 : 6) .map( (PersonFact fact) => _PeopleInsightRow( title: fact.label?.trim().isNotEmpty == true ? fact.label!.trim() : _factTypeLabel(fact.type), subtitle: fact.text, meta: '${_factTypeLabel(fact.type).toUpperCase()} • ${fact.sourceKind.name.toUpperCase()} • ${_shortDateTimeLabel(fact.updatedAt)}${fact.isSensitive ? ' • SENSITIVE' : ''}', actions: [ TextButton( onPressed: () async { final PersonFact? edited = await _showPersonFactEditor( context, fact: fact, ); if (edited == null) { return; } await ref .read(localRepositoryProvider.notifier) .updatePersonFact(edited); }, child: const Text('Edit'), ), TextButton( onPressed: () async { await ref .read(localRepositoryProvider.notifier) .deletePersonFact(fact.id); }, child: const Text('Delete'), ), ], ), ) .toList(growable: false), ), const SizedBox(height: 12), _PeopleInsightSectionCard( title: 'Important dates', icon: Icons.event_outlined, children: importantDates.isEmpty ? const [ _PeopleSectionEmpty( label: 'No structured dates yet. Save birthdays, anniversaries, and milestones from shared captures or manual edits.', ), ] : importantDates .take(compact ? 4 : 6) .map( (PersonImportantDate value) => _PeopleInsightRow( title: value.label, subtitle: _dateTimeLabel(value.date), meta: '${value.classification.toUpperCase()} • ${value.sourceKind.name.toUpperCase()}${value.isSensitive ? ' • SENSITIVE' : ''}', actions: [ TextButton( onPressed: () async { final PersonImportantDate? edited = await _showImportantDateEditor( context, value: value, ); if (edited == null) { return; } await ref .read(localRepositoryProvider.notifier) .updateImportantDate(edited); }, child: const Text('Edit'), ), TextButton( onPressed: () async { await ref .read(localRepositoryProvider.notifier) .deleteImportantDate(value.id); }, child: const Text('Delete'), ), ], ), ) .toList(growable: false), ), const SizedBox(height: 12), _PeopleInsightSectionCard( title: 'Chat-derived preferences', icon: Icons.psychology_alt_outlined, children: preferenceSignals.isEmpty ? const [ _PeopleSectionEmpty( label: 'No inferred preferences yet. Share messages from chat apps to build context for this person.', ), ] : preferenceSignals .map( (PersonPreferenceSignal signal) => _PeoplePreferenceSignalCard( signal: signal, compact: compact, onWhy: () => _showPeoplePreferenceEvidence( context, signal: signal, personName: person.name, ), onConfirm: () async { await ref .read(localRepositoryProvider.notifier) .confirmPreferenceSignal(signal.id); }, onDismiss: () async { await ref .read(localRepositoryProvider.notifier) .dismissPreferenceSignal(signal.id); }, ), ) .toList(growable: false), ), const SizedBox(height: 12), _PeopleInsightSectionCard( title: 'Ideas', icon: Icons.lightbulb_outline_rounded, children: ideas.isEmpty ? const [ _PeopleSectionEmpty( label: 'No ideas yet. Use Quick add to capture gift or event ideas.', ), ] : ideas .take(compact ? 4 : 6) .map( (RelationshipIdea idea) => _PeopleInsightRow( title: idea.title, subtitle: idea.details.isEmpty ? 'No details yet' : idea.details, meta: '${idea.type.name.toUpperCase()} • ${_shortDateTimeLabel(idea.createdAt)}${idea.isArchived ? ' • ARCHIVED' : ''}', ), ) .toList(growable: false), ), const SizedBox(height: 12), _PeopleInsightSectionCard( title: 'Moments', icon: Icons.auto_awesome_rounded, children: moments.isEmpty ? const [ _PeopleSectionEmpty( label: 'No captures logged yet for this person.', ), ] : moments .take(compact ? 4 : 6) .map( (RelationshipMoment moment) => _PeopleInsightRow( title: moment.title, subtitle: moment.summary, meta: '${moment.type.toUpperCase()} • ${_shortDateTimeLabel(moment.at)}', ), ) .toList(growable: false), ), const SizedBox(height: 12), _PeopleInsightSectionCard( title: 'Reminders', icon: Icons.notifications_active_outlined, children: reminders.isEmpty ? const [ _PeopleSectionEmpty( label: 'No reminders yet. Add one to keep the relationship active.', ), ] : reminders .take(compact ? 4 : 6) .map( (ReminderRule reminder) => _PeopleInsightRow( title: reminder.title, subtitle: reminder.enabled ? 'Enabled' : 'Disabled', meta: '${reminder.cadence.name.toUpperCase()} • Next ${_shortDateTimeLabel(reminder.nextAt)}', ), ) .toList(growable: false), ), const SizedBox(height: 12), _PeopleInsightSectionCard( title: 'Shared message history', icon: Icons.chat_bubble_outline_rounded, children: sharedMessages.isEmpty ? const [ _PeopleSectionEmpty( label: 'No imported shared messages for this profile yet.', ), ] : sharedMessages .take(compact ? 3 : 5) .map( (SharedMessageEntry entry) => _PeopleInsightRow( title: entry.sourceDisplayName?.trim().isNotEmpty == true ? 'Detected in text: ${entry.sourceDisplayName!.trim()}' : 'Shared from ${entry.sourceApp}', subtitle: entry.messageText.trim().isEmpty ? 'Imported message content is empty' : entry.messageText.trim(), meta: '${entry.resolvedAutomatically ? 'AUTO-LINKED' : 'MANUAL-LINK'} • ${_shortDateTimeLabel(entry.sharedAt)}', ), ) .followedBy([ if (sharedMessages.length > (compact ? 3 : 5)) Align( alignment: Alignment.centerLeft, child: TextButton.icon( onPressed: () => _showSharedMessageHistory( context, person: person, sharedMessages: sharedMessages, ), icon: const Icon(Icons.list_alt_rounded), label: Text( 'View all ${sharedMessages.length} shared texts', ), ), ), ]) .toList(growable: false), ), ], ); } } Future _showSharedMessageHistory( BuildContext context, { required PersonProfile person, required List sharedMessages, }) { return showModalBottomSheet( context: context, useSafeArea: true, isScrollControlled: true, showDragHandle: true, builder: (BuildContext context) { return DraggableScrollableSheet( expand: false, initialChildSize: 0.82, minChildSize: 0.45, maxChildSize: 0.95, builder: (BuildContext context, ScrollController scrollController) { return Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Shared texts', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 4), Text( '${person.name} • ${sharedMessages.length} item${sharedMessages.length == 1 ? '' : 's'}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppTheme.textSecondary, ), ), const SizedBox(height: 12), Expanded( child: ListView.separated( controller: scrollController, itemCount: sharedMessages.length, separatorBuilder: (_, _) => const SizedBox(height: 10), itemBuilder: (BuildContext context, int index) { final SharedMessageEntry entry = sharedMessages[index]; return _PeopleInsightRow( title: entry.sourceDisplayName?.trim().isNotEmpty == true ? 'Detected in text: ${entry.sourceDisplayName!.trim()}' : 'Shared from ${entry.sourceApp}', subtitle: entry.messageText.trim().isEmpty ? entry.url ?? 'Imported message content is empty' : entry.messageText.trim(), meta: '${entry.sourceApp.toUpperCase()} • ${entry.resolvedAutomatically ? 'AUTO-LINKED' : 'MANUAL-LINK'} • ${_shortDateTimeLabel(entry.sharedAt)}', ); }, ), ), ], ), ); }, ); }, ); } Future _showPeoplePreferenceEvidence( BuildContext context, { required PersonPreferenceSignal signal, required String personName, }) { return showModalBottomSheet( context: context, useSafeArea: true, showDragHandle: true, builder: (BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Why this signal?', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 6), Text( '${signal.label} • ${signal.category} • ${signal.polarity.name.toUpperCase()}', style: Theme.of( context, ).textTheme.bodyMedium?.copyWith(color: AppTheme.textSecondary), ), const SizedBox(height: 4), Text( 'For $personName • confidence ${(signal.confidence * 100).round()}%', style: Theme.of( context, ).textTheme.bodySmall?.copyWith(color: AppTheme.textSecondary), ), const SizedBox(height: 12), if (signal.evidenceSnippets.isEmpty) const _PeopleSectionEmpty( label: 'No evidence snippets stored yet for this signal.', ) else ...signal.evidenceSnippets.map( (String snippet) => _PeopleInsightRow( title: 'Evidence', subtitle: snippet, meta: 'Source(s): ${signal.sourceApps.join(', ')}', ), ), const SizedBox(height: 8), Align( alignment: Alignment.centerRight, child: TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Close'), ), ), ], ), ); }, ); } int _peoplePreferenceSignalSort( PersonPreferenceSignal a, PersonPreferenceSignal b, ) { int rank(PreferenceSignalStatus status) => switch (status) { PreferenceSignalStatus.inferred => 0, PreferenceSignalStatus.confirmed => 1, PreferenceSignalStatus.dismissed => 2, }; final int byStatus = rank(a.status).compareTo(rank(b.status)); if (byStatus != 0) { return byStatus; } final int byConfidence = b.confidence.compareTo(a.confidence); if (byConfidence != 0) { return byConfidence; } return a.label.toLowerCase().compareTo(b.label.toLowerCase()); } String _factTypeLabel(CapturedFactType type) { return switch (type) { CapturedFactType.note => 'Note', CapturedFactType.like => 'Like', CapturedFactType.dislike => 'Dislike', CapturedFactType.importantDate => 'Date', CapturedFactType.giftIdea => 'Gift Idea', CapturedFactType.placeIdea => 'Place Idea', CapturedFactType.activityIdea => 'Activity Idea', CapturedFactType.misc => 'Misc', }; } class _PeoplePreferenceSignalCard extends StatelessWidget { const _PeoplePreferenceSignalCard({ required this.signal, required this.compact, required this.onConfirm, required this.onDismiss, required this.onWhy, }); final PersonPreferenceSignal signal; final bool compact; final Future Function() onConfirm; final Future Function() onDismiss; final VoidCallback onWhy; @override Widget build(BuildContext context) { final Color tone = switch (signal.status) { PreferenceSignalStatus.confirmed => const Color(0xFF1D9C66), PreferenceSignalStatus.dismissed => const Color(0xFFA56A00), PreferenceSignalStatus.inferred => AppTheme.primary, }; final IconData polarityIcon = switch (signal.polarity) { PreferenceSignalPolarity.like => Icons.thumb_up_alt_outlined, PreferenceSignalPolarity.dislike => Icons.thumb_down_alt_outlined, PreferenceSignalPolarity.neutral => Icons.tune_rounded, }; return Padding( padding: const EdgeInsets.only(bottom: 10), child: Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFEAF1F4)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Wrap( spacing: 8, runSpacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [ Icon(polarityIcon, size: 16, color: tone), Text( signal.label, style: Theme.of(context).textTheme.titleSmall, ), _PeoplePreferenceBadge(label: signal.status.name, color: tone), _PeoplePreferenceBadge( label: '${(signal.confidence * 100).round()}%', color: AppTheme.textSecondary, filled: false, ), ], ), const SizedBox(height: 5), Text( '${signal.category.toUpperCase()} • seen ${signal.occurrenceCount}x', style: Theme.of( context, ).textTheme.labelSmall?.copyWith(color: AppTheme.textSecondary), ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: [ OutlinedButton(onPressed: onWhy, child: const Text('Why?')), if (signal.status != PreferenceSignalStatus.confirmed) FilledButton.tonal( onPressed: () async => onConfirm(), child: const Text('Confirm'), ), if (signal.status != PreferenceSignalStatus.dismissed) TextButton( onPressed: () async => onDismiss(), child: const Text('Dismiss'), ), ], ), if (!compact && signal.sourceApps.isNotEmpty) ...[ const SizedBox(height: 4), Text( 'Sources: ${signal.sourceApps.join(', ')}', style: Theme.of( context, ).textTheme.bodySmall?.copyWith(color: AppTheme.textSecondary), ), ], ], ), ), ); } } class _PeoplePreferenceBadge extends StatelessWidget { const _PeoplePreferenceBadge({ required this.label, required this.color, this.filled = true, }); final String label; final Color color; final bool filled; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: filled ? color.withValues(alpha: 0.12) : Colors.transparent, borderRadius: BorderRadius.circular(99), border: Border.all(color: color.withValues(alpha: filled ? 0.2 : 0.35)), ), child: Text( label.toUpperCase(), style: Theme.of(context).textTheme.labelSmall?.copyWith(color: color), ), ); } } class _PersonWorkspaceHero extends StatelessWidget { const _PersonWorkspaceHero({ required this.person, required this.compact, required this.sourceLinks, required this.onEdit, required this.onDelete, required this.onQuickAction, }); final PersonProfile person; final bool compact; final List sourceLinks; final VoidCallback onEdit; final VoidCallback onDelete; final ValueChanged<_PersonQuickAction> onQuickAction; @override Widget build(BuildContext context) { final Widget actions = Wrap( spacing: 8, runSpacing: 8, alignment: WrapAlignment.end, children: [ FilledButton.icon( onPressed: () => onQuickAction(_PersonQuickAction.capture), icon: const Icon(Icons.add_comment_outlined, size: 18), label: const Text('Capture'), ), OutlinedButton.icon( onPressed: () => onQuickAction(_PersonQuickAction.idea), icon: const Icon(Icons.lightbulb_outline_rounded, size: 18), label: const Text('Idea'), ), PopupMenuButton<_PersonQuickAction>( tooltip: 'More quick add actions', onSelected: onQuickAction, itemBuilder: (BuildContext context) => const >[ PopupMenuItem<_PersonQuickAction>( value: _PersonQuickAction.capture, child: Text('Add Capture'), ), PopupMenuItem<_PersonQuickAction>( value: _PersonQuickAction.note, child: Text('Add Note'), ), PopupMenuItem<_PersonQuickAction>( value: _PersonQuickAction.idea, child: Text('Add Idea'), ), PopupMenuItem<_PersonQuickAction>( value: _PersonQuickAction.reminder, child: Text('Add Reminder'), ), ], child: const _HeroIconButton( icon: Icons.add_circle_outline_rounded, tooltip: 'Quick add', ), ), IconButton( onPressed: onEdit, icon: const Icon(Icons.edit_rounded), tooltip: 'Edit person', ), IconButton( onPressed: onDelete, icon: const Icon(Icons.delete_outline_rounded), tooltip: 'Delete person', ), ], ); return Container( width: double.infinity, padding: EdgeInsets.all(compact ? 14 : 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(18), gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFF7FBFD), Color(0xFFEAF4FA)], ), border: Border.all(color: Colors.white.withValues(alpha: 0.9)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (compact) ...[ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _AvatarSeed(name: person.name, size: 58), const SizedBox(width: 12), Expanded(child: _HeroIdentity(person: person)), ], ), const SizedBox(height: 12), actions, ] else ...[ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _AvatarSeed(name: person.name, size: 64), const SizedBox(width: 14), Expanded(child: _HeroIdentity(person: person)), const SizedBox(width: 10), Flexible( child: Align(alignment: Alignment.topRight, child: actions), ), ], ), ], const SizedBox(height: 10), Wrap( spacing: 8, runSpacing: 8, children: [ _InlineMetaPill( icon: Icons.schedule_rounded, label: 'Next ${_dateTimeLabel(person.nextMoment)}', ), if (sourceLinks.isNotEmpty) _InlineMetaPill( icon: Icons.link_rounded, label: 'Last linked ${_shortDateTimeLabel(sourceLinks.first.lastSeenAt)}', ), ], ), ], ), ); } } class _HeroIdentity extends StatelessWidget { const _HeroIdentity({required this.person}); final PersonProfile person; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( person.name, style: Theme.of(context).textTheme.titleLarge, maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( person.relationship, style: Theme.of( context, ).textTheme.bodyLarge?.copyWith(color: AppTheme.textSecondary), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ); } } class _HeroIconButton extends StatelessWidget { const _HeroIconButton({required this.icon, required this.tooltip}); final IconData icon; final String tooltip; @override Widget build(BuildContext context) { return Tooltip( message: tooltip, child: Container( width: 40, height: 40, decoration: BoxDecoration( color: const Color(0xFFF2F7FA), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white), ), child: Icon(icon, color: AppTheme.primary, size: 20), ), ); } } class _PersonStatTile extends StatelessWidget { const _PersonStatTile({ required this.label, required this.value, required this.accent, required this.compact, }); final String label; final String value; final Color accent; final bool compact; @override Widget build(BuildContext context) { final double width = compact ? 154 : 172; return Container( width: width, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.66), borderRadius: BorderRadius.circular(14), border: Border.all(color: accent.withValues(alpha: 0.12)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: Theme.of( context, ).textTheme.labelMedium?.copyWith(color: AppTheme.textSecondary), ), const SizedBox(height: 6), Text( value, maxLines: 2, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: accent, fontWeight: FontWeight.w700, ), ), ], ), ); } } class _PeopleInsightSectionCard extends StatelessWidget { const _PeopleInsightSectionCard({ required this.title, required this.icon, required this.children, }); final String title; final IconData icon; final List children; @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFF8FBFD), borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.white.withValues(alpha: 0.9)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, size: 18, color: AppTheme.primary), const SizedBox(width: 8), Expanded( child: Text( title, style: Theme.of(context).textTheme.titleMedium, ), ), ], ), const SizedBox(height: 10), ...children, ], ), ); } } class _PeopleInsightRow extends StatelessWidget { const _PeopleInsightRow({ required this.title, required this.subtitle, required this.meta, this.actions = const [], }); final String title; final String subtitle; final String meta; final List actions; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 10), child: Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFEAF1F4)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: Theme.of(context).textTheme.titleSmall, maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( subtitle, maxLines: 3, overflow: TextOverflow.ellipsis, style: Theme.of( context, ).textTheme.bodyMedium?.copyWith(color: AppTheme.textSecondary), ), const SizedBox(height: 6), Text( meta, style: Theme.of( context, ).textTheme.labelSmall?.copyWith(color: AppTheme.textSecondary), ), if (actions.isNotEmpty) ...[ const SizedBox(height: 8), Wrap(spacing: 8, runSpacing: 8, children: actions), ], ], ), ), ); } } class _PeopleInsightBodyBlock extends StatelessWidget { const _PeopleInsightBodyBlock({required this.text, required this.muted}); final String text; final bool muted; @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFEAF1F4)), ), child: Text( text, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: muted ? AppTheme.textSecondary : null, height: 1.35, ), ), ); } } class _PeopleSectionEmpty extends StatelessWidget { const _PeopleSectionEmpty({required this.label}); final String label; @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFEAF1F4)), ), child: Text( label, style: Theme.of( context, ).textTheme.bodyMedium?.copyWith(color: AppTheme.textSecondary), ), ); } }