diff --git a/lib/app_router.dart b/lib/app_router.dart index 7d6fe865..66bd328e 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -17,6 +17,7 @@ import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart'; import 'package:eatvio_app/features/notifications/presentation/pages/notifications_screen.dart'; import 'package:eatvio_app/features/plans/models/plan_pagination_model.dart'; import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart'; +import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart'; import 'package:eatvio_app/features/recipes/presentation/pages/about_recipes_screen.dart'; import 'package:eatvio_app/features/recipes/presentation/pages/recipe_create_screen.dart'; @@ -173,27 +174,37 @@ class AppRouter { GoRoute( path: 'general-info-screen', name: 'generalInfoScreen', - builder: (context, state) => GeneralInfoScreen(), + builder: (context, state) => GeneralInfoScreen( + profileDetailsModel: state.extra as ProfileDetailsModel, + ), ), GoRoute( path: 'other-info-screen', name: 'otherInfoScreen', - builder: (context, state) => OtherInfoScreen(), + builder: (context, state) => OtherInfoScreen( + profileDetailsModel: state.extra as ProfileDetailsModel, + ), ), GoRoute( path: 'physical-activity-screen', name: 'physicalActivityScreen', - builder: (context, state) => PhysicalActivityScreen(), + builder: (context, state) => PhysicalActivityScreen( + profileDetailsModel: state.extra as ProfileDetailsModel, + ), ), GoRoute( path: 'physical-form-screen', name: 'physicalFormScreen', - builder: (context, state) => PhysicalFormScreen(), + builder: (context, state) => PhysicalFormScreen( + profileDetailsModel: state.extra as ProfileDetailsModel, + ), ), GoRoute( path: 'pressure-screen', name: 'pressureScreen', - builder: (context, state) => PressureScreen(), + builder: (context, state) => PressureScreen( + profileDetailsModel: state.extra as ProfileDetailsModel, + ), ), ], ), diff --git a/lib/features/settings/models/profile_settings_model.dart b/lib/features/settings/models/profile_settings_model.dart new file mode 100644 index 00000000..ebd33e95 --- /dev/null +++ b/lib/features/settings/models/profile_settings_model.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'profile_settings_model.freezed.dart'; + +part 'profile_settings_model.g.dart'; + +@freezed +class ProfileSettingsModel with _$ProfileSettingsModel { + const factory ProfileSettingsModel({ + @JsonKey(name: "new_updates_notifications") required dynamic newUpdatesNotifications, + @JsonKey(name: "diary_reminder_notifications") required dynamic diaryReminderNotifications, + @JsonKey(name: "is_mood_fillable") required bool isMoodFillable, + @JsonKey(name: "is_bedtime_fillable") required bool isBedtimeFillable, + @JsonKey(name: "is_public_profile") required bool isPublicProfile, + }) = _ProfileSettingsModel; + + factory ProfileSettingsModel.fromJson(Map json) => _$ProfileSettingsModelFromJson(json); +} diff --git a/lib/features/settings/models/profile_settings_model.freezed.dart b/lib/features/settings/models/profile_settings_model.freezed.dart new file mode 100644 index 00000000..e6a0ff83 --- /dev/null +++ b/lib/features/settings/models/profile_settings_model.freezed.dart @@ -0,0 +1,287 @@ +// 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_settings_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'); + +ProfileSettingsModel _$ProfileSettingsModelFromJson(Map json) { + return _ProfileSettingsModel.fromJson(json); +} + +/// @nodoc +mixin _$ProfileSettingsModel { + @JsonKey(name: "new_updates_notifications") + dynamic get newUpdatesNotifications => throw _privateConstructorUsedError; + @JsonKey(name: "diary_reminder_notifications") + dynamic get diaryReminderNotifications => throw _privateConstructorUsedError; + @JsonKey(name: "is_mood_fillable") + bool get isMoodFillable => throw _privateConstructorUsedError; + @JsonKey(name: "is_bedtime_fillable") + bool get isBedtimeFillable => throw _privateConstructorUsedError; + @JsonKey(name: "is_public_profile") + bool get isPublicProfile => throw _privateConstructorUsedError; + + /// Serializes this ProfileSettingsModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProfileSettingsModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfileSettingsModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfileSettingsModelCopyWith<$Res> { + factory $ProfileSettingsModelCopyWith(ProfileSettingsModel value, + $Res Function(ProfileSettingsModel) then) = + _$ProfileSettingsModelCopyWithImpl<$Res, ProfileSettingsModel>; + @useResult + $Res call( + {@JsonKey(name: "new_updates_notifications") + dynamic newUpdatesNotifications, + @JsonKey(name: "diary_reminder_notifications") + dynamic diaryReminderNotifications, + @JsonKey(name: "is_mood_fillable") bool isMoodFillable, + @JsonKey(name: "is_bedtime_fillable") bool isBedtimeFillable, + @JsonKey(name: "is_public_profile") bool isPublicProfile}); +} + +/// @nodoc +class _$ProfileSettingsModelCopyWithImpl<$Res, + $Val extends ProfileSettingsModel> + implements $ProfileSettingsModelCopyWith<$Res> { + _$ProfileSettingsModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfileSettingsModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? newUpdatesNotifications = freezed, + Object? diaryReminderNotifications = freezed, + Object? isMoodFillable = null, + Object? isBedtimeFillable = null, + Object? isPublicProfile = null, + }) { + return _then(_value.copyWith( + newUpdatesNotifications: freezed == newUpdatesNotifications + ? _value.newUpdatesNotifications + : newUpdatesNotifications // ignore: cast_nullable_to_non_nullable + as dynamic, + diaryReminderNotifications: freezed == diaryReminderNotifications + ? _value.diaryReminderNotifications + : diaryReminderNotifications // ignore: cast_nullable_to_non_nullable + as dynamic, + isMoodFillable: null == isMoodFillable + ? _value.isMoodFillable + : isMoodFillable // ignore: cast_nullable_to_non_nullable + as bool, + isBedtimeFillable: null == isBedtimeFillable + ? _value.isBedtimeFillable + : isBedtimeFillable // ignore: cast_nullable_to_non_nullable + as bool, + isPublicProfile: null == isPublicProfile + ? _value.isPublicProfile + : isPublicProfile // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProfileSettingsModelImplCopyWith<$Res> + implements $ProfileSettingsModelCopyWith<$Res> { + factory _$$ProfileSettingsModelImplCopyWith(_$ProfileSettingsModelImpl value, + $Res Function(_$ProfileSettingsModelImpl) then) = + __$$ProfileSettingsModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {@JsonKey(name: "new_updates_notifications") + dynamic newUpdatesNotifications, + @JsonKey(name: "diary_reminder_notifications") + dynamic diaryReminderNotifications, + @JsonKey(name: "is_mood_fillable") bool isMoodFillable, + @JsonKey(name: "is_bedtime_fillable") bool isBedtimeFillable, + @JsonKey(name: "is_public_profile") bool isPublicProfile}); +} + +/// @nodoc +class __$$ProfileSettingsModelImplCopyWithImpl<$Res> + extends _$ProfileSettingsModelCopyWithImpl<$Res, _$ProfileSettingsModelImpl> + implements _$$ProfileSettingsModelImplCopyWith<$Res> { + __$$ProfileSettingsModelImplCopyWithImpl(_$ProfileSettingsModelImpl _value, + $Res Function(_$ProfileSettingsModelImpl) _then) + : super(_value, _then); + + /// Create a copy of ProfileSettingsModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? newUpdatesNotifications = freezed, + Object? diaryReminderNotifications = freezed, + Object? isMoodFillable = null, + Object? isBedtimeFillable = null, + Object? isPublicProfile = null, + }) { + return _then(_$ProfileSettingsModelImpl( + newUpdatesNotifications: freezed == newUpdatesNotifications + ? _value.newUpdatesNotifications + : newUpdatesNotifications // ignore: cast_nullable_to_non_nullable + as dynamic, + diaryReminderNotifications: freezed == diaryReminderNotifications + ? _value.diaryReminderNotifications + : diaryReminderNotifications // ignore: cast_nullable_to_non_nullable + as dynamic, + isMoodFillable: null == isMoodFillable + ? _value.isMoodFillable + : isMoodFillable // ignore: cast_nullable_to_non_nullable + as bool, + isBedtimeFillable: null == isBedtimeFillable + ? _value.isBedtimeFillable + : isBedtimeFillable // ignore: cast_nullable_to_non_nullable + as bool, + isPublicProfile: null == isPublicProfile + ? _value.isPublicProfile + : isPublicProfile // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProfileSettingsModelImpl implements _ProfileSettingsModel { + const _$ProfileSettingsModelImpl( + {@JsonKey(name: "new_updates_notifications") + required this.newUpdatesNotifications, + @JsonKey(name: "diary_reminder_notifications") + required this.diaryReminderNotifications, + @JsonKey(name: "is_mood_fillable") required this.isMoodFillable, + @JsonKey(name: "is_bedtime_fillable") required this.isBedtimeFillable, + @JsonKey(name: "is_public_profile") required this.isPublicProfile}); + + factory _$ProfileSettingsModelImpl.fromJson(Map json) => + _$$ProfileSettingsModelImplFromJson(json); + + @override + @JsonKey(name: "new_updates_notifications") + final dynamic newUpdatesNotifications; + @override + @JsonKey(name: "diary_reminder_notifications") + final dynamic diaryReminderNotifications; + @override + @JsonKey(name: "is_mood_fillable") + final bool isMoodFillable; + @override + @JsonKey(name: "is_bedtime_fillable") + final bool isBedtimeFillable; + @override + @JsonKey(name: "is_public_profile") + final bool isPublicProfile; + + @override + String toString() { + return 'ProfileSettingsModel(newUpdatesNotifications: $newUpdatesNotifications, diaryReminderNotifications: $diaryReminderNotifications, isMoodFillable: $isMoodFillable, isBedtimeFillable: $isBedtimeFillable, isPublicProfile: $isPublicProfile)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfileSettingsModelImpl && + const DeepCollectionEquality().equals( + other.newUpdatesNotifications, newUpdatesNotifications) && + const DeepCollectionEquality().equals( + other.diaryReminderNotifications, diaryReminderNotifications) && + (identical(other.isMoodFillable, isMoodFillable) || + other.isMoodFillable == isMoodFillable) && + (identical(other.isBedtimeFillable, isBedtimeFillable) || + other.isBedtimeFillable == isBedtimeFillable) && + (identical(other.isPublicProfile, isPublicProfile) || + other.isPublicProfile == isPublicProfile)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(newUpdatesNotifications), + const DeepCollectionEquality().hash(diaryReminderNotifications), + isMoodFillable, + isBedtimeFillable, + isPublicProfile); + + /// Create a copy of ProfileSettingsModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfileSettingsModelImplCopyWith<_$ProfileSettingsModelImpl> + get copyWith => + __$$ProfileSettingsModelImplCopyWithImpl<_$ProfileSettingsModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ProfileSettingsModelImplToJson( + this, + ); + } +} + +abstract class _ProfileSettingsModel implements ProfileSettingsModel { + const factory _ProfileSettingsModel( + {@JsonKey(name: "new_updates_notifications") + required final dynamic newUpdatesNotifications, + @JsonKey(name: "diary_reminder_notifications") + required final dynamic diaryReminderNotifications, + @JsonKey(name: "is_mood_fillable") required final bool isMoodFillable, + @JsonKey(name: "is_bedtime_fillable") + required final bool isBedtimeFillable, + @JsonKey(name: "is_public_profile") + required final bool isPublicProfile}) = _$ProfileSettingsModelImpl; + + factory _ProfileSettingsModel.fromJson(Map json) = + _$ProfileSettingsModelImpl.fromJson; + + @override + @JsonKey(name: "new_updates_notifications") + dynamic get newUpdatesNotifications; + @override + @JsonKey(name: "diary_reminder_notifications") + dynamic get diaryReminderNotifications; + @override + @JsonKey(name: "is_mood_fillable") + bool get isMoodFillable; + @override + @JsonKey(name: "is_bedtime_fillable") + bool get isBedtimeFillable; + @override + @JsonKey(name: "is_public_profile") + bool get isPublicProfile; + + /// Create a copy of ProfileSettingsModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfileSettingsModelImplCopyWith<_$ProfileSettingsModelImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/features/settings/models/profile_settings_model.g.dart b/lib/features/settings/models/profile_settings_model.g.dart new file mode 100644 index 00000000..d7a7e6eb --- /dev/null +++ b/lib/features/settings/models/profile_settings_model.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'profile_settings_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ProfileSettingsModelImpl _$$ProfileSettingsModelImplFromJson( + Map json) => + _$ProfileSettingsModelImpl( + newUpdatesNotifications: json['new_updates_notifications'], + diaryReminderNotifications: json['diary_reminder_notifications'], + isMoodFillable: json['is_mood_fillable'] as bool, + isBedtimeFillable: json['is_bedtime_fillable'] as bool, + isPublicProfile: json['is_public_profile'] as bool, + ); + +Map _$$ProfileSettingsModelImplToJson( + _$ProfileSettingsModelImpl instance) => + { + 'new_updates_notifications': instance.newUpdatesNotifications, + 'diary_reminder_notifications': instance.diaryReminderNotifications, + 'is_mood_fillable': instance.isMoodFillable, + 'is_bedtime_fillable': instance.isBedtimeFillable, + 'is_public_profile': instance.isPublicProfile, + }; diff --git a/lib/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart b/lib/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart index a5b998f2..0b734d25 100644 --- a/lib/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart +++ b/lib/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart @@ -1,6 +1,39 @@ +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; +import 'package:eatvio_app/features/settings/models/profile_settings_model.dart'; import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_state.dart'; +import 'package:eatvio_app/features/settings/repositories/service_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class SettingsCubit extends Cubit { - SettingsCubit() : super(SettingsState()); -} \ No newline at end of file +class SettingsCubit extends Cubit { + final ProfileRepository profileRepository; + final SettingsRepository settingsRepository; + + SettingsCubit({ + required this.profileRepository, + required this.settingsRepository, + }) : super(SettingsState.loading()); + + init() async { + try { + emit(SettingsState.loaded( + profileSettings: await settingsRepository.getProfileSettings(), + profileDetails: await profileRepository.getMyProfileDetails(), + )); + } catch (e) { + emit(SettingsState.error(errorMessage: e.toString())); + } + } + + updateProfileSettings({ + required ProfileSettingsModel profileSettingsModel + }) async { + try { + await settingsRepository.updateProfileSettings( + profileSettingsModel: profileSettingsModel, + ); + } catch (e) { + print(e); + rethrow; + } + } +} diff --git a/lib/features/settings/presentation/bloc/settings_cubit/settings_state.dart b/lib/features/settings/presentation/bloc/settings_cubit/settings_state.dart index 474295d0..41977b39 100644 --- a/lib/features/settings/presentation/bloc/settings_cubit/settings_state.dart +++ b/lib/features/settings/presentation/bloc/settings_cubit/settings_state.dart @@ -1,3 +1,5 @@ +import 'package:eatvio_app/features/profile/models/profile_model.dart'; +import 'package:eatvio_app/features/settings/models/profile_settings_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'settings_state.freezed.dart'; @@ -5,8 +7,14 @@ part 'settings_state.freezed.dart'; @freezed abstract class SettingsState with _$SettingsState { const factory SettingsState() = _SettingsState; + const factory SettingsState.loading() = SettingsStateLoading; - const factory SettingsState.loaded() = SettingsStateLoaded; + + const factory SettingsState.loaded({ + required ProfileSettingsModel profileSettings, + required ProfileDetailsModel profileDetails, + }) = SettingsStateLoaded; + const factory SettingsState.error({ required String errorMessage, }) = SettingsStateError; diff --git a/lib/features/settings/presentation/bloc/settings_cubit/settings_state.freezed.dart b/lib/features/settings/presentation/bloc/settings_cubit/settings_state.freezed.dart index af636b51..f19981b6 100644 --- a/lib/features/settings/presentation/bloc/settings_cubit/settings_state.freezed.dart +++ b/lib/features/settings/presentation/bloc/settings_cubit/settings_state.freezed.dart @@ -20,7 +20,9 @@ mixin _$SettingsState { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function() loaded, + required TResult Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails) + loaded, required TResult Function(String errorMessage) error, }) => throw _privateConstructorUsedError; @@ -28,7 +30,9 @@ mixin _$SettingsState { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function()? loaded, + TResult? Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails)? + loaded, TResult? Function(String errorMessage)? error, }) => throw _privateConstructorUsedError; @@ -36,7 +40,9 @@ mixin _$SettingsState { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function()? loaded, + TResult Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails)? + loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) => @@ -132,7 +138,9 @@ class _$SettingsStateImpl implements _SettingsState { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function() loaded, + required TResult Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails) + loaded, required TResult Function(String errorMessage) error, }) { return $default(); @@ -143,7 +151,9 @@ class _$SettingsStateImpl implements _SettingsState { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function()? loaded, + TResult? Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails)? + loaded, TResult? Function(String errorMessage)? error, }) { return $default?.call(); @@ -154,7 +164,9 @@ class _$SettingsStateImpl implements _SettingsState { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function()? loaded, + TResult Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails)? + loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { @@ -250,7 +262,9 @@ class _$SettingsStateLoadingImpl implements SettingsStateLoading { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function() loaded, + required TResult Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails) + loaded, required TResult Function(String errorMessage) error, }) { return loading(); @@ -261,7 +275,9 @@ class _$SettingsStateLoadingImpl implements SettingsStateLoading { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function()? loaded, + TResult? Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails)? + loaded, TResult? Function(String errorMessage)? error, }) { return loading?.call(); @@ -272,7 +288,9 @@ class _$SettingsStateLoadingImpl implements SettingsStateLoading { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function()? loaded, + TResult Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails)? + loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { @@ -329,6 +347,13 @@ abstract class _$$SettingsStateLoadedImplCopyWith<$Res> { factory _$$SettingsStateLoadedImplCopyWith(_$SettingsStateLoadedImpl value, $Res Function(_$SettingsStateLoadedImpl) then) = __$$SettingsStateLoadedImplCopyWithImpl<$Res>; + @useResult + $Res call( + {ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails}); + + $ProfileSettingsModelCopyWith<$Res> get profileSettings; + $ProfileDetailsModelCopyWith<$Res> get profileDetails; } /// @nodoc @@ -341,37 +366,95 @@ class __$$SettingsStateLoadedImplCopyWithImpl<$Res> /// Create a copy of SettingsState /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? profileSettings = null, + Object? profileDetails = null, + }) { + return _then(_$SettingsStateLoadedImpl( + profileSettings: null == profileSettings + ? _value.profileSettings + : profileSettings // ignore: cast_nullable_to_non_nullable + as ProfileSettingsModel, + profileDetails: null == profileDetails + ? _value.profileDetails + : profileDetails // ignore: cast_nullable_to_non_nullable + as ProfileDetailsModel, + )); + } + + /// Create a copy of SettingsState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ProfileSettingsModelCopyWith<$Res> get profileSettings { + return $ProfileSettingsModelCopyWith<$Res>(_value.profileSettings, (value) { + return _then(_value.copyWith(profileSettings: value)); + }); + } + + /// Create a copy of SettingsState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ProfileDetailsModelCopyWith<$Res> get profileDetails { + return $ProfileDetailsModelCopyWith<$Res>(_value.profileDetails, (value) { + return _then(_value.copyWith(profileDetails: value)); + }); + } } /// @nodoc class _$SettingsStateLoadedImpl implements SettingsStateLoaded { - const _$SettingsStateLoadedImpl(); + const _$SettingsStateLoadedImpl( + {required this.profileSettings, required this.profileDetails}); + + @override + final ProfileSettingsModel profileSettings; + @override + final ProfileDetailsModel profileDetails; @override String toString() { - return 'SettingsState.loaded()'; + return 'SettingsState.loaded(profileSettings: $profileSettings, profileDetails: $profileDetails)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$SettingsStateLoadedImpl); + other is _$SettingsStateLoadedImpl && + (identical(other.profileSettings, profileSettings) || + other.profileSettings == profileSettings) && + (identical(other.profileDetails, profileDetails) || + other.profileDetails == profileDetails)); } @override - int get hashCode => runtimeType.hashCode; + int get hashCode => Object.hash(runtimeType, profileSettings, profileDetails); + + /// Create a copy of SettingsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SettingsStateLoadedImplCopyWith<_$SettingsStateLoadedImpl> get copyWith => + __$$SettingsStateLoadedImplCopyWithImpl<_$SettingsStateLoadedImpl>( + this, _$identity); @override @optionalTypeArgs TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function() loaded, + required TResult Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails) + loaded, required TResult Function(String errorMessage) error, }) { - return loaded(); + return loaded(profileSettings, profileDetails); } @override @@ -379,10 +462,12 @@ class _$SettingsStateLoadedImpl implements SettingsStateLoaded { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function()? loaded, + TResult? Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails)? + loaded, TResult? Function(String errorMessage)? error, }) { - return loaded?.call(); + return loaded?.call(profileSettings, profileDetails); } @override @@ -390,12 +475,14 @@ class _$SettingsStateLoadedImpl implements SettingsStateLoaded { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function()? loaded, + TResult Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails)? + loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { if (loaded != null) { - return loaded(); + return loaded(profileSettings, profileDetails); } return orElse(); } @@ -439,7 +526,19 @@ class _$SettingsStateLoadedImpl implements SettingsStateLoaded { } abstract class SettingsStateLoaded implements SettingsState { - const factory SettingsStateLoaded() = _$SettingsStateLoadedImpl; + const factory SettingsStateLoaded( + {required final ProfileSettingsModel profileSettings, + required final ProfileDetailsModel profileDetails}) = + _$SettingsStateLoadedImpl; + + ProfileSettingsModel get profileSettings; + ProfileDetailsModel get profileDetails; + + /// Create a copy of SettingsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SettingsStateLoadedImplCopyWith<_$SettingsStateLoadedImpl> get copyWith => + throw _privateConstructorUsedError; } /// @nodoc @@ -514,7 +613,9 @@ class _$SettingsStateErrorImpl implements SettingsStateError { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function() loaded, + required TResult Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails) + loaded, required TResult Function(String errorMessage) error, }) { return error(errorMessage); @@ -525,7 +626,9 @@ class _$SettingsStateErrorImpl implements SettingsStateError { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function()? loaded, + TResult? Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails)? + loaded, TResult? Function(String errorMessage)? error, }) { return error?.call(errorMessage); @@ -536,7 +639,9 @@ class _$SettingsStateErrorImpl implements SettingsStateError { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function()? loaded, + TResult Function(ProfileSettingsModel profileSettings, + ProfileDetailsModel profileDetails)? + loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { diff --git a/lib/features/settings/presentation/pages/general_info_screen.dart b/lib/features/settings/presentation/pages/general_info_screen.dart index 6e9cb67f..dbe7d57e 100644 --- a/lib/features/settings/presentation/pages/general_info_screen.dart +++ b/lib/features/settings/presentation/pages/general_info_screen.dart @@ -1,4 +1,5 @@ import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; @@ -7,7 +8,12 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class GeneralInfoScreen extends StatefulWidget { - const GeneralInfoScreen({super.key}); + final ProfileDetailsModel profileDetailsModel; + + const GeneralInfoScreen({ + super.key, + required this.profileDetailsModel, + }); @override State createState() => _GeneralInfoScreenState(); @@ -22,6 +28,9 @@ class _GeneralInfoScreenState extends State { void initState() { super.initState(); selectedDate = DateTime.now(); + firstNameController.text = widget.profileDetailsModel.firstName; + lastNameController.text = widget.profileDetailsModel.lastName; + selectedDate = DateTime.parse(widget.profileDetailsModel.birthdate); } @override @@ -35,7 +44,7 @@ class _GeneralInfoScreenState extends State { bottomSheet: _saveButton(), body: SingleChildScrollView( physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.only(top: 16, bottom: 80, left: 8, right: 8), + padding: EdgeInsets.all(16) + EdgeInsets.only(bottom: 64), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -145,7 +154,7 @@ class _GeneralInfoScreenState extends State { borderRadius: BorderRadius.circular(20), child: Ink( height: 52, - width: MediaQuery.of(context).size.width - 16, + width: MediaQuery.of(context).size.width - 32, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: appTheme.blueA400, diff --git a/lib/features/settings/presentation/pages/other_info_screen.dart b/lib/features/settings/presentation/pages/other_info_screen.dart index 99be2543..1232db7e 100644 --- a/lib/features/settings/presentation/pages/other_info_screen.dart +++ b/lib/features/settings/presentation/pages/other_info_screen.dart @@ -1,7 +1,17 @@ +import 'package:eatvio_app/features/profile/models/profile_model.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:eatvio_app/theme/theme_helper.dart'; +import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; class OtherInfoScreen extends StatefulWidget { - const OtherInfoScreen({super.key}); + final ProfileDetailsModel profileDetailsModel; + + const OtherInfoScreen({ + super.key, + required this.profileDetailsModel, + }); @override State createState() => _OtherInfoScreenState(); @@ -10,6 +20,107 @@ class OtherInfoScreen extends StatefulWidget { class _OtherInfoScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold(); + final s = S.of(context); + + return Scaffold( + appBar: AppBar( + title: Text('Прочая информаиця'), + ), + bottomSheet: _saveButton(), + body: SingleChildScrollView( + padding: EdgeInsets.all(16) + EdgeInsets.only(bottom: 64), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(s.allergies_presence), + SizedBox(height: 4), + CustomTextFormField( + hintText: 'Например: орехи, мёд и т.д.', + autofocus: true, + minLines: 2, + maxLines: 8, + onChanged: (newValue) {}, + ), + SizedBox(height: 16), + Text(s.do_you_take_pills), + SizedBox(height: 4), + CustomTextFormField( + hintText: s.enter_information, + autofocus: true, + minLines: 2, + maxLines: 8, + onChanged: (newValue) {}, + ), + SizedBox(height: 16), + Text(s.your_favorite_sports), + SizedBox(height: 4), + CustomTextFormField( + hintText: + "Я занимаюсь плаванием , почти каждый вечер прогулки на свежем воздухе, пару раз в месяц играю в волейбол. Стараюсь не сидеть на месте!", + autofocus: true, + maxLines: 8, + onChanged: (newValue) {}, + ), + SizedBox(height: 16), + Text(s.favorite_foods), + SizedBox(height: 4), + CustomTextFormField( + hintText: s.enter_information, + maxLines: 7, + minLines: 2, + onChanged: (newValue) {}, + ), + SizedBox(height: 16), + Text(s.unfavorite_foods), + SizedBox(height: 4), + CustomTextFormField( + hintText: s.enter_information, + maxLines: 7, + minLines: 2, + onChanged: (newValue) {}, + ), + SizedBox(height: 16), + Text( + 'Дополнительная информация', + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.start, + ), + SizedBox(height: 4), + CustomTextFormField( + hintText: "У меня крепкое здоровье, но последнее время часто утомляюсь, однако, это мне не мешает радоваться жизни", + maxLines: 8, + onChanged: (newValue) {}, + ), + ], + ), + ), + ); } -} \ No newline at end of file + + _saveButton() { + return InkWell( + onTap: () => GoRouter.of(context).pop(), + borderRadius: BorderRadius.circular(20), + child: Ink( + height: 52, + width: MediaQuery.of(context).size.width - 16, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: appTheme.blueA400, + ), + child: Center( + child: Text( + 'Сохранить', + style: TextStyle( + color: Colors.white, + height: 1, + fontWeight: FontWeight.w600, + fontSize: 18, + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/settings/presentation/pages/physical_activity_screen.dart b/lib/features/settings/presentation/pages/physical_activity_screen.dart index 57496c18..55878e4a 100644 --- a/lib/features/settings/presentation/pages/physical_activity_screen.dart +++ b/lib/features/settings/presentation/pages/physical_activity_screen.dart @@ -1,7 +1,15 @@ +import 'package:eatvio_app/features/profile/models/profile_model.dart'; +import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; class PhysicalActivityScreen extends StatefulWidget { - const PhysicalActivityScreen({super.key}); + final ProfileDetailsModel profileDetailsModel; + + const PhysicalActivityScreen({ + super.key, + required this.profileDetailsModel, + }); @override State createState() => _PhysicalActivityScreenState(); @@ -18,10 +26,14 @@ class _PhysicalActivityScreenState extends State { appBar: AppBar( title: Text('Физическая нагрузка'), ), + bottomSheet: _saveButton(), body: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.only(top: 16, bottom: 80, left: 8, right: 16), child: Column( children: [ RadioListTile( + contentPadding: EdgeInsets.zero, title: const Text('1,2 — минимальный (сидячая работа, отсутствие физических нагрузок);'), value: PhysicalActivity.minimum, groupValue: _physicalActivity, @@ -32,6 +44,7 @@ class _PhysicalActivityScreenState extends State { }, ), RadioListTile( + contentPadding: EdgeInsets.zero, title: const Text('1,375 — низкий (тренировки не менее 20 мин 1–3 раза в неделю);'), value: PhysicalActivity.low, groupValue: _physicalActivity, @@ -42,6 +55,7 @@ class _PhysicalActivityScreenState extends State { }, ), RadioListTile( + contentPadding: EdgeInsets.zero, title: const Text('1,55 — умеренный (тренировки 30–60 мин 3–4 раза в неделю);'), value: PhysicalActivity.medium, groupValue: _physicalActivity, @@ -52,6 +66,7 @@ class _PhysicalActivityScreenState extends State { }, ), RadioListTile( + contentPadding: EdgeInsets.zero, title: const Text('1,7 — высокий (тренировки 30–60 мин 5–7 раза в неделю; тяжёлая физическая работа);'), value: PhysicalActivity.high, groupValue: _physicalActivity, @@ -62,6 +77,7 @@ class _PhysicalActivityScreenState extends State { }, ), RadioListTile( + contentPadding: EdgeInsets.zero, title: const Text('1,9 — экстремальный (несколько интенсивных тренировок в день'), value: PhysicalActivity.extreme, groupValue: _physicalActivity, @@ -72,6 +88,7 @@ class _PhysicalActivityScreenState extends State { }, ), RadioListTile( + contentPadding: EdgeInsets.zero, title: const Text('1,9 6–7 раз в неделю; очень трудоёмкая работа).'), value: PhysicalActivity.hardWork, groupValue: _physicalActivity, @@ -86,4 +103,30 @@ class _PhysicalActivityScreenState extends State { ), ); } + + _saveButton() { + return InkWell( + onTap: () => GoRouter.of(context).pop(), + borderRadius: BorderRadius.circular(20), + child: Ink( + height: 52, + width: MediaQuery.of(context).size.width - 32, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: appTheme.blueA400, + ), + child: Center( + child: Text( + 'Сохранить', + style: TextStyle( + color: Colors.white, + height: 1, + fontWeight: FontWeight.w600, + fontSize: 18, + ), + ), + ), + ), + ); + } } diff --git a/lib/features/settings/presentation/pages/physical_form_screen.dart b/lib/features/settings/presentation/pages/physical_form_screen.dart index 8817d0be..3b5f7097 100644 --- a/lib/features/settings/presentation/pages/physical_form_screen.dart +++ b/lib/features/settings/presentation/pages/physical_form_screen.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; @@ -6,7 +7,12 @@ import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; class PhysicalFormScreen extends StatefulWidget { - const PhysicalFormScreen({super.key}); + final ProfileDetailsModel profileDetailsModel; + + const PhysicalFormScreen({ + super.key, + required this.profileDetailsModel, + }); @override State createState() => _PhysicalFormScreenState(); @@ -29,7 +35,7 @@ class _PhysicalFormScreenState extends State { bottomSheet: _saveButton(), body: SingleChildScrollView( physics: const BouncingScrollPhysics(), - padding: EdgeInsets.symmetric(vertical: 16, horizontal: 8), + padding: EdgeInsets.all(16) + EdgeInsets.only(bottom: 64), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -69,7 +75,7 @@ class _PhysicalFormScreenState extends State { borderRadius: BorderRadius.circular(20), child: Ink( height: 52, - width: MediaQuery.of(context).size.width - 16, + width: MediaQuery.of(context).size.width - 32, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: appTheme.blueA400, diff --git a/lib/features/settings/presentation/pages/pressure_screen.dart b/lib/features/settings/presentation/pages/pressure_screen.dart index c92b525a..5e8a9d51 100644 --- a/lib/features/settings/presentation/pages/pressure_screen.dart +++ b/lib/features/settings/presentation/pages/pressure_screen.dart @@ -1,15 +1,20 @@ -import 'package:eatvio_app/core/utils/image_constant.dart'; +import 'package:eatvio_app/data/models/selectionPopupModel/selection_popup_model.dart'; +import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:eatvio_app/widgets/custom_drop_down.dart'; -import 'package:eatvio_app/widgets/custom_image_view.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; class PressureScreen extends StatefulWidget { - const PressureScreen({super.key}); + final ProfileDetailsModel profileDetailsModel; + + const PressureScreen({ + super.key, + required this.profileDetailsModel, + }); @override State createState() => _PressureScreenState(); @@ -20,6 +25,16 @@ class _PressureScreenState extends State { final chestCircumferenceController = TextEditingController(); final waistCircumferenceController = TextEditingController(); final hipCircumferenceController = TextEditingController(); + final List selectionPopupList = [ + SelectionPopupModel( + id: 1, + title: "Да", + ), + SelectionPopupModel( + id: 0, + title: "Нет", + ), + ]; @override Widget build(BuildContext context) { @@ -27,12 +42,12 @@ class _PressureScreenState extends State { return Scaffold( appBar: AppBar( - title: Text('Физическая форма'), + title: Text('Давление'), ), bottomSheet: _saveButton(), body: SingleChildScrollView( physics: const BouncingScrollPhysics(), - padding: EdgeInsets.symmetric(vertical: 16, horizontal: 8), + padding: EdgeInsets.all(16) + EdgeInsets.only(bottom: 64), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -55,17 +70,8 @@ class _PressureScreenState extends State { Text(s.do_you_have_hypotension_tendency), SizedBox(height: 4), CustomDropDown( - icon: Container( - margin: EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - ), - child: CustomImageView( - imagePath: ImageConstant.imgFavorite, - height: 16, - width: 16, - ), - ), + items: selectionPopupList, + value: selectionPopupList[0], hintText: "Не выбрано", onChanged: (newPopup) { setState(() {}); @@ -75,17 +81,8 @@ class _PressureScreenState extends State { Text(s.do_you_have_hypertension_tendency), SizedBox(height: 4), CustomDropDown( - icon: Container( - margin: EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - ), - child: CustomImageView( - imagePath: ImageConstant.imgFavorite, - height: 16, - width: 16, - ), - ), + items: selectionPopupList, + value: selectionPopupList[0], hintText: "Не выбрано", onChanged: (newPopup) { setState(() {}); @@ -103,7 +100,7 @@ class _PressureScreenState extends State { borderRadius: BorderRadius.circular(20), child: Ink( height: 52, - width: MediaQuery.of(context).size.width - 16, + width: MediaQuery.of(context).size.width - 32, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: appTheme.blueA400, diff --git a/lib/features/settings/presentation/pages/settings_screen.dart b/lib/features/settings/presentation/pages/settings_screen.dart index 79b9e291..76456714 100644 --- a/lib/features/settings/presentation/pages/settings_screen.dart +++ b/lib/features/settings/presentation/pages/settings_screen.dart @@ -1,7 +1,11 @@ import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart'; +import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_state.dart'; +import 'package:eatvio_app/locator_service.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -13,32 +17,59 @@ class SettingsScreen extends StatefulWidget { } class _SettingsScreenState extends State { - bool updatesNotificationsSwitchState = true; - bool fillDiaryNotificationsSwitchState = true; + final SettingsCubit settingsCubit = sl()..init(); + late bool updatesNotificationsSwitchState; + late bool fillDiaryNotificationsSwitchState; @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Настройки'), - ), - body: SingleChildScrollView( - padding: EdgeInsets.symmetric(vertical: 16), - physics: const BouncingScrollPhysics(), - child: Column( - children: [ - _additionalInfo(), - const SizedBox(height: 12), - _notifications(), - const SizedBox(height: 12), - _aboutEatvio(), - const SizedBox(height: 12), - _becomeConsultantButton(), - const SizedBox(height: 8), - _deleteAccountButton(), - ], - ), - ), + return BlocConsumer( + bloc: settingsCubit, + listener: (BuildContext context, SettingsState state) { + if (state is SettingsStateLoaded) { + updatesNotificationsSwitchState = state.profileSettings.newUpdatesNotifications == 1 ? true : false; + fillDiaryNotificationsSwitchState = state.profileSettings.diaryReminderNotifications == 1 ? true : false; + } + }, + builder: (BuildContext context, SettingsState state) { + if (state is SettingsStateLoaded) { + return Scaffold( + appBar: AppBar( + title: Text('Настройки'), + ), + body: SingleChildScrollView( + padding: EdgeInsets.symmetric(vertical: 16), + physics: const BouncingScrollPhysics(), + child: Column( + children: [ + _additionalInfo(), + const SizedBox(height: 12), + _notifications(), + const SizedBox(height: 12), + _aboutEatvio(), + const SizedBox(height: 12), + _becomeConsultantButton(), + const SizedBox(height: 8), + _deleteAccountButton(), + ], + ), + ), + ); + } + if (state is SettingsStateError) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(16), + child: Center(child: Text(state.errorMessage)), + ), + ); + } + return Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + }, ); } @@ -59,27 +90,42 @@ class _SettingsScreenState extends State { ), ), _tile( - onTap: () => GoRouter.of(context).pushNamed('generalInfoScreen'), + onTap: () => GoRouter.of(context).pushNamed( + 'generalInfoScreen', + extra: (settingsCubit.state as SettingsStateLoaded).profileDetails, + ), title: 'Общая информация', ), _divider(context), _tile( - onTap: () => GoRouter.of(context).pushNamed('physicalFormScreen'), + onTap: () => GoRouter.of(context).pushNamed( + 'physicalFormScreen', + extra: (settingsCubit.state as SettingsStateLoaded).profileDetails, + ), title: 'Физическая форма', ), _divider(context), _tile( - onTap: () => GoRouter.of(context).pushNamed('pressureScreen'), + onTap: () => GoRouter.of(context).pushNamed( + 'pressureScreen', + extra: (settingsCubit.state as SettingsStateLoaded).profileDetails, + ), title: 'Давление', ), _divider(context), _tile( - onTap: () => GoRouter.of(context).pushNamed('otherInfoScreen'), + onTap: () => GoRouter.of(context).pushNamed( + 'otherInfoScreen', + extra: (settingsCubit.state as SettingsStateLoaded).profileDetails, + ), title: 'Прочая информация', ), _divider(context), _tile( - onTap: () => GoRouter.of(context).pushNamed('physicalActivityScreen'), + onTap: () => GoRouter.of(context).pushNamed( + 'physicalActivityScreen', + extra: (settingsCubit.state as SettingsStateLoaded).profileDetails, + ), title: 'Физическая нагрузка', ), ], @@ -106,6 +152,11 @@ class _SettingsScreenState extends State { setState(() { updatesNotificationsSwitchState = !updatesNotificationsSwitchState; }); + settingsCubit.updateProfileSettings( + profileSettingsModel: (settingsCubit.state as SettingsStateLoaded).profileSettings.copyWith( + newUpdatesNotifications: updatesNotificationsSwitchState, + ), + ); }, title: 'Уведомление о новых обновлениях', trailing: CupertinoSwitch( @@ -115,6 +166,11 @@ class _SettingsScreenState extends State { setState(() { updatesNotificationsSwitchState = newValue; }); + settingsCubit.updateProfileSettings( + profileSettingsModel: (settingsCubit.state as SettingsStateLoaded).profileSettings.copyWith( + newUpdatesNotifications: updatesNotificationsSwitchState, + ), + ); }, ), ), @@ -124,6 +180,11 @@ class _SettingsScreenState extends State { setState(() { fillDiaryNotificationsSwitchState = !fillDiaryNotificationsSwitchState; }); + settingsCubit.updateProfileSettings( + profileSettingsModel: (settingsCubit.state as SettingsStateLoaded).profileSettings.copyWith( + diaryReminderNotifications: fillDiaryNotificationsSwitchState, + ), + ); }, title: 'Напоминание о заполнении дневника', trailing: CupertinoSwitch( @@ -133,6 +194,11 @@ class _SettingsScreenState extends State { setState(() { fillDiaryNotificationsSwitchState = newValue; }); + settingsCubit.updateProfileSettings( + profileSettingsModel: (settingsCubit.state as SettingsStateLoaded).profileSettings.copyWith( + diaryReminderNotifications: fillDiaryNotificationsSwitchState, + ), + ); }, ), ), diff --git a/lib/features/settings/repositories/service_repository.dart b/lib/features/settings/repositories/service_repository.dart new file mode 100644 index 00000000..fe7d6492 --- /dev/null +++ b/lib/features/settings/repositories/service_repository.dart @@ -0,0 +1,39 @@ +import 'package:eatvio_app/features/profile/models/profile_model.dart'; +import 'package:eatvio_app/features/settings/models/profile_settings_model.dart'; +import 'package:eatvio_app/features/settings/services/settings_service.dart'; + +class SettingsRepository { + final SettingsService settingsService; + + const SettingsRepository({required this.settingsService}); + + Future getProfileSettings() async { + try { + return await settingsService.getProfileSettings(); + } catch (e) { + rethrow; + } + } + + Future updateProfileSettings({ + required ProfileSettingsModel profileSettingsModel, + }) async { + try { + return await settingsService.updateProfileSettings( + data: profileSettingsModel.toJson(), + ); + } catch (e) { + rethrow; + } + } + + Future updateProfileDetails({ + required ProfileDetailsModel profileDetailsModel, + }) async { + try { + return await settingsService.updateProfileDetails(profileDetailsModel: profileDetailsModel); + } catch (e) { + rethrow; + } + } +} diff --git a/lib/features/settings/services/settings_service.dart b/lib/features/settings/services/settings_service.dart new file mode 100644 index 00000000..8af3badc --- /dev/null +++ b/lib/features/settings/services/settings_service.dart @@ -0,0 +1,24 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/profile/models/profile_model.dart'; +import 'package:eatvio_app/features/settings/models/profile_settings_model.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'settings_service.g.dart'; + +@RestApi() +abstract class SettingsService { + factory SettingsService(Dio dio, {String baseUrl}) = _SettingsService; + + @GET('/api/profile/settings') + Future getProfileSettings(); + + @POST('/api/profile/settings/update') + Future updateProfileSettings({ + @Body() required Map data, + }); + + @POST('/api/profile/update') + Future updateProfileDetails({ + @MultiPart() required ProfileDetailsModel profileDetailsModel, + }); +} diff --git a/lib/features/settings/services/settings_service.g.dart b/lib/features/settings/services/settings_service.g.dart new file mode 100644 index 00000000..8fc04b6a --- /dev/null +++ b/lib/features/settings/services/settings_service.g.dart @@ -0,0 +1,155 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'settings_service.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element + +class _SettingsService implements SettingsService { + _SettingsService( + this._dio, { + this.baseUrl, + this.errorLogger, + }); + + final Dio _dio; + + String? baseUrl; + + final ParseErrorLogger? errorLogger; + + @override + Future getProfileSettings() async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/profile/settings', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late ProfileSettingsModel _value; + try { + _value = ProfileSettingsModel.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + @override + Future updateProfileSettings( + {required Map data}) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {}; + _data.addAll(data); + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/profile/settings/update', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late ProfileSettingsModel _value; + try { + _value = ProfileSettingsModel.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + @override + Future updateProfileDetails( + {required ProfileDetailsModel profileDetailsModel}) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/profile/update', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late ProfileDetailsModel _value; + try { + _value = ProfileDetailsModel.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls( + String dioBaseUrl, + String? baseUrl, + ) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} diff --git a/lib/locator_service.dart b/lib/locator_service.dart index 37c53064..e51ccc7c 100644 --- a/lib/locator_service.dart +++ b/lib/locator_service.dart @@ -32,6 +32,9 @@ import 'package:eatvio_app/features/recipes/services/recipe_service.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart'; import 'package:eatvio_app/features/registration/repositories/registration_repository.dart'; import 'package:eatvio_app/features/registration/services/registration_service.dart'; +import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart'; +import 'package:eatvio_app/features/settings/repositories/service_repository.dart'; +import 'package:eatvio_app/features/settings/services/settings_service.dart'; import 'package:get_it/get_it.dart'; final sl = GetIt.instance; @@ -56,6 +59,7 @@ Future init() async { sl.registerFactory(() => CommentsCubit(feedRepository: sl())); sl.registerFactory(() => ChatSelectCubit(chatRepository: sl())); sl.registerFactory(() => PlansCubit(plansRepository: sl())); + sl.registerFactory(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl())); // Repository sl.registerLazySingleton(() => ChatRepository(chatService: sl())); @@ -66,6 +70,7 @@ Future init() async { sl.registerLazySingleton(() => ProfileRepository(profileService: sl())); sl.registerLazySingleton(() => RecipeRepository(recipeService: sl())); sl.registerLazySingleton(() => PlansRepository(plansService: sl())); + sl.registerLazySingleton(() => SettingsRepository(settingsService: sl())); // Services sl.registerLazySingleton(() => ChatService(sl())); @@ -76,6 +81,7 @@ Future init() async { sl.registerLazySingleton(() => ProfileService(sl())); sl.registerLazySingleton(() => RecipeService(sl())); sl.registerLazySingleton(() => PlansService(sl())); + sl.registerLazySingleton(() => SettingsService(sl())); // External sl.registerLazySingleton( diff --git a/lib/widgets/custom_drop_down.dart b/lib/widgets/custom_drop_down.dart index 6843b7ba..a68a696f 100644 --- a/lib/widgets/custom_drop_down.dart +++ b/lib/widgets/custom_drop_down.dart @@ -62,11 +62,12 @@ class CustomDropDown extends StatelessWidget { icon: Icon(Icons.keyboard_arrow_down_rounded), autofocus: autofocus!, value: value, + isDense: true, isExpanded: true, style: textStyle ?? TextStyle( fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w400, ), items: items?.map((SelectionPopupModel item) { return DropdownMenuItem( @@ -76,7 +77,7 @@ class CustomDropDown extends StatelessWidget { style: hintStyle ?? TextStyle( fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w400, color: Colors.black, ), ), @@ -98,29 +99,29 @@ class CustomDropDown extends StatelessWidget { hintStyle: hintStyle ?? TextStyle( fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w400, color: Colors.grey.shade400, height: 1, ), prefixIcon: prefix, prefixIconConstraints: prefixConstraints, suffixIcon: suffix, - suffixIconConstraints: suffixConstraints, isDense: true, + suffixIconConstraints: suffixConstraints, contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 12), - fillColor: fillColor ?? Theme.of(context).colorScheme.onPrimary, + fillColor: Color(0xFFF6F6F6), filled: filled, border: OutlineInputBorder( borderRadius: const BorderRadius.all(Radius.circular(20)), - borderSide: BorderSide(color: Colors.grey, width: 0.2), + borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: const BorderRadius.all(Radius.circular(20)), - borderSide: BorderSide(color: Colors.grey, width: 0.2), + borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: const BorderRadius.all(Radius.circular(20)), - borderSide: BorderSide(color: Colors.grey, width: 0.2), + borderSide: BorderSide.none, ), ); }