Skip to content

Commit

Permalink
Merge pull request #745 from givtnl/feature/kids-838-implement-edit-a…
Browse files Browse the repository at this point in the history
…llowance-screen

Feature/kids 838 implement edit allowance screen
  • Loading branch information
Daniela510 authored May 17, 2024
2 parents b206690 + 626d7fe commit 6077f2d
Show file tree
Hide file tree
Showing 20 changed files with 489 additions and 105 deletions.
8 changes: 3 additions & 5 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -672,11 +672,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = YDSA4AY496;
DEVELOPMENT_TEAM = YDSA4AY496;
ENABLE_BITCODE = NO;
FLAVOR_APP_NAME = "[DEV] Givt";
INFOPLIST_FILE = Runner/Info.plist;
Expand All @@ -687,7 +686,6 @@
PRODUCT_BUNDLE_IDENTIFIER = net.givtapp.ios.test;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore net.givtapp.ios.test 1707347480";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
Expand Down
2 changes: 1 addition & 1 deletion lib/app/injection/injection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import 'package:givt_app/features/children/add_member/repository/add_member_repo
import 'package:givt_app/features/children/avatars/repositories/avatars_repository.dart';
import 'package:givt_app/features/children/cached_members/repositories/cached_members_repository.dart';
import 'package:givt_app/features/children/details/repositories/child_details_repository.dart';
import 'package:givt_app/features/children/edit_child/repositories/create_child_repository.dart';
import 'package:givt_app/features/children/edit_child/repositories/edit_child_repository.dart';
import 'package:givt_app/features/children/edit_profile/repositories/edit_profile_repository.dart';
import 'package:givt_app/features/children/family_goal/repositories/create_family_goal_repository.dart';
import 'package:givt_app/features/children/family_history/repository/family_history_repository.dart';
Expand Down
1 change: 1 addition & 0 deletions lib/app/routes/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ class AppRouter {
),
BlocProvider(
create: (_) => ChildDetailsCubit(
getIt(),
getIt(),
childProfile,
)..fetchChildDetails(),
Expand Down
1 change: 1 addition & 0 deletions lib/core/enums/amplitude_events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum AmplitudeEvents {
'child_details_edit_card_clicked',
),
childEditSaveClicked('child_edit_save_clicked'),
childEditMonthlyAllowanceSaveClicked('child_edit_monthly_allowance_save_clicked'),
childEditCancelClicked('child_edit_cancel_clicked'),
childProfileClicked('child_profile_clicked'),
adultProfileTileClicked('adult_profile_tile_clicked'),
Expand Down
21 changes: 21 additions & 0 deletions lib/core/network/api_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,27 @@ class APIService {
return response.statusCode == 200;
}

Future<bool> editChildAllowance(String childGUID, int allowance) async {
final url =
Uri.https(apiURL, '/givtservice/v1/profiles/$childGUID/allowance');

final response = await client.put(
url,
body: jsonEncode({'amount': allowance}),
headers: {
'Content-Type': 'application/json',
},
);

if (response.statusCode >= 300) {
throw GivtServerFailure(
statusCode: response.statusCode,
body: jsonDecode(response.body) as Map<String, dynamic>,
);
}
return response.statusCode == 200;
}

Future<List<dynamic>> fetchProfiles() async {
final url = Uri.https(
apiURL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class AllowanceCounter extends StatefulWidget {
super.key,
});

final String currency;
final String? currency;
final int? initialAllowance;
final void Function(int allowance)? onAllowanceChanged;

Expand Down
75 changes: 68 additions & 7 deletions lib/features/children/details/cubit/child_details_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,36 +1,97 @@
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:givt_app/core/enums/amplitude_events.dart';
import 'package:givt_app/core/logging/logging.dart';
import 'package:givt_app/features/children/details/models/profile_ext.dart';
import 'package:givt_app/features/children/details/repositories/child_details_repository.dart';
import 'package:givt_app/features/children/edit_child/repositories/edit_child_repository.dart';
import 'package:givt_app/features/children/overview/models/profile.dart';
import 'package:givt_app/utils/analytics_helper.dart';

part 'child_details_state.dart';

class ChildDetailsCubit extends Cubit<ChildDetailsState> {
ChildDetailsCubit(
this._childDetailsRepository,
this._editChildRepository,
this._profile,
) : super(const ChildDetailsInitialState());

final ChildDetailsRepository _childDetailsRepository;
final EditChildRepository _editChildRepository;
final Profile _profile;
ProfileExt? _profileExt;

Future<void> fetchChildDetails() async {
emit(const ChildDetailsFetchingState());
_emitLoading();
try {
final response = await _childDetailsRepository.fetchChildDetails(
_profileExt = await _childDetailsRepository.fetchChildDetails(
_profile,
);
emit(
ChildDetailsFetchedState(
profileDetails: response,
),
);
_emitData();
} catch (error, stackTrace) {
await LoggingInfo.instance
.error(error.toString(), methodName: stackTrace.toString());
emit(ChildDetailsErrorState(errorMessage: error.toString()));
}
}

void _emitLoading() {
emit(const ChildDetailsFetchingState());
}

void _emitData() {
emit(
ChildDetailsFetchedState(
profileDetails: _profileExt!,
),
);
}

Future<void> updateAllowance(int allowance) async {
await _logConfirmUpdateAllowanceEvent(allowance);

try {
_emitLoading();
final isSuccess = await _editChildRepository.editChildAllowance(
_profile.id,
allowance,
);
if (isSuccess) {
_emitData();
emit(ChildEditGivingAllowanceSuccessState(allowance: allowance));
//retrieve updated profile after changing the allowance
unawaited(fetchChildDetails());
} else {
emit(
const ChildDetailsErrorState(
errorMessage: 'Failed to update allowance',
),
);
}
} catch (e, s) {
_emitData();
await _handleEditAllowanceApiError(e, s);
}
}

Future<void> _handleEditAllowanceApiError(Object e, StackTrace s) async {
await LoggingInfo.instance.error(e.toString(), methodName: s.toString());
emit(
ChildDetailsErrorState(errorMessage: e.toString()),
);
}

Future<void> _logConfirmUpdateAllowanceEvent(int allowance) async {
await AnalyticsHelper.logEvent(
eventName: AmplitudeEvents.childEditMonthlyAllowanceSaveClicked,
eventProperties: {
'child_name': _profile.firstName,
'new_giving_allowance': allowance,
'old_giving_allowance': _profileExt?.givingAllowance.amount,
},
);
}
}
17 changes: 17 additions & 0 deletions lib/features/children/details/cubit/child_details_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ final class ChildDetailsErrorState extends ChildDetailsState {
});

final String errorMessage;

@override
List<Object> get props => [errorMessage];
}

final class ChildDetailsFetchedState extends ChildDetailsState {
Expand All @@ -29,4 +32,18 @@ final class ChildDetailsFetchedState extends ChildDetailsState {
});

final ProfileExt profileDetails;

@override
List<Object> get props => [profileDetails];
}

