Skip to content

Commit

Permalink
Use page title text by default if top bar title not provided
Browse files Browse the repository at this point in the history
  • Loading branch information
ulusoyca committed Jul 29, 2023
1 parent a51fc97 commit 430ce84
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class WaterSettingsModalPage {
);
},
),
topBarTitle: const ModalSheetTopBarTitle(pageTitle),
pageTitle: const ModalSheetTitle(pageTitle),
closeButton: WoltModalSheetCloseButton(onClosed: onClosed),
backButton: WoltModalSheetBackButton(onBackPressed: onBackButtonPressed),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:wolt_modal_sheet/src/content/components/main_content/wolt_modal_sheet_top_bar_title.dart';
import 'package:wolt_modal_sheet/src/modal_page/wolt_modal_sheet_page.dart';
import 'package:wolt_modal_sheet/src/utils/wolt_layout_transformation_utils.dart';

Expand Down Expand Up @@ -62,14 +63,16 @@ class WoltModalSheetTopBar extends StatelessWidget {
),
],
),
Center(child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
page.topBarTitle ?? const SizedBox.shrink(),
const SizedBox(height: _topBarTitleTranslationYAmount),
],
)),
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
WoltModalSheetTopBarTitle(page: page, pageTitleKey: titleKey),
const SizedBox(height: _topBarTitleTranslationYAmount),
],
),
),
],
);
}
Expand Down Expand Up @@ -103,12 +106,11 @@ class _TopBarFlowDelegate extends FlowDelegate {
const topBarTranslationYStart = 0.0;
final topBarTranslationYEnd = topBarTranslationYAmountInPx;
final topBarTranslationYAndOpacityStartPoint =
heroImageHeight == 0 ? 0 : heroImageHeight - topBarHeight;
heroImageHeight == 0 ? 0 : heroImageHeight - topBarHeight;

const topBarTitleTranslationYStart = 0.0;
const topBarTitleTranslationYAmount = WoltModalSheetTopBar._topBarTitleTranslationYAmount;
const topBarTitleTranslationYEnd =
topBarTitleTranslationYStart + topBarTitleTranslationYAmount;
const topBarTitleTranslationYEnd = topBarTitleTranslationYStart + topBarTitleTranslationYAmount;

pageTitleHeight = pageTitleHeight == 0 ? pageTitlePaddingTop : pageTitleHeight;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:wolt_modal_sheet/src/modal_page/wolt_modal_sheet_page.dart';

/// A widget to display the top bar title in a modal sheet.
///
/// It tries to display the title in the following order of preference:
/// - The `topBarTitle` widget if it's not null.
/// - The text data in `pageTitle` if it's a `Text` widget.
/// - The first text descendant of the `pageTitle` if it's not a `Text` widget and has a
/// [Text] descendant.
/// - If none of the above are applicable, it defaults to a `SizedBox.shrink`.
class WoltModalSheetTopBarTitle extends StatefulWidget {
const WoltModalSheetTopBarTitle({
Key? key,
required this.page,
required this.pageTitleKey,
}) : super(key: key);

final WoltModalSheetPage page;
final GlobalKey pageTitleKey;

@override
State<WoltModalSheetTopBarTitle> createState() => _WoltModalSheetTopBarTitleState();
}

class _WoltModalSheetTopBarTitleState extends State<WoltModalSheetTopBarTitle> {
String? pageTitleText;

@override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) {
_extractPageTitleText();
});
}

void _extractPageTitleText() {
final isPageTitleTextWidget = widget.page.pageTitle is Text;
final pageTitleElement = widget.pageTitleKey.currentContext;

if (widget.page.topBarTitle != null || isPageTitleTextWidget) {
return;
} else if (pageTitleElement != null) {
_visitAllDescendants(pageTitleElement);
}
}

