From 6a9c2967d2baaafdb5955552d0c2d1cd9567315a Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Mon, 3 Jun 2024 00:26:07 +0300 Subject: [PATCH] Implemented home creation and add counter to the sign up screen --- lib/features/app_layout/bloc/home_cubit.dart | 1 - lib/features/auth/bloc/auth_cubit.dart | 14 +- lib/features/auth/bloc/auth_state.dart | 2 + lib/features/auth/view/otp_view.dart | 113 ++++++++++- .../auth/view/widgets/forget_password.dart | 3 +- .../create_unit_bloc/create_unit_bloc.dart | 190 +++++++++++++++++- .../create_unit_bloc/create_unit_event.dart | 17 ++ .../create_unit_bloc/create_unit_state.dart | 2 + .../create_home_view.dart | 22 +- lib/features/splash/view/splash_view.dart | 2 +- lib/services/api/home_creation_api.dart | 166 +++++++++++++++ .../helpers/shared_preferences_helper.dart | 58 ++++++ 12 files changed, 577 insertions(+), 13 deletions(-) rename lib/features/menu/view/widgets/{home management => create_home}/create_home_view.dart (92%) create mode 100644 lib/services/api/home_creation_api.dart create mode 100644 lib/utils/helpers/shared_preferences_helper.dart diff --git a/lib/features/app_layout/bloc/home_cubit.dart b/lib/features/app_layout/bloc/home_cubit.dart index 38e307b..13d33e8 100644 --- a/lib/features/app_layout/bloc/home_cubit.dart +++ b/lib/features/app_layout/bloc/home_cubit.dart @@ -22,7 +22,6 @@ import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/navigation/navigation_service.dart'; import 'package:syncrow_app/services/api/spaces_api.dart'; import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; -import 'package:syncrow_app/utils/helpers/snack_bar.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart'; diff --git a/lib/features/auth/bloc/auth_cubit.dart b/lib/features/auth/bloc/auth_cubit.dart index 8098dc2..c62bc08 100644 --- a/lib/features/auth/bloc/auth_cubit.dart +++ b/lib/features/auth/bloc/auth_cubit.dart @@ -9,7 +9,6 @@ import 'package:syncrow_app/features/auth/model/user_model.dart'; import 'package:syncrow_app/navigation/navigation_service.dart'; import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/services/api/authentication_api.dart'; - part 'auth_state.dart'; class AuthCubit extends Cubit { @@ -202,15 +201,22 @@ class AuthCubit extends Cubit { sendOtp() async { try { - final response = - await AuthenticationAPI.sendOtp(body: {'email': email, 'type': 'VERIFICATION'}); - // otpCode = response['otp']; + await AuthenticationAPI.sendOtp(body: {'email': email, 'type': 'VERIFICATION'}); emit(AuthSignUpSuccess()); } catch (_) { emit(AuthLoginError(message: 'Something went wrong')); } } + reSendOtp() async { + try { + await AuthenticationAPI.sendOtp(body: {'email': email, 'type': 'VERIFICATION'}); + emit(ResendOtpSuccess()); + } catch (_) { + emit(AuthLoginError(message: 'Something went wrong')); + } + } + verifyOtp() async { emit(AuthLoginLoading()); try { diff --git a/lib/features/auth/bloc/auth_state.dart b/lib/features/auth/bloc/auth_state.dart index cadb9e0..e9075f2 100644 --- a/lib/features/auth/bloc/auth_state.dart +++ b/lib/features/auth/bloc/auth_state.dart @@ -24,6 +24,8 @@ class AuthOtpSuccess extends AuthSuccess {} class AuthSignUpSuccess extends AuthSuccess {} +class ResendOtpSuccess extends AuthSuccess {} + class AuthLoginError extends AuthError { AuthLoginError({required super.message, super.code}); } diff --git a/lib/features/auth/view/otp_view.dart b/lib/features/auth/view/otp_view.dart index f788d00..19b4486 100644 --- a/lib/features/auth/view/otp_view.dart +++ b/lib/features/auth/view/otp_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -9,12 +10,106 @@ import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.da import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/life_cycle_event_handler.dart'; +import 'package:syncrow_app/utils/helpers/shared_preferences_helper.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart'; import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; -class OtpView extends StatelessWidget { +class OtpView extends StatefulWidget { const OtpView({super.key}); + @override + State createState() => _OtpViewState(); +} + +class _OtpViewState extends State { + bool timerCanceled = false; + Timer? countdownTimer; + Duration myDuration = const Duration(); + late LifecycleEventHandler _lifecycleEventHandler; + String otpCode = ''; + int? remainingSec = 30; + + @override + void initState() { + super.initState(); + bool timerStarted = false; + _lifecycleEventHandler = LifecycleEventHandler( + resumeCallBack: () async { + SharedPreferencesHelper.saveBoolToSP('timeStampSaved', false); + String timeStampInBackground = await SharedPreferencesHelper.readStringFromSP('timeStamp'); + int savedCounter = await SharedPreferencesHelper.readIntFromSP('savedCounter'); + DateTime currentTime = DateTime.now(); + int differenceInSeconds = timeStampInBackground.isNotEmpty + ? currentTime.difference(DateTime.parse(timeStampInBackground)).inSeconds + : 0; + remainingSec = differenceInSeconds > savedCounter ? 0 : savedCounter - differenceInSeconds; + timerStarted = true; + startTimer(remainingSec ?? 0); + return; + }, + suspendingCallBack: () async { + handleTimerOnBackground(); + }, + onPauseCallBack: () async { + handleTimerOnBackground(); + }, + inactiveCallBack: () async { + handleTimerOnBackground(); + }, + ); + WidgetsBinding.instance.addObserver(_lifecycleEventHandler); + if (!timerStarted) { + timerStarted = false; + startTimer(remainingSec ?? 0); + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(_lifecycleEventHandler); + super.dispose(); + } + + handleTimerOnBackground() async { + bool timeStampSaved = await SharedPreferencesHelper.readBoolFromSP('timeStampSaved') ?? false; + if (!timeStampSaved) { + final dateInString = DateTime.now().toString(); + SharedPreferencesHelper.saveIntToSP('savedCounter', remainingSec ?? 0); + SharedPreferencesHelper.saveStringToSP('timeStamp', dateInString); + SharedPreferencesHelper.saveBoolToSP('timeStampSaved', true); + } + } + + void startTimer(int sec) { + timerCanceled = false; + if (countdownTimer != null) { + countdownTimer!.cancel(); + countdownTimer = null; + } + + myDuration = Duration(seconds: sec); + int seconds = sec; + countdownTimer = Timer.periodic(const Duration(seconds: 1), (values) { + seconds = seconds - 1; + remainingSec = seconds; + + if (mounted) { + if (seconds < 0) { + setState(() { + countdownTimer!.cancel(); + timerCanceled = true; + WidgetsBinding.instance.removeObserver(_lifecycleEventHandler); + }); + } else { + setState(() { + myDuration = Duration(seconds: remainingSec ?? seconds); + }); + } + } + }); + } + @override Widget build(BuildContext context) { String maskedEmail = AuthCubit.get(context).maskEmail(AuthCubit.get(context).email); @@ -23,6 +118,9 @@ class OtpView extends StatelessWidget { if (state is AuthOtpSuccess) { Navigator.pushNamedAndRemoveUntil(context, Routes.homeRoute, (value) => false); } + if (state is ResendOtpSuccess) { + startTimer(30); + } }, builder: (context, state) { return Scaffold( @@ -215,10 +313,18 @@ class OtpView extends StatelessWidget { Colors.white, ), ), - child: const Text( - 'Resend', + child: Text( + timerCanceled + ? 'Resend' + : myDuration.inSeconds + .remainder(60) + .toString() + .padLeft(2, '0'), ), onPressed: () async { + if (!timerCanceled) { + return; + } if ((state is! AuthLoading)) { await AuthCubit.get(context).sendOtp(); FocusScope.of(context).unfocus(); @@ -241,6 +347,5 @@ class OtpView extends StatelessWidget { ); }, ); - ; } } diff --git a/lib/features/auth/view/widgets/forget_password.dart b/lib/features/auth/view/widgets/forget_password.dart index 5d8e373..82d3306 100644 --- a/lib/features/auth/view/widgets/forget_password.dart +++ b/lib/features/auth/view/widgets/forget_password.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/utils/context_extension.dart'; class ForgetPassword extends StatelessWidget { @@ -14,7 +15,7 @@ class ForgetPassword extends StatelessWidget { const Spacer(), TextButton( onPressed: () { - //TODO Navigate to forgot password + Navigator.popAndPushNamed(context, Routes.otpRoute); }, child: BodyMedium( text: "Forgot Password?", diff --git a/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart b/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart index cf04d2c..5a00e20 100644 --- a/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart +++ b/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart @@ -1,7 +1,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:syncrow_app/features/auth/model/user_model.dart'; import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_event.dart'; import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart'; -import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/services/api/home_creation_api.dart'; class CreateUnitBloc extends Bloc { List rooms = []; @@ -11,6 +13,7 @@ class CreateUnitBloc extends Bloc { CreateUnitBloc() : super(InitialState()) { on(_createRoom); on(_removeRoom); + on(_save); } void _createRoom(CreateRoomEvent event, Emitter emit) async { @@ -39,4 +42,189 @@ class CreateUnitBloc extends Bloc { return; } } + + void _save(SaveRooms event, Emitter emit) async { + try { + emit(LoadingState()); + if (rooms.isEmpty) { + const ErrorState(message: 'Please add at least one room!'); + return; + } + + var storage = const FlutterSecureStorage(); + var userId = await storage.read(key: UserModel.userUuidKey) ?? ''; + + Map communityBody = {'communityName': event.communityName}; + final response = await HomeCreation.createCommunity(communityBody); + if (response['data']['uuid'] != '') { + // final result = + // await _assignToCommunity(communityId: response['data']['uuid'], userId: userId); + + // if (result) { + String buildingId = await _createBuilding( + buildingName: event.buildingName, + communityId: response['data']['uuid'], + userId: userId); + + if (buildingId.isNotEmpty) { + final floorId = await _createFloor( + floorName: event.floorName, buildingId: buildingId, userId: userId); + + if (floorId.isNotEmpty) { + final unitId = + await _createUnit(unitName: event.unitName, floorId: floorId, userId: userId); + + if (unitId.isNotEmpty && rooms.isNotEmpty) { + rooms.forEach((room) async { + await _createNewRoom(roomName: room, unitId: unitId, userId: userId); + }); + } + } + } + // } + } + emit(RoomsSavedSuccessfully()); + } catch (_) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } +} + +Future _assignToCommunity({required String communityId, required String userId}) async { + try { + Map body = { + 'communityUuid': communityId, + 'userUuid': userId, + }; + final response = await HomeCreation.assignUserToCommunity(body); + + return response['success'] ?? false; + } catch (_) { + return false; + } +} + +Future _createBuilding( + {required String buildingName, required String communityId, required String userId}) async { + try { + Map body = {'buildingName': buildingName, 'communityUuid': communityId}; + final response = await HomeCreation.createBuilding(body); + // if (response['data']['uuid'] != '') { + // final result = await _assignToBuilding(buildingId: response['data']['uuid'], userId: userId); + + return response['data']['uuid'] ?? ''; + // } else { + // return ''; + // } + } catch (_) { + return ''; + } +} + +Future _assignToBuilding({required String buildingId, required String userId}) async { + try { + Map body = { + 'buildingUuid': buildingId, + 'userUuid': userId, + }; + final response = await HomeCreation.assignUserToBuilding(body); + + return response['success'] ?? false; + } catch (_) { + return false; + } +} + +Future _createFloor( + {required String floorName, required String buildingId, required String userId}) async { + try { + Map body = {'floorName': floorName, 'buildingUuid': buildingId}; + final response = await HomeCreation.createFloor(body); + // if (response['data']['uuid'] != '') { + // final result = await _assignToFloor(buildingId: response['data']['uuid'], userId: userId); + + return response['data']['uuid'] ?? ''; + // } else { + // return ''; + // } + } catch (_) { + return ''; + } +} + +Future _assignToFloor({required String buildingId, required String userId}) async { + try { + Map body = { + 'floorUuid': buildingId, + 'userUuid': userId, + }; + final response = await HomeCreation.assignUserToFloor(body); + + return response['success'] ?? false; + } catch (_) { + return false; + } +} + +Future _createUnit( + {required String unitName, required String floorId, required String userId}) async { + try { + Map body = {'unitName': unitName, 'floorUuid': floorId}; + final response = await HomeCreation.createUnit(body); + if (response['data']['uuid'] != '') { + final result = await _assignToUnit(unitId: response['data']['uuid'], userId: userId); + + return result ? response['data']['uuid'] : ''; + } else { + return ''; + } + } catch (_) { + return ''; + } +} + +Future _assignToUnit({required String unitId, required String userId}) async { + try { + Map body = { + 'unitUuid': unitId, + 'userUuid': userId, + }; + final response = await HomeCreation.assignUserToUnit(body); + + return response['success'] ?? false; + } catch (_) { + return false; + } +} + +Future _createNewRoom( + {required String roomName, required String unitId, required String userId}) async { + try { + Map body = {'roomName': roomName, 'unitUuid': unitId}; + final response = await HomeCreation.createRoom(body); + // if (response['data']['uuid'] != '') { + // final result = await _assignToRoom(roomId: response['data']['uuid'], userId: userId); + + return response['data']['uuid'] ?? ''; + // } else { + // return ''; + // } + } catch (e) { + return ''; + } +} + +Future _assignToRoom({required String roomId, required String userId}) async { + try { + Map body = { + 'roomUuid': roomId, + 'userUuid': userId, + }; + final response = await HomeCreation.assignUserToRoom(body); + + return response['success'] ?? false; + } catch (_) { + return false; + } } diff --git a/lib/features/menu/bloc/create_unit_bloc/create_unit_event.dart b/lib/features/menu/bloc/create_unit_bloc/create_unit_event.dart index 3b82e6d..8c79c3e 100644 --- a/lib/features/menu/bloc/create_unit_bloc/create_unit_event.dart +++ b/lib/features/menu/bloc/create_unit_bloc/create_unit_event.dart @@ -28,3 +28,20 @@ class RemoveRoomEvent extends CreateUnitEvent { @override List get props => [roomName]; } + +class SaveRooms extends CreateUnitEvent { + final String communityName; + final String buildingName; + final String floorName; + final String unitName; + + const SaveRooms({ + required this.communityName, + required this.buildingName, + required this.floorName, + required this.unitName, + }); + + @override + List get props => [communityName, buildingName, floorName, unitName]; +} diff --git a/lib/features/menu/bloc/create_unit_bloc/create_unit_state.dart b/lib/features/menu/bloc/create_unit_bloc/create_unit_state.dart index 0d77a39..511c637 100644 --- a/lib/features/menu/bloc/create_unit_bloc/create_unit_state.dart +++ b/lib/features/menu/bloc/create_unit_bloc/create_unit_state.dart @@ -20,6 +20,8 @@ class CreateRoomState extends CreateUnitState { List get props => [roomList]; } +class RoomsSavedSuccessfully extends CreateUnitState {} + class ErrorState extends CreateUnitState { final String message; diff --git a/lib/features/menu/view/widgets/home management/create_home_view.dart b/lib/features/menu/view/widgets/create_home/create_home_view.dart similarity index 92% rename from lib/features/menu/view/widgets/home management/create_home_view.dart rename to lib/features/menu/view/widgets/create_home/create_home_view.dart index 6307fb0..2480e2e 100644 --- a/lib/features/menu/view/widgets/home management/create_home_view.dart +++ b/lib/features/menu/view/widgets/create_home/create_home_view.dart @@ -11,6 +11,8 @@ import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/helpers/snack_bar.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; @@ -26,6 +28,14 @@ class CreateUnitView extends StatelessWidget { return BlocProvider( create: (context) => CreateUnitBloc(), child: BlocConsumer(listener: (context, state) { + if (state is RoomsSavedSuccessfully) { + CustomSnackBar.displaySnackBar('Saved successfully'); + NavigationService.navigatorKey.currentState!.pushNamedAndRemoveUntil( + Routes.homeRoute, + (Route route) => false, + ); + } + if (state is ErrorState) { CustomSnackBar.displaySnackBar(state.message); } @@ -34,7 +44,17 @@ class CreateUnitView extends StatelessWidget { return DefaultScaffold( actions: [ TextButton( - onPressed: () {}, + onPressed: () { + if (unitName.isNotEmpty) { + BlocProvider.of(context).add(SaveRooms( + communityName: 'Community Test', + buildingName: 'Building Test', + floorName: 'Floor Test', + unitName: unitName)); + } else { + CustomSnackBar.displaySnackBar('Please enter the unit name'); + } + }, child: const BodyLarge( text: 'Save', fontWeight: FontWeight.bold, diff --git a/lib/features/splash/view/splash_view.dart b/lib/features/splash/view/splash_view.dart index 8e1c6fe..bc81f87 100644 --- a/lib/features/splash/view/splash_view.dart +++ b/lib/features/splash/view/splash_view.dart @@ -28,7 +28,7 @@ class _SplashViewState extends State { if (state is AuthTokenSuccess) { Navigator.pushNamedAndRemoveUntil(context, Routes.homeRoute, (route) => false); } else if (state is AuthTokenError) { - Future.delayed(const Duration(seconds: 1), () { + Future.delayed(const Duration(seconds: 2), () { Navigator.pushReplacement( context, CustomPageRoute(builder: (context) => const LoginView()), diff --git a/lib/services/api/home_creation_api.dart b/lib/services/api/home_creation_api.dart new file mode 100644 index 0000000..5ac6d5a --- /dev/null +++ b/lib/services/api/home_creation_api.dart @@ -0,0 +1,166 @@ +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class HomeCreation { + static final HTTPService _httpService = HTTPService(); + + static Future> createCommunity(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addCommunity, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> assignUserToCommunity(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addCommunityToUser, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> createBuilding(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addBuilding, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> assignUserToBuilding(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addBuildingToUser, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> createFloor(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addFloor, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> assignUserToFloor(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addBuildingToUser, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> createUnit(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addUnit, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> assignUserToUnit(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addUnitToUser, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> createRoom(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addRoom, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> assignUserToRoom(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addRoomToUser, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } +} diff --git a/lib/utils/helpers/shared_preferences_helper.dart b/lib/utils/helpers/shared_preferences_helper.dart new file mode 100644 index 0000000..b9c7e0f --- /dev/null +++ b/lib/utils/helpers/shared_preferences_helper.dart @@ -0,0 +1,58 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class SharedPreferencesHelper { + static saveStringToSP(String key, String value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(key, value); + } + + static saveBoolToSP(String key, bool value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(key, value); + } + + static saveIntToSP(String key, int value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt(key, value); + } + + static saveDoubleToSP(String key, double value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setDouble(key, value); + } + + static saveStringListToSP(String key, List value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setStringList(key, value); + } + + static Future readStringFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + String value = prefs.getString(key) ?? ''; + return value; + } + + static Future readBoolFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + bool? value = prefs.getBool(key); + return value; + } + + static Future readIntFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + int value = prefs.getInt(key) ?? 0; + return value; + } + + static Future> readStringListFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + List? value = prefs.getStringList(key) ?? []; + return value; + } + + static Future removeValueFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(key); + return true; + } +}