diff --git a/lib/app/data/models/alarm_model.dart b/lib/app/data/models/alarm_model.dart index 4bd43aad..3866cc92 100644 --- a/lib/app/data/models/alarm_model.dart +++ b/lib/app/data/models/alarm_model.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:cloud_firestore/cloud_firestore.dart' as firestore; - import 'package:isar/isar.dart'; import 'package:ultimate_alarm_clock/app/data/models/user_model.dart'; import 'package:ultimate_alarm_clock/app/utils/utils.dart'; @@ -62,54 +61,55 @@ class AlarmModel { @ignore Map? offsetDetails; - AlarmModel( - {required this.alarmTime, - required this.alarmID, - this.sharedUserIds = const [], - required this.ownerId, - required this.ownerName, - required this.lastEditedUserId, - required this.mutexLock, - this.isEnabled = true, - required this.days, - required this.intervalToAlarm, - required this.isActivityEnabled, - required this.minutesSinceMidnight, - required this.isLocationEnabled, - required this.isSharedAlarmEnabled, - required this.isWeatherEnabled, - required this.location, - required this.weatherTypes, - required this.isMathsEnabled, - required this.mathsDifficulty, - required this.numMathsQuestions, - required this.isShakeEnabled, - required this.shakeTimes, - required this.isQrEnabled, - required this.qrValue, - required this.isPedometerEnabled, - required this.numberOfSteps, - required this.activityInterval, - this.offsetDetails = const {}, - required this.mainAlarmTime, - required this.label, - required this.isOneTime, - required this.snoozeDuration, - required this.gradient, - required this.ringtoneName, - required this.note, - required this.deleteAfterGoesOff, - required this.showMotivationalQuote, - required this.volMax, - required this.volMin, - required this.activityMonitor, - required this.ringOn, - required this.alarmDate, - required this.profile, - required this.isGuardian, - required this.guardianTimer, - required this.guardian, - required this.isCall}); + AlarmModel({ + required this.alarmTime, + required this.alarmID, + this.sharedUserIds = const [], + required this.ownerId, + required this.ownerName, + required this.lastEditedUserId, + required this.mutexLock, + this.isEnabled = true, + required this.days, + required this.intervalToAlarm, + required this.isActivityEnabled, + required this.minutesSinceMidnight, + required this.isLocationEnabled, + required this.isSharedAlarmEnabled, + required this.isWeatherEnabled, + required this.location, + required this.weatherTypes, + required this.isMathsEnabled, + required this.mathsDifficulty, + required this.numMathsQuestions, + required this.isShakeEnabled, + required this.shakeTimes, + required this.isQrEnabled, + required this.qrValue, + required this.isPedometerEnabled, + required this.numberOfSteps, + required this.activityInterval, + this.offsetDetails = const {}, + required this.mainAlarmTime, + required this.label, + required this.isOneTime, + required this.snoozeDuration, + required this.gradient, + required this.ringtoneName, + required this.note, + required this.deleteAfterGoesOff, + required this.showMotivationalQuote, + required this.volMax, + required this.volMin, + required this.activityMonitor, + required this.ringOn, + required this.alarmDate, + required this.profile, + required this.isGuardian, + required this.guardianTimer, + required this.guardian, + required this.isCall, + }); AlarmModel.fromDocumentSnapshot({ required firestore.DocumentSnapshot documentSnapshot, @@ -122,7 +122,6 @@ class AlarmModel { if (isSharedAlarmEnabled && user != null) { mainAlarmTime = documentSnapshot['alarmTime']; // Using offsetted time only if it is enabled - alarmTime = (offsetDetails![user.id]['offsetDuration'] != 0) ? offsetDetails![user.id]['offsettedTime'] : documentSnapshot['alarmTime']; @@ -340,12 +339,8 @@ class AlarmModel { ringOn = alarmData['ringOn']; } - AlarmModel.fromJson(String alarmData, UserModel? user) { - AlarmModel.fromMap(jsonDecode(alarmData)); - } - - static String toJson(AlarmModel alarmRecord) { - return jsonEncode(AlarmModel.toMap(alarmRecord)); + String toJson() { + return jsonEncode(AlarmModel.toMap(this)); } static Map toMap(AlarmModel alarmRecord) { @@ -390,12 +385,13 @@ class AlarmModel { 'volMax': alarmRecord.volMax, 'activityMonitor': alarmRecord.activityMonitor, 'alarmDate': alarmRecord.alarmDate, + 'ringOn': alarmRecord.ringOn, 'profile': alarmRecord.profile, 'isGuardian': alarmRecord.isGuardian, 'guardianTimer': alarmRecord.guardianTimer, 'guardian': alarmRecord.guardian, 'isCall': alarmRecord.isCall, - 'ringOn': alarmRecord.ringOn + 'ringOn': alarmRecord.ringOn, }; if (alarmRecord.isSharedAlarmEnabled) { @@ -409,14 +405,13 @@ class AlarmModel { // Rotate the list to start with Sunday var rotatedList = [boolList.last] + boolList.sublist(0, boolList.length - 1); + // Convert the list of bools to a string of 1s and 0s return rotatedList.map((b) => b ? '1' : '0').join(); } List stringToBoolList(String s) { - // Rotate the string to start with Monday final rotatedString = s.substring(1) + s[0]; - // Convert the rotated string to a list of boolean values return rotatedString.split('').map((c) => c == '1').toList(); } } diff --git a/lib/app/data/providers/secure_storage_provider.dart b/lib/app/data/providers/secure_storage_provider.dart index 24592e8d..deb96449 100644 --- a/lib/app/data/providers/secure_storage_provider.dart +++ b/lib/app/data/providers/secure_storage_provider.dart @@ -107,7 +107,7 @@ class SecureStorageProvider { ); } - //check 24 hrs enabled + // Check 24 hrs enabled Future read24HoursEnabled({required String key}) async { return await _secureStorage.read(key: key) == 'true'; } @@ -212,4 +212,15 @@ class SecureStorageProvider { value: timerId.toString(), ); } + + + Future writeChallengeTimeLimit(int timeLimit) async { + await _secureStorage.write(key: 'challengeTimeLimit', value: timeLimit.toString()); +} + + + Future readChallengeTimeLimit() async { + String? timeLimit = await _secureStorage.read(key: 'challengeTimeLimit'); + return timeLimit != null ? int.parse(timeLimit) : 30; +} } diff --git a/lib/app/modules/alarmChallenge/controllers/alarm_challenge_controller.dart b/lib/app/modules/alarmChallenge/controllers/alarm_challenge_controller.dart index 9bc6e0de..2bd74619 100644 --- a/lib/app/modules/alarmChallenge/controllers/alarm_challenge_controller.dart +++ b/lib/app/modules/alarmChallenge/controllers/alarm_challenge_controller.dart @@ -10,6 +10,7 @@ import 'package:ultimate_alarm_clock/app/data/models/alarm_model.dart'; import 'package:ultimate_alarm_clock/app/utils/audio_utils.dart'; import 'package:ultimate_alarm_clock/app/utils/constants.dart'; import 'package:ultimate_alarm_clock/app/utils/utils.dart'; +import 'package:ultimate_alarm_clock/app/data/providers/secure_storage_provider.dart'; // Import secure storage provider class AlarmChallengeController extends GetxController { AlarmModel alarmRecord = Get.arguments; @@ -42,6 +43,7 @@ class AlarmChallengeController extends GetxController { bool shouldProcessStepCount = false; late Stream _stepCountStream; + Timer? _timer; void onButtonPressed(String buttonText) { displayValue.value += buttonText; @@ -89,7 +91,7 @@ class AlarmChallengeController extends GetxController { @override void onInit() async { super.onInit(); - _startTimer(); + await _startTimer(); String ringtoneName = alarmRecord.ringtoneName; @@ -199,29 +201,23 @@ class AlarmChallengeController extends GetxController { } } - void _startTimer() async { - const duration = Duration(seconds: 15); - const totalIterations = 1500000; - const decrement = 0.000001; - - for (var i = totalIterations; i > 0; i--) { - if (!isTimerEnabled) { - debugPrint('THIS IS THE BUG'); - break; - } - if (progress.value <= 0.0) { + Future _startTimer() async { + int challengeTimeLimit = await SecureStorageProvider().readChallengeTimeLimit(); + progress.value = 1.0; + _timer = Timer.periodic(Duration(milliseconds: 100), (timer) { + if (progress.value > 0) { + progress.value -= 1 / (challengeTimeLimit * 10); + } else { + timer.cancel(); shouldProcessStepCount = false; Get.until((route) => route.settings.name == '/alarm-ring'); - break; } - await Future.delayed(duration ~/ i); - progress.value -= decrement; - } + }); } restartTimer() { - progress.value = 1.0; // Reset the progress to its initial value - _startTimer(); // Start a new timer + _timer?.cancel(); + _startTimer(); } isChallengesComplete() { @@ -235,7 +231,7 @@ class AlarmChallengeController extends GetxController { @override void onClose() async { super.onClose(); - + _timer?.cancel(); shouldProcessStepCount = false; String ringtoneName = alarmRecord.ringtoneName; @@ -255,4 +251,3 @@ class AlarmChallengeController extends GetxController { } } } - diff --git a/lib/app/modules/alarmChallenge/views/alarm_challenge_view.dart b/lib/app/modules/alarmChallenge/views/alarm_challenge_view.dart index fef97975..d689ff50 100644 --- a/lib/app/modules/alarmChallenge/views/alarm_challenge_view.dart +++ b/lib/app/modules/alarmChallenge/views/alarm_challenge_view.dart @@ -18,8 +18,6 @@ class AlarmChallengeView extends GetView { @override Widget build(BuildContext context) { - // var width = Get.width; - // var height = Get.height; final double width = MediaQuery.of(context).size.width; final double height = MediaQuery.of(context).size.height; diff --git a/lib/app/modules/settings/controllers/settings_controller.dart b/lib/app/modules/settings/controllers/settings_controller.dart index 5db0c333..94ce553d 100644 --- a/lib/app/modules/settings/controllers/settings_controller.dart +++ b/lib/app/modules/settings/controllers/settings_controller.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:google_sign_in/google_sign_in.dart'; - import 'package:ultimate_alarm_clock/app/data/models/user_model.dart'; - import 'package:ultimate_alarm_clock/app/data/providers/secure_storage_provider.dart'; import 'package:ultimate_alarm_clock/app/modules/home/controllers/home_controller.dart'; import 'package:ultimate_alarm_clock/app/modules/settings/controllers/theme_controller.dart'; @@ -22,6 +20,7 @@ class SettingsController extends GetxController { final _hapticFeedbackKey = 'haptic_feedback'; var is24HrsEnabled = false.obs; final _f24HrsEnabledKey = '24_hours_format'; + final RxInt challengeTimeLimit = 30.obs; var isSortedAlarmListEnabled = true.obs; final _sortedAlarmListKey = 'sorted_alarm_list'; var currentLanguage = 'en_US'.obs; @@ -74,10 +73,21 @@ class SettingsController extends GetxController { userModel.value = await _secureStorageProvider.retrieveUserModel(); } _loadPreference(); + _loadChallengeSettings(); } - // Logins user using GoogleSignIn + void _loadChallengeSettings() async { + // Store the retrieved challenge time limit from secure storage + int storedTimeLimit = await _secureStorageProvider.readChallengeTimeLimit(); + challengeTimeLimit.value = storedTimeLimit; + } + + void updateTimeLimit(int newLimit) { + challengeTimeLimit.value = newLimit; + _secureStorageProvider.writeChallengeTimeLimit(newLimit); + } + // Logins user using GoogleSignIn Future logoutGoogle() async { await GoogleCloudProvider.logoutGoogle(); await SecureStorageProvider().deleteUserModel(); @@ -94,13 +104,11 @@ class SettingsController extends GetxController { getKey(ApiKeys key) async { return await _secureStorageProvider.retrieveApiKey(key); } - - // Add weather state to the flutter secure storage + addWeatherState(String weatherState) async { await _secureStorageProvider.storeWeatherState(weatherState); } - - // Get weather state from the flutter secure storage + getWeatherState() async { return await _secureStorageProvider.retrieveWeatherState(); } @@ -108,7 +116,6 @@ class SettingsController extends GetxController { Future isApiKeyValid(String apiKey) async { final weather = WeatherFactory(apiKey); try { - // ignore: unused_local_variable final currentWeather = await weather.currentWeatherByLocation( currentPoint.value.latitude, currentPoint.value.longitude, @@ -135,28 +142,22 @@ class SettingsController extends GetxController { Future _checkAndRequestPermission({bool? background}) async { if (!await FlLocation.isLocationServicesEnabled) { - // Location services are disabled. return false; } var locationPermission = await FlLocation.checkLocationPermission(); if (locationPermission == LocationPermission.deniedForever) { - // Cannot request runtime permission because location permission is - // denied forever. return false; } else if (locationPermission == LocationPermission.denied) { - // Ask the user for location permission. locationPermission = await FlLocation.requestLocationPermission(); if (locationPermission == LocationPermission.denied || - locationPermission == LocationPermission.deniedForever) return false; + locationPermission == LocationPermission.deniedForever) + return false; } - - // Location permission must always be allowed (LocationPermission.always) - // to collect location data in the background. if (background == true && - locationPermission == LocationPermission.whileInUse) return false; + locationPermission == LocationPermission.whileInUse) + return false; - // Location services has been enabled and permission have been granted. return true; } @@ -185,11 +186,7 @@ class SettingsController extends GetxController { // Store the retrieved weather state from the flutter secure storage String? retrievedWeatherState = await getWeatherState(); - - // If the weather state has been previously stored there if (retrievedWeatherState != null) { - // Assign the weatherKeyState to the previously stored weather state, - // but first convert the stored string to the WeatherKeyState enum weatherKeyState.value = WeatherKeyState.values.firstWhereOrNull( (weatherState) => weatherState.name == retrievedWeatherState, ) ?? @@ -242,4 +239,4 @@ class SettingsController extends GetxController { storage.writeCurrentLanguage(local.value); storage.writeLocale(languageCode, countryCode); } -} +} \ No newline at end of file diff --git a/lib/app/modules/settings/views/settings_view.dart b/lib/app/modules/settings/views/settings_view.dart index 680430cd..9fd3aeff 100644 --- a/lib/app/modules/settings/views/settings_view.dart +++ b/lib/app/modules/settings/views/settings_view.dart @@ -9,6 +9,7 @@ import 'package:ultimate_alarm_clock/app/modules/settings/views/enable_haptic_fe import 'package:ultimate_alarm_clock/app/modules/settings/views/enable_sorted_alarm_list.dart'; import 'package:ultimate_alarm_clock/app/modules/settings/views/language_menu.dart'; import 'package:ultimate_alarm_clock/app/modules/settings/views/theme_value_tile.dart'; +import 'package:ultimate_alarm_clock/app/modules/settings/views/time_limit_view.dart'; import 'package:ultimate_alarm_clock/app/utils/utils.dart'; import '../controllers/settings_controller.dart'; import 'google_sign_in.dart'; @@ -114,6 +115,12 @@ class SettingsView extends GetView { const SizedBox( height: 20, ), + TimeLimitView( + controller: controller, + height: height, + width: width, + themeController: controller.themeController, + ) ], ), ), @@ -121,4 +128,4 @@ class SettingsView extends GetView { ), ); } -} +} \ No newline at end of file diff --git a/lib/app/modules/settings/views/time_limit_view.dart b/lib/app/modules/settings/views/time_limit_view.dart new file mode 100644 index 00000000..dc3164db --- /dev/null +++ b/lib/app/modules/settings/views/time_limit_view.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ultimate_alarm_clock/app/utils/constants.dart'; +import 'package:ultimate_alarm_clock/app/utils/utils.dart'; +import '../controllers/settings_controller.dart'; +import '../controllers/theme_controller.dart'; + +class TimeLimitView extends StatelessWidget { + const TimeLimitView({ + super.key, + required this.controller, + required this.themeController, + required this.width, + required this.height, + }); + + final SettingsController controller; + final ThemeController themeController; + final double width; + final double height; + + @override + Widget build(BuildContext context) { + int initialTimeLimit; + return Obx( + () => InkWell( + onTap: () { + Utils.hapticFeedback(); + initialTimeLimit = controller.challengeTimeLimit.value; + Get.defaultDialog( + onWillPop: () async { + Get.back(); + controller.challengeTimeLimit.value = initialTimeLimit; + return true; + }, + titlePadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 5), + backgroundColor: themeController.secondaryBackgroundColor.value, + title: 'Customize Challenge Timer'.tr, + titleStyle: Theme.of(context).textTheme.displaySmall, + content: Obx( + () => Column( + children: [ + Text( + '${controller.challengeTimeLimit.value} seconds'.tr, + style: Theme.of(context).textTheme.displaySmall, + ), + Slider( + value: controller.challengeTimeLimit.value.toDouble(), + onChanged: (double newValue) { + controller.challengeTimeLimit.value = newValue.toInt(); + }, + min: 5.0, + max: 60.0, + divisions: 55, + label: controller.challengeTimeLimit.value.toString(), + ), + ElevatedButton( + onPressed: () { + // controller.saveSettings(); + Get.back(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: kprimaryColor, + ), + child: Text( + 'Apply Time Limit'.tr, + style: TextStyle( + color: themeController.secondaryTextColor.value, + ), + ), + ), + ], + ), + ), + ); + }, + child: Container( + width: width * 0.91, + height: height * 0.09, + decoration: Utils.getCustomTileBoxDecoration( + isLightMode: themeController.currentTheme.value == ThemeMode.light, + ), + child: Center( + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: ListTile( + tileColor: themeController.secondaryBackgroundColor.value, + title: Text( + 'Challenge Timer Limit'.tr, + style: TextStyle( + color: themeController.primaryTextColor.value, + fontSize: 15, + ), + ), + trailing: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Obx( + () => Text( + '${controller.challengeTimeLimit.value.round()} seconds', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: themeController.primaryTextColor.value, + fontSize: 13, + ), + ), + ), + Icon( + Icons.chevron_right, + color: themeController.primaryDisabledTextColor.value, + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +}