diff --git a/assets/icons/calendar.svg b/assets/icons/calendar.svg new file mode 100644 index 00000000..e9d97952 --- /dev/null +++ b/assets/icons/calendar.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/diets.svg b/assets/icons/diets.svg new file mode 100644 index 00000000..7714b3fa --- /dev/null +++ b/assets/icons/diets.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/lib/app_router.dart b/lib/app_router.dart index 57923180..3a662763 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -24,7 +24,6 @@ import 'package:eatvio_app/presentation/diets/diet_select_screen/diet_select_scr import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart'; import 'package:eatvio_app/presentation/glossary/glossary_screen/glossary_screen.dart'; import 'package:eatvio_app/presentation/plans/plan_select_screen/plan_select_screen.dart'; -import 'package:eatvio_app/presentation/profile/my_profile_screen/my_profile_screen.dart'; import 'package:eatvio_app/presentation/profile/my_profile_screen/my_settings_screen.dart'; import 'package:eatvio_app/presentation/recipe/recipe_select_screen/recipe_select_screen.dart'; import 'package:flutter/material.dart'; @@ -62,7 +61,7 @@ class AppRouter { }, routes: [ GoRoute( - pageBuilder: (context, state) => NoTransitionPage(child: MyProfileScreen()), + pageBuilder: (context, state) => NoTransitionPage(child: ProfileScreen()), path: '/my-profile-screen', name: 'myProfileScreen', redirect: (context, state) async { diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart new file mode 100644 index 00000000..e2bfd48b --- /dev/null +++ b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart @@ -0,0 +1,22 @@ +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart'; +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ProfileAvatarCubit extends Cubit { + final ProfileRepository profileRepository; + + ProfileAvatarCubit({ + required this.profileRepository, + }) : super(const ProfileAvatarState.loading()); + + getMyProfile() async { + try { + emit(ProfileAvatarState.loaded( + profile: await profileRepository.getMyProfile(), + )); + } catch (e) { + emit(ProfileAvatarState.error(errorMessage: e.toString())); + print(e); + } + } +} \ No newline at end of file diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart new file mode 100644 index 00000000..fe90fb6f --- /dev/null +++ b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart @@ -0,0 +1,19 @@ +import 'package:eatvio_app/features/profile/models/profile_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'profile_avatar_state.freezed.dart'; + +@freezed +abstract class ProfileAvatarState with _$ProfileAvatarState { + const factory ProfileAvatarState() = _ProfileAvatarState; + + const factory ProfileAvatarState.loading() = ProfileAvatarStateLoading; + + const factory ProfileAvatarState.loaded({ + required ProfileModel profile, + }) = ProfileAvatarStateLoaded; + + const factory ProfileAvatarState.error({ + required String errorMessage, + }) = ProfileAvatarStateError; +} diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.freezed.dart b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.freezed.dart new file mode 100644 index 00000000..5d8de7ff --- /dev/null +++ b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.freezed.dart @@ -0,0 +1,630 @@ +// 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 'profile_avatar_state.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'); + +/// @nodoc +mixin _$ProfileAvatarState { + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(ProfileModel profile) loaded, + required TResult Function(String errorMessage) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(ProfileModel profile)? loaded, + TResult? Function(String errorMessage)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(ProfileModel profile)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map( + TResult Function(_ProfileAvatarState value) $default, { + required TResult Function(ProfileAvatarStateLoading value) loading, + required TResult Function(ProfileAvatarStateLoaded value) loaded, + required TResult Function(ProfileAvatarStateError value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ProfileAvatarState value)? $default, { + TResult? Function(ProfileAvatarStateLoading value)? loading, + TResult? Function(ProfileAvatarStateLoaded value)? loaded, + TResult? Function(ProfileAvatarStateError value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ProfileAvatarState value)? $default, { + TResult Function(ProfileAvatarStateLoading value)? loading, + TResult Function(ProfileAvatarStateLoaded value)? loaded, + TResult Function(ProfileAvatarStateError value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfileAvatarStateCopyWith<$Res> { + factory $ProfileAvatarStateCopyWith( + ProfileAvatarState value, $Res Function(ProfileAvatarState) then) = + _$ProfileAvatarStateCopyWithImpl<$Res, ProfileAvatarState>; +} + +/// @nodoc +class _$ProfileAvatarStateCopyWithImpl<$Res, $Val extends ProfileAvatarState> + implements $ProfileAvatarStateCopyWith<$Res> { + _$ProfileAvatarStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$ProfileAvatarStateImplCopyWith<$Res> { + factory _$$ProfileAvatarStateImplCopyWith(_$ProfileAvatarStateImpl value, + $Res Function(_$ProfileAvatarStateImpl) then) = + __$$ProfileAvatarStateImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ProfileAvatarStateImplCopyWithImpl<$Res> + extends _$ProfileAvatarStateCopyWithImpl<$Res, _$ProfileAvatarStateImpl> + implements _$$ProfileAvatarStateImplCopyWith<$Res> { + __$$ProfileAvatarStateImplCopyWithImpl(_$ProfileAvatarStateImpl _value, + $Res Function(_$ProfileAvatarStateImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$ProfileAvatarStateImpl implements _ProfileAvatarState { + const _$ProfileAvatarStateImpl(); + + @override + String toString() { + return 'ProfileAvatarState()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$ProfileAvatarStateImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(ProfileModel profile) loaded, + required TResult Function(String errorMessage) error, + }) { + return $default(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(ProfileModel profile)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return $default?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(ProfileModel profile)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_ProfileAvatarState value) $default, { + required TResult Function(ProfileAvatarStateLoading value) loading, + required TResult Function(ProfileAvatarStateLoaded value) loaded, + required TResult Function(ProfileAvatarStateError value) error, + }) { + return $default(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ProfileAvatarState value)? $default, { + TResult? Function(ProfileAvatarStateLoading value)? loading, + TResult? Function(ProfileAvatarStateLoaded value)? loaded, + TResult? Function(ProfileAvatarStateError value)? error, + }) { + return $default?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ProfileAvatarState value)? $default, { + TResult Function(ProfileAvatarStateLoading value)? loading, + TResult Function(ProfileAvatarStateLoaded value)? loaded, + TResult Function(ProfileAvatarStateError value)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(this); + } + return orElse(); + } +} + +abstract class _ProfileAvatarState implements ProfileAvatarState { + const factory _ProfileAvatarState() = _$ProfileAvatarStateImpl; +} + +/// @nodoc +abstract class _$$ProfileAvatarStateLoadingImplCopyWith<$Res> { + factory _$$ProfileAvatarStateLoadingImplCopyWith( + _$ProfileAvatarStateLoadingImpl value, + $Res Function(_$ProfileAvatarStateLoadingImpl) then) = + __$$ProfileAvatarStateLoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ProfileAvatarStateLoadingImplCopyWithImpl<$Res> + extends _$ProfileAvatarStateCopyWithImpl<$Res, + _$ProfileAvatarStateLoadingImpl> + implements _$$ProfileAvatarStateLoadingImplCopyWith<$Res> { + __$$ProfileAvatarStateLoadingImplCopyWithImpl( + _$ProfileAvatarStateLoadingImpl _value, + $Res Function(_$ProfileAvatarStateLoadingImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$ProfileAvatarStateLoadingImpl implements ProfileAvatarStateLoading { + const _$ProfileAvatarStateLoadingImpl(); + + @override + String toString() { + return 'ProfileAvatarState.loading()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfileAvatarStateLoadingImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(ProfileModel profile) loaded, + required TResult Function(String errorMessage) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(ProfileModel profile)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(ProfileModel profile)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_ProfileAvatarState value) $default, { + required TResult Function(ProfileAvatarStateLoading value) loading, + required TResult Function(ProfileAvatarStateLoaded value) loaded, + required TResult Function(ProfileAvatarStateError value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ProfileAvatarState value)? $default, { + TResult? Function(ProfileAvatarStateLoading value)? loading, + TResult? Function(ProfileAvatarStateLoaded value)? loaded, + TResult? Function(ProfileAvatarStateError value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ProfileAvatarState value)? $default, { + TResult Function(ProfileAvatarStateLoading value)? loading, + TResult Function(ProfileAvatarStateLoaded value)? loaded, + TResult Function(ProfileAvatarStateError value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class ProfileAvatarStateLoading implements ProfileAvatarState { + const factory ProfileAvatarStateLoading() = _$ProfileAvatarStateLoadingImpl; +} + +/// @nodoc +abstract class _$$ProfileAvatarStateLoadedImplCopyWith<$Res> { + factory _$$ProfileAvatarStateLoadedImplCopyWith( + _$ProfileAvatarStateLoadedImpl value, + $Res Function(_$ProfileAvatarStateLoadedImpl) then) = + __$$ProfileAvatarStateLoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({ProfileModel profile}); + + $ProfileModelCopyWith<$Res> get profile; +} + +/// @nodoc +class __$$ProfileAvatarStateLoadedImplCopyWithImpl<$Res> + extends _$ProfileAvatarStateCopyWithImpl<$Res, + _$ProfileAvatarStateLoadedImpl> + implements _$$ProfileAvatarStateLoadedImplCopyWith<$Res> { + __$$ProfileAvatarStateLoadedImplCopyWithImpl( + _$ProfileAvatarStateLoadedImpl _value, + $Res Function(_$ProfileAvatarStateLoadedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? profile = null, + }) { + return _then(_$ProfileAvatarStateLoadedImpl( + profile: null == profile + ? _value.profile + : profile // ignore: cast_nullable_to_non_nullable + as ProfileModel, + )); + } + + @override + @pragma('vm:prefer-inline') + $ProfileModelCopyWith<$Res> get profile { + return $ProfileModelCopyWith<$Res>(_value.profile, (value) { + return _then(_value.copyWith(profile: value)); + }); + } +} + +/// @nodoc + +class _$ProfileAvatarStateLoadedImpl implements ProfileAvatarStateLoaded { + const _$ProfileAvatarStateLoadedImpl({required this.profile}); + + @override + final ProfileModel profile; + + @override + String toString() { + return 'ProfileAvatarState.loaded(profile: $profile)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfileAvatarStateLoadedImpl && + (identical(other.profile, profile) || other.profile == profile)); + } + + @override + int get hashCode => Object.hash(runtimeType, profile); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ProfileAvatarStateLoadedImplCopyWith<_$ProfileAvatarStateLoadedImpl> + get copyWith => __$$ProfileAvatarStateLoadedImplCopyWithImpl< + _$ProfileAvatarStateLoadedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(ProfileModel profile) loaded, + required TResult Function(String errorMessage) error, + }) { + return loaded(profile); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(ProfileModel profile)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loaded?.call(profile); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(ProfileModel profile)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(profile); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_ProfileAvatarState value) $default, { + required TResult Function(ProfileAvatarStateLoading value) loading, + required TResult Function(ProfileAvatarStateLoaded value) loaded, + required TResult Function(ProfileAvatarStateError value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ProfileAvatarState value)? $default, { + TResult? Function(ProfileAvatarStateLoading value)? loading, + TResult? Function(ProfileAvatarStateLoaded value)? loaded, + TResult? Function(ProfileAvatarStateError value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ProfileAvatarState value)? $default, { + TResult Function(ProfileAvatarStateLoading value)? loading, + TResult Function(ProfileAvatarStateLoaded value)? loaded, + TResult Function(ProfileAvatarStateError value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class ProfileAvatarStateLoaded implements ProfileAvatarState { + const factory ProfileAvatarStateLoaded( + {required final ProfileModel profile}) = _$ProfileAvatarStateLoadedImpl; + + ProfileModel get profile; + @JsonKey(ignore: true) + _$$ProfileAvatarStateLoadedImplCopyWith<_$ProfileAvatarStateLoadedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ProfileAvatarStateErrorImplCopyWith<$Res> { + factory _$$ProfileAvatarStateErrorImplCopyWith( + _$ProfileAvatarStateErrorImpl value, + $Res Function(_$ProfileAvatarStateErrorImpl) then) = + __$$ProfileAvatarStateErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String errorMessage}); +} + +/// @nodoc +class __$$ProfileAvatarStateErrorImplCopyWithImpl<$Res> + extends _$ProfileAvatarStateCopyWithImpl<$Res, + _$ProfileAvatarStateErrorImpl> + implements _$$ProfileAvatarStateErrorImplCopyWith<$Res> { + __$$ProfileAvatarStateErrorImplCopyWithImpl( + _$ProfileAvatarStateErrorImpl _value, + $Res Function(_$ProfileAvatarStateErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? errorMessage = null, + }) { + return _then(_$ProfileAvatarStateErrorImpl( + errorMessage: null == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$ProfileAvatarStateErrorImpl implements ProfileAvatarStateError { + const _$ProfileAvatarStateErrorImpl({required this.errorMessage}); + + @override + final String errorMessage; + + @override + String toString() { + return 'ProfileAvatarState.error(errorMessage: $errorMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfileAvatarStateErrorImpl && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ProfileAvatarStateErrorImplCopyWith<_$ProfileAvatarStateErrorImpl> + get copyWith => __$$ProfileAvatarStateErrorImplCopyWithImpl< + _$ProfileAvatarStateErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(ProfileModel profile) loaded, + required TResult Function(String errorMessage) error, + }) { + return error(errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(ProfileModel profile)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return error?.call(errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(ProfileModel profile)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_ProfileAvatarState value) $default, { + required TResult Function(ProfileAvatarStateLoading value) loading, + required TResult Function(ProfileAvatarStateLoaded value) loaded, + required TResult Function(ProfileAvatarStateError value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ProfileAvatarState value)? $default, { + TResult? Function(ProfileAvatarStateLoading value)? loading, + TResult? Function(ProfileAvatarStateLoaded value)? loaded, + TResult? Function(ProfileAvatarStateError value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ProfileAvatarState value)? $default, { + TResult Function(ProfileAvatarStateLoading value)? loading, + TResult Function(ProfileAvatarStateLoaded value)? loaded, + TResult Function(ProfileAvatarStateError value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class ProfileAvatarStateError implements ProfileAvatarState { + const factory ProfileAvatarStateError({required final String errorMessage}) = + _$ProfileAvatarStateErrorImpl; + + String get errorMessage; + @JsonKey(ignore: true) + _$$ProfileAvatarStateErrorImplCopyWith<_$ProfileAvatarStateErrorImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart index b3f74075..a7e12393 100644 --- a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart +++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart @@ -4,6 +4,8 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.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/notifications_cubit/notifications_state.dart'; +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/widgets/custom_navigation_menu.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -68,119 +70,154 @@ class _BottomNavigationBarWidgetState extends State { Widget build(BuildContext context) { return Scaffold( body: widget.child, - bottomNavigationBar: BottomNavigationBar( - type: BottomNavigationBarType.fixed, - onTap: _onItemTapped, - selectedFontSize: 12, - unselectedFontSize: 12, - selectedItemColor: appTheme.blueA400, - currentIndex: currentBottomNavBarIndex, - items: [ - BottomNavigationBarItem( - label: '', - icon: SvgPicture.asset( - ImageConstant.imgLogo, - ), - ), - BottomNavigationBarItem( - label: 'Лента', - icon: SvgPicture.asset( - colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn), - width: 24, - height: 24, - ImageConstant.icnFeed, - ), - activeIcon: SvgPicture.asset( - ImageConstant.icnFeed, - width: 24, - height: 24, - colorFilter: ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, + bottomNavigationBar: SizedBox( + height: 58, + child: BottomNavigationBar( + type: BottomNavigationBarType.fixed, + onTap: _onItemTapped, + selectedFontSize: 10, + unselectedFontSize: 10, + selectedItemColor: appTheme.blueA400, + currentIndex: currentBottomNavBarIndex, + items: [ + BottomNavigationBarItem( + label: '', + icon: SvgPicture.asset( + ImageConstant.imgLogo, + height: 32, ), ), - ), - BottomNavigationBarItem( - label: "diary".tr, - icon: SvgPicture.asset( - colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn), - ImageConstant.icnDiary, - width: 24, - height: 24, - ), - activeIcon: SvgPicture.asset( - ImageConstant.icnDiary, - width: 24, - height: 24, - colorFilter: ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, + BottomNavigationBarItem( + label: 'Лента', + icon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn), + width: 20, + height: 20, + ImageConstant.icnFeed, + ), + ), + activeIcon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + ImageConstant.icnFeed, + width: 20, + height: 20, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), + ), ), ), - ), - BottomNavigationBarItem( - label: "chat".tr, - icon: BlocBuilder( - builder: (BuildContext context, NewMessagesState state) { - return badges.Badge( - position: badges.BadgePosition.topEnd(), - badgeContent: Text( - (state is NewMessagesStateLoaded) ? state.messagesCount.toString() : '0', - style: TextStyle( - color: Colors.white, + BottomNavigationBarItem( + label: "diary".tr, + icon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn), + ImageConstant.icnDiary, + width: 20, + height: 20, + ), + ), + activeIcon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + ImageConstant.icnDiary, + width: 20, + height: 20, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), + ), + ), + ), + BottomNavigationBarItem( + label: "chat".tr, + icon: BlocBuilder( + builder: (BuildContext context, NewMessagesState state) { + return badges.Badge( + position: badges.BadgePosition.topEnd(), + badgeAnimation: badges.BadgeAnimation.fade(), + badgeContent: Text( + (state is NewMessagesStateLoaded) ? state.messagesCount.toString() : '0', + style: TextStyle( + color: Colors.white, + fontSize: 8, + ), ), - ), - child: SvgPicture.asset( - ImageConstant.icnChat, - width: 24, - height: 24, - colorFilter: currentBottomNavBarIndex == 3 - ? ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ) - : ColorFilter.mode(Colors.grey, BlendMode.srcIn), - ), - ); - }, - ), - ), - BottomNavigationBarItem( - label: '', - icon: BlocBuilder( - builder: (BuildContext context, NotificationsState state) { - return badges.Badge( - position: badges.BadgePosition.topEnd(), - badgeContent: Text( - (state is NotificationsStateLoaded) ? state.notificationsCount.toString() : '0', - style: TextStyle( - color: Colors.white, + child: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + ImageConstant.icnChat, + width: 20, + height: 20, + colorFilter: currentBottomNavBarIndex == 3 + ? ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ) + : ColorFilter.mode(Colors.grey, BlendMode.srcIn), + ), ), - ), - child: SvgPicture.asset( - ImageConstant.icnBell, - colorFilter: currentBottomNavBarIndex == 4 - ? ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ) - : ColorFilter.mode(Colors.grey, BlendMode.srcIn), - width: 24, - height: 24, - ), - ); - }, + ); + }, + ), ), - ), - BottomNavigationBarItem( - label: '', - icon: Image.asset( - ImageConstant.imgEllipse1, - width: 32, - height: 32, + BottomNavigationBarItem( + label: 'Уведомления', + icon: BlocBuilder( + builder: (BuildContext context, NotificationsState state) { + return badges.Badge( + position: badges.BadgePosition.topEnd(), + badgeAnimation: badges.BadgeAnimation.fade(), + badgeContent: Text( + (state is NotificationsStateLoaded) ? state.notificationsCount.toString() : '0', + style: TextStyle( + color: Colors.white, + fontSize: 8, + ), + ), + child: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + ImageConstant.icnBell, + colorFilter: currentBottomNavBarIndex == 4 + ? ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ) + : ColorFilter.mode(Colors.grey, BlendMode.srcIn), + width: 20, + height: 20, + ), + ), + ); + }, + ), ), - ), - ], + BottomNavigationBarItem( + label: '', + icon: BlocBuilder( + builder: (BuildContext context, ProfileAvatarState state) { + if (state is ProfileAvatarStateLoaded && state.profile.user.profilePhotoUrl.isNotEmpty) { + return CircleAvatar( + backgroundImage: NetworkImage(state.profile.user.profilePhotoUrl), + radius: 16, + ); + } + return CircleAvatar( + radius: 16, + backgroundColor: Colors.black12, + ); + }, + ), + ), + ], + ), ), ); } diff --git a/lib/features/chat/presentation/widgets/chat_bubble_widget.dart b/lib/features/chat/presentation/widgets/chat_bubble_widget.dart index f65fb3bc..eeba8f1e 100644 --- a/lib/features/chat/presentation/widgets/chat_bubble_widget.dart +++ b/lib/features/chat/presentation/widgets/chat_bubble_widget.dart @@ -1,8 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:dismissible_page/dismissible_page.dart'; -import 'package:eatvio_app/core/constants/constants.dart'; +import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; -import 'package:eatvio_app/theme/app_decoration.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -29,12 +28,17 @@ class ChatBubbleWidget extends StatelessWidget { child: DecoratedBox( decoration: BoxDecoration( color: isCurrentUser ? Color(0xFF7F9CF5) : Colors.white, - borderRadius: BorderRadiusStyle.circleBorder17, + borderRadius: mediaData.length > 0 + ? BorderRadius.circular(24) + : BorderRadius.horizontal( + left: isCurrentUser ? Radius.circular(32) : Radius.circular(16), + right: isCurrentUser ? Radius.circular(16) : Radius.circular(24), + ), ), child: Padding( - padding: const EdgeInsets.all(12), + padding: mediaData.length > 0 ? EdgeInsets.all(8) : EdgeInsets.only(top: 8, bottom: 8, left: isCurrentUser ? 16 : 8, right: isCurrentUser ? 8 : 16), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ if (mediaData.isNotEmpty) CustomScrollView( @@ -86,7 +90,7 @@ class ChatBubbleWidget extends StatelessWidget { tag: baseUrl + '/storage/' + '${mediaData[index]}', transitionOnUserGestures: true, child: ClipRRect( - borderRadius: BorderRadiusStyle.roundedBorder8, + borderRadius: BorderRadius.circular(16), child: CachedNetworkImage( imageUrl: baseUrl + '/storage/' + '${mediaData[index]}', ), @@ -103,10 +107,17 @@ class ChatBubbleWidget extends StatelessWidget { if (text.isNotEmpty) Text( text, - style: TextStyle( - color: isCurrentUser ? Colors.white : Color(0xFF323F4B), - ), + style: TextStyle(color: isCurrentUser ? Colors.white : Color(0xFF323F4B), height: 1), ), + SizedBox(height: 4), + Text( + DateTime.parse(sendTime).format('hh:mm'), + style: TextStyle( + color: isCurrentUser ? Colors.white.withOpacity(0.4) : Color(0xFF323F4B).withOpacity(0.4), + height: 1, + fontSize: 10, + ), + ), ], ), ), diff --git a/lib/features/chat/presentation/widgets/chat_search_bar_widget.dart b/lib/features/chat/presentation/widgets/chat_search_bar_widget.dart index 62199d0f..4889fdfd 100644 --- a/lib/features/chat/presentation/widgets/chat_search_bar_widget.dart +++ b/lib/features/chat/presentation/widgets/chat_search_bar_widget.dart @@ -1,5 +1,4 @@ import 'package:eatvio_app/core/app_export.dart'; -import 'package:eatvio_app/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart'; import 'package:flutter/material.dart'; class ChatSearchBarWidget extends StatelessWidget { 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 a1ee1be7..34f6385e 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart @@ -30,4 +30,20 @@ class ProfileCubit extends Cubit { print(e); } } + + addCircumference({ + required int chestCircumference, + required int waistCircumference, + required int hipCircumference, + }) async { + try { + await profileRepository.addCircumference( + chestCircumference: chestCircumference, + waistCircumference: waistCircumference, + hipCircumference: hipCircumference, + ); + } catch (e) { + emit(ProfileState.error(errorMessage: e.toString())); + } + } } diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index d4cec48f..5bd5154a 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -1,46 +1,84 @@ import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; +import 'package:eatvio_app/features/profile/models/profile_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/locator_service.dart'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; -class ProfileScreen extends StatelessWidget { - final Author author; +class ProfileScreen extends StatefulWidget { + final Author? author; - const ProfileScreen({super.key, required this.author}); + const ProfileScreen({super.key, this.author}); + + @override + State createState() => _ProfileScreenState(); +} + +class _ProfileScreenState extends State { + List chartData = []; @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => sl()..getProfileBySlug(author.authorSlug), + create: (BuildContext context) { + if (widget.author == null) + return sl()..getMyProfile(); + else + return sl()..getProfileBySlug(widget.author!.authorSlug); + }, child: BlocBuilder( builder: (BuildContext context, ProfileState state) { if (state is ProfileStateLoaded) { + chartData = state.profile.circumferences; return Scaffold( appBar: AppBar( - centerTitle: true, - title: Text( - state.profile.user.name, - style: TextStyle( - color: Colors.black, - fontSize: 24, - fontWeight: FontWeight.w400, - height: 1, - ), - ), + centerTitle: false, + title: Text(state.profile.user.name), ), body: SingleChildScrollView( - padding: EdgeInsets.all(16), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Center( + Padding( + padding: const EdgeInsets.only(left: 16, bottom: 32), child: CircleAvatar( - maxRadius: 128, - minRadius: 128, + radius: 32, backgroundImage: NetworkImage(state.profile.user.profilePhotoUrl), ), ), + SfCartesianChart( + margin: EdgeInsets.zero, + enableAxisAnimation: true, + enableSideBySideSeriesPlacement: true, + legend: const Legend( + isVisible: true, + opacity: 0.7, + position: LegendPosition.bottom, + ), + plotAreaBorderWidth: 0, + trackballBehavior: TrackballBehavior( + enable: true, + activationMode: ActivationMode.singleTap, + ), + primaryXAxis: DateTimeAxis( + dateFormat: DateFormat.MMMd('ru'), + axisLine: AxisLine( + width: 1, + ), + ), + primaryYAxis: NumericAxis( + labelFormat: '{value} см', + axisLine: AxisLine( + width: 1, + ), + majorTickLines: MajorTickLines(size: 0), + ), + series: _getSplineAreaSeries(), + tooltipBehavior: TooltipBehavior(enable: true), + ), ], ), ), @@ -67,4 +105,64 @@ class ProfileScreen extends StatelessWidget { ), ); } + + List> _getSplineAreaSeries() { + final Color seriesColor1 = Colors.red; + final Color seriesColor2 = Colors.blue; + final Color seriesColor3 = Colors.green; + + return >[ + SplineSeries( + markerSettings: MarkerSettings( + isVisible: true, + color: seriesColor1, + height: 12, + width: 12, + ), + dataLabelSettings: DataLabelSettings( + labelAlignment: ChartDataLabelAlignment.top, + isVisible: true, + ), + dataSource: chartData, + color: seriesColor1, + name: 'Грудь', + xValueMapper: (Circumference value, int index) => value.createdAt, + yValueMapper: (Circumference value, int index) => value.chestCircumference, + ), + SplineSeries( + markerSettings: MarkerSettings( + isVisible: true, + color: seriesColor2, + height: 12, + width: 12, + ), + dataLabelSettings: DataLabelSettings( + labelAlignment: ChartDataLabelAlignment.top, + isVisible: true, + ), + dataSource: chartData, + color: seriesColor2, + name: 'Талия', + xValueMapper: (Circumference value, int index) => value.createdAt, + yValueMapper: (Circumference value, int index) => value.waistCircumference, + ), + SplineSeries( + markerSettings: MarkerSettings( + isVisible: true, + color: seriesColor3, + height: 12, + width: 12, + ), + dataLabelSettings: DataLabelSettings( + labelAlignment: ChartDataLabelAlignment.top, + isVisible: true, + ), + dataSource: chartData, + color: seriesColor3, + name: 'Бёдра', + xValueMapper: (Circumference value, int index) => value.createdAt, + yValueMapper: (Circumference value, int index) => value.hipCircumference, + ), + ]; + } } diff --git a/lib/features/profile/repositories/profile_repository.dart b/lib/features/profile/repositories/profile_repository.dart index b9dbdf1e..a57be856 100644 --- a/lib/features/profile/repositories/profile_repository.dart +++ b/lib/features/profile/repositories/profile_repository.dart @@ -21,4 +21,22 @@ class ProfileRepository { rethrow; } } + + Future addCircumference({ + required int chestCircumference, + required int waistCircumference, + required int hipCircumference, + }) async { + try { + return (await profileService.addCircumference( + chestCircumference: chestCircumference, + waistCircumference: waistCircumference, + hipCircumference: hipCircumference, + )) + .values + .first; + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/profile/services/profile_service.dart b/lib/features/profile/services/profile_service.dart index 5f5657b9..dc9f2864 100644 --- a/lib/features/profile/services/profile_service.dart +++ b/lib/features/profile/services/profile_service.dart @@ -15,4 +15,11 @@ abstract class ProfileService { @GET('/api/profile/index') Future getMyProfile(); + + @POST('/api/profile/addCircumference') + Future> addCircumference({ + @Part(name: 'chest_circumference') required int chestCircumference, + @Part(name: 'waist_circumference') required int waistCircumference, + @Part(name: 'hip_circumference') required int hipCircumference, + }); } diff --git a/lib/features/profile/services/profile_service.g.dart b/lib/features/profile/services/profile_service.g.dart index bc096fdf..7b2969ad 100644 --- a/lib/features/profile/services/profile_service.g.dart +++ b/lib/features/profile/services/profile_service.g.dart @@ -87,6 +87,56 @@ class _ProfileService implements ProfileService { return _value; } + @override + Future> addCircumference({ + required int chestCircumference, + required int waistCircumference, + required int hipCircumference, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = FormData(); + _data.fields.add(MapEntry( + 'chest_circumference', + chestCircumference.toString(), + )); + _data.fields.add(MapEntry( + 'waist_circumference', + waistCircumference.toString(), + )); + _data.fields.add(MapEntry( + 'hip_circumference', + hipCircumference.toString(), + )); + final _options = _setStreamType>(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/profile/addCircumference', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late Map _value; + try { + _value = _result.data!.map((k, dynamic v) => + MapEntry(k, Circumference.fromJson(v as Map))); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/locator_service.dart b/lib/locator_service.dart index f0a28808..a73d21f1 100644 --- a/lib/locator_service.dart +++ b/lib/locator_service.dart @@ -3,6 +3,7 @@ import 'package:eatvio_app/core/constants/constants.dart'; import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.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/calendar/presentation/bloc/calendar_cubit.dart'; import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart'; import 'package:eatvio_app/features/calendar/services/calendar_service.dart'; @@ -45,6 +46,7 @@ Future init() async { sl.registerFactory(() => DiaryBloc(diaryRepository: sl())); sl.registerFactory(() => CalendarCubit(calendarRepository: sl())); sl.registerFactory(() => ProfileCubit(profileRepository: sl())); + sl.registerFactory(() => ProfileAvatarCubit(profileRepository: sl())); sl.registerFactory(() => RecipeCreateCubit(recipeRepository: sl())); sl.registerFactory(() => DietsCubit(feedRepository: sl())); sl.registerFactory(() => CommentsCubit(feedRepository: sl())); diff --git a/lib/main.dart b/lib/main.dart index e031d6a6..a0867795 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart'; import 'package:eatvio_app/core/initialization_cubit/initialization_state.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/calendar/presentation/bloc/calendar_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_bloc.dart'; @@ -42,6 +43,7 @@ class MyApp extends StatelessWidget { BlocProvider(create: (context) => sl()), BlocProvider(create: (context) => sl()), BlocProvider(create: (context) => sl()), + BlocProvider(create: (context) => sl()..getMyProfile()), BlocProvider(create: (context) => sl()..loadFeedBlogPostList()), BlocProvider(create: (context) => sl()..loadRecipesList()), BlocProvider(create: (context) => sl()..loadDiets()), diff --git a/lib/widgets/custom_navigation_menu.dart b/lib/widgets/custom_navigation_menu.dart index 4bc712d8..c3222e62 100644 --- a/lib/widgets/custom_navigation_menu.dart +++ b/lib/widgets/custom_navigation_menu.dart @@ -24,7 +24,7 @@ class CustomNavigationMenu extends StatelessWidget { onTap: () => GoRouter.of(context).push('/recipe-select-screen'), ), MenuItem( - icon: ImageConstant.icnVeggies, + icon: 'assets/icons/diets.svg', text: 'Мои диеты', onTap: () => GoRouter.of(context).push('/diet-select-screen'), ), @@ -34,7 +34,7 @@ class CustomNavigationMenu extends StatelessWidget { onTap: () => GoRouter.of(context).push('/plan-select-screen'), ), MenuItem( - icon: ImageConstant.icnPlans, + icon: 'assets/icons/calendar.svg', text: 'Календарь', onTap: () => GoRouter.of(context).push('/calendar-screen'), ), diff --git a/pubspec.yaml b/pubspec.yaml index e5f44648..514eb4a2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,7 +42,7 @@ dependencies: badges: ^3.1.2 google_fonts: ^6.2.1 location: ^6.0.2 - syncfusion_flutter_charts: ^26.2.10 + syncfusion_flutter_charts: ^26.2.11 table_calendar: ^3.1.2 flutter_keyboard_visibility: ^6.0.0 share_plus: ^10.0.0 @@ -65,3 +65,4 @@ flutter: - assets/ - assets/images/ - assets/audio/ + - assets/icons/