@override
Widget build(BuildContext context) {
final topBarTitle = widget.page.topBarTitle;
final pageTitleText =
(widget.page.pageTitle is Text) ? (widget.page.pageTitle as Text).data : this.pageTitleText;

if (topBarTitle != null) {
return topBarTitle;
} else if (pageTitleText != null) {
return Row(
children: [
const SizedBox(width: 72),
Expanded(
child: Text(
pageTitleText,
style: Theme.of(context).textTheme.titleSmall,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
const SizedBox(width: 72),
],
);
}
return const SizedBox.shrink();
}

// This function visits all descendants of an Element until it finds a Text widget.
// It could potentially have a performance impact if the descendant Text widget is deeply nested.
// However, since it stops visiting as soon as it finds a Text widget,
// the impact should be minimal unless the widget tree is exceptionally large or dense.
void _visitAllDescendants(BuildContext element) {
element.visitChildElements((childElement) {
if (childElement.widget is Text) {
setState(() {
pageTitleText = (childElement.widget as Text).data;
});
return; // stop visiting other children
}
// If it's not a Text widget, continue visiting its descendants
_visitAllDescendants(childElement);
});
}
}
16 changes: 15 additions & 1 deletion lib/src/modal_page/wolt_modal_sheet_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import 'package:flutter/material.dart';

/// A description for a page to be built within [WoltScrollableModalSheet].
class WoltModalSheetPage {
/// Represents the widget that stands for the page title.
/// Represents the widget that stands for the page title. In many cases the text content for the
/// [topBarTitle] is the same as the text content in [pageTitle]. Hence, the text data of this
/// widget will be used as the source for the text data of [topBarTitle], when not provided.
///
/// A deeply nested text in the [pageTitle] widget can cause performance issues during the title
/// retrieval process. Hence, it's recommended to keep the title text structure as simple as
/// possible or explicitly provide the [topBarTitle] widget.
final Widget? pageTitle;

/// distance between image and page title or top bar and main title (in case of no image)
Expand All @@ -16,6 +22,14 @@ class WoltModalSheetPage {
final Widget? singleChildContent;

/// A [Widget] representing the title displayed in the top bar.
///
/// When not provided, the data of the first "Text" direct child of the [pageTitle] widget will
/// be used as the data for topBarTitle Text widget. If you want to avoid using the pageTitle's
/// text data, you should explicitly provide topBarTitle widget or set it as SizedBox.shrink().
///
/// A deeply nested text in the [pageTitle] widget can cause performance issues during the title
/// retrieval process. Hence, it's recommended to keep the title text structure as simple as
/// possible or explicitly provide the [topBarTitle] widget.
final Widget? topBarTitle;

/// A [Widget] representing the hero image displayed on top of the main content.
Expand Down
1 change: 0 additions & 1 deletion playground/lib/home/pages/root_sheet_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class RootSheetPage {
},
),
pageTitle: const ModalSheetTitle(title),
topBarTitle: const ModalSheetTopBarTitle(title),
closeButton: WoltModalSheetCloseButton(onClosed: onClosed),
singleChildContent: Padding(
padding: const EdgeInsets.only(bottom: 120),
Expand Down
1 change: 0 additions & 1 deletion playground/lib/home/pages/sheet_page_with_lazy_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ class SheetPageWithLazyList {
image: AssetImage('lib/assets/images/material_colors_hero.png'),
fit: BoxFit.cover,
),
topBarTitle: const ModalSheetTopBarTitle(titleText),
backButton: WoltModalSheetBackButton(onBackPressed: onBackPressed),
closeButton: WoltModalSheetCloseButton(onClosed: onClosed),
sliverList: SliverList(
Expand Down
1 change: 0 additions & 1 deletion playground_navigator2/lib/modal/pages/root_sheet_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ class RootSheetPage {
},
),
pageTitle: const ModalSheetTitle(title),
topBarTitle: const ModalSheetTopBarTitle(title),
closeButton: WoltModalSheetCloseButton(onClosed: context.read<RouterCubit>().closeSheet),
singleChildContent: Padding(
padding: const EdgeInsets.only(bottom: 120),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ class SheetPageWithLazyList {
padding: EdgeInsets.symmetric(horizontal: 16),
child: ModalSheetTitle(titleText),
),
topBarTitle: const ModalSheetTopBarTitle(titleText),
backButton: WoltModalSheetBackButton(onBackPressed: () => cubit.goToPage(currentPage - 1)),
closeButton: WoltModalSheetCloseButton(onClosed: cubit.closeSheet),
sliverList: SliverList(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:wolt_modal_sheet/src/content/components/main_content/wolt_modal_sheet_top_bar_title.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

void main() {
group('WoltModalSheetTopBarTitle tests', () {
// Global key to be used for pageTitle widget
final GlobalKey pageTitleKey = GlobalKey();

testWidgets('should display top bar title when provided', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: WoltModalSheetTopBarTitle(
page: WoltModalSheetPage.withSingleChild(
child: const SizedBox.shrink(),
topBarTitle: const Text('Top Bar Title'),
pageTitle: const Text('Page Title'),
),
pageTitleKey: pageTitleKey,
),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Top Bar Title'), findsOneWidget);
expect(find.text('Page Title'), findsNothing);
});

testWidgets('should use page title when top bar title is not provided and page title is Text',
(tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: WoltModalSheetTopBarTitle(
page: WoltModalSheetPage.withSingleChild(
child: const SizedBox.shrink(),
pageTitle: Text('Page Title', key: pageTitleKey),
),
pageTitleKey: pageTitleKey,
),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Page Title'), findsOneWidget);
});

testWidgets('should display nothing when neither top bar title nor page title is provided',
(tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: WoltModalSheetTopBarTitle(
page: WoltModalSheetPage.withSingleChild(
child: const SizedBox.shrink(),
),
pageTitleKey: pageTitleKey,
),
),
),
);
await tester.pumpAndSettle();
expect(tester.element(find.byType(WoltModalSheetTopBarTitle).at(0)).size, Size.zero);
});
});
}
80 changes: 80 additions & 0 deletions test/widget_test_template.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

/// This is a template for widget tests.
/// It is meant to be copied and pasted into a new file and then modified to fit the widget or feature you're testing
/// An example of a test files that uses this template are:
/// [test/settings/task_history/task_history_group_task_list_v2_test.dart]
/// [test/localizations/localizations_test.dart]
/// Confluence page with more inspiration:
/// https://woltwide.atlassian.net/wiki/spaces/MAL/pages/3215262013/Widget+and+Unit+tests+for+a+new+features
void main() {
/// Name of the widget you're testing
group('Widget tests group description', () {
/// Registers a function to be run once before all tests.
/// This is where you should register all fallback values for fakes
setUpAll(() {});

/// This is a setup method that will be called everytime before each test.
/// Use it to initialize and register mocks.
/// Avoid stubbing on top level, rather do it in the method group setup callback or test case itself.
/// If you need test objects to be created, do it in the group or test case itself.
///
/// only do generic setup here, if you need to do specific setup for a test case, do it in the test case
setUp(() {});

/// This is a teardown method that will be called after each test
/// use it to dispose of mocks and fakes if needed
tearDown(() {});

/// If widget functionality is very complex and might be divided to a smaller logical groups,
/// test suite should be split to a smaller groups to ease understanding.
/// Any setup that is specific to the subgroup and needs to be done once should be done in the subgroup's setup method
/// otherwise in the test case itself
group('subgroup-1', () {
/// You can use the setup method to do stubbing for all test cases. If you need to do specific stubbing for a
/// test case, do it in the test case itself.
setUp(() {});

/// Each test case should have a name that describes what it is testing and what the expected result is.
/// Don't test too much in one case. If you need to test multiple things, create multiple test cases.
/// Ideally, start the name with the word 'should' to make it clear what the expected result is and use the
/// word 'when' to describe a specific scenario.
testWidgets('should <expected result> when <action to perform>', (tester) async {
/// Arrange
/// Create test objects and stub methods here
// Set up needed data
await tester.pumpWidget(
const MaterialApp(home: SizedBox.shrink() /* Insert your widget to test here */),
);
await tester.pumpAndSettle();

/// Act
/// Call the method you're testing
// Perform Step 1
// Perform Step 2

/// Assert
/// Verify that the method did what it was supposed to do
// Assert that steps reached <expected result>
expect(
'Actual result',
'Expected result',
reason: 'Failure reason',
);
});

testWidgets('should <expected result 2> when <action to perform 2>', (tester) async {});
});

group('subgroup-2', () {
setUp(() {});

testWidgets('should <expected result 1> when <actions to perform 1>', (tester) async {});

testWidgets('should <expected result 2> when <actions to perform 2>', (tester) async {});
});
});
}

0 comments on commit 430ce84

Please sign in to comment.