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
39 changes: 18 additions & 21 deletions lib/src/layouts/yaru_landscape_layout.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'dart:math';

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

import 'yaru_layout_index_controller.dart';
import 'yaru_master_detail_layout_delegate.dart';
import 'yaru_master_detail_page.dart';
import 'yaru_master_detail_theme.dart';
Expand All @@ -10,32 +13,24 @@ 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 YaruLayoutIndexController controller;

@override
State<YaruLandscapeLayout> createState() => _YaruLandscapeLayoutState();
Expand All @@ -73,24 +67,27 @@ 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();
}

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

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 +159,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 +195,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
23 changes: 23 additions & 0 deletions lib/src/layouts/yaru_layout_index_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';

class YaruLayoutIndexController extends ChangeNotifier {
YaruLayoutIndexController({required this.length, this.initialIndex = -1})
: _index = initialIndex,
_previousIndex = initialIndex;
final int length;
final int initialIndex;

int get index => _index;
int _index;
set index(int value) => _setIndex(value);

int get previousIndex => _previousIndex;
int _previousIndex;

void _setIndex(value) {
assert(value < length || length == 0);
_previousIndex = _index;
_index = value;
notifyListeners();
}
}
37 changes: 12 additions & 25 deletions lib/src/layouts/yaru_master_detail_page.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';

import '../constants.dart';
import 'yaru_detail_page.dart';
import 'yaru_landscape_layout.dart';
import 'yaru_layout_index_controller.dart';
import 'yaru_master_detail_layout_delegate.dart';
import 'yaru_master_detail_theme.dart';
import 'yaru_master_tile.dart';
Expand Down Expand Up @@ -55,7 +57,6 @@ class YaruMasterDetailPage extends StatefulWidget {
this.layoutDelegate =
const YaruMasterFixedPaneDelegate(paneWidth: _kDefaultPaneWidth),
this.appBar,
this.initialIndex,
jpnurmi marked this conversation as resolved.
Show resolved Hide resolved
this.onSelected,
this.controller,
});
Expand All @@ -81,43 +82,33 @@ class YaruMasterDetailPage extends StatefulWidget {
/// An optional custom AppBar for the left pane.
final PreferredSizeWidget? appBar;

/// An optional index of the initial page to show.
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 ValueNotifier<int>? controller;
final YaruLayoutIndexController? controller;

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

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

double? _previousPaneWidth;
late final YaruLayoutIndexController _controller;

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

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

@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 +119,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
73 changes: 36 additions & 37 deletions lib/src/layouts/yaru_portrait_layout.dart
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
import 'package:flutter/material.dart';

import 'yaru_layout_index_controller.dart';
import 'yaru_master_detail_page.dart';
import 'yaru_master_detail_theme.dart';
import 'yaru_master_list_view.dart';

class YaruPortraitLayout extends StatefulWidget {
const YaruPortraitLayout({
super.key,
required this.length,
required this.selectedIndex,
required this.tileBuilder,
required this.pageBuilder,
required this.onSelected,
this.onSelected,
this.appBar,
this.controller,
required this.controller,
});

final int length;
final int selectedIndex;
final YaruMasterDetailBuilder tileBuilder;
final IndexedWidgetBuilder pageBuilder;
final ValueChanged<int> onSelected;
final ValueChanged<int>? onSelected;

final PreferredSizeWidget? appBar;

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

@override
_YaruPortraitLayoutState createState() => _YaruPortraitLayoutState();
Expand All @@ -39,31 +35,34 @@ class _YaruPortraitLayoutState extends State<YaruPortraitLayout> {

@override
void initState() {
_selectedIndex = widget.selectedIndex;
widget.controller?.addListener(_controllerCallback);
widget.controller.addListener(_controllerCallback);
_selectedIndex = widget.controller.index;
super.initState();
}

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

void _controllerCallback() {
_navigator.popUntil((route) => route.isFirst);
_onTap(widget.controller!.value);
if (widget.controller.index != _selectedIndex) {
_selectedIndex = widget.controller.index;
setState(() => () {});
}
}

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

MaterialPageRoute pageRoute(int index) {
return MaterialPageRoute(
builder: (context) => widget.pageBuilder(context, index),
MaterialPage page(int index) {
return MaterialPage(
child: Builder(
builder: (context) => widget.pageBuilder(context, _selectedIndex),
),
);
}

Expand All @@ -78,24 +77,24 @@ class _YaruPortraitLayoutState extends State<YaruPortraitLayout> {
),
child: Navigator(
key: _navigatorKey,
onGenerateInitialRoutes: (navigator, initialRoute) {
return [
MaterialPageRoute(
builder: (context) {
return Scaffold(
appBar: widget.appBar,
body: YaruMasterListView(
length: widget.length,
selectedIndex: _selectedIndex,
onTap: _onTap,
builder: widget.tileBuilder,
),
);
},
),
if (_selectedIndex != -1) pageRoute(_selectedIndex)
];
onPopPage: (route, result) {
_selectedIndex = -1;
return route.didPop(result);
},
pages: [
MaterialPage(
child: Scaffold(
appBar: widget.appBar,
body: YaruMasterListView(
length: widget.controller.length,
selectedIndex: _selectedIndex,
onTap: _onTap,
builder: widget.tileBuilder,
),
),
),
if (_selectedIndex != -1) page(_selectedIndex)
],
),
),
);
Expand Down
1 change: 1 addition & 0 deletions lib/yaru_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export 'src/extensions/border_radius_extension.dart';
export 'src/layouts/yaru_compact_layout.dart';
export 'src/layouts/yaru_compact_layout_theme.dart';
export 'src/layouts/yaru_detail_page.dart';
export 'src/layouts/yaru_layout_index_controller.dart';
export 'src/layouts/yaru_master_detail_layout_delegate.dart';
export 'src/layouts/yaru_master_detail_page.dart';
export 'src/layouts/yaru_master_detail_theme.dart';
Expand Down
4 changes: 2 additions & 2 deletions test/pages/yaru_master_detail_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void main() {
'controller',
(tester) async {
final variant = goldenVariant.currentValue!;
final controller = ValueNotifier<int>(0);
final controller = YaruLayoutIndexController(length: 8);
await tester.pumpScaffold(
YaruMasterDetailPage(
controller: controller,
Expand All @@ -73,7 +73,7 @@ void main() {
size: Size(variant.value!, 480),
);

controller.value = 3;
controller.index = 3;
await tester.pumpAndSettle();
if (variant.label.startsWith('portrait')) {
expect(find.text('Master'), findsNothing);
Expand Down