From e2b54cca9953af2c4ae333fa293c646dc53876cc Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 18 Oct 2024 17:47:10 +0300 Subject: [PATCH] feat(profile): add badges and plans --- .../.kotlin/errors/errors-1729261970802.log | 4 + lib/features/profile/models/badge_model.dart | 2 +- .../profile/models/badge_model.freezed.dart | 34 ++++--- .../profile/models/badge_model.g.dart | 4 +- .../profile/models/profile_model.dart | 3 +- .../profile/models/profile_model.freezed.dart | 42 ++++++-- .../profile/models/profile_model.g.dart | 4 + .../presentation/pages/profile_screen.dart | 98 ++++++++++++++++--- 8 files changed, 151 insertions(+), 40 deletions(-) create mode 100644 android/.kotlin/errors/errors-1729261970802.log diff --git a/android/.kotlin/errors/errors-1729261970802.log b/android/.kotlin/errors/errors-1729261970802.log new file mode 100644 index 00000000..7cd151b6 --- /dev/null +++ b/android/.kotlin/errors/errors-1729261970802.log @@ -0,0 +1,4 @@ +kotlin version: 2.0.20 +error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output: + 1. Kotlin compile daemon is ready + diff --git a/lib/features/profile/models/badge_model.dart b/lib/features/profile/models/badge_model.dart index 5748c1b7..fc9f1f2a 100644 --- a/lib/features/profile/models/badge_model.dart +++ b/lib/features/profile/models/badge_model.dart @@ -12,7 +12,7 @@ class BadgeModel with _$BadgeModel { required String description, required String image, required String type, - required String parameters, + required List 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 index 8791257c..ef3d1d1a 100644 --- a/lib/features/profile/models/badge_model.freezed.dart +++ b/lib/features/profile/models/badge_model.freezed.dart @@ -25,7 +25,7 @@ mixin _$BadgeModel { String get description => throw _privateConstructorUsedError; String get image => throw _privateConstructorUsedError; String get type => throw _privateConstructorUsedError; - String get parameters => throw _privateConstructorUsedError; + List get parameters => throw _privateConstructorUsedError; /// Serializes this BadgeModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -49,7 +49,7 @@ abstract class $BadgeModelCopyWith<$Res> { String description, String image, String type, - String parameters}); + List parameters}); } /// @nodoc @@ -98,7 +98,7 @@ class _$BadgeModelCopyWithImpl<$Res, $Val extends BadgeModel> parameters: null == parameters ? _value.parameters : parameters // ignore: cast_nullable_to_non_nullable - as String, + as List, ) as $Val); } } @@ -117,7 +117,7 @@ abstract class _$$BadgeModelImplCopyWith<$Res> String description, String image, String type, - String parameters}); + List parameters}); } /// @nodoc @@ -162,9 +162,9 @@ class __$$BadgeModelImplCopyWithImpl<$Res> : type // ignore: cast_nullable_to_non_nullable as String, parameters: null == parameters - ? _value.parameters + ? _value._parameters : parameters // ignore: cast_nullable_to_non_nullable - as String, + as List, )); } } @@ -178,7 +178,8 @@ class _$BadgeModelImpl implements _BadgeModel { required this.description, required this.image, required this.type, - required this.parameters}); + required final List parameters}) + : _parameters = parameters; factory _$BadgeModelImpl.fromJson(Map json) => _$$BadgeModelImplFromJson(json); @@ -193,8 +194,13 @@ class _$BadgeModelImpl implements _BadgeModel { final String image; @override final String type; + final List _parameters; @override - final String parameters; + List get parameters { + if (_parameters is EqualUnmodifiableListView) return _parameters; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_parameters); + } @override String toString() { @@ -212,14 +218,14 @@ class _$BadgeModelImpl implements _BadgeModel { other.description == description) && (identical(other.image, image) || other.image == image) && (identical(other.type, type) || other.type == type) && - (identical(other.parameters, parameters) || - other.parameters == parameters)); + const DeepCollectionEquality() + .equals(other._parameters, _parameters)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => - Object.hash(runtimeType, id, title, description, image, type, parameters); + int get hashCode => Object.hash(runtimeType, id, title, description, image, + type, const DeepCollectionEquality().hash(_parameters)); /// Create a copy of BadgeModel /// with the given fields replaced by the non-null parameter values. @@ -244,7 +250,7 @@ abstract class _BadgeModel implements BadgeModel { required final String description, required final String image, required final String type, - required final String parameters}) = _$BadgeModelImpl; + required final List parameters}) = _$BadgeModelImpl; factory _BadgeModel.fromJson(Map json) = _$BadgeModelImpl.fromJson; @@ -260,7 +266,7 @@ abstract class _BadgeModel implements BadgeModel { @override String get type; @override - String get parameters; + List get parameters; /// Create a copy of BadgeModel /// with the given fields replaced by the non-null parameter values. diff --git a/lib/features/profile/models/badge_model.g.dart b/lib/features/profile/models/badge_model.g.dart index 041f007a..f2cf5531 100644 --- a/lib/features/profile/models/badge_model.g.dart +++ b/lib/features/profile/models/badge_model.g.dart @@ -13,7 +13,9 @@ _$BadgeModelImpl _$$BadgeModelImplFromJson(Map json) => description: json['description'] as String, image: json['image'] as String, type: json['type'] as String, - parameters: json['parameters'] as String, + parameters: (json['parameters'] as List) + .map((e) => e as String) + .toList(), ); Map _$$BadgeModelImplToJson(_$BadgeModelImpl instance) => diff --git a/lib/features/profile/models/profile_model.dart b/lib/features/profile/models/profile_model.dart index 547f2e8a..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'; @@ -17,7 +18,7 @@ class ProfileModel with _$ProfileModel { @JsonKey(name: "slug") required String slug, @JsonKey(name: "profile_photo_url") required String profilePhotoUrl, @JsonKey(name: "role") required String role, - // @JsonKey(name: "badges") required List badges, + @JsonKey(name: "badges") required List badges, @JsonKey(name: "subscriptions") required List subscriptions, @JsonKey(name: "subscribers") required List subscribers, }) = _ProfileModel; diff --git a/lib/features/profile/models/profile_model.freezed.dart b/lib/features/profile/models/profile_model.freezed.dart index 60d07e27..862e19e6 100644 --- a/lib/features/profile/models/profile_model.freezed.dart +++ b/lib/features/profile/models/profile_model.freezed.dart @@ -39,8 +39,9 @@ mixin _$ProfileModel { @JsonKey(name: "profile_photo_url") String get profilePhotoUrl => throw _privateConstructorUsedError; @JsonKey(name: "role") - String get role => - throw _privateConstructorUsedError; // @JsonKey(name: "badges") required List badges, + String get role => throw _privateConstructorUsedError; + @JsonKey(name: "badges") + List get badges => throw _privateConstructorUsedError; @JsonKey(name: "subscriptions") List get subscriptions => throw _privateConstructorUsedError; @@ -75,6 +76,7 @@ abstract class $ProfileModelCopyWith<$Res> { @JsonKey(name: "slug") String slug, @JsonKey(name: "profile_photo_url") String profilePhotoUrl, @JsonKey(name: "role") String role, + @JsonKey(name: "badges") List badges, @JsonKey(name: "subscriptions") List subscriptions, @JsonKey(name: "subscribers") List subscribers}); } @@ -104,6 +106,7 @@ class _$ProfileModelCopyWithImpl<$Res, $Val extends ProfileModel> Object? slug = null, Object? profilePhotoUrl = null, Object? role = null, + Object? badges = null, Object? subscriptions = null, Object? subscribers = null, }) { @@ -148,6 +151,10 @@ 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 @@ -179,6 +186,7 @@ abstract class _$$ProfileModelImplCopyWith<$Res> @JsonKey(name: "slug") String slug, @JsonKey(name: "profile_photo_url") String profilePhotoUrl, @JsonKey(name: "role") String role, + @JsonKey(name: "badges") List badges, @JsonKey(name: "subscriptions") List subscriptions, @JsonKey(name: "subscribers") List subscribers}); } @@ -206,6 +214,7 @@ class __$$ProfileModelImplCopyWithImpl<$Res> Object? slug = null, Object? profilePhotoUrl = null, Object? role = null, + Object? badges = null, Object? subscriptions = null, Object? subscribers = null, }) { @@ -250,6 +259,10 @@ 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 @@ -276,11 +289,13 @@ class _$ProfileModelImpl implements _ProfileModel { @JsonKey(name: "slug") required this.slug, @JsonKey(name: "profile_photo_url") required this.profilePhotoUrl, @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}) - : _subscriptions = subscriptions, + : _badges = badges, + _subscriptions = subscriptions, _subscribers = subscribers; factory _$ProfileModelImpl.fromJson(Map json) => @@ -316,9 +331,16 @@ class _$ProfileModelImpl implements _ProfileModel { @override @JsonKey(name: "role") final String role; -// @JsonKey(name: "badges") required List badges, + 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; -// @JsonKey(name: "badges") required List badges, @override @JsonKey(name: "subscriptions") List get subscriptions { @@ -338,7 +360,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, subscriptions: $subscriptions, subscribers: $subscribers)'; + 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 @@ -361,6 +383,7 @@ class _$ProfileModelImpl implements _ProfileModel { (identical(other.profilePhotoUrl, profilePhotoUrl) || other.profilePhotoUrl == profilePhotoUrl) && (identical(other.role, role) || other.role == role) && + const DeepCollectionEquality().equals(other._badges, _badges) && const DeepCollectionEquality() .equals(other._subscriptions, _subscriptions) && const DeepCollectionEquality() @@ -381,6 +404,7 @@ class _$ProfileModelImpl implements _ProfileModel { slug, profilePhotoUrl, role, + const DeepCollectionEquality().hash(_badges), const DeepCollectionEquality().hash(_subscriptions), const DeepCollectionEquality().hash(_subscribers)); @@ -412,6 +436,7 @@ abstract class _ProfileModel implements ProfileModel { @JsonKey(name: "slug") required final String slug, @JsonKey(name: "profile_photo_url") required final String profilePhotoUrl, @JsonKey(name: "role") required final String role, + @JsonKey(name: "badges") required final List badges, @JsonKey(name: "subscriptions") required final List subscriptions, @JsonKey(name: "subscribers") @@ -450,7 +475,10 @@ abstract class _ProfileModel implements ProfileModel { String get profilePhotoUrl; @override @JsonKey(name: "role") - String get role; // @JsonKey(name: "badges") required List badges, + String get role; + @override + @JsonKey(name: "badges") + List get badges; @override @JsonKey(name: "subscriptions") List get subscriptions; diff --git a/lib/features/profile/models/profile_model.g.dart b/lib/features/profile/models/profile_model.g.dart index 041a8bdb..aa2431ca 100644 --- a/lib/features/profile/models/profile_model.g.dart +++ b/lib/features/profile/models/profile_model.g.dart @@ -24,6 +24,9 @@ _$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(), @@ -44,6 +47,7 @@ Map _$$ProfileModelImplToJson(_$ProfileModelImpl instance) => 'slug': instance.slug, 'profile_photo_url': instance.profilePhotoUrl, 'role': instance.role, + 'badges': instance.badges, 'subscriptions': instance.subscriptions, 'subscribers': instance.subscribers, }; diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index bfb639c3..a9122aab 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -1,4 +1,5 @@ import 'package:cached_network_image/cached_network_image.dart'; +import 'package:eatvio_app/core/const.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'; @@ -121,6 +122,8 @@ class _ProfileScreenState extends State { if (widget.author == null) const SizedBox(height: 16), if (widget.author == null) _charts(), if (widget.author == null) const SizedBox(height: 16), + _activePlans(state), + if (widget.author == null) const SizedBox(height: 16), _newBlogPostButton(), _blogPosts(state), ], @@ -129,6 +132,74 @@ class _ProfileScreenState extends State { ); } + _activePlans(ProfileStateLoaded state) { + if (state.profile.role != 'consultant') { + return const SizedBox.shrink(); + } + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Активные планы', + style: TextStyle( + color: Color(0xFF323F4B), + fontSize: 24, + height: 1, + fontWeight: FontWeight.w600, + ), + ), + Text( + widget.author == null ? 'Редактировать' : 'Показать все', + style: TextStyle( + color: Color(0xFF2F80ED), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + const SizedBox(height: 8), + _bullets(0, 4), + ], + ); + } + + _bullets(int currentBullet, int bulletCount) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < bulletCount; i++) ...[ + _bullet(i == currentBullet), + i < bulletCount - 1 ? const SizedBox(width: 4) : const SizedBox.shrink(), + ], + ], + ), + ); + } + + _bullet(bool isActive) { + return AnimatedContainer( + width: isActive ? 10 : 8, + height: isActive ? 10 : 8, + duration: Duration(milliseconds: 150), + decoration: BoxDecoration( + color: isActive ? appTheme.blueA400 : Color(0xFFD7D9DC), + shape: BoxShape.circle, + ), + ); + } + _newBlogPostButton() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4), @@ -185,7 +256,7 @@ class _ProfileScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Дипломы\nисертификаты', + 'Дипломы\nи сертификаты', style: TextStyle( color: Color(0xFF323F4B), fontSize: 24, @@ -206,12 +277,13 @@ class _ProfileScreenState extends State { ), const SizedBox(height: 8), Container( - width: MediaQuery.of(context).size.width - 8, + width: MediaQuery.of(context).size.width - 4, padding: const EdgeInsets.symmetric(horizontal: 4), child: Material( color: Colors.white, borderRadius: BorderRadius.circular(12), child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( @@ -410,20 +482,14 @@ class _ProfileScreenState extends State { scrollDirection: Axis.horizontal, child: Row( children: [ - CircleAvatar( - radius: 20, - backgroundColor: Colors.grey.shade200, - ), - const SizedBox(width: 8), - CircleAvatar( - radius: 20, - backgroundColor: Colors.grey.shade200, - ), - const SizedBox(width: 8), - CircleAvatar( - radius: 20, - backgroundColor: Colors.grey.shade200, - ), + for (int i = 0; i < state.profile.badges.length; i++) ...[ + SvgPicture.network( + baseUrl + '/storage/' + state.profile.badges[i].image, + width: 40, + height: 40, + ), + i < state.profile.badges.length - 1 ? const SizedBox(width: 8) : const SizedBox.shrink(), + ], ], ), ),