feat:重构模块
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:food_hub_app/layout/app_actions.dart';
|
||||
import 'package:food_hub_app/layout/app_drawer.dart';
|
||||
import 'package:food_hub_app/layout/app_navbar.dart';
|
||||
import 'package:food_hub_app/layout/menu.dart';
|
||||
import 'package:food_hub_app/views/profile.dart';
|
||||
import 'package:food_hub_app/views/record.dart';
|
||||
import 'package:food_hub_app/views/stats.dart';
|
||||
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||
import 'package:liquid_glass_widgets/widgets/shared/glass_page.dart';
|
||||
import 'package:liquid_glass_widgets/widgets/surfaces/glass_app_bar.dart';
|
||||
import 'package:liquid_glass_widgets/widgets/surfaces/glass_bottom_bar.dart';
|
||||
import 'package:liquid_glass_widgets/widgets/surfaces/glass_scaffold.dart';
|
||||
|
||||
import 'moment.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
@@ -14,38 +20,52 @@ class HomePage extends StatefulWidget {
|
||||
class _HomePage extends State<HomePage> {
|
||||
int _currentIndex = 0;
|
||||
|
||||
final _pages = [
|
||||
const RecordPage(),
|
||||
const StatsPage(),
|
||||
const MomentPage(),
|
||||
const ProfilePage(),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text('食光集', style: TextStyle(color: Colors.white)),
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
leading: Builder(
|
||||
builder: (context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
color: Colors.white,
|
||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
);
|
||||
},
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return GlassScaffold(
|
||||
statusBarStyle: GlassStatusBarStyle.auto,
|
||||
bottomBar: GlassBottomBar(
|
||||
settings: LiquidGlassSettings(
|
||||
glassColor: colors.surface
|
||||
),
|
||||
actions: [AppActions(pageIndex: _currentIndex)],
|
||||
),
|
||||
// backgroundColor: Color(0xFFF5F5F5),
|
||||
drawer: AppDrawer(),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: tabPages[_currentIndex],
|
||||
),
|
||||
bottomNavigationBar: AppNavbar(
|
||||
currentPageIndex: _currentIndex,
|
||||
onTapNavbarItem: (index) {
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
});
|
||||
},
|
||||
selectedIndex: _currentIndex,
|
||||
onTabSelected: (i) => setState(() => _currentIndex = i),
|
||||
indicatorColor: colors.primary,
|
||||
selectedIconColor: colors.surface,
|
||||
unselectedIconColor: Color(0xFF000000),
|
||||
tabs: const [
|
||||
GlassBottomBarTab(
|
||||
icon: Icon(Icons.home_outlined),
|
||||
activeIcon: Icon(Icons.home),
|
||||
label: '记录',
|
||||
),
|
||||
GlassBottomBarTab(
|
||||
icon: Icon(Icons.pie_chart_outline),
|
||||
activeIcon: Icon(Icons.pie_chart),
|
||||
label: '统计',
|
||||
),
|
||||
GlassBottomBarTab(
|
||||
icon: Icon(Icons.group_outlined),
|
||||
activeIcon: Icon(Icons.group),
|
||||
label: '朋友圈',
|
||||
),
|
||||
GlassBottomBarTab(
|
||||
icon: Icon(Icons.account_circle_outlined),
|
||||
activeIcon: Icon(Icons.account_circle),
|
||||
label: '我的',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _pages[_currentIndex],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class _LoginPage extends State<LoginPage> {
|
||||
handleRememberState();
|
||||
handleTokenState(session.saToken.tokenValue);
|
||||
handleUserInfo(session.userInfo);
|
||||
|
||||
|
||||
LoadingDialog.hide(context);
|
||||
Navigator.pushNamed(context, '/home');
|
||||
} catch (e) {
|
||||
@@ -104,12 +104,12 @@ class _LoginPage extends State<LoginPage> {
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(child: buildLoginForm()),
|
||||
Expanded(child: buildLoginForm(context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 50),
|
||||
child: Column(
|
||||
children: [
|
||||
buildOtherLoginText(),
|
||||
buildOtherLoginText(context),
|
||||
const SizedBox(height: 15),
|
||||
buildOtherMethod(context),
|
||||
const SizedBox(height: 20),
|
||||
@@ -131,29 +131,29 @@ class _LoginPage extends State<LoginPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildLoginForm() {
|
||||
Widget buildLoginForm(BuildContext context) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
children: [
|
||||
const SizedBox(height: kToolbarHeight),
|
||||
buildTitle(),
|
||||
const SizedBox(height: 60),
|
||||
buildUsernameTextField(),
|
||||
buildUsernameTextField(context),
|
||||
const SizedBox(height: 20),
|
||||
buildPasswordTextField(),
|
||||
buildPasswordTextField(context),
|
||||
const SizedBox(height: 10),
|
||||
buildRememberPasswordCheckbox(),
|
||||
buildRememberPasswordCheckbox(context),
|
||||
const SizedBox(height: 20),
|
||||
buildLoginButton(),
|
||||
buildLoginButton(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildUsernameTextField() {
|
||||
Widget buildUsernameTextField(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
formLabelText(labelText: "账号:"),
|
||||
Text("账号:", style: Theme.of(context).textTheme.bodyLarge),
|
||||
const SizedBox(height: 10),
|
||||
FormBuilderTextField(
|
||||
name: 'username',
|
||||
@@ -171,11 +171,11 @@ class _LoginPage extends State<LoginPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildPasswordTextField() {
|
||||
Widget buildPasswordTextField(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
formLabelText(labelText: "密码:"),
|
||||
Text("密码:", style: Theme.of(context).textTheme.bodyLarge),
|
||||
const SizedBox(height: 10),
|
||||
FormBuilderTextField(
|
||||
name: 'password',
|
||||
@@ -205,7 +205,7 @@ class _LoginPage extends State<LoginPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildRememberPasswordCheckbox() {
|
||||
Widget buildRememberPasswordCheckbox(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
@@ -218,12 +218,12 @@ class _LoginPage extends State<LoginPage> {
|
||||
});
|
||||
},
|
||||
),
|
||||
const Text('记住密码', style: TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
Text('记住密码', style: Theme.of(context).textTheme.bodyMedium),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildLoginButton() {
|
||||
Widget buildLoginButton(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 45,
|
||||
width: double.infinity,
|
||||
@@ -243,23 +243,20 @@ class _LoginPage extends State<LoginPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildOtherLoginText() {
|
||||
Widget buildOtherLoginText(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(child: Divider(color: Colors.grey[300], thickness: 1)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'其他方式登录',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 14),
|
||||
),
|
||||
child: Text('其他方式登录', style: Theme.of(context).textTheme.bodyMedium),
|
||||
),
|
||||
Expanded(child: Divider(color: Colors.grey[300], thickness: 1)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildOtherMethod(context) {
|
||||
Widget buildOtherMethod(BuildContext context) {
|
||||
return OverflowBar(
|
||||
alignment: MainAxisAlignment.center,
|
||||
children:
|
||||
@@ -292,16 +289,19 @@ class _LoginPage extends State<LoginPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildRegisterText(context) {
|
||||
Widget buildRegisterText(BuildContext context) {
|
||||
return Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('没有账号?', style: TextStyle(fontSize: 18)),
|
||||
Text('没有账号?', style: Theme.of(context).textTheme.bodyMedium),
|
||||
GestureDetector(
|
||||
child: const Text(
|
||||
child: Text(
|
||||
'点击注册',
|
||||
style: TextStyle(fontSize: 18, color: Colors.green),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
print("点击注册");
|
||||
|
||||
@@ -59,8 +59,8 @@ class _MomentPageState extends State<MomentPage> {
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
// 当滚动距离超过300时显示返回顶部按钮
|
||||
if (_scrollController.offset > 300) {
|
||||
// 当滚动距离超过时显示返回顶部按钮
|
||||
if (_scrollController.offset > 100) {
|
||||
if (!_showScrollToTop) {
|
||||
setState(() {
|
||||
_showScrollToTop = true;
|
||||
@@ -99,26 +99,38 @@ class _MomentPageState extends State<MomentPage> {
|
||||
}
|
||||
|
||||
// 有数据时显示列表
|
||||
return Stack(
|
||||
children: [
|
||||
buildEasyRefresh(
|
||||
freshController: _freshController,
|
||||
onRefresh: _onRefresh,
|
||||
onLoad: _onLoad,
|
||||
body: Stack(
|
||||
children: [
|
||||
if (provider.isLoading)
|
||||
buildLoadingIndicator()
|
||||
else
|
||||
_buildMomentList(provider),
|
||||
],
|
||||
),
|
||||
),
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
title: Text('朋友圈', style: Theme.of(context).textTheme.titleLarge),
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsetsGeometry.all(10),
|
||||
child: Stack(
|
||||
children: [
|
||||
buildEasyRefresh(
|
||||
freshController: _freshController,
|
||||
onRefresh: _onRefresh,
|
||||
onLoad: _onLoad,
|
||||
body: Stack(
|
||||
children: [
|
||||
if (provider.isLoading)
|
||||
buildLoadingIndicator()
|
||||
else
|
||||
_buildMomentList(provider),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 返回顶部按钮
|
||||
if (_showScrollToTop)
|
||||
buildScrollToTop(context: context, scrollToTop: _scrollToTop),
|
||||
],
|
||||
// 返回顶部按钮
|
||||
if (_showScrollToTop)
|
||||
buildScrollToTop(context: context, scrollToTop: _scrollToTop),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_common/utils/log_utils.dart';
|
||||
import 'package:flutter_common/utils/minio_utils.dart';
|
||||
import 'package:flutter_common/utils/toast_util.dart';
|
||||
import 'package:flutter_common/widget/common_widget.dart';
|
||||
import 'package:flutter_common/widget/dialog_widget.dart';
|
||||
import 'package:flutter_common/widget/loading_widget.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:food_hub_app/config/app_config.dart';
|
||||
import 'package:food_hub_app/provider/food_provider.dart';
|
||||
import 'package:food_hub_app/utils/minio_utils.dart';
|
||||
import 'package:food_hub_app/widgets/common/form.dart';
|
||||
import 'package:food_hub_app/widgets/common/image.dart';
|
||||
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||
import 'package:liquid_glass_widgets/widgets/containers/glass_card.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MomentFormPage extends StatefulWidget {
|
||||
const MomentFormPage({super.key});
|
||||
class MomentForm extends StatefulWidget {
|
||||
const MomentForm({super.key});
|
||||
|
||||
@override
|
||||
State<MomentFormPage> createState() => _MomentFormPageState();
|
||||
State<MomentForm> createState() => _MomentFormState();
|
||||
}
|
||||
|
||||
class _MomentFormPageState extends State<MomentFormPage> {
|
||||
class _MomentFormState extends State<MomentForm> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
final _focusNode = FocusNode();
|
||||
static const String _contentField = 'content';
|
||||
@@ -34,8 +35,8 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
||||
name: _contentField,
|
||||
initialValue: provider.momentFormItem.content,
|
||||
focusNode: _focusNode,
|
||||
maxLines: 5,
|
||||
minLines: 3,
|
||||
maxLines: 8,
|
||||
minLines: 5,
|
||||
maxLength: maxContentLength,
|
||||
buildCounter: (
|
||||
context, {
|
||||
@@ -75,10 +76,10 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
childAspectRatio: 4/3,
|
||||
childAspectRatio: 4 / 3,
|
||||
),
|
||||
itemCount: totalItems,
|
||||
itemBuilder: (context, index) {
|
||||
@@ -111,7 +112,11 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
||||
_buildContentField(provider),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
buildFormLabel(context: context, text: '上传图片(最多9张)', isRequired: false),
|
||||
buildFormLabel(
|
||||
context: context,
|
||||
text: '上传图片(最多9张)',
|
||||
isRequired: false,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildImageField(provider),
|
||||
const SizedBox(height: 8),
|
||||
@@ -146,24 +151,27 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
||||
if (result != null) {
|
||||
// 限制选择数量
|
||||
final filesToUpload = result.files.take(remainingCount).toList();
|
||||
|
||||
LoadingDialog.show(context, message: '上传中');
|
||||
|
||||
try {
|
||||
final newImageUrls = <String>[];
|
||||
// 逐个上传图片
|
||||
for (final file in filesToUpload) {
|
||||
final fileName = await MinIOHelper().uploadFile(
|
||||
bucketName: AppConfig.bucketName,
|
||||
file: file,
|
||||
);
|
||||
logger.i('图片名称:$fileName');
|
||||
newImageUrls.add(fileName);
|
||||
}
|
||||
|
||||
final newImageUrls = <String>[];
|
||||
|
||||
// 逐个上传图片
|
||||
for (final file in filesToUpload) {
|
||||
final fileName = await MinIOHelper().uploadFile(
|
||||
bucketName: AppConfig.bucketName,
|
||||
file: file,
|
||||
);
|
||||
logger.i('图片名称:$fileName');
|
||||
newImageUrls.add(fileName);
|
||||
// 更新图片列表
|
||||
provider.addMomentFormImage(newImageUrls);
|
||||
} catch (e) {
|
||||
ToastUtil.error(e.toString());
|
||||
} finally {
|
||||
LoadingDialog.hide(context);
|
||||
}
|
||||
|
||||
// 更新图片列表
|
||||
LoadingDialog.hide(context);
|
||||
provider.addMomentFormImage(newImageUrls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +181,7 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
||||
}
|
||||
|
||||
/// 提交表单
|
||||
void _submitForm(FoodProvider provider) async{
|
||||
void _submitForm(FoodProvider provider) async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
LoadingDialog.show(context, message: '提交中');
|
||||
final result = await provider.handleMoment();
|
||||
@@ -205,24 +213,17 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final provider = context.watch<FoodProvider>();
|
||||
|
||||
final String title = provider.isEditing ? '编辑朋友圈' : '新增朋友圈';
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: colors.primary,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: CommonCard(child: _buildFormBuilder(provider)),
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: GlassCard(
|
||||
useOwnLayer: true,
|
||||
settings: LiquidGlassSettings(
|
||||
glassColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: _buildFormBuilder(provider),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:flutter_common/widget/dialog_widget.dart';
|
||||
import 'package:food_hub_app/provider/app_provider.dart';
|
||||
import 'package:food_hub_app/provider/user_provider.dart';
|
||||
import 'package:food_hub_app/widgets/profile/basic_info.dart';
|
||||
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||
import 'package:liquid_glass_widgets/widgets/containers/glass_card.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ProfilePage extends StatefulWidget {
|
||||
@@ -44,8 +46,13 @@ class ProfilePageState extends State<ProfilePage> {
|
||||
Navigator.pushNamed(context, '/momentUser');
|
||||
}
|
||||
|
||||
Widget _buildFunctionButtons() {
|
||||
return CommonCard(
|
||||
Widget _buildFunctionButtons(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return GlassCard(
|
||||
padding: EdgeInsetsGeometry.symmetric(vertical: 0, horizontal: 10),
|
||||
useOwnLayer: true,
|
||||
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildFunctionButton(
|
||||
@@ -56,13 +63,13 @@ class ProfilePageState extends State<ProfilePage> {
|
||||
// LogUtils.i('点击编辑资料');
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
const Divider(height: 1, thickness: 0.2),
|
||||
_buildFunctionButton(
|
||||
icon: Icons.group_outlined,
|
||||
text: '朋友圈',
|
||||
onTap: _onTapMoment,
|
||||
),
|
||||
const Divider(height: 1),
|
||||
const Divider(height: 1, thickness: 0.2),
|
||||
_buildFunctionButton(
|
||||
icon: Icons.favorite,
|
||||
text: '我的收藏',
|
||||
@@ -71,7 +78,7 @@ class ProfilePageState extends State<ProfilePage> {
|
||||
// LogUtils.i('点击浏览历史');
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
const Divider(height: 1, thickness: 0.2),
|
||||
_buildFunctionButton(
|
||||
icon: Icons.lock,
|
||||
text: '更改密码',
|
||||
@@ -80,11 +87,11 @@ class ProfilePageState extends State<ProfilePage> {
|
||||
// LogUtils.i('点击帮助与反馈');
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
const Divider(height: 1, thickness: 0.2),
|
||||
_buildFunctionButton(icon: Icons.message, text: '反馈意见', onTap: () {}),
|
||||
const Divider(height: 1),
|
||||
const Divider(height: 1, thickness: 0.2),
|
||||
_buildFunctionButton(icon: Icons.refresh, text: '检查更新', onTap: () {}),
|
||||
const Divider(height: 1),
|
||||
const Divider(height: 1, thickness: 0.2),
|
||||
_buildFunctionButton(
|
||||
icon: Icons.exit_to_app,
|
||||
text: '退出登录',
|
||||
@@ -117,13 +124,13 @@ class ProfilePageState extends State<ProfilePage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(UserProvider provider) {
|
||||
Widget _buildContent(UserProvider provider, BuildContext context) {
|
||||
final user = provider.currentUser;
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(width: double.infinity, child: ProfileInfo(user: user)),
|
||||
const SizedBox(height: 16),
|
||||
_buildFunctionButtons(),
|
||||
_buildFunctionButtons(context),
|
||||
const SizedBox(height: 8),
|
||||
_buildVersionInfo(),
|
||||
],
|
||||
@@ -134,15 +141,27 @@ class ProfilePageState extends State<ProfilePage> {
|
||||
Widget build(BuildContext context) {
|
||||
final provider = context.watch<UserProvider>();
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
if (provider.isLoading)
|
||||
buildLoadingIndicator()
|
||||
else if (provider.error.isNotEmpty)
|
||||
Text(provider.error)
|
||||
else
|
||||
_buildContent(provider),
|
||||
],
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
title: Text('我的', style: Theme.of(context).textTheme.titleLarge),
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsetsGeometry.all(10),
|
||||
child: Stack(
|
||||
children: [
|
||||
if (provider.isLoading)
|
||||
buildLoadingIndicator()
|
||||
else if (provider.error.isNotEmpty)
|
||||
Text(provider.error)
|
||||
else
|
||||
SingleChildScrollView(child: _buildContent(provider, context)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_common/widget/common_widget.dart';
|
||||
import 'package:food_hub_app/provider/food_provider.dart';
|
||||
import 'package:food_hub_app/views/moment_form.dart';
|
||||
import 'package:food_hub_app/views/record_form.dart';
|
||||
import 'package:food_hub_app/widgets/common/easy_refresh.dart';
|
||||
import 'package:food_hub_app/widgets/common/index.dart';
|
||||
import 'package:food_hub_app/widgets/recipe/calendar.dart';
|
||||
import 'package:food_hub_app/widgets/recipe/list.dart';
|
||||
@@ -24,12 +29,68 @@ class RecordPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RecordPageState extends State<RecordPage> {
|
||||
final EasyRefreshController _freshController = EasyRefreshController(
|
||||
controlFinishRefresh: true,
|
||||
controlFinishLoad: true,
|
||||
);
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
bool _showScrollToTop = false;
|
||||
final List<Widget> _tabPages = [
|
||||
RecipeList(),
|
||||
RecipeCalendar(),
|
||||
RecipeTimeline(),
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(_onScroll);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.removeListener(_onScroll);
|
||||
_freshController.dispose();
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
Future<void> _onRefresh() async {
|
||||
_freshController.finishRefresh();
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
Future<void> _onLoad() async {
|
||||
final provider = context.read<FoodProvider>();
|
||||
if (provider.hasMore) {
|
||||
await provider.refreshFoodStats();
|
||||
_freshController.finishLoad(IndicatorResult.success);
|
||||
} else {
|
||||
_freshController.finishLoad(IndicatorResult.noMore);
|
||||
}
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
// 当滚动距离超过时显示返回顶部按钮
|
||||
if (_scrollController.offset > 100) {
|
||||
if (!_showScrollToTop) {
|
||||
setState(() {
|
||||
_showScrollToTop = true;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (_showScrollToTop) {
|
||||
setState(() {
|
||||
_showScrollToTop = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动到顶部
|
||||
void _scrollToTop() => scrollToTopAnimateTo(_scrollController);
|
||||
|
||||
Widget _buildTabs({
|
||||
required BuildContext context,
|
||||
required RecordTab currentTab,
|
||||
@@ -52,24 +113,135 @@ class _RecordPageState extends State<RecordPage> {
|
||||
provider.onRecordTabChange();
|
||||
}
|
||||
|
||||
void _handleAddRecord(FoodProvider provider) {
|
||||
provider.resetRecordForm();
|
||||
provider.isEditing = false;
|
||||
Navigator.pop(context);
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
builder: (context) => RecordForm(),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleAddMoment(FoodProvider provider) {
|
||||
provider.resetMomentForm();
|
||||
provider.isEditing = false;
|
||||
Navigator.pop(context);
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
builder: (context) => MomentForm(),
|
||||
);
|
||||
}
|
||||
|
||||
void _onPressAdd(FoodProvider provider) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
builder:
|
||||
(context) => Padding(
|
||||
padding: EdgeInsetsGeometry.symmetric(horizontal: 0, vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Text('请选择操作', style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 10),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.book,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
title: Text(
|
||||
'新增菜谱',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
onTap: () => {},
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.note_add,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
title: Text(
|
||||
'新增记录',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
onTap: () => _handleAddRecord(provider),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.group,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
title: Text(
|
||||
'发布朋友圈',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
onTap: () => _handleAddMoment(provider),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final provider = context.watch<FoodProvider>();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_buildTabs(
|
||||
context: context,
|
||||
currentTab: provider.currentRecordTab,
|
||||
onTabChanged: (tab) => _onTabChange(tab, provider),
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
title: Text('记录', style: Theme.of(context).textTheme.titleLarge),
|
||||
centerTitle: true,
|
||||
leading: Builder(
|
||||
builder:
|
||||
(context) => IconButton(
|
||||
icon: Icon(Icons.menu, color: Theme.of(context).primaryColor),
|
||||
onPressed: () => {},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: IndexedStack(
|
||||
index: provider.currentRecordTab.index,
|
||||
children: _tabPages,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search, color: Theme.of(context).primaryColor),
|
||||
onPressed: () {},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.add, color: Theme.of(context).primaryColor),
|
||||
onPressed: () => _onPressAdd(provider),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildTabs(
|
||||
context: context,
|
||||
currentTab: provider.currentRecordTab,
|
||||
onTabChanged: (tab) => _onTabChange(tab, provider),
|
||||
),
|
||||
Expanded(child: _tabPages[provider.currentRecordTab.index]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_common/utils/minio_utils.dart';
|
||||
import 'package:flutter_common/utils/toast_util.dart';
|
||||
import 'package:flutter_common/widget/common_widget.dart';
|
||||
import 'package:flutter_common/widget/dialog_widget.dart';
|
||||
import 'package:flutter_common/widget/loading_widget.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:food_hub_app/apis/recipe.dart';
|
||||
import 'package:food_hub_app/config/app_config.dart';
|
||||
import 'package:food_hub_app/provider/food_provider.dart';
|
||||
import 'package:food_hub_app/utils/minio_utils.dart';
|
||||
import 'package:food_hub_app/widgets/common/form.dart';
|
||||
import 'package:food_hub_app/widgets/common/image.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class RecordFormPage extends StatefulWidget {
|
||||
const RecordFormPage({super.key});
|
||||
class RecordForm extends StatefulWidget {
|
||||
const RecordForm({super.key});
|
||||
|
||||
@override
|
||||
State<RecordFormPage> createState() => _RecordFormPageState();
|
||||
State<RecordForm> createState() => _RecordFormState();
|
||||
}
|
||||
|
||||
class _RecordFormPageState extends State<RecordFormPage> {
|
||||
class _RecordFormState extends State<RecordForm> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
static const String _nameField = 'name';
|
||||
static const String _dateField = 'date';
|
||||
@@ -49,19 +49,19 @@ class _RecordFormPageState extends State<RecordFormPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
buildFormLabel(context: context, text: '菜谱名称', isRequired: true),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 12),
|
||||
_buildRecipeField(provider),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
buildFormLabel(context: context, text: '完成时间', isRequired: true),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 12),
|
||||
_buildDateField(provider),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
buildFormLabel(context: context, text: '上传图片', isRequired: true),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 12),
|
||||
_buildImageField(provider),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
buildFormButtonGroup(
|
||||
context: context,
|
||||
@@ -254,13 +254,18 @@ class _RecordFormPageState extends State<RecordFormPage> {
|
||||
PlatformFile file = result.files.first;
|
||||
LoadingDialog.show(context, message: '上传中');
|
||||
|
||||
final fileName = await MinIOHelper().uploadFile(
|
||||
bucketName: AppConfig.bucketName,
|
||||
file: file,
|
||||
);
|
||||
|
||||
LoadingDialog.hide(context);
|
||||
provider.updateRecordFormItem(imageUrl: fileName);
|
||||
try {
|
||||
final fileName = await MinIOHelper().uploadFile(
|
||||
bucketName: AppConfig.bucketName,
|
||||
file: file,
|
||||
);
|
||||
ToastUtil.success('上传成功');
|
||||
provider.updateRecordFormItem(imageUrl: fileName);
|
||||
} catch (e) {
|
||||
ToastUtil.error(e.toString());
|
||||
} finally {
|
||||
LoadingDialog.hide(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,24 +331,17 @@ class _RecordFormPageState extends State<RecordFormPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final provider = context.watch<FoodProvider>();
|
||||
|
||||
final String title = provider.isEditing ? '编辑记录' : '新增记录';
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title, style: TextStyle(color: Colors.white)),
|
||||
backgroundColor: colors.primary,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: CommonCard(child: _buildFormBuilder(provider)),
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: GlassCard(
|
||||
useOwnLayer: true,
|
||||
settings: LiquidGlassSettings(
|
||||
glassColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: _buildFormBuilder(provider),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_common/widget/chart.dart';
|
||||
import 'package:flutter_common/widget/common_widget.dart';
|
||||
import 'package:food_hub_app/provider/food_provider.dart';
|
||||
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class StatsPage extends StatefulWidget {
|
||||
@@ -24,7 +25,9 @@ class _StatsPage extends State<StatsPage> {
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildSummaryStats(FoodProvider provider) {
|
||||
Widget _buildSummaryStats(BuildContext context, FoodProvider provider) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return GridView.count(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
@@ -34,42 +37,62 @@ class _StatsPage extends State<StatsPage> {
|
||||
mainAxisSpacing: 8,
|
||||
childAspectRatio: 2,
|
||||
children: [
|
||||
StatsCard(
|
||||
icon: Icons.restaurant_menu,
|
||||
title: '菜谱总数',
|
||||
value: provider.summaryStats.recipeCount.toString(),
|
||||
unit: '个',
|
||||
GlassCard(
|
||||
useOwnLayer: true,
|
||||
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||
child: StatsCard(
|
||||
icon: Icons.restaurant_menu,
|
||||
title: '菜谱总数',
|
||||
value: provider.summaryStats.recipeCount.toString(),
|
||||
unit: '个',
|
||||
),
|
||||
),
|
||||
StatsCard(
|
||||
icon: Icons.grid_view_rounded,
|
||||
title: '菜谱类别',
|
||||
value: provider.summaryStats.categoryCount.toString(),
|
||||
unit: '种',
|
||||
GlassCard(
|
||||
useOwnLayer: true,
|
||||
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||
child: StatsCard(
|
||||
icon: Icons.grid_view_rounded,
|
||||
title: '菜谱类别',
|
||||
value: provider.summaryStats.categoryCount.toString(),
|
||||
unit: '种',
|
||||
),
|
||||
),
|
||||
StatsCard(
|
||||
icon: Icons.flag,
|
||||
title: '做菜次数',
|
||||
value: provider.summaryStats.workCount.toString(),
|
||||
unit: '个',
|
||||
GlassCard(
|
||||
useOwnLayer: true,
|
||||
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||
child: StatsCard(
|
||||
icon: Icons.flag,
|
||||
title: '做菜次数',
|
||||
value: provider.summaryStats.workCount.toString(),
|
||||
unit: '个',
|
||||
),
|
||||
),
|
||||
StatsCard(
|
||||
icon: Icons.show_chart,
|
||||
title: '平均次数',
|
||||
value: provider.averageRecordCount.toStringAsFixed(2),
|
||||
unit: '次/月',
|
||||
GlassCard(
|
||||
useOwnLayer: true,
|
||||
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||
child: StatsCard(
|
||||
icon: Icons.show_chart,
|
||||
title: '平均次数',
|
||||
value: provider.averageRecordCount.toStringAsFixed(2),
|
||||
unit: '次/月',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(FoodProvider provider) {
|
||||
Widget _buildContent(BuildContext context, FoodProvider provider) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_buildSummaryStats(provider),
|
||||
_buildSummaryStats(context, provider),
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: chartHeight,
|
||||
child: CommonCard(
|
||||
child: GlassCard(
|
||||
useOwnLayer: true,
|
||||
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||
child: LineChart(
|
||||
title: '记录统计',
|
||||
xAxisName: '日期',
|
||||
@@ -82,7 +105,9 @@ class _StatsPage extends State<StatsPage> {
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: chartHeight,
|
||||
child: CommonCard(
|
||||
child: GlassCard(
|
||||
useOwnLayer: true,
|
||||
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||
child: PieChart(
|
||||
title: '菜谱统计',
|
||||
unit: '个',
|
||||
@@ -93,7 +118,9 @@ class _StatsPage extends State<StatsPage> {
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: chartHeight,
|
||||
child: CommonCard(
|
||||
child: GlassCard(
|
||||
useOwnLayer: true,
|
||||
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||
child: RankChart(title: '排行榜', data: provider.rankStats, unit: '次'),
|
||||
),
|
||||
),
|
||||
@@ -105,13 +132,28 @@ class _StatsPage extends State<StatsPage> {
|
||||
Widget build(BuildContext context) {
|
||||
final provider = context.watch<FoodProvider>();
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
if (provider.isLoading)
|
||||
buildLoadingIndicator()
|
||||
else
|
||||
SingleChildScrollView(child: _buildContent(provider)),
|
||||
],
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
title: Text('统计', style: Theme.of(context).textTheme.titleLarge),
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsetsGeometry.all(10),
|
||||
child: Stack(
|
||||
children: [
|
||||
if (provider.isLoading)
|
||||
buildLoadingIndicator()
|
||||
else
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(bottom: 90),
|
||||
child: _buildContent(context, provider),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user