完成目录
parent
14e8f42b9f
commit
7ac20f877a
@ -1,29 +1,178 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:isekai_wiki/api/response/parse.dart';
|
||||
import 'package:isekai_wiki/components/gesture_detector.dart';
|
||||
import 'package:isekai_wiki/reactive/reactive.dart';
|
||||
import 'package:isekai_wiki/components/safearea_builder.dart';
|
||||
import 'package:isekai_wiki/styles.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
class TOCList extends StatefulWidget {
|
||||
const TOCList({super.key});
|
||||
final String activedItem;
|
||||
final bool hasFirstSection;
|
||||
final List<MWParseSectionInfo> sections;
|
||||
final void Function(String anchor)? onSwitchTitle;
|
||||
|
||||
const TOCList({
|
||||
super.key,
|
||||
required this.sections,
|
||||
this.activedItem = "",
|
||||
this.hasFirstSection = false,
|
||||
this.onSwitchTitle,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _TOCListState();
|
||||
}
|
||||
|
||||
class _TOCListState extends ReactiveState<TOCList> {
|
||||
class _TOCListState extends State<TOCList> {
|
||||
String _activedItem = "";
|
||||
String _lastSelectedItem = "";
|
||||
final ItemScrollController _itemScrollController = ItemScrollController();
|
||||
|
||||
@override
|
||||
Widget render(BuildContext context) {
|
||||
return Container();
|
||||
void didUpdateWidget(covariant TOCList oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (_activedItem != widget.activedItem && _activedItem != _lastSelectedItem) {
|
||||
_activedItem = widget.activedItem;
|
||||
// Scroll to current item
|
||||
int activedIndex;
|
||||
if (_activedItem == "_firstSection") {
|
||||
activedIndex = 0;
|
||||
} else {
|
||||
activedIndex = widget.sections.indexWhere((element) => element.anchor == _activedItem);
|
||||
if (widget.hasFirstSection) {
|
||||
activedIndex += 1;
|
||||
}
|
||||
if (activedIndex == -1) {
|
||||
activedIndex = 0;
|
||||
}
|
||||
}
|
||||
_itemScrollController.jumpTo(index: activedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void handleTOCItemPressed(String anchor) {
|
||||
_lastSelectedItem = anchor;
|
||||
widget.onSwitchTitle?.call(_lastSelectedItem);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final itemCount = widget.hasFirstSection ? widget.sections.length + 1 : widget.sections.length;
|
||||
|
||||
return DefaultTextStyle(
|
||||
style: CupertinoTheme.of(context).textTheme.textStyle,
|
||||
child: Container(
|
||||
color: Styles.themePageBackgroundColor,
|
||||
child: SafeAreaBuilder(
|
||||
builder: (context, padding) => ScrollablePositionedList.builder(
|
||||
itemScrollController: _itemScrollController,
|
||||
padding: EdgeInsets.only(
|
||||
top: padding.top + 2,
|
||||
bottom: padding.bottom + 2,
|
||||
right: padding.right,
|
||||
),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
if (widget.hasFirstSection && index == 0) {
|
||||
// 创建首项
|
||||
return TOCItem(
|
||||
text: "简介",
|
||||
anchor: "_firstSection",
|
||||
active: widget.activedItem == "_firstSection",
|
||||
onPressed: handleTOCItemPressed,
|
||||
);
|
||||
}
|
||||
|
||||
var section =
|
||||
widget.hasFirstSection ? widget.sections[index - 1] : widget.sections[index];
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: index == 0
|
||||
? BorderSide.none
|
||||
: const BorderSide(color: Color.fromRGBO(234, 236, 240, 1))),
|
||||
),
|
||||
child: TOCItem(
|
||||
text: section.line,
|
||||
number: section.number,
|
||||
anchor: section.anchor,
|
||||
active: widget.activedItem == section.anchor,
|
||||
onPressed: handleTOCItemPressed,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TOCItem extends StatelessWidget {
|
||||
final VoidCallback? onTap;
|
||||
final void Function(String anchor)? onPressed;
|
||||
final bool active;
|
||||
final String? number;
|
||||
final String anchor;
|
||||
final String text;
|
||||
|
||||
const TOCItem(
|
||||
{super.key,
|
||||
this.onPressed,
|
||||
this.active = false,
|
||||
this.number,
|
||||
required this.text,
|
||||
required this.anchor});
|
||||
|
||||
const TOCItem({super.key, this.onTap, this.active = false});
|
||||
void handleTap() {
|
||||
onPressed?.call(anchor);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
return Stack(
|
||||
children: [
|
||||
ClickableBuilder(
|
||||
builder: (context, mode, child) => GestureDetector(
|
||||
onTap: handleTap,
|
||||
child: Container(
|
||||
color: mode == PointerActiveMode.active
|
||||
? Styles.themeNormalActionActiveColor
|
||||
: Styles.themeNormalActionColor,
|
||||
padding: const EdgeInsets.only(top: 12, right: 10, bottom: 12, left: 15),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
child: DefaultTextStyle(
|
||||
style: CupertinoTheme.of(context).textTheme.textStyle,
|
||||
child: Row(
|
||||
children: [
|
||||
if (number != null) Text(number!),
|
||||
if (number != null) const SizedBox(width: 10),
|
||||
Flexible(
|
||||
child: Text(
|
||||
text,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (active)
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: 4,
|
||||
child: Container(
|
||||
color: const Color.fromRGBO(51, 102, 204, 1),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue