Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/kids 838 implement edit allowance screen #745

Merged
merged 13 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";
TammiLion marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -325,6 +325,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
22 changes: 22 additions & 0 deletions lib/core/network/api_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class APIService {
String _apiURLAWS;

String get apiURL => _apiURL;

String get apiURLAWS => _apiURLAWS;

void updateApiUrl(String url, String awsurl) {
Expand Down Expand Up @@ -505,6 +506,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