From e38e26e68580a6a84868b6d6b569c531e50d20e4 Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Fri, 21 Nov 2025 14:11:35 +0800 Subject: [PATCH] =?UTF-8?q?factor:=E5=B8=83=E5=B1=80=E9=A2=9C=E8=89=B2?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/layout/app_drawer.dart | 121 +++++++++++++++++++++++++++++ lib/layout/app_navbar.dart | 36 +++++++++ lib/layout/drawer.dart | 79 ------------------- lib/layout/nav_bar.dart | 22 ------ lib/layout/theme_layout.dart | 128 +++++++++++++++++++++++++++++++ lib/main.dart | 9 ++- lib/pages/blog_detail_page.dart | 8 +- lib/pages/blog_page.dart | 6 +- lib/pages/category_page.dart | 4 +- lib/pages/home_page.dart | 49 +++--------- lib/pages/stats_page.dart | 8 +- lib/provider/theme_provider.dart | 76 ++++++++++++++++++ lib/utils/theme_utils.dart | 87 +++++++++++++++++++++ lib/widget/blog.dart | 38 ++++++--- lib/widget/chart.dart | 64 ++++++++++++++++ lib/widget/markdown.dart | 5 +- lib/widget/search.dart | 9 +-- lib/widget/year_selector.dart | 2 +- 18 files changed, 572 insertions(+), 179 deletions(-) create mode 100644 lib/layout/app_drawer.dart create mode 100644 lib/layout/app_navbar.dart delete mode 100644 lib/layout/drawer.dart delete mode 100644 lib/layout/nav_bar.dart create mode 100644 lib/layout/theme_layout.dart create mode 100644 lib/provider/theme_provider.dart create mode 100644 lib/utils/theme_utils.dart diff --git a/lib/layout/app_drawer.dart b/lib/layout/app_drawer.dart new file mode 100644 index 0000000..60f1cb2 --- /dev/null +++ b/lib/layout/app_drawer.dart @@ -0,0 +1,121 @@ +import 'package:blog_app/layout/menu.dart'; +import 'package:blog_app/layout/theme_layout.dart'; +import 'package:flutter/material.dart'; + +class AppDrawer extends StatefulWidget { + final int currentPageIndex; + final Function(int) onTapDrawerItem; + + const AppDrawer({ + super.key, + required this.currentPageIndex, + required this.onTapDrawerItem, + }); + + @override + State createState() => AppDrawerState(); +} + +class AppDrawerState extends State { + Widget _buildDrawerHeader() { + final colors = Theme.of(context).colorScheme; + + return Container( + width: double.infinity, + height: 200, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [colors.primary, colors.inversePrimary], + ), + ), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ClipOval( + child: Image.asset( + 'assets/images/avatar.jpg', + width: 80, + height: 80, + fit: BoxFit.cover, + ), + ), + SizedBox(height: 16), + Text( + 'Cxx0822', + style: TextStyle( + color: colors.surface, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ); + } + + Widget _buildDrawerBody() { + return ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: pages.length, + separatorBuilder: (context, index) => SizedBox(height: 8), + itemBuilder: (context, index) => _buildDrawerItem(index), + ); + } + + Widget _buildDrawerItem(int index) { + final colors = Theme.of(context).colorScheme; + final isSelected = index == widget.currentPageIndex; + final boxColor = + isSelected ? colors.primary.withAlpha(50) : Colors.transparent; + final iconColor = + isSelected ? colors.primary : colors.onSurface.withAlpha(100); + final textColor = + isSelected ? colors.primary : colors.onSurface.withAlpha(200); + + return Container( + margin: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: boxColor, + borderRadius: BorderRadius.circular(12), + ), + child: ListTile( + leading: Icon(pages[index].icon, color: iconColor, size: 24), + title: Text( + pages[index].title, + style: TextStyle( + color: textColor, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + fontSize: 16, + ), + ), + trailing: + isSelected + ? Icon(Icons.arrow_forward_ios, size: 16, color: iconColor) + : null, + onTap: () => widget.onTapDrawerItem(index), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Drawer( + child: Container( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + _buildDrawerHeader(), + _buildDrawerBody(), + const Divider(), + ThemeLayout(), + ], + ), + ), + ); + } +} diff --git a/lib/layout/app_navbar.dart b/lib/layout/app_navbar.dart new file mode 100644 index 0000000..2a733ee --- /dev/null +++ b/lib/layout/app_navbar.dart @@ -0,0 +1,36 @@ +import 'package:blog_app/layout/menu.dart'; +import 'package:flutter/material.dart'; + +class AppNavbar extends StatefulWidget { + final int currentPageIndex; + final Function(int) onTapNavbarItem; + + const AppNavbar({ + super.key, + required this.currentPageIndex, + required this.onTapNavbarItem, + }); + + @override + State createState() => AppNavbarState(); +} + +class AppNavbarState extends State { + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return BottomNavigationBar( + currentIndex: widget.currentPageIndex, + onTap: widget.onTapNavbarItem, + items: bottomNavItems, + type: BottomNavigationBarType.fixed, + selectedItemColor: colors.primary, + unselectedItemColor: colors.onSurface.withAlpha(150), + showSelectedLabels: true, + showUnselectedLabels: true, + backgroundColor: colors.surface, + elevation: 8, + ); + } +} diff --git a/lib/layout/drawer.dart b/lib/layout/drawer.dart deleted file mode 100644 index 0b3cce7..0000000 --- a/lib/layout/drawer.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'menu.dart'; - -Widget buildDrawerHeader(BuildContext context) { - final colors = Theme.of(context).colorScheme; - return Container( - width: double.infinity, - height: 200, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [colors.primary, colors.inversePrimary], - ), - ), - child: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ClipOval( - child: Image.asset( - 'assets/images/avatar.jpg', - width: 80, - height: 80, - fit: BoxFit.cover, - ), - ), - SizedBox(height: 16), - Text( - 'Cxx0822', - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ); -} - -Widget buildDrawerItem({ - required BuildContext context, - required PageInfo page, - required VoidCallback onTap, - required bool isSelected, -}) { - final colors = Theme.of(context).colorScheme; - - return Container( - margin: EdgeInsets.symmetric(horizontal: 12, vertical: 4), - decoration: BoxDecoration( - color: isSelected ? colors.primary.withAlpha(50) : Colors.transparent, - borderRadius: BorderRadius.circular(12), - ), - child: ListTile( - leading: Icon( - page.icon, - color: isSelected ? colors.primary : Colors.grey[700], - size: 24, - ), - title: Text( - page.title, - style: TextStyle( - color: isSelected ? colors.primary : Colors.grey[800], - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - fontSize: 16, - ), - ), - trailing: - isSelected - ? Icon(Icons.arrow_forward_ios, size: 16, color: colors.primary) - : null, - onTap: onTap, - ), - ); -} diff --git a/lib/layout/nav_bar.dart b/lib/layout/nav_bar.dart deleted file mode 100644 index 08584cc..0000000 --- a/lib/layout/nav_bar.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'menu.dart'; - -Widget buildBottomNavigationBar({ - required BuildContext context, - required int index, - required Function(int) onTap, -}) { - return BottomNavigationBar( - currentIndex: index, - onTap: onTap, - items: bottomNavItems, - type: BottomNavigationBarType.fixed, - selectedItemColor: Theme.of(context).colorScheme.primary, - unselectedItemColor: Colors.grey, - showSelectedLabels: true, - showUnselectedLabels: true, - backgroundColor: Colors.white, - elevation: 8, - ); -} diff --git a/lib/layout/theme_layout.dart b/lib/layout/theme_layout.dart new file mode 100644 index 0000000..5d80c62 --- /dev/null +++ b/lib/layout/theme_layout.dart @@ -0,0 +1,128 @@ +import 'package:blog_app/provider/theme_provider.dart'; +import 'package:blog_app/utils/theme_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class ThemeLayout extends StatelessWidget { + final Widget? child; + + const ThemeLayout({super.key, this.child}); + + Widget _buildThemeColorList(BuildContext context, ThemeProvider provider) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + childAspectRatio: 1.2, + ), + itemCount: provider.availableThemes.length, + itemBuilder: (context, index) { + final themeColor = provider.availableThemes[index]; + final isSelected = provider.currentTheme == themeColor; + + return _buildThemeColorItem( + themeColor: themeColor, + isSelected: isSelected, + onTap: () => provider.changeTheme(themeColor), + ); + }, + ), + ); + } + + Widget _buildThemeColorItem({ + required ThemeColor themeColor, + required bool isSelected, + required VoidCallback onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + decoration: BoxDecoration( + color: + isSelected + ? themeColor.primaryColor.withAlpha(50) + : Colors.transparent, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isSelected ? themeColor.primaryColor : Colors.grey.shade300, + width: isSelected ? 2 : 1, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 颜色圆点 + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: themeColor.primaryColor, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(10), + blurRadius: 2, + offset: const Offset(0, 1), + ), + ], + ), + ), + const SizedBox(height: 4), + Text( + themeColor.name, + style: TextStyle( + fontSize: 10, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + color: + isSelected ? themeColor.primaryColor : Colors.grey.shade600, + ), + ), + ], + ), + ), + ); + } + + Widget _buildDarkModeSection(BuildContext context, ThemeProvider provider) { + return ListTile( + leading: Icon( + provider.isDarkMode ? Icons.dark_mode : Icons.light_mode, + color: Theme.of(context).colorScheme.primary, + ), + title: const Text('暗黑模式'), + trailing: Switch( + value: provider.isDarkMode, + onChanged: (value) { + provider.toggleDarkMode(value); + }, + ), + onTap: () { + provider.toggleDarkMode(!provider.isDarkMode); + }, + ); + } + + @override + Widget build(BuildContext context) { + final provider = context.watch(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Text('主题颜色', style: Theme.of(context).textTheme.titleMedium), + ), + _buildDarkModeSection(context, provider), + _buildThemeColorList(context, provider), + ], + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index c6ef3f0..9099342 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:blog_app/pages/home_page.dart'; import 'package:blog_app/provider/blog.dart'; +import 'package:blog_app/provider/theme_provider.dart'; import 'package:blog_app/utils/sp_utils.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -12,6 +13,7 @@ void main() async { MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => BlogProvider()), + ChangeNotifierProvider(create: (context) => ThemeProvider()), ], child: MyApp(), ), @@ -23,13 +25,12 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { + final themeProvider = context.watch(); + return MaterialApp( home: HomePage(), debugShowCheckedModeBanner: false, - theme: ThemeData( - useMaterial3: true, - fontFamily: 'CustomFont', - ), + theme: themeProvider.currentThemeData, ); } } diff --git a/lib/pages/blog_detail_page.dart b/lib/pages/blog_detail_page.dart index 00bd7cd..74450ae 100644 --- a/lib/pages/blog_detail_page.dart +++ b/lib/pages/blog_detail_page.dart @@ -43,7 +43,7 @@ class _BlogDetailPageState extends State { return FloatingActionButton( onPressed: () => setState(() => _showToc = !_showToc), mini: true, - backgroundColor: Theme.of(context).primaryColor, + backgroundColor: Theme.of(context).colorScheme.primary, child: Icon(_showToc ? Icons.close : Icons.list, color: Colors.white), ); } else { @@ -73,7 +73,11 @@ class _BlogDetailPageState extends State { ), ], ), - buildTocPanel(tocController: tocController, showToc: _showToc), + buildTocPanel( + context: context, + tocController: tocController, + showToc: _showToc, + ), ], ); } diff --git a/lib/pages/blog_page.dart b/lib/pages/blog_page.dart index 4ea1ebb..6a9d5ec 100644 --- a/lib/pages/blog_page.dart +++ b/lib/pages/blog_page.dart @@ -80,13 +80,14 @@ class _BlogPageState extends State { if (provider.error != null) { return buildErrorInfo( errorInfo: provider.error!, - onPressed: () => provider.refreshBlogList, + onPressed: () => provider.refreshBlogList(), ); } return ListView.separated( controller: _scrollController, itemCount: provider.blogList.length, + separatorBuilder: (context, index) => SizedBox(height: 8), itemBuilder: (context, index) { final blog = provider.blogList[index]; return InkWell( @@ -94,9 +95,6 @@ class _BlogPageState extends State { child: BlogCard(blog: blog), ); }, - separatorBuilder: (BuildContext context, int index) { - return SizedBox(height: 8); - }, ); } diff --git a/lib/pages/category_page.dart b/lib/pages/category_page.dart index 6a8aef2..0944143 100644 --- a/lib/pages/category_page.dart +++ b/lib/pages/category_page.dart @@ -38,13 +38,11 @@ class _CategoryPageState extends State { Expanded( child: ListView.separated( itemCount: provider.categoryList.length, + separatorBuilder: (context, index) => SizedBox(height: 8), itemBuilder: (context, index) { final category = provider.categoryList[index]; return buildCategoryCard(context, category); }, - separatorBuilder: (BuildContext context, int index) { - return SizedBox(height: 8); - }, ), ), ], diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 49f4b34..2802cee 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,7 +1,7 @@ import 'package:blog_app/apis/blog.dart'; -import 'package:blog_app/layout/drawer.dart'; +import 'package:blog_app/layout/app_drawer.dart'; +import 'package:blog_app/layout/app_navbar.dart'; import 'package:blog_app/layout/menu.dart'; -import 'package:blog_app/layout/nav_bar.dart'; import 'package:blog_app/models/blog.dart'; import 'package:blog_app/widget/search.dart'; import 'package:flutter/material.dart'; @@ -165,7 +165,7 @@ class _HomePageState extends State { return buildSearchResultList(context, _searchResults); } - void _onBottomNavTap(int index) { + void _onTapNavbarItem(int index) { setState(() { _currentPageIndex = index; if (_isSearching) { @@ -194,37 +194,6 @@ class _HomePageState extends State { Navigator.pop(context); } - Widget _buildDrawerBody() { - return ListView( - padding: EdgeInsets.zero, - children: [ - ...List.generate( - pages.length, - (index) => buildDrawerItem( - context: context, - page: pages[index], - isSelected: index == _currentPageIndex, - onTap: () => onTapDrawerItem(index), - ), - ), - ], - ); - } - - Widget _buildDrawer() { - return Drawer( - child: Container( - color: Colors.white, - child: Column( - children: [ - buildDrawerHeader(context), - Expanded(child: _buildDrawerBody()), - ], - ), - ), - ); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -235,12 +204,14 @@ class _HomePageState extends State { leading: _buildLeading(), actions: _buildAppBarActions(), ), - drawer: _buildDrawer(), + drawer: AppDrawer( + currentPageIndex: _currentPageIndex, + onTapDrawerItem: onTapDrawerItem, + ), body: Padding(padding: EdgeInsets.all(10), child: _buildPageView()), - bottomNavigationBar: buildBottomNavigationBar( - context: context, - index: _currentPageIndex, - onTap: _onBottomNavTap, + bottomNavigationBar: AppNavbar( + currentPageIndex: _currentPageIndex, + onTapNavbarItem: _onTapNavbarItem, ), ); } diff --git a/lib/pages/stats_page.dart b/lib/pages/stats_page.dart index fedf58e..d372a75 100644 --- a/lib/pages/stats_page.dart +++ b/lib/pages/stats_page.dart @@ -76,10 +76,8 @@ class StatsPageState extends State { buildChartDivider(context), const SizedBox(height: 3), Expanded( - child: barChart( + child: rankChart( context: context, - xAxisName: '名称', - yAxisName: '访问量', unit: '人次', data: provider.visitRankStats, ), @@ -114,10 +112,8 @@ class StatsPageState extends State { buildChartDivider(context), const SizedBox(height: 3), Expanded( - child: barChart( + child: rankChart( context: context, - xAxisName: '名称', - yAxisName: '时长', unit: '分钟', data: provider.readRankStats, ), diff --git a/lib/provider/theme_provider.dart b/lib/provider/theme_provider.dart new file mode 100644 index 0000000..2bed2a2 --- /dev/null +++ b/lib/provider/theme_provider.dart @@ -0,0 +1,76 @@ +import 'package:blog_app/utils/theme_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class ThemeProvider with ChangeNotifier { + static const String _themeKey = 'selected_theme'; + static const String _darkModeKey = 'is_dark_mode'; + + bool isDarkMode = false; + ThemeColor currentTheme = defaultThemes[0]; + + late SharedPreferences _prefs; + bool isInitialized = false; + + ThemeProvider() { + _initPreferences(); + } + + // 初始化 SharedPreferences + Future _initPreferences() async { + _prefs = await SharedPreferences.getInstance(); + _loadPreferences(); + + isInitialized = true; + notifyListeners(); + } + + // 加载存储的设置 + void _loadPreferences() { + isDarkMode = _prefs.getBool(_darkModeKey) ?? false; + + // 加载主题色 + final themeName = _prefs.getString(_themeKey); + if (themeName != null) { + currentTheme = getThemeColor(themeName); + } + } + + // 保存主题色 + Future _saveTheme() async { + await _prefs.setString(_themeKey, currentTheme.name); + } + + // 保存暗黑模式 + Future _saveDarkMode() async { + await _prefs.setBool(_darkModeKey, isDarkMode); + } + + List get availableThemes => defaultThemes; + + // 切换明暗模式 + void toggleDarkMode(bool value) { + isDarkMode = value; + _saveDarkMode(); + notifyListeners(); + } + + // 更改主题色 + void changeTheme(ThemeColor theme) { + currentTheme = theme; + _saveTheme(); + notifyListeners(); + } + + ThemeData get currentThemeData { + return ThemeData( + primarySwatch: currentTheme.materialColor, + colorScheme: ColorScheme.fromSeed( + seedColor: currentTheme.primaryColor, + brightness: isDarkMode ? Brightness.dark : Brightness.light, + ), + useMaterial3: true, + fontFamily: 'CustomFont', + ); + } +} diff --git a/lib/utils/theme_utils.dart b/lib/utils/theme_utils.dart new file mode 100644 index 0000000..c24aeed --- /dev/null +++ b/lib/utils/theme_utils.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; + +class ThemeColor { + final String name; + final Color primaryColor; + final MaterialColor materialColor; + + ThemeColor({ + required this.name, + required this.primaryColor, + required this.materialColor, + }); +} + +final List defaultThemes = [ + ThemeColor( + name: '梦幻紫', + primaryColor: Color(0xFF8B5CF6), + materialColor: MaterialColor(0xFF8B5CF6, { + 50: Color(0xFFF5F3FF), + 100: Color(0xFFEDE9FE), + 200: Color(0xFFDDD6FE), + 300: Color(0xFFC4B5FD), + 400: Color(0xFFA78BFA), + 500: Color(0xFF8B5CF6), + 600: Color(0xFF7C3AED), + 700: Color(0xFF6D28D9), + 800: Color(0xFF5B21B6), + 900: Color(0xFF4C1D95), + }), + ), + ThemeColor( + name: '活力橙', + primaryColor: Color(0xFFF59E0B), + materialColor: MaterialColor(0xFFF59E0B, { + 50: Color(0xFFFFFBEB), + 100: Color(0xFFFEF3C7), + 200: Color(0xFFFDE68A), + 300: Color(0xFFFCD34D), + 400: Color(0xFFFBBF24), + 500: Color(0xFFF59E0B), + 600: Color(0xFFD97706), + 700: Color(0xFFB45309), + 800: Color(0xFF92400E), + 900: Color(0xFF78350F), + }), + ), + ThemeColor( + name: '浪漫粉', + primaryColor: Color(0xFFEC4899), + materialColor: MaterialColor(0xFFEC4899, { + 50: Color(0xFFFDF2F8), + 100: Color(0xFFFCE7F3), + 200: Color(0xFFFBCFE8), + 300: Color(0xFFF9A8D4), + 400: Color(0xFFF472B6), + 500: Color(0xFFEC4899), + 600: Color(0xFFDB2777), + 700: Color(0xFFBE185D), + 800: Color(0xFF9D174D), + 900: Color(0xFF831843), + }), + ), + ThemeColor( + name: '清新青', + primaryColor: Color(0xFF06B6D4), + materialColor: MaterialColor(0xFF06B6D4, { + 50: Color(0xFFF0FDFA), + 100: Color(0xFFCCFBF1), + 200: Color(0xFF99F6E4), + 300: Color(0xFF5EEAD4), + 400: Color(0xFF2DD4BF), + 500: Color(0xFF06B6D4), + 600: Color(0xFF0891B2), + 700: Color(0xFF0E7490), + 800: Color(0xFF155E75), + 900: Color(0xFF164E63), + }), + ), +]; + +ThemeColor getThemeColor(String themeName) { + return defaultThemes.firstWhere( + (theme) => theme.name == themeName, + orElse: () => defaultThemes[0], + ); +} diff --git a/lib/widget/blog.dart b/lib/widget/blog.dart index 0c70bff..4e4cbb3 100644 --- a/lib/widget/blog.dart +++ b/lib/widget/blog.dart @@ -53,7 +53,11 @@ class BlogCard extends StatelessWidget { alignment: WrapAlignment.center, children: [ if (blog.isGreat) - _buildBlogLabelItem('精品', Icons.workspace_premium, Colors.deepOrange), + _buildBlogLabelItem( + '精品', + Icons.workspace_premium, + Colors.deepOrange, + ), if (blog.topValue > 1) _buildBlogLabelItem('置顶', Icons.push_pin, Colors.red), _buildBlogLabelItem(blog.category, Icons.folder, Colors.blue), @@ -101,12 +105,14 @@ class BlogCard extends StatelessWidget { ); } - Widget _buildBlogSummary() { + Widget _buildBlogSummary(BuildContext context) { + final colors = Theme.of(context).colorScheme; + return SizedBox( width: double.infinity, child: Text( blog.summary, - style: TextStyle(color: Colors.grey.shade700, height: 1.6), + style: TextStyle(color: colors.secondary, height: 1.6), overflow: TextOverflow.ellipsis, ), ); @@ -126,7 +132,7 @@ class BlogCard extends StatelessWidget { SizedBox(height: 16), Container(height: 1, width: 80, color: colors.primary), SizedBox(height: 16), - _buildBlogSummary(), + _buildBlogSummary(context), ], ), ); @@ -181,14 +187,16 @@ Widget buildCategoryCard(BuildContext context, BlogCategory category) { ), ), trailing: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + width: 30, + height: 30, decoration: BoxDecoration( - color: colors.primary.withAlpha(50), - borderRadius: BorderRadius.circular(16), + color: Theme.of(context).colorScheme.primary.withAlpha(50), + shape: BoxShape.circle, ), - child: Text( - category.count.toString(), - style: TextStyle(color: colors.primary, fontWeight: FontWeight.bold), + child: Icon( + Icons.arrow_forward, + size: 16, + color: Theme.of(context).colorScheme.primary, ), ), onTap: () => navigateToBlogList(context, category), @@ -220,11 +228,17 @@ Widget buildBlogListItem(BuildContext context, Blog blog, int index) { padding: EdgeInsets.all(0), child: Row( children: [ - Text('$index.', style: TextStyle(fontWeight: FontWeight.w500)), + Text( + '$index.', + style: TextStyle( + color: colors.onSurface, + fontWeight: FontWeight.w500, + ), + ), SizedBox(width: 10), Text( formatDateString(blog.createTime), - style: TextStyle(fontSize: 14, color: Colors.grey.shade600), + style: TextStyle(fontSize: 14, color: colors.onSurface), ), SizedBox(width: 10), Expanded( diff --git a/lib/widget/chart.dart b/lib/widget/chart.dart index 9862c18..d9772c2 100644 --- a/lib/widget/chart.dart +++ b/lib/widget/chart.dart @@ -283,3 +283,67 @@ Widget pieChart({ ], ); } + +Color _getRankColor(BuildContext context, int index) { + switch (index) { + case 0: // 金牌 + return const Color(0xFFFFD700); // 标准金色 + case 1: // 银牌 + return const Color(0xFFC0C0C0); // 标准银色 + case 2: // 铜牌 + return const Color(0xFFCD7F32); // 标准铜色 + default: + return Theme.of(context).colorScheme.onSurface; // 普通颜色 + } +} + +Widget rankChart({ + required BuildContext context, + required String unit, + required List data, +}) { + return ListView.builder( + itemCount: data.length, + itemBuilder: (context, index) { + final item = data[index]; + final rank = index + 1; + return SizedBox( + height: 35, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 25, + alignment: Alignment.center, + child: Text( + '$rank.', + style: TextStyle( + fontWeight: FontWeight.bold, + color: _getRankColor(context, index), + ), + ), + ), + Text( + item.name, + style: TextStyle( + fontWeight: index < 3 ? FontWeight.bold : FontWeight.normal, + color: _getRankColor(context, index), + ), + ), + ], + ), + Text( + '${item.value.toStringAsFixed(0)} $unit', + style: TextStyle( + fontWeight: index < 3 ? FontWeight.bold : FontWeight.normal, + color: _getRankColor(context, index), + ), + ), + ], + ), + ); + }, + ); +} diff --git a/lib/widget/markdown.dart b/lib/widget/markdown.dart index aec3dc6..ef0d51b 100644 --- a/lib/widget/markdown.dart +++ b/lib/widget/markdown.dart @@ -8,6 +8,7 @@ Widget buildMarkdown({ }) => MarkdownWidget(data: data, tocController: tocController); Widget buildTocPanel({ + required BuildContext context, required TocController tocController, required bool showToc, }) => Visibility( @@ -18,7 +19,7 @@ Widget buildTocPanel({ width: 250, height: 400, decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( @@ -34,7 +35,7 @@ Widget buildTocPanel({ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.grey[100], + color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), diff --git a/lib/widget/search.dart b/lib/widget/search.dart index a59fe49..ffa389c 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -27,14 +27,13 @@ Widget buildSearchField({ } Widget buildSearchResultItem(BuildContext context, BlogSearch blog) { + final colors = Theme.of(context).colorScheme; + return BuildCard( child: ListTile( title: Text( blog.title, - style: TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ), + style: TextStyle(fontWeight: FontWeight.bold, color: colors.primary), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -42,7 +41,7 @@ Widget buildSearchResultItem(BuildContext context, BlogSearch blog) { SizedBox(height: 4), Text( blog.content, - style: TextStyle(color: Colors.grey[600], fontSize: 14), + style: TextStyle(color: colors.secondary, fontSize: 14), ), ], ), diff --git a/lib/widget/year_selector.dart b/lib/widget/year_selector.dart index e08b136..2f8d108 100644 --- a/lib/widget/year_selector.dart +++ b/lib/widget/year_selector.dart @@ -104,7 +104,7 @@ class _YearSelectorState extends State style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, + color: Theme.of(context).colorScheme.primary, ), ), ),