final class ChildEditGivingAllowanceSuccessState extends ChildDetailsState {
const ChildEditGivingAllowanceSuccessState({
required this.allowance,
});

final int allowance;

@override
List<Object> get props => [allowance];
}
54 changes: 46 additions & 8 deletions lib/features/children/details/pages/child_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import 'package:givt_app/features/children/details/cubit/child_details_cubit.dar
import 'package:givt_app/features/children/details/widgets/child_details_item.dart';
import 'package:givt_app/features/children/details/widgets/child_giving_allowance_card.dart';
import 'package:givt_app/features/children/overview/cubit/family_overview_cubit.dart';
import 'package:givt_app/features/children/overview/pages/edit_allowance_page.dart';
import 'package:givt_app/features/children/overview/pages/edit_allowance_success_page.dart';
import 'package:givt_app/features/children/overview/pages/models/edit_allowance_success_uimodel.dart';
import 'package:givt_app/l10n/l10n.dart';
import 'package:givt_app/shared/widgets/extensions/route_extensions.dart';
import 'package:givt_app/shared/widgets/extensions/string_extensions.dart';
import 'package:givt_app/utils/utils.dart';
import 'package:go_router/go_router.dart';

Expand All @@ -28,16 +33,29 @@ class ChildDetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocConsumer<ChildDetailsCubit, ChildDetailsState>(
listenWhen: (previous, current) {
return current is ChildDetailsErrorState ||
current is ChildEditGivingAllowanceSuccessState;
},
buildWhen: (previous, current) {
return current is ChildDetailsFetchingState ||
current is ChildDetailsFetchedState;
},
listener: (context, state) {
if (state is ChildDetailsErrorState) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
state.errorMessage,
textAlign: TextAlign.center,
SnackBarHelper.showMessage(
context,
text: state.errorMessage,
isError: true,
);
} else if (state is ChildEditGivingAllowanceSuccessState) {
Navigator.push(
context,
EditAllowanceSuccessPage(
uiModel: EditAllowanceSuccessUIModel(
amountWithCurrencySymbol: '\$${state.allowance}',
),
backgroundColor: Theme.of(context).errorColor,
),
).toRoute(context),
);
}
},
Expand Down Expand Up @@ -119,7 +137,11 @@ class ChildDetailsPage extends StatelessWidget {
.profileDetails.givingAllowance.amount,
},
);
_pushToEdit(context);
_navigateToEditAllowanceScreen(
context,
state.profileDetails.givingAllowance.amount
.toInt(),
);
},
),
),
Expand All @@ -131,4 +153,20 @@ class ChildDetailsPage extends StatelessWidget {
},
);
}

