Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yaru layout index controller #414

Merged
merged 12 commits into from
Nov 29, 2022
76 changes: 54 additions & 22 deletions lib/src/layouts/yaru_compact_layout.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import 'dart:math';

import 'package:flutter/material.dart';

import 'yaru_compact_layout_theme.dart';
import 'yaru_navigation_rail.dart';
import 'yaru_page_controller.dart';

typedef YaruCompactLayoutBuilder = Widget Function(
BuildContext context,
Expand All @@ -15,15 +18,17 @@ const _kScrollbarThickness = 4.0;
class YaruCompactLayout extends StatefulWidget {
const YaruCompactLayout({
super.key,
required this.length,
this.length,
required this.itemBuilder,
required this.pageBuilder,
this.initialIndex = 0,
jpnurmi marked this conversation as resolved.
Show resolved Hide resolved
this.initialIndex,
this.onSelected,
});
this.controller,
}) : assert(initialIndex == null || controller == null),
assert((length == null) != (controller == null));

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

/// A builder that is called for each page to build its navigation rail item.
///
Expand All @@ -35,33 +40,58 @@ class YaruCompactLayout extends StatefulWidget {
final IndexedWidgetBuilder pageBuilder;

/// The index of the initial page to show.
final int initialIndex;
final int? initialIndex;

/// Called when the user selects a page.
final ValueChanged<int>? onSelected;

/// An optional controller that can be used to navigate to a specific index.
final YaruPageController? controller;

@override
State<YaruCompactLayout> createState() => _YaruCompactLayoutState();
}

class _YaruCompactLayoutState extends State<YaruCompactLayout> {
late int _index;
late final ScrollController _scrollController;
late final YaruPageController _pageController;

late ScrollController _controller;
int get _length => widget.length ?? widget.controller!.length;

@override
void initState() {
_controller = ScrollController();
_index = widget.initialIndex;
super.initState();
_scrollController = ScrollController();
_updatePageController();
}

@override
void dispose() {
_controller.dispose();
_scrollController.dispose();
_pageController.removeListener(_pageControllerCallback);
if (widget.controller == null) _pageController.dispose();
super.dispose();
}

@override
void didUpdateWidget(covariant YaruCompactLayout oldWidget) {
super.didUpdateWidget(oldWidget);
jpnurmi marked this conversation as resolved.
Show resolved Hide resolved
if (widget.controller != oldWidget.controller) {
oldWidget.controller?.removeListener(_pageControllerCallback);
_updatePageController();
}
}

void _updatePageController() {
_pageController =
widget.controller ?? YaruPageController(length: widget.length!);
_pageController.addListener(_pageControllerCallback);
}

void _pageControllerCallback() {
setState(() {});
}

@override
Widget build(BuildContext context) {
return LayoutBuilder(
Expand All @@ -83,6 +113,11 @@ class _YaruCompactLayoutState extends State<YaruCompactLayout> {
);
}

void _onTap(int index) {
_pageController.index = index;
widget.onSelected?.call(index);
}

Widget _buildNavigationRail(BuildContext context, BoxConstraints constraint) {
return Theme(
data: Theme.of(context).copyWith(
Expand All @@ -91,18 +126,13 @@ class _YaruCompactLayoutState extends State<YaruCompactLayout> {
),
),
child: SingleChildScrollView(
controller: _controller,
controller: _scrollController,
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: YaruNavigationRail(
selectedIndex: _index,
onDestinationSelected: (index) {
setState(() {
_index = index;
widget.onSelected?.call(index);
});
},
length: widget.length,
selectedIndex: max(_pageController.index, 0),
onDestinationSelected: _onTap,
length: _length,
itemBuilder: widget.itemBuilder,
),
),
Expand All @@ -116,6 +146,8 @@ class _YaruCompactLayoutState extends State<YaruCompactLayout> {

Widget _buildPageView(BuildContext context) {
final theme = YaruCompactLayoutTheme.of(context);
final index = max(_pageController.index, 0);

return Expanded(
child: Theme(
data: Theme.of(context).copyWith(
Expand All @@ -124,9 +156,9 @@ class _YaruCompactLayoutState extends State<YaruCompactLayout> {
child: Navigator(
pages: [
MaterialPage(
key: ValueKey(_index),
child: widget.length > _index
? widget.pageBuilder(context, _index)
key: ValueKey(index),
child: _length > index
? widget.pageBuilder(context, index)
: widget.pageBuilder(context, 0),
),
],
Expand Down
47 changes: 26 additions & 21 deletions lib/src/layouts/yaru_landscape_layout.dart
Original file line number Diff line number Diff line change
@@ -1,41 +1,36 @@
import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'yaru_master_detail_layout_delegate.dart';
import 'yaru_master_detail_page.dart';
import 'yaru_master_detail_theme.dart';
import 'yaru_master_list_view.dart';
import 'yaru_page_controller.dart';

class YaruLandscapeLayout extends StatefulWidget {
/// Creates a landscape layout
const YaruLandscapeLayout({
super.key,
required this.length,
required this.selectedIndex,
required this.tileBuilder,
required this.pageBuilder,
required this.onSelected,
this.onSelected,
required this.layoutDelegate,
this.previousPaneWidth,
this.onLeftPaneWidthChange,
this.appBar,
this.controller,
required this.controller,
});

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

/// Current index of the selected page.
final int selectedIndex;

/// A builder that is called for each page to build its master tile.
final YaruMasterDetailBuilder tileBuilder;

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

/// Callback that returns an index when the page changes.
final ValueChanged<int> onSelected;
final ValueChanged<int>? onSelected;

/// Controls the pane width with defined parameters
final YaruMasterDetailPaneLayoutDelegate layoutDelegate;
Expand All @@ -50,8 +45,7 @@ class YaruLandscapeLayout extends StatefulWidget {
/// If provided, a second [AppBar] will be created right to it.
final PreferredSizeWidget? appBar;

/// An optional controller that can be used to navigate to a specific index.
final ValueNotifier<int>? controller;
final YaruPageController controller;

@override
State<YaruLandscapeLayout> createState() => _YaruLandscapeLayoutState();
Expand All @@ -73,24 +67,35 @@ class _YaruLandscapeLayoutState extends State<YaruLandscapeLayout> {
@override
void initState() {
super.initState();
_selectedIndex = widget.selectedIndex;
_paneWidth = widget.previousPaneWidth;
widget.controller?.addListener(_controllerCallback);
widget.controller.addListener(_controllerCallback);
_selectedIndex = max(widget.controller.index, 0);
}

@override
void dispose() {
widget.controller?.removeListener(_controllerCallback);
widget.controller.removeListener(_controllerCallback);
super.dispose();
}

@override
void didUpdateWidget(covariant YaruLandscapeLayout oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != oldWidget.controller) {
oldWidget.controller.removeListener(_controllerCallback);
widget.controller.addListener(_controllerCallback);
}
}

void _controllerCallback() {
_onTap(widget.controller!.value);
if (widget.controller.index != _selectedIndex) {
setState(() => _selectedIndex = max(widget.controller.index, 0));
}
}

void _onTap(int index) {
widget.onSelected(index);
setState(() => _selectedIndex = index);
widget.controller.index = index;
widget.onSelected?.call(_selectedIndex);
}

void updatePaneWidth({
Expand Down Expand Up @@ -162,7 +167,7 @@ class _YaruLandscapeLayoutState extends State<YaruLandscapeLayout> {
child: Scaffold(
appBar: widget.appBar,
body: YaruMasterListView(
length: widget.length,
length: widget.controller.length,
selectedIndex: _selectedIndex,
onTap: _onTap,
builder: widget.tileBuilder,
Expand Down Expand Up @@ -198,7 +203,7 @@ class _YaruLandscapeLayoutState extends State<YaruLandscapeLayout> {
pages: [
MaterialPage(
key: ValueKey(_selectedIndex),
child: widget.length > _selectedIndex
child: widget.controller.length > _selectedIndex
? widget.pageBuilder(context, _selectedIndex)
: widget.pageBuilder(context, 0),
),
Expand Down
51 changes: 27 additions & 24 deletions lib/src/layouts/yaru_master_detail_page.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';

import '../constants.dart';
import 'yaru_detail_page.dart';
import 'yaru_landscape_layout.dart';
import 'yaru_master_detail_layout_delegate.dart';
import 'yaru_master_detail_theme.dart';
import 'yaru_master_tile.dart';
import 'yaru_page_controller.dart';
import 'yaru_portrait_layout.dart';

const _kDefaultPaneWidth = 280.0;
Expand Down Expand Up @@ -49,7 +51,7 @@ typedef YaruMasterDetailBuilder = Widget Function(
class YaruMasterDetailPage extends StatefulWidget {
const YaruMasterDetailPage({
super.key,
required this.length,
this.length,
required this.tileBuilder,
required this.pageBuilder,
this.layoutDelegate =
Expand All @@ -58,10 +60,11 @@ class YaruMasterDetailPage extends StatefulWidget {
this.initialIndex,
jpnurmi marked this conversation as resolved.
Show resolved Hide resolved
this.onSelected,
this.controller,
});
}) : assert(initialIndex == null || controller == null),
assert((length == null) != (controller == null));

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

/// A builder that is called for each page to build its master tile.
///
Expand All @@ -88,36 +91,40 @@ class YaruMasterDetailPage extends StatefulWidget {
final ValueChanged<int?>? onSelected;

/// An optional controller that can be used to navigate to a specific index.
final ValueNotifier<int>? controller;
final YaruPageController? controller;

@override
_YaruMasterDetailPageState createState() => _YaruMasterDetailPageState();
}

class _YaruMasterDetailPageState extends State<YaruMasterDetailPage> {
var _index = -1;
var _previousIndex = 0;

double? _previousPaneWidth;
late final YaruPageController _controller;

void _setIndex(int index) {
_previousIndex = _index;
_index = index;
widget.onSelected?.call(index == -1 ? null : index);
}
void _updateController() => _controller = widget.controller ??
YaruPageController(
length: _length,
initialIndex: widget.initialIndex ?? -1,
);

int get _length => widget.length ?? widget.controller!.length;

@override
void initState() {
super.initState();
_index = widget.initialIndex ?? -1;
_updateController();
}

@override
void dispose() {
if (widget.controller == null) _controller.dispose();
super.dispose();
}

@override
void didUpdateWidget(covariant YaruMasterDetailPage oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.initialIndex != oldWidget.initialIndex) {
_index = widget.initialIndex ?? -1;
}
if (widget.controller != oldWidget.controller) _updateController();
jpnurmi marked this conversation as resolved.
Show resolved Hide resolved
}

@override
Expand All @@ -128,26 +135,22 @@ class _YaruMasterDetailPageState extends State<YaruMasterDetailPage> {
builder: (context, constraints) {
if (constraints.maxWidth < breakpoint) {
return YaruPortraitLayout(
length: widget.length,
selectedIndex: _index,
tileBuilder: widget.tileBuilder,
pageBuilder: widget.pageBuilder,
onSelected: _setIndex,
onSelected: widget.onSelected,
appBar: widget.appBar,
controller: widget.controller,
controller: _controller,
);
} else {
return YaruLandscapeLayout(
length: widget.length,
selectedIndex: _index == -1 ? _previousIndex : _index,
tileBuilder: widget.tileBuilder,
pageBuilder: widget.pageBuilder,
onSelected: _setIndex,
onSelected: widget.onSelected,
layoutDelegate: widget.layoutDelegate,
previousPaneWidth: _previousPaneWidth,
onLeftPaneWidthChange: (panWidth) => _previousPaneWidth = panWidth,
appBar: widget.appBar,
controller: widget.controller,
controller: _controller,
);
}
},
Expand Down