diff --git a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart b/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart index d3edddc8..6c119f19 100644 --- a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart +++ b/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart @@ -13,7 +13,7 @@ class ChatSelectCubit extends Cubit { emit(ChatSelectState.loading()); try { emit(ChatSelectState.loaded( - chatRooms: (await chatRepository.getChatRoomPreiews(query: query)), + chatRooms: (await chatRepository.getChatRoomPreviews(query: query)), )); } catch (e) { emit( diff --git a/lib/features/chat/repositories/chat_repository.dart b/lib/features/chat/repositories/chat_repository.dart index f83fa563..84347c68 100644 --- a/lib/features/chat/repositories/chat_repository.dart +++ b/lib/features/chat/repositories/chat_repository.dart @@ -55,7 +55,7 @@ class ChatRepository { } } - Future> getChatRoomPreiews({String? query}) async { + Future> getChatRoomPreviews({String? query}) async { try { return (await chatService.getChatRoomPreviews(query: query)).chatRooms; } catch (e) { @@ -81,4 +81,13 @@ class ChatRepository { rethrow; } } + + Future getChatRoomIdByUserSlug({required String slug}) async { + try { + return (await chatService.getChatRoomIdByUserSlug(user: slug)); + } catch (e) { + print(e); + rethrow; + } + } } diff --git a/lib/features/profile/models/badge_model.dart b/lib/features/profile/models/badge_model.dart new file mode 100644 index 00000000..5748c1b7 --- /dev/null +++ b/lib/features/profile/models/badge_model.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'badge_model.freezed.dart'; + +part 'badge_model.g.dart'; + +@freezed +class BadgeModel with _$BadgeModel { + const factory BadgeModel({ + required int id, + required String title, + required String description, + required String image, + required String type, + required String parameters, + }) = _BadgeModel; + + factory BadgeModel.fromJson(Map json) => _$BadgeModelFromJson(json); +} diff --git a/lib/features/profile/models/badge_model.freezed.dart b/lib/features/profile/models/badge_model.freezed.dart new file mode 100644 index 00000000..8791257c --- /dev/null +++ b/lib/features/profile/models/badge_model.freezed.dart @@ -0,0 +1,271 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'badge_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +BadgeModel _$BadgeModelFromJson(Map json) { + return _BadgeModel.fromJson(json); +} + +/// @nodoc +mixin _$BadgeModel { + int get id => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + String get image => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + String get parameters => throw _privateConstructorUsedError; + + /// Serializes this BadgeModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of BadgeModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $BadgeModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BadgeModelCopyWith<$Res> { + factory $BadgeModelCopyWith( + BadgeModel value, $Res Function(BadgeModel) then) = + _$BadgeModelCopyWithImpl<$Res, BadgeModel>; + @useResult + $Res call( + {int id, + String title, + String description, + String image, + String type, + String parameters}); +} + +/// @nodoc +class _$BadgeModelCopyWithImpl<$Res, $Val extends BadgeModel> + implements $BadgeModelCopyWith<$Res> { + _$BadgeModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of BadgeModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? description = null, + Object? image = null, + Object? type = null, + Object? parameters = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + image: null == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + parameters: null == parameters + ? _value.parameters + : parameters // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BadgeModelImplCopyWith<$Res> + implements $BadgeModelCopyWith<$Res> { + factory _$$BadgeModelImplCopyWith( + _$BadgeModelImpl value, $Res Function(_$BadgeModelImpl) then) = + __$$BadgeModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String title, + String description, + String image, + String type, + String parameters}); +} + +/// @nodoc +class __$$BadgeModelImplCopyWithImpl<$Res> + extends _$BadgeModelCopyWithImpl<$Res, _$BadgeModelImpl> + implements _$$BadgeModelImplCopyWith<$Res> { + __$$BadgeModelImplCopyWithImpl( + _$BadgeModelImpl _value, $Res Function(_$BadgeModelImpl) _then) + : super(_value, _then); + + /// Create a copy of BadgeModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? description = null, + Object? image = null, + Object? type = null, + Object? parameters = null, + }) { + return _then(_$BadgeModelImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + image: null == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + parameters: null == parameters + ? _value.parameters + : parameters // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BadgeModelImpl implements _BadgeModel { + const _$BadgeModelImpl( + {required this.id, + required this.title, + required this.description, + required this.image, + required this.type, + required this.parameters}); + + factory _$BadgeModelImpl.fromJson(Map json) => + _$$BadgeModelImplFromJson(json); + + @override + final int id; + @override + final String title; + @override + final String description; + @override + final String image; + @override + final String type; + @override + final String parameters; + + @override + String toString() { + return 'BadgeModel(id: $id, title: $title, description: $description, image: $image, type: $type, parameters: $parameters)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BadgeModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.title, title) || other.title == title) && + (identical(other.description, description) || + other.description == description) && + (identical(other.image, image) || other.image == image) && + (identical(other.type, type) || other.type == type) && + (identical(other.parameters, parameters) || + other.parameters == parameters)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, id, title, description, image, type, parameters); + + /// Create a copy of BadgeModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$BadgeModelImplCopyWith<_$BadgeModelImpl> get copyWith => + __$$BadgeModelImplCopyWithImpl<_$BadgeModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BadgeModelImplToJson( + this, + ); + } +} + +abstract class _BadgeModel implements BadgeModel { + const factory _BadgeModel( + {required final int id, + required final String title, + required final String description, + required final String image, + required final String type, + required final String parameters}) = _$BadgeModelImpl; + + factory _BadgeModel.fromJson(Map json) = + _$BadgeModelImpl.fromJson; + + @override + int get id; + @override + String get title; + @override + String get description; + @override + String get image; + @override + String get type; + @override + String get parameters; + + /// Create a copy of BadgeModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$BadgeModelImplCopyWith<_$BadgeModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/profile/models/badge_model.g.dart b/lib/features/profile/models/badge_model.g.dart new file mode 100644 index 00000000..041f007a --- /dev/null +++ b/lib/features/profile/models/badge_model.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'badge_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BadgeModelImpl _$$BadgeModelImplFromJson(Map json) => + _$BadgeModelImpl( + id: (json['id'] as num).toInt(), + title: json['title'] as String, + description: json['description'] as String, + image: json['image'] as String, + type: json['type'] as String, + parameters: json['parameters'] as String, + ); + +Map _$$BadgeModelImplToJson(_$BadgeModelImpl instance) => + { + 'id': instance.id, + 'title': instance.title, + 'description': instance.description, + 'image': instance.image, + 'type': instance.type, + 'parameters': instance.parameters, + }; diff --git a/lib/features/profile/models/profile_model.dart b/lib/features/profile/models/profile_model.dart index 12580fea..075604fc 100644 --- a/lib/features/profile/models/profile_model.dart +++ b/lib/features/profile/models/profile_model.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/features/profile/models/badge_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'profile_model.freezed.dart'; @@ -16,7 +17,10 @@ class ProfileModel with _$ProfileModel { @JsonKey(name: "is_public_profile") required bool isPublicProfile, @JsonKey(name: "slug") required String slug, @JsonKey(name: "profile_photo_url") required String profilePhotoUrl, - required String role, + @JsonKey(name: "role") required String role, + @JsonKey(name: "badges") required List badges, + @JsonKey(name: "subscriptions") required List subscriptions, + @JsonKey(name: "subscribers") required List subscribers, }) = _ProfileModel; factory ProfileModel.fromJson(Map json) => _$ProfileModelFromJson(json); @@ -56,3 +60,12 @@ class ProfileDetailsModel with _$ProfileDetailsModel { factory ProfileDetailsModel.fromJson(Map json) => _$ProfileDetailsModelFromJson(json); } + +@freezed +class ProfilePreviewModel with _$ProfilePreviewModel { + const factory ProfilePreviewModel({ + @JsonKey(name: "profile_photo_url") required String profilePhotoUrl, + }) = _ProfilePreviewModel; + + factory ProfilePreviewModel.fromJson(Map json) => _$ProfilePreviewModelFromJson(json); +} diff --git a/lib/features/profile/models/profile_model.freezed.dart b/lib/features/profile/models/profile_model.freezed.dart index 844e51af..862e19e6 100644 --- a/lib/features/profile/models/profile_model.freezed.dart +++ b/lib/features/profile/models/profile_model.freezed.dart @@ -38,7 +38,16 @@ mixin _$ProfileModel { String get slug => throw _privateConstructorUsedError; @JsonKey(name: "profile_photo_url") String get profilePhotoUrl => throw _privateConstructorUsedError; + @JsonKey(name: "role") String get role => throw _privateConstructorUsedError; + @JsonKey(name: "badges") + List get badges => throw _privateConstructorUsedError; + @JsonKey(name: "subscriptions") + List get subscriptions => + throw _privateConstructorUsedError; + @JsonKey(name: "subscribers") + List get subscribers => + throw _privateConstructorUsedError; /// Serializes this ProfileModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -66,7 +75,10 @@ abstract class $ProfileModelCopyWith<$Res> { @JsonKey(name: "is_public_profile") bool isPublicProfile, @JsonKey(name: "slug") String slug, @JsonKey(name: "profile_photo_url") String profilePhotoUrl, - String role}); + @JsonKey(name: "role") String role, + @JsonKey(name: "badges") List badges, + @JsonKey(name: "subscriptions") List subscriptions, + @JsonKey(name: "subscribers") List subscribers}); } /// @nodoc @@ -94,6 +106,9 @@ class _$ProfileModelCopyWithImpl<$Res, $Val extends ProfileModel> Object? slug = null, Object? profilePhotoUrl = null, Object? role = null, + Object? badges = null, + Object? subscriptions = null, + Object? subscribers = null, }) { return _then(_value.copyWith( id: null == id @@ -136,6 +151,18 @@ class _$ProfileModelCopyWithImpl<$Res, $Val extends ProfileModel> ? _value.role : role // ignore: cast_nullable_to_non_nullable as String, + badges: null == badges + ? _value.badges + : badges // ignore: cast_nullable_to_non_nullable + as List, + subscriptions: null == subscriptions + ? _value.subscriptions + : subscriptions // ignore: cast_nullable_to_non_nullable + as List, + subscribers: null == subscribers + ? _value.subscribers + : subscribers // ignore: cast_nullable_to_non_nullable + as List, ) as $Val); } } @@ -158,7 +185,10 @@ abstract class _$$ProfileModelImplCopyWith<$Res> @JsonKey(name: "is_public_profile") bool isPublicProfile, @JsonKey(name: "slug") String slug, @JsonKey(name: "profile_photo_url") String profilePhotoUrl, - String role}); + @JsonKey(name: "role") String role, + @JsonKey(name: "badges") List badges, + @JsonKey(name: "subscriptions") List subscriptions, + @JsonKey(name: "subscribers") List subscribers}); } /// @nodoc @@ -184,6 +214,9 @@ class __$$ProfileModelImplCopyWithImpl<$Res> Object? slug = null, Object? profilePhotoUrl = null, Object? role = null, + Object? badges = null, + Object? subscriptions = null, + Object? subscribers = null, }) { return _then(_$ProfileModelImpl( id: null == id @@ -226,6 +259,18 @@ class __$$ProfileModelImplCopyWithImpl<$Res> ? _value.role : role // ignore: cast_nullable_to_non_nullable as String, + badges: null == badges + ? _value._badges + : badges // ignore: cast_nullable_to_non_nullable + as List, + subscriptions: null == subscriptions + ? _value._subscriptions + : subscriptions // ignore: cast_nullable_to_non_nullable + as List, + subscribers: null == subscribers + ? _value._subscribers + : subscribers // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -243,7 +288,15 @@ class _$ProfileModelImpl implements _ProfileModel { @JsonKey(name: "is_public_profile") required this.isPublicProfile, @JsonKey(name: "slug") required this.slug, @JsonKey(name: "profile_photo_url") required this.profilePhotoUrl, - required this.role}); + @JsonKey(name: "role") required this.role, + @JsonKey(name: "badges") required final List badges, + @JsonKey(name: "subscriptions") + required final List subscriptions, + @JsonKey(name: "subscribers") + required final List subscribers}) + : _badges = badges, + _subscriptions = subscriptions, + _subscribers = subscribers; factory _$ProfileModelImpl.fromJson(Map json) => _$$ProfileModelImplFromJson(json); @@ -276,11 +329,38 @@ class _$ProfileModelImpl implements _ProfileModel { @JsonKey(name: "profile_photo_url") final String profilePhotoUrl; @override + @JsonKey(name: "role") final String role; + final List _badges; + @override + @JsonKey(name: "badges") + List get badges { + if (_badges is EqualUnmodifiableListView) return _badges; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_badges); + } + + final List _subscriptions; + @override + @JsonKey(name: "subscriptions") + List get subscriptions { + if (_subscriptions is EqualUnmodifiableListView) return _subscriptions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_subscriptions); + } + + final List _subscribers; + @override + @JsonKey(name: "subscribers") + List get subscribers { + if (_subscribers is EqualUnmodifiableListView) return _subscribers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_subscribers); + } @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)'; + 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)'; } @override @@ -302,13 +382,31 @@ class _$ProfileModelImpl implements _ProfileModel { (identical(other.slug, slug) || other.slug == slug) && (identical(other.profilePhotoUrl, profilePhotoUrl) || other.profilePhotoUrl == profilePhotoUrl) && - (identical(other.role, role) || other.role == role)); + (identical(other.role, role) || other.role == role) && + const DeepCollectionEquality().equals(other._badges, _badges) && + const DeepCollectionEquality() + .equals(other._subscriptions, _subscriptions) && + const DeepCollectionEquality() + .equals(other._subscribers, _subscribers)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, id, name, email, emailVerifiedAt, - createdAt, updatedAt, isPublicProfile, slug, profilePhotoUrl, role); + int get hashCode => Object.hash( + runtimeType, + id, + name, + email, + emailVerifiedAt, + createdAt, + updatedAt, + isPublicProfile, + slug, + profilePhotoUrl, + role, + const DeepCollectionEquality().hash(_badges), + const DeepCollectionEquality().hash(_subscriptions), + const DeepCollectionEquality().hash(_subscribers)); /// Create a copy of ProfileModel /// with the given fields replaced by the non-null parameter values. @@ -337,7 +435,13 @@ abstract class _ProfileModel implements ProfileModel { @JsonKey(name: "is_public_profile") required final bool isPublicProfile, @JsonKey(name: "slug") required final String slug, @JsonKey(name: "profile_photo_url") required final String profilePhotoUrl, - required final String role}) = _$ProfileModelImpl; + @JsonKey(name: "role") required final String role, + @JsonKey(name: "badges") required final List badges, + @JsonKey(name: "subscriptions") + required final List subscriptions, + @JsonKey(name: "subscribers") + required final List + subscribers}) = _$ProfileModelImpl; factory _ProfileModel.fromJson(Map json) = _$ProfileModelImpl.fromJson; @@ -370,7 +474,17 @@ abstract class _ProfileModel implements ProfileModel { @JsonKey(name: "profile_photo_url") String get profilePhotoUrl; @override + @JsonKey(name: "role") String get role; + @override + @JsonKey(name: "badges") + List get badges; + @override + @JsonKey(name: "subscriptions") + List get subscriptions; + @override + @JsonKey(name: "subscribers") + List get subscribers; /// Create a copy of ProfileModel /// with the given fields replaced by the non-null parameter values. @@ -1190,3 +1304,160 @@ abstract class _ProfileDetailsModel implements ProfileDetailsModel { _$$ProfileDetailsModelImplCopyWith<_$ProfileDetailsModelImpl> get copyWith => throw _privateConstructorUsedError; } + +ProfilePreviewModel _$ProfilePreviewModelFromJson(Map json) { + return _ProfilePreviewModel.fromJson(json); +} + +/// @nodoc +mixin _$ProfilePreviewModel { + @JsonKey(name: "profile_photo_url") + String get profilePhotoUrl => throw _privateConstructorUsedError; + + /// Serializes this ProfilePreviewModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProfilePreviewModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfilePreviewModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfilePreviewModelCopyWith<$Res> { + factory $ProfilePreviewModelCopyWith( + ProfilePreviewModel value, $Res Function(ProfilePreviewModel) then) = + _$ProfilePreviewModelCopyWithImpl<$Res, ProfilePreviewModel>; + @useResult + $Res call({@JsonKey(name: "profile_photo_url") String profilePhotoUrl}); +} + +/// @nodoc +class _$ProfilePreviewModelCopyWithImpl<$Res, $Val extends ProfilePreviewModel> + implements $ProfilePreviewModelCopyWith<$Res> { + _$ProfilePreviewModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfilePreviewModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? profilePhotoUrl = null, + }) { + return _then(_value.copyWith( + profilePhotoUrl: null == profilePhotoUrl + ? _value.profilePhotoUrl + : profilePhotoUrl // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProfilePreviewModelImplCopyWith<$Res> + implements $ProfilePreviewModelCopyWith<$Res> { + factory _$$ProfilePreviewModelImplCopyWith(_$ProfilePreviewModelImpl value, + $Res Function(_$ProfilePreviewModelImpl) then) = + __$$ProfilePreviewModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({@JsonKey(name: "profile_photo_url") String profilePhotoUrl}); +} + +/// @nodoc +class __$$ProfilePreviewModelImplCopyWithImpl<$Res> + extends _$ProfilePreviewModelCopyWithImpl<$Res, _$ProfilePreviewModelImpl> + implements _$$ProfilePreviewModelImplCopyWith<$Res> { + __$$ProfilePreviewModelImplCopyWithImpl(_$ProfilePreviewModelImpl _value, + $Res Function(_$ProfilePreviewModelImpl) _then) + : super(_value, _then); + + /// Create a copy of ProfilePreviewModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? profilePhotoUrl = null, + }) { + return _then(_$ProfilePreviewModelImpl( + profilePhotoUrl: null == profilePhotoUrl + ? _value.profilePhotoUrl + : profilePhotoUrl // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProfilePreviewModelImpl implements _ProfilePreviewModel { + const _$ProfilePreviewModelImpl( + {@JsonKey(name: "profile_photo_url") required this.profilePhotoUrl}); + + factory _$ProfilePreviewModelImpl.fromJson(Map json) => + _$$ProfilePreviewModelImplFromJson(json); + + @override + @JsonKey(name: "profile_photo_url") + final String profilePhotoUrl; + + @override + String toString() { + return 'ProfilePreviewModel(profilePhotoUrl: $profilePhotoUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfilePreviewModelImpl && + (identical(other.profilePhotoUrl, profilePhotoUrl) || + other.profilePhotoUrl == profilePhotoUrl)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, profilePhotoUrl); + + /// Create a copy of ProfilePreviewModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfilePreviewModelImplCopyWith<_$ProfilePreviewModelImpl> get copyWith => + __$$ProfilePreviewModelImplCopyWithImpl<_$ProfilePreviewModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ProfilePreviewModelImplToJson( + this, + ); + } +} + +abstract class _ProfilePreviewModel implements ProfilePreviewModel { + const factory _ProfilePreviewModel( + {@JsonKey(name: "profile_photo_url") + required final String profilePhotoUrl}) = _$ProfilePreviewModelImpl; + + factory _ProfilePreviewModel.fromJson(Map json) = + _$ProfilePreviewModelImpl.fromJson; + + @override + @JsonKey(name: "profile_photo_url") + String get profilePhotoUrl; + + /// Create a copy of ProfilePreviewModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfilePreviewModelImplCopyWith<_$ProfilePreviewModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/profile/models/profile_model.g.dart b/lib/features/profile/models/profile_model.g.dart index 7a0c1876..aa2431ca 100644 --- a/lib/features/profile/models/profile_model.g.dart +++ b/lib/features/profile/models/profile_model.g.dart @@ -24,6 +24,15 @@ _$ProfileModelImpl _$$ProfileModelImplFromJson(Map json) => slug: json['slug'] as String, profilePhotoUrl: json['profile_photo_url'] as String, role: json['role'] as String, + badges: (json['badges'] as List) + .map((e) => BadgeModel.fromJson(e as Map)) + .toList(), + subscriptions: (json['subscriptions'] as List) + .map((e) => ProfilePreviewModel.fromJson(e as Map)) + .toList(), + subscribers: (json['subscribers'] as List) + .map((e) => ProfilePreviewModel.fromJson(e as Map)) + .toList(), ); Map _$$ProfileModelImplToJson(_$ProfileModelImpl instance) => @@ -38,6 +47,9 @@ Map _$$ProfileModelImplToJson(_$ProfileModelImpl instance) => 'slug': instance.slug, 'profile_photo_url': instance.profilePhotoUrl, 'role': instance.role, + 'badges': instance.badges, + 'subscriptions': instance.subscriptions, + 'subscribers': instance.subscribers, }; _$ProfileDetailsModelImpl _$$ProfileDetailsModelImplFromJson( @@ -107,3 +119,15 @@ Map _$$ProfileDetailsModelImplToJson( 'is_bedtime_fillable': instance.isBedtimeFillable, 'activity_coefficient': instance.activityCoefficient, }; + +_$ProfilePreviewModelImpl _$$ProfilePreviewModelImplFromJson( + Map json) => + _$ProfilePreviewModelImpl( + profilePhotoUrl: json['profile_photo_url'] as String, + ); + +Map _$$ProfilePreviewModelImplToJson( + _$ProfilePreviewModelImpl instance) => + { + 'profile_photo_url': instance.profilePhotoUrl, + }; diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart index f6052a4b..9830b4be 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart @@ -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 { 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 { print(e); } } + + Future getChatRoomIdBySlug({ + required String slug, + }) async { + try { + return (await chatRepository.getChatRoomIdByUserSlug(slug: slug)).id; + } catch (e) { + print(e); + rethrow; + } + } } diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 7c61c915..ebeaa90c 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -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 createState() => _ProfileScreenState(); @@ -49,254 +52,7 @@ class _ProfileScreenState extends State { 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 { ); } - _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 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), + ], ), ); } diff --git a/lib/locator_service.dart b/lib/locator_service.dart index e64ee309..078ea81c 100644 --- a/lib/locator_service.dart +++ b/lib/locator_service.dart @@ -55,7 +55,7 @@ Future init() async { sl.registerFactory(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl())); sl.registerFactory(() => DiaryBloc(diaryRepository: sl())); sl.registerFactory(() => CalendarCubit(calendarRepository: sl())); - sl.registerFactory(() => ProfileCubit(profileRepository: sl())); + sl.registerFactory(() => ProfileCubit(profileRepository: sl(), chatRepository: sl())); sl.registerFactory(() => ProfileAvatarCubit(profileRepository: sl())); sl.registerFactory(() => RecipeCreateCubit(recipeRepository: sl())); sl.registerFactory(() => DietsCubit(feedRepository: sl()));