fix(studio): live-search exception, daemon-card width, featured tile size; add Start-hub button (v0.20.1)
Three reported bugs and one onramp gap: - Store live-search threw `setState() callback returned a Future`. The `=>` body of `_runSearch` evaluated to the Future returned by `_load()`. Switched to a block body so the closure resolves to void; the future itself is still awaited by the FutureBuilder. - Doctor's Daemon-control card was visibly narrower than its siblings — its only stretching child was a Wrap of small buttons. Wrapped the content in `SizedBox(width: infinity)` so the card fills the section's width like every other panel. Card now leads with the live `running on local: 127.0.0.1:50051` status row + status pill so operators see state at a glance, then offers Restart / Start (when stopped) / Stop / Status. - Featured tiles were 320×152 against the regular grid's ~360×168, so the editorial picks looked subordinate to the dense grid. Bumped to 480×220 with bigger icon, larger title, larger tagline so the strip reads as a hero band. - ConnectionPill in the sidebar now offers an inline "Start hub" button when the daemon is unreachable. Spawns `fai daemon start` via SystemActions; the shell-level health poll picks the daemon up on its next tick. Closes the chicken-and-egg for Windows users who can't open a shell to bootstrap. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
26691b7471
commit
fb7352d182
4 changed files with 256 additions and 96 deletions
|
|
@ -64,7 +64,15 @@ class _StorePageState extends State<StorePage> {
|
|||
return _installedOnly ? all.where((e) => e.installed).toList() : all;
|
||||
}
|
||||
|
||||
void _runSearch() => setState(() => _future = _load());
|
||||
void _runSearch() {
|
||||
// Block body, so the closure's return type is `void`
|
||||
// rather than `Future`. setState() rejects closures that
|
||||
// resolve to a Future — the assignment must be synchronous
|
||||
// even though the future itself is awaited later.
|
||||
setState(() {
|
||||
_future = _load();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _openDetail(StoreItem item) async {
|
||||
final changed = await _StoreDetailSheet.show(context, item, _locale);
|
||||
|
|
@ -533,7 +541,11 @@ class _FeaturedStrip extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: FaiSpace.sm),
|
||||
SizedBox(
|
||||
height: 152,
|
||||
// Tiles render larger than regular grid cards
|
||||
// (which are 168 × ~360) so the editorial picks
|
||||
// visibly stand out. Same hierarchy you see on app
|
||||
// stores: hero band before the dense list.
|
||||
height: 220,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: items.length,
|
||||
|
|
@ -541,7 +553,7 @@ class _FeaturedStrip extends StatelessWidget {
|
|||
itemBuilder: (context, i) {
|
||||
final item = items[i];
|
||||
return SizedBox(
|
||||
width: 320,
|
||||
width: 480,
|
||||
child: _FeaturedTile(
|
||||
item: item,
|
||||
locale: locale,
|
||||
|
|
@ -584,12 +596,12 @@ class _FeaturedTile extends StatelessWidget {
|
|||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(FaiSpace.md),
|
||||
padding: const EdgeInsets.all(FaiSpace.lg),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
theme.colorScheme.primary.withValues(alpha: 0.10),
|
||||
theme.colorScheme.primary.withValues(alpha: 0.14),
|
||||
theme.colorScheme.surfaceContainer.withValues(alpha: 0.0),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
|
|
@ -602,34 +614,46 @@ class _FeaturedTile extends StatelessWidget {
|
|||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 20,
|
||||
radius: 24,
|
||||
backgroundColor:
|
||||
theme.colorScheme.primary.withValues(alpha: 0.16),
|
||||
theme.colorScheme.primary.withValues(alpha: 0.18),
|
||||
child: Icon(
|
||||
_iconForCategory(item.category),
|
||||
size: 22,
|
||||
size: 26,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: FaiSpace.sm),
|
||||
const SizedBox(width: FaiSpace.md),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.name,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.name,
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (item.category.isNotEmpty)
|
||||
Text(
|
||||
item.category,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(Icons.star, size: 14, color: FaiColors.success),
|
||||
Icon(Icons.star, size: 18, color: FaiColors.success),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: FaiSpace.sm),
|
||||
const SizedBox(height: FaiSpace.md),
|
||||
Expanded(
|
||||
child: Text(
|
||||
tagline.isEmpty ? '(no tagline)' : tagline,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
style: theme.textTheme.bodyLarge,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
|
@ -659,11 +683,8 @@ class _FeaturedTile extends StatelessWidget {
|
|||
else if (installable)
|
||||
FilledButton.icon(
|
||||
onPressed: onInstall,
|
||||
icon: const Icon(Icons.download, size: 14),
|
||||
icon: const Icon(Icons.download, size: 16),
|
||||
label: const Text('Install'),
|
||||
style: FilledButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue