import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../core/navigation_provider.dart'; import '../providers/data_providers.dart'; /// 饮食记录列表 class DietRecordListPage extends ConsumerWidget { const DietRecordListPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final service = ref.watch(dietServiceProvider); return Scaffold( appBar: AppBar(title: const Text('饮食记录')), body: FutureBuilder>>( future: service.getRecords(), builder: (ctx, snap) { if (snap.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator()); if (!snap.hasData || snap.data!.isEmpty) return _empty(context, '饮食记录', '暂无饮食记录,可通过「拍饮食」录入'); return ListView.builder( itemCount: snap.data!.length, itemBuilder: (ctx, i) { final d = snap.data![i]; final items = (d['foodItems'] as List?)?.cast>() ?? []; return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: ListTile( title: Text('${d['mealType'] ?? ''} ${d['totalCalories'] ?? 0}千卡'), subtitle: Text(items.map((f) => f['name']).join(' | ')), trailing: _starWidget(d['healthScore']), ), ); }, ); }, ), ); } Widget _starWidget(dynamic score) { final s = score is int ? score : 3; return Row(mainAxisSize: MainAxisSize.min, children: List.generate(5, (i) => Icon(Icons.star, size: 16, color: i < s ? const Color(0xFFF9A825) : Colors.grey[300]))); } } /// 运动计划页 class ExercisePlanPage extends ConsumerWidget { const ExercisePlanPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final plan = ref.watch(currentExercisePlanProvider); return Scaffold( appBar: AppBar(title: const Text('运动计划'), centerTitle: true), floatingActionButton: FloatingActionButton.extended( onPressed: () => _createDefaultPlan(ref, context), icon: const Icon(Icons.add), label: const Text('创建本周计划'), backgroundColor: const Color(0xFF8B9CF7), ), body: plan.when( data: (data) { if (data == null || data.isEmpty) return _empty(context, '运动计划', '暂无运动计划,点击右下角创建'); final items = (data['items'] as List?)?.cast>() ?? []; final weekDays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']; final completedCount = items.where((i) => i['isCompleted'] == true).length; final totalCount = items.where((i) => i['isRestDay'] != true).length; return ListView(children: [ _buildProgressCard(completedCount, totalCount), const SizedBox(height: 16), ...items.asMap().entries.map((entry) { final i = entry.key; final item = entry.value; final day = item['dayOfWeek'] is int ? item['dayOfWeek'] as int : i; final isRest = item['isRestDay'] == true; final isDone = item['isCompleted'] == true; return _ExercisePlanItem( day: weekDays[day], dayIndex: day, isRest: isRest, isDone: isDone, exerciseType: item['exerciseType']?.toString() ?? '', duration: item['durationMinutes'] is int ? item['durationMinutes'] as int : 0, onCheckIn: () => _checkIn(ref, item['id'], context), ); }), ]); }, loading: () => const Center(child: CircularProgressIndicator(color: Color(0xFF8B9CF7))), error: (_, __) => _empty(context, '运动计划', '暂无运动计划,点击右下角创建'), ), ); } Widget _buildProgressCard(int completed, int total) { final progress = total > 0 ? (completed / total * 100).toInt() : 0; return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFF0F2FF), borderRadius: BorderRadius.circular(20), ), child: Column(children: [ const Text('🏃 本周运动进度', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), const SizedBox(height: 12), Row(children: [ Container( width: 60, height: 60, decoration: BoxDecoration( color: const Color(0xFF8B9CF7), borderRadius: BorderRadius.circular(30), ), child: Center( child: Text('$progress%', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white)), ), ), const SizedBox(width: 16), Expanded( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('已完成 $completed/$total 天', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), const SizedBox(height: 8), Container( height: 8, decoration: BoxDecoration(color: Colors.grey[200], borderRadius: BorderRadius.circular(4)), child: FractionallySizedBox( widthFactor: progress / 100, child: Container( decoration: BoxDecoration(color: const Color(0xFF8B9CF7), borderRadius: BorderRadius.circular(4)), ), ), ), ]), ), ]), ]), ); } void _createDefaultPlan(WidgetRef ref, BuildContext context) async { final service = ref.read(exerciseServiceProvider); final today = DateTime.now(); final monday = today.subtract(Duration(days: today.weekday - 1)); final items = List.generate(7, (i) => { 'dayOfWeek': i, 'exerciseType': i == 2 || i == 5 ? '休息' : '散步', 'durationMinutes': i == 2 || i == 5 ? 0 : 30, 'isRestDay': i == 2 || i == 5, }); await service.createPlan({ 'weekStartDate': '${monday.year}-${monday.month.toString().padLeft(2, '0')}-${monday.day.toString().padLeft(2, '0')}', 'items': items, }); ref.invalidate(currentExercisePlanProvider); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('运动计划已创建 ✅'), backgroundColor: Color(0xFF8B9CF7), )); } void _checkIn(WidgetRef ref, String itemId, BuildContext context) async { final service = ref.read(exerciseServiceProvider); await service.checkIn(itemId); ref.invalidate(currentExercisePlanProvider); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('打卡成功 ✅'), backgroundColor: Color(0xFF43A047), )); } } class _ExercisePlanItem extends StatelessWidget { final String day; final int dayIndex; final bool isRest; final bool isDone; final String exerciseType; final int duration; final VoidCallback onCheckIn; const _ExercisePlanItem({ required this.day, required this.dayIndex, required this.isRest, required this.isDone, required this.exerciseType, required this.duration, required this.onCheckIn, }); @override Widget build(BuildContext context) { final today = DateTime.now().weekday - 1; final isToday = dayIndex == today; return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isToday ? const Color(0xFFFEFCE8) : Colors.white, borderRadius: BorderRadius.circular(16), border: isToday ? Border.all(color: const Color(0xFFFCD34D), width: 2) : null, boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(8), blurRadius: 4, offset: const Offset(0, 2))], ), child: Row(children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: isDone ? const Color(0xFFDCFCE7) : isRest ? const Color(0xFFF3F4F6) : const Color(0xFFF0F2FF), borderRadius: BorderRadius.circular(12), ), child: isDone ? const Icon(Icons.check, size: 20, color: Color(0xFF43A047)) : isRest ? const Icon(Icons.coffee, size: 20, color: Color(0xFF999999)) : const Icon(Icons.directions_run, size: 20, color: Color(0xFF8B9CF7)), ), const SizedBox(width: 12), Expanded( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Text(day, style: TextStyle(fontSize: 16, fontWeight: isToday ? FontWeight.w600 : FontWeight.w500)), if (isToday) const SizedBox(width: 4), if (isToday) const Text('(今天)', style: TextStyle(fontSize: 12, color: Color(0xFFF59E0B))), ]), const SizedBox(height: 4), Text( isRest ? '休息日,好好休息' : '$exerciseType $duration分钟', style: TextStyle(fontSize: 14, color: Colors.grey[500]), ), ]), ), if (!isRest && !isDone) ElevatedButton( onPressed: onCheckIn, child: const Text('打卡'), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8B9CF7), foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), ), ), if (isDone) const Text('已完成', style: TextStyle(fontSize: 14, color: Color(0xFF43A047))), ]), ); } } /// 复查列表 class FollowUpListPage extends ConsumerWidget { const FollowUpListPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( appBar: AppBar(title: const Text('复查随访'), centerTitle: true), floatingActionButton: FloatingActionButton( onPressed: () => _showAddDialog(context), child: const Icon(Icons.add), backgroundColor: const Color(0xFF8B9CF7), ), body: ListView(children: _mockFollowUps.map((item) => _FollowUpItem(item: item)).toList()), ); } void _showAddDialog(BuildContext context) { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('添加复查提醒'), content: Column(mainAxisSize: MainAxisSize.min, children: [ TextField(decoration: const InputDecoration(labelText: '医院名称')), const SizedBox(height: 12), TextField(decoration: const InputDecoration(labelText: '科室')), const SizedBox(height: 12), TextField(decoration: const InputDecoration(labelText: '日期', hintText: 'YYYY-MM-DD')), const SizedBox(height: 12), TextField(decoration: const InputDecoration(labelText: '备注')), ]), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('取消')), TextButton( onPressed: () { Navigator.pop(ctx); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('复查提醒已添加 ✅'), backgroundColor: Color(0xFF8B9CF7), )); }, child: const Text('保存'), ), ], ), ); } } final _mockFollowUps = [ {'id': '1', 'hospital': '协和医院', 'department': '心内科', 'date': '2025-01-20', 'type': '复诊', 'status': 'upcoming', 'notes': '常规复查,带齐病历'}, {'id': '2', 'hospital': '人民医院', 'department': '骨科', 'date': '2025-01-25', 'type': '复查', 'status': 'upcoming', 'notes': '术后3个月复查'}, {'id': '3', 'hospital': '协和医院', 'department': '心内科', 'date': '2024-12-15', 'type': '复诊', 'status': 'completed', 'notes': '已完成'}, ]; class _FollowUpItem extends StatelessWidget { final Map item; const _FollowUpItem({required this.item}); @override Widget build(BuildContext context) { final isCompleted = item['status'] == 'completed'; return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 4, offset: const Offset(0, 2))], ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: isCompleted ? const Color(0xFFDCFCE7) : const Color(0xFFFEFCE8), borderRadius: BorderRadius.circular(8), ), child: Text( isCompleted ? '已完成' : '待就诊', style: TextStyle(fontSize: 12, color: isCompleted ? const Color(0xFF43A047) : const Color(0xFFF59E0B)), ), ), const SizedBox(width: 8), Text(item['type']?.toString() ?? '', style: TextStyle(fontSize: 14, color: Colors.grey[500])), ]), const SizedBox(height: 12), Text(item['hospital']?.toString() ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), const SizedBox(height: 4), Text('${item['department']} · ${item['date']}', style: TextStyle(fontSize: 14, color: Colors.grey[500])), if ((item['notes']?.toString() ?? '').isNotEmpty) ...[ const SizedBox(height: 8), Text(item['notes']?.toString() ?? '', style: TextStyle(fontSize: 14, color: Colors.grey[600])), ], ]), ); } } /// 健康档案 class HealthArchivePage extends ConsumerWidget { const HealthArchivePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final service = ref.watch(userServiceProvider); return Scaffold( appBar: AppBar(title: const Text('健康档案')), body: FutureBuilder?>( future: service.getHealthArchive(), builder: (ctx, snap) { if (snap.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator()); final data = snap.data; if (data == null || data.isEmpty) return _empty(context, '暂无健康档案', '可通过 AI 对话或手动填写'); return ListView( padding: const EdgeInsets.all(16), children: [ _Section(title: '基本信息', children: [ _Field('诊断', data['diagnosis']), _Field('手术类型', data['surgeryType']), _Field('手术日期', data['surgeryDate']), ]), _Section(title: '病史与限制', children: [ _Field('过敏史', _listStr(data['allergies'])), _Field('饮食限制', _listStr(data['dietRestrictions'])), _Field('慢性病史', _listStr(data['chronicDiseases'])), _Field('家族病史', data['familyHistory']), ]), ], ); }, ), ); } String _listStr(dynamic list) => list is List ? list.join('、') : '--'; } class _Section extends StatelessWidget { final String title; final List children; const _Section({required this.title, required this.children}); @override Widget build(BuildContext context) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding(padding: const EdgeInsets.only(bottom: 8, top: 16), child: Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)))), ...children, ]); } class _Field extends StatelessWidget { final String label; final String? value; const _Field(this.label, this.value); @override Widget build(BuildContext context) => Padding( padding: const EdgeInsets.only(bottom: 6), child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(width: 80, child: Text('$label:', style: const TextStyle(fontSize: 14, color: Color(0xFF666666)))), Expanded(child: Text(value ?? '--', style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)))), ]), ); } /// 编辑资料 class EditProfilePage extends ConsumerStatefulWidget { const EditProfilePage({super.key}); @override ConsumerState createState() => _EditProfilePageState(); } class _EditProfilePageState extends ConsumerState { final _nameCtrl = TextEditingController(); final _genderCtrl = TextEditingController(); final _birthCtrl = TextEditingController(); @override void dispose() { _nameCtrl.dispose(); _genderCtrl.dispose(); _birthCtrl.dispose(); super.dispose(); } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _load()); } void _load() async { final p = await ref.read(userServiceProvider).getProfile(); if (p != null && mounted) { setState(() { _nameCtrl.text = p['name'] ?? ''; _genderCtrl.text = p['gender'] ?? ''; _birthCtrl.text = p['birthDate'] ?? ''; }); } } Future _save() async { await ref.read(userServiceProvider).updateProfile(name: _nameCtrl.text, gender: _genderCtrl.text, birthDate: _birthCtrl.text); if (mounted) Navigator.pop(context); } @override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text('编辑资料')), body: ListView(padding: const EdgeInsets.all(16), children: [ TextField(controller: _nameCtrl, decoration: const InputDecoration(labelText: '姓名')), const SizedBox(height: 16), TextField(controller: _genderCtrl, decoration: const InputDecoration(labelText: '性别')), const SizedBox(height: 16), TextField(controller: _birthCtrl, decoration: const InputDecoration(labelText: '出生日期', hintText: 'YYYY-MM-DD')), const SizedBox(height: 32), SizedBox(width: double.infinity, child: ElevatedButton(onPressed: _save, child: const Text('保存'))), ]), ); } /// 健康日历 class HealthCalendarPage extends ConsumerStatefulWidget { const HealthCalendarPage({super.key}); @override ConsumerState createState() => _HealthCalendarPageState(); } class _HealthCalendarPageState extends ConsumerState { DateTime _currentMonth = DateTime.now(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('健康日历'), centerTitle: true), body: Column(children: [ _buildMonthHeader(), _buildWeekdayHeader(), _buildCalendarGrid(), const SizedBox(height: 16), _buildLegend(), ]), ); } Widget _buildMonthHeader() { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: const Icon(Icons.chevron_left, size: 32), onPressed: () => setState(() => _currentMonth = DateTime(_currentMonth.year, _currentMonth.month - 1)), ), Text( '${_currentMonth.year}年${_currentMonth.month}月', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600), ), IconButton( icon: const Icon(Icons.chevron_right, size: 32), onPressed: () => setState(() => _currentMonth = DateTime(_currentMonth.year, _currentMonth.month + 1)), ), ], ); } Widget _buildWeekdayHeader() { const weekdays = ['日', '一', '二', '三', '四', '五', '六']; return Row(children: weekdays.map((day) => Expanded( child: Center(child: Text(day, style: TextStyle(fontSize: 14, color: Colors.grey[500]))), )).toList()); } Widget _buildCalendarGrid() { final firstDay = DateTime(_currentMonth.year, _currentMonth.month, 1); final lastDay = DateTime(_currentMonth.year, _currentMonth.month + 1, 0); final daysInMonth = lastDay.day; final startWeekday = firstDay.weekday % 7; final days = List.generate(42, (i) { final dayIndex = i - startWeekday; if (dayIndex < 0 || dayIndex >= daysInMonth) return null; return dayIndex + 1; }); return Expanded( child: GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 7), itemCount: 42, itemBuilder: (ctx, i) { final day = days[i]; if (day == null) return const SizedBox(); return _buildDayCell(day); }, ), ); } Widget _buildDayCell(int day) { final date = DateTime(_currentMonth.year, _currentMonth.month, day); final today = DateTime.now(); final isToday = date.year == today.year && date.month == today.month && date.day == today.day; final events = _getEvents(date); return Container( decoration: isToday ? BoxDecoration( color: const Color(0xFF8B9CF7), borderRadius: BorderRadius.circular(20), ) : null, child: Stack( alignment: Alignment.center, children: [ Text( '$day', style: TextStyle( fontSize: 16, color: isToday ? Colors.white : Colors.black, fontWeight: isToday ? FontWeight.w600 : FontWeight.normal, ), ), if (events.isNotEmpty) Positioned( bottom: 4, child: Row(children: events.map((type) => Container( width: 6, height: 6, margin: const EdgeInsets.symmetric(horizontal: 1), decoration: BoxDecoration( color: _getEventColor(type), borderRadius: BorderRadius.circular(3), ), )).toList()), ), ], ), ); } List _getEvents(DateTime date) { final events = []; if (date.day == 5 || date.day == 12 || date.day == 19 || date.day == 26) events.add('medication'); if (date.day == 8 || date.day == 15 || date.day == 22 || date.day == 29) events.add('exercise'); if (date.day == 20) events.add('followup'); return events; } Color _getEventColor(String type) { switch (type) { case 'medication': return const Color(0xFF8B9CF7); case 'exercise': return const Color(0xFF43A047); case 'followup': return const Color(0xFFF59E0B); default: return Colors.grey; } } Widget _buildLegend() { final items = [ {'color': const Color(0xFF8B9CF7), 'label': '用药提醒'}, {'color': const Color(0xFF43A047), 'label': '运动计划'}, {'color': const Color(0xFFF59E0B), 'label': '复查随访'}, ]; return Container( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: items.map((item) => Row(children: [ Container(width: 10, height: 10, decoration: BoxDecoration(color: item['color'] as Color, borderRadius: BorderRadius.circular(5))), const SizedBox(width: 4), Text(item['label'] as String, style: TextStyle(fontSize: 12, color: Colors.grey[600])), const SizedBox(width: 20), ])).toList()), ); } } /// 静态文本页 class StaticTextPage extends ConsumerWidget { final String type; const StaticTextPage({super.key, required this.type}); @override Widget build(BuildContext context, WidgetRef ref) { final titles = {'privacy': '隐私协议', 'terms': '服务协议', 'about': '关于健康管家'}; final contents = { 'privacy': '''## 隐私政策 更新日期:2026年1月1日 ### 一、信息收集 我们收集以下类型的信息: - 账户信息:手机号、昵称、头像(您主动提供) - 健康数据:血压、心率、血糖、血氧、体重等健康指标记录 - 用药信息:药品名称、剂量、服药时间等用药计划数据 - 饮食记录:通过拍照或手动录入的饮食数据 - 设备信息:设备型号、操作系统版本(用于适配优化) - 日志信息:App 使用情况、崩溃报告 ### 二、信息使用 我们使用您的信息用于以下目的: - 提供和改进健康管理服务 - AI 健康分析和个性化建议 - 用药提醒和复查通知推送 - App 功能优化和问题修复 ### 三、信息保护 - 所有健康数据均采用 HTTPS 加密传输 - 数据存储于安全服务器,采用行业标准的加密措施 - 我们不会向任何第三方出售、出租或共享您的个人健康数据 - 医生仅可查看其签约患者的数据,且需经过您的授权 ### 四、信息保留 - 对话记录保留 30 天后自动删除 - 您可以随时删除自己的健康数据和对话记录 - 账号注销后,所有数据将在 7 天内永久删除 ### 五、您的权利 - 查看和导出您的个人数据 - 修改不准确的个人信息 - 删除不需要的数据 - 注销账号并清除所有数据 - 关闭推送通知 ### 六、联系我们 如有任何关于隐私的问题,请联系: 邮箱:privacy@healthbutler.com 电话:400-xxx-xxxx''', 'about': '''## 关于健康管家 版本:v1.0.0 (Build 20260101) ### 产品介绍 健康管家是一款面向心脏术后康复患者的私人 AI 健康管理应用。以对话为核心交互方式,患者可以通过自然语言记录健康数据、获取饮食运动建议、管理用药、解读检查报告。 ### 核心功能 - AI 智能问诊:基于大语言模型的健康咨询服务 - 健康数据管理:血压、心率、血糖、血氧、体重的记录与趋势分析 - 智能用药管理:AI 解析处方,自动生成用药计划和提醒 - 饮食识别分析:拍照即可识别食物种类、估算热量营养素 - 报告智能解读:上传检查报告,AI 自动提取指标并预解读 - 运动计划管理:制定和追踪每日运动目标 - 在线医生问诊:与签约医生进行远程咨询 ### 开发团队 由专业医疗团队与 AI 技术团队联合打造。 ### 技术支持 如遇到问题或有建议,请通过以下方式联系我们: - 在线客服:App 内「设置」→「意见反馈」 - 客服热线:400-xxx-xxxx(工作日 9:00-18:00) ### 版权声明 © 2025-2026 健康管家团队。保留所有权利。 本软件受中华人民共和国著作权法保护。''', }; return Scaffold( appBar: AppBar( backgroundColor: Colors.white, elevation: 0, leading: IconButton(icon: const Icon(Icons.chevron_left), onPressed: () => popRoute(ref)), title: Text(titles[type] ?? '', style: const TextStyle(color: Color(0xFF1A1A1A), fontWeight: FontWeight.w600)), centerTitle: true, ), body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Text(contents[type] ?? '内容加载中...', style: const TextStyle(fontSize: 14, height: 1.8, color: Color(0xFF333333))), ), ); } } /// 设备管理(占位) class DeviceManagementPage extends ConsumerWidget { const DeviceManagementPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) => _empty(context, '设备管理', '暂无绑定设备'); } Widget _empty(BuildContext context, String title, String subtitle) => Scaffold( appBar: AppBar(title: Text(title)), body: Center(child: Column(mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.inbox_outlined, size: 64, color: Colors.grey[300]), const SizedBox(height: 12), Text(subtitle, style: Theme.of(context).textTheme.bodyMedium), ])), );