Skip to content

Commit

Permalink
Updated initial project structure
Browse files Browse the repository at this point in the history
  • Loading branch information
matuella committed Feb 11, 2021
1 parent 88ea64a commit e0d1ba4
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 89 deletions.
22 changes: 16 additions & 6 deletions examples/no_package_example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,32 @@ import 'package:no_package_example/pages/home.dart';
import 'package:responsive_layout/common_layout.dart';

void main() {
final commonLayout = CommonLayout();

runApp(MaterialApp(
runApp(MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
onGenerateRoute: (settings) {
// TODO: Why we have to wrap around each route
switch (settings.name) {
case '/': return MaterialPageRoute(builder: (context) {
return LayoutResolverWidget(resolver: commonLayout, child: HomePage());
return LayoutResolverWidget(resolver: _resolverIn(context), child: HomePage());
});
case '/details': return MaterialPageRoute(builder: (context) {
return LayoutResolverWidget(resolver: commonLayout, child: DetailsPage());
return LayoutResolverWidget(resolver: _resolverIn(context), child: DetailsPage());
}, fullscreenDialog: true);
default:
throw 'No route with name ${settings.name}';
}
},
),);
);
}

// TODO: Shouldn't we go for a smallestSide instead of always width?
LayoutResolver _resolverIn(BuildContext context) => CommonLayout(context.deviceWidth);
}
20 changes: 12 additions & 8 deletions examples/no_package_example/lib/pages/details.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class DetailsPage extends StatelessWidget {

@override
Widget build(BuildContext context) {
final layout = context.layout;

final showText = 'Show Snack';
final showIcon = Icon(Icons.message);

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

final pageTitle = 'Hybrid Details';

return Scaffold(
appBar: context.isTabletOrSmaller ? AppBar(
appBar: layout.isTabletOrSmaller ? AppBar(
title: Text(pageTitle),
) : null,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (context.isDesktop) Padding(
children: [
if (layout.isDesktop) Padding(
padding: const EdgeInsets.only(bottom: 40),
child: Text(pageTitle, style: Theme.of(context).textTheme.headline3,),
),
Text(
'This is a responsive text!',
style: context.isPhoneOrSmaller ? Theme.of(context).textTheme.headline6 : Theme.of(context).textTheme.headline5,
style: layout.isPhoneOrSmaller ? Theme.of(context).textTheme.headline6 : Theme.of(context).textTheme.headline5,
),
if (context.isTabletOrLarger) desktopButton,
if (layout.isTabletOrLarger) desktopButton,
],
),
),
floatingActionButton: context.isPhoneOrSmaller ? mobileFab : null,
floatingActionButton: layout.isPhoneOrSmaller ? mobileFab : null,
);
}

void _displaySnackbar(BuildContext context) {
final snackBarTextStyle = context.isPhoneOrSmaller ? Theme.of(context).textTheme.bodyText2 : Theme.of(context).textTheme.subtitle1;
final layout = context.layout;

final snackBarTextStyle = layout.isPhoneOrSmaller ? Theme.of(context).textTheme.bodyText2 : Theme.of(context).textTheme.subtitle1;

final snackBarText = Text('A responsive text inside the SnackBar!', style: snackBarTextStyle?.copyWith(color: Colors.white));
final snackBar = SnackBar(content: snackBarText);
Expand Down
5 changes: 3 additions & 2 deletions examples/no_package_example/lib/pages/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ class HomePage extends StatelessWidget {

@override
Widget build(BuildContext context) {
final layout = context.layout;
return Scaffold(
appBar: context.isTabletOrSmaller ? AppBar(
appBar: layout.isTabletOrSmaller ? AppBar(
title: Text('Splitted Home'),
) : null,
body: Padding(
padding: EdgeInsets.all(16),
child: Container(
constraints: BoxConstraints.expand(),
child: SingleChildScrollView(
child: context.layoutValue(
child: layout.value(
tablet: _HomeContents(children: _randomMockedChildren),
phone: _CompactHomeContents(children: _randomMockedChildren),
),
Expand Down
106 changes: 36 additions & 70 deletions lib/common_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,36 @@ import 'package:flutter/widgets.dart';
import 'package:responsive_layout/responsive_layout.dart';

import 'src/layout_resolver.dart';
import 'src/utilities.dart';


export 'responsive_layout.dart';

// extension LayoutMetadataCtx on BuildContext {
// LayoutMetadata get layoutMetadata => watch<LayoutMetadata>();
// }

// typedef LayoutValue<T> = T Function(BuildContext context);



enum CommonBreakpoint {
desktop, tablet, phone, tinyHardware
}


@immutable
class CommonLayout extends LayoutResolver<CommonBreakpoint> {
CommonLayout({
this.desktop = 769,
this.tablet = 481,
this.phone = 321
}) {
if (desktop <= tablet || desktop <= phone) {
throw 'tablet (tablet) and/or phone (phone) size was greater than desktop size (desktop)';
}

if (tablet <= phone) {
throw 'phone size (phone) was greater than tablet size (tablet)';
}
}
CommonLayout(double size, {
int desktop = 769,
int tablet = 481,
int phone = 321
}): super(size: size, breakpoints: [Breakpoint(desktop, value: CommonBreakpoint.desktop), Breakpoint(tablet, value: CommonBreakpoint.tablet), Breakpoint(phone, value: CommonBreakpoint.phone), Breakpoint(null, value: CommonBreakpoint.tinyHardware),]);

final int desktop;
final int tablet;
final int phone;
// Helpers

@override
CommonBreakpoint resolveBreakpoint(double size) {
if (size < phone) {
return CommonBreakpoint.tinyHardware;
} else if (size < tablet) {
return CommonBreakpoint.phone;
} else if (size < desktop) {
return CommonBreakpoint.tablet;
}

return CommonBreakpoint.desktop;
}
}

extension CommonLayoutValue on BuildContext {
CommonLayout get commonLayout => LayoutResolverWidget.of(this).resolver as CommonLayout;
CommonBreakpoint get breakpoint => commonLayout.resolveBreakpoint(deviceWidth);

bool get isTinyHardware => breakpoint == CommonBreakpoint.tinyHardware;
bool get isTinyHardware => matchesValue(CommonBreakpoint.tinyHardware);

bool get isPhoneOrSmaller => breakpoint == CommonBreakpoint.phone || breakpoint == CommonBreakpoint.tinyHardware;
bool get isPhone => breakpoint == CommonBreakpoint.phone;
bool get isPhoneOrLarger => breakpoint != CommonBreakpoint.tinyHardware;
bool get isPhoneOrSmaller => matchesValueOrSmaller(CommonBreakpoint.phone);
bool get isPhone => matchesValue(CommonBreakpoint.phone);
bool get isPhoneOrLarger => matchesValueOrLarger(CommonBreakpoint.phone);

bool get isTabletOrSmaller => breakpoint != CommonBreakpoint.desktop;
bool get isTablet => breakpoint == CommonBreakpoint.tablet;
bool get isTabletOrLarger => breakpoint != CommonBreakpoint.phone && breakpoint != CommonBreakpoint.tinyHardware;
bool get isTabletOrSmaller => matchesValueOrSmaller(CommonBreakpoint.tablet);
bool get isTablet => matchesValue(CommonBreakpoint.tablet);
bool get isTabletOrLarger => matchesValueOrLarger(CommonBreakpoint.tablet);

bool get isDesktop => breakpoint == CommonBreakpoint.desktop;
bool get isDesktop => matchesValue(CommonBreakpoint.desktop);

T layoutValue<T>({
T value<T>({
T? desktop,
T? tablet,
T? phone,
Expand All @@ -79,27 +41,31 @@ extension CommonLayoutValue on BuildContext {
throw 'At least one breakpoint must be provided';
}

// If it's in the exact range and has a layout supplied, try to use it,
// otherwise cascade down all the available values
if (desktop != null && breakpoint == CommonBreakpoint.desktop) {
// If it's in the exact range and has a layout supplied, try to use it...
if (desktop != null && breakpointValue == CommonBreakpoint.desktop) {
return desktop;
} else if (tablet != null && breakpoint == CommonBreakpoint.tablet) {
} else if (tablet != null && breakpointValue == CommonBreakpoint.tablet) {
return tablet;
} else if (phone != null && breakpoint == CommonBreakpoint.phone) {
} else if (phone != null && breakpointValue == CommonBreakpoint.phone) {
return phone;
} else if (tinyHardware != null) {
return tinyHardware;
}

// Get the largest non-null layout supplied
if (desktop != null) {
return desktop;
} else if (tablet != null) {
return tablet;
} else if (phone != null) {
return phone;
} else {
return tinyHardware!;
}
// ... otherwise cascade down all the available values to get the largest
// non-null layout supplied
if (desktop != null) {
return desktop;
} else if (tablet != null) {
return tablet;
} else if (phone != null) {
return phone;
} else {
return tinyHardware!;
}
}
}

// Widget Utility
extension CommonResolverWidget on BuildContext {
CommonLayout get layout => LayoutResolverWidget.of(this).resolver as CommonLayout;
}
86 changes: 83 additions & 3 deletions lib/src/layout_resolver.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,87 @@
abstract class LayoutResolver<T> {
const LayoutResolver();
import 'dart:collection';

class Breakpoint<T> implements Comparable<Breakpoint<T>> {
Breakpoint(this.size, {required this.value}): assert(size == null || size > 0);

final int? size;
final T value;

operator > (Breakpoint<T> other) => (size ?? 0) > (other.size ?? 0);
operator >= (Breakpoint<T> other) => (size ?? 0) >= (other.size ?? 0);
operator <= (Breakpoint<T> other) => (size ?? 0) <= (other.size ?? 0);
operator < (Breakpoint<T> other) => (size ?? 0) < (other.size ?? 0);

@override
int compareTo(Breakpoint<T> other) {
if (other > this) { return -1; }
if (other < this) { return 1; }

return 0;
}
}

extension BreakpointListExtension<T> on List<Breakpoint<T>> {

List<Breakpoint<T>> get descendingSorted {
final descendingSorted = List<Breakpoint<T>>.from(this);

descendingSorted..sort((a, b) {
final comparison = b.compareTo(a);
if (comparison == 0) {
throw 'breakpoint of size `${b.size}` (value `${b.value}`) had the same size of other breakpoint (value `${a.value})';
}

return comparison;
});

return descendingSorted;
}

T resolveBreakpoint(double size);

Breakpoint<T> firstCascadingBreakpoint(double size) {
for (final breakpoint in this) {
if (size >= (breakpoint.size ?? 0)) {
return breakpoint;
}
}

return last;
}
}



abstract class LayoutResolver<T> {
LayoutResolver({required double size, required List<Breakpoint<T>> breakpoints}): assert(size > 0), assert(breakpoints.length > 1), _descendingBreakpoints = breakpoints.descendingSorted, breakpoint = breakpoints.descendingSorted.firstCascadingBreakpoint(size);

final List<Breakpoint<T>> _descendingBreakpoints;


List<Breakpoint<T>> get breakpoints => _descendingBreakpoints;
final Breakpoint<T> breakpoint;
T get breakpointValue => breakpoint.value;

bool matchesValueOrSmaller(T match) {
final valueBreakpoint = breakpoints.firstWhere((breakpoint) => breakpoint.value == match, orElse: () {
throw 'TODO: throw or return false?';
});

return breakpoint <= valueBreakpoint;
}

bool matchesValue(T match) {
final valueBreakpoint = breakpoints.firstWhere((breakpoint) => breakpoint.value == match, orElse: () {
throw 'TODO';
});

return breakpoint.size == valueBreakpoint.size;
}

bool matchesValueOrLarger(T match) {
final valueBreakpoint = breakpoints.firstWhere((breakpoint) => breakpoint.value == match, orElse: () {
throw 'TODO';
});

return breakpoint >= valueBreakpoint;
}
}

0 comments on commit e0d1ba4

Please sign in to comment.