Home v2: top bar with Join/Host tabs, content-first layout

Top bar merges window chrome, profile, tabs and page icons. Join: one
compact row plus recent-server tiles with live /health status and a
needs-update badge. Host: reusable MediaPicker (Jellyfin placeholder,
Link, File) filling the width, server settings in a narrow column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-07 01:04:45 +03:00
1 parent 81cac1214d
commit 0669ec0fc2
12 files changed
+1689 -557

No files matched your search

+38 -23
View File
@@ -229,7 +229,11 @@ Future<void> showProfileDialog(BuildContext context) {
/// Home's top-left: avatar + name, click to edit.
class ProfileChip extends StatelessWidget {
const ProfileChip({super.key});
/// One line and a small avatar, for the Home top bar.
final bool dense;
/// Just the avatar, for very narrow windows.
final bool avatarOnly;
const ProfileChip({super.key, this.dense = false, this.avatarOnly = false});
@override
Widget build(BuildContext context) {
@@ -239,31 +243,42 @@ class ProfileChip extends StatelessWidget {
child: InkWell(
borderRadius: BorderRadius.circular(kRadius),
onTap: () => showProfileDialog(context),
child: Padding(
padding: const EdgeInsets.fromLTRB(6, 6, 14, 6),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const ProfileAvatar(radius: 20),
const SizedBox(width: 12),
SignalBuilder(builder: (context) {
final n = p.name.value;
final t = Theme.of(context).textTheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
child: avatarOnly
? const Padding(padding: EdgeInsets.all(4), child: ProfileAvatar(radius: 14))
: Padding(
padding: dense
? const EdgeInsets.fromLTRB(4, 4, 10, 4)
: const EdgeInsets.fromLTRB(6, 6, 14, 6),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(n.isEmpty ? 'Set your name' : n,
style: t.titleMedium?.copyWith(
color: n.isEmpty ? Colors.grey : null)),
Text('Edit profile',
style: t.bodySmall?.copyWith(color: Colors.grey)),
ProfileAvatar(radius: dense ? 14 : 20),
SizedBox(width: dense ? 8 : 12),
SignalBuilder(builder: (context) {
final n = p.name.value;
final t = Theme.of(context).textTheme;
final name = Text(n.isEmpty ? 'Set your name' : n,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: (dense ? t.titleSmall : t.titleMedium)
?.copyWith(color: n.isEmpty ? Colors.grey : null));
if (dense) {
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200), child: name);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
name,
Text('Edit profile',
style: t.bodySmall?.copyWith(color: Colors.grey)),
],
);
}),
],
);
}),
],
),
),
),
),
),
);
}