feat(plans): hide for ios

This commit is contained in:
mso7nueh
2025-08-01 10:53:28 +03:00
parent 8a26fa0990
commit f5d27ed056
4 changed files with 184 additions and 555 deletions

View File

@@ -1,4 +1,5 @@
String baseUrl = 'https://eatvio.ru';
bool hidePlans = true;
const List<int> durationValues = [7, 14, 21];

View File

@@ -1,4 +1,5 @@
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/widgets/add_item_button.dart';
import 'package:eatvio_app/core/widgets/custom_search_bar.dart';
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
@@ -32,24 +33,12 @@ class FeedScreen extends StatelessWidget {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<RecipesCubit>(
create: (context) => sl<RecipesCubit>()..loadRecipesList(),
),
BlocProvider<FeedProfileCubit>(
create: (context) => sl<FeedProfileCubit>()..getProfile(),
),
BlocProvider<FeedCubit>(
create: (context) => sl<FeedCubit>()..loadFeedBlogPostList(),
),
BlocProvider<DietsCubit>(
create: (context) => sl<DietsCubit>()..loadDiets(),
),
BlocProvider<PlansCubit>(
create: (context) => sl<PlansCubit>()..loadPlans(),
),
BlocProvider<ConsultantsCubit>(
create: (context) => sl<ConsultantsCubit>()..getConsultants(),
),
BlocProvider<RecipesCubit>(create: (context) => sl<RecipesCubit>()..loadRecipesList()),
BlocProvider<FeedProfileCubit>(create: (context) => sl<FeedProfileCubit>()..getProfile()),
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
BlocProvider<DietsCubit>(create: (context) => sl<DietsCubit>()..loadDiets()),
if (!hidePlans) BlocProvider<PlansCubit>(create: (context) => sl<PlansCubit>()..loadPlans()),
BlocProvider<ConsultantsCubit>(create: (context) => sl<ConsultantsCubit>()..getConsultants()),
],
child: const FeedScreenContent(),
);
@@ -69,7 +58,8 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
final dietsController = TextEditingController();
final plansController = TextEditingController();
final consultantsController = TextEditingController();
final List<String> chipsLabelList = ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты'];
late final List<String> chipsLabelList;
int _selectedChipIndex = 0;
final blogPostsScrollController = ScrollController();
final recipesScrollController = ScrollController();
@@ -78,12 +68,13 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
final consultantsScrollController = ScrollController();
@override
void initState() {
void initState() {
// Initialize chipsLabelList based on hidePlans setting
chipsLabelList = hidePlans ? ['Все', 'Рецепты', 'Диеты', 'Консультанты'] : ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты'];
consultantsScrollController.addListener(_onScrollConsultants);
super.initState();
}
}
void _onScrollConsultants() {
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
@@ -108,45 +99,36 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
heroTag: 'recipesTagsFilter',
backgroundColor: appTheme.blueA400,
shape: const CircleBorder(),
child: const Icon(
Icons.filter_list_rounded,
color: Colors.white,
),
child: const Icon(Icons.filter_list_rounded, color: Colors.white),
onPressed: () async {
final recipesCubit = context.read<RecipesCubit>();
await showModalBottomSheet(
useRootNavigator: true,
context: context,
builder: (context) => TagsModalWidget(
recipesCubit: recipesCubit,
),
builder: (context) => TagsModalWidget(recipesCubit: recipesCubit),
);
recipesCubit.loadRecipesList();
},
)
: null,
appBar: AppBar(
backgroundColor: Colors.white,
toolbarHeight: 0,
),
appBar: AppBar(backgroundColor: Colors.white, toolbarHeight: 0),
backgroundColor: Colors.grey.shade200,
body: NestedScrollView(
controller: switch (_selectedChipIndex) {
0 => blogPostsScrollController,
1 => recipesScrollController,
2 => dietsScrollController,
3 => plansScrollController,
4 => consultantsScrollController,
_ => blogPostsScrollController
3 when !hidePlans => plansScrollController,
3 when hidePlans => consultantsScrollController,
4 when !hidePlans => consultantsScrollController,
_ => blogPostsScrollController,
},
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
BlocBuilder<FeedProfileCubit, ProfileState>(
builder: (context, state) {
return SliverAppBar(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(12))),
backgroundColor: Colors.white,
foregroundColor: Colors.white,
automaticallyImplyLeading: false,
@@ -172,78 +154,71 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
searchController: switch (_selectedChipIndex) {
1 => recipesController,
2 => dietsController,
3 => plansController,
4 => consultantsController,
3 when !hidePlans => plansController,
3 when hidePlans => consultantsController,
4 when !hidePlans => consultantsController,
_ => postsController,
},
onChanged: (query) {
if (query.length > 2) {
EasyDebounce.debounce(
'searchFeed',
const Duration(milliseconds: 500),
() {
switch (_selectedChipIndex) {
case 0:
context.read<FeedCubit>().loadFeedBlogPostList(query: query);
break;
case 1:
context.read<RecipesCubit>().loadRecipesList(query: query);
break;
case 2:
context.read<DietsCubit>().loadDiets(query: query);
break;
case 3:
context.read<PlansCubit>().loadPlans(query: query);
break;
case 4:
context.read<ConsultantsCubit>().getConsultants();
break;
}
},
);
EasyDebounce.debounce('searchFeed', const Duration(milliseconds: 500), () {
switch (_selectedChipIndex) {
case 0:
context.read<FeedCubit>().loadFeedBlogPostList(query: query);
break;
case 1:
context.read<RecipesCubit>().loadRecipesList(query: query);
break;
case 2:
context.read<DietsCubit>().loadDiets(query: query);
break;
case 3 when !hidePlans:
context.read<PlansCubit>().loadPlans(query: query);
break;
case 3 when hidePlans:
case 4 when !hidePlans:
context.read<ConsultantsCubit>().getConsultants();
break;
}
});
} else {
EasyDebounce.debounce(
'searchFeed',
const Duration(milliseconds: 500),
() {
switch (_selectedChipIndex) {
case 0:
context.read<FeedCubit>().loadFeedBlogPostList();
break;
case 1:
context.read<RecipesCubit>().loadRecipesList();
break;
case 2:
context.read<DietsCubit>().loadDiets();
break;
case 3:
context.read<PlansCubit>().loadPlans();
break;
case 4:
context.read<ConsultantsCubit>().getConsultants();
break;
}
},
);
EasyDebounce.debounce('searchFeed', const Duration(milliseconds: 500), () {
switch (_selectedChipIndex) {
case 0:
context.read<FeedCubit>().loadFeedBlogPostList();
break;
case 1:
context.read<RecipesCubit>().loadRecipesList();
break;
case 2:
context.read<DietsCubit>().loadDiets();
break;
case 3 when !hidePlans:
context.read<PlansCubit>().loadPlans();
break;
case 3 when hidePlans:
case 4 when !hidePlans:
context.read<ConsultantsCubit>().getConsultants();
break;
}
});
}
},
),
const SizedBox(width: 12),
switch (_selectedChipIndex) {
1 => AddItemButton(
onTap: () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name),
),
1 => AddItemButton(onTap: () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name)),
2 => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false
? () async {
await GoRouter.of(context).pushNamed(CreateDietScreen.name);
}
: null,
),
3 => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
),
_ => const AddItemButton()
onTap: state.profile?.roles.contains('consultant') ?? false
? () async {
await GoRouter.of(context).pushNamed(CreateDietScreen.name);
}
: null,
),
3 when !hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
),
_ => const AddItemButton(),
},
],
),
@@ -256,8 +231,9 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
0 => const BlogPosts(),
1 => const Recipes(),
2 => const Diets(),
3 => const Plans(),
4 => const Consultants(),
3 when !hidePlans => const Plans(),
3 when hidePlans => const Consultants(),
4 when !hidePlans => const Consultants(),
_ => Center(child: Text(chipsLabelList[_selectedChipIndex])),
},
),

