From 27754082173d1dd14915f8aa674417080b527b93 Mon Sep 17 00:00:00 2001 From: Inderjeet Singh Date: Tue, 21 Apr 2026 23:16:38 +0530 Subject: [PATCH] Added full support of clean pomodoro timer implementation --- lib/app/data/models/pomodoro_state_model.dart | 55 +++ .../data/providers/get_storage_provider.dart | 16 + .../bindings/splash_screen_binding.dart | 5 + .../modules/timer/bindings/timer_binding.dart | 16 +- .../controllers/pomodoro_controller.dart | 433 ++++++++++++++++++ .../timer/views/pomodoro_tab_view.dart | 331 +++++++++++++ lib/app/modules/timer/views/timer_view.dart | 278 ++++++----- lib/app/utils/language.dart | 35 +- .../utils/languages/french_translations.dart | 34 +- .../utils/languages/german_translations.dart | 39 +- .../utils/languages/russian_translations.dart | 31 +- .../utils/languages/spanish_translations.dart | 38 +- 12 files changed, 1168 insertions(+), 143 deletions(-) create mode 100644 lib/app/data/models/pomodoro_state_model.dart create mode 100644 lib/app/modules/timer/controllers/pomodoro_controller.dart create mode 100644 lib/app/modules/timer/views/pomodoro_tab_view.dart diff --git a/lib/app/data/models/pomodoro_state_model.dart b/lib/app/data/models/pomodoro_state_model.dart new file mode 100644 index 000000000..e808b5f7d --- /dev/null +++ b/lib/app/data/models/pomodoro_state_model.dart @@ -0,0 +1,55 @@ +class PomodoroStateModel { + final int workMinutes; + final int shortBreakMinutes; + final int totalSessions; + final int currentSession; + final String currentPhase; + final int phaseDurationMs; + final int remainingMs; + final bool isRunning; + final int phaseAnchorRemainingMs; + final int? phaseStartedAtMs; + + const PomodoroStateModel({ + required this.workMinutes, + required this.shortBreakMinutes, + required this.totalSessions, + required this.currentSession, + required this.currentPhase, + required this.phaseDurationMs, + required this.remainingMs, + required this.isRunning, + required this.phaseAnchorRemainingMs, + this.phaseStartedAtMs, + }); + + Map toJson() { + return { + 'workMinutes': workMinutes, + 'shortBreakMinutes': shortBreakMinutes, + 'totalSessions': totalSessions, + 'currentSession': currentSession, + 'currentPhase': currentPhase, + 'phaseDurationMs': phaseDurationMs, + 'remainingMs': remainingMs, + 'isRunning': isRunning, + 'phaseAnchorRemainingMs': phaseAnchorRemainingMs, + 'phaseStartedAtMs': phaseStartedAtMs, + }; + } + + factory PomodoroStateModel.fromJson(Map map) { + return PomodoroStateModel( + workMinutes: (map['workMinutes'] ?? 25) as int, + shortBreakMinutes: (map['shortBreakMinutes'] ?? 5) as int, + totalSessions: (map['totalSessions'] ?? 4) as int, + currentSession: (map['currentSession'] ?? 1) as int, + currentPhase: (map['currentPhase'] ?? 'idle') as String, + phaseDurationMs: (map['phaseDurationMs'] ?? 0) as int, + remainingMs: (map['remainingMs'] ?? 0) as int, + isRunning: (map['isRunning'] ?? false) as bool, + phaseAnchorRemainingMs: (map['phaseAnchorRemainingMs'] ?? 0) as int, + phaseStartedAtMs: map['phaseStartedAtMs'] as int?, + ); + } +} diff --git a/lib/app/data/providers/get_storage_provider.dart b/lib/app/data/providers/get_storage_provider.dart index 8258dc4d5..487896178 100644 --- a/lib/app/data/providers/get_storage_provider.dart +++ b/lib/app/data/providers/get_storage_provider.dart @@ -65,4 +65,20 @@ class GetStorageProvider { void writeWorldClocks(List clocks) { _getStorage.write('worldClocks', clocks.map((e) => e.toJson()).toList()); } + + Future writePomodoroState(Map state) async { + await _getStorage.write('pomodoroState', state); + } + + Map? readPomodoroState() { + final dynamic raw = _getStorage.read('pomodoroState'); + if (raw is Map) { + return Map.from(raw); + } + return null; + } + + Future clearPomodoroState() async { + await _getStorage.remove('pomodoroState'); + } } diff --git a/lib/app/modules/splashScreen/bindings/splash_screen_binding.dart b/lib/app/modules/splashScreen/bindings/splash_screen_binding.dart index a7b1ccb90..0b3b02501 100644 --- a/lib/app/modules/splashScreen/bindings/splash_screen_binding.dart +++ b/lib/app/modules/splashScreen/bindings/splash_screen_binding.dart @@ -4,6 +4,7 @@ import 'package:ultimate_alarm_clock/app/modules/settings/controllers/theme_cont import 'package:ultimate_alarm_clock/app/modules/splashScreen/controllers/splash_screen_controller.dart'; import '../../home/controllers/home_controller.dart'; +import '../../timer/controllers/pomodoro_controller.dart'; import '../../timer/controllers/timer_controller.dart'; class SplashScreenBinding extends Bindings { @@ -21,5 +22,9 @@ class SplashScreenBinding extends Bindings { TimerController(), permanent: true, ); + Get.put( + PomodoroController(), + permanent: true, + ); } } diff --git a/lib/app/modules/timer/bindings/timer_binding.dart b/lib/app/modules/timer/bindings/timer_binding.dart index d3ae437d5..c13a11a17 100644 --- a/lib/app/modules/timer/bindings/timer_binding.dart +++ b/lib/app/modules/timer/bindings/timer_binding.dart @@ -1,13 +1,19 @@ import 'package:get/get.dart'; +import 'package:ultimate_alarm_clock/app/modules/timer/controllers/pomodoro_controller.dart'; import 'package:ultimate_alarm_clock/app/modules/timer/controllers/timer_controller.dart'; class TimerBinding extends Bindings { @override void dependencies() { - Get.put( - TimerController(), - ); - - + if (!Get.isRegistered()) { + Get.put( + TimerController(), + ); + } + if (!Get.isRegistered()) { + Get.put( + PomodoroController(), + ); + } } } diff --git a/lib/app/modules/timer/controllers/pomodoro_controller.dart b/lib/app/modules/timer/controllers/pomodoro_controller.dart new file mode 100644 index 000000000..2ee27cec2 --- /dev/null +++ b/lib/app/modules/timer/controllers/pomodoro_controller.dart @@ -0,0 +1,433 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:get/get.dart'; +import 'package:ultimate_alarm_clock/app/data/models/timer_model.dart'; +import 'package:ultimate_alarm_clock/app/data/models/pomodoro_state_model.dart'; +import 'package:ultimate_alarm_clock/app/data/providers/get_storage_provider.dart'; +import 'package:ultimate_alarm_clock/app/modules/settings/controllers/theme_controller.dart'; +import 'package:ultimate_alarm_clock/app/utils/audio_utils.dart'; + +class PomodoroController extends GetxController { + PomodoroController(); + + static const String phaseIdle = 'idle'; + static const String phaseWork = 'work'; + static const String phaseBreak = 'break'; + static const String phaseCompleted = 'completed'; + + final RxInt workMinutes = 25.obs; + final RxInt shortBreakMinutes = 5.obs; + final RxInt totalSessions = 4.obs; + + final RxInt currentSession = 1.obs; + final RxString currentPhase = phaseIdle.obs; + final RxInt phaseDurationMs = 0.obs; + final RxInt remainingMs = 0.obs; + final RxBool isRunning = false.obs; + + final GetStorageProvider storage = Get.find(); + final ThemeController themeController = Get.find(); + + Timer? _ticker; + int _phaseAnchorRemainingMs = 0; + int? _phaseStartedAtMs; + + @override + void onInit() { + super.onInit(); + _restoreState(); + } + + @override + void onClose() { + _ticker?.cancel(); + AudioUtils.stopTimer(ringtoneName: 'Default'); + super.onClose(); + } + + void updateWorkMinutes(int value) { + workMinutes.value = max(1, value); + if (!isActive) { + _persistState(); + } + } + + void updateShortBreakMinutes(int value) { + shortBreakMinutes.value = max(1, value); + if (!isActive) { + _persistState(); + } + } + + void updateTotalSessions(int value) { + totalSessions.value = max(1, value); + if (currentSession.value > totalSessions.value) { + currentSession.value = totalSessions.value; + } + if (!isActive) { + _persistState(); + } + } + + bool get isActive => + currentPhase.value == phaseWork || currentPhase.value == phaseBreak; + + bool get canStart => + currentPhase.value == phaseIdle || currentPhase.value == phaseCompleted; + + bool get canResume => isActive && !isRunning.value; + + bool get canPause => isActive && isRunning.value; + + bool get canSkip => isActive; + + int get displayRemainingMs { + if (isActive) { + return remainingMs.value; + } + return Duration(minutes: workMinutes.value).inMilliseconds; + } + + double get progress { + if (phaseDurationMs.value <= 0) { + return 0; + } + final int elapsed = phaseDurationMs.value - remainingMs.value; + return elapsed / phaseDurationMs.value; + } + + String get phaseLabel { + if (currentPhase.value == phaseWork) { + return 'Work'.tr; + } + if (currentPhase.value == phaseBreak) { + return 'Break'.tr; + } + if (currentPhase.value == phaseCompleted) { + return 'Completed'.tr; + } + return 'Ready'.tr; + } + + Future startOrResumePomodoro() async { + if (canStart) { + if (!_validateConfig()) { + return; + } + currentSession.value = 1; + _startWorkPhase(showStartToast: true); + return; + } + + if (canResume) { + _phaseStartedAtMs = DateTime.now().millisecondsSinceEpoch; + _phaseAnchorRemainingMs = remainingMs.value; + isRunning.value = true; + _startTicker(); + _persistState(); + _showToast('Pomodoro resumed'.tr); + } + } + + void pausePomodoro() { + if (!canPause) { + return; + } + _updateRemainingFromClock(); + _ticker?.cancel(); + _ticker = null; + _phaseStartedAtMs = null; + isRunning.value = false; + _persistState(); + _showToast('Pomodoro paused'.tr); + } + + void skipPhase() { + if (!canSkip) { + _showError('No active pomodoro phase to skip'.tr); + return; + } + + if (currentPhase.value == phaseWork) { + _showToast('Work session skipped'.tr); + _startBreakPhase(showStartToast: true); + return; + } + + _showToast('Break skipped'.tr); + _startNextWorkOrFinish(showStartToast: true); + } + + Future resetPomodoro() async { + _ticker?.cancel(); + _ticker = null; + _phaseStartedAtMs = null; + _phaseAnchorRemainingMs = 0; + + currentPhase.value = phaseIdle; + currentSession.value = 1; + phaseDurationMs.value = 0; + remainingMs.value = 0; + isRunning.value = false; + + await storage.clearPomodoroState(); + _showToast('Pomodoro reset'.tr); + } + + void _startWorkPhase({required bool showStartToast}) { + _startPhase( + phase: phaseWork, + durationMs: Duration(minutes: workMinutes.value).inMilliseconds, + ); + if (showStartToast) { + _showToast('Work session started'.tr); + } + } + + void _startBreakPhase({required bool showStartToast}) { + _startPhase( + phase: phaseBreak, + durationMs: Duration(minutes: shortBreakMinutes.value).inMilliseconds, + ); + if (showStartToast) { + _showToast('Break started'.tr); + } + } + + void _startPhase({ + required String phase, + required int durationMs, + }) { + _ticker?.cancel(); + currentPhase.value = phase; + phaseDurationMs.value = durationMs; + remainingMs.value = durationMs; + _phaseAnchorRemainingMs = durationMs; + _phaseStartedAtMs = DateTime.now().millisecondsSinceEpoch; + isRunning.value = true; + + _startTicker(); + _persistState(); + } + + void _startTicker() { + _ticker?.cancel(); + _ticker = Timer.periodic(const Duration(seconds: 1), (_) { + _updateRemainingFromClock(); + + if (remainingMs.value <= 0) { + _handlePhaseComplete(); + } else { + _persistState(); + } + }); + } + + void _updateRemainingFromClock() { + if (_phaseStartedAtMs == null) { + return; + } + + final int elapsed = + DateTime.now().millisecondsSinceEpoch - _phaseStartedAtMs!; + final int nextRemaining = _phaseAnchorRemainingMs - elapsed; + remainingMs.value = max(0, nextRemaining); + } + + void _handlePhaseComplete() { + _ticker?.cancel(); + _ticker = null; + _phaseStartedAtMs = null; + _phaseAnchorRemainingMs = 0; + + _showToast('Phase completed'.tr); + + if (currentPhase.value == phaseWork) { + if (currentSession.value >= totalSessions.value) { + _markCompleted(); + } else { + _startBreakPhase(showStartToast: true); + } + return; + } + + if (currentPhase.value == phaseBreak) { + _startNextWorkOrFinish(showStartToast: true); + } + } + + void _startNextWorkOrFinish({required bool showStartToast}) { + if (currentSession.value >= totalSessions.value) { + _markCompleted(); + return; + } + + currentSession.value += 1; + _startWorkPhase(showStartToast: showStartToast); + } + + void _markCompleted() { + currentPhase.value = phaseCompleted; + phaseDurationMs.value = 0; + remainingMs.value = 0; + isRunning.value = false; + _phaseStartedAtMs = null; + _phaseAnchorRemainingMs = 0; + _persistState(); + + _playShortCompletionSound(); + _showToast('Pomodoro completed'.tr); + } + + bool _validateConfig() { + if (workMinutes.value < 1 || + shortBreakMinutes.value < 1 || + totalSessions.value < 1) { + _showError('Please set valid pomodoro values'.tr); + return false; + } + return true; + } + + void _playShortCompletionSound() { + final TimerModel completionTone = TimerModel( + timerValue: 0, + timeElapsed: 0, + startedOn: DateTime.now().toString(), + ringtoneName: 'Default', + timerName: 'Pomodoro', + ); + + AudioUtils.playTimer(alarmRecord: completionTone); + Future.delayed(const Duration(seconds: 2), () { + AudioUtils.stopTimer(ringtoneName: 'Default'); + }); + } + + void _persistState() { + final PomodoroStateModel state = PomodoroStateModel( + workMinutes: workMinutes.value, + shortBreakMinutes: shortBreakMinutes.value, + totalSessions: totalSessions.value, + currentSession: currentSession.value, + currentPhase: currentPhase.value, + phaseDurationMs: phaseDurationMs.value, + remainingMs: remainingMs.value, + isRunning: isRunning.value, + phaseAnchorRemainingMs: _phaseAnchorRemainingMs, + phaseStartedAtMs: _phaseStartedAtMs, + ); + + storage.writePomodoroState(state.toJson()); + } + + void _restoreState() { + final Map? raw = storage.readPomodoroState(); + if (raw == null) { + return; + } + + final PomodoroStateModel state = PomodoroStateModel.fromJson(raw); + workMinutes.value = max(1, state.workMinutes); + shortBreakMinutes.value = max(1, state.shortBreakMinutes); + totalSessions.value = max(1, state.totalSessions); + + currentSession.value = + max(1, min(state.currentSession, totalSessions.value)); + currentPhase.value = state.currentPhase; + phaseDurationMs.value = max(0, state.phaseDurationMs); + remainingMs.value = max(0, state.remainingMs); + isRunning.value = state.isRunning; + + _phaseAnchorRemainingMs = max(0, state.phaseAnchorRemainingMs); + _phaseStartedAtMs = state.phaseStartedAtMs; + + if (!isRunning.value || !isActive || _phaseStartedAtMs == null) { + isRunning.value = false; + _phaseStartedAtMs = null; + _phaseAnchorRemainingMs = remainingMs.value; + return; + } + + _updateRemainingFromClock(); + + if (remainingMs.value <= 0) { + _handleRestorePhaseCompletion(); + return; + } + + _phaseAnchorRemainingMs = remainingMs.value; + _phaseStartedAtMs = DateTime.now().millisecondsSinceEpoch; + _startTicker(); + _persistState(); + } + + void _handleRestorePhaseCompletion() { + if (currentPhase.value == phaseWork) { + if (currentSession.value >= totalSessions.value) { + _markCompletedAfterRestore(); + } else { + _startBreakAfterRestore(); + } + return; + } + + if (currentPhase.value == phaseBreak) { + if (currentSession.value >= totalSessions.value) { + _markCompletedAfterRestore(); + } else { + currentSession.value += 1; + _startWorkAfterRestore(); + } + return; + } + + isRunning.value = false; + _phaseStartedAtMs = null; + _phaseAnchorRemainingMs = 0; + _persistState(); + } + + void _startWorkAfterRestore() { + _startPhase( + phase: phaseWork, + durationMs: Duration(minutes: workMinutes.value).inMilliseconds, + ); + } + + void _startBreakAfterRestore() { + _startPhase( + phase: phaseBreak, + durationMs: Duration(minutes: shortBreakMinutes.value).inMilliseconds, + ); + } + + void _markCompletedAfterRestore() { + currentPhase.value = phaseCompleted; + phaseDurationMs.value = 0; + remainingMs.value = 0; + isRunning.value = false; + _phaseStartedAtMs = null; + _phaseAnchorRemainingMs = 0; + _persistState(); + } + + void _showToast(String message) { + Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_SHORT, + backgroundColor: themeController.secondaryBackgroundColor.value, + textColor: themeController.primaryTextColor.value, + ); + } + + void _showError(String message) { + Get.snackbar( + 'Error'.tr, + message, + snackPosition: SnackPosition.BOTTOM, + ); + } +} diff --git a/lib/app/modules/timer/views/pomodoro_tab_view.dart b/lib/app/modules/timer/views/pomodoro_tab_view.dart new file mode 100644 index 000000000..4ff09c960 --- /dev/null +++ b/lib/app/modules/timer/views/pomodoro_tab_view.dart @@ -0,0 +1,331 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:numberpicker/numberpicker.dart'; +import 'package:ultimate_alarm_clock/app/modules/settings/controllers/theme_controller.dart'; +import 'package:ultimate_alarm_clock/app/modules/timer/controllers/pomodoro_controller.dart'; +import 'package:ultimate_alarm_clock/app/utils/constants.dart'; +import 'package:ultimate_alarm_clock/app/utils/utils.dart'; + +class PomodoroTabView extends GetView { + PomodoroTabView({super.key}); + + final ThemeController themeController = Get.find(); + + @override + Widget build(BuildContext context) { + return Obx( + () => SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildTopSummary(context), + _buildCenterTimer(context), + _buildBottomControls(context), + ], + ), + ), + ), + ); + } + + Widget _buildTopSummary(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 20), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: kprimaryColor.withOpacity(0.15), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.lens, size: 12, color: kprimaryColor), + const SizedBox(width: 8), + Text( + '${controller.phaseLabel} • ${'Session'.tr} ${controller.currentSession.value}/${controller.totalSessions.value}', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: kprimaryColor, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildCenterTimer(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + Utils.formatMilliseconds(controller.displayRemainingMs), + style: Theme.of(context).textTheme.displayLarge!.copyWith( + color: themeController.primaryTextColor.value, + fontWeight: FontWeight.w300, + fontSize: 84, + ), + ), + const SizedBox(height: 24), + if (controller.isActive) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: LinearProgressIndicator( + minHeight: 6, + value: controller.progress.clamp(0.0, 1.0), + backgroundColor: themeController.secondaryBackgroundColor.value, + valueColor: const AlwaysStoppedAnimation(kprimaryColor), + ), + ), + ) + else + InkWell( + onTap: () => _showSetupBottomSheet(context), + borderRadius: BorderRadius.circular(20), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: themeController.secondaryBackgroundColor.value, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.tune, + size: 20, + color: themeController.primaryDisabledTextColor.value, + ), + const SizedBox(width: 10), + Text( + '${controller.workMinutes.value}m / ${controller.shortBreakMinutes.value}m / ${controller.totalSessions.value} ${'sessions'.tr}', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: themeController.primaryTextColor.value, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ], + ); + } + + Widget _buildBottomControls(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (controller.isActive) ...[ + _buildCircularButton( + context: context, + icon: Icons.skip_next_rounded, + color: themeController.secondaryBackgroundColor.value, + iconColor: themeController.primaryDisabledTextColor.value, + onPressed: controller.canSkip ? controller.skipPhase : null, + ), + const SizedBox(width: 24), + ], + _buildCircularButton( + context: context, + icon: controller.canPause + ? Icons.pause_rounded + : Icons.play_arrow_rounded, + color: kprimaryColor, + iconColor: ksecondaryBackgroundColor, + isLarge: true, + onPressed: () { + if (controller.canPause) { + controller.pausePomodoro(); + } else { + controller.startOrResumePomodoro(); + } + }, + ), + if (controller.isActive) ...[ + const SizedBox(width: 24), + _buildCircularButton( + context: context, + icon: Icons.refresh_rounded, + color: themeController.secondaryBackgroundColor.value, + iconColor: themeController.primaryDisabledTextColor.value, + onPressed: controller.resetPomodoro, + ), + ] else ...[ + // Keep layout balanced when not active + const SizedBox(width: 24), + SizedBox( + height: 60, + width: 60, + child: IconButton( + icon: Icon( + Icons.settings, + color: themeController.primaryDisabledTextColor.value, + ), + onPressed: () => _showSetupBottomSheet(context), + ), + ), + ], + ], + ), + ); + } + + Widget _buildCircularButton({ + required BuildContext context, + required IconData icon, + required Color color, + required Color iconColor, + required VoidCallback? onPressed, + bool isLarge = false, + }) { + final double size = isLarge ? 80 : 60; + final double iconSize = isLarge ? 40 : 28; + + return InkWell( + onTap: onPressed, + borderRadius: BorderRadius.circular(size / 2), + child: Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: onPressed == null ? color.withOpacity(0.5) : color, + ), + child: Icon( + icon, + color: onPressed == null ? iconColor.withOpacity(0.5) : iconColor, + size: iconSize, + ), + ), + ); + } + + void _showSetupBottomSheet(BuildContext context) { + Get.bottomSheet( + Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: themeController.secondaryBackgroundColor.value, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: Obx( + () => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Pomodoro Setup'.tr, + style: Theme.of(context).textTheme.displaySmall!.copyWith( + color: themeController.primaryTextColor.value, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _durationPicker( + context: context, + title: 'Work'.tr, + value: controller.workMinutes.value, + minValue: 1, + maxValue: 120, + onChanged: controller.updateWorkMinutes, + ), + _durationPicker( + context: context, + title: 'Break'.tr, + value: controller.shortBreakMinutes.value, + minValue: 1, + maxValue: 60, + onChanged: controller.updateShortBreakMinutes, + ), + _durationPicker( + context: context, + title: 'Sessions'.tr, + value: controller.totalSessions.value, + minValue: 1, + maxValue: 12, + onChanged: controller.updateTotalSessions, + ), + ], + ), + const SizedBox(height: 32), + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: kprimaryColor, + foregroundColor: ksecondaryBackgroundColor, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + onPressed: () => Get.back(), + child: Text( + 'Done'.tr, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _durationPicker({ + required BuildContext context, + required String title, + required int value, + required int minValue, + required int maxValue, + required ValueChanged onChanged, + }) { + return Column( + children: [ + Text( + title, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: themeController.primaryDisabledTextColor.value, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + NumberPicker( + minValue: minValue, + maxValue: maxValue, + value: value, + onChanged: onChanged, + itemWidth: 60, + itemHeight: 40, + selectedTextStyle: + Theme.of(context).textTheme.displayMedium!.copyWith( + color: kprimaryColor, + fontWeight: FontWeight.w700, + ), + textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: themeController.primaryDisabledTextColor.value, + ), + axis: Axis.vertical, + ), + ], + ); + } +} diff --git a/lib/app/modules/timer/views/timer_view.dart b/lib/app/modules/timer/views/timer_view.dart index a18a5014d..a8fed097d 100644 --- a/lib/app/modules/timer/views/timer_view.dart +++ b/lib/app/modules/timer/views/timer_view.dart @@ -8,6 +8,7 @@ import 'package:ultimate_alarm_clock/app/data/providers/isar_provider.dart'; import 'package:ultimate_alarm_clock/app/modules/settings/controllers/theme_controller.dart'; import 'package:ultimate_alarm_clock/app/modules/timer/controllers/timer_controller.dart'; import 'package:ultimate_alarm_clock/app/modules/timer/views/timer_animation.dart'; +import 'package:ultimate_alarm_clock/app/modules/timer/views/pomodoro_tab_view.dart'; import 'package:ultimate_alarm_clock/app/utils/constants.dart'; import 'package:ultimate_alarm_clock/app/utils/end_drawer.dart'; import 'package:ultimate_alarm_clock/app/utils/hover_preset_button.dart'; @@ -22,129 +23,175 @@ class TimerView extends GetView { final ThemeController themeController = Get.find(); // var width = Get.width; // var height = Get.height; - + @override Widget build(BuildContext context) { final double height = MediaQuery.of(context).size.height; final double width = MediaQuery.of(context).size.width; - return Scaffold( - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - appBar: PreferredSize( - preferredSize: Size.fromHeight(height / 8.9), - child: AppBar( - toolbarHeight: height / 7.9, - elevation: 0.0, - title: Obx( - () => Text( - 'Timer', - style: Theme.of(context).textTheme.displaySmall!.copyWith( - color: themeController.primaryTextColor.value.withOpacity( - 0.75, + return DefaultTabController( + length: 2, + child: Builder( + builder: (BuildContext tabContext) { + final TabController tabController = + DefaultTabController.of(tabContext); + return AnimatedBuilder( + animation: tabController, + builder: (BuildContext context, Widget? child) { + final bool isTimerTab = tabController.index == 0; + return Scaffold( + floatingActionButtonLocation: + FloatingActionButtonLocation.centerFloat, + appBar: PreferredSize( + preferredSize: Size.fromHeight(height / 6.3), + child: AppBar( + toolbarHeight: height / 10.0, + elevation: 0.0, + title: Obx( + () => Text( + 'Timer'.tr, + style: + Theme.of(context).textTheme.displaySmall!.copyWith( + color: themeController.primaryTextColor.value + .withOpacity( + 0.75, + ), + fontSize: 26, + ), + ), ), - fontSize: 26, - ), - ), - ), - actions: [ - LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Obx( - () => IconButton( - onPressed: () { - Utils.hapticFeedback(); - Scaffold.of(context).openEndDrawer(); - }, - icon: const Icon( - Icons.menu, + actions: [ + LayoutBuilder( + builder: + (BuildContext context, BoxConstraints constraints) { + return Obx( + () => IconButton( + onPressed: () { + Utils.hapticFeedback(); + Scaffold.of(context).openEndDrawer(); + }, + icon: const Icon(Icons.menu), + color: themeController.primaryTextColor.value + .withOpacity(0.75), + iconSize: 27, + ), + ); + }, + ), + ], + bottom: PreferredSize( + preferredSize: const Size.fromHeight(44), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Obx( + () => TabBar( + indicatorColor: kprimaryColor, + indicatorWeight: 3, + labelColor: kprimaryColor, + unselectedLabelColor: + themeController.primaryDisabledTextColor.value, + tabs: [ + Tab( + text: 'Timer'.tr, + ), + Tab( + text: 'Pomodoro'.tr, + ), + ], + ), + ), + ), ), - color: themeController.primaryTextColor.value - .withOpacity(0.75), - iconSize: 27, ), - ); - }, - ), - ], - ), - ), - floatingActionButton: Obx( - () => Visibility( - visible: - controller.isbottom.value && controller.timerList.length >= 3, - child: Container( - height: 85, - child: FittedBox( - child: FloatingActionButton( - onPressed: () { - Utils.hapticFeedback(); - timerSelector(context, width, height); - }, - backgroundColor: kprimaryColor, - child: const Icon( - Icons.add_alarm, - color: ksecondaryBackgroundColor, - size: 26, ), - ), - ), - ), - ), - ), - body: Obx(() { - final hasTimers = controller.timerList.isNotEmpty; - - return Stack( - children: [ - // Main content: Timer list - if (hasTimers) - Positioned.fill( - child: StreamBuilder>( - stream: IsarDb.getTimers(), - builder: (context, snapshot) { - if (!snapshot.hasData || snapshot.data!.isEmpty) { - return const Center( - child: CircularProgressIndicator.adaptive( - backgroundColor: Colors.transparent, - valueColor: AlwaysStoppedAnimation(kprimaryColor), + floatingActionButton: isTimerTab + ? Obx( + () => Visibility( + visible: controller.isbottom.value && + controller.timerList.length >= 3, + child: SizedBox( + height: 85, + child: FittedBox( + child: FloatingActionButton( + onPressed: () { + Utils.hapticFeedback(); + timerSelector(context, width, height); + }, + backgroundColor: kprimaryColor, + child: const Icon( + Icons.add_alarm, + color: ksecondaryBackgroundColor, + size: 26, + ), + ), + ), + ), ), - ); - } else { - List timers = snapshot.data!; - return ListView.builder( - controller: controller.scrollController, - itemCount: timers.length + - 1, // Include space for addATimerSpace only if not sticky - padding: const EdgeInsets.only(bottom: 80), - itemBuilder: (BuildContext context, int index) { - if (index == timers.length) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: - buildAddTimerSection(context, width, height), - ); - } - return TimerAnimatedCard( - key: ValueKey(timers[index].timerId), - index: index, - timer: timers[index], - ); - }, - ); - } - }, + ) + : null, + body: TabBarView( + children: [ + buildTimerTabBody(context, width, height), + PomodoroTabView(), + ], ), - ), + endDrawer: buildEndDrawer(context), + ); + }, + ); + }, + ), + ); + } + + Widget buildTimerTabBody(BuildContext context, double width, double height) { + return Obx(() { + final hasTimers = controller.timerList.isNotEmpty; - // No timers: Centered addATimerSpace and preset buttons - if (!hasTimers) - Center( - child: buildAddTimerSection(context, width, height), + return Stack( + children: [ + if (hasTimers) + Positioned.fill( + child: StreamBuilder>( + stream: IsarDb.getTimers(), + builder: (context, snapshot) { + if (!snapshot.hasData || snapshot.data!.isEmpty) { + return const Center( + child: CircularProgressIndicator.adaptive( + backgroundColor: Colors.transparent, + valueColor: AlwaysStoppedAnimation(kprimaryColor), + ), + ); + } + + List timers = snapshot.data!; + return ListView.builder( + controller: controller.scrollController, + itemCount: timers.length + 1, + padding: const EdgeInsets.only(bottom: 80), + itemBuilder: (BuildContext context, int index) { + if (index == timers.length) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: buildAddTimerSection(context, width, height), + ); + } + return TimerAnimatedCard( + key: ValueKey(timers[index].timerId), + index: index, + timer: timers[index], + ); + }, + ); + }, ), - ], - ); - }), - endDrawer: buildEndDrawer(context), - ); + ), + if (!hasTimers) + Center( + child: buildAddTimerSection(context, width, height), + ), + ], + ); + }); } Widget buildAddTimerSection( @@ -559,7 +606,8 @@ class TimerView extends GetView { border: InputBorder.none, ), textAlign: TextAlign.center, - controller: controller.inputHoursControllerTimer, + controller: controller + .inputHoursControllerTimer, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.allow( @@ -628,7 +676,8 @@ class TimerView extends GetView { border: InputBorder.none, ), textAlign: TextAlign.center, - controller: controller.inputMinutesControllerTimer, + controller: controller + .inputMinutesControllerTimer, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.allow( @@ -697,7 +746,8 @@ class TimerView extends GetView { border: InputBorder.none, ), textAlign: TextAlign.center, - controller: controller.inputSecondsControllerTimer, + controller: controller + .inputSecondsControllerTimer, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.allow( diff --git a/lib/app/utils/language.dart b/lib/app/utils/language.dart index 3b9058aaa..a2a2c17a1 100644 --- a/lib/app/utils/language.dart +++ b/lib/app/utils/language.dart @@ -14,6 +14,34 @@ class AppTranslations extends Translations { 'en_US': { 'Alarm': 'Alarm', 'Timer': 'Timer', + 'Pomodoro': 'Pomodoro', + 'Pomodoro setup': 'Pomodoro setup', + 'Work': 'Work', + 'Break': 'Break', + 'Sessions': 'Sessions', + 'Current phase': 'Current phase', + 'Session': 'Session', + 'Start Pomodoro': 'Start Pomodoro', + 'Pause': 'Pause', + 'Resume': 'Resume', + 'Skip': 'Skip', + 'Reset': 'Reset', + 'Completed': 'Completed', + 'Ready': 'Ready', + 'min': 'min', + 'Work session started': 'Work session started', + 'Break started': 'Break started', + 'Phase completed': 'Phase completed', + 'Pomodoro completed': 'Pomodoro completed', + 'Pomodoro resumed': 'Pomodoro resumed', + 'Pomodoro paused': 'Pomodoro paused', + 'Work session skipped': 'Work session skipped', + 'Break skipped': 'Break skipped', + 'Pomodoro reset': 'Pomodoro reset', + 'No active pomodoro phase to skip': + 'No active pomodoro phase to skip', + 'Please set valid pomodoro values': + 'Please set valid pomodoro values', 'Enable 24 Hour Format': 'Enable 24 Hour Format', 'Enable Haptic Feedback': 'Enable Haptic Feedback', 'Enable Sorted Alarm List': 'Enable Sorted Alarm List', @@ -57,7 +85,7 @@ class AppTranslations extends Translations { 'No': 'No', 'Confirmation': 'Confirmation', 'want to delete?': 'Are you sure you want to delete this alarm?', - 'delete' : 'Delete', + 'delete': 'Delete', 'You cannot join your own alarm!': 'You cannot join your own alarm!', 'An alarm with this ID doesn\'t exist!': @@ -87,7 +115,6 @@ class AppTranslations extends Translations { 'Leave': 'Leave', 'Save': 'Save', 'Update': 'Update', - 'Rings in @timeToAlarm': 'Rings in @timeToAlarm', 'Uh-oh!': 'Uh-oh!', 'alarmEditing': 'This alarm is currently being edited!', 'Go back': 'Go back', @@ -131,7 +158,8 @@ class AppTranslations extends Translations { 'question': 'question', //pedometer_challenge_tile.dart 'Pedometer': 'Pedometer', - 'pedometerDescription': 'You will have to walk a set number of steps to dismiss the alarm.', + 'pedometerDescription': + 'You will have to walk a set number of steps to dismiss the alarm.', //note.dart 'Add a note': 'Add a note', // qr_bar_code_tile.dart @@ -161,7 +189,6 @@ class AppTranslations extends Translations { 'times': 'times', 'time': 'time', //'shared_alarm_tile.dart - 'Shared Alarm': 'Shared Alarm', 'Shared alarms': 'Shared alarms', 'sharedDescription': 'Share alarms with others using the Alarm ID. Each shared user can choose to have their alarm ring before or after the set time.', diff --git a/lib/app/utils/languages/french_translations.dart b/lib/app/utils/languages/french_translations.dart index 4fb193bba..bff13f76b 100644 --- a/lib/app/utils/languages/french_translations.dart +++ b/lib/app/utils/languages/french_translations.dart @@ -8,6 +8,34 @@ class FrenchTranslations extends Translations { 'fr_FR': { 'Alarm': 'Réveil', 'Timer': 'Minuteur', + 'Pomodoro': 'Pomodoro', + 'Pomodoro setup': 'Configuration Pomodoro', + 'Work': 'Travail', + 'Break': 'Pause', + 'Sessions': 'Sessions', + 'Current phase': 'Phase actuelle', + 'Session': 'Session', + 'Start Pomodoro': 'Démarrer Pomodoro', + 'Pause': 'Pause', + 'Resume': 'Reprendre', + 'Skip': 'Passer', + 'Reset': 'Réinitialiser', + 'Completed': 'Terminé', + 'Ready': 'Prêt', + 'min': 'min', + 'Work session started': 'Session de travail démarrée', + 'Break started': 'Pause démarrée', + 'Phase completed': 'Phase terminée', + 'Pomodoro completed': 'Pomodoro terminé', + 'Pomodoro resumed': 'Pomodoro repris', + 'Pomodoro paused': 'Pomodoro en pause', + 'Work session skipped': 'Session de travail ignorée', + 'Break skipped': 'Pause ignorée', + 'Pomodoro reset': 'Pomodoro réinitialisé', + 'No active pomodoro phase to skip': + 'Aucune phase Pomodoro active à passer', + 'Please set valid pomodoro values': + 'Veuillez définir des valeurs Pomodoro valides', 'StopWatch': 'Chronomètre', 'Enable 24 Hour Format': 'Activer le format 24 heures', 'Enable Haptic Feedback': 'Activer les commentaires haptiques', @@ -55,8 +83,9 @@ class FrenchTranslations extends Translations { 'Yes': 'Oui', 'No': 'Non', 'Confirmation': 'Confirmation', - 'want to delete?': 'Êtes-vous sûr de vouloir supprimer cette alarme ?', - 'delete' : 'supprimer', + 'want to delete?': + 'Êtes-vous sûr de vouloir supprimer cette alarme ?', + 'delete': 'supprimer', 'You cannot join your own alarm!': 'Vous ne pouvez pas rejoindre votre propre réveil !', 'An alarm with this ID doesn\'t exist!': @@ -185,7 +214,6 @@ class FrenchTranslations extends Translations { 'times': 'fois', 'time': 'fois', //'shared_alarm_tile.dart - 'Shared Alarm': 'Alarme partagée', 'Shared alarms': 'Alarmes partagées', 'sharedDescription': 'Partagez des alarmes avec d\'autres en utilisant l\'ID de l\'alarme. Chaque utilisateur partagé peut choisir de faire sonner son alarme avant ou après l\'heure définie.', diff --git a/lib/app/utils/languages/german_translations.dart b/lib/app/utils/languages/german_translations.dart index 239bb3320..f965363b0 100644 --- a/lib/app/utils/languages/german_translations.dart +++ b/lib/app/utils/languages/german_translations.dart @@ -6,6 +6,34 @@ class GermanTranslations extends Translations { 'de_DE': { 'Alarm': 'Wecker', 'Timer': 'Schaltuhr', + 'Pomodoro': 'Pomodoro', + 'Pomodoro setup': 'Pomodoro-Einstellungen', + 'Work': 'Arbeit', + 'Break': 'Pause', + 'Sessions': 'Sitzungen', + 'Current phase': 'Aktuelle Phase', + 'Session': 'Sitzung', + 'Start Pomodoro': 'Pomodoro starten', + 'Pause': 'Pause', + 'Resume': 'Fortsetzen', + 'Skip': 'Überspringen', + 'Reset': 'Zurücksetzen', + 'Completed': 'Abgeschlossen', + 'Ready': 'Bereit', + 'min': 'Min', + 'Work session started': 'Arbeitssitzung gestartet', + 'Break started': 'Pause gestartet', + 'Phase completed': 'Phase abgeschlossen', + 'Pomodoro completed': 'Pomodoro abgeschlossen', + 'Pomodoro resumed': 'Pomodoro fortgesetzt', + 'Pomodoro paused': 'Pomodoro pausiert', + 'Work session skipped': 'Arbeitssitzung übersprungen', + 'Break skipped': 'Pause übersprungen', + 'Pomodoro reset': 'Pomodoro zurückgesetzt', + 'No active pomodoro phase to skip': + 'Keine aktive Pomodoro-Phase zum Überspringen', + 'Please set valid pomodoro values': + 'Bitte gültige Pomodoro-Werte festlegen', 'StopWatch': 'Stoppuhr', 'Enable 24 Hour Format': '24-Stunden-Format aktivieren', @@ -48,8 +76,9 @@ class GermanTranslations extends Translations { 'Yes': 'Ja', 'No': 'Nein', 'Confirmation': 'Bestätigung', - 'want to delete?': 'Sind Sie sicher, dass Sie diesen Alarm löschen möchten?', - 'delete' : 'löschen', + 'want to delete?': + 'Sind Sie sicher, dass Sie diesen Alarm löschen möchten?', + 'delete': 'löschen', 'You cannot join your own alarm!': 'Sie können Ihrem eigenen Alarm nicht beitreten!', 'An alarm with this ID doesn\'t exist!': @@ -147,10 +176,6 @@ class GermanTranslations extends Translations { 'steps': 'Schritte', 'pedometerDescription': 'Schreiten Sie voran, um zu entlassen! Setzen Sie ein Schrittziel, um den Wecker auszuschalten und einen aktiven und energiegeladenen Start in den Tag zu fördern.', - - 'Solve Maths questions': 'Lösen Sie mathematische Fragen', - 'questions': 'Fragen', - 'question': 'Frage', //note.dart 'Add a note': 'Füg ein Notiz hinzu', // qr_bar_code_tile.dart @@ -180,8 +205,6 @@ class GermanTranslations extends Translations { 'times': 'Mal', 'time': 'Mal', //'shared_alarm_tile.dart - 'Shared Alarm': 'Geteilter Alarm', - 'Shared alarms': 'Geteilte Alarme', 'sharedDescription': 'Teilen Sie Alarme mit anderen mithilfe der Alarm-ID. Jeder geteilte Benutzer kann wählen, ob sein Alarm vor oder nach der eingestellten Zeit klingelt.', diff --git a/lib/app/utils/languages/russian_translations.dart b/lib/app/utils/languages/russian_translations.dart index b55902ac1..00249e59e 100644 --- a/lib/app/utils/languages/russian_translations.dart +++ b/lib/app/utils/languages/russian_translations.dart @@ -6,6 +6,34 @@ class RussianTranslations extends Translations { 'ru_RU': { 'Alarm': 'Будильник', 'Timer': 'Таймер', + 'Pomodoro': 'Помодоро', + 'Pomodoro setup': 'Настройка помодоро', + 'Work': 'Работа', + 'Break': 'Перерыв', + 'Sessions': 'Сессии', + 'Current phase': 'Текущая фаза', + 'Session': 'Сессия', + 'Start Pomodoro': 'Запустить помодоро', + 'Pause': 'Пауза', + 'Resume': 'Продолжить', + 'Skip': 'Пропустить', + 'Reset': 'Сброс', + 'Completed': 'Завершено', + 'Ready': 'Готово', + 'min': 'мин', + 'Work session started': 'Рабочая сессия началась', + 'Break started': 'Перерыв начался', + 'Phase completed': 'Фаза завершена', + 'Pomodoro completed': 'Помодоро завершено', + 'Pomodoro resumed': 'Помодоро продолжено', + 'Pomodoro paused': 'Помодоро приостановлено', + 'Work session skipped': 'Рабочая сессия пропущена', + 'Break skipped': 'Перерыв пропущен', + 'Pomodoro reset': 'Помодоро сброшено', + 'No active pomodoro phase to skip': + 'Нет активной фазы помодоро для пропуска', + 'Please set valid pomodoro values': + 'Укажите корректные значения помодоро', 'StopWatch': 'Секундомер', 'Enable 24 Hour Format': 'Включить 24-часовой формат', 'Enable Haptic Feedback': 'Включить тактильную отдачу', @@ -55,7 +83,7 @@ class RussianTranslations extends Translations { 'No': 'Нет', 'Confirmation': 'подтверждение', 'want to delete?': 'Вы уверены, что хотите удалить этот будильник?', - 'delete':'удалить', + 'delete': 'удалить', 'You cannot join your own alarm!': 'Вы не можете присоединиться к собственной тревоге!', 'An alarm with this ID doesn\'t exist!': @@ -169,7 +197,6 @@ class RussianTranslations extends Translations { 'times': 'раз', 'time': 'раз', //'shared_alarm_tile.dart - 'Shared Alarm': 'Общий будильник', 'Shared alarms': 'Общие будильники', 'sharedDescription': 'Делитесь будильниками с другими, используя ID будильника. Каждый общий пользователь может выбрать, звонить ли его будильнику до или после установленного времени.', diff --git a/lib/app/utils/languages/spanish_translations.dart b/lib/app/utils/languages/spanish_translations.dart index 2e5326a48..7ed60525f 100644 --- a/lib/app/utils/languages/spanish_translations.dart +++ b/lib/app/utils/languages/spanish_translations.dart @@ -8,6 +8,34 @@ class SpanishTranslations extends Translations { 'es_ES': { 'Alarm': 'Alarma', 'Timer': 'Temporizador', + 'Pomodoro': 'Pomodoro', + 'Pomodoro setup': 'Configuración Pomodoro', + 'Work': 'Trabajo', + 'Break': 'Descanso', + 'Sessions': 'Sesiones', + 'Current phase': 'Fase actual', + 'Session': 'Sesión', + 'Start Pomodoro': 'Iniciar Pomodoro', + 'Pause': 'Pausa', + 'Resume': 'Reanudar', + 'Skip': 'Saltar', + 'Reset': 'Restablecer', + 'Completed': 'Completado', + 'Ready': 'Listo', + 'min': 'min', + 'Work session started': 'Sesión de trabajo iniciada', + 'Break started': 'Descanso iniciado', + 'Phase completed': 'Fase completada', + 'Pomodoro completed': 'Pomodoro completado', + 'Pomodoro resumed': 'Pomodoro reanudado', + 'Pomodoro paused': 'Pomodoro en pausa', + 'Work session skipped': 'Sesión de trabajo omitida', + 'Break skipped': 'Descanso omitido', + 'Pomodoro reset': 'Pomodoro restablecido', + 'No active pomodoro phase to skip': + 'No hay una fase de pomodoro activa para omitir', + 'Please set valid pomodoro values': + 'Establece valores válidos de pomodoro', 'StopWatch': 'Cronómetro', 'Enable 24 Hour Format': 'Habilitar formato de 24 horas', @@ -57,8 +85,9 @@ class SpanishTranslations extends Translations { 'Yes': 'Sí', 'No': 'No', 'Confirmation': 'Confirmación', - 'want to delete?': '¿Estás seguro de que deseas eliminar esta alarma?', - 'delete':'borrar', + 'want to delete?': + '¿Estás seguro de que deseas eliminar esta alarma?', + 'delete': 'borrar', 'You cannot join your own alarm!': '¡No puedes unirte a tu propia alarma!', 'An alarm with this ID doesn\'t exist!': @@ -139,10 +168,6 @@ class SpanishTranslations extends Translations { 'Easy': 'Fácil', 'Medium': 'Medio', 'Hard': 'Difícil', - -//maths_challenge_tile.dart - 'Maths': 'Matemáticas', - 'Math problems': 'Problemas matemáticos', 'mathDescription': 'Deberá resolver problemas matemáticos simples del nivel de dificultad elegido para desactivar la alarma.', 'Solve Maths questions': 'Resolver preguntas de matemáticas', @@ -185,7 +210,6 @@ class SpanishTranslations extends Translations { 'times': 'veces', 'time': 'vez', //'shared_alarm_tile.dart - 'Shared Alarm': 'Alarma compartida', 'Shared alarms': 'Alarmas compartidas', 'sharedDescription': 'Comparta alarmas con otros utilizando el ID de la alarma. Cada usuario compartido puede elegir hacer sonar su alarma antes o después del tiempo establecido.',