feat(profile): add subscribers and subscriptions

This commit is contained in:
mso7nueh
2024-10-16 15:52:23 +03:00
parent b0f8057aa0
commit 079a712997
11 changed files with 1046 additions and 267 deletions

View File

@@ -1,13 +1,16 @@
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';
import 'package:flutter_bloc/flutter_bloc.dart';
class ProfileCubit extends Cubit<ProfileState> {
final ProfileRepository profileRepository;
final ChatRepository chatRepository;
String _currentPeriod = '1';
ProfileCubit({
required this.profileRepository,
required this.chatRepository,
}) : super(const ProfileState.loading());
getProfileBySlug(String slug) async {
@@ -71,4 +74,15 @@ class ProfileCubit extends Cubit<ProfileState> {
print(e);
}
}
Future<String> getChatRoomIdBySlug({
required String slug,
}) async {
try {
return (await chatRepository.getChatRoomIdByUserSlug(slug: slug)).id;
} catch (e) {
print(e);
rethrow;
}
}
}

View File

@@ -17,7 +17,10 @@ import 'package:rive/rive.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();
@@ -49,254 +52,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
circumferencePoints = state.circumferencePoints!;
weightPoints = state.weightPoints!;
}
return Scaffold(
backgroundColor: Colors.grey.shade200,
appBar: widget.author == null ? AppBar(toolbarHeight: 0) : AppBar(),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(16),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
CircleAvatar(
radius: 32,
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
state.profile.name,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
height: 1,
),
),
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),
),
),
],
),
],
),
const Spacer(),
IconButton(
onPressed: () {},
icon: Icon(Icons.more_horiz_rounded),
),
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Container(
// child: SvgPicture.asset(
// height: 28,
// width: 28,
// 'assets/icons/subscribers.svg',
// colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn),
// ),
// ),
// SizedBox(width: 8),
// Column(
// children: [
// Text(
// '123',
// style: TextStyle(
// height: 1,
// fontSize: 18,
// fontWeight: FontWeight.w500,
// color: Colors.black87,
// ),
// ),
// Text(
// 'Подписчика',
// style: TextStyle(
// height: 1,
// fontSize: 12,
// fontWeight: FontWeight.w400,
// color: Colors.black38,
// ),
// ),
// ],
// ),
// ],
// ),
// GestureDetector(
// onTap: () => Share.shareUri(
// Uri.parse(
// baseUrl + '/ru/profile/' + state.profile.slug,
// ),
// ),
// child: Container(
// padding: EdgeInsets.only(left: 16),
// child: SvgPicture.asset(
// height: 24,
// width: 24,
// 'assets/icons/share.svg',
// colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn),
// ),
// ),
// ),
],
),
),
if (state.profileDetails.additionalInfo.isNotEmpty)
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(16),
child: Text(
state.profileDetails.additionalInfo,
style: TextStyle(
color: Colors.black.withOpacity(0.4),
height: 1,
),
),
),
if (widget.author != null) const SizedBox(height: 8),
if (widget.author != null)
Row(
children: [
SizedBox(width: 16),
GestureDetector(
onTap: () => GoRouter.of(context).pushNamed(
'chatScreen',
),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(8),
width: (MediaQuery.of(context).size.width - 40) / 2,
decoration: BoxDecoration(
color: appTheme.blueA400.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Text(
'Сообщение',
style: TextStyle(
color: appTheme.blueA400,
),
),
),
),
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,
),
),
),
SizedBox(width: 16),
],
),
if (widget.author != null) SizedBox(height: 16),
if (widget.author == null) _divider(context),
if (widget.author == null)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Row(
children: [
_choiceChipWidget(0),
SizedBox(width: 8),
_choiceChipWidget(1),
SizedBox(width: 8),
_choiceChipWidget(2),
],
),
),
Builder(
builder: (context) {
switch (_selectedChipIndex) {
case 0:
return WeightChartWidget(
weightPoints: weightPoints,
profileCubit: profileCubit,
);
case 1:
return CircumferenceChartWidget(
circumferencePoints: circumferencePoints,
profileCubit: profileCubit,
);
case 2:
return ImtChartWidget(
weightPoints: weightPoints,
);
}
return SizedBox.shrink();
},
),
SizedBox(height: 8),
Row(
children: [
SizedBox(width: 16),
_choiceIntervalChipWidget(0),
SizedBox(width: 8),
_choiceIntervalChipWidget(1),
SizedBox(width: 8),
_choiceIntervalChipWidget(2),
SizedBox(width: 8),
_choiceIntervalChipWidget(3),
SizedBox(width: 8),
_choiceIntervalChipWidget(4),
],
),
SizedBox(height: 8),
],
),
],
),
),
SizedBox(height: 16),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => BlogPostWidget(feedBlogPostModel: state.blogPosts[index]),
separatorBuilder: (context, index) => SizedBox(height: 8),
itemCount: state.blogPosts.length,
),
SizedBox(height: 16),
],
),
),
);
return _profileScreen(state);
} else if (state is ProfileStateError) {
return Scaffold(
appBar: AppBar(),
@@ -325,13 +81,388 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
_divider(BuildContext context) {
_profileScreen(ProfileStateLoaded state) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
appBar: widget.author == null ? AppBar(toolbarHeight: 0) : AppBar(),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(16),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_header(state),
if (state.profileDetails.additionalInfo.isNotEmpty) _additionalInfo(state),
if (widget.author != null) const SizedBox(height: 8),
if (widget.author != null) _messageAndSubscribe(),
if (widget.author != null) SizedBox(height: 16),
],
),
),
_subscriptionsAndSubscribers(state),
_badges(state),
if (widget.author == null) SizedBox(height: 16),
if (widget.author == null) _charts(),
_blogPosts(state),
],
),
),
);
}
_badges(ProfileStateLoaded state) {
if (state.profile.badges.isEmpty) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: InkWell(
onTap: () {},
borderRadius: BorderRadius.circular(12),
child: Ink(
padding: const EdgeInsets.all(8),
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Достижения',
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
// CircleAvatar(
// backgroundImage: NetworkImage(
// state.profile.badges.first.image,
// ),
// radius: 20,
// ),
],
),
),
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
),
),
);
}
_subscriptionsAndSubscribers(ProfileStateLoaded state) {
return Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
InkWell(
onTap: () {},
borderRadius: BorderRadius.circular(12),
child: Ink(
padding: const EdgeInsets.all(8),
width: MediaQuery.of(context).size.width / 2 - 6,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Подписки',
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
Text(
'${state.profile.subscriptions.length}',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 22,
fontWeight: FontWeight.w700,
),
),
],
),
Padding(
padding: EdgeInsets.only(right: 8),
child: _miniAvatars(state.profile.subscriptions.map((e) => e.profilePhotoUrl).toList()),
),
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
),
),
SizedBox(width: 4),
InkWell(
onTap: () {},
borderRadius: BorderRadius.circular(12),
child: Ink(
padding: const EdgeInsets.all(8),
width: MediaQuery.of(context).size.width / 2 - 6,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Подписчики',
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
Text(
'${state.profile.subscribers.length}',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 22,
fontWeight: FontWeight.w700,
),
),
],
),
Padding(
padding: EdgeInsets.only(right: 8),
child: _miniAvatars(state.profile.subscribers.map((e) => e.profilePhotoUrl).toList()),
),
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
),
),
],
),
);
}
_miniAvatars(List<String> urls) {
int maxLength = urls.length > 2 ? 3 : urls.length;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (int i = 0; i < maxLength; i++)
Align(
widthFactor: 0.8,
child: CircleAvatar(
radius: 14,
backgroundImage: NetworkImage(
urls[i],
),
),
),
],
);
}
_blogPosts(ProfileStateLoaded state) {
return ListView.separated(
padding: EdgeInsets.symmetric(vertical: 16),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => BlogPostWidget(feedBlogPostModel: state.blogPosts[index]),
separatorBuilder: (context, index) => SizedBox(height: 8),
itemCount: state.blogPosts.length,
);
}
_additionalInfo(ProfileStateLoaded state) {
return Container(
height: 0.4,
width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration(
color: Colors.black26,
borderRadius: BorderRadius.circular(16),
alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(16),
child: Text(
state.profileDetails.additionalInfo,
style: TextStyle(
color: Colors.black.withOpacity(0.4),
height: 1,
),
),
);
}
_header(ProfileStateLoaded state) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
CircleAvatar(
radius: 32,
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
state.profile.name,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
height: 1,
),
),
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),
),
),
],
),
],
),
const Spacer(),
IconButton(
onPressed: () {},
icon: Icon(Icons.more_horiz_rounded),
),
],
),
);
}
_messageAndSubscribe() {
return Row(
children: [
SizedBox(width: 16),
GestureDetector(
onTap: () async {
GoRouter.of(context).pushNamed(
'chatScreen',
extra: await profileCubit.getChatRoomIdBySlug(slug: widget.author!.authorSlug),
);
},
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(8),
width: (MediaQuery.of(context).size.width - 40) / 2,
decoration: BoxDecoration(
color: appTheme.blueA400.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Text(
'Сообщение',
style: TextStyle(
color: appTheme.blueA400,
),
),
),
),
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,
),
),
),
SizedBox(width: 16),
],
);
}
_charts() {
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Row(
children: [
_choiceChipWidget(0),
SizedBox(width: 8),
_choiceChipWidget(1),
SizedBox(width: 8),
_choiceChipWidget(2),
],
),
),
Builder(
builder: (context) {
switch (_selectedChipIndex) {
case 0:
return WeightChartWidget(
weightPoints: weightPoints,
profileCubit: profileCubit,
);
case 1:
return CircumferenceChartWidget(
circumferencePoints: circumferencePoints,
profileCubit: profileCubit,
);
case 2:
return ImtChartWidget(
weightPoints: weightPoints,
);
}
return SizedBox.shrink();
},
),
SizedBox(height: 8),
Row(
children: [
SizedBox(width: 16),
_choiceIntervalChipWidget(0),
SizedBox(width: 8),
_choiceIntervalChipWidget(1),
SizedBox(width: 8),
_choiceIntervalChipWidget(2),
SizedBox(width: 8),
_choiceIntervalChipWidget(3),
SizedBox(width: 8),
_choiceIntervalChipWidget(4),
],
),
SizedBox(height: 8),
],
),
);
}