PluriRootHeader rendered its 56px title/actions row flush at y=0 on every device, since app.dart's root SafeArea(top: false) deliberately excludes the top inset (so each root's full-bleed background paints edge-to-edge behind the status bar) but the header itself never added MediaQuery.paddingOf(context).top anywhere. The header now wraps its existing 56px content row in an outer top padding equal to that inset, so total rendered height is height + topInset while `height` keeps meaning the content row's own height (verified no call site did total-height math against the old fixed constant).
90 lines
3.3 KiB
Dart
90 lines
3.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../l10n/gen/app_localizations.dart';
|
|
import '../tema/pluriwave_theme.dart';
|
|
import 'pluri_layout.dart';
|
|
|
|
/// S1 (Tier 1 visual fidelity): the prototype (`t4`) draws no global
|
|
/// `AppBar` — each root paints its own 56px title row inside its own
|
|
/// content instead (e.g. Alarmas `height:56px;padding:0 12px 0 20px`,
|
|
/// Ajustes `height:56px;padding:0 20px`, Explorar `height:56px`). This
|
|
/// widget is that row, reused by all 5 roots so the sleep-timer action
|
|
/// that used to live on `app.dart`'s single shared `AppBar` stays
|
|
/// reachable from every tab.
|
|
class PluriRootHeader extends StatelessWidget {
|
|
const PluriRootHeader({
|
|
super.key,
|
|
required this.title,
|
|
required this.onSleepTimer,
|
|
this.actions = const <Widget>[],
|
|
});
|
|
|
|
final String title;
|
|
final VoidCallback onSleepTimer;
|
|
|
|
/// S2 (Tier 1 visual fidelity): the now-retired `PluriScreenHeader` was
|
|
/// also the only home for a couple of roots' SINGLE functional action —
|
|
/// Alarmas' create-alarm button, Buscar's filters entry point. Rendered
|
|
/// before the shared bedtime button; empty by default (every other root
|
|
/// needs nothing extra here).
|
|
final List<Widget> actions;
|
|
|
|
/// Fix `safearea-top-inset`: this is the CONTENT row's height only —
|
|
/// NOT this widget's total rendered height. `app.dart`'s root
|
|
/// `SafeArea(top: false, ...)` deliberately excludes the top inset (so
|
|
/// each root's own full-bleed background paints genuinely edge-to-edge
|
|
/// behind the status bar), which left this header's title/actions row
|
|
/// with zero top-inset awareness — flush at y=0 under the status bar /
|
|
/// camera cutout on every device. This widget now adds
|
|
/// `MediaQuery.paddingOf(context).top` ABOVE this content height itself
|
|
/// (see [build]), so the total rendered height is
|
|
/// `height + MediaQuery.paddingOf(context).top`. Callers doing
|
|
/// total-height math (none currently do — checked every `PluriRootHeader`
|
|
/// call site) must add that inset separately; this constant's MEANING
|
|
/// (content height) is unchanged.
|
|
static const double height = 56;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final type = context.pluriType;
|
|
final l10n = AppLocalizations.of(context);
|
|
final topInset = MediaQuery.paddingOf(context).top;
|
|
return Padding(
|
|
padding: EdgeInsets.only(top: topInset),
|
|
child: SizedBox(
|
|
height: height,
|
|
child: Padding(
|
|
key: const ValueKey('pluri-root-header-content'),
|
|
// S5: the prototype's own header padding is title-tier on the
|
|
// left, row-tier on the right (t4 e.g. Alarmas
|
|
// `padding:0 12px 0 20px`).
|
|
padding: const EdgeInsets.fromLTRB(
|
|
PluriLayout.titleHorizontal,
|
|
0,
|
|
PluriLayout.rowHorizontal,
|
|
0,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
title,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: type.sectionTitle,
|
|
),
|
|
),
|
|
...actions,
|
|
IconButton(
|
|
icon: const Icon(Icons.bedtime_outlined),
|
|
tooltip: l10n.sleepTimer,
|
|
onPressed: onSleepTimer,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|