Skip to content

Commit

Permalink
feat(ib): scroll to index links in table of contents
Browse files Browse the repository at this point in the history
Signed-off-by: Manjot Sidhu <manjot.techie@gmail.com>
  • Loading branch information
manjotsidhu committed Aug 9, 2021
1 parent 0bce311 commit d508ad2
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 35 deletions.
6 changes: 4 additions & 2 deletions lib/models/ib/ib_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import 'package:flutter/material.dart';
abstract class IbContent {
String content;

IbContent({@required this.content});
IbContent({leading, @required this.content});
}

class IbTocItem extends IbContent {
final String leading;
final List<IbTocItem> items;

IbTocItem({@required String content, this.items}) : super(content: content);
IbTocItem({this.leading, @required String content, this.items})
: super(content: content);
}

class IbMd extends IbContent {
Expand Down
33 changes: 24 additions & 9 deletions lib/services/ib_engine_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ import 'package:mobile_app/utils/api_utils.dart';

/// Interactive Book Parser Engine
abstract class IbEngineService {
/// Generates slug from given [text]
static String getSlug(String text) {
return text
.trim()
.toLowerCase()
.replaceAll(RegExp(r'[^\w\s-]+'), '')
.replaceAll(RegExp(r'\s+'), '-');
}

Future<List<IbChapter>> getChapters();
Future<IbPageData> getPageData({String id = 'index.md'});
Future<String> getHtmlInteraction(String id);
Expand Down Expand Up @@ -157,14 +166,16 @@ class IbEngineServiceImpl implements IbEngineService {
if (li.getElementsByTagName('ol').isNotEmpty) {
toc.add(
IbTocItem(
content: '$eff_index. ${li.firstChild.text}',
leading: '$eff_index.',
content: li.firstChild.text,
items: _parseToc(li.children[1], num: !num),
),
);
} else {
toc.add(
IbTocItem(
content: '$eff_index. ${li.text}',
leading: '$eff_index.',
content: li.text,
),
);
}
Expand All @@ -186,17 +197,21 @@ class IbEngineServiceImpl implements IbEngineService {

for (var node in li.nodes) {
if (node is Element && node.localName == 'ul') {
sublist.addAll(_parseChapterContents(node, num: !num));
sublist.addAll(_parseChapterContents(
node,
num: !num,
));
break;
}
}

toc.add(IbTocItem(
content: root
? '$eff_index. ${li.nodes[0].text.trim()}'
: '$eff_index. ${li.text.trim()}',
items: sublist.isNotEmpty ? sublist : null,
));
toc.add(
IbTocItem(
leading: '$eff_index.',
content: root ? li.nodes[0].text.trim() : li.text.trim(),
items: sublist.isNotEmpty ? sublist : null,
),
);

index += 1;
}
Expand Down
38 changes: 38 additions & 0 deletions lib/ui/views/ib/builders/ib_headings_builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:mobile_app/services/ib_engine_service.dart';
import 'package:scroll_to_index/scroll_to_index.dart';

class IbHeadingsBuilder extends MarkdownElementBuilder {
final Map<String, int> slugMap;
final AutoScrollController controller;
final bool selectable;
var index = 0;

IbHeadingsBuilder({this.slugMap, this.controller, this.selectable = true});

@override
Widget visitElementAfter(md.Element element, TextStyle preferredStyle) {
var text = element.textContent;

var widget = selectable
? SelectableText(
text,
style: preferredStyle,
)
: Text(
text,
style: preferredStyle,
);

slugMap[IbEngineService.getSlug(text)] = index;

return AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index++,
child: widget,
);
}
}
39 changes: 34 additions & 5 deletions lib/ui/views/ib/ib_page_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import 'package:mobile_app/ib_theme.dart';
import 'package:mobile_app/models/ib/ib_chapter.dart';
import 'package:mobile_app/models/ib/ib_content.dart';
import 'package:mobile_app/models/ib/ib_page_data.dart';
import 'package:mobile_app/services/ib_engine_service.dart';
import 'package:mobile_app/ui/views/base_view.dart';
import 'package:mobile_app/ui/views/ib/builders/ib_chapter_contents_builder.dart';
import 'package:mobile_app/ui/views/ib/builders/ib_headings_builder.dart';
import 'package:mobile_app/ui/views/ib/builders/ib_interaction_builder.dart';
import 'package:mobile_app/ui/views/ib/builders/ib_mathjax_builder.dart';
import 'package:mobile_app/ui/views/ib/builders/ib_pop_quiz_builder.dart';
Expand All @@ -24,6 +26,7 @@ import 'package:mobile_app/ui/views/ib/syntaxes/ib_mathjax_syntax.dart';
import 'package:mobile_app/ui/views/ib/syntaxes/ib_md_tag_syntax.dart';
import 'package:mobile_app/utils/url_launcher.dart';
import 'package:mobile_app/viewmodels/ib/ib_page_viewmodel.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:url_launcher/url_launcher.dart';

typedef TocCallback = void Function(Function);
Expand All @@ -48,14 +51,17 @@ class IbPageView extends StatefulWidget {

class _IbPageViewState extends State<IbPageView> {
IbPageViewModel _model;
ScrollController _hideButtonController;
AutoScrollController _hideButtonController;
bool _isFabsVisible = true;

/// To track index through slug for scroll_to_index
final Map<String, int> _slugMap = {};

@override
void initState() {
super.initState();
_isFabsVisible = true;
_hideButtonController = ScrollController();
_hideButtonController = AutoScrollController(axis: Axis.vertical);
_hideButtonController.addListener(() {
if (_hideButtonController.position.userScrollDirection ==
ScrollDirection.reverse) {
Expand Down Expand Up @@ -130,6 +136,12 @@ class _IbPageViewState extends State<IbPageView> {

Widget _buildMarkdown(IbMd data) {
final _selectable = false;
final _headingsBuilder = IbHeadingsBuilder(
slugMap: _slugMap,
controller: _hideButtonController,
selectable: _selectable,
);

final _inlineBuilders = {
'sup': IbSuperscriptBuilder(selectable: _selectable),
'sub': IbSubscriptBuilder(selectable: _selectable),
Expand All @@ -143,6 +155,12 @@ class _IbPageViewState extends State<IbPageView> {
imageBuilder: _buildMarkdownImage,
onTapLink: _onTapLink,
blockBuilders: {
'h1': _headingsBuilder,
'h2': _headingsBuilder,
'h3': _headingsBuilder,
'h4': _headingsBuilder,
'h5': _headingsBuilder,
'h6': _headingsBuilder,
'chapter_contents': IbChapterContentsBuilder(
chapterContents: _model.pageData?.chapterOfContents?.isNotEmpty ??
false
Expand Down Expand Up @@ -213,29 +231,40 @@ class _IbPageViewState extends State<IbPageView> {
);
}

Widget _buildTocListTile(String content,
Future _onTocListTileTap(String title) async {
var slug = IbEngineService.getSlug(title);

Navigator.pop(context);
await _hideButtonController.scrollToIndex(_slugMap[slug],
preferPosition: AutoScrollPosition.begin);
}

Widget _buildTocListTile(String leading, String content,
{bool root = true, bool padding = true}) {
if (!root) {
return ListTile(
leading: Text(''),
visualDensity: !padding ? VisualDensity(vertical: -3) : null,
contentPadding: EdgeInsets.symmetric(horizontal: padding ? 16.0 : 0.0),
minLeadingWidth: 20,
title: Text(content),
title: Text('$leading $content'),
onTap: () async => _onTocListTileTap(content),
);
}

return ListTile(
visualDensity: !padding ? VisualDensity(vertical: -3) : null,
contentPadding: EdgeInsets.symmetric(horizontal: padding ? 16.0 : 0.0),
title: Text(content),
title: Text('$leading $content'),
onTap: () async => _onTocListTileTap(content),
);
}

List<Widget> _buildTocItems(IbTocItem item,
{bool root = false, bool padding = true}) {
var items = <Widget>[
_buildTocListTile(
item.leading,
item.content,
root: root,
padding: padding,
Expand Down
7 changes: 7 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.1"
scroll_to_index:
dependency: "direct main"
description:
name: scroll_to_index
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
share:
dependency: "direct main"
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies:
hive: ^2.0.4
hive_flutter: ^1.1.0
flutter_math_fork: ^0.3.3+1
scroll_to_index: ^2.0.0

dev_dependencies:
flutter_test:
Expand Down
Loading

0 comments on commit d508ad2

Please sign in to comment.