Skip to content

Commit 84353f4

Browse files
committed
Responsive Visibility Creation #3
*Correctly calculate visibility for ResponsiveVisibility.
1 parent 161e59b commit 84353f4

File tree

3 files changed

+79
-107
lines changed

3 files changed

+79
-107
lines changed

lib/responsive_value.dart

Lines changed: 49 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class ResponsiveValue<T> {
1111
String smallerThan = '<';
1212
}
1313

14-
class ResponsiveVisibility extends StatefulWidget {
14+
class ResponsiveVisibility extends StatelessWidget {
1515
final Widget child;
1616
final bool visible;
1717
final List<Condition> visibleWhen;
@@ -27,8 +27,8 @@ class ResponsiveVisibility extends StatefulWidget {
2727
Key key,
2828
@required this.child,
2929
this.visible = true,
30-
this.visibleWhen,
31-
this.hiddenWhen,
30+
this.visibleWhen = const [],
31+
this.hiddenWhen = const [],
3232
this.replacement = const SizedBox.shrink(),
3333
this.maintainState = false,
3434
this.maintainAnimation = false,
@@ -37,41 +37,6 @@ class ResponsiveVisibility extends StatefulWidget {
3737
this.maintainInteractivity = false,
3838
}) : super(key: key);
3939

40-
@override
41-
_ResponsiveVisibilityState createState() => _ResponsiveVisibilityState();
42-
}
43-
44-
class _ResponsiveVisibilityState extends State<ResponsiveVisibility>
45-
with WidgetsBindingObserver {
46-
List<Condition> conditions = [];
47-
Condition activeCondition;
48-
bool visibleValue;
49-
50-
void setDimensions() {
51-
// Breakpoint reference check. Verify a parent
52-
// [ResponsiveWrapper] exists if a reference is found.
53-
if (conditions.firstWhere((element) => element.name != null,
54-
orElse: () => null) !=
55-
null) {
56-
try {
57-
ResponsiveWrapper.of(context);
58-
} catch (e) {
59-
throw FlutterError.fromParts(<DiagnosticsNode>[
60-
ErrorSummary(
61-
'A ResponsiveCondition was caught referencing a nonexistant breakpoint.'),
62-
ErrorDescription(
63-
'A ResponsiveCondition requires a parent ResponsiveWrapper '
64-
'to reference breakpoints. Add a ResponsiveWrapper or remove breakpoint references.')
65-
]);
66-
}
67-
}
68-
69-
// Find the active condition.
70-
activeCondition = getActiveCondition();
71-
// Set value to active condition value or default value if null.
72-
visibleValue = activeCondition?.value ?? widget.visible;
73-
}
74-
7540
/// Set [activeCondition].
7641
/// The active condition is found by matching the
7742
/// search criteria in order of precedence:
@@ -84,7 +49,8 @@ class _ResponsiveVisibilityState extends State<ResponsiveVisibility>
8449
/// a. Named breakpoints.
8550
/// b. Unnamed breakpoints.
8651
/// Returns null if no Active Condition is found.
87-
Condition getActiveCondition() {
52+
Condition getActiveCondition(
53+
BuildContext context, List<Condition> conditions) {
8854
Condition equalsCondition = conditions.firstWhere((element) {
8955
if (element.condition == Conditional.EQUALS) {
9056
return ResponsiveWrapper.of(context).activeBreakpoint?.name ==
@@ -128,59 +94,55 @@ class _ResponsiveVisibilityState extends State<ResponsiveVisibility>
12894
return null;
12995
}
13096

131-
@override
132-
void initState() {
133-
super.initState();
134-
// Initialize value.
135-
visibleValue = widget.visible;
136-
// Combine [ResponsiveCondition]s.
137-
conditions
138-
.addAll(widget.visibleWhen?.map((e) => e.copyWith(value: true)) ?? []);
139-
conditions
140-
.addAll(widget.hiddenWhen?.map((e) => e.copyWith(value: false)) ?? []);
141-
// Sort by breakpoint value.
142-
conditions.sort((a, b) => a.breakpoint.compareTo(b.breakpoint));
143-
144-
WidgetsBinding.instance.addObserver(this);
145-
WidgetsBinding.instance.addPostFrameCallback((_) {
146-
setDimensions();
147-
setState(() {});
148-
});
149-
}
150-
151-
@override
152-
void dispose() {
153-
WidgetsBinding.instance.removeObserver(this);
154-
super.dispose();
155-
}
156-
157-
@override
158-
void didChangeMetrics() {
159-
super.didChangeMetrics();
160-
WidgetsBinding.instance.addPostFrameCallback((_) {
161-
setDimensions();
162-
setState(() {});
163-
});
164-
}
97+
bool getVisibleValue(BuildContext context, List<Condition> conditions) {
98+
// Breakpoint reference check. Verify a parent
99+
// [ResponsiveWrapper] exists if a reference is found.
100+
if (conditions.firstWhere((element) => element.name != null,
101+
orElse: () => null) !=
102+
null) {
103+
try {
104+
ResponsiveWrapper.of(context);
105+
} catch (e) {
106+
throw FlutterError.fromParts(<DiagnosticsNode>[
107+
ErrorSummary(
108+
'A ResponsiveCondition was caught referencing a nonexistant breakpoint.'),
109+
ErrorDescription(
110+
'A ResponsiveCondition requires a parent ResponsiveWrapper '
111+
'to reference breakpoints. Add a ResponsiveWrapper or remove breakpoint references.')
112+
]);
113+
}
114+
}
165115

166-
@override
167-
void didUpdateWidget(ResponsiveVisibility oldWidget) {
168-
super.didUpdateWidget(oldWidget);
169-
setDimensions();
170-
setState(() {});
116+
// Find the active condition.
117+
Condition activeCondition = getActiveCondition(context, conditions);
118+
// Return active condition value or default value if null.
119+
return activeCondition?.value ?? visible;
171120
}
172121

173122
@override
174123
Widget build(BuildContext context) {
124+
// Initialize mutable value holders.
125+
List<Condition> conditions = [];
126+
Condition activeCondition;
127+
bool visibleValue = visible;
128+
129+
// Combine Conditions.
130+
conditions.addAll(visibleWhen?.map((e) => e.copyWith(value: true)) ?? []);
131+
conditions.addAll(hiddenWhen?.map((e) => e.copyWith(value: false)) ?? []);
132+
// Sort by breakpoint value.
133+
conditions.sort((a, b) => a.breakpoint.compareTo(b.breakpoint));
134+
// Get visible value from active condition.
135+
visibleValue = getVisibleValue(context, conditions);
136+
175137
return Visibility(
176-
child: widget.child,
177-
replacement: widget.replacement,
138+
child: child,
139+
replacement: replacement,
178140
visible: visibleValue,
179-
maintainState: widget.maintainState,
180-
maintainAnimation: widget.maintainAnimation,
181-
maintainSize: widget.maintainSize,
182-
maintainSemantics: widget.maintainSemantics,
183-
maintainInteractivity: widget.maintainInteractivity,
141+
maintainState: maintainState,
142+
maintainAnimation: maintainAnimation,
143+
maintainSize: maintainSize,
144+
maintainSemantics: maintainSemantics,
145+
maintainInteractivity: maintainInteractivity,
184146
);
185147
}
186148
}
@@ -209,13 +171,13 @@ class Condition {
209171
this.value = value;
210172

211173
Condition.largerThan({int breakpoint, String name, bool value})
212-
: this.breakpoint = breakpoint ?? double.infinity,
174+
: this.breakpoint = breakpoint,
213175
this.name = name,
214176
this.condition = Conditional.LARGER_THAN,
215177
this.value = value;
216178

217179
Condition.smallerThan({int breakpoint, String name, bool value})
218-
: this.breakpoint = breakpoint ?? double.negativeInfinity,
180+
: this.breakpoint = breakpoint,
219181
this.name = name,
220182
this.condition = Conditional.SMALLER_THAN,
221183
this.value = value;

lib/responsive_wrapper.dart

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,10 @@ class _ResponsiveWrapperState extends State<ResponsiveWrapper>
286286
/// Active breakpoint segment is the first breakpoint segment
287287
/// smaller or equal to the [windowWidth].
288288
_ResponsiveBreakpointSegment getActiveBreakpointSegment(double windowWidth) {
289-
_ResponsiveBreakpointSegment activeBreakpoint = breakpointSegments.reversed
290-
.firstWhere(
289+
_ResponsiveBreakpointSegment activeBreakpointSegment =
290+
breakpointSegments.reversed.firstWhere(
291291
(element) => windowWidth >= element.breakpoint && !element.isTag);
292-
return activeBreakpoint;
292+
return activeBreakpointSegment;
293293
}
294294

295295
@override
@@ -531,6 +531,7 @@ class _ResponsiveWrapperState extends State<ResponsiveWrapper>
531531
breakpointHolder = breakpoint;
532532
break;
533533
case _ResponsiveBreakpointBehavior.TAG:
534+
breakpointHolder = breakpointHolder.copyWith(name: breakpoint.name);
534535
breakpointSegmentHolder = _ResponsiveBreakpointSegment(
535536
breakpoint: breakpoint.breakpoint,
536537
// Tag inherits behavior from previous breakpoint.
@@ -569,6 +570,7 @@ class ResponsiveWrapperData {
569570
final double scaledWidth;
570571
final double scaledHeight;
571572
final List<ResponsiveBreakpoint> breakpoints;
573+
final List<_ResponsiveBreakpointSegment> breakpointSegments;
572574
final ResponsiveBreakpoint activeBreakpoint;
573575
final bool isMobile;
574576
final bool isPhone;
@@ -585,6 +587,7 @@ class ResponsiveWrapperData {
585587
this.scaledWidth,
586588
this.scaledHeight,
587589
this.breakpoints,
590+
this.breakpointSegments,
588591
this.activeBreakpoint,
589592
this.isMobile,
590593
this.isPhone,
@@ -601,6 +604,7 @@ class ResponsiveWrapperData {
601604
scaledWidth: state.scaledWidth,
602605
scaledHeight: state.scaledHeight,
603606
breakpoints: state.breakpoints,
607+
breakpointSegments: state.breakpointSegments,
604608
activeBreakpoint: state.activeBreakpointSegment.responsiveBreakpoint,
605609
isMobile:
606610
state.activeBreakpointSegment.responsiveBreakpoint.name == MOBILE,
@@ -625,6 +629,8 @@ class ResponsiveWrapperData {
625629
scaledHeight?.toString() +
626630
', breakpoints: ' +
627631
breakpoints?.asMap().toString() +
632+
', breakpointSegments: ' +
633+
breakpointSegments.toString() +
628634
', activeBreakpoint: ' +
629635
activeBreakpoint.toString() +
630636
', isMobile: ' +
@@ -637,16 +643,16 @@ class ResponsiveWrapperData {
637643
isDesktop?.toString() +
638644
')';
639645

640-
bool equals(String breakpointName) => activeBreakpoint.name == breakpointName;
646+
bool equals(String name) => activeBreakpoint.name == name;
641647

642-
/// Is the [scaledWidth] larger than or equal to [breakpointName]?
643-
/// Defaults to false if the [breakpointName] cannot be found.
644-
bool isLargerThan(String breakpointName) {
648+
/// Is the [scaledWidth] larger than or equal to [name]?
649+
/// Defaults to false if the [name] cannot be found.
650+
bool isLargerThan(String name) {
645651
// No breakpoints to match.
646652
if (breakpoints.length == 0) return false;
647653

648654
// Breakpoint is active breakpoint.
649-
if (activeBreakpoint.name == breakpointName) return false;
655+
if (activeBreakpoint.name == name) return false;
650656

651657
// Single breakpoint is active and screen width
652658
// is larger than default breakpoint.
@@ -657,18 +663,24 @@ class ResponsiveWrapperData {
657663
// than screen width. Breakpoint names could be
658664
// chained so perform a full search from largest to smallest.
659665
for (var i = breakpoints.length - 2; i >= 0; i--) {
660-
if (breakpoints[i].name == breakpointName &&
661-
breakpoints[i + 1].name != breakpointName &&
666+
if (breakpoints[i].name == name &&
667+
breakpoints[i + 1].name != name &&
662668
screenWidth >= breakpoints[i + 1].breakpoint) return true;
663669
}
664670

665671
return false;
666672
}
667673

668-
/// Is the [scaledWidth] smaller than the [breakpointName]?
669-
/// Defaults to false if the [breakpointName] cannot be found.
670-
bool isSmallerThan(String breakpointName) => breakpoints.any((element) =>
671-
element.name == breakpointName && scaledWidth < element.breakpoint);
674+
/// Is the [screenWidth] smaller than the [name]?
675+
/// Defaults to false if the [name] cannot be found.
676+
bool isSmallerThan(String name) =>
677+
screenWidth <
678+
breakpointSegments
679+
.firstWhere(
680+
(element) => element.responsiveBreakpoint.name == name,
681+
orElse: null)
682+
?.breakpoint ??
683+
0;
672684
}
673685

674686
/// Creates an immutable widget that exposes [ResponsiveWrapperData]

test/responsive_wrapper_test.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -534,9 +534,7 @@ void main() {
534534
});
535535

536536
// Test infinite screen width and height.
537-
testWidgets('Screen Size Infinite', (WidgetTester tester) async {
538-
// Infinite screen width or height is not allowed.
539-
}, skip: true);
537+
// Infinite screen width or height is not allowed.
540538

541539
// Test convenience comparators.
542540
testWidgets('Breakpoint Comparators', (WidgetTester tester) async {
@@ -587,7 +585,7 @@ void main() {
587585
expect(
588586
ResponsiveWrapperData.fromResponsiveWrapper(state)
589587
.isSmallerThan(TABLET),
590-
true);
588+
false);
591589
expect(
592590
ResponsiveWrapperData.fromResponsiveWrapper(state)
593591
.isSmallerThan(DESKTOP),
@@ -600,7 +598,7 @@ void main() {
600598
key: key,
601599
breakpoints: [
602600
ResponsiveBreakpoint.resize(450, name: MOBILE),
603-
ResponsiveBreakpoint.resize(500, name: TABLET),
601+
ResponsiveBreakpoint.resize(500, name: MOBILE),
604602
ResponsiveBreakpoint.resize(550, name: MOBILE),
605603
ResponsiveBreakpoint.resize(600),
606604
ResponsiveBreakpoint.resize(650, name: TABLET),
@@ -627,7 +625,7 @@ void main() {
627625
expect(
628626
ResponsiveWrapperData.fromResponsiveWrapper(state)
629627
.isLargerThan(TABLET),
630-
true);
628+
false);
631629
expect(
632630
ResponsiveWrapperData.fromResponsiveWrapper(state)
633631
.isLargerThan(DESKTOP),

0 commit comments

Comments
 (0)