feat:重构模块
This commit is contained in:
@@ -27,7 +27,7 @@ pluginManagement {
|
|||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.7.0" apply false
|
id("com.android.application") version "8.7.0" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
/// 应用信息
|
/// 应用信息
|
||||||
class AppConfig {
|
class AppConfig {
|
||||||
// 网络配置
|
// 网络配置
|
||||||
// http://192.168.1.3:8100
|
|
||||||
// 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 = "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 = "https://cxx0822.s.3q.hair/food-api";
|
||||||
static const String rustfsIp = '14.103.235.151';
|
static const String rustfsIp = '43.248.188.28';
|
||||||
static const String rustfsFileUrl = 'http://14.103.235.151:9100';
|
static const int rustfsPort = 23125;
|
||||||
|
static const String rustfsAccessKey = 'sZuAg1WCo5i4kD936Tqh';
|
||||||
|
static const String rustfsSecretKey = 'g2fEyk6bslGrHepBvjhu0OLNF4qCRziPJIx1KAS8';
|
||||||
|
static const String rustfsFileUrl = 'http://$rustfsIp:$rustfsPort';
|
||||||
static const String bucketName = 'food';
|
static const String bucketName = 'food';
|
||||||
static const String imageBaseUrl = '$rustfsFileUrl/$bucketName/';
|
static const String imageBaseUrl = '$rustfsFileUrl/$bucketName/';
|
||||||
// static const String imageBaseUrl = '$baseApiUrl/';
|
// static const String imageBaseUrl = '$baseApiUrl/';
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'menu.dart';
|
|
||||||
|
|
||||||
class AppNavbar extends StatefulWidget {
|
|
||||||
final int currentPageIndex;
|
|
||||||
final Function(int) onTapNavbarItem;
|
|
||||||
|
|
||||||
const AppNavbar({
|
|
||||||
super.key,
|
|
||||||
required this.currentPageIndex,
|
|
||||||
required this.onTapNavbarItem,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => AppNavbarState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppNavbarState extends State<AppNavbar> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colors = Theme.of(context).colorScheme;
|
|
||||||
|
|
||||||
return BottomNavigationBar(
|
|
||||||
currentIndex: widget.currentPageIndex,
|
|
||||||
onTap: widget.onTapNavbarItem,
|
|
||||||
items: bottomNavItems,
|
|
||||||
type: BottomNavigationBarType.fixed,
|
|
||||||
selectedItemColor: colors.primary,
|
|
||||||
unselectedItemColor: colors.onSurface.withAlpha(150),
|
|
||||||
showSelectedLabels: true,
|
|
||||||
showUnselectedLabels: true,
|
|
||||||
backgroundColor: colors.surface,
|
|
||||||
elevation: 8,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:food_hub_app/models/layout.dart';
|
|
||||||
import 'package:food_hub_app/views/moment.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';
|
|
||||||
|
|
||||||
final List<PageInfo> pages = [
|
|
||||||
PageInfo(
|
|
||||||
label: "记录",
|
|
||||||
icon: Icons.home_outlined,
|
|
||||||
activeIcon: Icons.home,
|
|
||||||
page: RecordPage(),
|
|
||||||
),
|
|
||||||
PageInfo(
|
|
||||||
label: "统计",
|
|
||||||
icon: Icons.pie_chart_outline,
|
|
||||||
activeIcon: Icons.pie_chart,
|
|
||||||
page: StatsPage(),
|
|
||||||
),
|
|
||||||
PageInfo(
|
|
||||||
label: "朋友圈",
|
|
||||||
icon: Icons.group_outlined,
|
|
||||||
activeIcon: Icons.group,
|
|
||||||
page: MomentPage(),
|
|
||||||
),
|
|
||||||
PageInfo(
|
|
||||||
label: "我的",
|
|
||||||
icon: Icons.account_circle_outlined,
|
|
||||||
activeIcon: Icons.account_circle,
|
|
||||||
page: ProfilePage(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
final List<BottomNavigationBarItem> bottomNavItems =
|
|
||||||
pages.map((page) {
|
|
||||||
return BottomNavigationBarItem(icon: Icon(page.icon), label: page.label);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final List<Widget> tabPages = pages.map((item) => item.page).toList();
|
|
||||||
@@ -16,6 +16,7 @@ import 'package:food_hub_app/views/recipe_form.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';
|
||||||
|
import 'package:liquid_glass_widgets/liquid_glass_setup.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@@ -23,17 +24,22 @@ 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();
|
// await initLogger();
|
||||||
|
|
||||||
|
// 预编译 shader,防止首帧白闪
|
||||||
|
await LiquidGlassWidgets.initialize();
|
||||||
|
// wrap() 安装无障碍桥接、全局主题、自适应质量
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
LiquidGlassWidgets.wrap(
|
||||||
|
child: MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
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()),
|
ChangeNotifierProvider(create: (context) => AppProvider()),
|
||||||
],
|
],
|
||||||
child: MyApp(),
|
child: const MyApp(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -45,10 +51,52 @@ class MyApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final themeProvider = context.watch<ThemeProvider>();
|
final themeProvider = context.watch<ThemeProvider>();
|
||||||
|
final brightness = MediaQuery.of(context).platformBrightness;
|
||||||
|
themeProvider.isDarkMode = brightness == Brightness.dark;
|
||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: themeProvider.currentThemeData,
|
theme: ThemeData(
|
||||||
|
scaffoldBackgroundColor: const Color(0xFFFAF6F0),
|
||||||
|
colorScheme: ColorScheme(
|
||||||
|
brightness: Brightness.light,
|
||||||
|
primary: const Color(0xFFE89F71),
|
||||||
|
secondary: const Color(0xFFE89F71),
|
||||||
|
surface: const Color(0xFFFFFFFF),
|
||||||
|
error: const Color(0xFFE76F51),
|
||||||
|
|
||||||
|
onPrimary: Colors.white,
|
||||||
|
onSecondary: Colors.white,
|
||||||
|
onSurface: const Color(0xFF3A332C),
|
||||||
|
onError: Colors.white,
|
||||||
|
),
|
||||||
|
textTheme: TextTheme(
|
||||||
|
titleLarge: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: const Color(0xFF3A332C),
|
||||||
|
),
|
||||||
|
titleMedium: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: const Color(0xFF3A332C),
|
||||||
|
),
|
||||||
|
bodyLarge: TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
color: const Color(0xFF3A332C),
|
||||||
|
),
|
||||||
|
bodyMedium: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: const Color(0xFF8C8379),
|
||||||
|
),
|
||||||
|
bodySmall: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: const Color(0xFF8C8379),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
fontFamily: 'CustomFont',
|
||||||
|
useMaterial3: true,
|
||||||
|
),
|
||||||
supportedLocales: const [
|
supportedLocales: const [
|
||||||
Locale('en', 'US'), // 英语
|
Locale('en', 'US'), // 英语
|
||||||
Locale('zh', 'CN'), // 中文
|
Locale('zh', 'CN'), // 中文
|
||||||
@@ -71,13 +119,13 @@ class MyApp extends StatelessWidget {
|
|||||||
home: LoginPage(),
|
home: LoginPage(),
|
||||||
routes: {
|
routes: {
|
||||||
'/home': (context) => HomePage(),
|
'/home': (context) => HomePage(),
|
||||||
'/recordForm': (context) => RecordFormPage(),
|
'/recordForm': (context) => RecordForm(),
|
||||||
'/recipeForm': (context) => RecipeFormPage(),
|
'/recipeForm': (context) => RecipeFormPage(),
|
||||||
'/recipeDetail': (context) => RecipeDetailPage(),
|
'/recipeDetail': (context) => RecipeDetailPage(),
|
||||||
'/momentForm': (context) => MomentFormPage(),
|
'/momentForm': (context) => MomentForm(),
|
||||||
'/moment': (context) => MomentPage(),
|
'/moment': (context) => MomentPage(),
|
||||||
'/momentUser': (context) => MomentUserPage(),
|
'/momentUser': (context) => MomentUserPage(),
|
||||||
'/ProfileUser': (context) => ProfileUserPage()
|
'/ProfileUser': (context) => ProfileUserPage(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
43
lib/utils/minio_utils.dart
Normal file
43
lib/utils/minio_utils.dart
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter_common/utils/file_utils.dart';
|
||||||
|
import 'package:food_hub_app/config/app_config.dart';
|
||||||
|
import 'package:minio/io.dart';
|
||||||
|
import 'package:minio/minio.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class MinIOHelper {
|
||||||
|
static final MinIOHelper _instance = MinIOHelper._internal();
|
||||||
|
|
||||||
|
factory MinIOHelper() => _instance;
|
||||||
|
|
||||||
|
final String ip = AppConfig.rustfsIp;
|
||||||
|
|
||||||
|
MinIOHelper._internal() {
|
||||||
|
_minio = Minio(
|
||||||
|
endPoint: AppConfig.rustfsIp,
|
||||||
|
port: AppConfig.rustfsPort,
|
||||||
|
accessKey: AppConfig.rustfsAccessKey,
|
||||||
|
secretKey: AppConfig.rustfsSecretKey,
|
||||||
|
useSSL: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late Minio _minio;
|
||||||
|
|
||||||
|
Future<String> uploadFile({
|
||||||
|
required PlatformFile file,
|
||||||
|
required String bucketName,
|
||||||
|
Function(double)? onProgress,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
String hashName = await generateMD5HashName(file.path!);
|
||||||
|
String fileName = '$hashName${getFileExtension(file.name)}';
|
||||||
|
|
||||||
|
await _minio.fPutObject(bucketName, fileName, file.path!);
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('文件上传失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/layout/app_actions.dart';
|
import 'package:food_hub_app/views/profile.dart';
|
||||||
import 'package:food_hub_app/layout/app_drawer.dart';
|
import 'package:food_hub_app/views/record.dart';
|
||||||
import 'package:food_hub_app/layout/app_navbar.dart';
|
import 'package:food_hub_app/views/stats.dart';
|
||||||
import 'package:food_hub_app/layout/menu.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 {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({super.key});
|
const HomePage({super.key});
|
||||||
@@ -14,38 +20,52 @@ class HomePage extends StatefulWidget {
|
|||||||
class _HomePage extends State<HomePage> {
|
class _HomePage extends State<HomePage> {
|
||||||
int _currentIndex = 0;
|
int _currentIndex = 0;
|
||||||
|
|
||||||
|
final _pages = [
|
||||||
|
const RecordPage(),
|
||||||
|
const StatsPage(),
|
||||||
|
const MomentPage(),
|
||||||
|
const ProfilePage(),
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
final colors = Theme.of(context).colorScheme;
|
||||||
appBar: AppBar(
|
|
||||||
centerTitle: true,
|
return GlassScaffold(
|
||||||
title: Text('食光集', style: TextStyle(color: Colors.white)),
|
statusBarStyle: GlassStatusBarStyle.auto,
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
bottomBar: GlassBottomBar(
|
||||||
leading: Builder(
|
settings: LiquidGlassSettings(
|
||||||
builder: (context) {
|
glassColor: colors.surface
|
||||||
return IconButton(
|
|
||||||
icon: const Icon(Icons.menu),
|
|
||||||
color: Colors.white,
|
|
||||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
actions: [AppActions(pageIndex: _currentIndex)],
|
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: '记录',
|
||||||
),
|
),
|
||||||
// backgroundColor: Color(0xFFF5F5F5),
|
GlassBottomBarTab(
|
||||||
drawer: AppDrawer(),
|
icon: Icon(Icons.pie_chart_outline),
|
||||||
body: Padding(
|
activeIcon: Icon(Icons.pie_chart),
|
||||||
padding: EdgeInsets.all(10),
|
label: '统计',
|
||||||
child: tabPages[_currentIndex],
|
|
||||||
),
|
),
|
||||||
bottomNavigationBar: AppNavbar(
|
GlassBottomBarTab(
|
||||||
currentPageIndex: _currentIndex,
|
icon: Icon(Icons.group_outlined),
|
||||||
onTapNavbarItem: (index) {
|
activeIcon: Icon(Icons.group),
|
||||||
setState(() {
|
label: '朋友圈',
|
||||||
_currentIndex = index;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
GlassBottomBarTab(
|
||||||
|
icon: Icon(Icons.account_circle_outlined),
|
||||||
|
activeIcon: Icon(Icons.account_circle),
|
||||||
|
label: '我的',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: _pages[_currentIndex],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,12 +104,12 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: buildLoginForm()),
|
Expanded(child: buildLoginForm(context)),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 50),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 50),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
buildOtherLoginText(),
|
buildOtherLoginText(context),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
buildOtherMethod(context),
|
buildOtherMethod(context),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
@@ -131,29 +131,29 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildLoginForm() {
|
Widget buildLoginForm(BuildContext context) {
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: kToolbarHeight),
|
const SizedBox(height: kToolbarHeight),
|
||||||
buildTitle(),
|
buildTitle(),
|
||||||
const SizedBox(height: 60),
|
const SizedBox(height: 60),
|
||||||
buildUsernameTextField(),
|
buildUsernameTextField(context),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
buildPasswordTextField(),
|
buildPasswordTextField(context),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
buildRememberPasswordCheckbox(),
|
buildRememberPasswordCheckbox(context),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
buildLoginButton(),
|
buildLoginButton(context),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildUsernameTextField() {
|
Widget buildUsernameTextField(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
formLabelText(labelText: "账号:"),
|
Text("账号:", style: Theme.of(context).textTheme.bodyLarge),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: 'username',
|
name: 'username',
|
||||||
@@ -171,11 +171,11 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildPasswordTextField() {
|
Widget buildPasswordTextField(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
formLabelText(labelText: "密码:"),
|
Text("密码:", style: Theme.of(context).textTheme.bodyLarge),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: 'password',
|
name: 'password',
|
||||||
@@ -205,7 +205,7 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildRememberPasswordCheckbox() {
|
Widget buildRememberPasswordCheckbox(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
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(
|
return SizedBox(
|
||||||
height: 45,
|
height: 45,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
@@ -243,23 +243,20 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildOtherLoginText() {
|
Widget buildOtherLoginText(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Divider(color: Colors.grey[300], thickness: 1)),
|
Expanded(child: Divider(color: Colors.grey[300], thickness: 1)),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Text(
|
child: Text('其他方式登录', style: Theme.of(context).textTheme.bodyMedium),
|
||||||
'其他方式登录',
|
|
||||||
style: TextStyle(color: Colors.grey, fontSize: 14),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Expanded(child: Divider(color: Colors.grey[300], thickness: 1)),
|
Expanded(child: Divider(color: Colors.grey[300], thickness: 1)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildOtherMethod(context) {
|
Widget buildOtherMethod(BuildContext context) {
|
||||||
return OverflowBar(
|
return OverflowBar(
|
||||||
alignment: MainAxisAlignment.center,
|
alignment: MainAxisAlignment.center,
|
||||||
children:
|
children:
|
||||||
@@ -292,16 +289,19 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildRegisterText(context) {
|
Widget buildRegisterText(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Text('没有账号?', style: TextStyle(fontSize: 18)),
|
Text('没有账号?', style: Theme.of(context).textTheme.bodyMedium),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: const Text(
|
child: Text(
|
||||||
'点击注册',
|
'点击注册',
|
||||||
style: TextStyle(fontSize: 18, color: Colors.green),
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
print("点击注册");
|
print("点击注册");
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ class _MomentPageState extends State<MomentPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onScroll() {
|
void _onScroll() {
|
||||||
// 当滚动距离超过300时显示返回顶部按钮
|
// 当滚动距离超过时显示返回顶部按钮
|
||||||
if (_scrollController.offset > 300) {
|
if (_scrollController.offset > 100) {
|
||||||
if (!_showScrollToTop) {
|
if (!_showScrollToTop) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_showScrollToTop = true;
|
_showScrollToTop = true;
|
||||||
@@ -99,7 +99,17 @@ class _MomentPageState extends State<MomentPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 有数据时显示列表
|
// 有数据时显示列表
|
||||||
return Stack(
|
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: [
|
children: [
|
||||||
buildEasyRefresh(
|
buildEasyRefresh(
|
||||||
freshController: _freshController,
|
freshController: _freshController,
|
||||||
@@ -119,6 +129,8 @@ class _MomentPageState extends State<MomentPage> {
|
|||||||
if (_showScrollToTop)
|
if (_showScrollToTop)
|
||||||
buildScrollToTop(context: context, scrollToTop: _scrollToTop),
|
buildScrollToTop(context: context, scrollToTop: _scrollToTop),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_common/utils/log_utils.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/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/dialog_widget.dart';
|
||||||
import 'package:flutter_common/widget/loading_widget.dart';
|
import 'package:flutter_common/widget/loading_widget.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:food_hub_app/config/app_config.dart';
|
import 'package:food_hub_app/config/app_config.dart';
|
||||||
import 'package:food_hub_app/provider/food_provider.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/form.dart';
|
||||||
import 'package:food_hub_app/widgets/common/image.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';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class MomentFormPage extends StatefulWidget {
|
class MomentForm extends StatefulWidget {
|
||||||
const MomentFormPage({super.key});
|
const MomentForm({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MomentFormPage> createState() => _MomentFormPageState();
|
State<MomentForm> createState() => _MomentFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MomentFormPageState extends State<MomentFormPage> {
|
class _MomentFormState extends State<MomentForm> {
|
||||||
final _formKey = GlobalKey<FormBuilderState>();
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
final _focusNode = FocusNode();
|
final _focusNode = FocusNode();
|
||||||
static const String _contentField = 'content';
|
static const String _contentField = 'content';
|
||||||
@@ -34,8 +35,8 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
|||||||
name: _contentField,
|
name: _contentField,
|
||||||
initialValue: provider.momentFormItem.content,
|
initialValue: provider.momentFormItem.content,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
maxLines: 5,
|
maxLines: 8,
|
||||||
minLines: 3,
|
minLines: 5,
|
||||||
maxLength: maxContentLength,
|
maxLength: maxContentLength,
|
||||||
buildCounter: (
|
buildCounter: (
|
||||||
context, {
|
context, {
|
||||||
@@ -75,7 +76,7 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 2,
|
||||||
crossAxisSpacing: 8,
|
crossAxisSpacing: 8,
|
||||||
mainAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
childAspectRatio: 4 / 3,
|
childAspectRatio: 4 / 3,
|
||||||
@@ -111,7 +112,11 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
|||||||
_buildContentField(provider),
|
_buildContentField(provider),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
buildFormLabel(context: context, text: '上传图片(最多9张)', 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),
|
||||||
@@ -146,11 +151,10 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
|||||||
if (result != null) {
|
if (result != null) {
|
||||||
// 限制选择数量
|
// 限制选择数量
|
||||||
final filesToUpload = result.files.take(remainingCount).toList();
|
final filesToUpload = result.files.take(remainingCount).toList();
|
||||||
|
|
||||||
LoadingDialog.show(context, message: '上传中');
|
LoadingDialog.show(context, message: '上传中');
|
||||||
|
|
||||||
|
try {
|
||||||
final newImageUrls = <String>[];
|
final newImageUrls = <String>[];
|
||||||
|
|
||||||
// 逐个上传图片
|
// 逐个上传图片
|
||||||
for (final file in filesToUpload) {
|
for (final file in filesToUpload) {
|
||||||
final fileName = await MinIOHelper().uploadFile(
|
final fileName = await MinIOHelper().uploadFile(
|
||||||
@@ -162,8 +166,12 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新图片列表
|
// 更新图片列表
|
||||||
LoadingDialog.hide(context);
|
|
||||||
provider.addMomentFormImage(newImageUrls);
|
provider.addMomentFormImage(newImageUrls);
|
||||||
|
} catch (e) {
|
||||||
|
ToastUtil.error(e.toString());
|
||||||
|
} finally {
|
||||||
|
LoadingDialog.hide(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,24 +213,17 @@ class _MomentFormPageState extends State<MomentFormPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final colors = Theme.of(context).colorScheme;
|
|
||||||
final provider = context.watch<FoodProvider>();
|
final provider = context.watch<FoodProvider>();
|
||||||
|
|
||||||
final String title = provider.isEditing ? '编辑朋友圈' : '新增朋友圈';
|
return SingleChildScrollView(
|
||||||
|
|
||||||
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(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: EdgeInsets.all(10),
|
||||||
child: CommonCard(child: _buildFormBuilder(provider)),
|
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/app_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/widgets/profile/basic_info.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';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ProfilePage extends StatefulWidget {
|
class ProfilePage extends StatefulWidget {
|
||||||
@@ -44,8 +46,13 @@ class ProfilePageState extends State<ProfilePage> {
|
|||||||
Navigator.pushNamed(context, '/momentUser');
|
Navigator.pushNamed(context, '/momentUser');
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFunctionButtons() {
|
Widget _buildFunctionButtons(BuildContext context) {
|
||||||
return CommonCard(
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return GlassCard(
|
||||||
|
padding: EdgeInsetsGeometry.symmetric(vertical: 0, horizontal: 10),
|
||||||
|
useOwnLayer: true,
|
||||||
|
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildFunctionButton(
|
_buildFunctionButton(
|
||||||
@@ -56,13 +63,13 @@ class ProfilePageState extends State<ProfilePage> {
|
|||||||
// LogUtils.i('点击编辑资料');
|
// LogUtils.i('点击编辑资料');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1, thickness: 0.2),
|
||||||
_buildFunctionButton(
|
_buildFunctionButton(
|
||||||
icon: Icons.group_outlined,
|
icon: Icons.group_outlined,
|
||||||
text: '朋友圈',
|
text: '朋友圈',
|
||||||
onTap: _onTapMoment,
|
onTap: _onTapMoment,
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1, thickness: 0.2),
|
||||||
_buildFunctionButton(
|
_buildFunctionButton(
|
||||||
icon: Icons.favorite,
|
icon: Icons.favorite,
|
||||||
text: '我的收藏',
|
text: '我的收藏',
|
||||||
@@ -71,7 +78,7 @@ class ProfilePageState extends State<ProfilePage> {
|
|||||||
// LogUtils.i('点击浏览历史');
|
// LogUtils.i('点击浏览历史');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1, thickness: 0.2),
|
||||||
_buildFunctionButton(
|
_buildFunctionButton(
|
||||||
icon: Icons.lock,
|
icon: Icons.lock,
|
||||||
text: '更改密码',
|
text: '更改密码',
|
||||||
@@ -80,11 +87,11 @@ class ProfilePageState extends State<ProfilePage> {
|
|||||||
// LogUtils.i('点击帮助与反馈');
|
// LogUtils.i('点击帮助与反馈');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1, thickness: 0.2),
|
||||||
_buildFunctionButton(icon: Icons.message, text: '反馈意见', onTap: () {}),
|
_buildFunctionButton(icon: Icons.message, text: '反馈意见', onTap: () {}),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1, thickness: 0.2),
|
||||||
_buildFunctionButton(icon: Icons.refresh, text: '检查更新', onTap: () {}),
|
_buildFunctionButton(icon: Icons.refresh, text: '检查更新', onTap: () {}),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1, thickness: 0.2),
|
||||||
_buildFunctionButton(
|
_buildFunctionButton(
|
||||||
icon: Icons.exit_to_app,
|
icon: Icons.exit_to_app,
|
||||||
text: '退出登录',
|
text: '退出登录',
|
||||||
@@ -117,13 +124,13 @@ class ProfilePageState extends State<ProfilePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContent(UserProvider provider) {
|
Widget _buildContent(UserProvider provider, BuildContext context) {
|
||||||
final user = provider.currentUser;
|
final user = provider.currentUser;
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: double.infinity, child: ProfileInfo(user: user)),
|
SizedBox(width: double.infinity, child: ProfileInfo(user: user)),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildFunctionButtons(),
|
_buildFunctionButtons(context),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildVersionInfo(),
|
_buildVersionInfo(),
|
||||||
],
|
],
|
||||||
@@ -134,15 +141,27 @@ class ProfilePageState extends State<ProfilePage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<UserProvider>();
|
final provider = context.watch<UserProvider>();
|
||||||
|
|
||||||
return Stack(
|
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: [
|
children: [
|
||||||
if (provider.isLoading)
|
if (provider.isLoading)
|
||||||
buildLoadingIndicator()
|
buildLoadingIndicator()
|
||||||
else if (provider.error.isNotEmpty)
|
else if (provider.error.isNotEmpty)
|
||||||
Text(provider.error)
|
Text(provider.error)
|
||||||
else
|
else
|
||||||
_buildContent(provider),
|
SingleChildScrollView(child: _buildContent(provider, context)),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
import 'package:easy_refresh/easy_refresh.dart';
|
||||||
import 'package:flutter/material.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/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/common/index.dart';
|
||||||
import 'package:food_hub_app/widgets/recipe/calendar.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/list.dart';
|
||||||
@@ -24,12 +29,68 @@ class RecordPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RecordPageState extends State<RecordPage> {
|
class _RecordPageState extends State<RecordPage> {
|
||||||
|
final EasyRefreshController _freshController = EasyRefreshController(
|
||||||
|
controlFinishRefresh: true,
|
||||||
|
controlFinishLoad: true,
|
||||||
|
);
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
bool _showScrollToTop = false;
|
||||||
final List<Widget> _tabPages = [
|
final List<Widget> _tabPages = [
|
||||||
RecipeList(),
|
RecipeList(),
|
||||||
RecipeCalendar(),
|
RecipeCalendar(),
|
||||||
RecipeTimeline(),
|
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({
|
Widget _buildTabs({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required RecordTab currentTab,
|
required RecordTab currentTab,
|
||||||
@@ -52,24 +113,135 @@ class _RecordPageState extends State<RecordPage> {
|
|||||||
provider.onRecordTabChange();
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<FoodProvider>();
|
final provider = context.watch<FoodProvider>();
|
||||||
|
|
||||||
return Column(
|
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: () => {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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: [
|
children: [
|
||||||
_buildTabs(
|
_buildTabs(
|
||||||
context: context,
|
context: context,
|
||||||
currentTab: provider.currentRecordTab,
|
currentTab: provider.currentRecordTab,
|
||||||
onTabChanged: (tab) => _onTabChange(tab, provider),
|
onTabChanged: (tab) => _onTabChange(tab, provider),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(child: _tabPages[provider.currentRecordTab.index]),
|
||||||
child: IndexedStack(
|
|
||||||
index: provider.currentRecordTab.index,
|
|
||||||
children: _tabPages,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.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/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/dialog_widget.dart';
|
||||||
import 'package:flutter_common/widget/loading_widget.dart';
|
import 'package:flutter_common/widget/loading_widget.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:food_hub_app/apis/recipe.dart';
|
import 'package:food_hub_app/apis/recipe.dart';
|
||||||
import 'package:food_hub_app/config/app_config.dart';
|
import 'package:food_hub_app/config/app_config.dart';
|
||||||
import 'package:food_hub_app/provider/food_provider.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/form.dart';
|
||||||
import 'package:food_hub_app/widgets/common/image.dart';
|
import 'package:food_hub_app/widgets/common/image.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class RecordFormPage extends StatefulWidget {
|
class RecordForm extends StatefulWidget {
|
||||||
const RecordFormPage({super.key});
|
const RecordForm({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RecordFormPage> createState() => _RecordFormPageState();
|
State<RecordForm> createState() => _RecordFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RecordFormPageState extends State<RecordFormPage> {
|
class _RecordFormState extends State<RecordForm> {
|
||||||
final _formKey = GlobalKey<FormBuilderState>();
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
static const String _nameField = 'name';
|
static const String _nameField = 'name';
|
||||||
static const String _dateField = 'date';
|
static const String _dateField = 'date';
|
||||||
@@ -49,19 +49,19 @@ class _RecordFormPageState extends State<RecordFormPage> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
buildFormLabel(context: context, text: '菜谱名称', isRequired: true),
|
buildFormLabel(context: context, text: '菜谱名称', isRequired: true),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 12),
|
||||||
_buildRecipeField(provider),
|
_buildRecipeField(provider),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
buildFormLabel(context: context, text: '完成时间', isRequired: true),
|
buildFormLabel(context: context, text: '完成时间', isRequired: true),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 12),
|
||||||
_buildDateField(provider),
|
_buildDateField(provider),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
buildFormLabel(context: context, text: '上传图片', isRequired: true),
|
buildFormLabel(context: context, text: '上传图片', isRequired: true),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 12),
|
||||||
_buildImageField(provider),
|
_buildImageField(provider),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
buildFormButtonGroup(
|
buildFormButtonGroup(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -254,13 +254,18 @@ class _RecordFormPageState extends State<RecordFormPage> {
|
|||||||
PlatformFile file = result.files.first;
|
PlatformFile file = result.files.first;
|
||||||
LoadingDialog.show(context, message: '上传中');
|
LoadingDialog.show(context, message: '上传中');
|
||||||
|
|
||||||
|
try {
|
||||||
final fileName = await MinIOHelper().uploadFile(
|
final fileName = await MinIOHelper().uploadFile(
|
||||||
bucketName: AppConfig.bucketName,
|
bucketName: AppConfig.bucketName,
|
||||||
file: file,
|
file: file,
|
||||||
);
|
);
|
||||||
|
ToastUtil.success('上传成功');
|
||||||
LoadingDialog.hide(context);
|
|
||||||
provider.updateRecordFormItem(imageUrl: fileName);
|
provider.updateRecordFormItem(imageUrl: fileName);
|
||||||
|
} catch (e) {
|
||||||
|
ToastUtil.error(e.toString());
|
||||||
|
} finally {
|
||||||
|
LoadingDialog.hide(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,24 +331,17 @@ class _RecordFormPageState extends State<RecordFormPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final colors = Theme.of(context).colorScheme;
|
|
||||||
final provider = context.watch<FoodProvider>();
|
final provider = context.watch<FoodProvider>();
|
||||||
|
|
||||||
final String title = provider.isEditing ? '编辑记录' : '新增记录';
|
return SingleChildScrollView(
|
||||||
|
|
||||||
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(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(10),
|
padding: EdgeInsets.all(10),
|
||||||
child: CommonCard(child: _buildFormBuilder(provider)),
|
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/chart.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:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class StatsPage extends StatefulWidget {
|
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(
|
return GridView.count(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
@@ -34,42 +37,62 @@ class _StatsPage extends State<StatsPage> {
|
|||||||
mainAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
childAspectRatio: 2,
|
childAspectRatio: 2,
|
||||||
children: [
|
children: [
|
||||||
StatsCard(
|
GlassCard(
|
||||||
|
useOwnLayer: true,
|
||||||
|
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||||
|
child: StatsCard(
|
||||||
icon: Icons.restaurant_menu,
|
icon: Icons.restaurant_menu,
|
||||||
title: '菜谱总数',
|
title: '菜谱总数',
|
||||||
value: provider.summaryStats.recipeCount.toString(),
|
value: provider.summaryStats.recipeCount.toString(),
|
||||||
unit: '个',
|
unit: '个',
|
||||||
),
|
),
|
||||||
StatsCard(
|
),
|
||||||
|
GlassCard(
|
||||||
|
useOwnLayer: true,
|
||||||
|
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||||
|
child: StatsCard(
|
||||||
icon: Icons.grid_view_rounded,
|
icon: Icons.grid_view_rounded,
|
||||||
title: '菜谱类别',
|
title: '菜谱类别',
|
||||||
value: provider.summaryStats.categoryCount.toString(),
|
value: provider.summaryStats.categoryCount.toString(),
|
||||||
unit: '种',
|
unit: '种',
|
||||||
),
|
),
|
||||||
StatsCard(
|
),
|
||||||
|
GlassCard(
|
||||||
|
useOwnLayer: true,
|
||||||
|
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||||
|
child: StatsCard(
|
||||||
icon: Icons.flag,
|
icon: Icons.flag,
|
||||||
title: '做菜次数',
|
title: '做菜次数',
|
||||||
value: provider.summaryStats.workCount.toString(),
|
value: provider.summaryStats.workCount.toString(),
|
||||||
unit: '个',
|
unit: '个',
|
||||||
),
|
),
|
||||||
StatsCard(
|
),
|
||||||
|
GlassCard(
|
||||||
|
useOwnLayer: true,
|
||||||
|
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||||
|
child: StatsCard(
|
||||||
icon: Icons.show_chart,
|
icon: Icons.show_chart,
|
||||||
title: '平均次数',
|
title: '平均次数',
|
||||||
value: provider.averageRecordCount.toStringAsFixed(2),
|
value: provider.averageRecordCount.toStringAsFixed(2),
|
||||||
unit: '次/月',
|
unit: '次/月',
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContent(FoodProvider provider) {
|
Widget _buildContent(BuildContext context, FoodProvider provider) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
_buildSummaryStats(provider),
|
_buildSummaryStats(context, provider),
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: chartHeight,
|
height: chartHeight,
|
||||||
child: CommonCard(
|
child: GlassCard(
|
||||||
|
useOwnLayer: true,
|
||||||
|
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||||
child: LineChart(
|
child: LineChart(
|
||||||
title: '记录统计',
|
title: '记录统计',
|
||||||
xAxisName: '日期',
|
xAxisName: '日期',
|
||||||
@@ -82,7 +105,9 @@ class _StatsPage extends State<StatsPage> {
|
|||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: chartHeight,
|
height: chartHeight,
|
||||||
child: CommonCard(
|
child: GlassCard(
|
||||||
|
useOwnLayer: true,
|
||||||
|
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||||
child: PieChart(
|
child: PieChart(
|
||||||
title: '菜谱统计',
|
title: '菜谱统计',
|
||||||
unit: '个',
|
unit: '个',
|
||||||
@@ -93,7 +118,9 @@ class _StatsPage extends State<StatsPage> {
|
|||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: chartHeight,
|
height: chartHeight,
|
||||||
child: CommonCard(
|
child: GlassCard(
|
||||||
|
useOwnLayer: true,
|
||||||
|
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||||
child: RankChart(title: '排行榜', data: provider.rankStats, unit: '次'),
|
child: RankChart(title: '排行榜', data: provider.rankStats, unit: '次'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -105,13 +132,28 @@ class _StatsPage extends State<StatsPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<FoodProvider>();
|
final provider = context.watch<FoodProvider>();
|
||||||
|
|
||||||
return Stack(
|
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: [
|
children: [
|
||||||
if (provider.isLoading)
|
if (provider.isLoading)
|
||||||
buildLoadingIndicator()
|
buildLoadingIndicator()
|
||||||
else
|
else
|
||||||
SingleChildScrollView(child: _buildContent(provider)),
|
SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.only(bottom: 90),
|
||||||
|
child: _buildContent(context, provider),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,15 +43,17 @@ Widget buildScrollToTop({
|
|||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required VoidCallback scrollToTop,
|
required VoidCallback scrollToTop,
|
||||||
}) {
|
}) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
return Positioned(
|
return Positioned(
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 20,
|
bottom: 80,
|
||||||
child: FloatingActionButton(
|
child: FloatingActionButton(
|
||||||
onPressed: scrollToTop,
|
onPressed: scrollToTop,
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: colors.primary,
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
mini: true,
|
mini: true,
|
||||||
child: const Icon(Icons.arrow_upward, color: Colors.white),
|
child: Icon(Icons.arrow_upward, color: colors.surface),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,26 +12,26 @@ InputDecoration buildInputDecoration({
|
|||||||
|
|
||||||
return InputDecoration(
|
return InputDecoration(
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
hintStyle: TextStyle(color: colors.onSurface.withAlpha(100)),
|
hintStyle: Theme.of(context).textTheme.bodyMedium,
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: colors.surface,
|
fillColor: colors.surface,
|
||||||
border: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderSide: const BorderSide(color: Color(0xFFE5E7EB)),
|
borderSide: BorderSide.none,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(color: colors.primary, width: 2),
|
borderSide: BorderSide(color: colors.primary, width: 2),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
errorBorder: OutlineInputBorder(
|
errorBorder: OutlineInputBorder(
|
||||||
borderSide: const BorderSide(color: Colors.red, width: 2),
|
borderSide: const BorderSide(color: Colors.red, width: 2),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14),
|
||||||
errorStyle: const TextStyle(fontSize: 12, height: 1, color: Colors.red),
|
errorStyle: const TextStyle(fontSize: 12, height: 1, color: Colors.red),
|
||||||
prefixIcon:
|
prefixIcon:
|
||||||
prefixIcon != null
|
prefixIcon != null
|
||||||
? Icon(prefixIcon, size: 20, color: Color(0xFF86909C))
|
? Icon(prefixIcon, size: 20, color: colors.primary)
|
||||||
: null,
|
: null,
|
||||||
prefixIconConstraints: const BoxConstraints(minWidth: 40),
|
prefixIconConstraints: const BoxConstraints(minWidth: 40),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/config/app_config.dart';
|
import 'package:food_hub_app/config/app_config.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
@@ -53,6 +54,8 @@ class _ImagePreviewPageState extends State<ImagePreviewPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final index = '${_currentIndex + 1}/${widget.images.length}';
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -63,10 +66,7 @@ class _ImagePreviewPageState extends State<ImagePreviewPage> {
|
|||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
// 显示实时更新的页码(当前索引+1 / 总数量)
|
// 显示实时更新的页码(当前索引+1 / 总数量)
|
||||||
title: Text(
|
title: Text(index, style: const TextStyle(color: Colors.white)),
|
||||||
'${_currentIndex + 1}/${widget.images.length}',
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: PhotoViewGallery(
|
body: PhotoViewGallery(
|
||||||
@@ -95,21 +95,6 @@ class CommonImage extends StatelessWidget {
|
|||||||
|
|
||||||
const CommonImage({super.key, required this.imageUrls, required this.index});
|
const CommonImage({super.key, required this.imageUrls, required this.index});
|
||||||
|
|
||||||
Widget _buildImageLoadingIndicator(ImageChunkEvent? progress) {
|
|
||||||
final value =
|
|
||||||
progress?.expectedTotalBytes != null
|
|
||||||
? progress!.cumulativeBytesLoaded / progress.expectedTotalBytes!
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 30,
|
|
||||||
height: 30,
|
|
||||||
child: CircularProgressIndicator(value: value),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildErrorImage() {
|
Widget _buildErrorImage() {
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
@@ -140,14 +125,16 @@ class CommonImage extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
_navigateToImagePreview(context, imageUrls, index);
|
_navigateToImagePreview(context, imageUrls, index);
|
||||||
},
|
},
|
||||||
child: Image.network(
|
child: CachedNetworkImage(
|
||||||
'${AppConfig.imageBaseUrl}${imageUrls[index]}',
|
imageUrl: '${AppConfig.imageBaseUrl}${imageUrls[index]}',
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
loadingBuilder: (context, child, loadingProgress) {
|
progressIndicatorBuilder:
|
||||||
if (loadingProgress == null) return child;
|
(context, url, downloadProgress) => CircularProgressIndicator(
|
||||||
return _buildImageLoadingIndicator(loadingProgress);
|
strokeWidth: 1.5,
|
||||||
},
|
padding: EdgeInsetsGeometry.all(20),
|
||||||
errorBuilder: (context, error, stackTrace) => _buildErrorImage(),
|
value: downloadProgress.progress,
|
||||||
|
),
|
||||||
|
errorWidget: (context, error, stackTrace) => _buildErrorImage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -213,19 +200,19 @@ Widget buildImageUploadButton({
|
|||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 96,
|
width: double.infinity,
|
||||||
height: 96,
|
height: 250,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colors.surface,
|
color: colors.surface,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(color: Colors.black),
|
border: Border.all(color: colors.surface),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.add, color: colors.onSurface.withAlpha(100)),
|
Icon(Icons.camera_alt, color: colors.onSurface.withAlpha(100)),
|
||||||
SizedBox(height: 6),
|
SizedBox(height: 6),
|
||||||
Text('添加图片', style: TextStyle(color: colors.onSurface.withAlpha(100))),
|
Text('点击上传美食图片', style: Theme.of(context).textTheme.bodyMedium),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ Widget buildToggleSwitch<T extends Enum>({
|
|||||||
totalSwitches: tabValues.length,
|
totalSwitches: tabValues.length,
|
||||||
labels: labels,
|
labels: labels,
|
||||||
activeBgColor: [colors.primary],
|
activeBgColor: [colors.primary],
|
||||||
activeFgColor: Colors.white,
|
activeFgColor: colors.surface,
|
||||||
inactiveBgColor: colors.surfaceContainer,
|
inactiveBgColor: colors.surface,
|
||||||
inactiveFgColor: colors.onSurface,
|
inactiveFgColor: colors.onSurface,
|
||||||
cornerRadius: 12.0,
|
cornerRadius: 12.0,
|
||||||
customTextStyles: [TextStyle(fontSize: 12, fontWeight: FontWeight.w500)],
|
customTextStyles: [TextStyle(fontSize: 12, fontWeight: FontWeight.w500)],
|
||||||
@@ -85,10 +85,10 @@ Widget buildTag(BuildContext context, String title) {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colors.primary.withAlpha(60),
|
color: colors.primary,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Text(title, style: TextStyle(color: colors.primary)),
|
child: Text(title, style: TextStyle(color: colors.surface)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,24 +135,10 @@ Widget buildInfoButton({
|
|||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
child: Text(text, style: TextStyle(color: Colors.white)),
|
child: Text(text),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 取消按钮样式
|
|
||||||
ButtonStyle cancelButtonStyle() {
|
|
||||||
return ButtonStyle(
|
|
||||||
backgroundColor: WidgetStateProperty.all(Colors.grey),
|
|
||||||
shape: WidgetStateProperty.all(
|
|
||||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Text buttonText({required String text}) {
|
|
||||||
return Text(text, style: TextStyle(color: Colors.white));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 图片错误显示
|
/// 图片错误显示
|
||||||
Widget errorImageContainer(double height) {
|
Widget errorImageContainer(double height) {
|
||||||
return Container(
|
return Container(
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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/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';
|
|
||||||
|
|
||||||
class MomentCard extends StatefulWidget {
|
class MomentCard extends StatefulWidget {
|
||||||
final Moment moment;
|
final Moment moment;
|
||||||
@@ -26,21 +25,24 @@ class MomentCardState extends State<MomentCard> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
InkWell(onTap: () => _onTapUser(context), child: _buildAvatar()),
|
InkWell(
|
||||||
|
onTap: () => _onTapUser(context),
|
||||||
|
child: _buildAvatar(context),
|
||||||
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildNickname(),
|
_buildNickname(context),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
_buildContent(),
|
_buildContent(context),
|
||||||
if (widget.moment.imageList.isNotEmpty) ...[
|
if (widget.moment.imageList.isNotEmpty) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildPostImages(),
|
_buildPostImages(),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
_buildTime(),
|
_buildTime(context),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
_buildActionButtons(),
|
_buildActionButtons(),
|
||||||
if (widget.moment.commentList!.isNotEmpty) ...[
|
if (widget.moment.commentList!.isNotEmpty) ...[
|
||||||
@@ -69,38 +71,35 @@ class MomentCardState extends State<MomentCard> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 构建头像组件
|
// 构建头像组件
|
||||||
Widget _buildAvatar() {
|
Widget _buildAvatar(BuildContext context) {
|
||||||
if (widget.moment.avatar!.isEmpty) {
|
if (widget.moment.avatar!.isEmpty) {
|
||||||
return TDAvatar(
|
return CircleAvatar(
|
||||||
size: TDAvatarSize.medium,
|
radius: 18,
|
||||||
type: TDAvatarType.customText,
|
|
||||||
shape: TDAvatarShape.circle,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
text: widget.moment.username?[0],
|
foregroundColor: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Text(widget.moment.username![0], style: TextStyle(fontSize: 14)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return TDAvatar(
|
return CircleAvatar(
|
||||||
size: TDAvatarSize.medium,
|
radius: 18,
|
||||||
type: TDAvatarType.normal,
|
backgroundImage: NetworkImage('${AppConfig.imageBaseUrl}/${widget.moment.avatar}')
|
||||||
fit: BoxFit.contain,
|
|
||||||
avatarUrl: '${AppConfig.imageBaseUrl}/${widget.moment.avatar}',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建昵称组件
|
// 构建昵称组件
|
||||||
Widget _buildNickname() {
|
Widget _buildNickname(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
widget.moment.username ?? "",
|
widget.moment.username ?? "",
|
||||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建内容组件
|
// 构建内容组件
|
||||||
Widget _buildContent() {
|
Widget _buildContent(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
widget.moment.content,
|
widget.moment.content,
|
||||||
style: const TextStyle(fontSize: 15, height: 1.3),
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,10 +141,10 @@ class MomentCardState extends State<MomentCard> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 构建时间组件
|
// 构建时间组件
|
||||||
Widget _buildTime() {
|
Widget _buildTime(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
widget.moment.date ?? "",
|
widget.moment.date ?? "",
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,14 +177,14 @@ class MomentCardState extends State<MomentCard> {
|
|||||||
alignment: Alignment.bottomLeft,
|
alignment: Alignment.bottomLeft,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.thumb_up_alt_outlined),
|
Icon(Icons.thumb_up_alt_outlined),
|
||||||
Positioned(
|
// Positioned(
|
||||||
left: 20,
|
// left: 20,
|
||||||
bottom: 15,
|
// bottom: 15,
|
||||||
child: TDBadge(
|
// child: TDBadge(
|
||||||
TDBadgeType.message,
|
// TDBadgeType.message,
|
||||||
count: (widget.moment.likeList?.length ?? 0).toString(),
|
// count: (widget.moment.likeList?.length ?? 0).toString(),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -196,14 +195,14 @@ class MomentCardState extends State<MomentCard> {
|
|||||||
alignment: Alignment.bottomLeft,
|
alignment: Alignment.bottomLeft,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.comment_outlined),
|
Icon(Icons.comment_outlined),
|
||||||
Positioned(
|
// Positioned(
|
||||||
left: 20,
|
// left: 20,
|
||||||
bottom: 15,
|
// bottom: 15,
|
||||||
child: TDBadge(
|
// child: TDBadge(
|
||||||
TDBadgeType.message,
|
// TDBadgeType.message,
|
||||||
count: (widget.moment.commentList?.length ?? 0).toString(),
|
// count: (widget.moment.commentList?.length ?? 0).toString(),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ 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/session.dart';
|
import 'package:food_hub_app/models/session.dart';
|
||||||
import 'package:food_hub_app/widgets/common/index.dart';
|
import 'package:food_hub_app/widgets/common/index.dart';
|
||||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||||
|
import 'package:liquid_glass_widgets/widgets/containers/glass_card.dart';
|
||||||
|
|
||||||
class ProfileInfo extends StatelessWidget {
|
class ProfileInfo extends StatelessWidget {
|
||||||
final User user;
|
final User user;
|
||||||
@@ -13,19 +14,21 @@ class ProfileInfo extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildAvatar(BuildContext context, User user) {
|
Widget _buildAvatar(BuildContext context, User user) {
|
||||||
if (user.avatar == null) {
|
if (user.avatar == null) {
|
||||||
return TDAvatar(
|
return CircleAvatar(
|
||||||
size: TDAvatarSize.large,
|
radius: 24,
|
||||||
type: TDAvatarType.customText,
|
|
||||||
shape: TDAvatarShape.circle,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
text: user.username.isNotEmpty ? user.username[0] : '?',
|
foregroundColor: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Text(
|
||||||
|
user.username.isNotEmpty ? user.username[0] : '?',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return TDAvatar(
|
return CircleAvatar(
|
||||||
size: TDAvatarSize.large,
|
radius: 24,
|
||||||
type: TDAvatarType.normal,
|
backgroundImage: NetworkImage(
|
||||||
fit: BoxFit.contain,
|
'${AppConfig.imageBaseUrl}/${user.avatar}',
|
||||||
avatarUrl: '${AppConfig.imageBaseUrl}/${user.avatar}',
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,22 +44,24 @@ class ProfileInfo extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 16, color: colors.primary),
|
Icon(icon, size: 16, color: colors.primary),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(text),
|
Text(text, style: Theme.of(context).textTheme.bodyMedium),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CommonCard(
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return GlassCard(
|
||||||
|
padding: EdgeInsetsGeometry.all(10),
|
||||||
|
useOwnLayer: true,
|
||||||
|
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildAvatar(context, user),
|
_buildAvatar(context, user),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(user.username, style: Theme.of(context).textTheme.titleMedium),
|
||||||
user.username,
|
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
if (user.tags != null && user.tags!.isNotEmpty)
|
if (user.tags != null && user.tags!.isNotEmpty)
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_common/widget/common_widget.dart';
|
|||||||
import 'package:food_hub_app/models/recipe.dart';
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
import 'package:food_hub_app/provider/food_provider.dart';
|
import 'package:food_hub_app/provider/food_provider.dart';
|
||||||
import 'package:food_hub_app/utils/index.dart';
|
import 'package:food_hub_app/utils/index.dart';
|
||||||
|
import 'package:food_hub_app/views/record_form.dart';
|
||||||
import 'package:food_hub_app/widgets/common/index.dart';
|
import 'package:food_hub_app/widgets/common/index.dart';
|
||||||
import 'package:table_calendar/table_calendar.dart';
|
import 'package:table_calendar/table_calendar.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -62,19 +63,16 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
|
|||||||
Text(formatDateTime(provider.selectedDay, 'MM月dd日 EEEE')),
|
Text(formatDateTime(provider.selectedDay, 'MM月dd日 EEEE')),
|
||||||
SizedBox(height: 4),
|
SizedBox(height: 4),
|
||||||
if (provider.selectRecordList.isEmpty)
|
if (provider.selectRecordList.isEmpty)
|
||||||
buildEmptyData()
|
SizedBox(height: 100, child: buildEmptyData())
|
||||||
else
|
else
|
||||||
Expanded(
|
Column(
|
||||||
child: ListView.separated(
|
children:
|
||||||
itemCount: provider.selectRecordList.length,
|
provider.selectRecordList.map((record) {
|
||||||
separatorBuilder: (context, index) => SizedBox(height: 8),
|
return Padding(
|
||||||
itemBuilder: (context, index) {
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
return _buildRecordItem(
|
child: _buildRecordItem(context, record),
|
||||||
context,
|
|
||||||
provider.selectRecordList[index],
|
|
||||||
);
|
);
|
||||||
},
|
}).toList(),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -112,7 +110,15 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
|
|||||||
|
|
||||||
provider.initRecordForm(record);
|
provider.initRecordForm(record);
|
||||||
provider.isEditing = true;
|
provider.isEditing = true;
|
||||||
Navigator.pushNamed(context, "/recordForm");
|
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
|
),
|
||||||
|
builder: (context) => RecordForm(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRecordItem(BuildContext context, FoodRecord record) {
|
Widget _buildRecordItem(BuildContext context, FoodRecord record) {
|
||||||
@@ -195,12 +201,15 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContent(FoodProvider provider) {
|
Widget _buildContent(FoodProvider provider) {
|
||||||
return Column(
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.only(bottom: 80),
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
CommonCard(child: _recipeCalendar(provider)),
|
CommonCard(child: _recipeCalendar(provider)),
|
||||||
SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Expanded(child: CommonCard(child: _dailyItem(context, provider))),
|
CommonCard(child: _dailyItem(context, provider)),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,13 +217,10 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<FoodProvider>();
|
final provider = context.watch<FoodProvider>();
|
||||||
|
|
||||||
return Stack(
|
if (provider.isLoading) {
|
||||||
children: [
|
return buildLoadingIndicator();
|
||||||
if (provider.isLoading)
|
}
|
||||||
buildLoadingIndicator()
|
|
||||||
else
|
return _buildContent(provider);
|
||||||
_buildContent(provider),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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/models/recipe.dart';
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
import 'package:food_hub_app/widgets/common/image.dart';
|
import 'package:food_hub_app/widgets/common/image.dart';
|
||||||
|
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||||
|
|
||||||
class RecipeCard extends StatelessWidget {
|
class RecipeCard extends StatelessWidget {
|
||||||
final RecipeSummary recipe;
|
final RecipeSummary recipe;
|
||||||
@@ -17,14 +18,22 @@ class RecipeCard extends StatelessWidget {
|
|||||||
final List<String> imageUrls =
|
final List<String> imageUrls =
|
||||||
recipe.recordList.reversed.map((record) => record.imageUrl).toList();
|
recipe.recordList.reversed.map((record) => record.imageUrl).toList();
|
||||||
|
|
||||||
return CommonCard(
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return GlassCard(
|
||||||
|
padding: EdgeInsetsGeometry.all(10),
|
||||||
|
useOwnLayer: true,
|
||||||
|
settings: LiquidGlassSettings(glassColor: colors.surface),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
CommonImage(imageUrls: imageUrls, index: 0),
|
CommonImage(imageUrls: imageUrls, index: 0),
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
_buildRecipeContent(context),
|
InkWell(
|
||||||
|
onTap: () => navigatorToRecipeDetail(context),
|
||||||
|
child: _buildRecipeContent(context),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -32,6 +41,7 @@ class RecipeCard extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildRecipeContent(BuildContext context) {
|
Widget _buildRecipeContent(BuildContext context) {
|
||||||
final colors = Theme.of(context).colorScheme;
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -42,17 +52,10 @@ class RecipeCard extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
recipe.name,
|
recipe.name,
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
style: textTheme.titleMedium,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
InkWell(
|
|
||||||
onTap: () => navigatorToRecipeDetail(context),
|
|
||||||
child: Icon(
|
|
||||||
Icons.arrow_circle_right_sharp,
|
|
||||||
color: colors.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
@@ -62,19 +65,13 @@ class RecipeCard extends StatelessWidget {
|
|||||||
_buildIconText(
|
_buildIconText(
|
||||||
icon: Icons.food_bank,
|
icon: Icons.food_bank,
|
||||||
text: recipe.category,
|
text: recipe.category,
|
||||||
color: Color(0xFF6A5ACD),
|
color: colors.primary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
_buildIconText(
|
_buildIconText(
|
||||||
icon: Icons.thumb_up_alt_outlined,
|
icon: Icons.note_add,
|
||||||
text: recipe.likeCount.toString(),
|
text: recipe.recordList.length.toString(),
|
||||||
color: Color(0xFFDC143C),
|
color: colors.primary,
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
_buildIconText(
|
|
||||||
icon: Icons.star_outline,
|
|
||||||
text: recipe.favouriteCount.toString(),
|
|
||||||
color: Color(0xFF20B2AA),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -23,55 +23,9 @@ class _RecipeListState extends State<RecipeList> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSelectCategory(FoodProvider provider) {
|
|
||||||
final colors = Theme.of(context).colorScheme;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: 140,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: colors.surfaceContainer,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: DropdownButton<String>(
|
|
||||||
value: provider.queryCategory,
|
|
||||||
isExpanded: true,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
dropdownColor: colors.surfaceContainer,
|
|
||||||
elevation: 6,
|
|
||||||
underline: Container(),
|
|
||||||
items:
|
|
||||||
provider.categoryList.map((String value) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: value,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: colors.primary,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(value, style: TextStyle(fontSize: 14)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
provider.queryCategory = value!;
|
|
||||||
provider.refreshRecipeList();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildRecipeList(FoodProvider provider) {
|
Widget _buildRecipeList(FoodProvider provider) {
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
|
padding: const EdgeInsets.only(bottom: 80),
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
crossAxisSpacing: 8,
|
crossAxisSpacing: 8,
|
||||||
@@ -91,8 +45,6 @@ class _RecipeListState extends State<RecipeList> {
|
|||||||
} else {
|
} else {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
_buildSelectCategory(provider),
|
|
||||||
SizedBox(height: 5),
|
|
||||||
Expanded(child: _buildRecipeList(provider)),
|
Expanded(child: _buildRecipeList(provider)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,26 +28,24 @@ class _RecipeTimeline extends State<RecipeTimeline> {
|
|||||||
SizedBox(width: 5),
|
SizedBox(width: 5),
|
||||||
Text(
|
Text(
|
||||||
record.date,
|
record.date,
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 6),
|
SizedBox(height: 8),
|
||||||
Padding(
|
CommonCard(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 0),
|
|
||||||
child: CommonCard(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
record.name,
|
record.name,
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
CommonImage(imageUrls: [record.imageUrl], index: 0),
|
CommonImage(imageUrls: [record.imageUrl], index: 0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 20)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -59,6 +57,7 @@ class _RecipeTimeline extends State<RecipeTimeline> {
|
|||||||
final colors = Theme.of(context).colorScheme;
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
return Timeline.tileBuilder(
|
return Timeline.tileBuilder(
|
||||||
|
padding: const EdgeInsets.only(bottom: 80),
|
||||||
theme: TimelineThemeData(nodePosition: 0, indicatorPosition: 0),
|
theme: TimelineThemeData(nodePosition: 0, indicatorPosition: 0),
|
||||||
builder: TimelineTileBuilder.connected(
|
builder: TimelineTileBuilder.connected(
|
||||||
itemCount: recordList.length,
|
itemCount: recordList.length,
|
||||||
|
|||||||
@@ -6,14 +6,10 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <file_selector_linux/file_selector_plugin.h>
|
|
||||||
#include <rive_native/rive_native_plugin.h>
|
#include <rive_native/rive_native_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
|
||||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
|
||||||
g_autoptr(FlPluginRegistrar) rive_native_registrar =
|
g_autoptr(FlPluginRegistrar) rive_native_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "RiveNativePlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "RiveNativePlugin");
|
||||||
rive_native_plugin_register_with_registrar(rive_native_registrar);
|
rive_native_plugin_register_with_registrar(rive_native_registrar);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
file_selector_linux
|
|
||||||
rive_native
|
rive_native
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ import FlutterMacOS
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import file_picker
|
import file_picker
|
||||||
import file_selector_macos
|
|
||||||
import flutter_image_compress_macos
|
import flutter_image_compress_macos
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import rive_native
|
import rive_native
|
||||||
import share_plus
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
import sqflite_darwin
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
|
||||||
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
RiveNativePlugin.register(with: registry.registrar(forPlugin: "RiveNativePlugin"))
|
RiveNativePlugin.register(with: registry.registrar(forPlugin: "RiveNativePlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
220
pubspec.lock
220
pubspec.lock
@@ -121,6 +121,30 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.10.1"
|
version: "8.10.1"
|
||||||
|
cached_network_image:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cached_network_image
|
||||||
|
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.4.1"
|
||||||
|
cached_network_image_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cached_network_image_platform_interface
|
||||||
|
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.1"
|
||||||
|
cached_network_image_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cached_network_image_web
|
||||||
|
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -213,18 +237,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dio
|
name: dio
|
||||||
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
|
sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.8.0+1"
|
version: "5.9.2"
|
||||||
dio_web_adapter:
|
dio_web_adapter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dio_web_adapter
|
name: dio_web_adapter
|
||||||
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
easy_refresh:
|
easy_refresh:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -233,14 +257,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.0"
|
version: "3.4.0"
|
||||||
|
equatable:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: equatable
|
||||||
|
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.8"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.3"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -310,6 +342,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_cache_manager:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_cache_manager
|
||||||
|
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.4.1"
|
||||||
flutter_carousel_widget:
|
flutter_carousel_widget:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -402,22 +442,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.28"
|
version: "2.0.28"
|
||||||
flutter_slidable:
|
flutter_shaders:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_slidable
|
name: flutter_shaders
|
||||||
sha256: a857de7ea701f276fd6a6c4c67ae885b60729a3449e42766bb0e655171042801
|
sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "0.1.3"
|
||||||
flutter_swiper_null_safety:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: flutter_swiper_null_safety
|
|
||||||
sha256: "5a855e0080d035c08e82f8b7fd2f106344943a30c9ab483b2584860a2f22eaaf"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.2"
|
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -432,10 +464,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fluttertoast
|
name: fluttertoast
|
||||||
sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1"
|
sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.12"
|
version: "8.2.14"
|
||||||
form_builder_validators:
|
form_builder_validators:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -560,10 +592,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0"
|
version: "0.20.2"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -600,26 +632,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.8"
|
version: "11.0.2"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.9"
|
version: "3.0.10"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_testing
|
name: leak_tracker_testing
|
||||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.2"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -628,6 +660,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.1.1"
|
||||||
|
liquid_glass_widgets:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: liquid_glass_widgets
|
||||||
|
sha256: "776adcdb7d48af0b935642425936813074355830eb0a47ecca8b227549d5b057"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.16.3"
|
||||||
logger:
|
logger:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -664,10 +704,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.17.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -692,6 +732,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
octo_image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: octo_image
|
||||||
|
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -840,18 +888,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: rive
|
name: rive
|
||||||
sha256: fc0abf65d03d1c9afaeb35be9e71c7cf04d2d1f76e94e69d2af1b3ba413cddf9
|
sha256: "84a640f48122679e48397935ed1b971db19ef2e44bfcbae60e42fb5ce29a0a4e"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.14.0-dev.14"
|
version: "0.14.9"
|
||||||
rive_native:
|
rive_native:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: rive_native
|
name: rive_native
|
||||||
sha256: e9c7d36f19eb6d32f563825d4e9d5032b19a36d3ca3341641431035bca022d19
|
sha256: "85dc8413398f7540eab9b73c6d704ecd5051ab9c57e539eed85ccd2a69f119aa"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.17"
|
version: "0.1.9"
|
||||||
|
rxdart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: rxdart
|
||||||
|
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.28.0"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -872,26 +928,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.3"
|
version: "2.5.5"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
|
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.10"
|
version: "2.4.23"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4"
|
version: "2.5.6"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -904,10 +960,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_platform_interface
|
name: shared_preferences_platform_interface
|
||||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.2"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -977,6 +1033,46 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.1"
|
version: "1.10.1"
|
||||||
|
sqflite:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite
|
||||||
|
sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2+1"
|
||||||
|
sqflite_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_android
|
||||||
|
sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2+3"
|
||||||
|
sqflite_common:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_common
|
||||||
|
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.8"
|
||||||
|
sqflite_darwin:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_darwin
|
||||||
|
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
|
sqflite_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_platform_interface
|
||||||
|
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1013,34 +1109,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: syncfusion_flutter_charts
|
name: syncfusion_flutter_charts
|
||||||
sha256: c58ca79e072680af6f0554f7c4b91886c1d8808f2522b49d557f16fb5cb3bb04
|
sha256: "68fdb029dad34a46e4c9cfad8ad66fe29db7b303bd96849261ab2b23a168d0e8"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "30.1.41"
|
version: "30.2.7"
|
||||||
syncfusion_flutter_core:
|
syncfusion_flutter_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: syncfusion_flutter_core
|
name: syncfusion_flutter_core
|
||||||
sha256: "536753489e168f49659261d0abd02b101b34c0b1bde27898f2869aab05f1cbb6"
|
sha256: bfd026c0f9822b49ff26fed11cd3334519acb6a6ad4b0c81d9cd18df6af1c4c0
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "30.1.41"
|
version: "30.2.7"
|
||||||
|
synchronized:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: synchronized
|
||||||
|
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.4.0"
|
||||||
table_calendar:
|
table_calendar:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: table_calendar
|
name: table_calendar
|
||||||
sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63
|
sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.2.0"
|
||||||
tdesign_flutter:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: tdesign_flutter
|
|
||||||
sha256: b36b6f939f7a585184665202b6b3acf1922728962312b65182dbbff64c3b62f2
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.3"
|
|
||||||
tdesign_flutter_adaptation:
|
tdesign_flutter_adaptation:
|
||||||
dependency: "direct overridden"
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
@@ -1061,10 +1157,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.4"
|
version: "0.7.7"
|
||||||
timelines_plus:
|
timelines_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1141,10 +1237,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_math
|
name: vector_math
|
||||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.2.0"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1218,5 +1314,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.0 <4.0.0"
|
dart: ">=3.10.0 <4.0.0"
|
||||||
flutter: ">=3.29.0"
|
flutter: ">=3.38.0"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
name: food_hub_app
|
name: food_hub_app
|
||||||
@@ -6,14 +6,11 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
|
||||||
#include <rive_native/rive_native_plugin.h>
|
#include <rive_native/rive_native_plugin.h>
|
||||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
|
||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
|
||||||
RiveNativePluginRegisterWithRegistrar(
|
RiveNativePluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("RiveNativePlugin"));
|
registry->GetRegistrarForPlugin("RiveNativePlugin"));
|
||||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
file_selector_windows
|
|
||||||
rive_native
|
rive_native
|
||||||
share_plus
|
share_plus
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
|
|||||||
Reference in New Issue
Block a user