View File

@@ -2,12 +2,7 @@ import 'package:eatvio_app/features/feed/presentation/widgets/choice_chip_widget
import 'package:flutter/widgets.dart';
class ChipGroup extends StatelessWidget {
const ChipGroup({
super.key,
required this.selectedChipIndex,
required this.chipsLabelList,
required this.setSelectedChipIndex,
});
const ChipGroup({super.key, required this.selectedChipIndex, required this.chipsLabelList, required this.setSelectedChipIndex});
final int selectedChipIndex;
final List<String> chipsLabelList;
final void Function(int) setSelectedChipIndex;
@@ -22,7 +17,7 @@ class ChipGroup extends StatelessWidget {
child: Row(
children: [
...List.generate(
5,
chipsLabelList.length,
(index) => Padding(
padding: const EdgeInsets.only(right: 8.0),
child: ChoiceChipWidget(

View File

@@ -45,10 +45,7 @@ import 'package:url_launcher/url_launcher.dart';
class ProfileScreen extends StatefulWidget {
final Author? author;
const ProfileScreen({
super.key,
this.author,
});
const ProfileScreen({super.key, this.author});
@override
State<ProfileScreen> createState() => _ProfileScreenState();
@@ -91,7 +88,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
if (isFirstFetch) {
canSub = !state.profile.isEntitySubscribedByMe;
if (state.profile.roles.contains('consultant')) {
if (state.profile.roles.contains('consultant') && !hidePlans) {
plansCubit.getPlans(state.profile.slug);
}
isFirstFetch = false;
@@ -109,10 +106,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
),
);
} else {
return Scaffold(
appBar: AppBar(),
body: const Loader(),
);
return Scaffold(appBar: AppBar(), body: const Loader());
}
},
),
@@ -130,9 +124,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(16),
),
borderRadius: BorderRadius.vertical(bottom: Radius.circular(16)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
@@ -147,28 +139,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
const SizedBox(width: 16),
const Text(
'Консультант',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(
0xFF323F4B,
),
),
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF323F4B)),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFFF6FAFF),
borderRadius: BorderRadius.circular(12),
),
decoration: BoxDecoration(color: const Color(0xFFF6FAFF), borderRadius: BorderRadius.circular(12)),
child: Text(
'${state.consultantInfo!.ratingPlace}-й в рейтинге',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: appTheme.blueA400,
),
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: appTheme.blueA400),
),
),
],
@@ -177,17 +156,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
const Row(
children: [
SizedBox(width: 16),
Icon(
Icons.done_rounded,
color: Colors.green,
),
Icon(Icons.done_rounded, color: Colors.green),
Text(
'Медицинское образование',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 14,
fontWeight: FontWeight.w600,
),
style: TextStyle(color: Color(0xFF323F4B), fontSize: 14, fontWeight: FontWeight.w600),
),
],
),
@@ -205,7 +177,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _socialLinks(state),
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _education(state),
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _diplomasAndCertificates(state),
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _activePlans(state),
if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !hidePlans) _activePlans(state),
_blogPosts(state),
],
),
@@ -216,9 +188,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
Scaffold _myProfileScreen(ProfileStateLoaded state) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
appBar: AppBar(
toolbarHeight: widget.author == null ? 0 : null,
),
appBar: AppBar(toolbarHeight: widget.author == null ? 0 : null),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -226,9 +196,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(16),
),
borderRadius: BorderRadius.vertical(bottom: Radius.circular(16)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
@@ -243,28 +211,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
const SizedBox(width: 16),
const Text(
'Консультант',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(
0xFF323F4B,
),
),
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF323F4B)),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFFF6FAFF),
borderRadius: BorderRadius.circular(12),
),
decoration: BoxDecoration(color: const Color(0xFFF6FAFF), borderRadius: BorderRadius.circular(12)),
child: Text(
'${state.consultantInfo!.ratingPlace}-й в рейтинге',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: appTheme.blueA400,
),
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: appTheme.blueA400),
),
),
],
@@ -273,17 +228,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
const Row(
children: [
SizedBox(width: 16),
Icon(
Icons.done_rounded,
color: Colors.green,
),
Icon(Icons.done_rounded, color: Colors.green),
Text(
'Медицинское образование',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 14,
fontWeight: FontWeight.w600,
),
style: TextStyle(color: Color(0xFF323F4B), fontSize: 14, fontWeight: FontWeight.w600),
),
],
),
@@ -299,7 +247,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
if (state.profile.roles.contains('consultant')) _education(state),
if (state.profile.roles.contains('consultant')) _diplomasAndCertificates(state),
Padding(padding: const EdgeInsets.only(bottom: 16), child: _charts()),
if (state.profile.roles.contains('consultant')) _activePlans(state),
if (state.profile.roles.contains('consultant') && !hidePlans) _activePlans(state),
_newBlogPostButton(),
_blogPosts(state),
],
@@ -309,7 +257,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
Widget _activePlans(ProfileStateLoaded state) {
if (!state.profile.roles.contains('consultant')) {
if (!state.profile.roles.contains('consultant') || hidePlans) {
return const SizedBox.shrink();
}
return BlocBuilder<PlansCubit, PlansState>(
@@ -328,12 +276,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
Text(
'Активные планы',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 24,
height: 1,
fontWeight: FontWeight.w600,
),
style: TextStyle(color: Color(0xFF323F4B), fontSize: 24, height: 1, fontWeight: FontWeight.w600),
),
],
),
@@ -343,10 +286,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
height: 781,
child: PageView.builder(
itemBuilder: (BuildContext context, int index) {
return PlanWidget(
plan: state.plansList[index],
index: index,
);
return PlanWidget(plan: state.plansList[index], index: index);
},
onPageChanged: (bullet) {
setState(() {
@@ -370,17 +310,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
Container _bullets(int currentBullet, int bulletCount) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 0; i < bulletCount; i++) ...[
_bullet(i == currentBullet),
i < bulletCount - 1 ? const SizedBox(width: 4) : const SizedBox.shrink(),
],
for (int i = 0; i < bulletCount; i++) ...[_bullet(i == currentBullet), i < bulletCount - 1 ? const SizedBox(width: 4) : const SizedBox.shrink()],
],
),
);
@@ -391,10 +325,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
width: isActive ? 10 : 8,
height: isActive ? 10 : 8,
duration: const Duration(milliseconds: 150),
decoration: BoxDecoration(
color: isActive ? appTheme.blueA400 : const Color(0xFFD7D9DC),
shape: BoxShape.circle,
),
decoration: BoxDecoration(color: isActive ? appTheme.blueA400 : const Color(0xFFD7D9DC), shape: BoxShape.circle),
);
}
@@ -411,25 +342,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
borderRadius: BorderRadius.circular(8),
child: Ink(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF6FAFF),
borderRadius: BorderRadius.circular(8),
),
decoration: BoxDecoration(color: const Color(0xFFF6FAFF), borderRadius: BorderRadius.circular(8)),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.add_circle_outline_rounded,
color: Color(0xFF2F80ED),
),
Icon(Icons.add_circle_outline_rounded, color: Color(0xFF2F80ED)),
SizedBox(width: 12),
Text(
'Новая запись',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF2F80ED),
),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF2F80ED)),
),
],
),
@@ -454,12 +375,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
const Text(
'Дипломы\nи сертификаты',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 24,
height: 1,
fontWeight: FontWeight.w600,
),
style: TextStyle(color: Color(0xFF323F4B), fontSize: 24, height: 1, fontWeight: FontWeight.w600),
),
GestureDetector(
onTap: () {
@@ -467,11 +383,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
},
child: Text(
widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '',
style: const TextStyle(
color: Color(0xFF2F80ED),
fontSize: 16,
fontWeight: FontWeight.w500,
),
style: const TextStyle(color: Color(0xFF2F80ED), fontSize: 16, fontWeight: FontWeight.w500),
),
),
],
@@ -491,17 +403,14 @@ class _ProfileScreenState extends State<ProfileScreen> {
child: Row(
children: [
if (state.consultantInfo!.photos.isNotEmpty)
...List.generate(
state.consultantInfo!.photos.length * 2 - 1,
(index) {
if (index.isEven) {
final photo = state.consultantInfo!.photos[index ~/ 2];
return _diplomaImage(photo.url);
} else {
return const SizedBox(width: 8);
}
},
),
...List.generate(state.consultantInfo!.photos.length * 2 - 1, (index) {
if (index.isEven) {
final photo = state.consultantInfo!.photos[index ~/ 2];
return _diplomaImage(photo.url);
} else {
return const SizedBox(width: 8);
}
}),
],
),
),
@@ -515,24 +424,13 @@ class _ProfileScreenState extends State<ProfileScreen> {
GestureDetector _diplomaImage(String url) {
return GestureDetector(
onTap: () {
context.pushTransparentRoute(
rootNavigator: true,
PhotoViewScreen(
imageUrls: [url],
index: 0,
),
);
context.pushTransparentRoute(rootNavigator: true, PhotoViewScreen(imageUrls: [url], index: 0));
},
child: Hero(
tag: url,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: CachedNetworkImage(
imageUrl: url,
fit: BoxFit.fitHeight,
width: MediaQuery.of(context).size.width / 3 - (60 / 3),
height: 160,
),
child: CachedNetworkImage(imageUrl: url, fit: BoxFit.fitHeight, width: MediaQuery.of(context).size.width / 3 - (60 / 3), height: 160),
),
),
);
@@ -555,11 +453,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
const Text(
'Образование',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 24,
fontWeight: FontWeight.w600,
),
style: TextStyle(color: Color(0xFF323F4B), fontSize: 24, fontWeight: FontWeight.w600),
),
GestureDetector(
onTap: () {
@@ -567,11 +461,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
},
child: Text(
widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '',
style: const TextStyle(
color: Color(0xFF2F80ED),
fontSize: 16,
fontWeight: FontWeight.w500,
),
style: const TextStyle(color: Color(0xFF2F80ED), fontSize: 16, fontWeight: FontWeight.w500),
),
),
],
@@ -590,8 +480,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _educationBlock(state.consultantInfo!.educationModel.completedCourses![index].values.first.title,
state.consultantInfo!.educationModel.completedCourses![index].values.first.date),
itemBuilder: (context, index) => _educationBlock(
state.consultantInfo!.educationModel.completedCourses![index].values.first.title,
state.consultantInfo!.educationModel.completedCourses![index].values.first.date,
),
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemCount: state.consultantInfo!.educationModel.completedCourses!.length,
),
@@ -621,22 +513,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
Text(
title,
style: const TextStyle(
height: 1,
color: Color(0xFF323F4B),
fontWeight: FontWeight.w500,
fontSize: 16,
),
style: const TextStyle(height: 1, color: Color(0xFF323F4B), fontWeight: FontWeight.w500, fontSize: 16),
),
const SizedBox(height: 4),
Text(
formattedDate,
style: const TextStyle(
height: 1,
color: Color(0xFF6D6F71),
fontWeight: FontWeight.w500,
fontSize: 14,
),
style: const TextStyle(height: 1, color: Color(0xFF6D6F71), fontWeight: FontWeight.w500, fontSize: 14),
),
],
),
@@ -660,12 +542,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
_socialLinkButton(
() {
launchUrl(
Uri.parse(
state.consultantInfo!.educationModel.vkLink ?? '',
),
mode: LaunchMode.externalApplication,
);
launchUrl(Uri.parse(state.consultantInfo!.educationModel.vkLink ?? ''), mode: LaunchMode.externalApplication);
},
'assets/icons/vk.svg',
state.consultantInfo!.educationModel.vkLink!.isEmpty ? true : false,
@@ -673,12 +550,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
const SizedBox(width: 12),
_socialLinkButton(
() {
launchUrl(
Uri.parse(
state.consultantInfo!.educationModel.tgLink ?? '',
),
mode: LaunchMode.externalApplication,
);
launchUrl(Uri.parse(state.consultantInfo!.educationModel.tgLink ?? ''), mode: LaunchMode.externalApplication);
},
'assets/icons/telegram.svg',
state.consultantInfo!.educationModel.tgLink!.isEmpty ? true : false,
@@ -686,12 +558,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
const SizedBox(width: 12),
_socialLinkButton(
() {
launchUrl(
Uri.parse(
state.consultantInfo!.educationModel.personalSiteLink ?? '',
),
mode: LaunchMode.externalApplication,
);
launchUrl(Uri.parse(state.consultantInfo!.educationModel.personalSiteLink ?? ''), mode: LaunchMode.externalApplication);
},
'assets/icons/web.svg',
state.consultantInfo!.educationModel.personalSiteLink!.isEmpty ? true : false,
@@ -709,10 +576,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width / 3 - (56 / 3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: const Color(0xFFF6F6F6),
),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFF6F6F6)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16),
child: SvgPicture.asset(
@@ -734,20 +598,13 @@ class _ProfileScreenState extends State<ProfileScreen> {
child: Ink(
padding: const EdgeInsets.all(8),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.white),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Достижения',
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 14,
fontWeight: FontWeight.w600,
),
style: TextStyle(color: Color(0xFF6D6F71), fontSize: 14, fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
SingleChildScrollView(
@@ -759,51 +616,25 @@ class _ProfileScreenState extends State<ProfileScreen> {
? Tooltip(
message: state.profile.badges[i].description,
triggerMode: TooltipTriggerMode.tap,
textStyle: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500,
),
textStyle: const TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.w500),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha((255 * 0.2).toInt()),
blurRadius: 32,
)
],
),
child: SvgPicture.network(
'$baseUrl/storage/${state.profile.badges[i].image}',
width: 40,
height: 40,
boxShadow: [BoxShadow(color: Colors.black.withAlpha((255 * 0.2).toInt()), blurRadius: 32)],
),
child: SvgPicture.network('$baseUrl/storage/${state.profile.badges[i].image}', width: 40, height: 40),
)
: Tooltip(
message: state.profile.badges[i].description,
margin: const EdgeInsets.symmetric(horizontal: 32),
triggerMode: TooltipTriggerMode.tap,
textStyle: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500,
),
textStyle: const TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.w500),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha((255 * 0.2).toInt()),
blurRadius: 32,
)
],
),
child: CachedNetworkImage(
imageUrl: '$baseUrl/storage/${state.profile.badges[i].image}',
width: 40,
height: 40,
boxShadow: [BoxShadow(color: Colors.black.withAlpha((255 * 0.2).toInt()), blurRadius: 32)],
),
child: CachedNetworkImage(imageUrl: '$baseUrl/storage/${state.profile.badges[i].image}', width: 40, height: 40),
),
i < state.profile.badges.length - 1 ? const SizedBox(width: 8) : const SizedBox.shrink(),
],
@@ -823,21 +654,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
child: Row(
children: [
InkWell(
onTap: () => GoRouter.of(context).pushNamed(
'subsScreen',
extra: SubType.subscriptions,
pathParameters: {
'slug': state.profile.slug,
},
),
onTap: () => GoRouter.of(context).pushNamed('subsScreen', extra: SubType.subscriptions, pathParameters: {'slug': state.profile.slug}),
borderRadius: BorderRadius.circular(12),
child: Ink(
padding: const EdgeInsets.all(8),
width: MediaQuery.of(context).size.width / 2 - 6,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.white),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
@@ -847,47 +669,27 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
const Text(
'Подписки',
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 14,
fontWeight: FontWeight.w600,
),
style: TextStyle(color: Color(0xFF6D6F71), fontSize: 14, fontWeight: FontWeight.w600),
),
Text(
'${state.profile.subscriptions.length}',
style: const TextStyle(
color: Color(0xFF323F4B),
fontSize: 22,
fontWeight: FontWeight.w700,
),
style: const TextStyle(color: Color(0xFF323F4B), fontSize: 22, fontWeight: FontWeight.w700),
),
],
),
Padding(
padding: const EdgeInsets.only(right: 8),
child: _miniAvatars(state.profile.subscriptions),
),
Padding(padding: const EdgeInsets.only(right: 8), child: _miniAvatars(state.profile.subscriptions)),
],
),
),
),
const SizedBox(width: 4),
InkWell(
onTap: () => GoRouter.of(context).pushNamed(
'subsScreen',
extra: SubType.subscribers,
pathParameters: {
'slug': state.profile.slug,
},
),
onTap: () => GoRouter.of(context).pushNamed('subsScreen', extra: SubType.subscribers, pathParameters: {'slug': state.profile.slug}),
borderRadius: BorderRadius.circular(12),
child: Ink(
padding: const EdgeInsets.all(8),
width: MediaQuery.of(context).size.width / 2 - 6,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.white),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
@@ -897,26 +699,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
const Text(
'Подписчики',
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 14,
fontWeight: FontWeight.w600,
),
style: TextStyle(color: Color(0xFF6D6F71), fontSize: 14, fontWeight: FontWeight.w600),
),
Text(
'${state.profile.subscribers.length}',
style: const TextStyle(
color: Color(0xFF323F4B),
fontSize: 22,
fontWeight: FontWeight.w700,
),
style: const TextStyle(color: Color(0xFF323F4B), fontSize: 22, fontWeight: FontWeight.w700),
),
],
),
Padding(
padding: const EdgeInsets.only(right: 8),
child: _miniAvatars(state.profile.subscribers),
),
Padding(padding: const EdgeInsets.only(right: 8), child: _miniAvatars(state.profile.subscribers)),
],
),
),
@@ -934,13 +725,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
for (int i = 0; i < maxLength; i++)
Align(
widthFactor: 0.8,
child: CircleAvatar(
radius: 14,
foregroundImage: NetworkImage(
urls[i],
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
child: CircleAvatar(radius: 14, foregroundImage: NetworkImage(urls[i]), backgroundImage: const AssetImage('assets/images/no_avatar.png')),
),
],
);
@@ -951,10 +736,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
padding: const EdgeInsets.symmetric(vertical: 16),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => ProfileBlogPostWidget(
index: index,
profileCubit: profileCubit,
),
itemBuilder: (context, index) => ProfileBlogPostWidget(index: index, profileCubit: profileCubit),
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemCount: state.blogPosts.length,
);
@@ -968,13 +750,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
return Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(16),
child: Text(
text,
style: TextStyle(
color: Colors.black.withValues(alpha: 0.4),
height: 1,
),
),
child: Text(text, style: TextStyle(color: Colors.black.withValues(alpha: 0.4), height: 1)),
);
}
@@ -986,13 +762,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
PopupMenuButton(
onSelected: (value) async {
if (value == 'view') {
context.pushTransparentRoute(
rootNavigator: true,
PhotoViewScreen(
imageUrls: [baseUrl + state.profile.profilePhotoUrl],
index: 0,
),
);
context.pushTransparentRoute(rootNavigator: true, PhotoViewScreen(imageUrls: [baseUrl + state.profile.profilePhotoUrl], index: 0));
}
if (value == 'replace') {
showCupertinoModalPopup<void>(
@@ -1019,13 +789,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
context: context,
);
},
child: const Row(
children: [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
child: const Row(children: [Icon(Icons.photo_camera), SizedBox(width: 8), Text('Фото')]),
),
CupertinoActionSheetAction(
onPressed: () async {
@@ -1045,13 +809,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
context: context,
);
},
child: const Row(
children: [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
),
child: const Row(children: [Icon(Icons.photo), SizedBox(width: 8), Text('Галерея')]),
),
],
cancelButton: CupertinoActionSheetAction(
@@ -1069,20 +827,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'view',
child: Text('Посмотреть'),
),
if (widget.author == null || widget.author!.authorIsMe)
const PopupMenuItem(
value: 'replace',
child: Text('Заменить'),
),
if (widget.author == null || widget.author!.authorIsMe)
const PopupMenuItem(
value: 'delete',
child: Text('Удалить'),
),
const PopupMenuItem(value: 'view', child: Text('Посмотреть')),
if (widget.author == null || widget.author!.authorIsMe) const PopupMenuItem(value: 'replace', child: Text('Заменить')),
if (widget.author == null || widget.author!.authorIsMe) const PopupMenuItem(value: 'delete', child: Text('Удалить')),
],
child: Hero(
tag: state.profile.profilePhotoUrl,
@@ -1101,12 +848,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
Text(
'${state.profileDetails.firstName} ${state.profileDetails.lastName}',
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 16,
height: 1,
),
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.w600, fontSize: 16, height: 1),
),
const SizedBox(height: 8),
GestureDetector(
@@ -1124,11 +866,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
const Text(
'Подробнее',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: Color(0xFF323F4B),
),
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700, color: Color(0xFF323F4B)),
),
const SizedBox(height: 16),
_additional('Дата рожденя: ', state.profileDetails.birthdate),
@@ -1148,19 +886,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
},
child: const Row(
children: [
Icon(
Icons.info_outline,
size: 20,
color: Color(0xFF6D6F71),
),
Icon(Icons.info_outline, size: 20, color: Color(0xFF6D6F71)),
SizedBox(width: 4),
Text(
'Дополнительно',
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 14,
fontWeight: FontWeight.w500,
),
style: TextStyle(color: Color(0xFF6D6F71), fontSize: 14, fontWeight: FontWeight.w500),
),
],
),
@@ -1173,10 +903,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
if (value == 'share') {
final box = context.findRenderObject() as RenderBox?;
SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/profile/${state.profile.slug}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
ShareParams(uri: Uri.parse('$baseUrl/ru/profile/${state.profile.slug}'), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size),
);
} else if (value == 'block') {
isFirstFetch = true;
@@ -1198,33 +925,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => ComplainingBottomSheet(
complainedContentType: ComplainedContent.profile,
slug: widget.author?.authorSlug,
),
builder: (context) => ComplainingBottomSheet(complainedContentType: ComplainedContent.profile, slug: widget.author?.authorSlug),
);
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'share',
child: Text('Поделиться'),
),
if (widget.author != null)
const PopupMenuItem(
value: 'complain',
child: Text('Пожаловаться'),
),
if (widget.author != null && !state.profile.isBlockedByMe)
const PopupMenuItem(
value: 'block',
child: Text('Заблокировать'),
),
if (widget.author != null && state.profile.isBlockedByMe)
const PopupMenuItem(
value: 'unblock',
child: Text('Разблокировать'),
),
const PopupMenuItem(value: 'share', child: Text('Поделиться')),
if (widget.author != null) const PopupMenuItem(value: 'complain', child: Text('Пожаловаться')),
if (widget.author != null && !state.profile.isBlockedByMe) const PopupMenuItem(value: 'block', child: Text('Заблокировать')),
if (widget.author != null && state.profile.isBlockedByMe) const PopupMenuItem(value: 'unblock', child: Text('Разблокировать')),
],
child: const Icon(Icons.more_horiz_rounded),
),
@@ -1238,19 +947,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF6D6F71),
),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF6D6F71)),
),
Text(
value,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF323F4B),
),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF323F4B)),
),
],
);
@@ -1266,16 +967,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
alignment: Alignment.center,
padding: const EdgeInsets.all(8),
width: (MediaQuery.of(context).size.width - 40) / 2,
decoration: BoxDecoration(
color: appTheme.blueA400.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
),
child: Text(
'Сообщение',
style: TextStyle(
color: appTheme.blueA400,
),
),
decoration: BoxDecoration(color: appTheme.blueA400.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16)),
child: Text('Сообщение', style: TextStyle(color: appTheme.blueA400)),
),
),
const SizedBox(width: 8),
@@ -1290,16 +983,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
alignment: Alignment.center,
padding: const EdgeInsets.all(8),
width: (MediaQuery.of(context).size.width - 40) / 2,
decoration: BoxDecoration(
color: !canSub ? Colors.grey.shade200 : appTheme.blueA400,
borderRadius: BorderRadius.circular(16),
),
child: Text(
!canSub ? 'Отписаться' : 'Подписаться',
style: TextStyle(
color: !canSub ? Colors.black : Colors.white,
),
),
decoration: BoxDecoration(color: !canSub ? Colors.grey.shade200 : appTheme.blueA400, borderRadius: BorderRadius.circular(16)),
child: Text(!canSub ? 'Отписаться' : 'Подписаться', style: TextStyle(color: !canSub ? Colors.black : Colors.white)),
),
),
const SizedBox(width: 16),
@@ -1318,33 +1003,17 @@ class _ProfileScreenState extends State<ProfileScreen> {
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Row(
children: [
_choiceChipWidget(0),
const SizedBox(width: 8),
_choiceChipWidget(1),
const SizedBox(width: 8),
_choiceChipWidget(2),
],
),
child: Row(children: [_choiceChipWidget(0), const SizedBox(width: 8), _choiceChipWidget(1), const SizedBox(width: 8), _choiceChipWidget(2)]),
),
Builder(
builder: (context) {
switch (_selectedChipIndex) {
case 0:
return WeightChartWidget(
weightPoints: weightPoints,
profileCubit: profileCubit,
);
return WeightChartWidget(weightPoints: weightPoints, profileCubit: profileCubit);
case 1:
return CircumferenceChartWidget(
circumferencePoints: circumferencePoints,
profileCubit: profileCubit,
);
return CircumferenceChartWidget(circumferencePoints: circumferencePoints, profileCubit: profileCubit);
case 2:
return ImtChartWidget(
weightPoints: weightPoints,
);
return ImtChartWidget(weightPoints: weightPoints);
default:
return const SizedBox.shrink();
}
@@ -1377,16 +1046,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
pressElevation: 0,
showCheckmark: false,
side: BorderSide.none,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
label: Text(chipsIntervalLabelList[index]),
labelStyle: TextStyle(
color: appTheme.gray600,
fontWeight: FontWeight.w500,
fontSize: 12,
),
labelStyle: TextStyle(color: appTheme.gray600, fontWeight: FontWeight.w500, fontSize: 12),
selected: _selectedIntervalChipIndex == index,
onSelected: (bool selected) {
profileCubit.getMyProfile(period: chipsIntervalList[index]);
@@ -1403,16 +1066,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
pressElevation: 0,
showCheckmark: false,
side: BorderSide.none,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
label: Text(chipsLabelList[index]),
labelStyle: TextStyle(
color: index == _selectedChipIndex ? Colors.white : appTheme.gray600,
fontWeight: FontWeight.w500,
fontSize: 12,
),
labelStyle: TextStyle(color: index == _selectedChipIndex ? Colors.white : appTheme.gray600, fontWeight: FontWeight.w500, fontSize: 12),
selected: _selectedChipIndex == index,
backgroundColor: appTheme.gray100,
onSelected: (bool selected) {