feat: tablet landscape improvements
Shell: move Projects/News/Calendar into ShellRoute so the NavigationRail persists across all screens. Show all 6 nav destinations on tablet instead of 3+More overflow. Phone bottom nav unchanged. Chat: master-detail layout on tablet — conversations list (320px) with inline chat panel. Tapping a conversation updates state instead of pushing a new route. Knowledge: responsive grid (2 cols portrait, 3 cols landscape) with card layout showing icon, title, body preview, and tags. Fix snippet data — read json['snippet'] which the API actually sends. Bump snippet length from 120 to 200 chars. Tasks now show description as snippet. News: responsive grid with the same breakpoints. Larger snippet (5 lines) in grid mode via snippetMaxLines parameter. Briefing: centered reading column (maxWidth 700) on wide screens, matching the web UI layout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,12 +27,24 @@ class KnowledgeItemCard extends StatelessWidget {
|
||||
|
||||
String? get _subtitle {
|
||||
if (item.noteType == 'task') {
|
||||
if (item.body.trim().isNotEmpty) {
|
||||
final preview = item.body.trim().replaceAll('\n', ' ');
|
||||
return preview.length > 200 ? '${preview.substring(0, 200)}…' : preview;
|
||||
}
|
||||
if (item.dueDate != null) return 'Due ${item.dueDate}';
|
||||
return item.status;
|
||||
}
|
||||
if (item.body.trim().isEmpty) return null;
|
||||
final preview = item.body.trim().replaceAll('\n', ' ');
|
||||
return preview.length > 120 ? '${preview.substring(0, 120)}…' : preview;
|
||||
return preview.length > 200 ? '${preview.substring(0, 200)}…' : preview;
|
||||
}
|
||||
|
||||
void _onTap(BuildContext context) {
|
||||
if (item.noteType == 'task') {
|
||||
context.push('/tasks/${item.id}/edit');
|
||||
} else {
|
||||
context.push('/notes/${item.id}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -53,13 +65,64 @@ class KnowledgeItemCard extends StatelessWidget {
|
||||
)
|
||||
: null,
|
||||
trailing: item.tags.isNotEmpty ? _TagChips(tags: item.tags) : null,
|
||||
onTap: () {
|
||||
if (item.noteType == 'task') {
|
||||
context.push('/tasks/${item.id}/edit');
|
||||
} else {
|
||||
context.push('/notes/${item.id}');
|
||||
}
|
||||
},
|
||||
onTap: () => _onTap(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildGridCard(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
return Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
side: BorderSide(color: scheme.outlineVariant),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: () => _onTap(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(_icon, size: 18, color: _statusColor(context)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.title.isEmpty ? '(untitled)' : item.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textTheme.titleSmall
|
||||
?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_subtitle != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_subtitle!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 4,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: scheme.onSurfaceVariant,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (item.tags.isNotEmpty) ...[
|
||||
const Spacer(),
|
||||
_TagChips(tags: item.tags),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ class NewsCard extends StatelessWidget {
|
||||
final String? reaction; // 'up' | 'down' | null
|
||||
final void Function(int itemId, String reaction) onReaction;
|
||||
final VoidCallback? onDiscuss;
|
||||
final int snippetMaxLines;
|
||||
|
||||
const NewsCard({
|
||||
super.key,
|
||||
@@ -61,6 +62,7 @@ class NewsCard extends StatelessWidget {
|
||||
required this.reaction,
|
||||
required this.onReaction,
|
||||
this.onDiscuss,
|
||||
this.snippetMaxLines = 2,
|
||||
});
|
||||
|
||||
Future<void> _openUrl() async {
|
||||
@@ -135,7 +137,7 @@ class NewsCard extends StatelessWidget {
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
item.snippet,
|
||||
maxLines: 2,
|
||||
maxLines: snippetMaxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: scheme.onSurfaceVariant,
|
||||
|
||||
Reference in New Issue
Block a user