Future<void> _navigateToEditAllowanceScreen(
BuildContext context,
int currentAllowance,
) async {
final dynamic result = await Navigator.push(
context,
EditAllowancePage(
currency: r'$',
initialAllowance: currentAllowance,
).toRoute(context),
);
if (result != null && result is int && context.mounted) {
await context.read<ChildDetailsCubit>().updateAllowance(result);
}
}
}
10 changes: 5 additions & 5 deletions lib/features/children/edit_child/cubit/edit_child_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:givt_app/core/enums/enums.dart';
import 'package:givt_app/core/logging/logging.dart';
import 'package:givt_app/features/children/edit_child/mixins/mixins.dart';
import 'package:givt_app/features/children/edit_child/repositories/create_child_repository.dart';
import 'package:givt_app/features/children/details/models/profile_ext.dart';
import 'package:givt_app/features/children/edit_child/mixins/mixins.dart';
import 'package:givt_app/features/children/edit_child/models/edit_child.dart';
import 'package:givt_app/features/children/edit_child/repositories/edit_child_repository.dart';
import 'package:givt_app/l10n/l10n.dart';
import 'package:givt_app/utils/utils.dart';

Expand All @@ -14,7 +14,7 @@ part 'edit_child_state.dart';
class EditChildCubit extends Cubit<EditChildState>
with ChildNameValidator, ChildGivingAllowanceValidator {
EditChildCubit(
this._createChildRepository,
this._editChildRepository,
this._locals,
this._profileDetails,
) : super(const EditChildInitialState()) {
Expand All @@ -28,7 +28,7 @@ class EditChildCubit extends Cubit<EditChildState>
});
}

final EditChildRepository _createChildRepository;
final EditChildRepository _editChildRepository;
final AppLocalizations _locals;
final ProfileExt _profileDetails;

Expand Down Expand Up @@ -67,7 +67,7 @@ class EditChildCubit extends Cubit<EditChildState>

emit(const EditChildUploadingState());
try {
final isChildUpdated = await _createChildRepository.editChild(
final isChildUpdated = await _editChildRepository.editChild(
_profileDetails.profile.id,
child,
);
Expand Down
42 changes: 0 additions & 42 deletions lib/features/children/edit_child/pages/edit_child_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -182,48 +182,6 @@ class _EditChildPageState extends State<EditChildPage> {
textInputAction: TextInputAction.next,
readOnly: true,
),
const SizedBox(height: 40),
CreateChildTextField(
enabled:
!state.profileDetails.pendingAllowance,
labelText: context
.l10n.createChildGivingAllowanceTitle,
errorText: state is EditChildInputErrorState
? state.allowanceErrorMessage
: null,
controller: _allowanceController,
maxLength: 4,
textInputAction: TextInputAction.done,
inputFormatters: [
CurrencyTextInputFormatter(
locale: currency.locale,
decimalDigits: 0,
turnOffGrouping: true,
enableNegative: false,
symbol: currency.currencySymbol,
)
],
keyboardType: TextInputType.number,
),
Padding(
padding: const EdgeInsets.only(left: 5),
child: Text(
state.profileDetails.pendingAllowance
? context.l10n
.editChildAllowancePendingInfo
: context.l10n
.childMonthlyGivingAllowanceRange,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(
color: AppTheme
.childGivingAllowanceHint,
),
),
),
const SizedBox(height: 10),
const GivingAllowanceInfoButton(),
const SizedBox(
height: 80,
),
Expand Down
Loading

0 comments on commit 6077f2d

Please sign in to comment.