From f11b8d6a30e481929ac1fe18ac5049d4833bd293 Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Sun, 23 Nov 2025 20:28:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0common=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/apis/blog.dart | 60 ++- lib/layout/app_drawer.dart | 2 +- lib/layout/theme_layout.dart | 128 ------- lib/main.dart | 4 +- lib/models/common.dart | 53 --- lib/models/common.g.dart | 39 -- lib/pages/blog_detail_page.dart | 2 +- lib/pages/blog_page.dart | 2 +- lib/pages/category_list_page.dart | 2 +- lib/pages/category_page.dart | 2 +- lib/pages/stats_page.dart | 169 +++------ lib/pages/timeline_page.dart | 14 +- lib/provider/blog.dart | 11 +- lib/provider/theme_provider.dart | 76 ---- lib/utils/date_utils.dart | 13 - lib/utils/http_utils.dart | 244 ------------ lib/utils/index.dart | 53 --- lib/utils/log_utils.dart | 12 - lib/utils/sp_utils.dart | 61 --- lib/utils/theme_utils.dart | 87 ----- lib/widget/blog.dart | 77 +--- lib/widget/chart.dart | 349 ------------------ lib/widget/common.dart | 58 --- lib/widget/search.dart | 4 +- lib/widget/year_selector.dart | 119 ------ linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 6 + pubspec.lock | 167 ++++++++- pubspec.yaml | 2 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 32 files changed, 280 insertions(+), 1545 deletions(-) delete mode 100644 lib/layout/theme_layout.dart delete mode 100644 lib/models/common.dart delete mode 100644 lib/models/common.g.dart delete mode 100644 lib/provider/theme_provider.dart delete mode 100644 lib/utils/date_utils.dart delete mode 100644 lib/utils/http_utils.dart delete mode 100644 lib/utils/index.dart delete mode 100644 lib/utils/log_utils.dart delete mode 100644 lib/utils/sp_utils.dart delete mode 100644 lib/utils/theme_utils.dart delete mode 100644 lib/widget/chart.dart delete mode 100644 lib/widget/common.dart delete mode 100644 lib/widget/year_selector.dart diff --git a/lib/apis/blog.dart b/lib/apis/blog.dart index fd46c0f..39171ef 100644 --- a/lib/apis/blog.dart +++ b/lib/apis/blog.dart @@ -1,13 +1,14 @@ +import 'package:blog_app/config/app_config.dart'; import 'package:blog_app/models/blog.dart'; -import 'package:blog_app/models/common.dart'; -import 'package:blog_app/utils/http_utils.dart'; -import 'package:blog_app/utils/index.dart'; +import 'package:flutter_common/models/common_model.dart'; +import 'package:flutter_common/utils/convert_utils.dart'; +import 'package:flutter_common/utils/http_utils.dart'; Future> queryBlogByPageApi(int currentPage, int pageSize) { - return HttpUtil().get>( + return HttpUtil(baseUrl: AppConfig.baseApiUrl).get>( "/blog/page", queryParameters: {"currentPage": currentPage, "pageSize": pageSize}, - converter: (data) => convertPageResponse(data, Blog.fromJson), + converter: (data) => convertPage(data, Blog.fromJson), ); } @@ -28,83 +29,74 @@ Future> queryBlogByConditionApi( queryParams['year'] = year; } - return HttpUtil().get>( + return HttpUtil(baseUrl: AppConfig.baseApiUrl).get>( "/blog/condition", queryParameters: queryParams, - converter: (data) => convertListResponse(data, Blog.fromJson), + converter: (data) => convertList(data, Blog.fromJson), ); } Future> searchBlog(String keyword) { - return HttpUtil().get>( + return HttpUtil(baseUrl: AppConfig.baseApiUrl).get>( "/blog/search", queryParameters: {"keyword": keyword}, - converter: - (data) => convertListResponse(data, BlogSearch.fromJson), + converter: (data) => convertList(data, BlogSearch.fromJson), ); } Future queryBlogByIdApi(int id) { - return HttpUtil().get( - "/blog/$id/content", - converter: (data) => Blog.fromJson(data), - ); + return HttpUtil( + baseUrl: AppConfig.baseApiUrl, + ).get("/blog/$id/content", converter: (data) => Blog.fromJson(data)); } Future> queryBlogCategoryApi() { - return HttpUtil().get>( + return HttpUtil(baseUrl: AppConfig.baseApiUrl).get>( "/blog/category", - converter: - (data) => - convertListResponse(data, BlogCategory.fromJson), + converter: (data) => convertList(data, BlogCategory.fromJson), ); } Future queryBlogOverviewStatsApi() { - return HttpUtil().get( + return HttpUtil(baseUrl: AppConfig.baseApiUrl).get( "/stats/overview", converter: (data) => BlogStats.fromJson(data), ); } Future> queryBlogApprovedStatsApi(int year) { - return HttpUtil().get>( + return HttpUtil(baseUrl: AppConfig.baseApiUrl).get>( "/stats/approved/monthly", queryParameters: {"year": year}, - converter: - (data) => convertListResponse(data, ChartData.fromJson), + converter: (data) => convertList(data, ChartData.fromJson), ); } Future> queryBlogVisitStatsApi(int year) { - return HttpUtil().get>( + return HttpUtil(baseUrl: AppConfig.baseApiUrl).get>( "/stats/visit/monthly", queryParameters: {"year": year}, - converter: - (data) => convertListResponse(data, ChartData.fromJson), + converter: (data) => convertList(data, ChartData.fromJson), ); } Future> queryBlogVisitRankStatsApi() { - return HttpUtil().get>( + return HttpUtil(baseUrl: AppConfig.baseApiUrl).get>( "/stats/visit/rank", - converter: - (data) => convertListResponse(data, ChartData.fromJson), + converter: (data) => convertList(data, ChartData.fromJson), ); } Future> queryBlogCategoryStatsApi() { - return HttpUtil().get>( + return HttpUtil(baseUrl: AppConfig.baseApiUrl).get>( "/stats/category", - converter: - (data) => convertListResponse(data, ChartData.fromJson), + converter: (data) => convertList(data, ChartData.fromJson), ); } Future> queryBlogReadRankStatsApi() { - return HttpUtil().get>( + return HttpUtil(baseUrl: AppConfig.baseApiUrl).get>( "/stats/read/rank", - converter: - (data) => convertListResponse(data, ChartData.fromJson), + converter: (data) => convertList(data, ChartData.fromJson), ); } diff --git a/lib/layout/app_drawer.dart b/lib/layout/app_drawer.dart index 60f1cb2..f6ccc21 100644 --- a/lib/layout/app_drawer.dart +++ b/lib/layout/app_drawer.dart @@ -1,6 +1,6 @@ import 'package:blog_app/layout/menu.dart'; -import 'package:blog_app/layout/theme_layout.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_common/layout/theme_layout.dart'; class AppDrawer extends StatefulWidget { final int currentPageIndex; diff --git a/lib/layout/theme_layout.dart b/lib/layout/theme_layout.dart deleted file mode 100644 index 5d80c62..0000000 --- a/lib/layout/theme_layout.dart +++ /dev/null @@ -1,128 +0,0 @@ -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 9099342..3763c00 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,8 @@ 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:flutter_common/provider/theme_provider.dart'; +import 'package:flutter_common/utils/sp_utils.dart'; import 'package:provider/provider.dart'; void main() async { diff --git a/lib/models/common.dart b/lib/models/common.dart deleted file mode 100644 index de296e0..0000000 --- a/lib/models/common.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'common.g.dart'; - -@JsonSerializable(genericArgumentFactories: true) -class PageResult { - @JsonKey(name: 'records') - final List records; - - @JsonKey(name: 'total') - final int total; - - @JsonKey(name: 'size') - final int size; - - @JsonKey(name: 'current') - final int current; - - @JsonKey(name: 'pages') - final int pages; - - const PageResult({ - required this.records, - required this.total, - required this.size, - required this.current, - required this.pages, - }); - - factory PageResult.fromJson( - Map json, - T Function(Object? json) fromJsonT, - ) => _$PageResultFromJson(json, fromJsonT); - - Map toJson(Object? Function(T value) toJsonT) => - _$PageResultToJson(this, toJsonT); -} - -@JsonSerializable(genericArgumentFactories: true) -class ChartData { - @JsonKey(name: 'name') - final String name; - - @JsonKey(name: 'value') - final double value; - - const ChartData({required this.name, required this.value}); - - factory ChartData.fromJson(Map json) => - _$ChartDataFromJson(json); - - Map toJson() => _$ChartDataToJson(this); -} diff --git a/lib/models/common.g.dart b/lib/models/common.g.dart deleted file mode 100644 index 55aa9e9..0000000 --- a/lib/models/common.g.dart +++ /dev/null @@ -1,39 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'common.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -PageResult _$PageResultFromJson( - Map json, - T Function(Object? json) fromJsonT, -) => PageResult( - records: (json['records'] as List).map(fromJsonT).toList(), - total: (json['total'] as num).toInt(), - size: (json['size'] as num).toInt(), - current: (json['current'] as num).toInt(), - pages: (json['pages'] as num).toInt(), -); - -Map _$PageResultToJson( - PageResult instance, - Object? Function(T value) toJsonT, -) => { - 'records': instance.records.map(toJsonT).toList(), - 'total': instance.total, - 'size': instance.size, - 'current': instance.current, - 'pages': instance.pages, -}; - -ChartData _$ChartDataFromJson(Map json) => ChartData( - name: json['name'] as String, - value: (json['value'] as num).toDouble(), -); - -Map _$ChartDataToJson(ChartData instance) => { - 'name': instance.name, - 'value': instance.value, -}; diff --git a/lib/pages/blog_detail_page.dart b/lib/pages/blog_detail_page.dart index 74450ae..2dd85b8 100644 --- a/lib/pages/blog_detail_page.dart +++ b/lib/pages/blog_detail_page.dart @@ -1,5 +1,5 @@ -import 'package:blog_app/widget/common.dart'; import 'package:blog_app/widget/markdown.dart'; +import 'package:flutter_common/widget/common_widget.dart'; import 'package:provider/provider.dart'; import 'package:blog_app/provider/blog.dart'; import 'package:flutter/material.dart'; diff --git a/lib/pages/blog_page.dart b/lib/pages/blog_page.dart index 6a9d5ec..7e3e39a 100644 --- a/lib/pages/blog_page.dart +++ b/lib/pages/blog_page.dart @@ -1,6 +1,6 @@ import 'package:blog_app/provider/blog.dart'; -import 'package:blog_app/widget/common.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_common/widget/common_widget.dart'; import 'package:provider/provider.dart'; import 'package:blog_app/widget/easy_refresh.dart'; import 'package:blog_app/widget/blog.dart'; diff --git a/lib/pages/category_list_page.dart b/lib/pages/category_list_page.dart index e3c6568..05bbf5c 100644 --- a/lib/pages/category_list_page.dart +++ b/lib/pages/category_list_page.dart @@ -1,4 +1,4 @@ -import 'package:blog_app/widget/common.dart'; +import 'package:flutter_common/widget/common_widget.dart'; import 'package:provider/provider.dart'; import 'package:blog_app/provider/blog.dart'; import 'package:blog_app/widget/blog.dart'; diff --git a/lib/pages/category_page.dart b/lib/pages/category_page.dart index 0944143..02b0f68 100644 --- a/lib/pages/category_page.dart +++ b/lib/pages/category_page.dart @@ -1,4 +1,4 @@ -import 'package:blog_app/widget/common.dart'; +import 'package:flutter_common/widget/common_widget.dart'; import 'package:provider/provider.dart'; import 'package:blog_app/provider/blog.dart'; import 'package:blog_app/widget/blog.dart'; diff --git a/lib/pages/stats_page.dart b/lib/pages/stats_page.dart index d372a75..5299bee 100644 --- a/lib/pages/stats_page.dart +++ b/lib/pages/stats_page.dart @@ -1,10 +1,9 @@ -import 'package:blog_app/utils/index.dart'; +import 'package:flutter_common/utils/number_utils.dart'; +import 'package:flutter_common/widget/chart.dart'; +import 'package:flutter_common/widget/common_widget.dart'; +import 'package:flutter_common/widget/year_selector.dart'; import 'package:provider/provider.dart'; import 'package:blog_app/provider/blog.dart'; -import 'package:blog_app/widget/blog.dart'; -import 'package:blog_app/widget/chart.dart'; -import 'package:blog_app/widget/common.dart'; -import 'package:blog_app/widget/year_selector.dart'; import 'package:flutter/material.dart'; class StatsPage extends StatefulWidget { @@ -28,100 +27,6 @@ class StatsPageState extends State { }); } - Widget _buildApprovedStats(BlogProvider provider) { - return Column( - children: [ - buildChartTitle(context, '每月博客发布统计', Icons.show_chart), - const SizedBox(height: 3), - buildChartDivider(context), - const SizedBox(height: 3), - Expanded( - child: lineChart( - context: context, - xAxisName: '月份', - yAxisName: '数量', - unit: '篇', - data: provider.approvedStats, - ), - ), - ], - ); - } - - Widget _buildVisitStats(BlogProvider provider) { - return Column( - children: [ - buildChartTitle(context, '每月博客访问统计', Icons.show_chart), - const SizedBox(height: 3), - buildChartDivider(context), - const SizedBox(height: 3), - Expanded( - child: lineChart( - context: context, - xAxisName: '月份', - yAxisName: '访问量', - unit: '人次', - data: provider.visitStats, - ), - ), - ], - ); - } - - Widget _buildVisitRankStats(BlogProvider provider) { - return Column( - children: [ - buildChartTitle(context, '博客访问数量排行', Icons.bar_chart), - const SizedBox(height: 3), - buildChartDivider(context), - const SizedBox(height: 3), - Expanded( - child: rankChart( - context: context, - unit: '人次', - data: provider.visitRankStats, - ), - ), - ], - ); - } - - Widget _buildCategoryStats(BlogProvider provider) { - return Column( - children: [ - buildChartTitle(context, '博客类别统计', Icons.pie_chart), - const SizedBox(height: 3), - buildChartDivider(context), - const SizedBox(height: 3), - Expanded( - child: pieChart( - context: context, - unit: '篇', - data: provider.categoryStats, - ), - ), - ], - ); - } - - Widget _buildReadRankStats(BlogProvider provider) { - return Column( - children: [ - buildChartTitle(context, '博客阅读时长排行', Icons.bar_chart), - const SizedBox(height: 3), - buildChartDivider(context), - const SizedBox(height: 3), - Expanded( - child: rankChart( - context: context, - unit: '分钟', - data: provider.readRankStats, - ), - ), - ], - ); - } - Widget _buildBlogStatsGrid(BlogProvider provider) { return GridView.count( crossAxisCount: 2, @@ -131,30 +36,26 @@ class StatsPageState extends State { mainAxisSpacing: 8, crossAxisSpacing: 8, children: [ - buildStatsCard( + StatsCard( icon: Icons.article, - context: context, title: '博客数量', count: provider.overviewStats.blogCount.toString(), unit: '篇', ), - buildStatsCard( + StatsCard( icon: Icons.folder, - context: context, title: '分类数量', count: provider.overviewStats.categoryCount.toString(), unit: '个', ), - buildStatsCard( + StatsCard( icon: Icons.remove_red_eye, - context: context, title: '访问量', count: formatChineseDecimal(provider.overviewStats.visitCount), unit: '次', ), - buildStatsCard( + StatsCard( icon: Icons.text_fields, - context: context, title: '总字数', count: formatChineseDecimal(provider.overviewStats.wordCount), unit: '字', @@ -174,14 +75,10 @@ class StatsPageState extends State { return Column( children: [ YearSelector( - initialYear: provider.currentYear, - minYear: 2000, - maxYear: 2100, + currentYear: provider.currentStatsYear, onYearChanged: (year) { - setState(() { - provider.currentYear = year; - provider.refreshBlogStats(); - }); + provider.currentStatsYear = year; + provider.refreshBlogStats(); }, ), const SizedBox(height: 8), @@ -189,27 +86,61 @@ class StatsPageState extends State { const SizedBox(height: 8), SizedBox( height: chartHeight, - child: BuildCard(child: _buildApprovedStats(provider)), + child: CommonCard( + child: LineChart( + title: '每月博客发布统计', + xAxisName: '月份', + yAxisName: '数量', + unit: '篇', + data: provider.approvedStats, + ), + ), ), const SizedBox(height: 8), SizedBox( height: chartHeight, - child: BuildCard(child: _buildVisitStats(provider)), + child: CommonCard( + child: LineChart( + title: '每月博客访问统计', + xAxisName: '月份', + yAxisName: '访问量', + unit: '人次', + data: provider.visitStats, + ), + ), ), const SizedBox(height: 8), SizedBox( height: chartHeight, - child: BuildCard(child: _buildVisitRankStats(provider)), + child: CommonCard( + child: RankChart( + title: '博客访问数量排行', + unit: '人次', + data: provider.visitRankStats, + ), + ), ), const SizedBox(height: 8), SizedBox( height: chartHeight, - child: BuildCard(child: _buildCategoryStats(provider)), + child: CommonCard( + child: PieChart( + title: '博客类别统计', + unit: '篇', + data: provider.categoryStats, + ), + ), ), const SizedBox(height: 8), SizedBox( height: chartHeight, - child: BuildCard(child: _buildReadRankStats(provider)), + child: CommonCard( + child: RankChart( + title: '博客阅读时长排行', + unit: '分钟', + data: provider.readRankStats, + ), + ), ), ], ); diff --git a/lib/pages/timeline_page.dart b/lib/pages/timeline_page.dart index 766b882..846cad7 100644 --- a/lib/pages/timeline_page.dart +++ b/lib/pages/timeline_page.dart @@ -1,8 +1,8 @@ +import 'package:flutter_common/widget/common_widget.dart'; +import 'package:flutter_common/widget/year_selector.dart'; import 'package:provider/provider.dart'; import 'package:blog_app/provider/blog.dart'; import 'package:blog_app/widget/blog.dart'; -import 'package:blog_app/widget/common.dart'; -import 'package:blog_app/widget/year_selector.dart'; import 'package:flutter/material.dart'; import 'package:timelines_plus/timelines_plus.dart'; @@ -63,14 +63,10 @@ class TimelinePageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ YearSelector( - initialYear: DateTime.now().year, - minYear: 2000, - maxYear: 2100, + currentYear: provider.currentQueryYear, onYearChanged: (year) { - setState(() { - provider.currentYear = year; - provider.refreshBlogByYear(); - }); + provider.currentQueryYear = year; + provider.refreshBlogByYear(); }, ), const SizedBox(height: 8), diff --git a/lib/provider/blog.dart b/lib/provider/blog.dart index ed154b4..f2425ce 100644 --- a/lib/provider/blog.dart +++ b/lib/provider/blog.dart @@ -1,7 +1,7 @@ -import 'package:blog_app/models/common.dart'; import 'package:flutter/foundation.dart'; import 'package:blog_app/apis/blog.dart'; import 'package:blog_app/models/blog.dart'; +import 'package:flutter_common/models/common_model.dart'; class BlogProvider with ChangeNotifier { List blogList = []; @@ -24,7 +24,8 @@ class BlogProvider with ChangeNotifier { final int pageSize = 5; bool hasMore = true; bool isLoading = false; - int currentYear = DateTime.now().year; + int currentStatsYear = DateTime.now().year; + int currentQueryYear = DateTime.now().year; String? error; late List approvedStats = []; @@ -107,8 +108,8 @@ class BlogProvider with ChangeNotifier { error = null; notifyListeners(); - final approvedResult = await queryBlogApprovedStatsApi(currentYear); - final visitResult = await queryBlogVisitStatsApi(currentYear); + final approvedResult = await queryBlogApprovedStatsApi(currentStatsYear); + final visitResult = await queryBlogVisitStatsApi(currentStatsYear); final visitRankResult = await queryBlogVisitRankStatsApi(); final categoryResult = await queryBlogCategoryStatsApi(); final readRankResult = await queryBlogReadRankStatsApi(); @@ -158,7 +159,7 @@ class BlogProvider with ChangeNotifier { blogList = []; notifyListeners(); - final result = await queryBlogByConditionApi(null, null, currentYear); + final result = await queryBlogByConditionApi(null, null, currentQueryYear); blogList = result; } catch (e) { error = '加载数据失败: $e'; diff --git a/lib/provider/theme_provider.dart b/lib/provider/theme_provider.dart deleted file mode 100644 index 2bed2a2..0000000 --- a/lib/provider/theme_provider.dart +++ /dev/null @@ -1,76 +0,0 @@ -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/date_utils.dart b/lib/utils/date_utils.dart deleted file mode 100644 index be9d81a..0000000 --- a/lib/utils/date_utils.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:intl/intl.dart'; - -String formatDate(DateTime date) { - return DateFormat('yyyy-MM-dd').format(date); -} - -String formatDateString(String date) { - return DateFormat('yyyy-MM-dd').format(DateTime.parse(date)); -} - -String formatTime(DateTime datetime) { - return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime); -} diff --git a/lib/utils/http_utils.dart b/lib/utils/http_utils.dart deleted file mode 100644 index d1e91df..0000000 --- a/lib/utils/http_utils.dart +++ /dev/null @@ -1,244 +0,0 @@ -import 'package:blog_app/config/app_config.dart'; -import 'package:blog_app/utils/sp_utils.dart'; -import 'package:dio/dio.dart'; - -import 'log_utils.dart'; - -class HttpUtil { - static final HttpUtil _instance = HttpUtil._internal(); - - factory HttpUtil() => _instance; - - late Dio _dio; - - // 请求头配置 - Map headers = { - 'Content-Type': 'application/json;charset=UTF-8', - 'Accept': 'application/json', - }; - - // 超时时间 - final int timeout = 5; - - HttpUtil._internal() { - // 初始化Dio实例 - BaseOptions options = BaseOptions( - baseUrl: AppConfig.baseApiUrl, - connectTimeout: Duration(seconds: timeout), - receiveTimeout: Duration(seconds: timeout), - headers: headers, - ); - - _dio = Dio(options); - - // 添加请求拦截器 - _dio.interceptors.add( - InterceptorsWrapper( - onRequest: (options, handler) { - logger.d("请求URL: ${options.uri}"); - if (options.data != null) { - logger.d("请求参数: ${options.data}"); - } - - if (SPUtil.getString('token').isNotEmpty) { - options.headers['satoken'] = SPUtil.getString('token'); - } - return handler.next(options); - }, - ), - ); - - // 添加响应拦截器 - _dio.interceptors.add( - InterceptorsWrapper( - onResponse: (response, handler) { - logger.d("响应状态码: ${response.statusCode}"); - // logger.d("响应数据: ${response.data}"); - return handler.next(response); - }, - ), - ); - - // 添加错误拦截器 - _dio.interceptors.add( - InterceptorsWrapper( - onError: (DioException e, handler) { - _handleError(e); - return handler.next(e); - }, - ), - ); - } - - // 基础请求方法(处理所有类型的请求) - Future _request( - String path, { - required String method, - dynamic data, - Map? queryParameters, - T Function(dynamic data)? converter, - }) async { - try { - Response response = await _dio.request( - path, - data: data, - queryParameters: queryParameters, - options: Options(method: method), - ); - - // 处理响应数据 - if (converter != null) { - return converter(response.data); - } - - // 没有转换器时尝试直接返回(可能不安全,建议提供转换器) - return response.data; - } catch (e) { - rethrow; - } - } - - // GET请求 - Future get( - String path, { - Map? queryParameters, - T Function(dynamic data)? converter, - }) => _request( - path, - method: "GET", - queryParameters: queryParameters, - converter: converter, - ); - - // POST请求 - Future post( - String path, { - dynamic data, - Map? queryParameters, - T Function(dynamic data)? converter, - }) => _request( - path, - method: "POST", - data: data, - queryParameters: queryParameters, - converter: converter, - ); - - // PUT请求 - Future put( - String path, { - dynamic data, - Map? queryParameters, - T Function(dynamic data)? converter, - }) => _request( - path, - method: "PUT", - data: data, - queryParameters: queryParameters, - converter: converter, - ); - - // DELETE请求 - Future delete( - String path, { - dynamic data, - Map? queryParameters, - T Function(dynamic data)? converter, - }) => _request( - path, - method: "DELETE", - data: data, - queryParameters: queryParameters, - converter: converter, - ); - - // 文件上传 - Future upload( - String path, - String filePath, { - Map? queryParameters, - }) async { - try { - FormData formData = FormData.fromMap({ - 'file': await MultipartFile.fromFile(filePath), - }); - - Response response = await _dio.post( - path, - data: formData, - queryParameters: queryParameters, - ); - return response.data; - } catch (e) { - _handleError(e); - rethrow; - } - } - - // 错误处理 - void _handleError(dynamic error) { - String errorMessage = '未知错误'; - if (error is DioException) { - switch (error.type) { - case DioExceptionType.connectionTimeout: - errorMessage = '连接超时,请检查网络连接'; - break; - case DioExceptionType.sendTimeout: - errorMessage = '发送超时,请检查网络连接'; - break; - case DioExceptionType.receiveTimeout: - errorMessage = '接收超时,请检查网络连接'; - break; - case DioExceptionType.cancel: - errorMessage = '请求已取消'; - break; - case DioExceptionType.badCertificate: - errorMessage = '证书验证失败'; - break; - case DioExceptionType.badResponse: - // 处理HTTP响应错误(4xx, 5xx) - final statusCode = error.response?.statusCode ?? 0; - final responseData = error.response?.data; - - if (responseData is Map && responseData.containsKey('message')) { - // 服务器返回了自定义错误消息 - errorMessage = responseData['message']?.toString() ?? '未知错误'; - } else { - errorMessage = _getHttpErrorMessage(statusCode); - } - break; - case DioExceptionType.connectionError: - errorMessage = '网络连接错误,请检查网络设置'; - break; - case DioExceptionType.unknown: - if (error.error != null) { - errorMessage = '未知错误: ${error.error.toString()}'; - } - errorMessage = '发生未知错误'; - break; - } - } else { - errorMessage = '非Dio错误: $error'; - } - - print(errorMessage); - // showErrorToast(errorMessage); - } - - String _getHttpErrorMessage(int statusCode) { - // 根据HTTP状态码返回对应的错误消息 - switch (statusCode) { - case 400: return '错误请求,请检查参数'; - case 401: return '未授权,请登录'; - case 403: return '禁止访问,权限不足'; - case 404: return '资源不存在'; - case 405: return '方法不允许'; - case 408: return '请求超时'; - case 500: return '服务器内部错误'; - case 502: return '网关错误'; - case 503: return '服务不可用'; - case 504: return '网关超时'; - default: return 'HTTP错误: $statusCode'; - } - } -} diff --git a/lib/utils/index.dart b/lib/utils/index.dart deleted file mode 100644 index 36921f1..0000000 --- a/lib/utils/index.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:blog_app/models/common.dart'; - -List convertListResponse( - dynamic data, - T Function(Map) fromJson, -) { - if (data is List) { - return data.map((item) => fromJson(item as Map)).toList(); - } - throw FormatException( - 'Expected a list of items for conversion, but got ${data.runtimeType}', - ); -} - -PageResult convertPageResponse( - dynamic data, - T Function(Map) fromJson, -) { - if (data is Map) { - return PageResult.fromJson( - data, - (json) => fromJson(json as Map), - ); - } - throw FormatException( - 'Expected a Map for page response conversion, but got ${data.runtimeType}', - ); -} - -/// 将数字格式化为中文单位(万/亿) -String formatChineseDecimal(num number, {int decimalPlaces = 2}) { - if (number < 10000) { - return number.toString(); - } else if (number < 100000000) { - // 万单位 - double result = number / 10000.0; - return '${_formatDecimal(result, decimalPlaces)}万'; - } else { - // 亿单位 - double result = number / 100000000.0; - return '${_formatDecimal(result, decimalPlaces)}亿'; - } -} - -String _formatDecimal(double number, int decimalPlaces) { - // 去除末尾的0和小数点 - String formatted = number.toStringAsFixed(decimalPlaces); - if (formatted.contains('.')) { - formatted = formatted.replaceAll(RegExp(r'0*$'), ''); - formatted = formatted.replaceAll(RegExp(r'\.$'), ''); - } - return formatted; -} diff --git a/lib/utils/log_utils.dart b/lib/utils/log_utils.dart deleted file mode 100644 index f571317..0000000 --- a/lib/utils/log_utils.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:logger/logger.dart'; - -// 全局日志实例,在整个项目中共享 -final Logger logger = Logger( - printer: PrettyPrinter( - methodCount: 1, - colors: true, - dateTimeFormat: DateTimeFormat.dateAndTime, - ), - // 可选:配置输出到文件(需配合文件操作库) - // output: FileOutput(file: File('logs/app.log')), -); \ No newline at end of file diff --git a/lib/utils/sp_utils.dart b/lib/utils/sp_utils.dart deleted file mode 100644 index b255633..0000000 --- a/lib/utils/sp_utils.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:shared_preferences/shared_preferences.dart'; - -class SPUtil { - static late SharedPreferences _prefs; - - /// 初始化 - static Future init() async { - _prefs = await SharedPreferences.getInstance(); - } - - /// 存储数据 - static Future set(String key, dynamic value) { - if (value is String) return _prefs.setString(key, value); - if (value is int) return _prefs.setInt(key, value); - if (value is bool) return _prefs.setBool(key, value); - if (value is double) return _prefs.setDouble(key, value); - if (value is List) return _prefs.setStringList(key, value); - throw ArgumentError('Unsupported value type: ${value.runtimeType}'); - } - - /// 获取数据 - static dynamic get(String key, [dynamic defaultValue]) { - if (!_prefs.containsKey(key)) return defaultValue; - return _prefs.get(key) ?? defaultValue; - } - - /// 获取字符串 - static String getString(String key, [String defaultValue = '']) { - return _prefs.getString(key) ?? defaultValue; - } - - /// 获取布尔值 - static bool getBool(String key, [bool defaultValue = false]) { - return _prefs.getBool(key) ?? defaultValue; - } - - /// 获取整数 - static int getInt(String key, [int defaultValue = 0]) { - return _prefs.getInt(key) ?? defaultValue; - } - - /// 获取浮点数 - static double getDouble(String key, [double defaultValue = 0.0]) { - return _prefs.getDouble(key) ?? defaultValue; - } - - /// 获取字符串列表 - static List getStringList(String key, [List defaultValue = const []]) { - return _prefs.getStringList(key) ?? defaultValue; - } - - /// 删除数据 - static Future remove(String key) { - return _prefs.remove(key); - } - - /// 清空所有数据 - static Future clear() { - return _prefs.clear(); - } -} \ No newline at end of file diff --git a/lib/utils/theme_utils.dart b/lib/utils/theme_utils.dart deleted file mode 100644 index c24aeed..0000000 --- a/lib/utils/theme_utils.dart +++ /dev/null @@ -1,87 +0,0 @@ -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 4e4cbb3..5e52f05 100644 --- a/lib/widget/blog.dart +++ b/lib/widget/blog.dart @@ -1,9 +1,9 @@ import 'package:blog_app/models/blog.dart'; import 'package:blog_app/pages/blog_detail_page.dart'; import 'package:blog_app/pages/category_list_page.dart'; -import 'package:blog_app/utils/date_utils.dart'; -import 'package:blog_app/widget/common.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_common/utils/date_utils.dart'; +import 'package:flutter_common/widget/common_widget.dart'; class BlogLabel { final String text; @@ -122,7 +122,7 @@ class BlogCard extends StatelessWidget { Widget build(BuildContext context) { final colors = Theme.of(context).colorScheme; - return BuildCard( + return CommonCard( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -164,7 +164,7 @@ Widget buildCategoryTitle(BuildContext context, int count) { Widget buildCategoryCard(BuildContext context, BlogCategory category) { final colors = Theme.of(context).colorScheme; - return BuildCard( + return CommonCard( child: ListTile( leading: Container( width: 50, @@ -223,7 +223,7 @@ Widget buildListTitle(int count) { Widget buildBlogListItem(BuildContext context, Blog blog, int index) { final colors = Theme.of(context).colorScheme; - return BuildCard( + return CommonCard( child: Padding( padding: EdgeInsets.all(0), child: Row( @@ -259,73 +259,6 @@ Widget buildBlogListItem(BuildContext context, Blog blog, int index) { ); } -Widget buildStatsCard({ - required BuildContext context, - required IconData icon, - required String title, - required String count, - required String unit, -}) { - final colors = Theme.of(context).colorScheme; - - return BuildCard( - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: colors.primary.withAlpha(50), - shape: BoxShape.circle, - ), - child: Icon(icon, size: 26, color: colors.primary), - ), - SizedBox(width: 6), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - title, - style: TextStyle( - fontSize: 16, - color: Colors.grey.shade600, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 6), - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - count, - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: colors.primary, - height: 1.0, - ), - ), - const SizedBox(width: 4), - Text( - unit, - style: TextStyle( - fontSize: 14, - color: Colors.grey.shade600, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ], - ), - ), - ], - ), - ); -} - void navigatorToBlogDetail(BuildContext context, int blogId) { Navigator.push( context, diff --git a/lib/widget/chart.dart b/lib/widget/chart.dart deleted file mode 100644 index d9772c2..0000000 --- a/lib/widget/chart.dart +++ /dev/null @@ -1,349 +0,0 @@ -import 'package:blog_app/models/common.dart'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -Widget buildChartTitle(BuildContext context, String title, IconData icon) { - return Row( - children: [ - Icon(icon, color: Theme.of(context).colorScheme.primary), - Text(title), - ], - ); -} - -Widget buildChartDivider(BuildContext context) { - return Divider( - height: 1, - thickness: 1, - color: Theme.of(context).colorScheme.primary, - indent: 0, - endIndent: 0, - ); -} - -Widget lineChart({ - required BuildContext context, - required String xAxisName, - required String yAxisName, - required String unit, - required List data, -}) { - return SfCartesianChart( - // 图表标题 - // title: ChartTitle(text: '2023年上半年销售额(万元)'), - - // X轴配置(类别轴) - primaryXAxis: CategoryAxis(majorGridLines: MajorGridLines(width: 0)), - - // Y轴配置(数值轴) - // primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName($unit)')), - - // 启用图例 - legend: Legend(isVisible: true, position: LegendPosition.top), - - // 启用交互提示(点击数据点显示详情) - tooltipBehavior: TooltipBehavior( - enable: true, - format: 'point.x: point.y $unit', - ), - - // 折线图数据系列 - series: [ - LineSeries( - dataSource: data, - // X轴数据映射 - xValueMapper: (ChartData chart, _) => chart.name, - // Y轴数据映射 - yValueMapper: (ChartData chart, _) => chart.value, - // 线条颜色 - color: Theme.of(context).colorScheme.primary, - // 线条宽度 - width: 3, - - // 数据点样式 - markerSettings: const MarkerSettings( - isVisible: true, - color: Colors.white, - shape: DataMarkerType.circle, - height: 6, - width: 6, - ), - - // 折线名称(会显示在图例中) - name: yAxisName, - - // 启用数据标签(直接显示数值) - dataLabelSettings: const DataLabelSettings( - isVisible: true, - color: Colors.white, - opacity: 0, - ), - - // 动画效果 - animationDuration: 2000, // 动画时长(毫秒) - ), - ], - ); -} - -Widget barChart({ - required BuildContext context, - required String xAxisName, - required String yAxisName, - required String unit, - required List data, -}) { - return SfCartesianChart( - // X轴配置(类别轴) - primaryXAxis: CategoryAxis(majorGridLines: MajorGridLines(width: 0)), - - // Y轴配置(数值轴) - // primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName($unit)')), - - // 启用交互提示 - tooltipBehavior: TooltipBehavior( - enable: true, - format: 'point.x: point.y $unit', - ), - - // 柱状图数据系列 - series: [ - ColumnSeries( - dataSource: data, - // X轴数据映射 - xValueMapper: (ChartData chart, _) => chart.name, - // Y轴数据映射 - yValueMapper: (ChartData chart, _) => chart.value, - - // 名称 - name: yAxisName, - - // 柱子颜色 - color: Theme.of(context).colorScheme.primary, - - // 柱子宽度(0-1之间,1表示占满类别间隔) - width: 0.6, - - // 柱子边框 - borderWidth: 1, - borderColor: Colors.black12, - - // 数据标签 - dataLabelSettings: const DataLabelSettings( - isVisible: true, - color: Colors.white, - opacity: 0, - alignment: ChartAlignment.center, - ), - - // 动画效果 - animationDuration: 2000, - ), - ], - ); -} - -Widget doubleBarChart({ - required BuildContext context, - required String xAxisName, - required String yAxisName, - required String unit, - required List data1, - required List data2, - required String series1Name, - required String series2Name, -}) { - return SfCartesianChart( - // X轴配置(类别轴) - primaryXAxis: CategoryAxis(majorGridLines: const MajorGridLines(width: 0)), - - // Y轴配置(数值轴) - // primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName($unit)')), - - // 图例配置 - legend: Legend( - isVisible: true, - position: LegendPosition.top, - overflowMode: LegendItemOverflowMode.wrap, - ), - - // 启用交互提示 - tooltipBehavior: TooltipBehavior( - enable: true, - format: 'series.name: point.y $unit', - ), - - // 双柱状图数据系列 - series: >[ - ColumnSeries( - dataSource: data1, - // X轴数据映射 - xValueMapper: (ChartData chart, _) => chart.name, - // Y轴数据映射 - yValueMapper: (ChartData chart, _) => chart.value, - // 系列名称 - name: series1Name, - // 柱子颜色 - color: Theme.of(context).colorScheme.primary, - // 柱子宽度 - width: 0.3, - // 柱子边框 - borderWidth: 1, - borderColor: Colors.black12, - // 数据标签 - dataLabelSettings: const DataLabelSettings( - isVisible: true, - color: Colors.white, - opacity: 0, - alignment: ChartAlignment.center, - ), - // 动画效果 - animationDuration: 2000, - ), - ColumnSeries( - dataSource: data2, - // X轴数据映射 - xValueMapper: (ChartData chart, _) => chart.name, - // Y轴数据映射 - yValueMapper: (ChartData chart, _) => chart.value, - // 系列名称 - name: series2Name, - // 柱子颜色 - color: Theme.of(context).colorScheme.inversePrimary, - // 柱子宽度 - width: 0.3, - // 柱子边框 - borderWidth: 1, - borderColor: Colors.black12, - // 数据标签 - dataLabelSettings: const DataLabelSettings( - isVisible: true, - color: Colors.white, - opacity: 0, - alignment: ChartAlignment.center, - ), - // 动画效果 - animationDuration: 2000, - ), - ], - ); -} - -Widget pieChart({ - required BuildContext context, - required String unit, - required List data, -}) { - // 计算 value 的总和 - double sumValue = data.fold(0.0, (sum, item) => sum + item.value); - - return SfCircularChart( - // 饼图标题 - // title: ChartTitle(text: '菜谱类别占比分布'), - - // 启用图例 - legend: const Legend(isVisible: true, position: LegendPosition.right), - - // 启用交互提示(点击扇区显示详情) - tooltipBehavior: TooltipBehavior( - enable: true, - format: 'point.x: point.y $unit', - ), - - // 饼图系列配置 - series: [ - PieSeries( - dataSource: data, - // 类别映射(饼图扇区名称) - xValueMapper: (ChartData data, _) => data.name, - // 数值映射(扇区大小占比) - yValueMapper: (ChartData data, _) => data.value, - - // 扇区半径(0-1之间,1表示充满容器) - // radius: '50%', - - // 启用扇区分离效果 - explode: true, - // 指定分离的扇区索引(这里分离第一个扇区) - explodeIndex: 0, - // 分离距离 - explodeOffset: '5%', - - dataLabelMapper: (ChartData data, _) { - final percentage = (data.value / sumValue * 100).toStringAsFixed(0); - return '$percentage%'; - }, - - // 数据标签(显示在扇区上的文本) - dataLabelSettings: DataLabelSettings(isVisible: true), - - // 动画效果 - animationDuration: 2000, - ), - ], - ); -} - -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/common.dart b/lib/widget/common.dart deleted file mode 100644 index 46a6083..0000000 --- a/lib/widget/common.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:flutter/material.dart'; - -class BuildCard extends StatelessWidget { - final Widget? child; - - const BuildCard({super.key, this.child}); - - @override - Widget build(BuildContext context) { - final colors = Theme.of(context).colorScheme; - - return Container( - decoration: BoxDecoration( - color: colors.surfaceContainer, - borderRadius: BorderRadius.circular(12), - ), - padding: const EdgeInsets.all(16), - child: child, - ); - } -} - -Widget circleIconButton({ - required IconData icon, - required VoidCallback onPressed, - required BuildContext context, -}) { - return ElevatedButton( - onPressed: onPressed, - style: ElevatedButton.styleFrom( - shape: CircleBorder(), - elevation: 0, - backgroundColor: Theme.of(context).colorScheme.primary, - padding: EdgeInsets.zero, - minimumSize: const Size(0, 0), - ), - child: Icon(icon, color: Colors.white), - ); -} - -Widget buildErrorInfo({ - required String errorInfo, - required VoidCallback onPressed, -}) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('加载失败: $errorInfo'), - ElevatedButton(onPressed: onPressed, child: Text('重试')), - ], - ), - ); -} - -Widget buildLoadingIndicator() { - return Center(child: CircularProgressIndicator()); -} diff --git a/lib/widget/search.dart b/lib/widget/search.dart index ffa389c..171d6c8 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -1,7 +1,7 @@ import 'package:blog_app/models/blog.dart'; import 'package:blog_app/widget/blog.dart'; -import 'package:blog_app/widget/common.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_common/widget/common_widget.dart'; Widget buildSearchField({ required TextEditingController controller, @@ -29,7 +29,7 @@ Widget buildSearchField({ Widget buildSearchResultItem(BuildContext context, BlogSearch blog) { final colors = Theme.of(context).colorScheme; - return BuildCard( + return CommonCard( child: ListTile( title: Text( blog.title, diff --git a/lib/widget/year_selector.dart b/lib/widget/year_selector.dart deleted file mode 100644 index 2f8d108..0000000 --- a/lib/widget/year_selector.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:blog_app/widget/common.dart'; -import 'package:flutter/material.dart'; - -class YearSelector extends StatefulWidget { - final int initialYear; - final int? minYear; - final int? maxYear; - final Function(int) onYearChanged; - - const YearSelector({ - super.key, - required this.initialYear, - required this.onYearChanged, - this.minYear, - this.maxYear, - }); - - @override - State createState() => _YearSelectorState(); -} - -// SingleTickerProviderStateMixin 动画控制器 -class _YearSelectorState extends State - with SingleTickerProviderStateMixin { - late int _currentYear; - - // 用于动画效果 - late AnimationController _animationController; - late Animation _scaleAnimation; - - @override - void initState() { - super.initState(); - _currentYear = widget.initialYear; - - // 初始化动画控制器 - _animationController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 200), - ); - - // 缩放动画 - _scaleAnimation = Tween(begin: 1.0, end: 1.1).animate( - CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), - ); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - - /// 切换到上一年 - void _previousYear() { - if (widget.minYear == null || _currentYear > widget.minYear!) { - _animateYearChange(() { - setState(() { - _currentYear--; - }); - widget.onYearChanged(_currentYear); - }); - } - } - - /// 切换到下一年 - void _nextYear() { - if (widget.maxYear == null || _currentYear < widget.maxYear!) { - _animateYearChange(() { - setState(() { - _currentYear++; - }); - widget.onYearChanged(_currentYear); - }); - } - } - - /// 年份变化时的动画效果 - void _animateYearChange(VoidCallback onComplete) { - _animationController.forward().then((_) { - onComplete(); - _animationController.reverse(); - }); - } - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - circleIconButton( - context: context, - icon: Icons.chevron_left, - onPressed: () => _previousYear(), - ), - // 年份显示 - AnimatedBuilder( - animation: _scaleAnimation, - builder: (context, child) { - return Transform.scale(scale: _scaleAnimation.value, child: child); - }, - child: Text( - '$_currentYear', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ), - ), - ), - circleIconButton( - context: context, - icon: Icons.chevron_right, - onPressed: () => _nextYear(), - ) - ], - ); - } -} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index f6f23bf..08ab439 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) rive_native_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "RiveNativePlugin"); + rive_native_plugin_register_with_registrar(rive_native_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index f16b4c3..c9870b8 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + rive_native url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 997e35d..4c427d4 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,16 @@ import FlutterMacOS import Foundation +import file_picker +import flutter_image_compress_macos +import rive_native import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) + RiveNativePlugin.register(with: registry.registrar(forPlugin: "RiveNativePlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 7c1978c..79f994e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.12.0" + awesome_dialog: + dependency: transitive + description: + name: awesome_dialog + sha256: "4c5821a0a637ceee022084e78c1b8237dd4b8bfca4dd24ac2484662a56707338" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.3.0" boolean_selector: dependency: transitive description: @@ -41,6 +49,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" + buffer: + dependency: transitive + description: + name: buffer + sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.3" build: dependency: transitive description: @@ -153,6 +169,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.5" crypto: dependency: transitive description: @@ -177,8 +201,16 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.1.1" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.11" dio: - dependency: "direct main" + dependency: transitive description: name: dio sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 @@ -225,6 +257,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" + file_picker: + dependency: transitive + description: + name: file_picker + sha256: "7872545770c277236fd32b022767576c562ba28366204ff1a5628853cf8f2200" + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.3.7" fixnum: dependency: transitive description: @@ -238,6 +278,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_common: + dependency: "direct main" + description: + path: "D:\\Projects\\FlutterProjects\\flutter_common" + relative: false + source: path + version: "1.0.0+1" flutter_highlight: dependency: transitive description: @@ -246,6 +293,54 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.7.0" + flutter_image_compress: + dependency: transitive + description: + name: flutter_image_compress + sha256: "51d23be39efc2185e72e290042a0da41aed70b14ef97db362a6b5368d0523b27" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" + flutter_image_compress_common: + dependency: transitive + description: + name: flutter_image_compress_common + sha256: c5c5d50c15e97dd7dc72ff96bd7077b9f791932f2076c5c5b6c43f2c88607bfb + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.6" + flutter_image_compress_macos: + dependency: transitive + description: + name: flutter_image_compress_macos + sha256: "20019719b71b743aba0ef874ed29c50747461e5e8438980dfa5c2031898f7337" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.3" + flutter_image_compress_ohos: + dependency: transitive + description: + name: flutter_image_compress_ohos + sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.3" + flutter_image_compress_platform_interface: + dependency: transitive + description: + name: flutter_image_compress_platform_interface + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + flutter_image_compress_web: + dependency: transitive + description: + name: flutter_image_compress_web + sha256: b9b141ac7c686a2ce7bb9a98176321e1182c9074650e47bb140741a44b6f5a96 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.5" flutter_lints: dependency: "direct dev" description: @@ -254,6 +349,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.31" flutter_test: dependency: "direct dev" description: flutter @@ -264,6 +367,14 @@ packages: description: flutter source: sdk version: "0.0.0" + fluttertoast: + dependency: transitive + description: + name: fluttertoast + sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.2.14" frontend_server_client: dependency: transitive description: @@ -393,7 +504,7 @@ packages: source: hosted version: "5.1.1" logger: - dependency: "direct main" + dependency: transitive description: name: logger sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 @@ -456,6 +567,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" + minio: + dependency: transitive + description: + name: minio + sha256: ee2ce47766e46c7d164f960f2f5ed6a9a82844d877f6b82574f6876ec50c56d1 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.5.8" nested: dependency: transitive description: @@ -520,6 +639,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.1.0" platform: dependency: transitive description: @@ -568,6 +695,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.5.0" + rive: + dependency: transitive + description: + name: rive + sha256: fc0abf65d03d1c9afaeb35be9e71c7cf04d2d1f76e94e69d2af1b3ba413cddf9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.14.0-dev.14" + rive_native: + dependency: transitive + description: + name: rive_native + sha256: e9c7d36f19eb6d32f563825d4e9d5032b19a36d3ca3341641431035bca022d19 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.17" scroll_to_index: dependency: transitive description: @@ -577,7 +720,7 @@ packages: source: hosted version: "3.0.1" shared_preferences: - dependency: "direct main" + dependency: transitive description: name: shared_preferences sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" @@ -710,7 +853,7 @@ packages: source: hosted version: "1.4.1" syncfusion_flutter_charts: - dependency: "direct main" + dependency: transitive description: name: syncfusion_flutter_charts sha256: "68fdb029dad34a46e4c9cfad8ad66fe29db7b303bd96849261ab2b23a168d0e8" @@ -893,6 +1036,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.3" + win32: + dependency: transitive + description: + name: win32 + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.13.0" xdg_directories: dependency: transitive description: @@ -901,6 +1052,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.5.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b921842..3037fa8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1 +1 @@ -name: blog_app description: "博客App" # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. # In Android, build-name is used as versionName while build-number used as versionCode. # Read more about Android versioning at https://developer.android.com/studio/publish/versioning # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: sdk: ^3.7.0 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions # consider running `flutter pub upgrade --major-versions`. Alternatively, # dependencies can be manually updated by changing the version numbers below to # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 markdown_widget: ^2.3.2+8 dio: ^5.7.0 shared_preferences: ^2.3.0 logger: ^2.6.0 json_annotation: ^4.9.0 intl: ^0.19.0 easy_refresh: ^3.4.0 timelines_plus: ^1.0.7 syncfusion_localizations: ^30.1.37 syncfusion_flutter_charts: ^30.1.41 provider: ^6.1.1 dev_dependencies: flutter_test: sdk: flutter # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^5.0.0 build_runner: ^2.4.5 json_serializable: ^6.7.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec flutter: uses-material-design: true assets: - assets/images/ fonts: - family: CustomFont fonts: - asset: assets/fonts/custom.ttf \ No newline at end of file +name: blog_app description: "博客App" # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. # In Android, build-name is used as versionName while build-number used as versionCode. # Read more about Android versioning at https://developer.android.com/studio/publish/versioning # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: sdk: ^3.7.0 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions # consider running `flutter pub upgrade --major-versions`. Alternatively, # dependencies can be manually updated by changing the version numbers below to # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 markdown_widget: ^2.3.2+8 json_annotation: ^4.9.0 intl: ^0.19.0 easy_refresh: ^3.4.0 timelines_plus: ^1.0.7 syncfusion_localizations: ^30.1.37 provider: ^6.1.1 flutter_common: path: ..\flutter_common dev_dependencies: flutter_test: sdk: flutter # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^5.0.0 build_runner: ^2.4.5 json_serializable: ^6.7.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec flutter: uses-material-design: true assets: - assets/images/ fonts: - family: CustomFont fonts: - asset: assets/fonts/custom.ttf \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 4f78848..cb80819 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + RiveNativePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RiveNativePlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 88b22e5..8dae662 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + rive_native url_launcher_windows )