Skip to content

Commit e0d1ba4

Browse files
committed
Updated initial project structure
1 parent 88ea64a commit e0d1ba4

File tree

5 files changed

+150
-89
lines changed

5 files changed

+150
-89
lines changed

examples/no_package_example/lib/main.dart

+16-6
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,32 @@ import 'package:no_package_example/pages/home.dart';
44
import 'package:responsive_layout/common_layout.dart';
55

66
void main() {
7-
final commonLayout = CommonLayout();
8-
9-
runApp(MaterialApp(
7+
runApp(MyApp());
8+
}
9+
10+
class MyApp extends StatelessWidget {
11+
const MyApp({Key? key}) : super(key: key);
12+
13+
@override
14+
Widget build(BuildContext context) {
15+
return MaterialApp(
1016
initialRoute: '/',
1117
onGenerateRoute: (settings) {
1218
// TODO: Why we have to wrap around each route
1319
switch (settings.name) {
1420
case '/': return MaterialPageRoute(builder: (context) {
15-
return LayoutResolverWidget(resolver: commonLayout, child: HomePage());
21+
return LayoutResolverWidget(resolver: _resolverIn(context), child: HomePage());
1622
});
1723
case '/details': return MaterialPageRoute(builder: (context) {
18-
return LayoutResolverWidget(resolver: commonLayout, child: DetailsPage());
24+
return LayoutResolverWidget(resolver: _resolverIn(context), child: DetailsPage());
1925
}, fullscreenDialog: true);
2026
default:
2127
throw 'No route with name ${settings.name}';
2228
}
2329
},
24-
),);
30+
);
31+
}
32+
33+
// TODO: Shouldn't we go for a smallestSide instead of always width?
34+
LayoutResolver _resolverIn(BuildContext context) => CommonLayout(context.deviceWidth);
2535
}

examples/no_package_example/lib/pages/details.dart

