feat:增加查询朋友朋友圈功能
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="食光集"
|
android:label="食光集"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_common/provider/theme_provider.dart';
|
import 'package:flutter_common/provider/theme_provider.dart';
|
||||||
|
import 'package:flutter_common/utils/log_utils.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/app_provider.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/provider/user_provider.dart';
|
||||||
import 'package:food_hub_app/views/home.dart';
|
import 'package:food_hub_app/views/home.dart';
|
||||||
@@ -9,6 +11,7 @@ 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/moment_user.dart';
|
||||||
|
import 'package:food_hub_app/views/profile_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';
|
||||||
@@ -19,6 +22,7 @@ void main() async {
|
|||||||
|
|
||||||
FormBuilderLocalizations.delegate.load(const Locale('zh', 'CN'));
|
FormBuilderLocalizations.delegate.load(const Locale('zh', 'CN'));
|
||||||
await SPUtil.init();
|
await SPUtil.init();
|
||||||
|
await initLogger();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
@@ -26,6 +30,7 @@ void main() async {
|
|||||||
ChangeNotifierProvider(create: (context) => FoodProvider()),
|
ChangeNotifierProvider(create: (context) => FoodProvider()),
|
||||||
ChangeNotifierProvider(create: (context) => ThemeProvider()),
|
ChangeNotifierProvider(create: (context) => ThemeProvider()),
|
||||||
ChangeNotifierProvider(create: (context) => UserProvider()),
|
ChangeNotifierProvider(create: (context) => UserProvider()),
|
||||||
|
ChangeNotifierProvider(create: (context) => AppProvider()),
|
||||||
],
|
],
|
||||||
child: MyApp(),
|
child: MyApp(),
|
||||||
),
|
),
|
||||||
@@ -69,7 +74,8 @@ class MyApp extends StatelessWidget {
|
|||||||
'/recipeDetail': (context) => RecipeDetailPage(),
|
'/recipeDetail': (context) => RecipeDetailPage(),
|
||||||
'/momentForm': (context) => MomentFormPage(),
|
'/momentForm': (context) => MomentFormPage(),
|
||||||
'/moment': (context) => MomentPage(),
|
'/moment': (context) => MomentPage(),
|
||||||
'/momentUser': (context) => MomentUserPage()
|
'/momentUser': (context) => MomentUserPage(),
|
||||||
|
'/ProfileUser': (context) => ProfileUserPage()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
8
lib/provider/app_provider.dart
Normal file
8
lib/provider/app_provider.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AppProvider with ChangeNotifier {
|
||||||
|
String appVersion = '1.0.0';
|
||||||
|
String buildNumber = '1';
|
||||||
|
|
||||||
|
String get fullVersion => '$appVersion+$buildNumber';
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:food_hub_app/apis/moment.dart';
|
||||||
import 'package:food_hub_app/apis/user.dart';
|
import 'package:food_hub_app/apis/user.dart';
|
||||||
|
import 'package:food_hub_app/models/moment.dart';
|
||||||
import 'package:food_hub_app/models/session.dart';
|
import 'package:food_hub_app/models/session.dart';
|
||||||
|
|
||||||
class UserProvider with ChangeNotifier {
|
class UserProvider with ChangeNotifier {
|
||||||
late User currentUser = User.getEmpty();
|
late User currentUser = User.getEmpty();
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
String error = '';
|
String error = '';
|
||||||
|
List<Moment> momentList = [];
|
||||||
|
|
||||||
Future<void> refreshUser(int id) async {
|
Future<void> refreshUser(int id) async {
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
@@ -25,4 +28,24 @@ class UserProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> queryMomentByUserId(int userId) async {
|
||||||
|
if (isLoading) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
isLoading = true;
|
||||||
|
error = '';
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
final result = await queryMomentListByUserIdApi(userId);
|
||||||
|
momentList = result;
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
error = '加载数据失败: $e';
|
||||||
|
debugPrint('加载数据失败: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_common/utils/sp_utils.dart';
|
import 'package:flutter_common/utils/sp_utils.dart';
|
||||||
import 'package:flutter_common/widget/common_widget.dart';
|
import 'package:flutter_common/widget/common_widget.dart';
|
||||||
import 'package:food_hub_app/provider/food_provider.dart';
|
import 'package:food_hub_app/provider/food_provider.dart';
|
||||||
import 'package:food_hub_app/widgets/moment/card.dart';
|
import 'package:food_hub_app/widgets/moment/list.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class MomentUserPage extends StatefulWidget {
|
class MomentUserPage extends StatefulWidget {
|
||||||
@@ -23,16 +23,6 @@ class _MomentUserPageState extends State<MomentUserPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<FoodProvider>();
|
final provider = context.watch<FoodProvider>();
|
||||||
@@ -51,13 +41,15 @@ class _MomentUserPageState extends State<MomentUserPage> {
|
|||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: const Color(0xFFF5F5F5),
|
|
||||||
body:
|
body:
|
||||||
provider.isLoading
|
provider.isLoading
|
||||||
? buildLoadingIndicator()
|
? buildLoadingIndicator()
|
||||||
: Padding(
|
: Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: CommonCard(child: _buildMomentList(provider)),
|
child: MomentList(
|
||||||
|
momentList: provider.momentList,
|
||||||
|
isUser: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
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/utils/sp_utils.dart';
|
||||||
import 'package:flutter_common/widget/common_widget.dart';
|
import 'package:flutter_common/widget/common_widget.dart';
|
||||||
import 'package:flutter_common/widget/dialog_widget.dart';
|
import 'package:flutter_common/widget/dialog_widget.dart';
|
||||||
import 'package:food_hub_app/config/app_config.dart';
|
import 'package:food_hub_app/provider/app_provider.dart';
|
||||||
import 'package:food_hub_app/models/session.dart';
|
|
||||||
import 'package:food_hub_app/provider/user_provider.dart';
|
import 'package:food_hub_app/provider/user_provider.dart';
|
||||||
import 'package:food_hub_app/widgets/common/index.dart';
|
import 'package:food_hub_app/widgets/profile/basic_info.dart';
|
||||||
import 'package:provider/provider.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});
|
||||||
@@ -28,37 +25,6 @@ class ProfilePageState extends State<ProfilePage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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({
|
Widget _buildFunctionButton({
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
required String text,
|
required String text,
|
||||||
@@ -74,59 +40,6 @@ class ProfilePageState extends State<ProfilePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
void _onTapMoment() {
|
||||||
Navigator.pushNamed(context, '/momentUser');
|
Navigator.pushNamed(context, '/momentUser');
|
||||||
}
|
}
|
||||||
@@ -168,25 +81,51 @@ class ProfilePageState extends State<ProfilePage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
|
_buildFunctionButton(icon: Icons.message, text: '反馈意见', onTap: () {}),
|
||||||
|
const Divider(height: 1),
|
||||||
|
_buildFunctionButton(icon: Icons.refresh, text: '检查更新', onTap: () {}),
|
||||||
|
const Divider(height: 1),
|
||||||
_buildFunctionButton(
|
_buildFunctionButton(
|
||||||
icon: Icons.exit_to_app,
|
icon: Icons.exit_to_app,
|
||||||
text: '退出登录',
|
text: '退出登录',
|
||||||
onTap: () {
|
onTap: _onTapLogout,
|
||||||
showConfirmDialog(context, '确认要退出吗?');
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onTapLogout() async {
|
||||||
|
final result = await showConfirmDialog(context, '确认要退出吗?');
|
||||||
|
if (result == true) {
|
||||||
|
SPUtil.remove('token');
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.pushNamed(context, '/login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildVersionInfo() {
|
||||||
|
final provider = context.read<AppProvider>();
|
||||||
|
|
||||||
|
return Text(
|
||||||
|
'版本 v${provider.fullVersion}',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildContent(UserProvider provider) {
|
Widget _buildContent(UserProvider provider) {
|
||||||
final user = provider.currentUser;
|
final user = provider.currentUser;
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: double.infinity, child: _buildBasicInfo(user)),
|
SizedBox(width: double.infinity, child: ProfileInfo(user: user)),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildFunctionButtons(),
|
_buildFunctionButtons(),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildVersionInfo(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
117
lib/views/profile_user.dart
Normal file
117
lib/views/profile_user.dart
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import 'package:flutter/material.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/models/session.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/common/index.dart';
|
||||||
|
import 'package:food_hub_app/widgets/moment/list.dart';
|
||||||
|
import 'package:food_hub_app/widgets/profile/basic_info.dart';
|
||||||
|
import 'package:food_hub_app/widgets/recipe/calendar.dart';
|
||||||
|
import 'package:food_hub_app/widgets/recipe/list.dart';
|
||||||
|
import 'package:food_hub_app/widgets/recipe/timeline.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
enum ProfileTab {
|
||||||
|
moment('朋友圈'),
|
||||||
|
recipe('菜谱');
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
const ProfileTab(this.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProfileUserPage extends StatefulWidget {
|
||||||
|
const ProfileUserPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ProfileUserPage> createState() => ProfileUserPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProfileUserPageState extends State<ProfileUserPage> {
|
||||||
|
late ProfileTab currentTab = ProfileTab.moment;
|
||||||
|
|
||||||
|
void _onTabChange(ProfileTab tab) {
|
||||||
|
setState(() {
|
||||||
|
currentTab = tab;
|
||||||
|
});
|
||||||
|
|
||||||
|
// provider.onRecordTabChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTabs({
|
||||||
|
required BuildContext context,
|
||||||
|
required ProfileTab currentTab,
|
||||||
|
required ValueChanged<ProfileTab> onTabChanged,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
child: buildToggleSwitch(
|
||||||
|
context: context,
|
||||||
|
currentTab: currentTab,
|
||||||
|
tabValues: ProfileTab.values,
|
||||||
|
labels: ProfileTab.values.map((e) => e.label).toList(),
|
||||||
|
onTabChanged: onTabChanged,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContent(UserProvider provider) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ProfileInfo(user: provider.currentUser),
|
||||||
|
),
|
||||||
|
_buildTabs(
|
||||||
|
context: context,
|
||||||
|
currentTab: currentTab,
|
||||||
|
onTabChanged: (tab) => _onTabChange(tab),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: IndexedStack(
|
||||||
|
index: currentTab.index,
|
||||||
|
children: [
|
||||||
|
MomentList(momentList: provider.momentList, isUser: false),
|
||||||
|
Text('个人菜谱'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final provider = context.watch<UserProvider>();
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
'${provider.currentUser.username}的个人信息',
|
||||||
|
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: Padding(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
if (provider.isLoading)
|
||||||
|
buildLoadingIndicator()
|
||||||
|
else if (provider.error.isNotEmpty)
|
||||||
|
Text(provider.error)
|
||||||
|
else
|
||||||
|
_buildContent(provider),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,32 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_common/flutter_common.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/provider/food_provider.dart';
|
||||||
|
import 'package:food_hub_app/provider/user_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:provider/provider.dart';
|
||||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||||
|
|
||||||
class MomentCard extends StatelessWidget {
|
class MomentCard extends StatefulWidget {
|
||||||
final Moment moment;
|
final Moment moment;
|
||||||
final bool isUser;
|
final bool isUser;
|
||||||
|
|
||||||
const MomentCard({super.key, required this.moment, required this.isUser});
|
const MomentCard({super.key, required this.moment, required this.isUser});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => MomentCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MomentCardState extends State<MomentCard> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CommonCard(
|
return CommonCard(
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildAvatar(context),
|
InkWell(onTap: () => _onTapUser(context), child: _buildAvatar()),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -28,15 +35,15 @@ class MomentCard extends StatelessWidget {
|
|||||||
_buildNickname(),
|
_buildNickname(),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
_buildContent(),
|
_buildContent(),
|
||||||
if (moment.imageList.isNotEmpty) ...[
|
if (widget.moment.imageList.isNotEmpty) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildPostImages(context),
|
_buildPostImages(),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
_buildTime(),
|
_buildTime(),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
_buildActionButtons(context),
|
_buildActionButtons(),
|
||||||
if (moment.commentList!.isNotEmpty) ...[
|
if (widget.moment.commentList!.isNotEmpty) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildCommentList(),
|
_buildCommentList(),
|
||||||
],
|
],
|
||||||
@@ -48,22 +55,35 @@ class MomentCard extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onTapUser(BuildContext context) async {
|
||||||
|
final provider = context.read<UserProvider>();
|
||||||
|
|
||||||
|
if (widget.moment.userId != SPUtil.getInt('userId')) {
|
||||||
|
await provider.refreshUser(widget.moment.userId!);
|
||||||
|
await provider.queryMomentByUserId(widget.moment.userId!);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.pushNamed(context, '/ProfileUser');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 构建头像组件
|
// 构建头像组件
|
||||||
Widget _buildAvatar(BuildContext context) {
|
Widget _buildAvatar() {
|
||||||
if (moment.avatar!.isEmpty) {
|
if (widget.moment.avatar!.isEmpty) {
|
||||||
return TDAvatar(
|
return TDAvatar(
|
||||||
size: TDAvatarSize.medium,
|
size: TDAvatarSize.medium,
|
||||||
type: TDAvatarType.customText,
|
type: TDAvatarType.customText,
|
||||||
shape: TDAvatarShape.circle,
|
shape: TDAvatarShape.circle,
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
text: moment.username?[0],
|
text: widget.moment.username?[0],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return TDAvatar(
|
return TDAvatar(
|
||||||
size: TDAvatarSize.medium,
|
size: TDAvatarSize.medium,
|
||||||
type: TDAvatarType.normal,
|
type: TDAvatarType.normal,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
avatarUrl: '${AppConfig.imageBaseUrl}/${moment.avatar}',
|
avatarUrl: '${AppConfig.imageBaseUrl}/${widget.moment.avatar}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +91,7 @@ class MomentCard extends StatelessWidget {
|
|||||||
// 构建昵称组件
|
// 构建昵称组件
|
||||||
Widget _buildNickname() {
|
Widget _buildNickname() {
|
||||||
return Text(
|
return Text(
|
||||||
moment.username ?? "",
|
widget.moment.username ?? "",
|
||||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -79,14 +99,14 @@ class MomentCard extends StatelessWidget {
|
|||||||
// 构建内容组件
|
// 构建内容组件
|
||||||
Widget _buildContent() {
|
Widget _buildContent() {
|
||||||
return Text(
|
return Text(
|
||||||
moment.content,
|
widget.moment.content,
|
||||||
style: const TextStyle(fontSize: 15, height: 1.3),
|
style: const TextStyle(fontSize: 15, height: 1.3),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建朋友圈图片列表(网格布局)
|
// 构建朋友圈图片列表(网格布局)
|
||||||
Widget _buildPostImages(BuildContext context) {
|
Widget _buildPostImages() {
|
||||||
final imageCount = moment.imageList.length;
|
final imageCount = widget.moment.imageList.length;
|
||||||
|
|
||||||
// 计算网格列数
|
// 计算网格列数
|
||||||
int crossAxisCount;
|
int crossAxisCount;
|
||||||
@@ -106,7 +126,7 @@ class MomentCard extends StatelessWidget {
|
|||||||
|
|
||||||
// 生成图片URL列表(用于预览时切换)
|
// 生成图片URL列表(用于预览时切换)
|
||||||
final List<String> imageUrls =
|
final List<String> imageUrls =
|
||||||
moment.imageList.map((path) => path).toList();
|
widget.moment.imageList.map((path) => path).toList();
|
||||||
|
|
||||||
return GridView.count(
|
return GridView.count(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@@ -124,33 +144,30 @@ class MomentCard extends StatelessWidget {
|
|||||||
// 构建时间组件
|
// 构建时间组件
|
||||||
Widget _buildTime() {
|
Widget _buildTime() {
|
||||||
return Text(
|
return Text(
|
||||||
moment.date ?? "",
|
widget.moment.date ?? "",
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onTapEdit(BuildContext context) {
|
void _onTapEdit() {
|
||||||
final provider = Provider.of<FoodProvider>(context, listen: false);
|
final provider = Provider.of<FoodProvider>(context, listen: false);
|
||||||
provider.isEditing = true;
|
provider.isEditing = true;
|
||||||
provider.momentFormItem = moment;
|
provider.momentFormItem = widget.moment;
|
||||||
Navigator.pushNamed(context, '/momentForm');
|
Navigator.pushNamed(context, '/momentForm');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建点赞和评论按钮
|
// 构建点赞和评论按钮
|
||||||
Widget _buildActionButtons(BuildContext context) {
|
Widget _buildActionButtons() {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
if (isUser)
|
if (widget.isUser)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 42,
|
width: 42,
|
||||||
height: 36,
|
height: 36,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.bottomLeft,
|
alignment: Alignment.bottomLeft,
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(onTap: () => _onTapEdit(), child: Icon(Icons.edit)),
|
||||||
onTap: () => _onTapEdit(context),
|
|
||||||
child: Icon(Icons.edit),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -166,7 +183,7 @@ class MomentCard extends StatelessWidget {
|
|||||||
bottom: 15,
|
bottom: 15,
|
||||||
child: TDBadge(
|
child: TDBadge(
|
||||||
TDBadgeType.message,
|
TDBadgeType.message,
|
||||||
count: (moment.likeList?.length ?? 0).toString(),
|
count: (widget.moment.likeList?.length ?? 0).toString(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -184,7 +201,7 @@ class MomentCard extends StatelessWidget {
|
|||||||
bottom: 15,
|
bottom: 15,
|
||||||
child: TDBadge(
|
child: TDBadge(
|
||||||
TDBadgeType.message,
|
TDBadgeType.message,
|
||||||
count: (moment.commentList?.length ?? 0).toString(),
|
count: (widget.moment.commentList?.length ?? 0).toString(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -236,10 +253,10 @@ class MomentCard extends StatelessWidget {
|
|||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: moment.commentList!.length,
|
itemCount: widget.moment.commentList!.length,
|
||||||
separatorBuilder: (context, index) => SizedBox(height: 8),
|
separatorBuilder: (context, index) => SizedBox(height: 8),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return _buildCommentItem(moment.commentList![index]);
|
return _buildCommentItem(widget.moment.commentList![index]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
21
lib/widgets/moment/list.dart
Normal file
21
lib/widgets/moment/list.dart
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:food_hub_app/models/moment.dart';
|
||||||
|
import 'package:food_hub_app/widgets/moment/card.dart';
|
||||||
|
|
||||||
|
class MomentList extends StatelessWidget {
|
||||||
|
final List<Moment> momentList;
|
||||||
|
final bool isUser;
|
||||||
|
|
||||||
|
const MomentList({super.key, required this.momentList, required this.isUser});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.separated(
|
||||||
|
itemCount: momentList.length,
|
||||||
|
separatorBuilder: (context, index) => SizedBox(height: 8),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return MomentCard(moment: momentList[index], isUser: isUser);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
113
lib/widgets/profile/basic_info.dart
Normal file
113
lib/widgets/profile/basic_info.dart
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_common/utils/date_utils.dart';
|
||||||
|
import 'package:flutter_common/widget/common_widget.dart';
|
||||||
|
import 'package:food_hub_app/config/app_config.dart';
|
||||||
|
import 'package:food_hub_app/models/session.dart';
|
||||||
|
import 'package:food_hub_app/widgets/common/index.dart';
|
||||||
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||||
|
|
||||||
|
class ProfileInfo extends StatelessWidget {
|
||||||
|
final User user;
|
||||||
|
|
||||||
|
const ProfileInfo({super.key, required this.user});
|
||||||
|
|
||||||
|
Widget _buildAvatar(BuildContext context, User user) {
|
||||||
|
if (user.avatar == null) {
|
||||||
|
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 BuildContext context,
|
||||||
|
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),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CommonCard(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildAvatar(context, 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(
|
||||||
|
context: context,
|
||||||
|
icon: Icons.location_on,
|
||||||
|
text: '${user.area![0]}-${user.area![1]}',
|
||||||
|
),
|
||||||
|
if (user.birthDate != null)
|
||||||
|
_buildInfoItem(
|
||||||
|
context: context,
|
||||||
|
icon: Icons.cake,
|
||||||
|
text: formatDate(user.birthDate!),
|
||||||
|
),
|
||||||
|
if (user.job != null && user.job!.isNotEmpty)
|
||||||
|
_buildInfoItem(
|
||||||
|
context: context,
|
||||||
|
icon: Icons.work,
|
||||||
|
text: user.job!,
|
||||||
|
),
|
||||||
|
if (user.phoneNumber != null && user.phoneNumber!.isNotEmpty)
|
||||||
|
_buildInfoItem(
|
||||||
|
context: context,
|
||||||
|
icon: Icons.phone_android,
|
||||||
|
text: user.phoneNumber!,
|
||||||
|
),
|
||||||
|
if (user.email != null && user.email!.isNotEmpty)
|
||||||
|
_buildInfoItem(
|
||||||
|
context: context,
|
||||||
|
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!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user