diff --git a/assets/ringtones/break_complete.mp3 b/assets/ringtones/break_complete.mp3 new file mode 100644 index 00000000..d6706d2d Binary files /dev/null and b/assets/ringtones/break_complete.mp3 differ diff --git a/assets/ringtones/work_complete.mp3 b/assets/ringtones/work_complete.mp3 new file mode 100644 index 00000000..eba60090 Binary files /dev/null and b/assets/ringtones/work_complete.mp3 differ 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 00000000..50473ed7 --- /dev/null +++ b/lib/app/modules/timer/controllers/pomodoro_controller.dart @@ -0,0 +1,241 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ultimate_alarm_clock/app/modules/timer/views/pomodoro_completion_view.dart'; +import 'package:ultimate_alarm_clock/app/utils/constants.dart'; +import 'package:audioplayers/audioplayers.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +enum TimerType { work, Break } + +class PomodoroController extends GetxController { + RxInt selectedWorkTime = 45.obs; // Default work time in minutes + RxInt selectedBreakTime = 15.obs; // Default break time in minutes + RxString selectedLabel = "Work".obs; + RxBool isRunning = false.obs; + RxBool isBreakTime = false.obs; + RxInt remainingSeconds = 0.obs; + Timer? timer; + + // Added number of intervals feature + RxInt selectedIntervals = 4.obs; // Default intervals + RxInt currentInterval = 0.obs; // Current interval tracker + + final AudioPlayer audioPlayer = AudioPlayer(); + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + + // For custom labels + RxList labelOptions = ["Work", "Study", "Sleep", "Focus", "Create"].obs; + TextEditingController newLabelController = TextEditingController(); + + @override + void onInit() { + super.onInit(); + remainingSeconds.value = selectedWorkTime.value * 60; + _initAudioPlayer(); + _initNotifications(); + } + + @override + void onClose() { + timer?.cancel(); + newLabelController.dispose(); + audioPlayer.dispose(); + super.onClose(); + } + + // Initialize the audio player + void _initAudioPlayer() async { + // Pre-load sounds for faster playback when needed + await audioPlayer.setSource(AssetSource('ringtones/work_complete.mp3')); + } + + // Play notification sound + void playNotificationSound(bool isBreakFinished) async { + try { + // Different sounds for work end and break end + if (isBreakFinished) { + await audioPlayer.setSource(AssetSource('ringtones/break_complete.mp3')); + } else { + await audioPlayer.setSource(AssetSource('ringtones/work_complete.mp3')); + } + + await audioPlayer.resume(); + } catch (e) { + print('Error playing notification sound: $e'); + } + } + + // Initialize notifications + Future _initNotifications() async { + // Initialize settings + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('@mipmap/ic_launcher'); + + const DarwinInitializationSettings initializationSettingsIOS = + DarwinInitializationSettings(); + + const InitializationSettings initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + ); + + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + } + + // Method to show notification + Future _showLocalNotification(String title, String body) async { + const AndroidNotificationDetails androidPlatformChannelSpecifics = + AndroidNotificationDetails( + 'pomodoro_timer_channel', + 'Pomodoro Timer Notifications', + channelDescription: 'Notifications for pomodoro timer events', + importance: Importance.high, + priority: Priority.high, + sound: RawResourceAndroidNotificationSound('notification_sound'), + ); + + const NotificationDetails platformChannelSpecifics = + NotificationDetails(android: androidPlatformChannelSpecifics); + + await flutterLocalNotificationsPlugin.show( + 0, + title, + body, + platformChannelSpecifics, + ); + } + + void startTimer() { + isRunning.value = true; + if (currentInterval.value == 0) { + // First start - reset interval counter + currentInterval.value = 1; + } + + timer = Timer.periodic(Duration(seconds: 1), (timer) { + if (remainingSeconds.value > 0) { + remainingSeconds.value--; + } else { + // Timer completed + if (isBreakTime.value) { + // Break time finished - play break end sound + playNotificationSound(true); + _showLocalNotification( + 'Break Time Complete', + 'Time to focus for ${selectedWorkTime.value} minutes!' + ); + + // Break time finished + currentInterval.value++; + + // Check if we've completed all intervals + if (currentInterval.value > selectedIntervals.value) { + // All intervals completed + timer.cancel(); + isRunning.value = false; + isBreakTime.value = false; + currentInterval.value = 0; + remainingSeconds.value = selectedWorkTime.value * 60; + Get.off(() => CompletionScreen(type: 'all', duration: selectedWorkTime.value * selectedIntervals.value)); + return; + } + + // Still have intervals to go, switch to work time + isBreakTime.value = false; + remainingSeconds.value = selectedWorkTime.value * 60; + Get.off(() => CompletionScreen(type: 'break', duration: selectedBreakTime.value, currentInterval: currentInterval.value, totalIntervals: selectedIntervals.value)); + } else { + // Work time finished - play work end sound + playNotificationSound(false); + _showLocalNotification( + 'Work Time Complete', + 'Time for a ${selectedBreakTime.value} minute break!' + ); + + // Check if it's the last interval, if so, no break needed + if (currentInterval.value >= selectedIntervals.value) { + // All intervals completed + timer.cancel(); + isRunning.value = false; + isBreakTime.value = false; + currentInterval.value = 0; + remainingSeconds.value = selectedWorkTime.value * 60; + Get.off(() => CompletionScreen(type: 'all', duration: selectedWorkTime.value * selectedIntervals.value)); + return; + } + + // Not the last interval, switch to break time + isBreakTime.value = true; + remainingSeconds.value = selectedBreakTime.value * 60; + Get.off(() => CompletionScreen(type: 'work', duration: selectedWorkTime.value, currentInterval: currentInterval.value, totalIntervals: selectedIntervals.value)); + } + } + }); + } + + void stopTimer() { + Get.defaultDialog( + title: "Give Up?", + middleText: "Are you sure you want to give up this session?", + textConfirm: "Yes", + textCancel: "No", + confirmTextColor: Colors.white, + buttonColor: kprimaryColor, + backgroundColor: Colors.white, + titleStyle: TextStyle(color: Colors.black), + middleTextStyle: TextStyle(color: Colors.black54), + onConfirm: () { + timer?.cancel(); + isRunning.value = false; + isBreakTime.value = false; + currentInterval.value = 0; + remainingSeconds.value = selectedWorkTime.value * 60; + Get.back(); // Close Dialog + }, + onCancel: () { + Get.back(); // Close Dialog + }, + ); + } + + void setWorkTime(int minutes) { + selectedWorkTime.value = minutes; + if (!isRunning.value && !isBreakTime.value) { + remainingSeconds.value = minutes * 60; + } + } + + void setBreakTime(int minutes) { + selectedBreakTime.value = minutes; + } + + void setIntervals(int intervals) { + selectedIntervals.value = intervals; + } + + void setLabel(String label) { + selectedLabel.value = label; + } + + void addCustomLabel(String label) { + if (label.isNotEmpty && !labelOptions.contains(label)) { + labelOptions.add(label); + selectedLabel.value = label; + } + newLabelController.clear(); + } + + String get formattedTime { + int minutes = remainingSeconds.value ~/ 60; + int seconds = remainingSeconds.value % 60; + return "${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}"; + } + + String get progressText { + return isBreakTime.value + ? "Break ${currentInterval.value}/${selectedIntervals.value}" + : "Session ${currentInterval.value}/${selectedIntervals.value}"; + } +} \ No newline at end of file diff --git a/lib/app/modules/timer/views/pomodoro_completion_view.dart b/lib/app/modules/timer/views/pomodoro_completion_view.dart new file mode 100644 index 00000000..6be96577 --- /dev/null +++ b/lib/app/modules/timer/views/pomodoro_completion_view.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ultimate_alarm_clock/app/modules/timer/views/pomodoro_timer_view.dart'; + + +class CompletionScreen extends StatelessWidget { + final String type; // 'work' or 'break' + final int duration; + final int? currentInterval; // Optional parameter for current interval + final int? totalIntervals; // Optional parameter for total intervals + + CompletionScreen({ + required this.type, + required this.duration, + this.currentInterval, + this.totalIntervals, + }); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Success Icon + Container( + height: 120, + width: 120, + decoration: BoxDecoration( + color: type == 'work' ? Colors.green.withOpacity(0.2) : Colors.orange.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Center( + child: Icon( + type == 'work' ? Icons.emoji_emotions : Icons.coffee, + color: type == 'work' ? Colors.green : Colors.orange, + size: 70, + ), + ), + ), + + SizedBox(height: 30), + + // Great! text + Text( + "Great!", + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + + SizedBox(height: 20), + + // Message + Text( + type == 'work' + ? "You've completed a $duration-minute work session!" + : "Break time complete! Ready to get back to work?", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Colors.black54, + ), + ), + + SizedBox(height: 40), + + // Continue Button + GestureDetector( + onTap: () => Get.to(PomodoroPage()), + child: Container( + width: 180, + padding: EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: type == 'work' ? Colors.green : Colors.orange, + borderRadius: BorderRadius.circular(30), + boxShadow: [ + BoxShadow( + color: (type == 'work' ? Colors.green : Colors.orange).withOpacity(0.3), + blurRadius: 10, + offset: Offset(0, 4), + ), + ], + ), + child: Center( + child: Text( + "Continue", + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + + SizedBox(height: 30), + + // Small text at bottom + Text( + "Taking regular breaks helps reduce eye strain and mental fatigue", + style: TextStyle( + fontSize: 12, + color: Colors.black38, + ), + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/app/modules/timer/views/pomodoro_timer_view.dart b/lib/app/modules/timer/views/pomodoro_timer_view.dart new file mode 100644 index 00000000..b28e4c87 --- /dev/null +++ b/lib/app/modules/timer/views/pomodoro_timer_view.dart @@ -0,0 +1,689 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ultimate_alarm_clock/app/modules/timer/controllers/pomodoro_controller.dart'; +import 'package:ultimate_alarm_clock/app/utils/constants.dart'; + +class PomodoroPage extends StatelessWidget { + final PomodoroController controller = Get.put(PomodoroController()); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 20), + // App Bar with settings button + AppBar( + centerTitle: true, + title: Text( + "Pomodoro Timer", + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + backgroundColor: Colors.transparent, + elevation: 0, + actions: [ + IconButton( + icon: Icon(Icons.settings, color: Colors.white), + onPressed: () => _showSettingsSheet(context), + ), + ], + ), + + SizedBox(height: 30), + // Main Timer Display + Expanded( + child: Center( + child: Container( + width: double.infinity, + padding: EdgeInsets.all(30), + margin: EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.greenAccent, kprimaryColor.withOpacity(0.5)], + ), + borderRadius: BorderRadius.circular(100), + boxShadow: [ + BoxShadow( + color: Colors.green.withOpacity(0.3), + blurRadius: 15, + offset: Offset(0, 5), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Obx(() => Container( + padding: EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + controller.isBreakTime.value + ? 'Break Time' + : controller.selectedLabel.value, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + )), + SizedBox(width: 10), + // Add interval counter + Obx(() => controller.isRunning.value || + controller.currentInterval.value > 0 + ? Container( + padding: EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + controller.progressText, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ) + : SizedBox()) + ], + ), + SizedBox(height: 20), + // Timer Display + Obx(() => Text( + controller.formattedTime, + style: TextStyle( + fontSize: 70, + fontWeight: FontWeight.bold, + color: Colors.white, + letterSpacing: 2, + shadows: [ + Shadow( + color: Colors.black.withOpacity(0.3), + offset: Offset(0, 3), + blurRadius: 5, + ), + ], + ), + )), + SizedBox(height: 30), + // Start/Give Up Button + Obx(() => GestureDetector( + onTap: () { + controller.isRunning.value + ? controller.stopTimer() + : controller.startTimer(); + }, + child: Container( + width: 180, + height: 50, + decoration: BoxDecoration( + color: controller.isRunning.value + ? Colors.red.withOpacity(0.3) + : Colors.white.withOpacity(0.3), + borderRadius: BorderRadius.circular(30), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Center( + child: Text( + controller.isRunning.value + ? "Give Up" + : "Start", + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + )), + ], + ), + ), + ), + ), + // Time Selection Panel + Container( + margin: EdgeInsets.symmetric(vertical: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildTimeOption("30:00", 30), + _buildTimeOption("45:00", 45), + _buildTimeOption("60:00", 60), + ], + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildTimeOption(String time, int minutes) { + return Obx(() => GestureDetector( + onTap: () { + if (!controller.isRunning.value) { + controller.setWorkTime(minutes); + } + }, + child: Container( + width: 80, + padding: EdgeInsets.symmetric(vertical: 12), + margin: EdgeInsets.symmetric(horizontal: 5), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: controller.selectedWorkTime.value == minutes + ? [kprimaryColor, kprimaryColor.withOpacity(0.5)] + : [Colors.green.shade700, Colors.green.shade900], + ), + borderRadius: BorderRadius.circular(15), + boxShadow: controller.selectedWorkTime.value == minutes + ? [ + BoxShadow( + color: Colors.green.withOpacity(0.3), + blurRadius: 10, + offset: Offset(0, 3), + ), + ] + : [], + ), + child: Center( + child: Text( + time, + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + )); + } + + void _showSettingsSheet(BuildContext context) { + Get.bottomSheet( + SingleChildScrollView( + child: Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 40, + height: 5, + margin: EdgeInsets.only(bottom: 20), + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(5), + ), + ), + ), + Row( + children: [ + Icon(Icons.settings, color: Colors.green, size: 24), + SizedBox(width: 10), + Text( + "Settings", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.black), + ), + ], + ), + SizedBox(height: 20), + Row(children: [ + Icon(Icons.label, color: Colors.amber), + SizedBox(width: 10), + Text("Labels", + style: TextStyle(fontSize: 16, + fontWeight: FontWeight.bold, color: Colors.black)), + ]), + + SizedBox(height: 10), + SizedBox( + height: 50, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: controller.labelOptions.length, + itemBuilder: (context, index) { + final label = controller.labelOptions[index]; + return Obx(() => GestureDetector( + onTap: label == "Create" + ? () => _showAddLabelDialog(context) + : () => controller.setLabel(label), + + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + margin: EdgeInsets.only(right: 10), + decoration: BoxDecoration( + color: controller.selectedLabel.value == label + ? kprimaryColor + : Colors.grey.shade200, + borderRadius: BorderRadius.circular(20), + boxShadow: controller.selectedLabel.value == label + ? [ + BoxShadow( + color: Colors.green.withOpacity(0.3), + blurRadius: 5, + offset: Offset(0, 2), + ), + ] + : [], + ), + child: Text( + label, + style: TextStyle( + color: controller.selectedLabel.value == label + ? Colors.white + : Colors.black, + fontWeight: FontWeight.w500, + ), + ), + ), + )); + }, + ), + ), + SizedBox(height: 30), + + // Number of Intervals Slider + Row( + children: [ + Icon(Icons.repeat, color: Colors.purple), + SizedBox(width: 10), + Text("Number of Intervals", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Colors.black)), + ], + ), + SizedBox(height: 5), + Container( + padding: EdgeInsets.all(15), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(15), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Intervals", + style: TextStyle(color: Colors.grey.shade600)), + Obx(() => Container( + padding: EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.purple.withOpacity(0.2), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + "${controller.selectedIntervals.value} intervals", + style: TextStyle( + color: Colors.purple.shade700, + fontWeight: FontWeight.bold, + ), + ), + )), + ], + ), + SizedBox(height: 10), + Obx(() => SliderTheme( + data: SliderThemeData( + trackHeight: 4, + activeTrackColor: Colors.purple, + inactiveTrackColor: Colors.grey.shade300, + thumbColor: Colors.white, + thumbShape: RoundSliderThumbShape( + enabledThumbRadius: 8, + elevation: 4, + ), + overlayColor: Colors.purple.withOpacity(0.2), + overlayShape: + RoundSliderOverlayShape(overlayRadius: 16), + ), + child: Slider( + min: 1, + max: 10, + divisions: 9, + value: + controller.selectedIntervals.value.toDouble(), + onChanged: (value) { + if (!controller.isRunning.value) { + controller.setIntervals(value.toInt()); + } + }, + ), + )), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("1", + style: TextStyle( + fontSize: 12, color: Colors.grey.shade600)), + Text("10", + style: TextStyle( + fontSize: 12, color: Colors.grey.shade600)), + ], + ), + ], + ), + ), + + SizedBox(height: 20), + + // Work Time Slider + Row( + children: [ + Icon(Icons.access_time, color: Colors.green), + SizedBox(width: 10), + Text("Work Time", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Colors.black)), + ], + ), + SizedBox(height: 5), + Container( + padding: EdgeInsets.all(15), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(15), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Duration", + style: TextStyle(color: Colors.grey.shade600)), + Obx(() => Container( + padding: EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.green.withOpacity(0.2), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + "${controller.selectedWorkTime.value} minutes", + style: TextStyle( + color: Colors.green.shade700, + fontWeight: FontWeight.bold, + ), + ), + )), + ], + ), + SizedBox(height: 10), + Obx(() => SliderTheme( + data: SliderThemeData( + trackHeight: 4, + activeTrackColor: Colors.green, + inactiveTrackColor: Colors.grey.shade300, + thumbColor: Colors.white, + thumbShape: RoundSliderThumbShape( + enabledThumbRadius: 8, + elevation: 4, + ), + overlayColor: Colors.green.withOpacity(0.2), + overlayShape: + RoundSliderOverlayShape(overlayRadius: 16), + ), + child: Slider( + min: 1, + max: 120, + divisions: 119, + value: controller.selectedWorkTime.value.toDouble(), + onChanged: (value) { + if (!controller.isRunning.value) { + controller.setWorkTime(value.toInt()); + } + }, + ), + )), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("1 min", + style: TextStyle( + fontSize: 12, color: Colors.grey.shade600)), + Text("120 min", + style: TextStyle( + fontSize: 12, color: Colors.grey.shade600)), + ], + ), + ], + ), + ), + + SizedBox(height: 20), + + // Improved Break Time Slider + Row( + children: [ + Icon(Icons.coffee, color: Colors.orange), + SizedBox(width: 10), + Text("Break Time", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 16 , color: Colors.black)), + ], + ), + SizedBox(height: 5), + Container( + padding: EdgeInsets.all(15), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(15), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Duration", + style: TextStyle(color: Colors.grey.shade600)), + Obx(() => Container( + padding: EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.orange.withOpacity(0.2), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + "${controller.selectedBreakTime.value} minutes", + style: TextStyle( + color: Colors.orange.shade700, + fontWeight: FontWeight.bold, + ), + ), + )), + ], + ), + SizedBox(height: 10), + Obx(() => SliderTheme( + data: SliderThemeData( + trackHeight: 4, + activeTrackColor: Colors.orange, + inactiveTrackColor: Colors.grey.shade300, + thumbColor: Colors.white, + thumbShape: RoundSliderThumbShape( + enabledThumbRadius: 8, + elevation: 4, + ), + overlayColor: Colors.orange.withOpacity(0.2), + overlayShape: + RoundSliderOverlayShape(overlayRadius: 16), + ), + child: Slider( + min: 1, + max: 30, + divisions: 29, + value: + controller.selectedBreakTime.value.toDouble(), + onChanged: (value) { + controller.setBreakTime(value.toInt()); + }, + ), + )), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("1 min", + style: TextStyle( + fontSize: 12, color: Colors.grey.shade600)), + Text("30 min", + style: TextStyle( + fontSize: 12, color: Colors.grey.shade600)), + ], + ), + ], + ), + ), + + SizedBox(height: 30), + Center( + child: GestureDetector( + onTap: () => Get.back(), + child: Container( + width: 120, + padding: EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: kprimaryColor, + borderRadius: BorderRadius.circular(30), + ), + child: Center( + child: Text( + "Save", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + SizedBox(height: 20), + ], + ), + ), + ), + ); + } + + void _showAddLabelDialog(BuildContext context) { + Get.dialog( + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: Container( + padding: EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Create Custom Label", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + SizedBox(height: 20), + TextField( + controller: controller.newLabelController, + decoration: InputDecoration( + hintText: "Enter label name", + hintStyle: TextStyle(color: Colors.white), + filled: true, + fillColor: Colors.grey.shade400, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(15), + borderSide: BorderSide.none, + ), + contentPadding: + EdgeInsets.symmetric(horizontal: 15, vertical: 15), + ), + ), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + "Cancel", + style: TextStyle(color: Colors.grey), + ), + ), + SizedBox(width: 10), + ElevatedButton( + onPressed: () { + controller + .addCustomLabel(controller.newLabelController.text); + Get.back(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + padding: + EdgeInsets.symmetric(horizontal: 20, vertical: 12), + ), + child: Text( + "Add", + style: TextStyle(color: Colors.white), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/app/modules/timer/views/timer_view.dart b/lib/app/modules/timer/views/timer_view.dart index 463a9732..9c2f8239 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/addOrUpdateAlarm/controllers/input_time_controller.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/pomodoro_timer_view.dart'; import 'package:ultimate_alarm_clock/app/modules/timer/views/timer_animation.dart'; import 'package:ultimate_alarm_clock/app/utils/constants.dart'; import 'package:ultimate_alarm_clock/app/utils/end_drawer.dart'; @@ -50,6 +51,26 @@ class TimerView extends GetView { ), ), actions: [ + LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Obx( + () => IconButton( + onPressed: () { + Utils.hapticFeedback(); + Get.to(PomodoroPage()); + }, + icon: const Icon( + Icons.hourglass_top, + color: kprimaryColor, + ), + tooltip: 'Pomodoro Timer', + color: themeController.primaryTextColor.value + .withOpacity(0.75), + iconSize: 27, + ), + ); + }, + ), LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Obx( @@ -68,6 +89,7 @@ class TimerView extends GetView { ); }, ), + ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index ddf51bbd..43229fdd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,6 +57,8 @@ dependencies: intl_phone_number_input: ^0.7.4 firebase_messaging: ^14.7.19 shared_preferences: ^2.2.3 + confetti: ^0.8.0 + flutter_local_notifications: ^18.0.1 dev_dependencies: flutter_lints: ^4.0.0