+12-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ class DetailsPage extends StatelessWidget {
66

77
@override
88
Widget build(BuildContext context) {
9+
final layout = context.layout;
10+
911
final showText = 'Show Snack';
1012
final showIcon = Icon(Icons.message);
1113

@@ -18,33 +20,35 @@ class DetailsPage extends StatelessWidget {
1820
final desktopButton = OutlinedButton.icon(onPressed: () => _displaySnackbar(context), icon: showIcon, label: Text(showText),);
1921

2022
final pageTitle = 'Hybrid Details';
21-
23+
2224
return Scaffold(
23-
appBar: context.isTabletOrSmaller ? AppBar(
25+
appBar: layout.isTabletOrSmaller ? AppBar(
2426
title: Text(pageTitle),
2527
) : null,
2628
body: Center(
2729
child: Column(
2830
mainAxisAlignment: MainAxisAlignment.center,
29-
children: <Widget>[
30-
if (context.isDesktop) Padding(
31+
children: [
32+
if (layout.isDesktop) Padding(
3133
padding: const EdgeInsets.only(bottom: 40),
3234
child: Text(pageTitle, style: Theme.of(context).textTheme.headline3,),
3335
),
3436
Text(
3537
'This is a responsive text!',
36-
style: context.isPhoneOrSmaller ? Theme.of(context).textTheme.headline6 : Theme.of(context).textTheme.headline5,
38+
style: layout.isPhoneOrSmaller ? Theme.of(context).textTheme.headline6 : Theme.of(context).textTheme.headline5,
3739
),
38-
if (context.isTabletOrLarger) desktopButton,
40+
if (layout.isTabletOrLarger) desktopButton,
3941
],
4042
),
4143
),
42-
floatingActionButton: context.isPhoneOrSmaller ? mobileFab : null,
44+
floatingActionButton: layout.isPhoneOrSmaller ? mobileFab : null,
4345
);
4446
}
4547

4648
void _displaySnackbar(BuildContext context) {
47-
final snackBarTextStyle = context.isPhoneOrSmaller ? Theme.of(context).textTheme.bodyText2 : Theme.of(context).textTheme.subtitle1;
49+
final layout = context.layout;
50+
51+
final snackBarTextStyle = layout.isPhoneOrSmaller ? Theme.of(context).textTheme.bodyText2 : Theme.of(context).textTheme.subtitle1;
4852

4953
final snackBarText = Text('A responsive text inside the SnackBar!', style: snackBarTextStyle?.copyWith(color: Colors.white));
5054
final snackBar = SnackBar(content: snackBarText);

examples/no_package_example/lib/pages/home.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@ class HomePage extends StatelessWidget {
1212

1313
@override
1414
Widget build(BuildContext context) {
15+
final layout = context.layout;
1516
return Scaffold(
16-
appBar: context.isTabletOrSmaller ? AppBar(
17+
appBar: layout.isTabletOrSmaller ? AppBar(
1718
title: Text('Splitted Home'),
1819
) : null,
1920
body: Padding(
2021
padding: EdgeInsets.all(16),
2122
child: Container(
2223
constraints: BoxConstraints.expand(),
2324
child: SingleChildScrollView(
24-
child: context.layoutValue(
25+
child: layout.value(
2526
tablet: _HomeContents(children: _randomMockedChildren),
2627
phone: _CompactHomeContents(children: _randomMockedChildren),
2728
),

lib/common_layout.dart

+36-70
Original file line numberDiff line numberDiff line change
@@ -2,74 +2,36 @@ import 'package:flutter/widgets.dart';
22
import 'package:responsive_layout/responsive_layout.dart';
33

44
import 'src/layout_resolver.dart';
5-
import 'src/utilities.dart';
6-
75

86
export 'responsive_layout.dart';
97

10-
// extension LayoutMetadataCtx on BuildContext {
11-
// LayoutMetadata get layoutMetadata => watch<LayoutMetadata>();
12-
// }
13-
14-
// typedef LayoutValue<T> = T Function(BuildContext context);
15-
16-
17-
188
enum CommonBreakpoint {
199
desktop, tablet, phone, tinyHardware
2010
}
2111

22-
12+
@immutable
2313
class CommonLayout extends LayoutResolver<CommonBreakpoint> {
24-
CommonLayout({
25-
this.desktop = 769,
26-
this.tablet = 481,
27-
this.phone = 321
28-
}) {
29-
if (desktop <= tablet || desktop <= phone) {
30-
throw 'tablet (tablet) and/or phone (phone) size was greater than desktop size (desktop)';
31-
}
32-
33-
if (tablet <= phone) {
34-
throw 'phone size (phone) was greater than tablet size (tablet)';
35-
}
36-
}
14+
CommonLayout(double size, {
15+
int desktop = 769,
16+
int tablet = 481,
17+
int phone = 321
18+
}): super(size: size, breakpoints: [Breakpoint(desktop, value: CommonBreakpoint.desktop), Breakpoint(tablet, value: CommonBreakpoint.tablet), Breakpoint(phone, value: CommonBreakpoint.phone), Breakpoint(null, value: CommonBreakpoint.tinyHardware),]);
3719

38-
final int desktop;
39-
final int tablet;
40-
final int phone;
20+
// Helpers
4121

42-
@override
43-
CommonBreakpoint resolveBreakpoint(double size) {
44-
if (size < phone) {
45-
return CommonBreakpoint.tinyHardware;
46-
} else if (size < tablet) {
47-
return CommonBreakpoint.phone;
48-
} else if (size < desktop) {
49-
return CommonBreakpoint.tablet;
50-
}
51-
52-
return CommonBreakpoint.desktop;
53-
}
54-
}
55-
56-
extension CommonLayoutValue on BuildContext {
57-
CommonLayout get commonLayout => LayoutResolverWidget.of(this).resolver as CommonLayout;
58-
CommonBreakpoint get breakpoint => commonLayout.resolveBreakpoint(deviceWidth);
59-
60-
bool get isTinyHardware => breakpoint == CommonBreakpoint.tinyHardware;
22+
bool get isTinyHardware => matchesValue(CommonBreakpoint.tinyHardware);
6123

62-
bool get isPhoneOrSmaller => breakpoint == CommonBreakpoint.phone || breakpoint == CommonBreakpoint.tinyHardware;
63-
bool get isPhone => breakpoint == CommonBreakpoint.phone;
64-
bool get isPhoneOrLarger => breakpoint != CommonBreakpoint.tinyHardware;
24+
bool get isPhoneOrSmaller => matchesValueOrSmaller(CommonBreakpoint.phone);
25+
bool get isPhone => matchesValue(CommonBreakpoint.phone);
26+
bool get isPhoneOrLarger => matchesValueOrLarger(CommonBreakpoint.phone);
6527

66-
bool get isTabletOrSmaller => breakpoint != CommonBreakpoint.desktop;
67-
bool get isTablet => breakpoint == CommonBreakpoint.tablet;
68-
bool get isTabletOrLarger => breakpoint != CommonBreakpoint.phone && breakpoint != CommonBreakpoint.tinyHardware;
28+
bool get isTabletOrSmaller => matchesValueOrSmaller(CommonBreakpoint.tablet);
29+
bool get isTablet => matchesValue(CommonBreakpoint.tablet);
30+
bool get isTabletOrLarger => matchesValueOrLarger(CommonBreakpoint.tablet);
6931

70-
bool get isDesktop => breakpoint == CommonBreakpoint.desktop;
32+
bool get isDesktop => matchesValue(CommonBreakpoint.desktop);
7133

72-
T layoutValue<T>({
34+
T value<T>({
7335
T? desktop,
7436
T? tablet,
7537
T? phone,
@@ -79,27 +41,31 @@ extension CommonLayoutValue on BuildContext {
7941
throw 'At least one breakpoint must be provided';
8042
}
8143

82-
// If it's in the exact range and has a layout supplied, try to use it,
83-
// otherwise cascade down all the available values
84-
if (desktop != null && breakpoint == CommonBreakpoint.desktop) {
44+
// If it's in the exact range and has a layout supplied, try to use it...
45+
if (desktop != null && breakpointValue == CommonBreakpoint.desktop) {
8546
return desktop;
86-
} else if (tablet != null && breakpoint == CommonBreakpoint.tablet) {
47+
} else if (tablet != null && breakpointValue == CommonBreakpoint.tablet) {
8748
return tablet;
88-
} else if (phone != null && breakpoint == CommonBreakpoint.phone) {
49+
} else if (phone != null && breakpointValue == CommonBreakpoint.phone) {
8950
return phone;
9051
} else if (tinyHardware != null) {
9152
return tinyHardware;
9253
}
93-
94-
// Get the largest non-null layout supplied
95-
if (desktop != null) {
96-
return desktop;
97-
} else if (tablet != null) {
98-
return tablet;
99-
} else if (phone != null) {
100-
return phone;
101-
} else {
102-
return tinyHardware!;
103-
}
54+
// ... otherwise cascade down all the available values to get the largest
55+
// non-null layout supplied
56+
if (desktop != null) {
57+
return desktop;
58+
} else if (tablet != null) {
59+
return tablet;
60+
} else if (phone != null) {
61+
return phone;
62+
} else {
63+
return tinyHardware!;
64+
}
10465
}
10566
}
67+
68+
// Widget Utility
69+
extension CommonResolverWidget on BuildContext {
70+
CommonLayout get layout => LayoutResolverWidget.of(this).resolver as CommonLayout;
71+
}

lib/src/layout_resolver.dart

+83-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,87 @@
1-
abstract class LayoutResolver<T> {
2-
const LayoutResolver();
1+
import 'dart:collection';
2+
3+
class Breakpoint<T> implements Comparable<Breakpoint<T>> {
4+
Breakpoint(this.size, {required this.value}): assert(size == null || size > 0);
5+
6+
final int? size;
7+
final T value;
8+
9+
operator > (Breakpoint<T> other) => (size ?? 0) > (other.size ?? 0);
10+
operator >= (Breakpoint<T> other) => (size ?? 0) >= (other.size ?? 0);
11+
operator <= (Breakpoint<T> other) => (size ?? 0) <= (other.size ?? 0);
12+
operator < (Breakpoint<T> other) => (size ?? 0) < (other.size ?? 0);
13+
14+
@override
15+
int compareTo(Breakpoint<T> other) {
16+
if (other > this) { return -1; }
17+
if (other < this) { return 1; }
18+
19+
return 0;
20+
}
21+
}
22+
23+
extension BreakpointListExtension<T> on List<Breakpoint<T>> {
24+
25+
List<Breakpoint<T>> get descendingSorted {
26+
final descendingSorted = List<Breakpoint<T>>.from(this);
27+
28+
descendingSorted..sort((a, b) {
29+
final comparison = b.compareTo(a);
30+
if (comparison == 0) {
31+
throw 'breakpoint of size `${b.size}` (value `${b.value}`) had the same size of other breakpoint (value `${a.value})';
32+
}
33+
34+
return comparison;
35+
});
36+
37+
return descendingSorted;
38+
}
339

4-
T resolveBreakpoint(double size);
40+
41+
Breakpoint<T> firstCascadingBreakpoint(double size) {
42+
for (final breakpoint in this) {
43+
if (size >= (breakpoint.size ?? 0)) {
44+
return breakpoint;
45+
}
46+
}
47+
48+
return last;
49+
}
550
}
651

752

53+
54+
abstract class LayoutResolver<T> {
55+
LayoutResolver({required double size, required List<Breakpoint<T>> breakpoints}): assert(size > 0), assert(breakpoints.length > 1), _descendingBreakpoints = breakpoints.descendingSorted, breakpoint = breakpoints.descendingSorted.firstCascadingBreakpoint(size);
56+
57+
final List<Breakpoint<T>> _descendingBreakpoints;
58+
59+
60+
List<Breakpoint<T>> get breakpoints => _descendingBreakpoints;
61+
final Breakpoint<T> breakpoint;
62+
T get breakpointValue => breakpoint.value;
63+
64+
bool matchesValueOrSmaller(T match) {
65+
final valueBreakpoint = breakpoints.firstWhere((breakpoint) => breakpoint.value == match, orElse: () {
66+
throw 'TODO: throw or return false?';
67+
});
68+
69+
return breakpoint <= valueBreakpoint;
70+
}
71+
72+
bool matchesValue(T match) {
73+
final valueBreakpoint = breakpoints.firstWhere((breakpoint) => breakpoint.value == match, orElse: () {
74+
throw 'TODO';
75+
});
76+
77+
return breakpoint.size == valueBreakpoint.size;
78+
}
79+
80+
bool matchesValueOrLarger(T match) {
81+
final valueBreakpoint = breakpoints.firstWhere((breakpoint) => breakpoint.value == match, orElse: () {
82+
throw 'TODO';
83+
});
84+
85+
return breakpoint >= valueBreakpoint;
86+
}
87+
}

0 commit comments

Comments
 (0)