From 7ac20f877aa5a8a12469c357b5c230b7f5032875 Mon Sep 17 00:00:00 2001 From: Lex Lim Date: Sat, 28 Jan 2023 16:04:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/js/app_bridge.js | 21 ++-- lib/api/mw/parse.dart | 13 +++ lib/api/response/parse.dart | 10 +- lib/app.dart | 4 +- lib/components/gesture_detector.dart | 15 +-- lib/components/toc.dart | 165 +++++++++++++++++++++++++-- lib/components/wikipage_parser.dart | 41 ++++--- lib/pages/article.dart | 71 +++++++++--- lib/pages/home.dart | 43 +++---- lib/styles.dart | 34 +++--- pubspec.lock | 7 ++ pubspec.yaml | 1 + 12 files changed, 315 insertions(+), 110 deletions(-) diff --git a/assets/js/app_bridge.js b/assets/js/app_bridge.js index bc76a41..2715a53 100644 --- a/assets/js/app_bridge.js +++ b/assets/js/app_bridge.js @@ -1,7 +1,9 @@ var _InAppWebViewReady = false; +var enableScrollSpy = true; var titleList = []; var currentTitle = '_top'; +var titleOffset = 10; function onReadyStateChange() { if (_InAppWebViewReady && document.readyState === "complete") { @@ -31,9 +33,9 @@ function debouce(callback, ms) { } document.addEventListener('scroll', debouce(function() { - if (!titleList || titleList.length === 0) return; + if (!enableScrollSpy || !titleList || titleList.length === 0) return true; - var offsetTop = (navigator.safeArea ? navigator.safeArea.top : 0) + 10; + var offsetTop = (navigator.safeArea ? navigator.safeArea.top : 0) + titleOffset; var currentTitleEl = null; var isFirstSection = false; @@ -69,19 +71,24 @@ document.addEventListener('scroll', debouce(function() { } }, 200), { passive: true }); -var MugenApp = { +window.MugenApp = { scrollToTitle: function (anchor) { var el = document.getElementById(anchor); if (el) { - var scrollTop = el.offsetTop; + var scrollTop = window.scrollY + el.getBoundingClientRect().top; if (navigator.safeArea) { - scrollTop += navigator.safeArea.top; + scrollTop -= navigator.safeArea.top + titleOffset; } - + scrollTop = Math.max(0, scrollTop); + + enableScrollSpy = false; window.scrollTo({ top: scrollTop, - behavior: "smooth", }); + + setTimeout(function() { + enableScrollSpy = true; + }, 50); } }, }; \ No newline at end of file diff --git a/lib/api/mw/parse.dart b/lib/api/mw/parse.dart index 567ef5c..ccfba5b 100644 --- a/lib/api/mw/parse.dart +++ b/lib/api/mw/parse.dart @@ -1,3 +1,4 @@ +import 'package:intl/intl.dart'; import 'package:isekai_wiki/api/mw/mw_api.dart'; import 'package:isekai_wiki/api/response/parse.dart'; import 'package:isekai_wiki/global.dart'; @@ -48,6 +49,18 @@ class MWApiParse { var mwRes = await MWApi.get("parse", params: query); var parseInfo = MWParseInfo.fromJson(mwRes.data); + if (parseInfo.sections.isNotEmpty) { + parseInfo = parseInfo.copyWith( + sections: parseInfo.sections + .map( + (section) => section.copyWith( + line: Bidi.stripHtmlIfNeeded(section.line), + ), + ) + .toList(), + ); + } + return mwRes.replaceData(parseInfo); } } diff --git a/lib/api/response/parse.dart b/lib/api/response/parse.dart index 15df1ef..09d078e 100644 --- a/lib/api/response/parse.dart +++ b/lib/api/response/parse.dart @@ -3,7 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'parse.freezed.dart'; part 'parse.g.dart'; -@Freezed(copyWith: false) +@freezed class MWParseCategoryInfo with _$MWParseCategoryInfo { factory MWParseCategoryInfo({ required String category, @@ -14,7 +14,7 @@ class MWParseCategoryInfo with _$MWParseCategoryInfo { _$MWParseCategoryInfoFromJson(json); } -@Freezed(copyWith: false) +@freezed class MWParseLangLinkInfo with _$MWParseLangLinkInfo { factory MWParseLangLinkInfo({ required String lang, @@ -28,7 +28,7 @@ class MWParseLangLinkInfo with _$MWParseLangLinkInfo { _$MWParseLangLinkInfoFromJson(json); } -@Freezed(copyWith: false) +@freezed class MWParsePageLinkInfo with _$MWParsePageLinkInfo { factory MWParsePageLinkInfo({ required int ns, @@ -40,7 +40,7 @@ class MWParsePageLinkInfo with _$MWParsePageLinkInfo { _$MWParsePageLinkInfoFromJson(json); } -@Freezed(copyWith: false) +@freezed class MWParseSectionInfo with _$MWParseSectionInfo { factory MWParseSectionInfo({ required int toclevel, @@ -57,7 +57,7 @@ class MWParseSectionInfo with _$MWParseSectionInfo { _$MWParseSectionInfoFromJson(json); } -@Freezed(copyWith: false) +@freezed class MWParseInfo with _$MWParseInfo { factory MWParseInfo({ required String title, diff --git a/lib/app.dart b/lib/app.dart index 970da85..aa48084 100755 --- a/lib/app.dart +++ b/lib/app.dart @@ -97,9 +97,7 @@ class IsekaiWikiApp extends StatelessWidget { ? Styles.materialLightTheme : Styles.materialDarkTheme, child: CupertinoTheme( - data: - Styles.cupertinoTheme.copyWith(brightness: brightness), - child: child), + data: Styles.cupertinoTheme.copyWith(brightness: brightness), child: child), ), ); } diff --git a/lib/components/gesture_detector.dart b/lib/components/gesture_detector.dart index d9ec28e..c9dfa63 100755 --- a/lib/components/gesture_detector.dart +++ b/lib/components/gesture_detector.dart @@ -25,8 +25,7 @@ class _OpacityGestureDetectorState extends State { Widget build(BuildContext context) { return Text( widget.text, - style: const TextStyle( - color: Styles.themeNavTitleColor, fontWeight: FontWeight.normal), + style: const TextStyle(color: Styles.themeNavTitleColor, fontWeight: FontWeight.normal), ); } } @@ -34,8 +33,7 @@ class _OpacityGestureDetectorState extends State { enum PointerActiveMode { none, hover, active } class ClickableBuilder extends StatefulWidget { - final Widget Function( - BuildContext context, PointerActiveMode mode, Widget child) builder; + final Widget Function(BuildContext context, PointerActiveMode mode, Widget child) builder; final Widget? child; const ClickableBuilder({super.key, required this.builder, this.child}); @@ -69,7 +67,7 @@ class _ClickableBuilder extends State { setState(() { isActive = true; }); - Future.delayed(const Duration(milliseconds: 300)).then((value) { + Future.delayed(const Duration(milliseconds: 150)).then((value) { if (isPointerDown) { isPersistActive = true; } else { @@ -99,11 +97,14 @@ class _ClickableBuilder extends State { innerItem = widget.builder(context, PointerActiveMode.active, child); } else if (isHover) { innerItem = widget.builder(context, PointerActiveMode.hover, child); + } else { + innerItem = widget.builder(context, PointerActiveMode.none, child); } - innerItem = widget.builder(context, PointerActiveMode.none, child); return Listener( - onPointerDown: (event) {}, + onPointerDown: onPointerDown, + onPointerUp: onPointerUp, + onPointerCancel: onPointerUp, child: MouseRegion( onEnter: onMouseEnter, onExit: onMouseExit, diff --git a/lib/components/toc.dart b/lib/components/toc.dart index c43fb27..aaf195f 100644 --- a/lib/components/toc.dart +++ b/lib/components/toc.dart @@ -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 sections; + final void Function(String anchor)? onSwitchTitle; + + const TOCList({ + super.key, + required this.sections, + this.activedItem = "", + this.hasFirstSection = false, + this.onSwitchTitle, + }); @override State createState() => _TOCListState(); } -class _TOCListState extends ReactiveState { +class _TOCListState extends State { + 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), + ), + ), + ], + ); } } diff --git a/lib/components/wikipage_parser.dart b/lib/components/wikipage_parser.dart index ccd6cda..438b0ac 100644 --- a/lib/components/wikipage_parser.dart +++ b/lib/components/wikipage_parser.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -19,6 +21,7 @@ class WikiPageParserController extends GetxController { static SimpleTemplate? renderTemplate; static String jsBridge = ""; + Function(String anchor)? onSectionChange; InAppWebViewController? webviewCotroller; ScrollController scrollController = ScrollController(); @@ -88,8 +91,7 @@ class WikiPageParserController extends GetxController { tagList.add(''); } - if (parseInfo.value?.modulestyles != null && - parseInfo.value!.modulestyles.isNotEmpty) { + if (parseInfo.value?.modulestyles != null && parseInfo.value!.modulestyles.isNotEmpty) { var pageModules = parseInfo.value!.modulestyles .where((item) => !Global.siteConfig.moduleStyles.contains(item)); @@ -178,8 +180,7 @@ class WikiPageParserController extends GetxController { return; } var anchor = args[0] as String; - - debugPrint("sectionChange: $anchor"); + onSectionChange?.call(anchor); }, ); @@ -216,8 +217,7 @@ navigator.safeArea = { try { var bytes = await rootBundle.load("assets/${uri.path}"); var data = bytes.buffer.asUint8List(); - var mimeType = MimeTypeResolver() - .lookup(uri.path, headerBytes: data.sublist(0, 20)); + var mimeType = MimeTypeResolver().lookup(uri.path, headerBytes: data.sublist(0, 20)); CustomSchemeResponse( data: data, contentType: mimeType ?? "text/plain", @@ -231,15 +231,30 @@ navigator.safeArea = { } void onScrollChanged(InAppWebViewController controller, int x, int y) {} + + void scrollToAnchor(String anchor) { + String encodedAnchor = jsonEncode(anchor); + + webviewCotroller?.evaluateJavascript( + source: "window.MugenApp && window.MugenApp.scrollToTitle($encodedAnchor)"); + } } class WikiPageParser extends StatefulWidget { final PageInfo? pageInfo; final MWParseInfo? parseInfo; final EdgeInsets? padding; - - const WikiPageParser( - {super.key, this.pageInfo, this.parseInfo, this.padding}); + final Function(String anchor)? onSectionChange; + final Function(WikiPageParserController controller)? ref; + + const WikiPageParser({ + super.key, + this.pageInfo, + this.parseInfo, + this.padding, + this.onSectionChange, + this.ref, + }); @override State createState() { @@ -262,8 +277,9 @@ class _WikiParserState extends ReactiveState { c.parseInfo.value = widget.parseInfo; c.contentHtml.value = widget.parseInfo?.text ?? ""; c.safeAreaPadding.value = widget.padding ?? const EdgeInsets.all(0); - c.textZoom.value = - (MediaQuery.of(Get.context!).textScaleFactor * 100).round(); + c.textZoom.value = (MediaQuery.of(Get.context!).textScaleFactor * 100).round(); + c.onSectionChange = widget.onSectionChange; + widget.ref?.call(c); } Widget _buildRender() { @@ -328,7 +344,6 @@ class _WikiParserState extends ReactiveState { @override Widget build(BuildContext context) { var sc = Get.find(); - return Obx( - () => sc.betaPageRender.value ? _buildRender() : _buildWebview()); + return Obx(() => sc.betaPageRender.value ? _buildRender() : _buildWebview()); } } diff --git a/lib/pages/article.dart b/lib/pages/article.dart index e028cd8..d27c9b9 100755 --- a/lib/pages/article.dart +++ b/lib/pages/article.dart @@ -10,6 +10,7 @@ import 'package:isekai_wiki/api/response/parse.dart'; import 'package:isekai_wiki/components/bottom_nav_bar.dart'; import 'package:isekai_wiki/components/nav_bar_button.dart'; import 'package:isekai_wiki/components/safearea_builder.dart'; +import 'package:isekai_wiki/components/toc.dart'; import 'package:isekai_wiki/components/wikipage_parser.dart'; import 'package:isekai_wiki/models/favorite_list.dart'; import 'package:isekai_wiki/reactive/reactive.dart'; @@ -25,11 +26,7 @@ class MinimumArticleData { final String? mainCategory; final DateTime? updateTime; - MinimumArticleData( - {required this.title, - this.description, - this.mainCategory, - this.updateTime}); + MinimumArticleData({required this.title, this.description, this.mainCategory, this.updateTime}); } class ArticleCategoryData { @@ -40,6 +37,8 @@ class ArticleCategoryData { } class ArticlePageController extends GetxController { + WikiPageParserController? parserController; + var loading = true.obs; var menuSlider = GlobalKey(); @@ -52,6 +51,19 @@ class ArticlePageController extends GetxController { var displayTitle = "".obs; + var activedSection = "_firstSection".obs; + + bool get hasFirstSection { + if (parseInfo.value == null) return false; + if (parseInfo.value!.sections.isEmpty) return false; + return parseInfo.value!.sections.first.byteoffset == 0; + } + + @override + void onInit() { + super.onInit(); + } + Future loadPageContent() async { if (pageTitle.isNotEmpty || pageId.value != 0) { try { @@ -59,11 +71,9 @@ class ArticlePageController extends GetxController { loading.value = true; MWResponse> pageInfoRes; if (pageId.value != 0) { - pageInfoRes = - await MWApiList.getPageInfoList(pageids: [pageId.value]); + pageInfoRes = await MWApiList.getPageInfoList(pageids: [pageId.value]); } else { - pageInfoRes = - await MWApiList.getPageInfoList(titles: [pageTitle.value]); + pageInfoRes = await MWApiList.getPageInfoList(titles: [pageTitle.value]); } if (pageInfoRes.data.isEmpty) { throw MWApiErrorException(code: 'no-page', info: "页面信息丢失"); @@ -78,8 +88,7 @@ class ArticlePageController extends GetxController { var parseRes = await MWApiParse.parse(pageId: pageId.value); parseInfo.value = parseRes.data; } catch (err, stack) { - alert(Get.overlayContext!, ErrorUtils.getErrorMessage(err), - title: "错误"); + alert(Get.overlayContext!, ErrorUtils.getErrorMessage(err), title: "错误"); if (kDebugMode) { print("Exception in page: $err"); stack.printError(); @@ -97,6 +106,20 @@ class ArticlePageController extends GetxController { void handleMenuButtonClick() { menuSlider.currentState?.openSlider(); } + + void handleSwitchTitle(String anchor) { + menuSlider.currentState?.closeSlider(); + + Future.delayed(const Duration(milliseconds: 360)).then((_) { + activedSection.value = anchor; + }); + + parserController?.scrollToAnchor(anchor); + } + + void handleSectionChange(String anchor) { + activedSection.value = anchor; + } } class ArticlePage extends StatefulWidget { @@ -104,8 +127,7 @@ class ArticlePage extends StatefulWidget { final String? targetPage; final int? targetPageId; - const ArticlePage( - {super.key, this.targetPage, this.targetPageId, this.initialArticleData}); + const ArticlePage({super.key, this.targetPage, this.targetPageId, this.initialArticleData}); @override State createState() => _ArticlePageState(); @@ -137,6 +159,17 @@ class _ArticlePageState extends ReactiveState { c.displayTitle.value = widget.initialArticleData?.title ?? "加载中"; } + Widget renderTOC(BuildContext context) { + return Obx( + () => TOCList( + sections: c.parseInfo.value?.sections ?? const [], + activedItem: c.activedSection.value, + hasFirstSection: c.hasFirstSection, + onSwitchTitle: c.handleSwitchTitle, + ), + ); + } + @override Widget render(BuildContext context) { final flc = Get.find(); @@ -147,9 +180,8 @@ class _ArticlePageState extends ReactiveState { appBar: null, showCover: true, slideDirection: SlideDirection.RIGHT_TO_LEFT, - slider: Container( - color: CupertinoColors.systemRed, - ), + slider: renderTOC(context), + animationDuration: 350, child: IsekaiPageScaffold( navigationBar: IsekaiNavigationBar( middle: Obx(() => Text(c.displayTitle.value)), @@ -167,8 +199,7 @@ class _ArticlePageState extends ReactiveState { semanticLabel: "讨论", ), Obx(() { - if (c.pageInfo.value != null && - flc.isFavorite(c.pageInfo.value!)) { + if (c.pageInfo.value != null && flc.isFavorite(c.pageInfo.value!)) { return NavBarButton( icon: CupertinoIcons.heart_fill, onPressed: () {}, @@ -207,6 +238,10 @@ class _ArticlePageState extends ReactiveState { padding: padding, pageInfo: c.pageInfo.value, parseInfo: c.parseInfo.value, + onSectionChange: c.handleSectionChange, + ref: (controller) { + c.parserController = controller; + }, ), ), ), diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 26caea5..da1647f 100755 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -22,8 +22,7 @@ const Color _kDefaultTabBarBorderColor = CupertinoDynamicColor.withBrightness( enum HomeTabs { newest, followed } -class HomeController extends GetxController - with GetSingleTickerProviderStateMixin { +class HomeController extends GetxController with GetSingleTickerProviderStateMixin { double _navSearchButtonOffset = 90; var showNavSearchButton = false.obs; @@ -42,15 +41,12 @@ class HomeController extends GetxController void onInit() { tabController = TabController(length: 2, vsync: this); - _navSearchButtonOffset = - 48 * MediaQuery.of(Get.context!).textScaleFactor + 48; + _navSearchButtonOffset = 48 * MediaQuery.of(Get.context!).textScaleFactor + 48; scrollController.addListener(() { - if (scrollController.offset >= _navSearchButtonOffset && - !showNavSearchButton.value) { + if (scrollController.offset >= _navSearchButtonOffset && !showNavSearchButton.value) { showNavSearchButton.value = true; - } else if (scrollController.offset < _navSearchButtonOffset && - showNavSearchButton.value) { + } else if (scrollController.offset < _navSearchButtonOffset && showNavSearchButton.value) { showNavSearchButton.value = false; } }); @@ -108,8 +104,7 @@ class HomeTab extends StatelessWidget { duration: const Duration(milliseconds: 100), child: CupertinoButton( padding: EdgeInsets.zero, - child: const Icon(CupertinoIcons.bell, - size: 26, color: Styles.themeNavTitleColor), + child: const Icon(CupertinoIcons.bell, size: 26, color: Styles.themeNavTitleColor), onPressed: () {}, ), ), @@ -125,8 +120,7 @@ class HomeTab extends StatelessWidget { duration: const Duration(milliseconds: 100), child: CupertinoButton( padding: EdgeInsets.zero, - child: const Icon(CupertinoIcons.search, - size: 26, color: Styles.themeNavTitleColor), + child: const Icon(CupertinoIcons.search, size: 26, color: Styles.themeNavTitleColor), onPressed: () { onSearchClick?.call(); }, @@ -152,8 +146,7 @@ class HomeTab extends StatelessWidget { leading: _buildSearchIconButton(), backgroundColor: Styles.themeMainColor, brightness: Brightness.dark, - largeTitle: const Text('首页', - style: TextStyle(color: Styles.themeNavTitleColor)), + largeTitle: const Text('首页', style: TextStyle(color: Styles.themeNavTitleColor)), trailing: _buildNotificationIconButton(), border: Border.all(style: BorderStyle.none), ), @@ -173,8 +166,7 @@ class HomeTab extends StatelessWidget { ), ], ), - padding: - const EdgeInsets.only(bottom: 10, left: 12, right: 12), + padding: const EdgeInsets.only(bottom: 10, left: 12, right: 12), child: CupertinoButton( color: Colors.white, padding: const EdgeInsets.all(0), @@ -187,12 +179,10 @@ class HomeTab extends StatelessWidget { children: [ Container( padding: const EdgeInsets.all(1), - child: const Icon(CupertinoIcons.search, - color: Colors.black54), + child: const Icon(CupertinoIcons.search, color: Colors.black54), ), const Text("搜索页面...", - textAlign: TextAlign.center, - style: TextStyle(color: Colors.black54)) + textAlign: TextAlign.center, style: TextStyle(color: Colors.black54)) ], ), ), @@ -223,10 +213,7 @@ class HomeTab extends StatelessWidget { indicatorColor: Styles.themeMainColor, labelColor: Styles.themeMainColor, unselectedLabelColor: Colors.black45, - tabs: const [ - CollapsedTabText('最新'), - CollapsedTabText('关注') - ], + tabs: const [CollapsedTabText('最新'), CollapsedTabText('关注')], onTap: (int selected) {}, ), ), @@ -243,13 +230,12 @@ class HomeTab extends StatelessWidget { maxHeight: 1, child: Container( decoration: BoxDecoration( - color: Colors.transparent, + color: Theme.of(context).shadowColor, boxShadow: [ BoxShadow( color: Theme.of(context).shadowColor, - spreadRadius: 2, + spreadRadius: 5, blurRadius: 4, - offset: const Offset(0, 2), ) ], ), @@ -292,8 +278,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { double get maxExtent => max(maxHeight, minHeight); @override - Widget build( - BuildContext context, double shrinkOffset, bool overlapsContent) { + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return SizedBox.expand(child: child); } diff --git a/lib/styles.dart b/lib/styles.dart index f68c7b6..8a02e2b 100755 --- a/lib/styles.dart +++ b/lib/styles.dart @@ -8,20 +8,7 @@ abstract class Styles { static const double defaultFontSize = 16; - static const CupertinoTextThemeData defaultTextTheme = CupertinoTextThemeData( - textStyle: TextStyle( - fontSize: defaultFontSize, - fontFamilyFallback: [ - "PingFang SC", - "Heiti SC", - "Noto Sans SC", - "Microsoft YaHei", - "Hei", - "SimHei", - ], - color: CupertinoColors.label, - ), - ); + static const CupertinoTextThemeData defaultTextTheme = CupertinoTextThemeData(); static CupertinoThemeData cupertinoTheme = const CupertinoThemeData( primaryColor: themeMainColor, @@ -57,8 +44,7 @@ abstract class Styles { static const TextStyle listTileLargeTitle = TextStyle(fontSize: 18); static const TextStyle listTileSubTitle = TextStyle(fontSize: 16); - static const TextStyle loadingDialogTitle = - TextStyle(fontSize: 18, fontWeight: FontWeight.w500); + static const TextStyle loadingDialogTitle = TextStyle(fontSize: 18, fontWeight: FontWeight.w500); static const Color websiteNavbarColor = Color.fromRGBO(33, 37, 41, 1); @@ -70,18 +56,26 @@ abstract class Styles { darkColor: Color.fromRGBO(41, 41, 41, 1), ); - static const Color themePageBackgroundColor = - CupertinoDynamicColor.withBrightness( + static const Color themePageBackgroundColor = CupertinoDynamicColor.withBrightness( color: Color.fromRGBO(240, 240, 240, 1), darkColor: Color.fromRGBO(0, 0, 0, 1), ); - static const Color themePureBackgroundColor = - CupertinoDynamicColor.withBrightness( + static const Color themePureBackgroundColor = CupertinoDynamicColor.withBrightness( color: Color.fromRGBO(255, 255, 255, 1), darkColor: Color.fromRGBO(0, 0, 0, 1), ); + static const Color themeNormalActionColor = CupertinoDynamicColor.withBrightness( + color: Color.fromRGBO(255, 255, 255, 1), + darkColor: Color.fromRGBO(41, 41, 41, 1), + ); + + static const Color themeNormalActionActiveColor = CupertinoDynamicColor.withBrightness( + color: Color.fromRGBO(204, 204, 204, 1), + darkColor: Color.fromRGBO(0, 0, 0, 1), + ); + static const Color themeNavSegmentTextColor = Color.fromRGBO(12, 12, 12, 1); static const Color linkColor = CupertinoColors.link; static const double largeTitleFontSize = 32; diff --git a/pubspec.lock b/pubspec.lock index 80dc654..4483290 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -777,6 +777,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" + scrollable_positioned_list: + dependency: "direct main" + description: + name: scrollable_positioned_list + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.5" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cf0f3c4..5f0257f 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,7 @@ dependencies: like_button: ^2.0.4 skeletons: ^0.0.3 scroll_bottom_navigation_bar: ^4.0.0 + scrollable_positioned_list: ^0.3.5 modal_bottom_sheet: ^2.1.2 fluttertoast: ^8.1.2 animated_snack_bar: ^0.3.0