1+ import 'package:borneo_common/io/net/rssi.dart' ;
12import 'package:flutter/material.dart' ;
23import 'package:provider/provider.dart' ;
34import 'package:flutter_gettext/flutter_gettext/context_ext.dart' ;
@@ -14,6 +15,66 @@ import 'editor/schedule_editor_view.dart';
1415import 'editor/sun_editor_view.dart' ;
1516// screen_top_rounded_container is used inside slider lists
1617
18+ class DimmingAppBar extends StatelessWidget {
19+ final VoidCallback ? onBack;
20+ const DimmingAppBar ({super .key, this .onBack});
21+
22+ @override
23+ Widget build (BuildContext context) {
24+ return SliverAppBar (
25+ pinned: false ,
26+ floating: false ,
27+ snap: false ,
28+ foregroundColor: Theme .of (context).colorScheme.onSurface,
29+ backgroundColor: Theme .of (context).scaffoldBackgroundColor,
30+ centerTitle: true ,
31+ leading: Selector <LyfiViewModel , bool >(
32+ selector: (context, vm) => vm.isBusy,
33+ builder: (context, isBusy, child) =>
34+ IconButton (icon: const Icon (Icons .arrow_back), onPressed: isBusy ? null : onBack),
35+ ),
36+ title: Selector <LyfiViewModel , ({LyfiMode mode, bool canSwitch})>(
37+ selector: (context, vm) => (
38+ mode: vm.mode,
39+ canSwitch: vm.isOnline && ! vm.isSuspectedOffline && vm.isOn && vm.state == LyfiState .dimming,
40+ ),
41+ builder: (context, data, _) {
42+ return SegmentedButton <LyfiMode >(
43+ showSelectedIcon: false ,
44+ selected: < LyfiMode > {data.mode},
45+ segments: [
46+ ButtonSegment <LyfiMode >(value: LyfiMode .manual, icon: const Icon (Icons .bar_chart_outlined, size: 20 )),
47+ ButtonSegment <LyfiMode >(value: LyfiMode .scheduled, icon: const Icon (Icons .alarm_outlined, size: 20 )),
48+ ButtonSegment <LyfiMode >(value: LyfiMode .sun, icon: const Icon (Icons .wb_sunny_outlined, size: 20 )),
49+ ],
50+ onSelectionChanged: data.canSwitch
51+ ? (Set <LyfiMode > newSelection) {
52+ if (data.mode != newSelection.single) {
53+ context.read <LyfiViewModel >().switchMode (newSelection.single);
54+ }
55+ }
56+ : null ,
57+ );
58+ },
59+ ),
60+ actions: [
61+ Selector <LyfiViewModel , RssiLevel ?>(
62+ selector: (_, vm) => vm.rssiLevel,
63+ builder: (context, rssi, _) => Center (
64+ child: switch (rssi) {
65+ null => Icon (Icons .wifi_off, size: 24 , color: Theme .of (context).colorScheme.error),
66+ RssiLevel .strong => const Icon (Icons .wifi_rounded, size: 24 ),
67+ RssiLevel .medium => const Icon (Icons .wifi_2_bar_rounded, size: 24 ),
68+ RssiLevel .weak => const Icon (Icons .wifi_1_bar_rounded, size: 24 ),
69+ },
70+ ),
71+ ),
72+ const SizedBox (width: 16 ),
73+ ],
74+ );
75+ }
76+ }
77+
1778class DimmingScreen extends StatelessWidget {
1879 static const routeName = '/lyfi/dimming' ;
1980 const DimmingScreen ({super .key});
@@ -35,13 +96,14 @@ class DimmingScreen extends StatelessWidget {
3596 child: Scaffold (
3697 body: Stack (
3798 children: [
38- // keep the sliver headers but disable scrolling entirely; this mirrors
39- // the pattern used by the details screen and ensures the UI is fixed
40- // in place instead of being scrollable.
41- NestedScrollView (
99+ // CustomScrollView with NeverScrollableScrollPhysics keeps the
100+ // header slivers pinned while the body itself cannot scroll.
101+ // The SingleChildScrollView inside each editor view handles local
102+ // scrolling for just the slider-list area.
103+ CustomScrollView (
42104 physics: const NeverScrollableScrollPhysics (),
43- headerSliverBuilder : (context, innerBoxIsScrolled) => [
44- LyfiAppBar (
105+ slivers : [
106+ DimmingAppBar (
45107 onBack: () async {
46108 final vm = context.read <LyfiViewModel >();
47109 if (! vm.isLocked && ! vm.isSuspectedOffline) {
@@ -54,8 +116,8 @@ class DimmingScreen extends StatelessWidget {
54116 ),
55117 const LyfiBusyIndicatorSliver (),
56118 const LyfiStatusBannersSliver (),
119+ const SliverFillRemaining (hasScrollBody: true , child: DimmingView ()),
57120 ],
58- body: DimmingView (),
59121 ),
60122 const _ConnectionGuardOverlay (),
61123 ],
@@ -65,72 +127,12 @@ class DimmingScreen extends StatelessWidget {
65127 }
66128}
67129
68- class DimmingHeroPanel extends StatelessWidget {
69- const DimmingHeroPanel ({super .key});
70-
71- @override
72- Widget build (BuildContext context) {
73- return Material (
74- color: Theme .of (context).scaffoldBackgroundColor,
75- child: Row (
76- mainAxisAlignment: MainAxisAlignment .center,
77- crossAxisAlignment: CrossAxisAlignment .end,
78- children: [
79- Selector <LyfiViewModel , ({LyfiMode mode, bool canSwitch})>(
80- selector: (context, vm) => (
81- mode: vm.mode,
82- canSwitch: vm.isOnline && ! vm.isSuspectedOffline && vm.isOn && vm.state == LyfiState .dimming,
83- ),
84- builder: (context, vm, _) {
85- return SegmentedButton <LyfiMode >(
86- showSelectedIcon: false ,
87- selected: < LyfiMode > {vm.mode},
88- segments: [
89- ButtonSegment <LyfiMode >(
90- value: LyfiMode .manual,
91- label: Text (context.translate ('MANU' )),
92- icon: const Icon (Icons .bar_chart_outlined, size: 16 ),
93- ),
94- ButtonSegment <LyfiMode >(
95- value: LyfiMode .scheduled,
96- label: Text (context.translate ('SCHED' )),
97- icon: const Icon (Icons .alarm_outlined, size: 16 ),
98- ),
99- ButtonSegment <LyfiMode >(
100- value: LyfiMode .sun,
101- label: Text (context.translate ('SUN' )),
102- icon: const Icon (Icons .wb_sunny_outlined, size: 16 ),
103- ),
104- ],
105- onSelectionChanged: vm.canSwitch
106- ? (Set <LyfiMode > newSelection) {
107- if (vm.mode != newSelection.single) {
108- context.read <LyfiViewModel >().switchMode (newSelection.single);
109- }
110- }
111- : null ,
112- );
113- },
114- ),
115- ],
116- ),
117- );
118- }
119- }
120-
121130class DimmingView extends StatelessWidget {
122131 const DimmingView ({super .key});
123132
124133 @override
125134 Widget build (BuildContext context) {
126- return Column (
127- spacing: 8 ,
128- mainAxisAlignment: MainAxisAlignment .start,
129- children: [
130- const DimmingHeroPanel (),
131- Expanded (child: const EditorHost ()),
132- ],
133- );
135+ return const EditorHost ();
134136 }
135137}
136138
0 commit comments