feat:更新个人主页模块
This commit is contained in:
@@ -24,6 +24,13 @@ Future<List<Moment>> queryMomentListApi() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Moment>> queryMomentListByUserIdApi(int userId) {
|
||||||
|
return httpUtil.get<List<Moment>>(
|
||||||
|
"/moment/$userId",
|
||||||
|
converter: (data) => convertList<Moment>(data, Moment.fromJson),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<PageMoment> queryMomentByPageApi(int currentPage, int pageSize) {
|
Future<PageMoment> queryMomentByPageApi(int currentPage, int pageSize) {
|
||||||
return httpUtil.get<PageMoment>(
|
return httpUtil.get<PageMoment>(
|
||||||
"/moment/page",
|
"/moment/page",
|
||||||
|
|||||||
12
lib/apis/user.dart
Normal file
12
lib/apis/user.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:flutter_common/utils/http_utils.dart';
|
||||||
|
import 'package:food_hub_app/config/app_config.dart';
|
||||||
|
import 'package:food_hub_app/models/session.dart';
|
||||||
|
|
||||||
|
final httpUtil = HttpUtil(baseUrl: AppConfig.baseApiUrl);
|
||||||
|
|
||||||
|
Future<User> queryUserApi(int number) {
|
||||||
|
return httpUtil.get<User>(
|
||||||
|
"/user/$number",
|
||||||
|
converter: (data) => User.fromJson(data),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ class AppConfig {
|
|||||||
// http://14.103.235.151:81/food-service
|
// http://14.103.235.151:81/food-service
|
||||||
// static const String baseApiUrl = "http://14.103.235.151:81/food-service";
|
// static const String baseApiUrl = "http://14.103.235.151:81/food-service";
|
||||||
// static const String baseApiUrl = "http://192.168.1.3:8100";
|
// static const String baseApiUrl = "http://192.168.1.3:8100";
|
||||||
// static const String baseApiUrl = "https://cxx0822.iepose.cn/food-api";
|
static const String baseApiUrl = "https://cxx0822.iepose.cn/food-api";
|
||||||
static const String baseApiUrl = "http://192.168.1.4:8083";
|
// static const String baseApiUrl = "http://192.168.1.4:8083";
|
||||||
// static const String baseApiUrl = "http://192.168.1.103:8083";
|
// static const String baseApiUrl = "http://192.168.1.103:8083";
|
||||||
static const String rustfsIp = '14.103.235.151';
|
static const String rustfsIp = '14.103.235.151';
|
||||||
static const String rustfsFileUrl = 'http://14.103.235.151:9100';
|
static const String rustfsFileUrl = 'http://14.103.235.151:9100';
|
||||||
|
|||||||
@@ -3,23 +3,15 @@ import 'package:food_hub_app/provider/food_provider.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class AppActions extends StatefulWidget {
|
class AppActions extends StatefulWidget {
|
||||||
const AppActions({super.key});
|
final int pageIndex;
|
||||||
|
|
||||||
|
const AppActions({super.key, required this.pageIndex});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => AppActionsState();
|
State<StatefulWidget> createState() => AppActionsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppActionsState extends State<AppActions> {
|
class AppActionsState extends State<AppActions> {
|
||||||
void _buildBottomSheet(FoodProvider provider) {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
|
||||||
),
|
|
||||||
builder: (context) => _buildBottomSheetBody(provider),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildBottomSheetBody(FoodProvider provider) {
|
Widget _buildBottomSheetBody(FoodProvider provider) {
|
||||||
final colors = Theme.of(context).colorScheme;
|
final colors = Theme.of(context).colorScheme;
|
||||||
final provider = context.watch<FoodProvider>();
|
final provider = context.watch<FoodProvider>();
|
||||||
@@ -33,16 +25,19 @@ class AppActionsState extends State<AppActions> {
|
|||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
if (widget.pageIndex == 0)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(Icons.book, color: colors.primary),
|
leading: Icon(Icons.book, color: colors.primary),
|
||||||
title: const Text('新增菜谱'),
|
title: const Text('新增菜谱'),
|
||||||
onTap: () => _handleAddRecipe(provider),
|
onTap: () => _handleAddRecipe(provider),
|
||||||
),
|
),
|
||||||
|
if (widget.pageIndex == 0)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(Icons.note_add, color: colors.primary),
|
leading: Icon(Icons.note_add, color: colors.primary),
|
||||||
title: const Text('新增记录'),
|
title: const Text('新增记录'),
|
||||||
onTap: () => _handleAddRecord(provider),
|
onTap: () => _handleAddRecord(provider),
|
||||||
),
|
),
|
||||||
|
if (widget.pageIndex == 2)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(Icons.group, color: colors.primary),
|
leading: Icon(Icons.group, color: colors.primary),
|
||||||
title: const Text('发布朋友圈'),
|
title: const Text('发布朋友圈'),
|
||||||
@@ -52,6 +47,22 @@ class AppActionsState extends State<AppActions> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onPressAdd(FoodProvider provider) {
|
||||||
|
if (widget.pageIndex == 0) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
|
),
|
||||||
|
builder: (context) => _buildBottomSheetBody(provider),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.pageIndex == 2) {
|
||||||
|
_handleAddMoment(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _handleAddRecipe(FoodProvider provider) {
|
void _handleAddRecipe(FoodProvider provider) {
|
||||||
// provider.resetRecordForm();
|
// provider.resetRecordForm();
|
||||||
// Navigator.pop(context);
|
// Navigator.pop(context);
|
||||||
@@ -68,7 +79,6 @@ class AppActionsState extends State<AppActions> {
|
|||||||
void _handleAddMoment(FoodProvider provider) {
|
void _handleAddMoment(FoodProvider provider) {
|
||||||
provider.resetMomentForm();
|
provider.resetMomentForm();
|
||||||
provider.isEditing = false;
|
provider.isEditing = false;
|
||||||
Navigator.pop(context);
|
|
||||||
Navigator.pushNamed(context, "/momentForm");
|
Navigator.pushNamed(context, "/momentForm");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,11 +94,10 @@ class AppActionsState extends State<AppActions> {
|
|||||||
// 搜索功能
|
// 搜索功能
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (widget.pageIndex == 0 || widget.pageIndex == 2)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.add, color: Colors.white),
|
icon: Icon(Icons.add, color: Colors.white),
|
||||||
onPressed: () {
|
onPressed: () => _onPressAdd(provider),
|
||||||
_buildBottomSheet(provider);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import 'package:flutter_common/provider/theme_provider.dart';
|
|||||||
import 'package:flutter_common/utils/sp_utils.dart';
|
import 'package:flutter_common/utils/sp_utils.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:food_hub_app/provider/food_provider.dart';
|
import 'package:food_hub_app/provider/food_provider.dart';
|
||||||
|
import 'package:food_hub_app/provider/user_provider.dart';
|
||||||
import 'package:food_hub_app/views/home.dart';
|
import 'package:food_hub_app/views/home.dart';
|
||||||
import 'package:food_hub_app/views/login.dart';
|
import 'package:food_hub_app/views/login.dart';
|
||||||
import 'package:food_hub_app/views/moment.dart';
|
import 'package:food_hub_app/views/moment.dart';
|
||||||
import 'package:food_hub_app/views/moment_form.dart';
|
import 'package:food_hub_app/views/moment_form.dart';
|
||||||
|
import 'package:food_hub_app/views/moment_user.dart';
|
||||||
import 'package:food_hub_app/views/record_form.dart';
|
import 'package:food_hub_app/views/record_form.dart';
|
||||||
import 'package:food_hub_app/views/recipe_detail.dart';
|
import 'package:food_hub_app/views/recipe_detail.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
@@ -23,6 +25,7 @@ void main() async {
|
|||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (context) => FoodProvider()),
|
ChangeNotifierProvider(create: (context) => FoodProvider()),
|
||||||
ChangeNotifierProvider(create: (context) => ThemeProvider()),
|
ChangeNotifierProvider(create: (context) => ThemeProvider()),
|
||||||
|
ChangeNotifierProvider(create: (context) => UserProvider()),
|
||||||
],
|
],
|
||||||
child: MyApp(),
|
child: MyApp(),
|
||||||
),
|
),
|
||||||
@@ -65,7 +68,8 @@ class MyApp extends StatelessWidget {
|
|||||||
'/recordForm': (context) => RecordFormPage(),
|
'/recordForm': (context) => RecordFormPage(),
|
||||||
'/recipeDetail': (context) => RecipeDetailPage(),
|
'/recipeDetail': (context) => RecipeDetailPage(),
|
||||||
'/momentForm': (context) => MomentFormPage(),
|
'/momentForm': (context) => MomentFormPage(),
|
||||||
'/moment': (context) => MomentPage()
|
'/moment': (context) => MomentPage(),
|
||||||
|
'/momentUser': (context) => MomentUserPage()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,14 +30,16 @@ class SaTokenInfo {
|
|||||||
this.tag,
|
this.tag,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory SaTokenInfo.fromJson(Map<String, dynamic> json) => _$SaTokenInfoFromJson(json);
|
factory SaTokenInfo.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SaTokenInfoFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$SaTokenInfoToJson(this);
|
Map<String, dynamic> toJson() => _$SaTokenInfoToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class User {
|
class User {
|
||||||
int? id;
|
int? id;
|
||||||
String? username;
|
String username;
|
||||||
int? gender;
|
int? gender;
|
||||||
String? phoneNumber;
|
String? phoneNumber;
|
||||||
String? email;
|
String? email;
|
||||||
@@ -51,7 +53,7 @@ class User {
|
|||||||
|
|
||||||
User({
|
User({
|
||||||
this.id,
|
this.id,
|
||||||
this.username,
|
required this.username,
|
||||||
this.gender,
|
this.gender,
|
||||||
this.phoneNumber,
|
this.phoneNumber,
|
||||||
this.email,
|
this.email,
|
||||||
@@ -65,7 +67,24 @@ class User {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
|
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$UserToJson(this);
|
Map<String, dynamic> toJson() => _$UserToJson(this);
|
||||||
|
|
||||||
|
static User getEmpty() {
|
||||||
|
return User(
|
||||||
|
username: '',
|
||||||
|
gender: 0,
|
||||||
|
phoneNumber: '',
|
||||||
|
email: '',
|
||||||
|
birthDate: null,
|
||||||
|
avatar: '',
|
||||||
|
area: [],
|
||||||
|
address: '',
|
||||||
|
job: '',
|
||||||
|
tags: [],
|
||||||
|
description: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
@@ -75,6 +94,8 @@ class Session {
|
|||||||
|
|
||||||
Session({required this.saToken, required this.userInfo});
|
Session({required this.saToken, required this.userInfo});
|
||||||
|
|
||||||
factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json);
|
factory Session.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SessionFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$SessionToJson(this);
|
Map<String, dynamic> toJson() => _$SessionToJson(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Map<String, dynamic> _$SaTokenInfoToJson(SaTokenInfo instance) =>
|
|||||||
|
|
||||||
User _$UserFromJson(Map<String, dynamic> json) => User(
|
User _$UserFromJson(Map<String, dynamic> json) => User(
|
||||||
id: (json['id'] as num?)?.toInt(),
|
id: (json['id'] as num?)?.toInt(),
|
||||||
username: json['username'] as String?,
|
username: json['username'] as String,
|
||||||
gender: (json['gender'] as num?)?.toInt(),
|
gender: (json['gender'] as num?)?.toInt(),
|
||||||
phoneNumber: json['phoneNumber'] as String?,
|
phoneNumber: json['phoneNumber'] as String?,
|
||||||
email: json['email'] as String?,
|
email: json['email'] as String?,
|
||||||
|
|||||||
@@ -306,6 +306,26 @@ class FoodProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> queryMomentByUserId(int userId) async {
|
||||||
|
if (isLoading) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
isLoading = true;
|
||||||
|
error = null;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
final result = await queryMomentListByUserIdApi(userId);
|
||||||
|
momentList = result;
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
error = '加载数据失败: $e';
|
||||||
|
debugPrint('加载数据失败: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loadMoreMomentList() async {
|
Future<void> loadMoreMomentList() async {
|
||||||
if (hasMore && !isLoading) {
|
if (hasMore && !isLoading) {
|
||||||
await queryMomentByPage(isRefresh: false);
|
await queryMomentByPage(isRefresh: false);
|
||||||
|
|||||||
28
lib/provider/user_provider.dart
Normal file
28
lib/provider/user_provider.dart
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:food_hub_app/apis/user.dart';
|
||||||
|
import 'package:food_hub_app/models/session.dart';
|
||||||
|
|
||||||
|
class UserProvider with ChangeNotifier {
|
||||||
|
late User currentUser = User.getEmpty();
|
||||||
|
bool isLoading = false;
|
||||||
|
String error = '';
|
||||||
|
|
||||||
|
Future<void> refreshUser(int id) async {
|
||||||
|
if (isLoading) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
isLoading = true;
|
||||||
|
error = '';
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
final result = await queryUserApi(id);
|
||||||
|
currentUser = result;
|
||||||
|
} catch (e) {
|
||||||
|
error = '加载数据失败: $e';
|
||||||
|
debugPrint('加载数据失败: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ class _HomePage extends State<HomePage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [AppActions()],
|
actions: [AppActions(pageIndex: _currentIndex)],
|
||||||
),
|
),
|
||||||
// backgroundColor: Color(0xFFF5F5F5),
|
// backgroundColor: Color(0xFFF5F5F5),
|
||||||
drawer: AppDrawer(),
|
drawer: AppDrawer(),
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
SPUtil.set('token', token);
|
SPUtil.set('token', token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleUserInfo(User user) {
|
||||||
|
SPUtil.set('userId', user.id);
|
||||||
|
}
|
||||||
|
|
||||||
void loginClick() async {
|
void loginClick() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
@@ -78,6 +82,7 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
ToastUtil.success('登录成功');
|
ToastUtil.success('登录成功');
|
||||||
handleRememberState();
|
handleRememberState();
|
||||||
handleTokenState(session.saToken.tokenValue);
|
handleTokenState(session.saToken.tokenValue);
|
||||||
|
handleUserInfo(session.userInfo);
|
||||||
|
|
||||||
LoadingDialog.hide(context);
|
LoadingDialog.hide(context);
|
||||||
Navigator.pushNamed(context, '/home');
|
Navigator.pushNamed(context, '/home');
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class _MomentPageState extends State<MomentPage> {
|
|||||||
itemCount: provider.momentList.length,
|
itemCount: provider.momentList.length,
|
||||||
separatorBuilder: (context, index) => SizedBox(height: 8),
|
separatorBuilder: (context, index) => SizedBox(height: 8),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return MomentCard(moment: provider.momentList[index]);
|
return MomentCard(moment: provider.momentList[index], isUser: false);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
|||||||
|
|
||||||
return FormBuilderTextField(
|
return FormBuilderTextField(
|
||||||
name: _contentField,
|
name: _contentField,
|
||||||
|
initialValue: provider.momentFormItem.content,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
maxLines: 5,
|
maxLines: 5,
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
@@ -110,7 +111,7 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
|||||||
_buildContentField(provider),
|
_buildContentField(provider),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
buildFormLabel(context: context, text: '上传图片', isRequired: false),
|
buildFormLabel(context: context, text: '上传图片(最多9张)', isRequired: false),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildImageField(provider),
|
_buildImageField(provider),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -218,7 +219,6 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
|||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: const Color(0xFFF5F5F5),
|
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
|
|||||||
64
lib/views/moment_user.dart
Normal file
64
lib/views/moment_user.dart
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_common/utils/sp_utils.dart';
|
||||||
|
import 'package:flutter_common/widget/common_widget.dart';
|
||||||
|
import 'package:food_hub_app/provider/food_provider.dart';
|
||||||
|
import 'package:food_hub_app/widgets/moment/card.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class MomentUserPage extends StatefulWidget {
|
||||||
|
const MomentUserPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MomentUserPage> createState() => _MomentUserPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MomentUserPageState extends State<MomentUserPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// 初始化加载数据
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context.read<FoodProvider>().queryMomentByUserId(SPUtil.getInt('userId'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMomentList(FoodProvider provider) {
|
||||||
|
return ListView.separated(
|
||||||
|
itemCount: provider.momentList.length,
|
||||||
|
separatorBuilder: (context, index) => SizedBox(height: 8),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return MomentCard(moment: provider.momentList[index], isUser: true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final provider = context.watch<FoodProvider>();
|
||||||
|
|
||||||
|
// 空状态显示
|
||||||
|
if (provider.momentList.isEmpty) {
|
||||||
|
return buildEmptyData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('我的朋友圈', style: const TextStyle(color: Colors.white)),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: const Color(0xFFF5F5F5),
|
||||||
|
body:
|
||||||
|
provider.isLoading
|
||||||
|
? buildLoadingIndicator()
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: CommonCard(child: _buildMomentList(provider)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,209 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_common/utils/date_utils.dart';
|
||||||
|
import 'package:flutter_common/utils/sp_utils.dart';
|
||||||
|
import 'package:flutter_common/widget/common_widget.dart';
|
||||||
|
import 'package:flutter_common/widget/dialog_widget.dart';
|
||||||
|
import 'package:food_hub_app/config/app_config.dart';
|
||||||
|
import 'package:food_hub_app/models/session.dart';
|
||||||
|
import 'package:food_hub_app/provider/user_provider.dart';
|
||||||
|
import 'package:food_hub_app/widgets/common/index.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||||
|
|
||||||
class ProfilePage extends StatefulWidget {
|
class ProfilePage extends StatefulWidget {
|
||||||
const ProfilePage({super.key});
|
const ProfilePage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ProfilePage> createState() => _ProfilePage();
|
State<ProfilePage> createState() => ProfilePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ProfilePage extends State<ProfilePage>{
|
class ProfilePageState extends State<ProfilePage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// 初始化加载数据
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context.read<UserProvider>().refreshUser(SPUtil.getInt('userId'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAvatar(User user) {
|
||||||
|
if (user.avatar!.isEmpty) {
|
||||||
|
return TDAvatar(
|
||||||
|
size: TDAvatarSize.large,
|
||||||
|
type: TDAvatarType.customText,
|
||||||
|
shape: TDAvatarShape.circle,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
text: user.username.isNotEmpty ? user.username[0] : '?',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return TDAvatar(
|
||||||
|
size: TDAvatarSize.large,
|
||||||
|
type: TDAvatarType.normal,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
avatarUrl: '${AppConfig.imageBaseUrl}/${user.avatar}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoItem({required IconData icon, required String text}) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 16, color: colors.primary),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(text),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFunctionButton({
|
||||||
|
required IconData icon,
|
||||||
|
required String text,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
leading: Icon(icon, color: colors.primary),
|
||||||
|
title: Text(text),
|
||||||
|
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: colors.primary),
|
||||||
|
onTap: onTap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBasicInfo(User user) {
|
||||||
|
return CommonCard(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildAvatar(user),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
user.username,
|
||||||
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (user.tags != null && user.tags!.isNotEmpty)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children:
|
||||||
|
user.tags!.map((tag) => buildTag(context, tag)).toList(),
|
||||||
|
),
|
||||||
|
if (user.tags != null && user.tags!.isNotEmpty)
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
if (user.area != null && user.area!.length >= 2)
|
||||||
|
_buildInfoItem(
|
||||||
|
icon: Icons.location_on,
|
||||||
|
text: '${user.area![0]}-${user.area![1]}',
|
||||||
|
),
|
||||||
|
if (user.birthDate != null)
|
||||||
|
_buildInfoItem(
|
||||||
|
icon: Icons.cake,
|
||||||
|
text: formatDate(user.birthDate!),
|
||||||
|
),
|
||||||
|
if (user.job != null && user.job!.isNotEmpty)
|
||||||
|
_buildInfoItem(icon: Icons.work, text: user.job!),
|
||||||
|
if (user.phoneNumber != null && user.phoneNumber!.isNotEmpty)
|
||||||
|
_buildInfoItem(
|
||||||
|
icon: Icons.phone_android,
|
||||||
|
text: user.phoneNumber!,
|
||||||
|
),
|
||||||
|
if (user.email != null && user.email!.isNotEmpty)
|
||||||
|
_buildInfoItem(icon: Icons.email, text: user.email!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (user.description != null && user.description!.isNotEmpty)
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (user.description != null && user.description!.isNotEmpty)
|
||||||
|
Text(user.description!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onTapMoment() {
|
||||||
|
Navigator.pushNamed(context, '/momentUser');
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFunctionButtons() {
|
||||||
|
return CommonCard(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildFunctionButton(
|
||||||
|
icon: Icons.account_circle,
|
||||||
|
text: '基本资料',
|
||||||
|
onTap: () {
|
||||||
|
// 跳转到编辑资料页面
|
||||||
|
// LogUtils.i('点击编辑资料');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
_buildFunctionButton(
|
||||||
|
icon: Icons.group_outlined,
|
||||||
|
text: '朋友圈',
|
||||||
|
onTap: _onTapMoment,
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
_buildFunctionButton(
|
||||||
|
icon: Icons.favorite,
|
||||||
|
text: '我的收藏',
|
||||||
|
onTap: () {
|
||||||
|
// 跳转到浏览历史页面
|
||||||
|
// LogUtils.i('点击浏览历史');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
_buildFunctionButton(
|
||||||
|
icon: Icons.lock,
|
||||||
|
text: '更改密码',
|
||||||
|
onTap: () {
|
||||||
|
// 跳转到帮助与反馈页面
|
||||||
|
// LogUtils.i('点击帮助与反馈');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
_buildFunctionButton(
|
||||||
|
icon: Icons.exit_to_app,
|
||||||
|
text: '退出登录',
|
||||||
|
onTap: () {
|
||||||
|
showConfirmDialog(context, '确认要退出吗?');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContent(UserProvider provider) {
|
||||||
|
final user = provider.currentUser;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(width: double.infinity, child: _buildBasicInfo(user)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildFunctionButtons(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Center(child: Text("个人主页"));
|
final provider = context.watch<UserProvider>();
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
if (provider.isLoading)
|
||||||
|
buildLoadingIndicator()
|
||||||
|
else if (provider.error.isNotEmpty)
|
||||||
|
Text(provider.error)
|
||||||
|
else
|
||||||
|
_buildContent(provider),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_common/widget/common_widget.dart';
|
import 'package:flutter_common/widget/common_widget.dart';
|
||||||
import 'package:food_hub_app/config/app_config.dart';
|
import 'package:food_hub_app/config/app_config.dart';
|
||||||
import 'package:food_hub_app/models/moment.dart';
|
import 'package:food_hub_app/models/moment.dart';
|
||||||
|
import 'package:food_hub_app/provider/food_provider.dart';
|
||||||
import 'package:food_hub_app/widgets/common/image.dart';
|
import 'package:food_hub_app/widgets/common/image.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||||
|
|
||||||
class MomentCard extends StatelessWidget {
|
class MomentCard extends StatelessWidget {
|
||||||
final Moment moment;
|
final Moment moment;
|
||||||
|
final bool isUser;
|
||||||
|
|
||||||
const MomentCard({super.key, required this.moment});
|
const MomentCard({super.key, required this.moment, required this.isUser});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -32,7 +35,7 @@ class MomentCard extends StatelessWidget {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
_buildTime(),
|
_buildTime(),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
_buildActionButtons(),
|
_buildActionButtons(context),
|
||||||
if (moment.commentList!.isNotEmpty) ...[
|
if (moment.commentList!.isNotEmpty) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildCommentList(),
|
_buildCommentList(),
|
||||||
@@ -52,7 +55,7 @@ class MomentCard extends StatelessWidget {
|
|||||||
size: TDAvatarSize.medium,
|
size: TDAvatarSize.medium,
|
||||||
type: TDAvatarType.customText,
|
type: TDAvatarType.customText,
|
||||||
shape: TDAvatarShape.circle,
|
shape: TDAvatarShape.circle,
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
text: moment.username?[0],
|
text: moment.username?[0],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -126,10 +129,31 @@ class MomentCard extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onTapEdit(BuildContext context) {
|
||||||
|
final provider = Provider.of<FoodProvider>(context, listen: false);
|
||||||
|
provider.isEditing = true;
|
||||||
|
provider.momentFormItem = moment;
|
||||||
|
Navigator.pushNamed(context, '/momentForm');
|
||||||
|
}
|
||||||
|
|
||||||
// 构建点赞和评论按钮
|
// 构建点赞和评论按钮
|
||||||
Widget _buildActionButtons() {
|
Widget _buildActionButtons(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
|
if (isUser)
|
||||||
|
SizedBox(
|
||||||
|
width: 42,
|
||||||
|
height: 36,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () => _onTapEdit(context),
|
||||||
|
child: Icon(Icons.edit),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 42,
|
width: 42,
|
||||||
height: 36,
|
height: 36,
|
||||||
@@ -166,30 +190,6 @@ class MomentCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// _buildActionButton(
|
|
||||||
// icon: Icons.thumb_up_alt_outlined,
|
|
||||||
// count: moment.likeList?.length ?? 0,
|
|
||||||
// ),
|
|
||||||
// const SizedBox(width: 20),
|
|
||||||
// _buildActionButton(
|
|
||||||
// icon: Icons.comment_outlined,
|
|
||||||
// count: moment.commentList?.length ?? 0,
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建带数字标记的动作按钮
|
|
||||||
Widget _buildActionButton({required IconData icon, required int count}) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Icon(icon, color: Colors.grey[500], size: 18),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
if (count > 0)
|
|
||||||
Text(
|
|
||||||
count.toString(),
|
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,8 +321,8 @@ packages:
|
|||||||
flutter_common:
|
flutter_common:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "D:\\Projects\\FlutterProjects\\flutter_common"
|
path: "../flutter_common"
|
||||||
relative: false
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.0.0+1"
|
version: "1.0.0+1"
|
||||||
flutter_form_builder:
|
flutter_form_builder:
|
||||||
|
|||||||
120
pubspec.yaml
120
pubspec.yaml
@@ -1,119 +1 @@
|
|||||||
name: food_hub_app
|
name: food_hub_app
|
||||||
description: "A new Flutter project."
|
|
||||||
# The following line prevents the package from being accidentally published to
|
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
||||||
|
|
||||||
# The following defines the version and build number for your application.
|
|
||||||
# A version number is three numbers separated by dots, like 1.2.43
|
|
||||||
# followed by an optional build number separated by a +.
|
|
||||||
# Both the version and the builder number may be overridden in flutter
|
|
||||||
# build by specifying --build-name and --build-number, respectively.
|
|
||||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
|
||||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
|
||||||
# Read more about iOS versioning at
|
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
|
||||||
version: 1.0.0+1
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: ^3.7.0
|
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
|
||||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
|
||||||
# dependencies can be manually updated by changing the version numbers below to
|
|
||||||
# the latest version available on pub.dev. To see which dependencies have newer
|
|
||||||
# versions available, run `flutter pub outdated`.
|
|
||||||
dependencies:
|
|
||||||
flutter:
|
|
||||||
sdk: flutter
|
|
||||||
flutter_localizations:
|
|
||||||
sdk: flutter
|
|
||||||
# The following adds the Cupertino Icons fonts to your application.
|
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
|
||||||
cupertino_icons: ^1.0.8
|
|
||||||
provider: ^6.1.1
|
|
||||||
timelines_plus: ^1.0.7
|
|
||||||
table_calendar: ^3.1.3
|
|
||||||
toggle_switch: ^2.3.0
|
|
||||||
flutter_form_builder: ^10.0.0
|
|
||||||
form_builder_validators: ^11.1.2
|
|
||||||
intl: ^0.19.0
|
|
||||||
tdesign_flutter: ^0.2.3
|
|
||||||
json_annotation: ^4.9.0
|
|
||||||
logger: ^2.6.0
|
|
||||||
photo_view: ^0.15.0
|
|
||||||
flutter_carousel_widget: ^3.1.0
|
|
||||||
easy_refresh: ^3.4.0
|
|
||||||
file_picker: ^10.3.3
|
|
||||||
share_plus: ^11.0.0
|
|
||||||
path_provider: ^2.1.5
|
|
||||||
flutter_common:
|
|
||||||
path: ..\flutter_common
|
|
||||||
|
|
||||||
dependency_overrides:
|
|
||||||
tdesign_flutter_adaptation: 3.16.0
|
|
||||||
image_picker: 1.0.8
|
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
flutter_test:
|
|
||||||
sdk: flutter
|
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
|
||||||
# package. See that file for information about deactivating specific lint
|
|
||||||
# rules and activating additional ones.
|
|
||||||
flutter_lints: ^5.0.0
|
|
||||||
# flutter pub run build_runner build
|
|
||||||
build_runner: ^2.4.5
|
|
||||||
json_serializable: ^6.7.1
|
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
|
||||||
flutter:
|
|
||||||
|
|
||||||
# The following line ensures that the Material Icons fonts is
|
|
||||||
# included with your application, so that you can use the icons in
|
|
||||||
# the material Icons class.
|
|
||||||
uses-material-design: true
|
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
|
||||||
# assets:
|
|
||||||
# - images/a_dot_burr.jpeg
|
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/to/resolution-aware-images
|
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
|
||||||
# https://flutter.dev/to/asset-from-package
|
|
||||||
|
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the fonts family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the fonts. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts from package dependencies,
|
|
||||||
# see https://flutter.dev/to/font-from-package
|
|
||||||
fonts:
|
|
||||||
- family: CustomFont
|
|
||||||
fonts:
|
|
||||||
- asset: fonts/custom.ttf
|
|
||||||
Reference in New Issue
Block a user