Skip to content

Customizable Time Limits for Alarm Challenges #734

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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
113 changes: 54 additions & 59 deletions lib/app/data/models/alarm_model.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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,
Expand All @@ -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'];
Expand Down Expand Up @@ -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<String, dynamic> toMap(AlarmModel alarmRecord) {
Expand Down Expand Up @@ -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) {
Expand All @@ -409,14 +405,13 @@ class AlarmModel {
// Rotate the list to start with Sunday
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bring back these comments, helps us keep track of the logic.

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<bool> 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();
}
}
13 changes: 12 additions & 1 deletion lib/app/data/providers/secure_storage_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class SecureStorageProvider {
);
}

//check 24 hrs enabled
// Check 24 hrs enabled
Future<bool> read24HoursEnabled({required String key}) async {
return await _secureStorage.read(key: key) == 'true';
}
Expand Down Expand Up @@ -212,4 +212,15 @@ class SecureStorageProvider {
value: timerId.toString(),
);
}


Future<void> writeChallengeTimeLimit(int timeLimit) async {
await _secureStorage.write(key: 'challengeTimeLimit', value: timeLimit.toString());
}


Future<int> readChallengeTimeLimit() async {
String? timeLimit = await _secureStorage.read(key: 'challengeTimeLimit');
return timeLimit != null ? int.parse(timeLimit) : 30;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -42,6 +43,7 @@ class AlarmChallengeController extends GetxController {
bool shouldProcessStepCount = false;

late Stream<StepCount> _stepCountStream;
Timer? _timer;

void onButtonPressed(String buttonText) {
displayValue.value += buttonText;
Expand Down Expand Up @@ -89,7 +91,7 @@ class AlarmChallengeController extends GetxController {
@override
void onInit() async {
super.onInit();
_startTimer();
await _startTimer();

String ringtoneName = alarmRecord.ringtoneName;

Expand Down Expand Up @@ -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<void> _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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was done like this to keep the timer decreasing smoothly.

progress.value -= decrement;
}
});
}

restartTimer() {
progress.value = 1.0; // Reset the progress to its initial value
_startTimer(); // Start a new timer
_timer?.cancel();
_startTimer();
}

isChallengesComplete() {
Expand All @@ -235,7 +231,7 @@ class AlarmChallengeController extends GetxController {
@override
void onClose() async {
super.onClose();

_timer?.cancel();
shouldProcessStepCount = false;

String ringtoneName = alarmRecord.ringtoneName;
Expand All @@ -255,4 +251,3 @@ class AlarmChallengeController extends GetxController {
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ class AlarmChallengeView extends GetView<AlarmChallengeController> {

@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;

Expand Down
43 changes: 20 additions & 23 deletions lib/app/modules/settings/controllers/settings_controller.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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<void> logoutGoogle() async {
await GoogleCloudProvider.logoutGoogle();
await SecureStorageProvider().deleteUserModel();
Expand All @@ -94,21 +104,18 @@ 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();
}

Future<bool> isApiKeyValid(String apiKey) async {
final weather = WeatherFactory(apiKey);
try {
// ignore: unused_local_variable
final currentWeather = await weather.currentWeatherByLocation(
currentPoint.value.latitude,
currentPoint.value.longitude,
Expand All @@ -135,28 +142,22 @@ class SettingsController extends GetxController {

Future<bool> _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;
}

Expand Down Expand Up @@ -185,11 +186,7 @@ class SettingsController extends GetxController {

// Store the retrieved weather state from the flutter secure storage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add back these comments

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,
) ??
Expand Down Expand Up @@ -242,4 +239,4 @@ class SettingsController extends GetxController {
storage.writeCurrentLanguage(local.value);
storage.writeLocale(languageCode, countryCode);
}
}
}
Loading