feat(profile): Подписки и подписчики, Аватарка (загрузить/удалить/посмотреть), Дополнительная информация (модалка), Поделиться профилем

This commit is contained in:
Alex
2024-12-02 17:12:13 +03:00
parent 6a98bcb492
commit 92a3a60636
13 changed files with 328 additions and 64 deletions

View File

@@ -1,3 +1,6 @@
import 'dart:io';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
@@ -80,4 +83,9 @@ class ProfileCubit extends Cubit<ProfileState> {
rethrow;
}
}
uploadPhoto(File file) async {
await profileRepository.uploadPhoto(await ImageCompressUtility.compressFile(file));
return await getMyProfile();
}
}

View File

@@ -1,22 +1,32 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dismissible_page/dismissible_page.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_widget.dart';
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
import 'package:eatvio_app/features/profile/models/weight_model.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
import 'package:eatvio_app/features/profile/presentation/widgets/circumference_chart_widget.dart';
import 'package:eatvio_app/features/profile/presentation/widgets/imt_chart_widget.dart';
import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_widget.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:image_picker/image_picker.dart';
import 'package:share_plus/share_plus.dart';
class ProfileScreen extends StatefulWidget {
final Author? author;
@@ -40,6 +50,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
List<CircumferenceModel> circumferencePoints = [];
int _selectedChipIndex = 0;
int _selectedIntervalChipIndex = 0;
late bool canSub;
bool isFirstFetch = true;
@override
void initState() {
@@ -57,6 +69,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
circumferencePoints = state.circumferencePoints!;
weightPoints = state.weightPoints!;
}
if (isFirstFetch) {
canSub = !state.profile.isEntitySubscribedByMe;
isFirstFetch = false;
}
return widget.author == null ? _myProfileScreen(state) : _profileScreen(state);
} else if (state is ProfileStateError) {
return Scaffold(
@@ -99,7 +115,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
_header(state),
if (state.profileDetails.additionalInfo.isNotEmpty) _additionalInfo(state),
const SizedBox(height: 8),
_messageAndSubscribe(),
_messageAndSubscribe(state),
const SizedBox(height: 16),
],
),
@@ -692,16 +708,96 @@ class _ProfileScreenState extends State<ProfileScreen> {
padding: const EdgeInsets.all(16),
child: Row(
children: [
CircleAvatar(
radius: 32,
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
PopupMenuButton(
onSelected: (value) {
if (value == 'view') {
context.pushTransparentRoute(
rootNavigator: true,
PhotoViewScreen(
imageUrls: [state.profile.profilePhotoUrl],
index: 0,
),
);
}
if (value == 'replace') {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
Navigator.of(context).pop();
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.camera);
if (image != null) {
await profileCubit.uploadPhoto(File(image.path));
await sl<ProfileAvatarCubit>().getMyProfile();
}
},
child: const Row(
children: const [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
),
CupertinoActionSheetAction(
onPressed: () async {
Navigator.of(context).pop();
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
await profileCubit.uploadPhoto(File(image.path));
await sl<ProfileAvatarCubit>().getMyProfile();
}
},
child: const Row(
children: const [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
),
);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'view',
child: Text('Посмотреть'),
),
PopupMenuItem(
value: 'replace',
child: Text('Заменить'),
),
PopupMenuItem(
value: 'delete',
child: Text('Удалить'),
),
],
child: Hero(
tag: state.profile.profilePhotoUrl,
child: CircleAvatar(
radius: 32,
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
),
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
state.profile.name,
state.profileDetails.firstName + ' ' + state.profileDetails.lastName,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
@@ -710,37 +806,110 @@ class _ProfileScreenState extends State<ProfileScreen> {
),
),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.info_outline,
size: 20,
color: Color(0xFF6D6F71),
),
const SizedBox(width: 4),
Text(
'Дополнительно',
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 14,
fontWeight: FontWeight.w500,
GestureDetector(
onTap: () {
showModalBottomSheet(
useRootNavigator: true,
showDragHandle: true,
useSafeArea: true,
context: context,
builder: (context) => Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Подробнее',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: Color(0xFF323F4B),
),
),
const SizedBox(height: 16),
_additional('Дата рожденя: ', '${state.profileDetails.birthdate}'),
const SizedBox(height: 12),
_additional('Страна: ', '${state.profileDetails.country}'),
const SizedBox(height: 12),
_additional('Город: ', '${state.profileDetails.city}'),
const SizedBox(height: 12),
_additional('Язык общения: ', '${state.profileDetails.lang}'),
const SizedBox(height: 12),
_additional('В Eatvio с: ', '${state.profile.createdAt?.format('d.MM.yyyy', 'ru')}'),
const SizedBox(height: 12),
],
),
),
),
],
);
},
child: Row(
children: [
Icon(
Icons.info_outline,
size: 20,
color: Color(0xFF6D6F71),
),
const SizedBox(width: 4),
Text(
'Дополнительно',
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
const Spacer(),
IconButton(
onPressed: () {},
icon: Icon(Icons.more_horiz_rounded),
PopupMenuButton(
onSelected: (value) {
Share.shareUri(
Uri.parse(
baseUrl + '/ru/profile/' + state.profile.slug,
),
);
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'share',
child: Text('Поделиться'),
),
],
child: Icon(Icons.more_horiz_rounded),
),
],
),
);
}
_messageAndSubscribe() {
_additional(String title, String value) {
return Row(
children: [
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF6D6F71),
),
),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF323F4B),
),
),
],
);
}
_messageAndSubscribe(ProfileStateLoaded state) {
return Row(
children: [
SizedBox(width: 16),
@@ -768,18 +937,26 @@ class _ProfileScreenState extends State<ProfileScreen> {
),
),
SizedBox(width: 8),
Container(
alignment: Alignment.center,
padding: EdgeInsets.all(8),
width: (MediaQuery.of(context).size.width - 40) / 2,
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(16),
),
child: Text(
'Подписаться',
style: TextStyle(
color: Colors.white,
GestureDetector(
onTap: () {
setState(() {
canSub = !canSub;
});
canSub ? sl<SubCubit>().unsubscribe(state.profile.slug) : sl<SubCubit>().subscribe(state.profile.slug);
},
child: Container(
alignment: Alignment.center,
padding: 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,
),
),
),
),
@@ -826,7 +1003,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
return ImtChartWidget(
weightPoints: weightPoints,
);
default: return SizedBox.shrink();
default:
return SizedBox.shrink();
}
},
),