forked from eatvio/eatvio_app
feat(profile): Подписки и подписчики, Аватарка (загрузить/удалить/посмотреть), Дополнительная информация (модалка), Поделиться профилем
This commit is contained in:
@@ -61,6 +61,7 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
|
||||
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit());
|
||||
sl.registerLazySingleton<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||
sl.registerLazySingleton<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
|
||||
|
||||
// Cubit factories
|
||||
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
|
||||
@@ -68,7 +69,6 @@ Future<void> init() async {
|
||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl()));
|
||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl()));
|
||||
sl.registerFactory<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
|
||||
sl.registerFactory<RecipeCreateCubit>(() => RecipeCreateCubit(recipeRepository: sl()));
|
||||
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
||||
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl()));
|
||||
|
||||
@@ -10,10 +10,9 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
||||
}) : super(const ProfileAvatarState.loading());
|
||||
|
||||
getMyProfile() async {
|
||||
emit(const ProfileAvatarState.loading());
|
||||
try {
|
||||
emit(ProfileAvatarState.loaded(
|
||||
profile: await profileRepository.getMyProfile(),
|
||||
),);
|
||||
emit(ProfileAvatarState.loaded(profile: await profileRepository.getMyProfile()));
|
||||
} catch (e) {
|
||||
emit(ProfileAvatarState.error(errorMessage: e.toString()));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/noti
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -72,22 +71,19 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
||||
items: [
|
||||
BottomNavigationBarItem(
|
||||
label: '',
|
||||
icon: BlocProvider<ProfileAvatarCubit>(
|
||||
create: (context) => sl<ProfileAvatarCubit>()..getMyProfile(),
|
||||
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
||||
builder: (BuildContext context, ProfileAvatarState state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
return CircleAvatar(
|
||||
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
|
||||
radius: 16,
|
||||
);
|
||||
}
|
||||
icon: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
||||
builder: (BuildContext context, ProfileAvatarState state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
return CircleAvatar(
|
||||
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
|
||||
radius: 16,
|
||||
backgroundColor: Colors.black12,
|
||||
);
|
||||
},
|
||||
),
|
||||
}
|
||||
return CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Colors.black12,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
|
||||
@@ -15,6 +15,7 @@ class ProfileModel with _$ProfileModel {
|
||||
@JsonKey(name: "created_at") DateTime? createdAt,
|
||||
@JsonKey(name: "updated_at") DateTime? updatedAt,
|
||||
@JsonKey(name: "is_public_profile") required bool isPublicProfile,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") required String slug,
|
||||
@JsonKey(name: "profile_photo_url") required String profilePhotoUrl,
|
||||
@JsonKey(name: "role") required String role,
|
||||
|
||||
@@ -34,6 +34,8 @@ mixin _$ProfileModel {
|
||||
DateTime? get updatedAt => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "is_public_profile")
|
||||
bool get isPublicProfile => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
bool get isEntitySubscribedByMe => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "slug")
|
||||
String get slug => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "profile_photo_url")
|
||||
@@ -73,6 +75,7 @@ abstract class $ProfileModelCopyWith<$Res> {
|
||||
@JsonKey(name: "created_at") DateTime? createdAt,
|
||||
@JsonKey(name: "updated_at") DateTime? updatedAt,
|
||||
@JsonKey(name: "is_public_profile") bool isPublicProfile,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") String slug,
|
||||
@JsonKey(name: "profile_photo_url") String profilePhotoUrl,
|
||||
@JsonKey(name: "role") String role,
|
||||
@@ -103,6 +106,7 @@ class _$ProfileModelCopyWithImpl<$Res, $Val extends ProfileModel>
|
||||
Object? createdAt = freezed,
|
||||
Object? updatedAt = freezed,
|
||||
Object? isPublicProfile = null,
|
||||
Object? isEntitySubscribedByMe = null,
|
||||
Object? slug = null,
|
||||
Object? profilePhotoUrl = null,
|
||||
Object? role = null,
|
||||
@@ -139,6 +143,10 @@ class _$ProfileModelCopyWithImpl<$Res, $Val extends ProfileModel>
|
||||
? _value.isPublicProfile
|
||||
: isPublicProfile // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isEntitySubscribedByMe: null == isEntitySubscribedByMe
|
||||
? _value.isEntitySubscribedByMe
|
||||
: isEntitySubscribedByMe // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
slug: null == slug
|
||||
? _value.slug
|
||||
: slug // ignore: cast_nullable_to_non_nullable
|
||||
@@ -183,6 +191,7 @@ abstract class _$$ProfileModelImplCopyWith<$Res>
|
||||
@JsonKey(name: "created_at") DateTime? createdAt,
|
||||
@JsonKey(name: "updated_at") DateTime? updatedAt,
|
||||
@JsonKey(name: "is_public_profile") bool isPublicProfile,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") String slug,
|
||||
@JsonKey(name: "profile_photo_url") String profilePhotoUrl,
|
||||
@JsonKey(name: "role") String role,
|
||||
@@ -211,6 +220,7 @@ class __$$ProfileModelImplCopyWithImpl<$Res>
|
||||
Object? createdAt = freezed,
|
||||
Object? updatedAt = freezed,
|
||||
Object? isPublicProfile = null,
|
||||
Object? isEntitySubscribedByMe = null,
|
||||
Object? slug = null,
|
||||
Object? profilePhotoUrl = null,
|
||||
Object? role = null,
|
||||
@@ -247,6 +257,10 @@ class __$$ProfileModelImplCopyWithImpl<$Res>
|
||||
? _value.isPublicProfile
|
||||
: isPublicProfile // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isEntitySubscribedByMe: null == isEntitySubscribedByMe
|
||||
? _value.isEntitySubscribedByMe
|
||||
: isEntitySubscribedByMe // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
slug: null == slug
|
||||
? _value.slug
|
||||
: slug // ignore: cast_nullable_to_non_nullable
|
||||
@@ -286,6 +300,8 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
@JsonKey(name: "created_at") this.createdAt,
|
||||
@JsonKey(name: "updated_at") this.updatedAt,
|
||||
@JsonKey(name: "is_public_profile") required this.isPublicProfile,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
required this.isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") required this.slug,
|
||||
@JsonKey(name: "profile_photo_url") required this.profilePhotoUrl,
|
||||
@JsonKey(name: "role") required this.role,
|
||||
@@ -323,6 +339,9 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
@JsonKey(name: "is_public_profile")
|
||||
final bool isPublicProfile;
|
||||
@override
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
final bool isEntitySubscribedByMe;
|
||||
@override
|
||||
@JsonKey(name: "slug")
|
||||
final String slug;
|
||||
@override
|
||||
@@ -360,7 +379,7 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfileModel(id: $id, name: $name, email: $email, emailVerifiedAt: $emailVerifiedAt, createdAt: $createdAt, updatedAt: $updatedAt, isPublicProfile: $isPublicProfile, slug: $slug, profilePhotoUrl: $profilePhotoUrl, role: $role, badges: $badges, subscriptions: $subscriptions, subscribers: $subscribers)';
|
||||
return 'ProfileModel(id: $id, name: $name, email: $email, emailVerifiedAt: $emailVerifiedAt, createdAt: $createdAt, updatedAt: $updatedAt, isPublicProfile: $isPublicProfile, isEntitySubscribedByMe: $isEntitySubscribedByMe, slug: $slug, profilePhotoUrl: $profilePhotoUrl, role: $role, badges: $badges, subscriptions: $subscriptions, subscribers: $subscribers)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -379,6 +398,8 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
other.updatedAt == updatedAt) &&
|
||||
(identical(other.isPublicProfile, isPublicProfile) ||
|
||||
other.isPublicProfile == isPublicProfile) &&
|
||||
(identical(other.isEntitySubscribedByMe, isEntitySubscribedByMe) ||
|
||||
other.isEntitySubscribedByMe == isEntitySubscribedByMe) &&
|
||||
(identical(other.slug, slug) || other.slug == slug) &&
|
||||
(identical(other.profilePhotoUrl, profilePhotoUrl) ||
|
||||
other.profilePhotoUrl == profilePhotoUrl) &&
|
||||
@@ -401,6 +422,7 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
createdAt,
|
||||
updatedAt,
|
||||
isPublicProfile,
|
||||
isEntitySubscribedByMe,
|
||||
slug,
|
||||
profilePhotoUrl,
|
||||
role,
|
||||
@@ -433,6 +455,8 @@ abstract class _ProfileModel implements ProfileModel {
|
||||
@JsonKey(name: "created_at") final DateTime? createdAt,
|
||||
@JsonKey(name: "updated_at") final DateTime? updatedAt,
|
||||
@JsonKey(name: "is_public_profile") required final bool isPublicProfile,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
required final bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") required final String slug,
|
||||
@JsonKey(name: "profile_photo_url") required final String profilePhotoUrl,
|
||||
@JsonKey(name: "role") required final String role,
|
||||
@@ -468,6 +492,9 @@ abstract class _ProfileModel implements ProfileModel {
|
||||
@JsonKey(name: "is_public_profile")
|
||||
bool get isPublicProfile;
|
||||
@override
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
bool get isEntitySubscribedByMe;
|
||||
@override
|
||||
@JsonKey(name: "slug")
|
||||
String get slug;
|
||||
@override
|
||||
|
||||
@@ -21,6 +21,7 @@ _$ProfileModelImpl _$$ProfileModelImplFromJson(Map<String, dynamic> json) =>
|
||||
? null
|
||||
: DateTime.parse(json['updated_at'] as String),
|
||||
isPublicProfile: json['is_public_profile'] as bool,
|
||||
isEntitySubscribedByMe: json['is_entity_subscribed_by_me'] as bool,
|
||||
slug: json['slug'] as String,
|
||||
profilePhotoUrl: json['profile_photo_url'] as String,
|
||||
role: json['role'] as String,
|
||||
@@ -44,6 +45,7 @@ Map<String, dynamic> _$$ProfileModelImplToJson(_$ProfileModelImpl instance) =>
|
||||
'created_at': instance.createdAt?.toIso8601String(),
|
||||
'updated_at': instance.updatedAt?.toIso8601String(),
|
||||
'is_public_profile': instance.isPublicProfile,
|
||||
'is_entity_subscribed_by_me': instance.isEntitySubscribedByMe,
|
||||
'slug': instance.slug,
|
||||
'profile_photo_url': instance.profilePhotoUrl,
|
||||
'role': instance.role,
|
||||
|
||||
@@ -249,7 +249,7 @@ mixin _$SubModel {
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String get city => throw _privateConstructorUsedError;
|
||||
DateTime get birthdate => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
bool get isEntitySubscribedByMe => throw _privateConstructorUsedError;
|
||||
String get email => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'profile_photo_url')
|
||||
@@ -283,7 +283,7 @@ abstract class $SubModelCopyWith<$Res> {
|
||||
String name,
|
||||
String city,
|
||||
DateTime birthdate,
|
||||
@JsonKey(name: "is_entity_subscribed_by_me") bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') bool isEntitySubscribedByMe,
|
||||
String email,
|
||||
@JsonKey(name: 'profile_photo_url') String profilePhotoUrl,
|
||||
String role,
|
||||
@@ -387,7 +387,7 @@ abstract class _$$SubModelImplCopyWith<$Res>
|
||||
String name,
|
||||
String city,
|
||||
DateTime birthdate,
|
||||
@JsonKey(name: "is_entity_subscribed_by_me") bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') bool isEntitySubscribedByMe,
|
||||
String email,
|
||||
@JsonKey(name: 'profile_photo_url') String profilePhotoUrl,
|
||||
String role,
|
||||
@@ -484,7 +484,7 @@ class _$SubModelImpl implements _SubModel {
|
||||
required this.name,
|
||||
required this.city,
|
||||
required this.birthdate,
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
required this.isEntitySubscribedByMe,
|
||||
required this.email,
|
||||
@JsonKey(name: 'profile_photo_url') required this.profilePhotoUrl,
|
||||
@@ -507,7 +507,7 @@ class _$SubModelImpl implements _SubModel {
|
||||
@override
|
||||
final DateTime birthdate;
|
||||
@override
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
final bool isEntitySubscribedByMe;
|
||||
@override
|
||||
final String email;
|
||||
@@ -596,7 +596,7 @@ abstract class _SubModel implements SubModel {
|
||||
required final String name,
|
||||
required final String city,
|
||||
required final DateTime birthdate,
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
required final bool isEntitySubscribedByMe,
|
||||
required final String email,
|
||||
@JsonKey(name: 'profile_photo_url') required final String profilePhotoUrl,
|
||||
@@ -620,7 +620,7 @@ abstract class _SubModel implements SubModel {
|
||||
@override
|
||||
DateTime get birthdate;
|
||||
@override
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
bool get isEntitySubscribedByMe;
|
||||
@override
|
||||
String get email;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
@@ -145,4 +147,12 @@ class ProfileRepository {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> uploadPhoto(File file) async {
|
||||
try {
|
||||
return await profileService.uploadPhoto(photo: file);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
|
||||
@@ -83,4 +85,10 @@ abstract class ProfileService {
|
||||
Future<void> unsubscribe({
|
||||
@Path() required String slug,
|
||||
});
|
||||
|
||||
@POST('/api/profile/photo/upload')
|
||||
@MultiPart()
|
||||
Future<void> uploadPhoto({
|
||||
@Part(name: 'photo') required File photo,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -504,6 +504,39 @@ class _ProfileService implements ProfileService {
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> uploadPhoto({required File photo}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = FormData();
|
||||
_data.files.add(MapEntry(
|
||||
'photo',
|
||||
MultipartFile.fromFileSync(
|
||||
photo.path,
|
||||
filename: photo.path.split(Platform.pathSeparator).last,
|
||||
),
|
||||
));
|
||||
final _options = _setStreamType<void>(Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
contentType: 'multipart/form-data',
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/profile/photo/upload',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -31,6 +32,7 @@ class MyApp extends StatelessWidget {
|
||||
BlocProvider<NewMessagesCubit>(create: (context) => sl<NewMessagesCubit>()),
|
||||
BlocProvider<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()),
|
||||
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
|
||||
BlocProvider<ProfileAvatarCubit>(create: (context) => sl<ProfileAvatarCubit>()..getMyProfile()),
|
||||
],
|
||||
child: Portal(
|
||||
child: KeyboardDismisser(
|
||||
|
||||
Reference in New Issue
Block a user