From eb14cf19575598c0693e40e4a8ad0d00c60c260d Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Mon, 1 Dec 2025 19:59:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A1=86=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/gradle.properties | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 6 +- lib/history.dart | 0 lib/layout/app_navbar.dart | 36 ++ lib/main.dart | 8 +- lib/modesl/health.dart | 30 +- lib/modesl/menu.dart | 27 + lib/pages/history_page.dart | 15 + lib/pages/home_page.dart | 100 +++ lib/pages/record_page.dart | 111 ++++ lib/pages/setting_page.dart | 15 + lib/pages/stats_page.dart | 15 + lib/record.dart | 568 ------------------ lib/setting.dart | 0 lib/stats.dart | 0 lib/widget/dropdown_select.dart | 46 ++ lib/widget/number_input.dart | 131 ++++ lib/widget/record/common.dart | 37 ++ lib/widget/record/goal_progress.dart | 95 +++ lib/widget/record/today_record.dart | 359 +++++++++++ lib/widget/select_input.dart | 188 ++++++ linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 10 + pubspec.lock | 374 +++++++++++- pubspec.yaml | 32 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 28 files changed, 1596 insertions(+), 620 deletions(-) delete mode 100644 lib/history.dart create mode 100644 lib/layout/app_navbar.dart create mode 100644 lib/modesl/menu.dart create mode 100644 lib/pages/history_page.dart create mode 100644 lib/pages/home_page.dart create mode 100644 lib/pages/record_page.dart create mode 100644 lib/pages/setting_page.dart create mode 100644 lib/pages/stats_page.dart delete mode 100644 lib/record.dart delete mode 100644 lib/setting.dart delete mode 100644 lib/stats.dart create mode 100644 lib/widget/dropdown_select.dart create mode 100644 lib/widget/number_input.dart create mode 100644 lib/widget/record/common.dart create mode 100644 lib/widget/record/goal_progress.dart create mode 100644 lib/widget/record/today_record.dart create mode 100644 lib/widget/select_input.dart diff --git a/android/gradle.properties b/android/gradle.properties index f018a61..bfd5523 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1 @@ -org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -android.enableJetifier=true +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 6dcc88d..d6dd6df 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.10.2-all.zip +distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.10.2-all.zip \ No newline at end of file diff --git a/lib/history.dart b/lib/history.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/layout/app_navbar.dart b/lib/layout/app_navbar.dart new file mode 100644 index 0000000..a2236f0 --- /dev/null +++ b/lib/layout/app_navbar.dart @@ -0,0 +1,36 @@ +import 'package:fitnote/modesl/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/main.dart b/lib/main.dart index 5c228c1..b84f1e0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,4 @@ -import 'package:fitnote/record.dart'; +import 'package:fitnote/pages/home_page.dart'; import 'package:flutter/material.dart'; void main() { @@ -11,13 +11,13 @@ class HealthLogApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'HealthLog - 健康记录', + title: '健康记录', theme: ThemeData( fontFamily: 'Inter', scaffoldBackgroundColor: const Color(0xFFF9FAFB), - primaryColor: const Color(0xFF38BDF8), // 主色:天空蓝 + primaryColor: const Color(0xFF38BDF8), ), - home: const RecordPage(), + home: HomePage(), debugShowCheckedModeBanner: false, ); } diff --git a/lib/modesl/health.dart b/lib/modesl/health.dart index 921bd98..2131bed 100644 --- a/lib/modesl/health.dart +++ b/lib/modesl/health.dart @@ -1,7 +1,9 @@ +import 'package:flutter/cupertino.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:json_annotation/json_annotation.dart'; @JsonSerializable() -class Health { +class HealthRecord { @JsonKey(name: 'id') int? id; @@ -9,10 +11,10 @@ class Health { String? sportProject; @JsonKey(name: 'sportDuration') - int? sportDuration; + num? sportDuration; @JsonKey(name: 'weight') - int? weight; + num? weight; @JsonKey(name: 'sleepStartTime') String? sleepStartTime; @@ -21,12 +23,12 @@ class Health { String? sleepEndTime; @JsonKey(name: 'sleepDuration') - int? sleepDuration; + num? sleepDuration; @JsonKey(name: 'date') String date; - - Health({ + + HealthRecord({ this.id, this.sportProject, this.sportDuration, @@ -34,10 +36,22 @@ class Health { this.sleepStartTime, this.sleepEndTime, this.sleepDuration, - required this.date + required this.date, }); // factory Health.fromJson(Map json) => _$HealthFromJson(json); // // Map toJson() => _$HealthToJson(this); -} \ No newline at end of file +} + +enum RecordType { + weight('体重', FontAwesomeIcons.weightScale, Color(0xFFF59E0B)), + sport('运动', FontAwesomeIcons.personRunning, Color(0xFF38BDF8)), + sleep('睡眠', FontAwesomeIcons.moon, Color(0xFF10B981)); + + final String title; + final IconData icon; + final Color color; + + const RecordType(this.title, this.icon, this.color); +} diff --git a/lib/modesl/menu.dart b/lib/modesl/menu.dart new file mode 100644 index 0000000..9f799bf --- /dev/null +++ b/lib/modesl/menu.dart @@ -0,0 +1,27 @@ +import 'package:fitnote/pages/history_page.dart'; +import 'package:fitnote/pages/record_page.dart'; +import 'package:fitnote/pages/setting_page.dart'; +import 'package:fitnote/pages/stats_page.dart'; +import 'package:flutter/material.dart'; + +class PageInfo { + final String title; + final IconData icon; + final Widget page; + + const PageInfo({required this.title, required this.icon, required this.page}); +} + +final List pages = [ + PageInfo(title: '记录', icon: Icons.article, page: RecordPage()), + PageInfo(title: '历史', icon: Icons.history, page: HistoryPage()), + PageInfo(title: '统计', icon: Icons.insert_chart, page: StatsPage()), + PageInfo(title: '设置', icon: Icons.settings, page: SettingPage()), +]; + +final List pageWidgets = pages.map((item) => item.page).toList(); + +final List bottomNavItems = + pages.map((page) { + return BottomNavigationBarItem(icon: Icon(page.icon), label: page.title); + }).toList(); diff --git a/lib/pages/history_page.dart b/lib/pages/history_page.dart new file mode 100644 index 0000000..f332fb3 --- /dev/null +++ b/lib/pages/history_page.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class HistoryPage extends StatefulWidget { + const HistoryPage({super.key}); + + @override + State createState() => _HistoryPageState(); +} + +class _HistoryPageState extends State { + @override + Widget build(BuildContext context) { + return Center(child: Text('历史记录')); + } +} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart new file mode 100644 index 0000000..38642e9 --- /dev/null +++ b/lib/pages/home_page.dart @@ -0,0 +1,100 @@ +import 'package:fitnote/layout/app_navbar.dart'; +import 'package:fitnote/modesl/menu.dart'; +import 'package:flutter/material.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _currentPageIndex = 0; + late PageController _pageController; + + @override + void initState() { + super.initState(); + _pageController = PageController(initialPage: _currentPageIndex); + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + Widget _buildLeading() { + return Builder( + builder: + (context) => IconButton( + icon: Icon(Icons.menu, color: Colors.white), + onPressed: () => Scaffold.of(context).openDrawer(), + ), + ); + } + + Widget _buildAppBarTitle() { + return Text( + pages[_currentPageIndex].title, + style: TextStyle(color: Colors.white), + ); + } + + Widget _buildPageView() { + return PageView( + controller: _pageController, + onPageChanged: (index) { + setState(() { + _currentPageIndex = index; + }); + }, + children: pageWidgets, + ); + } + + void _onTapNavbarItem(int index) { + setState(() { + _currentPageIndex = index; + }); + _pageController.animateToPage( + index, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + + void onTapDrawerItem(int index) { + setState(() { + _currentPageIndex = index; + }); + _pageController.animateToPage( + index, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: _buildAppBarTitle(), + backgroundColor: Theme.of(context).colorScheme.primary, + elevation: 0, + leading: _buildLeading(), + ), + // drawer: AppDrawer( + // currentPageIndex: _currentPageIndex, + // onTapDrawerItem: onTapDrawerItem, + // ), + body: Padding(padding: EdgeInsets.all(10), child: _buildPageView()), + bottomNavigationBar: AppNavbar( + currentPageIndex: _currentPageIndex, + onTapNavbarItem: _onTapNavbarItem, + ), + ); + } +} diff --git a/lib/pages/record_page.dart b/lib/pages/record_page.dart new file mode 100644 index 0000000..a67e873 --- /dev/null +++ b/lib/pages/record_page.dart @@ -0,0 +1,111 @@ +import 'package:fitnote/widget/record/goal_progress.dart'; +import 'package:fitnote/widget/record/today_record.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:intl/intl.dart'; + +class RecordPage extends StatefulWidget { + const RecordPage({super.key}); + + @override + State createState() => _RecordPageState(); +} + +class _RecordPageState extends State { + // 格式化当前日期 + String _formatCurrentDate() { + final now = DateTime.now(); + // 格式:2025年11月28日,周五 + return "${DateFormat('yyyy年MM月dd日').format(now)},${_getWeekday(now.weekday)}"; + } + + // 数字星期转中文 + String _getWeekday(int weekday) { + const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; + return weekdays[weekday - 1]; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [GoalProgress(), const SizedBox(height: 24), TodayRecord()], + ), + ); + } + + // 顶部导航栏 + Widget _buildHeader() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + color: Colors.white, + // boxShadow: const [ + // BoxShadow( + // color: Color(0x0A000000), + // blurRadius: 4, + // offset: Offset(0, 2), + // ), + // ], + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '早上好,用户', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF1E293B), + ), + ), + const SizedBox(height: 4), + Text( + '今天是 ${_formatCurrentDate()}', + style: const TextStyle(fontSize: 12, color: Color(0xFF64748B)), + ), + ], + ), + Row( + children: [ + // 通知按钮 + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: const Color(0xFFF1F5F9), + borderRadius: BorderRadius.circular(20), + ), + child: Icon( + FontAwesomeIcons.bell, + color: const Color(0xFF64748B), + size: 18, + ), + ), + // 用户头像 + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: const Color(0xFFE0F2FE), // 主色浅背景 + borderRadius: BorderRadius.circular(20), + ), + child: const Center( + child: Text( + 'U', + style: TextStyle( + color: Color(0xFF38BDF8), + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/setting_page.dart b/lib/pages/setting_page.dart new file mode 100644 index 0000000..a801a25 --- /dev/null +++ b/lib/pages/setting_page.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class SettingPage extends StatefulWidget { + const SettingPage({super.key}); + + @override + State createState() => _SettingPageState(); +} + +class _SettingPageState extends State { + @override + Widget build(BuildContext context) { + return Center(child: Text('设置')); + } +} diff --git a/lib/pages/stats_page.dart b/lib/pages/stats_page.dart new file mode 100644 index 0000000..78ab504 --- /dev/null +++ b/lib/pages/stats_page.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class StatsPage extends StatefulWidget { + const StatsPage({super.key}); + + @override + State createState() => _StatsPageState(); +} + +class _StatsPageState extends State { + @override + Widget build(BuildContext context) { + return Center(child: Text('统计')); + } +} diff --git a/lib/record.dart b/lib/record.dart deleted file mode 100644 index 5ddd540..0000000 --- a/lib/record.dart +++ /dev/null @@ -1,568 +0,0 @@ -import 'package:fitnote/modesl/health.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:getwidget/getwidget.dart'; -import 'package:intl/intl.dart'; - -class RecordPage extends StatefulWidget { - const RecordPage({super.key}); - - @override - State createState() => _RecordPageState(); -} - -class _RecordPageState extends State { - // 格式化当前日期 - String _formatCurrentDate() { - final now = DateTime.now(); - // 格式:2025年11月28日,周五 - return "${DateFormat('yyyy年MM月dd日').format(now)},${_getWeekday(now.weekday)}"; - } - - // 数字星期转中文 - String _getWeekday(int weekday) { - const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; - return weekdays[weekday - 1]; - } - - // 记录项数据(图标、标题、颜色) - final List recordItems = [ - RecordItem( - icon: FontAwesomeIcons.weightScale, - title: '体重', - color: const Color(0xFFF59E0B), // 橙色 - ), - RecordItem( - icon: FontAwesomeIcons.personRunning, - title: '运动', - color: const Color(0xFF38BDF8), // 天空蓝 - ), - RecordItem( - icon: FontAwesomeIcons.moon, - title: '睡眠', - color: const Color(0xFF10B981), // 薄荷绿 - ), - ]; - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Column( - children: [ - // 顶部导航栏 - _buildHeader(), - // 主要内容区(滚动) - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Column( - children: [ - // 本月目标进度卡片 - _buildGoalProgressCard(), - const SizedBox(height: 24), - // 今日记录区 - _buildTodayRecordSection(), - ], - ), - ), - ), - ], - ), - ), - ); - } - - // 顶部导航栏 - Widget _buildHeader() { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - color: Colors.white, - // boxShadow: const [ - // BoxShadow( - // color: Color(0x0A000000), - // blurRadius: 4, - // offset: Offset(0, 2), - // ), - // ], - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - '早上好,用户', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF1E293B), - ), - ), - const SizedBox(height: 4), - Text( - '今天是 ${_formatCurrentDate()}', - style: const TextStyle(fontSize: 12, color: Color(0xFF64748B)), - ), - ], - ), - Row( - children: [ - // 通知按钮 - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: const Color(0xFFF1F5F9), - borderRadius: BorderRadius.circular(20), - ), - child: Icon( - FontAwesomeIcons.bell, - color: const Color(0xFF64748B), - size: 18, - ), - ), - // 用户头像 - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: const Color(0xFFE0F2FE), // 主色浅背景 - borderRadius: BorderRadius.circular(20), - ), - child: const Center( - child: Text( - 'U', - style: TextStyle( - color: Color(0xFF38BDF8), - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - ), - ), - ], - ), - ], - ), - ); - } - - Widget _buildCard({required Widget child}) { - return Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: const Color(0xFF38BDF8).withOpacity(0.08), - blurRadius: 25, - offset: const Offset(0, 10), - ), - BoxShadow( - color: const Color(0xFF38BDF8).withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 8), - ), - ], - ), - child: child, - ); - } - - // 本月目标进度卡片 - Widget _buildGoalProgressCard() { - return _buildCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 卡片标题 - Row( - children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: const Color(0xFFE0F2FE), - borderRadius: BorderRadius.circular(20), - ), - child: const Icon( - FontAwesomeIcons.trophy, - color: Color(0xFF38BDF8), - size: 18, - ), - ), - SizedBox(width: 6), - const Text( - '本月目标进度', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF1E293B), - ), - ), - ], - ), - const SizedBox(height: 28), - // 目标体重 - _buildProgressItem( - title: '目标体重', - current: '65.2', - target: '60', - unit: 'kg', - progress: 0.85, - color: const Color(0xFFF59E0B), - ), - const SizedBox(height: 28), - // 运动总时长目标 - _buildProgressItem( - title: '运动总时长', - current: '450', - target: '600', - unit: '分钟', - progress: 0.75, - color: const Color(0xFF38BDF8), - ), - const SizedBox(height: 28), - // 日均睡眠目标 - _buildProgressItem( - title: '日均睡眠', - current: '7.2', - target: '8', - unit: '小时', - progress: 0.9, - color: const Color(0xFF10B981), - ), - ], - ), - ); - } - - // 单个进度项组件 - Widget _buildProgressItem({ - required String title, - required String current, - required String target, - required String unit, - required double progress, - required Color color, - }) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 标题+进度数值 - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - title, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Color(0xFF475569), - ), - ), - Text( - '$current/$target $unit', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: color, - ), - ), - ], - ), - const SizedBox(height: 8), - GFProgressBar( - animation: true, - lineHeight: 20, - percentage: progress, - progressBarColor: color, - child: Text( - '${progress * 100}%', - textAlign: TextAlign.end, - style: TextStyle(color: Colors.white), - ), - ), - ], - ); - } - - Widget _buildCircleIcon({required IconData icon, required Color color}) { - return Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withAlpha(50), - borderRadius: BorderRadius.circular(20), - ), - child: Icon(icon, color: Theme.of(context).colorScheme.primary, size: 18), - ); - } - - // 今日记录区 - Widget _buildTodayRecordSection() { - return _buildCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 区域标题 - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - _buildCircleIcon( - icon: FontAwesomeIcons.calendarCheck, - color: Color(0xFF10B981), - ), - SizedBox(width: 6), - Text( - '今日记录', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ), - ), - ], - ), - // 日期标签 - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 4, - ), - decoration: BoxDecoration( - color: const Color(0xFFF1F5F9), - borderRadius: BorderRadius.circular(16), - ), - child: Text( - DateFormat('MM月dd日').format(DateTime.now()), - style: const TextStyle( - fontSize: 12, - color: Color(0xFF64748B), - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - const SizedBox(height: 20), - // 记录卡片列表 - ListView.separated( - // 禁用滚动(父布局已为 SingleChildScrollView) - physics: const NeverScrollableScrollPhysics(), - // 固定高度(3个item + 间距:每个item高度≈72,间距16,总高度=72*3 + 16*2 = 248) - shrinkWrap: true, - itemCount: recordItems.length, - separatorBuilder: (context, index) => SizedBox(height: 8), - itemBuilder: (context, index) { - final item = recordItems[index]; - return _buildRecordListTile(item); - }, - ), - ], - ), - ); - } - - // 增强版记录卡片(显示具体记录内容) - Widget _buildRecordListTile(RecordItem item) { - // 模拟已记录数据(实际项目可替换为真实数据) - final healthRecord = Health( - id: 1, - sportProject: '羽毛球', - sleepDuration: 20, - weight: 72, - sleepStartTime: '2025-11-27 23:00', - sleepEndTime: '2025-11-28 24:00', - date: '2025-11-28', - ); - - final recordData = _getMockRecordData(item.title); - - return Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [item.color.withAlpha(10), Colors.white.withAlpha(200)], - ), - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.black.withAlpha(10), - blurRadius: 12, - offset: const Offset(0, 4), - ), - ], - ), - child: ListTile( - // 左侧图标 - leading: Container( - width: 48, - height: 48, - decoration: BoxDecoration( - color: item.color.withAlpha(30), - borderRadius: BorderRadius.circular(24), - ), - child: Icon(item.icon, color: item.color, size: 22), - ), - // 标题+具体记录内容 - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - // 主标题 - Text( - item.title, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: item.color, - ), - ), - const SizedBox(height: 4), - // 具体记录内容(根据类型显示不同布局) - _buildRecordContent(item.title, recordData), - ], - ), - // 右侧按钮(添加/编辑) - trailing: GestureDetector( - onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - '${recordData["hasData"] ? "编辑" : "添加"}${item.title}记录', - ), - ), - ); - }, - child: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: item.color.withAlpha(30), - borderRadius: BorderRadius.circular(20), - ), - child: Icon( - recordData["hasData"] - ? FontAwesomeIcons.pencil - : FontAwesomeIcons.plus, - color: item.color, - size: 18, - ), - ), - ), - // 取消默认波纹效果 - splashColor: Colors.transparent, - // 增加内边距,适配新增内容 - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 12, - ), - ), - ); - } - - // 模拟记录数据(实际项目替换为真实数据来源) - Map _getMockRecordData(String type) { - switch (type) { - case "体重": - return { - "hasData": false, - "weight": 65.2, // kg - "updateTime": "08:30", - }; - case "运动": - return { - "hasData": true, - "sportType": "跑步", - "duration": 45, // 分钟 - "distance": 5.2, // km - "updateTime": "18:45", - }; - case "睡眠": - return { - "hasData": true, - "sleepTime": "23:15", - "wakeTime": "07:30", - "duration": "8小时15分钟", - "updateTime": "今天", - }; - default: - return {"hasData": false}; - } - } - - // 根据类型构建不同的记录内容布局 - Widget _buildRecordContent(String type, Map data) { - // 未记录状态 - if (!data["hasData"]) { - return Text( - "尚未记录今日数据,点击添加", - style: TextStyle(fontSize: 12, color: Colors.grey[500]), - ); - } - - // 已记录状态:根据类型显示不同内容 - switch (type) { - case "体重": - return Row( - children: [ - Text( - "${data["weight"]} kg", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.grey[800], - ), - ), - ], - ); - case "运动": - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "${data["sportType"]} · ${data["duration"]}分钟", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.grey[800], - ), - ), - ], - ); - case "睡眠": - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "入睡 ${data["sleepTime"]} · 起床 ${data["wakeTime"]} · ${data["duration"]}", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.grey[800], - ), - ), - ], - ); - default: - return const SizedBox.shrink(); - } - } -} - -// 记录项模型 -class RecordItem { - final IconData icon; - final String title; - final Color color; - - RecordItem({required this.icon, required this.title, required this.color}); -} diff --git a/lib/setting.dart b/lib/setting.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/stats.dart b/lib/stats.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/widget/dropdown_select.dart b/lib/widget/dropdown_select.dart new file mode 100644 index 0000000..ccd8cc9 --- /dev/null +++ b/lib/widget/dropdown_select.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:getwidget/getwidget.dart'; + +class DropdownSelect extends StatelessWidget { + final String value; + final ValueChanged onChanged; + final List items; + + const DropdownSelect({ + Key? key, + required this.value, + required this.onChanged, + required this.items, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return Container( + width: 150, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + border: Border.all(color: colors.primary, width: 1.5), + ), + child: DropdownButtonHideUnderline( + child: GFDropdown( + itemHeight: 32, + padding: const EdgeInsets.all(0), + borderRadius: BorderRadius.circular(8), + value: value, + onChanged: onChanged, + items: + items + .map( + (value) => DropdownMenuItem( + value: value, + child: Text(value, style: const TextStyle(fontSize: 16)), + ), + ) + .toList(), + ), + ), + ); + } +} diff --git a/lib/widget/number_input.dart b/lib/widget/number_input.dart new file mode 100644 index 0000000..88da71b --- /dev/null +++ b/lib/widget/number_input.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; + +class NumberInput extends StatefulWidget { + final num? value; + final ValueChanged? onChanged; + final double min; + final double max; + final double step; + final int precision; + final String unit; + + const NumberInput({ + Key? key, + this.value, + this.onChanged, + this.min = double.negativeInfinity, + this.max = double.infinity, + this.step = 1, + this.precision = 2, + this.unit = '' + }) : super(key: key); + + @override + State createState() => _NumberInputState(); +} + +class _NumberInputState extends State { + late TextEditingController _controller; + late num _currentValue; + + final Color disabledColor = Colors.grey.shade400; + + @override + void initState() { + super.initState(); + _currentValue = widget.value ?? 0.0; + _controller = TextEditingController(text: _formatValue(_currentValue)); + } + + @override + void didUpdateWidget(NumberInput oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.value != oldWidget.value && widget.value != _currentValue) { + _currentValue = widget.value ?? 0.0; + _controller.text = _formatValue(_currentValue); + } + } + + String _formatValue(num value) { + if (widget.precision == 0) return value.toInt().toString(); + return value.toStringAsFixed(widget.precision); + } + + void _handleValueChange(num newValue) { + num clampedValue = newValue.clamp(widget.min, widget.max); + if (widget.precision > 0) { + clampedValue = double.parse( + clampedValue.toStringAsFixed(widget.precision), + ); + } + + setState(() { + _currentValue = clampedValue; + _controller.text = _formatValue(clampedValue); + }); + widget.onChanged?.call(clampedValue); + } + + Widget _buildControlButton({ + required IconData icon, + required VoidCallback? onPressed, + required bool disabled, + }) { + final colors = Theme.of(context).colorScheme; + + return Container( + width: 32, + height: 32, + child: Material( + color: disabled ? disabledColor : colors.primary.withAlpha(100), + child: InkWell( + onTap: disabled ? null : onPressed, + borderRadius: BorderRadius.circular(4), + child: Icon(icon, size: 18, color: colors.onSurface), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return Container( + width: 150, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + border: Border.all(color: colors.primary, width: 1.5), + ), + child: IntrinsicWidth( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildControlButton( + icon: Icons.remove, + onPressed: () => _handleValueChange(_currentValue - widget.step), + disabled: _currentValue <= widget.min, + ), + Expanded( + child: Text( + '${_currentValue} ${widget.unit}', + textAlign: TextAlign.center, + ), + ), + _buildControlButton( + icon: Icons.add, + onPressed: () => _handleValueChange(_currentValue + widget.step), + disabled: _currentValue >= widget.max, + ), + ], + ), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} diff --git a/lib/widget/record/common.dart b/lib/widget/record/common.dart new file mode 100644 index 0000000..4e8bbe2 --- /dev/null +++ b/lib/widget/record/common.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +Widget buildCardTitle({ + required BuildContext context, + required String title, + required IconData icon, +}) { + return Row( + children: [ + buildCircleIcon(context: context, icon: icon), + SizedBox(width: 6), + Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ); +} + +Widget buildCircleIcon({ + required BuildContext context, + required IconData icon, +}) { + return Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withAlpha(50), + borderRadius: BorderRadius.circular(20), + ), + child: Icon(icon, color: Theme.of(context).colorScheme.primary, size: 18), + ); +} diff --git a/lib/widget/record/goal_progress.dart b/lib/widget/record/goal_progress.dart new file mode 100644 index 0000000..2ef068f --- /dev/null +++ b/lib/widget/record/goal_progress.dart @@ -0,0 +1,95 @@ +import 'package:fitnote/modesl/health.dart'; +import 'package:fitnote/widget/record/common.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_common/widget/common_widget.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:getwidget/getwidget.dart'; + +class GoalProgress extends StatefulWidget { + const GoalProgress({super.key}); + + @override + State createState() => _GoalProgressState(); +} + +class _GoalProgressState extends State { + @override + Widget build(BuildContext context) { + return CommonCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildCardTitle( + context: context, + title: '本月目标进度', + icon: FontAwesomeIcons.trophy, + ), + const SizedBox(height: 28), + _buildProgressItem( + title: '目标体重', + current: '65.2', + target: '60', + unit: 'kg', + progress: 0.85, + color: RecordType.weight.color, + ), + const SizedBox(height: 28), + _buildProgressItem( + title: '运动总时长', + current: '450', + target: '600', + unit: '分钟', + progress: 0.75, + color: RecordType.sport.color, + ), + const SizedBox(height: 28), + _buildProgressItem( + title: '日均睡眠', + current: '7.2', + target: '8', + unit: '小时', + progress: 0.9, + color: RecordType.sleep.color, + ), + ], + ), + ); + } + + Widget _buildProgressItem({ + required String title, + required String current, + required String target, + required String unit, + required double progress, + required Color color, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title), + Text( + '$current/$target $unit', + style: TextStyle(fontWeight: FontWeight.w500, color: color), + ), + ], + ), + const SizedBox(height: 8), + GFProgressBar( + animation: true, + lineHeight: 20, + percentage: progress, + progressBarColor: color, + child: Text( + '${progress * 100}%', + textAlign: TextAlign.end, + style: TextStyle(color: Colors.white), + ), + ), + ], + ); + } +} diff --git a/lib/widget/record/today_record.dart b/lib/widget/record/today_record.dart new file mode 100644 index 0000000..72f55b5 --- /dev/null +++ b/lib/widget/record/today_record.dart @@ -0,0 +1,359 @@ +import 'package:fitnote/modesl/health.dart'; +import 'package:fitnote/widget/dropdown_select.dart'; +import 'package:fitnote/widget/number_input.dart'; +import 'package:fitnote/widget/record/common.dart'; +import 'package:fitnote/widget/select_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_common/widget/common_widget.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:intl/intl.dart'; +import 'package:numberpicker/numberpicker.dart'; + +class TodayRecord extends StatefulWidget { + const TodayRecord({super.key}); + + @override + State createState() => _TodayRecordState(); +} + +class _TodayRecordState extends State { + final record = HealthRecord( + id: 1, + sportProject: '羽毛球', + sportDuration: 20, + sleepStartTime: '23:00', + sleepEndTime: '24:00', + sleepDuration: 60, + weight: 0, + date: '2025-11-28', + ); + + final recordForm = HealthRecord( + id: 1, + sportProject: '羽毛球', + sportDuration: 20, + sleepStartTime: '23:00', + sleepEndTime: '24:00', + sleepDuration: 60, + weight: 70, + date: '2025-11-28', + ); + + @override + Widget build(BuildContext context) { + return CommonCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + buildCardTitle( + context: context, + title: '今日记录', + icon: FontAwesomeIcons.calendarCheck, + ), + _buildTodayTag(), + ], + ), + const SizedBox(height: 20), + ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: RecordType.values.length, + separatorBuilder: (context, index) => SizedBox(height: 8), + itemBuilder: (context, index) { + final recordType = RecordType.values[index]; + return _buildRecordListTile(recordType); + }, + ), + ], + ), + ); + } + + Widget _buildTodayTag() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFFF1F5F9), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + DateFormat('MM月dd日').format(DateTime.now()), + style: const TextStyle( + fontSize: 12, + color: Color(0xFF64748B), + fontWeight: FontWeight.w500, + ), + ), + ); + } + + Widget _buildRecordListTile(RecordType type) { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [type.color.withAlpha(10), Colors.white.withAlpha(200)], + ), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(10), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: ListTile( + leading: Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: type.color.withAlpha(30), + borderRadius: BorderRadius.circular(24), + ), + child: Icon(type.icon, color: type.color, size: 22), + ), + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + type.title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: type.color, + ), + ), + const SizedBox(height: 4), + _buildRecordContent(type), + ], + ), + trailing: GestureDetector( + onTap: () => onTapRecord(type), + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: type.color.withAlpha(30), + borderRadius: BorderRadius.circular(20), + ), + child: Icon( + _checkHasRecordValue(type) + ? FontAwesomeIcons.pencil + : FontAwesomeIcons.plus, + color: type.color, + size: 18, + ), + ), + ), + splashColor: Colors.transparent, + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12, + ), + ), + ); + } + + bool _checkHasRecordValue(RecordType type) { + switch (type) { + case RecordType.weight: + return record.weight != null; + case RecordType.sport: + return record.sportProject != null; + case RecordType.sleep: + return record.sleepStartTime != null; + } + } + + Widget _buildDialogButton(RecordType type) { + final colors = Theme.of(context).colorScheme; + + return Row( + children: [ + Expanded( + child: GFButton( + onPressed: () => Navigator.of(context).pop(), + color: colors.secondary.withAlpha(100), + text: "取消", + ), + ), + const SizedBox(width: 12), + Expanded( + child: GFButton(onPressed: () {}, color: colors.primary, text: "确定"), + ), + ], + ); + } + + // void _saveRecordData(RecordType type) { + // setState(() { + // switch (type) { + // case RecordType.weight: + // record.weight = _dialogWeight; + // break; + // case RecordType.sport: + // record.sportProject = _dialogSportProject; + // record.sportDuration = _dialogSportDuration.toInt(); + // break; + // case RecordType.sleep: + // // 处理睡眠数据 + // break; + // } + // }); + // } + + Widget _buildDialogTitle(String title) { + return Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), + ); + } + + Widget _buildFiledTitle(String title) { + return Text(title, style: TextStyle(fontSize: 16)); + } + + Widget _buildDialogContent(RecordType type) { + final colors = Theme.of(context).colorScheme; + + switch (type) { + case RecordType.weight: + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildDialogTitle('记录体重'), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildFiledTitle('体重:'), + SizedBox(width: 10), + NumberInput( + value: recordForm.weight!, + min: 0, + max: 100, + step: 0.1, + unit: 'Kg', + onChanged: + (value) => setState(() => recordForm.weight = value!), + ), + ], + ), + SizedBox(height: 20), + _buildDialogButton(type), + ], + ); + case RecordType.sport: + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildDialogTitle('记录运动'), + SizedBox(height: 24), + + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildFiledTitle('运动类型:'), + SizedBox(width: 10), + DropdownSelect( + value: recordForm.sportProject!, + items: ['羽毛球', '乒乓球', '篮球', '跑步'], + onChanged: (newValue) { + setState(() { + recordForm.sportProject = newValue!; + }); + }, + ), + ], + ), + SizedBox(height: 20), + + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 运动时长 + _buildFiledTitle('运动时长:'), + SizedBox(width: 10), + NumberInput( + value: recordForm.sportDuration, + min: 5, + max: 240, + step: 5, + unit: '分钟', + onChanged: + (value) => + setState(() => recordForm.sportDuration = value!), + ), + ], + ), + SizedBox(height: 24), + _buildDialogButton(type), + ], + ); + case RecordType.sleep: + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildFiledTitle('记录睡眠'), + SizedBox(height: 20), + // 这里添加睡眠相关的输入控件 + SizedBox(height: 20), + _buildDialogButton(type), + ], + ); + } + } + + void onTapRecord(RecordType type) { + showDialog( + context: context, + builder: + (context) => Dialog( + child: Padding( + padding: EdgeInsets.all(20), + child: _buildDialogContent(type), + ), + ), + ); + } + + Widget _buildRecordContent(RecordType type) { + if (!_checkHasRecordValue(type)) { + return Text( + "尚未记录今日数据,点击添加", + style: TextStyle(fontSize: 12, color: Colors.grey[500]), + ); + } + + late String title; + switch (type) { + case RecordType.weight: + title = "${record.weight} kg"; + case RecordType.sport: + title = "${record.sportProject} · ${record.sportDuration}分钟"; + case RecordType.sleep: + title = + "入睡 ${record.sleepStartTime} · 起床 ${record.sleepEndTime} · ${record.sleepDuration}分钟"; + } + + return Text( + title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.grey[800], + ), + ); + } +} diff --git a/lib/widget/select_input.dart b/lib/widget/select_input.dart new file mode 100644 index 0000000..d0434a2 --- /dev/null +++ b/lib/widget/select_input.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; + +class SelectInputField extends StatefulWidget { + final List options; + final String? value; + final ValueChanged? onChanged; + final String hintText; + final String labelText; + final bool enabled; + final EdgeInsetsGeometry? padding; + final InputBorder? border; + + const SelectInputField({ + Key? key, + required this.options, + this.value, + this.onChanged, + this.hintText = '请选择或输入', + this.labelText = '', + this.enabled = true, + this.padding, + this.border, + }) : super(key: key); + + @override + State createState() => _SelectInputFieldState(); +} + +class _SelectInputFieldState extends State { + final TextEditingController _controller = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + bool _showDropdown = false; + String? _selectedValue; + + @override + void initState() { + super.initState(); + _selectedValue = widget.value; + _controller.text = widget.value ?? ''; + } + + @override + void didUpdateWidget(SelectInputField oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.value != oldWidget.value) { + _selectedValue = widget.value; + _controller.text = widget.value ?? ''; + } + } + + void _onTextChanged(String text) { + setState(() { + _selectedValue = text.isEmpty ? null : text; + _showDropdown = text.isNotEmpty; + }); + widget.onChanged?.call(text.isEmpty ? null : text); + } + + void _onOptionSelected(String option) { + setState(() { + _selectedValue = option; + _controller.text = option; + _showDropdown = false; + }); + widget.onChanged?.call(option); + _focusNode.unfocus(); + } + + void _onFieldTap() { + setState(() { + _showDropdown = !_showDropdown; + }); + } + + void _onFocusChange(bool hasFocus) { + setState(() { + _showDropdown = hasFocus; + }); + } + + List get _filteredOptions { + if (_controller.text.isEmpty) { + return widget.options; + } + return widget.options + .where((option) => + option.toLowerCase().contains(_controller.text.toLowerCase())) + .toList(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.labelText.isNotEmpty) ...[ + Text( + widget.labelText, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.grey[700], + ), + ), + const SizedBox(height: 8), + ], + Stack( + children: [ + TextField( + controller: _controller, + focusNode: _focusNode, + onChanged: _onTextChanged, + onTap: _onFieldTap, + enabled: widget.enabled, + decoration: InputDecoration( + hintText: widget.hintText, + border: widget.border ?? const OutlineInputBorder(), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 14, + ), + suffixIcon: IconButton( + icon: Icon( + _showDropdown ? Icons.arrow_drop_up : Icons.arrow_drop_down, + color: Colors.grey[600], + ), + onPressed: () { + setState(() { + _showDropdown = !_showDropdown; + }); + if (_showDropdown) { + _focusNode.requestFocus(); + } else { + _focusNode.unfocus(); + } + }, + ), + ), + ), + if (_showDropdown && _filteredOptions.isNotEmpty) + Positioned( + top: 60, + left: 0, + right: 0, + child: _buildDropdownList(), + ), + ], + ), + ], + ); + } + + Widget _buildDropdownList() { + return Material( + elevation: 4, + borderRadius: BorderRadius.circular(8), + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey[300]!), + ), + constraints: const BoxConstraints(maxHeight: 200), + child: ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: _filteredOptions.length, + itemBuilder: (context, index) { + final option = _filteredOptions[index]; + return ListTile( + title: Text(option), + onTap: () => _onOptionSelected(option), + dense: true, + visualDensity: VisualDensity.compact, + ); + }, + ), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } +} \ No newline at end of file diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..92ce211 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#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); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..b7557d4 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + rive_native ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..8c2686f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,16 @@ import FlutterMacOS import Foundation +import file_picker +import flutter_image_compress_macos +import path_provider_foundation +import rive_native +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + RiveNativePlugin.register(with: registry.registrar(forPlugin: "RiveNativePlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 29546f3..4dd4f75 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,6 +201,30 @@ 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: transitive + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" fake_async: dependency: transitive description: @@ -185,6 +233,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.2" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" file: dependency: transitive description: @@ -193,6 +249,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: @@ -206,6 +270,61 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_common: + dependency: "direct main" + description: + path: "../flutter_common" + relative: true + source: path + version: "1.0.0+1" + 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: @@ -214,11 +333,32 @@ 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 source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + 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" font_awesome_flutter: dependency: "direct main" description: @@ -283,6 +423,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.1.2" + infinite_listview: + dependency: transitive + description: + name: infinite_listview + sha256: f6062c1720eb59be553dfa6b89813d3e8dd2f054538445aaa5edaddfa5195ce6 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" intl: dependency: "direct main" description: @@ -355,6 +503,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.1.1" + logger: + dependency: transitive + description: + name: logger + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.6.2" logging: dependency: transitive description: @@ -395,6 +551,30 @@ 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: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + numberpicker: + dependency: "direct main" + description: + name: numberpicker + sha256: "4c129154944b0f6b133e693f8749c3f8bfb67c4d07ef9dcab48b595c22d1f156" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" package_config: dependency: transitive description: @@ -411,6 +591,78 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.19" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + 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: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" pool: dependency: transitive description: @@ -419,6 +671,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.5.2" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.1.5+1" pub_semver: dependency: transitive description: @@ -435,6 +695,78 @@ 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" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.13" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" shelf: dependency: transitive description: @@ -512,6 +844,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" + syncfusion_flutter_charts: + dependency: transitive + description: + name: syncfusion_flutter_charts + sha256: "68fdb029dad34a46e4c9cfad8ad66fe29db7b303bd96849261ab2b23a168d0e8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "30.2.7" + syncfusion_flutter_core: + dependency: transitive + description: + name: syncfusion_flutter_core + sha256: bfd026c0f9822b49ff26fed11cd3334519acb6a6ad4b0c81d9cd18df6af1c4c0 + url: "https://pub.flutter-io.cn" + source: hosted + version: "30.2.7" term_glyph: dependency: transitive description: @@ -592,6 +940,30 @@ 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: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + 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: @@ -602,4 +974,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.7.0 <4.0.0" - flutter: ">=3.27.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index e42a85d..2ad2d05 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,31 +1 @@ -name: fitnote -description: "记录健康生活" -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -version: 1.0.0+1 - -environment: - sdk: ^3.7.0 - -dependencies: - flutter: - sdk: flutter - - cupertino_icons: ^1.0.8 - getwidget: ^7.0.0 - intl: ^0.19.0 - font_awesome_flutter: ^10.7.0 - json_annotation: ^4.9.0 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^5.0.0 - build_runner: ^2.4.5 - json_serializable: ^6.7.1 - - -# The following section is specific to Flutter packages. -flutter: - uses-material-design: true - +name: fitnote description: "记录健康生活" publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: ^3.7.0 dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.8 getwidget: ^7.0.0 intl: ^0.19.0 font_awesome_flutter: ^10.7.0 json_annotation: ^4.9.0 numberpicker: ^2.1.2 flutter_common: path: ..\flutter_common dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 build_runner: ^2.4.5 json_serializable: ^6.7.1 # The following section is specific to Flutter packages. flutter: uses-material-design: true \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..1a3db62 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + RiveNativePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RiveNativePlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..22806d1 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + rive_native ) list(APPEND FLUTTER_FFI_PLUGIN_LIST