Skip to content

Commit

Permalink
YaruCompactLayout: replace icon/titleBuilder with itemBuilder (#284)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpnurmi committed Oct 12, 2022
1 parent d6a01a7 commit 64063fd
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 231 deletions.
18 changes: 9 additions & 9 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,16 @@ class _CompactPage extends StatelessWidget {
final pageItems = [configItem] + examplePageItems;

return YaruCompactLayout(
style: width > 1000
? YaruNavigationRailStyle.labelledExtended
: width > 500
? YaruNavigationRailStyle.labelled
: YaruNavigationRailStyle.compact,
length: pageItems.length,
iconBuilder: (context, index, selected) =>
pageItems[index].iconBuilder(context, selected),
titleBuilder: (context, index, selected) =>
pageItems[index].titleBuilder(context),
itemBuilder: (context, index, selected) => YaruNavigationRailItem(
icon: pageItems[index].iconBuilder(context, selected),
label: pageItems[index].titleBuilder(context),
style: width > 1000
? YaruNavigationRailStyle.labelledExtended
: width > 500
? YaruNavigationRailStyle.labelled
: YaruNavigationRailStyle.compact,
),
pageBuilder: (context, index) => pageItems[index].pageBuilder(context),
);
}
Expand Down
21 changes: 7 additions & 14 deletions lib/src/pages/layouts/yaru_compact_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,24 @@ class YaruCompactLayout extends StatefulWidget {
const YaruCompactLayout({
super.key,
required this.length,
required this.iconBuilder,
required this.titleBuilder,
required this.itemBuilder,
required this.pageBuilder,
this.style = YaruNavigationRailStyle.compact,
this.initialIndex = 0,
this.onSelected,
});

/// The total number of pages.
final int length;

/// A builder that is called for each page to build its icon.
final YaruCompactLayoutBuilder iconBuilder;

/// A builder that is called for each page to build its title.
final YaruCompactLayoutBuilder titleBuilder;
/// A builder that is called for each page to build its navigation rail item.
///
/// See also:
/// * [YaruNavigationRailItem]
final YaruCompactLayoutBuilder itemBuilder;

/// A builder that is called for each page to build its content.
final IndexedWidgetBuilder pageBuilder;

/// Define the navigation rail style, see [YaruNavigationRailStyle]
final YaruNavigationRailStyle style;

/// The index of the [YaruPageItem] that is selected from [pageItems]
final int initialIndex;

Expand Down Expand Up @@ -92,7 +87,6 @@ class _YaruCompactLayoutState extends State<YaruCompactLayout> {
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: YaruNavigationRail(
style: widget.style,
selectedIndex: _index,
onDestinationSelected: (index) {
setState(() {
Expand All @@ -101,8 +95,7 @@ class _YaruCompactLayoutState extends State<YaruCompactLayout> {
});
},
length: widget.length,
iconBuilder: widget.iconBuilder,
titleBuilder: widget.titleBuilder,
itemBuilder: widget.itemBuilder,
),
),
);
Expand Down
224 changes: 16 additions & 208 deletions lib/src/pages/layouts/yaru_navigation_rail.dart
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
import 'package:flutter/material.dart';
import '../../../yaru_widgets.dart';

/// Defines the look of a [YaruNavigationRail]
enum YaruNavigationRailStyle {
/// Will only show icons
compact,

/// Will show both icons and labels vertically
labelled,

/// Will show both icons and labels horizontally
labelledExtended,
}

const _kSizeAnimationDuration = Duration(milliseconds: 200);
const _kSelectedIconAnimationDuration = Duration(milliseconds: 250);
import 'yaru_compact_layout.dart';
import 'yaru_navigation_rail_item.dart';

class YaruNavigationRail extends StatelessWidget {
const YaruNavigationRail({
super.key,
required this.length,
required this.iconBuilder,
required this.titleBuilder,
required this.itemBuilder,
required this.selectedIndex,
required this.onDestinationSelected,
this.style = YaruNavigationRailStyle.compact,
}) : assert(length >= 2),
assert(
selectedIndex == null ||
Expand All @@ -34,11 +19,8 @@ class YaruNavigationRail extends StatelessWidget {
/// The total number of pages.
final int length;

/// A builder that is called for each page to build its icon.
final YaruCompactLayoutBuilder iconBuilder;

/// A builder that is called for each page to build its title.
final YaruCompactLayoutBuilder titleBuilder;
/// A builder that is called for each page to build its navigation rail item.
final YaruCompactLayoutBuilder itemBuilder;

/// The index into [destinations] for the current selected
/// [YaruPageItem] or null if no destination is selected.
Expand All @@ -51,201 +33,27 @@ class YaruNavigationRail extends StatelessWidget {
/// `setState` to rebuild the navigation rail with the new [selectedIndex].
final ValueChanged<int>? onDestinationSelected;

/// Define the navigation rail style, see [YaruNavigationRailStyle]
final YaruNavigationRailStyle style;

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Column(
children: <Widget>[
for (int i = 0; i < length; i += 1)
_YaruNavigationRailItem(
i,
i == selectedIndex,
iconBuilder,
titleBuilder,
onDestinationSelected,
style,
YaruNavigationRailItemScope(
index: i,
selected: i == selectedIndex,
onTap: () => onDestinationSelected?.call(i),
child: Builder(
builder: (context) => itemBuilder(
context,
i,
i == selectedIndex,
),
),
)
],
),
);
}
}

class _YaruNavigationRailItem extends StatefulWidget {
const _YaruNavigationRailItem(
this.index,
this.selected,
this.iconBuilder,
this.titleBuilder,
this.onDestinationSelected,
this.style,
);

final int index;
final bool selected;
final YaruCompactLayoutBuilder iconBuilder;
final YaruCompactLayoutBuilder titleBuilder;
final ValueChanged<int>? onDestinationSelected;
final YaruNavigationRailStyle style;

@override
State<_YaruNavigationRailItem> createState() =>
_YaruNavigationRailItemState();
}

class _YaruNavigationRailItemState extends State<_YaruNavigationRailItem> {
YaruNavigationRailStyle? oldStyle;

@override
void didUpdateWidget(_YaruNavigationRailItem oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.style != oldWidget.style) {
oldStyle = oldWidget.style;
}
}

@override
Widget build(BuildContext context) {
return _buildSizedContainer(
Material(
child: InkWell(
onTap: () {
if (widget.onDestinationSelected != null) {
widget.onDestinationSelected!.call(widget.index);
}
},
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(
vertical:
widget.style == YaruNavigationRailStyle.labelledExtended
? 10
: 5,
horizontal:
widget.style == YaruNavigationRailStyle.labelledExtended
? 8
: 5,
),
child: _buildColumnOrRow([
_buildIcon(context),
if (widget.style != YaruNavigationRailStyle.compact) ...[
_buildGap(),
_buildLabel(context),
]
]),
),
),
),
),
);
}

Alignment get _alignement {
return widget.style == YaruNavigationRailStyle.labelledExtended ||
oldStyle == YaruNavigationRailStyle.labelledExtended
? Alignment.centerLeft
: Alignment.topCenter;
}

double get _width {
switch (widget.style) {
case YaruNavigationRailStyle.labelledExtended:
return 250;
case YaruNavigationRailStyle.labelled:
return 100;
case YaruNavigationRailStyle.compact:
return 60;
}
}

Widget _buildSizedContainer(Widget child) {
return AnimatedSize(
duration: _kSizeAnimationDuration,
alignment: _alignement,
child: Align(
alignment: _alignement,
child: SizedBox(
width: _width,
child: child,
),
),
);
}

Widget _buildColumnOrRow(List<Widget> children) {
const mainAxisAlignment = MainAxisAlignment.start;

if (widget.style == YaruNavigationRailStyle.labelledExtended) {
return Row(
mainAxisAlignment: mainAxisAlignment,
children: children,
);
}

return Column(
mainAxisAlignment: mainAxisAlignment,
children: children,
);
}

Widget _buildIcon(BuildContext context) {
return AnimatedContainer(
duration: _kSelectedIconAnimationDuration,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: widget.selected
? Theme.of(context).colorScheme.onSurface.withOpacity(.1)
: null,
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 2,
horizontal: 10,
),
child: widget.iconBuilder(
context,
widget.index,
widget.selected,
),
),
);
}

Widget _buildGap() {
if (widget.style == YaruNavigationRailStyle.labelledExtended) {
return const SizedBox(width: 10);
}

return const SizedBox(height: 5);
}

Widget _buildLabel(BuildContext context) {
var label = widget.titleBuilder(context, widget.index, widget.selected);

if (label is YaruPageItemTitle) {
label = DefaultTextStyle.merge(
child: label,
style: TextStyle(
fontSize: widget.style == YaruNavigationRailStyle.labelledExtended
? 13
: 12,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
softWrap: true,
textAlign: widget.style == YaruNavigationRailStyle.labelledExtended
? null
: TextAlign.center,
maxLines: 1,
);
}

return widget.style == YaruNavigationRailStyle.labelledExtended
? Expanded(child: label)
: label;
}
}

0 comments on commit 64063fd

Please sign in to comment.