From d5f3f3b868b03c1dc85384ee27e3f3fab0183022 Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 17 Jan 2026 01:08:21 -0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E5=AE=9E=E7=8E=B0=E6=88=91?= =?UTF-8?q?=E7=9A=84=E9=A1=B5=E9=9D=A2=E5=85=B6=E4=BB=96=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?4=E9=A1=B9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 消息通知: 添加开关控制,状态持久化到SharedPreferences - 深色模式: 添加开关控制,状态持久化到SharedPreferences - 帮助中心: 新建页面,包含常见问题FAQ和联系方式 - 关于我们: 新建页面,包含应用简介、功能特点、联系方式和法律条款 新增文件: - settings_providers.dart: 设置状态管理 - help_center_page.dart: 帮助中心页面 - about_page.dart: 关于我们页面 Co-Authored-By: Claude Opus 4.5 --- backend/services/docker-compose.2.0.yml | 2 + .../lib/core/router/app_router.dart | 10 + .../mining-app/lib/core/router/routes.dart | 3 + .../pages/profile/about_page.dart | 437 ++++++++++++++++++ .../pages/profile/help_center_page.dart | 276 +++++++++++ .../pages/profile/profile_page.dart | 25 +- .../providers/settings_providers.dart | 76 +++ frontend/mining-app/pubspec.yaml | 1 + 8 files changed, 821 insertions(+), 9 deletions(-) create mode 100644 frontend/mining-app/lib/presentation/pages/profile/about_page.dart create mode 100644 frontend/mining-app/lib/presentation/pages/profile/help_center_page.dart create mode 100644 frontend/mining-app/lib/presentation/providers/settings_providers.dart diff --git a/backend/services/docker-compose.2.0.yml b/backend/services/docker-compose.2.0.yml index a4687645..da71f2bf 100644 --- a/backend/services/docker-compose.2.0.yml +++ b/backend/services/docker-compose.2.0.yml @@ -78,6 +78,8 @@ services: KAFKA_BROKERS: kafka:29092 # JWT 配置 (与 auth-service 共享密钥以验证 token) JWT_SECRET: ${JWT_SECRET:-your-jwt-secret-change-in-production} + # 2.0 内部服务调用 + CONTRIBUTION_SERVICE_URL: http://contribution-service:3020 ports: - "3021:3021" healthcheck: diff --git a/frontend/mining-app/lib/core/router/app_router.dart b/frontend/mining-app/lib/core/router/app_router.dart index d92b976d..f095b2ad 100644 --- a/frontend/mining-app/lib/core/router/app_router.dart +++ b/frontend/mining-app/lib/core/router/app_router.dart @@ -21,6 +21,8 @@ import '../../presentation/pages/c2c/c2c_publish_page.dart'; import '../../presentation/pages/c2c/c2c_order_detail_page.dart'; import '../../presentation/pages/profile/team_page.dart'; import '../../presentation/pages/profile/trading_records_page.dart'; +import '../../presentation/pages/profile/help_center_page.dart'; +import '../../presentation/pages/profile/about_page.dart'; import '../../presentation/widgets/main_shell.dart'; import '../../presentation/providers/user_providers.dart'; import 'routes.dart'; @@ -155,6 +157,14 @@ final appRouterProvider = Provider((ref) { path: Routes.tradingRecords, builder: (context, state) => const TradingRecordsPage(), ), + GoRoute( + path: Routes.helpCenter, + builder: (context, state) => const HelpCenterPage(), + ), + GoRoute( + path: Routes.about, + builder: (context, state) => const AboutPage(), + ), ShellRoute( builder: (context, state, child) => MainShell(child: child), routes: [ diff --git a/frontend/mining-app/lib/core/router/routes.dart b/frontend/mining-app/lib/core/router/routes.dart index 86611159..66f5f027 100644 --- a/frontend/mining-app/lib/core/router/routes.dart +++ b/frontend/mining-app/lib/core/router/routes.dart @@ -23,4 +23,7 @@ class Routes { static const String myTeam = '/my-team'; // 交易记录 static const String tradingRecords = '/trading-records'; + // 其他设置 + static const String helpCenter = '/help-center'; + static const String about = '/about'; } diff --git a/frontend/mining-app/lib/presentation/pages/profile/about_page.dart b/frontend/mining-app/lib/presentation/pages/profile/about_page.dart new file mode 100644 index 00000000..f8bf9650 --- /dev/null +++ b/frontend/mining-app/lib/presentation/pages/profile/about_page.dart @@ -0,0 +1,437 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:go_router/go_router.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +/// 关于我们页面 +class AboutPage extends StatefulWidget { + const AboutPage({super.key}); + + @override + State createState() => _AboutPageState(); +} + +class _AboutPageState extends State { + static const Color _orange = Color(0xFFFF6B00); + static const Color _darkText = Color(0xFF1F2937); + static const Color _grayText = Color(0xFF6B7280); + static const Color _bgGray = Color(0xFFF3F4F6); + + String _version = ''; + String _buildNumber = ''; + + @override + void initState() { + super.initState(); + _loadPackageInfo(); + } + + Future _loadPackageInfo() async { + try { + final info = await PackageInfo.fromPlatform(); + setState(() { + _version = info.version; + _buildNumber = info.buildNumber; + }); + } catch (e) { + setState(() { + _version = '1.0.0'; + _buildNumber = '1'; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: _bgGray, + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios, color: _darkText, size: 20), + onPressed: () => context.pop(), + ), + title: const Text( + '关于我们', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: _darkText, + ), + ), + centerTitle: true, + ), + body: SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height: 32), + // Logo和应用名称 + _buildAppHeader(), + const SizedBox(height: 32), + // 应用简介 + _buildIntroSection(), + const SizedBox(height: 16), + // 功能特点 + _buildFeaturesSection(), + const SizedBox(height: 16), + // 联系信息 + _buildContactSection(), + const SizedBox(height: 16), + // 版本信息和法律条款 + _buildLegalSection(), + const SizedBox(height: 24), + // 版权信息 + _buildCopyright(), + const SizedBox(height: 24), + ], + ), + ), + ); + } + + Widget _buildAppHeader() { + return Column( + children: [ + // 应用图标 + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: _orange, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: _orange.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: const Icon( + Icons.eco, + color: Colors.white, + size: 48, + ), + ), + const SizedBox(height: 16), + // 应用名称 + const Text( + '股行', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: _darkText, + ), + ), + const SizedBox(height: 8), + // 版本号 + Text( + 'Version $_version${_buildNumber.isNotEmpty ? ' ($_buildNumber)' : ''}', + style: const TextStyle( + fontSize: 14, + color: _grayText, + ), + ), + ], + ); + } + + Widget _buildIntroSection() { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionTitle('应用简介'), + const SizedBox(height: 12), + Text( + '股行是一款创新的数字资产管理平台,致力于为用户提供便捷、安全的榴莲树认种和积分管理服务。' + '通过认种榴莲树,用户可以获得贡献值,并根据贡献值占比获得每日积分股分配。', + style: TextStyle( + fontSize: 14, + color: _grayText.withOpacity(0.9), + height: 1.6, + ), + ), + ], + ), + ); + } + + Widget _buildFeaturesSection() { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionTitle('核心功能'), + const SizedBox(height: 16), + _buildFeatureItem( + icon: Icons.eco, + title: '榴莲认种', + description: '认种真实榴莲树,获得贡献值奖励', + ), + const SizedBox(height: 12), + _buildFeatureItem( + icon: Icons.trending_up, + title: '每日收益', + description: '根据贡献值占比,每日自动分配积分股', + ), + const SizedBox(height: 12), + _buildFeatureItem( + icon: Icons.swap_horiz, + title: '便捷交易', + description: '支持积分股卖出兑换,积分值自由转账', + ), + const SizedBox(height: 12), + _buildFeatureItem( + icon: Icons.people, + title: '团队收益', + description: '邀请好友,获得团队贡献值奖励', + ), + ], + ), + ); + } + + Widget _buildFeatureItem({ + required IconData icon, + required String title, + required String description, + }) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: _orange.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Icon(icon, color: _orange, size: 22), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: _darkText, + ), + ), + const SizedBox(height: 2), + Text( + description, + style: TextStyle( + fontSize: 12, + color: _grayText.withOpacity(0.9), + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildContactSection() { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionTitle('联系方式'), + const SizedBox(height: 16), + _buildContactItem( + icon: Icons.email_outlined, + label: '客服邮箱', + value: 'support@guhang.com', + onTap: () { + Clipboard.setData(const ClipboardData(text: 'support@guhang.com')); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('邮箱已复制'), + duration: Duration(seconds: 1), + ), + ); + }, + ), + const Divider(height: 24), + _buildContactItem( + icon: Icons.language, + label: '官方网站', + value: 'www.guhang.com', + onTap: () { + // TODO: 打开官网 + }, + ), + ], + ), + ); + } + + Widget _buildContactItem({ + required IconData icon, + required String label, + required String value, + VoidCallback? onTap, + }) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Icon(icon, size: 20, color: _orange), + const SizedBox(width: 12), + Text( + '$label: ', + style: const TextStyle( + fontSize: 14, + color: _grayText, + ), + ), + Expanded( + child: Text( + value, + style: const TextStyle( + fontSize: 14, + color: _darkText, + fontWeight: FontWeight.w500, + ), + ), + ), + if (onTap != null) + const Icon(Icons.chevron_right, size: 20, color: _grayText), + ], + ), + ), + ); + } + + Widget _buildLegalSection() { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionTitle('法律条款'), + const SizedBox(height: 16), + _buildLegalItem( + title: '用户协议', + onTap: () { + // TODO: 跳转用户协议页面 + }, + ), + const Divider(height: 24), + _buildLegalItem( + title: '隐私政策', + onTap: () { + // TODO: 跳转隐私政策页面 + }, + ), + ], + ), + ); + } + + Widget _buildLegalItem({ + required String title, + required VoidCallback onTap, + }) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Expanded( + child: Text( + title, + style: const TextStyle( + fontSize: 14, + color: _darkText, + ), + ), + ), + const Icon(Icons.chevron_right, size: 20, color: _grayText), + ], + ), + ), + ); + } + + Widget _buildSectionTitle(String title) { + return Row( + children: [ + Container( + width: 4, + height: 16, + decoration: BoxDecoration( + color: _orange, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 8), + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: _darkText, + ), + ), + ], + ); + } + + Widget _buildCopyright() { + return Column( + children: [ + Text( + 'Copyright © ${DateTime.now().year} 股行', + style: const TextStyle( + fontSize: 12, + color: _grayText, + ), + ), + const SizedBox(height: 4), + const Text( + 'All Rights Reserved', + style: TextStyle( + fontSize: 12, + color: _grayText, + ), + ), + ], + ); + } +} diff --git a/frontend/mining-app/lib/presentation/pages/profile/help_center_page.dart b/frontend/mining-app/lib/presentation/pages/profile/help_center_page.dart new file mode 100644 index 00000000..e8b13f90 --- /dev/null +++ b/frontend/mining-app/lib/presentation/pages/profile/help_center_page.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +/// 帮助中心页面 +class HelpCenterPage extends StatelessWidget { + const HelpCenterPage({super.key}); + + static const Color _orange = Color(0xFFFF6B00); + static const Color _darkText = Color(0xFF1F2937); + static const Color _grayText = Color(0xFF6B7280); + static const Color _bgGray = Color(0xFFF3F4F6); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: _bgGray, + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios, color: _darkText, size: 20), + onPressed: () => context.pop(), + ), + title: const Text( + '帮助中心', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: _darkText, + ), + ), + centerTitle: true, + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + // 常见问题分类 + _buildSection('常见问题', [ + _FAQItem( + question: '如何认种榴莲树?', + answer: '在首页点击"去认种"按钮,选择认种数量和支付方式,完成支付后即可认种成功。认种成功后,您将获得对应的贡献值。', + ), + _FAQItem( + question: '贡献值是什么?', + answer: '贡献值是您在平台认种榴莲树后获得的一种权益凭证。贡献值越高,您每日可获得的积分股分配越多。贡献值有效期为730天。', + ), + _FAQItem( + question: '如何获得积分股?', + answer: '系统每日会根据您的贡献值占比,自动分配积分股到您的账户。积分股可用于兑换或交易。', + ), + _FAQItem( + question: '积分值和积分股有什么区别?', + answer: '积分股是通过贡献值分配获得的,可用于卖出兑换。积分值是一种通用积分,可以转账给其他用户。两者用途不同,请注意区分。', + ), + ]), + const SizedBox(height: 16), + _buildSection('交易相关', [ + _FAQItem( + question: '如何卖出积分股?', + answer: '进入"兑换"页面,输入要卖出的积分股数量,确认后即可完成卖出。卖出时会扣除10%进入积分股池。', + ), + _FAQItem( + question: '卖出积分股为什么要扣除10%?', + answer: '卖出积分股时,10%会进入积分股池,用于系统生态建设和价值稳定。这是系统规则的一部分。', + ), + _FAQItem( + question: '如何发送积分值给其他用户?', + answer: '进入"资产"页面,点击"发送"按钮,输入对方手机号和转账金额,确认后即可完成转账。注意:积分值转账不可撤销。', + ), + ]), + const SizedBox(height: 16), + _buildSection('账户安全', [ + _FAQItem( + question: '如何修改登录密码?', + answer: '进入"我的"页面,点击"账户安全",可以修改登录密码。建议定期更换密码以确保账户安全。', + ), + _FAQItem( + question: '忘记密码怎么办?', + answer: '在登录页面点击"忘记密码",通过手机号验证后可以重置密码。', + ), + ]), + const SizedBox(height: 16), + _buildSection('团队收益', [ + _FAQItem( + question: '如何邀请好友?', + answer: '您的手机号就是您的邀请码。好友注册时填写您的手机号作为邀请人,即可建立引荐关系。', + ), + _FAQItem( + question: '团队收益如何计算?', + answer: '当您引荐的好友认种榴莲树后,您将获得团队下级贡献值奖励。引荐的用户越多、认种数量越多,您的团队收益越高。', + ), + ]), + const SizedBox(height: 16), + // 联系客服 + _buildContactSection(context), + const SizedBox(height: 24), + ], + ), + ), + ); + } + + Widget _buildSection(String title, List<_FAQItem> items) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Row( + children: [ + Container( + width: 4, + height: 16, + decoration: BoxDecoration( + color: _orange, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 8), + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: _darkText, + ), + ), + ], + ), + ), + ...items.asMap().entries.map((entry) { + final isLast = entry.key == items.length - 1; + return _buildFAQTile(entry.value, showDivider: !isLast); + }), + ], + ), + ); + } + + Widget _buildFAQTile(_FAQItem item, {bool showDivider = true}) { + return Theme( + data: ThemeData( + dividerColor: Colors.transparent, + ), + child: Column( + children: [ + ExpansionTile( + tilePadding: const EdgeInsets.symmetric(horizontal: 16), + childrenPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + title: Text( + item.question, + style: const TextStyle( + fontSize: 14, + color: _darkText, + ), + ), + iconColor: _orange, + collapsedIconColor: _grayText, + children: [ + Text( + item.answer, + style: TextStyle( + fontSize: 13, + color: _grayText.withOpacity(0.9), + height: 1.5, + ), + ), + ], + ), + if (showDivider) + const Divider(height: 1, indent: 16, endIndent: 16), + ], + ), + ); + } + + Widget _buildContactSection(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 4, + height: 16, + decoration: BoxDecoration( + color: _orange, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 8), + const Text( + '联系我们', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: _darkText, + ), + ), + ], + ), + const SizedBox(height: 16), + Text( + '如果您有其他问题,可以通过以下方式联系我们:', + style: TextStyle( + fontSize: 13, + color: _grayText.withOpacity(0.9), + ), + ), + const SizedBox(height: 16), + _buildContactItem( + icon: Icons.email_outlined, + label: '客服邮箱', + value: 'support@guhang.com', + ), + const SizedBox(height: 12), + _buildContactItem( + icon: Icons.access_time, + label: '服务时间', + value: '周一至周五 9:00-18:00', + ), + ], + ), + ); + } + + Widget _buildContactItem({ + required IconData icon, + required String label, + required String value, + }) { + return Row( + children: [ + Icon(icon, size: 20, color: _orange), + const SizedBox(width: 12), + Text( + '$label: ', + style: const TextStyle( + fontSize: 13, + color: _grayText, + ), + ), + Text( + value, + style: const TextStyle( + fontSize: 13, + color: _darkText, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } +} + +class _FAQItem { + final String question; + final String answer; + + _FAQItem({required this.question, required this.answer}); +} diff --git a/frontend/mining-app/lib/presentation/pages/profile/profile_page.dart b/frontend/mining-app/lib/presentation/pages/profile/profile_page.dart index a368cd0c..49ff5753 100644 --- a/frontend/mining-app/lib/presentation/pages/profile/profile_page.dart +++ b/frontend/mining-app/lib/presentation/pages/profile/profile_page.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/router/routes.dart'; import '../../providers/user_providers.dart'; import '../../providers/profile_providers.dart'; +import '../../providers/settings_providers.dart'; import '../../widgets/shimmer_loading.dart'; class ProfilePage extends ConsumerWidget { @@ -78,7 +78,7 @@ class ProfilePage extends ConsumerWidget { const SizedBox(height: 16), // 其他设置 - _buildOtherSettings(context), + _buildOtherSettings(context, ref), const SizedBox(height: 24), @@ -463,7 +463,10 @@ class ProfilePage extends ConsumerWidget { ); } - Widget _buildOtherSettings(BuildContext context) { + Widget _buildOtherSettings(BuildContext context, WidgetRef ref) { + final notificationsEnabled = ref.watch(notificationsEnabledProvider); + final darkModeEnabled = ref.watch(darkModeEnabledProvider); + return Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( @@ -487,24 +490,28 @@ class ProfilePage extends ConsumerWidget { _buildSwitchItem( icon: Icons.notifications_outlined, label: '消息通知', - value: true, - onChanged: (value) {}, + value: notificationsEnabled, + onChanged: (value) { + ref.read(notificationsEnabledProvider.notifier).setEnabled(value); + }, ), _buildSwitchItem( icon: Icons.dark_mode_outlined, label: '深色模式', - value: false, - onChanged: (value) {}, + value: darkModeEnabled, + onChanged: (value) { + ref.read(darkModeEnabledProvider.notifier).setEnabled(value); + }, ), _buildSettingItem( icon: Icons.help_outline, label: '帮助中心', - onTap: () {}, + onTap: () => context.push(Routes.helpCenter), ), _buildSettingItem( icon: Icons.info_outline, label: '关于我们', - onTap: () {}, + onTap: () => context.push(Routes.about), showDivider: false, ), ], diff --git a/frontend/mining-app/lib/presentation/providers/settings_providers.dart b/frontend/mining-app/lib/presentation/providers/settings_providers.dart new file mode 100644 index 00000000..e0c93cf1 --- /dev/null +++ b/frontend/mining-app/lib/presentation/providers/settings_providers.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +// ==================== 设置键名常量 ==================== + +const String _keyNotificationsEnabled = 'settings_notifications_enabled'; +const String _keyDarkModeEnabled = 'settings_dark_mode_enabled'; + +// ==================== 消息通知设置 ==================== + +/// 消息通知开关状态 Provider +final notificationsEnabledProvider = StateNotifierProvider((ref) { + return NotificationsNotifier(); +}); + +class NotificationsNotifier extends StateNotifier { + NotificationsNotifier() : super(true) { + _loadFromStorage(); + } + + Future _loadFromStorage() async { + final prefs = await SharedPreferences.getInstance(); + state = prefs.getBool(_keyNotificationsEnabled) ?? true; + } + + Future toggle() async { + state = !state; + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_keyNotificationsEnabled, state); + } + + Future setEnabled(bool enabled) async { + state = enabled; + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_keyNotificationsEnabled, enabled); + } +} + +// ==================== 深色模式设置 ==================== + +/// 深色模式开关状态 Provider +final darkModeEnabledProvider = StateNotifierProvider((ref) { + return DarkModeNotifier(); +}); + +class DarkModeNotifier extends StateNotifier { + DarkModeNotifier() : super(false) { + _loadFromStorage(); + } + + Future _loadFromStorage() async { + final prefs = await SharedPreferences.getInstance(); + state = prefs.getBool(_keyDarkModeEnabled) ?? false; + } + + Future toggle() async { + state = !state; + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_keyDarkModeEnabled, state); + } + + Future setEnabled(bool enabled) async { + state = enabled; + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_keyDarkModeEnabled, enabled); + } +} + +// ==================== 主题模式 Provider ==================== + +/// 应用主题模式 Provider +final themeModeProvider = Provider((ref) { + final isDarkMode = ref.watch(darkModeEnabledProvider); + return isDarkMode ? ThemeMode.dark : ThemeMode.light; +}); diff --git a/frontend/mining-app/pubspec.yaml b/frontend/mining-app/pubspec.yaml index 685865ac..e3395888 100644 --- a/frontend/mining-app/pubspec.yaml +++ b/frontend/mining-app/pubspec.yaml @@ -44,6 +44,7 @@ dependencies: # 其他 intl: ^0.18.0 logger: ^2.0.0 + package_info_plus: ^8.0.0 dev_dependencies: flutter_test: