User:Alessia/pomodoro timer: Difference between revisions
mNo edit summary Β |
|||
Line 726: | Line 726: | ||
https://cuckoo.team/ <br> | https://cuckoo.team/ <br> | ||
Productivity timer for remote teams, shareable link<br> | Productivity timer for remote teams, shareable link<br> | ||
https://github.com/meribold/muccadoro | |||
=Android studio, APK= | =Android studio, APK= |
Latest revision as of 16:31, 29 January 2024
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
π
I want to make my own pomodoro timer app. I'll study everything needed to understand how a timer works before, through javascript and python, then get into the app making with the aesthetic I want.
Pomodoro Timer with JavaScript
I am following this tutorial to get a little bit used to javascript as well:
https://freshman.tech/pomodoro-timer/
html, css, javascript.
Needed: Git, Node.js, npm
basics
What is Git in simple terms?
Git, a version control system for software development, ensures developers can revert back to specific past versions of their work to see records of work completed and allows for streamlined collaboration by permitting changes made by multiple people to be merged into one location.
What is a version control system (VCS)?
a time machine for your files, an assistant tool to organise your work, collaborate with others, lets you travel back in time if things go wrong?
a Version control systems is a software tool that help software teams manage changes to source code over time. As development environments have accelerated, version control systems help software teams work faster and smarter.
It records changes made to a code over time in a database called repository, there you can look at the project history. Without a version control system it could be mandatory to constantly store copied of the entire projects in various folders, manually merging...
Why should I use Git with my coding projects?
It's sort of a cloud for code. You don't have to necessarily use GIT. You might use Mercurial or Subversion as SCM/VCS...
In this case, Git is useful to clone the repository(?)
what is Node.js?
multitasking assistant, allows you to use javascript on the server side as well, not just the front-end part, the web browser. Scalability?
Node.js is a runtime environment for executing JavaScript on the server side, while Git is a version control system for tracking and managing changes in your codebase. Both tools play important roles in modern software development, with Node.js handling the server-side logic and Git managing version control and collaboration. Using them together can contribute to a more efficient and organized development process.
what is a nmp?
NPM is like an online store for JavaScript tools and libraries. These tools can be anything from small utility functions to entire frameworks that help you build web applications. When you're building a project using Node.js, you often rely on existing code written by others. NPM helps you easily find and install these pieces of code (packages) into your project. These packages are the dependencies your project needs to run.
difference between Git and npm?
Git is for tracking changes in your code, while NPM is for adding ready-made code components to your project.
what is Github?
It's the social network for Git. It hosts Git repositories online.
what does it mean markup?
In the web development contest "markup" refers to the practice of adding special symbols or codes to a document to provide information about the structure, formatting, or presentation of the content. These symbols or codes are often called "tags." The most common form of markup is HTML (Hypertext Markup Language), which is used to structure content on the web. HTML uses tags to define elements such as headings, paragraphs, links, images, and more.
Markup is a way to add structure and meaning to content, allowing computers or other systems to interpret and display it in a specific way.
what is a browser-sync dependency?
No clue still.
clone the repository to your filesystem
git clone https://github.com/Freshman-tech/pomodoro-starter-files.git
cd into it in your terminal
cd pomodoro-starter-files
run the following command to install the browser-sync dependency which is used to automatically refresh the browser once a file is changed
npm install
start the app on http://localhost:3000 using:
npm start
getting into the browser
Then interface of Freshman timer is quite simple. a progress bar + 3 buttons for the 3 modes of the application.Then the countdown timer and the start button.
As a tradition pomodoro session is 25 mins + short break 5 mins/ long break 15 mins after 4 consecutive session.
To create a timer variable follow:
main.js const timer = { pomodoro: 25, shortBreak: 5, longBreak: 15, longBreakInterval: 4, };
what is const? a keyword to declare a costant variable, cannot be changed.
so this is to get the variables
We need to update the countdown with the right amount of minutes and seconds. We need to create an event listener that detects a click on the buttons and a function to switch the mode of the timer appropriately.
what the hell is an event listener?
a function in JavaScript that waits for an event to occur then responds to it
main.js const modeButtons = document.querySelector('#js-mode-buttons'); modeButtons.addEventListener('click', handleMode); function handleMode(event) { const { mode } = event.target.dataset; if (!mode) return; switchMode(mode); }
document refers to the entire HTML document
On this previous part we use event delegation to detect a click on any mode buttons.
what is an event delegation? technique where instead of attaching an event listener to each individual element, you attach a single event listener to a common ancestor (like a parent element) of multiple elements. This single event listener then handles events for all the elements inside that ancestor.
what's an ancestor in javascript? I'll go with a logical guess
The modeButtons variable points to the containing element and once a click is detected on the element, the handleMode() function is invoked. Within the handleMode() function, the value of the data-mode attribute is retrieved from the target element. If this attribute does not exist, it means that the target element was not one of the buttons and the function exits. Otherwise, a switchMode() function is invoked with the value of the data-mode attribute as its only argument.
Then create the switchMode() function just above handleMode()
main.js function switchMode(mode) { timer.mode = mode; timer.remainingTime = { total: timer[mode] * 60, minutes: timer[mode], seconds: 0, }; document .querySelectorAll('button[data-mode]') .forEach(e => e.classList.remove('active')); document.querySelector(`[data-mode="${mode}"]`).classList.add('active'); document.body.style.backgroundColor = `var(--${mode})`; updateClock(); }
switch statement: evaluates an expression. The value of the expression is then compared with the values of each case in the structure. If there is a match, the associated block of code is executed.
switchMode is a function that takes a 'mode' as an input.
switchMode usage
function switchMode(mode) { switch (mode) { case 'A': // Do something for mode A break; case 'B': // Do something for mode B break; default: // Do something if mode doesn't match A or B } }
switchMode is a function that takes a mode as an input.
Inside the function, there's a switch statement that checks the value of mode.
If mode is 'A', it performs the actions specified for mode A.
If mode is 'B', it performs the actions specified for mode B.
If mode doesn't match either 'A' or 'B', it performs actions specified in the default case. SwitchMode is like a function that does different things based on the value of mode you give it. It's a way to handle different cases or scenarios in the code.
In this case the switchMode() adds two properties to the "timer" object. First a "mode" property to current mode which could be pomodoro, shortBreak or longBreak.
Then remainingTime set on the timer. remainingTime contains three properties: total (number of seconds remaining), set to the number of minutes of the current mode multiplied by 60.
So if mode is shortBreak, total will be se to 300 (5*60).
minutes is the number of minutes for the mode (pomodoro-25 min)
seconds is always set to zero at the start of a session
querySelector is a method provided by the document object that allows you to find and select HTML elements on the page
querySelector('.example') - the example is the CSS selector, it's looking for an element with that class name
CSS selector: selectors allow you to target specific elements in an HTML document so that you can apply styling or perform other operations on them (element, class, ID, attribute, descendant, child)
let's move to css
styles.css
:root { --pomodoro: hsl(223, 25%, 40%); --shortBreak: hsl(48, 23%, 40%); --longBreak: hsl(105, 16%, 40%); }
The updateClock() function extracts the value of the minutes and seconds properties on the remainingTime object and adds eros where necessary so that the number always has a width of two. For example, 8 seconds will become 08 seconds, but 12 minutes will be left as 12 minutes.
Let's start the timer next step: add the ability to start the timer and countdown to zero, declare an interval variable under timer (just after const timer on the first line)
main.js let interval;
above updateClock() add startTimer():
main.js function startTimer() { let { total } = timer.remainingTime; const endTime = Date.parse(new Date()) + total * 1000; interval = setInterval(function() { timer.remainingTime = getRemainingTime(endTime); updateClock(); total = timer.remainingTime.total; if (total <= 0) { clearInterval(interval); } }, 1000); }
Before being able to start the timer we need to get the exact time in the future when the timer will end, achieved by retrieving the timestamp of the current moment? (Date.parse(new Date())) which is in milliseconds, 1 second = 1000ms. This value is then stored in the endTime variable.
The interval variable is set to the setInterval() method which executes the callback function every 1000 milliseconds (1 second). This callback function references a getRemainingTime() function which should be created above startTimer
callback(data), callback funtion is a way to handle asynchtonous operations
main.js function getRemainingTime(endTime) { const currentTime = Date.parse(new Date()); const difference = endTime - currentTime; const total = Number.parseInt(difference / 1000, 10); const minutes = Number.parseInt((total / 60) % 60, 10); const seconds = Number.parseInt(total % 60, 10); return { total, minutes, seconds, }; }
The function above takes a timestamp argument and finds the difference between the current time and the end time in milliseconds.
This value is stored in the difference variable and used to compute the total number of seconds left by dividing by 1000. The result is converted to an integer in base 10 through the Number.parseInt() method and stored in the total variable.
The minutes variable contains the number of whole minutes left (if any) and seconds is the number of seconds left after whole minutes have been accounted for. For example, if total is 230 seconds, minutes will equal 3 and seconds will be 50.
-you should learn a little bit more about this part-
calling the startTimer() is needed once the start button is clicked. Add this above the modeButtons:
const mainButton = document.getElementById('js-btn'); mainButton.addEventListener('click', () => { const { action } = mainButton.dataset; if (action === 'start') { startTimer(); } });
We need to make a small modification to startTimer() so that the button text changes to βstopβ and the button becomes depressed like a hardware button (add the mainButton part)
function startTimer() { let { total } = timer.remainingTime; const endTime = Date.parse(new Date()) + total * 1000; mainButton.dataset.action = 'stop'; mainButton.textContent = 'stop'; mainButton.classList.add('active'); interval = setInterval(function() { timer.remainingTime = getRemainingTime(endTime); updateClock(); total = timer.remainingTime.total; if (total <= 0) { clearInterval(interval); } }, 1000); }
A final thing to do in this section is to ensure that the mode and remainingTime properties are set on the timer object on page load. To do so, we can execute the switchMode() property once the DOMContentLoaded event is fired. This ensures that the default mode for the timer is pomodoro and the contents of timer.remainingTime is set to the appropriate values for a pomodoro session. If the above snippet is not present, the program will crash with a TypeError if startTimer() is invoked because timer.remainingTime will not exist and weβre trying to access the value of the total property in that object on the first line of the function.
(I didn't understand this part). Add this at the end of main.js
document.addEventListener('DOMContentLoaded', () => {
switchMode('pomodoro');
});
to test the app set timer.pomodoro to 1 temporaroly and click the start button. Refresh before continuing.
Let's stop the timer
to stop the timer when the stop button is clicked. It's the value of the data-action attribute on the button that allows to determine whether to start or stop the timer.
Then add a new stopTimer() below startTimer()
function stopTimer() { clearInterval(interval); mainButton.dataset.action = 'start'; mainButton.textContent = 'start'; mainButton.classList.remove('active'); }
clearInterval() cause the setInterval() triggered in startTimer() to be cancelled so that the countdown is paused.Then the value of the button's data-action attribute is changed to "start" and it is returned to its original form by removing the active class.
To execute stopTimer() when data-action is set to βstopβ, modify the mainButton like this:
mainButton.addEventListener('click', () => { const { action } = mainButton.dataset; if (action === 'start') { startTimer(); } else { stopTimer(); } });
Also needed to stop the timer when the mode is changed by clicking any of the three buttons above the countdown:
function handleMode(event) { const { mode } = event.target.dataset; if (!mode) return; switchMode(mode); stopTimer(); }
How to automatically starting session
The pomodoro sessions and break sessions need to connect to each other, starting one after another. And after 4 consecutive pomodoro sessions a long break should be triggered.
Add a new sessions property to the timer object
const timer = { pomodoro: 25, shortBreak: 5, longBreak: 15, longBreakInterval: 4, sessions: 0, };
modify the startTimer() function so that the sessions property is incremented at the start of a pomodoro session
function startTimer() { let { total } = timer.remainingTime; const endTime = Date.parse(new Date()) + total * 1000; if (timer.mode === 'pomodoro') timer.sessions++; mainButton.dataset.action = 'stop'; mainButton.textContent = 'stop'; mainButton.classList.add('active'); interval = setInterval(function() { timer.remainingTime = getRemainingTime(endTime); updateClock(); total = timer.remainingTime.total; if (total <= 0) { clearInterval(interval); } }, 1000); }
if (timer.mode === 'pomodoro') timer.sessions++; this line checks if the mode is pomodoro and increments the timer.session by 1.
Then for the auto switch to the next session on completion of the current modify the startTimer() function:
function startTimer() { let { total } = timer.remainingTime; const endTime = Date.parse(new Date()) + total * 1000; if (timer.mode === 'pomodoro') timer.sessions++; mainButton.dataset.action = 'stop'; mainButton.textContent = 'stop'; mainButton.classList.add('active'); interval = setInterval(function() { timer.remainingTime = getRemainingTime(endTime); updateClock(); total = timer.remainingTime.total; if (total <= 0) { clearInterval(interval); switch (timer.mode) { case 'pomodoro': if (timer.sessions % timer.longBreakInterval === 0) { switchMode('longBreak'); } else { switchMode('shortBreak'); } break; default: switchMode('pomodoro'); } startTimer(); } }, 1000); }
Once the countdown reaches 0, the switch statement present above causes the app to switch to a new break session or pomodoro session depending on the value of timer.mode. The if statement checks if timer.sessions is divisible by timer.longBreakInterval. Otherwise a short break session is triggered. The default case is executed if a break session is ending which causes a new pomodoro session to begin.
startTimer() is executed again causing the countdown to start again as before. Itβs possible to execute a function from within itself!
To test it set pomodoro, shortBreak and longBreak to 1 temporarily, change it again after!
Update the progress bar
The progress bar is represented by the <progress> element which needs a max and a value attribute
index.html!!!
<progress id="js-progress" value="0"></progress>
By default, the value attribute is set to 0 indicating that no progress has been made but the max attribute is left out. This attribute is essential to determine what represents 100% completion of a task and it must be greater than zero. The max attribute is set on the <progress> element in switchMode()
main.js function switchMode(mode) { timer.mode = mode; timer.remainingTime = { total: timer[mode] * 60, minutes: timer[mode], seconds: 0, }; document .querySelectorAll('button[data-mode]') .forEach(e => e.classList.remove('active')); document.querySelector(`[data-mode="${mode}"]`).classList.add('active'); document.body.style.backgroundColor = `var(--${mode})`; document .getElementById('js-progress') .setAttribute('max', timer.remainingTime.total); updateClock(); }
change updateClock() function
main.js function updateClock() { const { remainingTime } = timer; const minutes = `${remainingTime.minutes}`.padStart(2, '0'); const seconds = `${remainingTime.seconds}`.padStart(2, '0'); const min = document.getElementById('js-minutes'); const sec = document.getElementById('js-seconds'); min.textContent = minutes; sec.textContent = seconds; const progress = document.getElementById('js-progress'); progress.value = timer[timer.mode] * 60 - timer.remainingTime.total; }
Each time updateClock() is invoked, the value attribute of the <progress> element is updated to the result of the remaining amount of seconds minus from the total number of seconds in the session and this causes the progress bar to update according to it.
Freshman advise to reflect the countdown on the page title as well, for practical reason, so that users don't switch tabs.
Modify the updateClock() function
main.js function updateClock() { const { remainingTime } = timer; const minutes = `${remainingTime.minutes}`.padStart(2, '0'); const seconds = `${remainingTime.seconds}`.padStart(2, '0'); const min = document.getElementById('js-minutes'); const sec = document.getElementById('js-seconds'); const time = `${minutes}:${seconds}`; min.textContent = minutes; sec.textContent = seconds; const text = timer.mode === 'pomodoro' ? 'Get back to work!' : 'Take a break!'; document.title = `${minutes}:${seconds} β ${text}`; const progress = document.getElementById('js-progress'); progress.value = timer[timer.mode] * 60 - timer.remainingTime.total; }
Wow! Amazing, easy, just put the document.title there and it's done!
The ternary operator is used to modify the title depending on if the current mode is set to pomodoro or not.
ternary operator: a concise way to write a conditional (if-else) statement. It's often used when you want to assign a value to a variable based on a condition. -didn't understand fully-
Playing sounds
To play a sound when the timer is started, stopped, and during transitions between sessions. Still in main.js. Needed to create a new audio object using a file that is inside the project folder (iin this case button-sound.mp3).
main.js const buttonSound = new Audio('button-sound.mp3'); const mainButton = document.getElementById('js-btn'); mainButton.addEventListener('click', () => { buttonSound.play(); const { action } = mainButton.dataset; if (action === 'start') { startTimer(); } else { stopTimer(); } });
To play a sound between transitions -> in the index.html file, three audio elements with a data-sound corresponding to the three modes available.
index.html <audio src="backtowork.mp3" data-sound="pomodoro"></audio> <audio src="break.mp3" data-sound="shortBreak"></audio> <audio src="break.mp3" data-sound="longBreak"></audio>
add this line in main.js below switch in the startTimer() function
document.querySelector(`[data-sound="${timer.mode}"]`).play();
Display notifications to users
First ask permission when the page loads, then if the request is granted display notification (or not)
main.js document.addEventListener('DOMContentLoaded', () => { // Let's check if the browser supports notifications if ('Notification' in window) { // If notification permissions have neither been granted or denied if (Notification.permission !== 'granted' && Notification.permission !== 'denied') { // ask the user for permission Notification.requestPermission().then(function(permission) { // If permission is granted if (permission === 'granted') { // Create a new notification new Notification( 'Awesome! You will be notified at the start of each session' ); } }); } } switchMode('pomodoro'); });
Next, add these lines below the switch block in startTimer to display a notification
if (Notification.permission === 'granted') { const text = timer.mode === 'pomodoro' ? 'Get back to work!' : 'Take a break!'; new Notification(text); }
ΛβΊβ§βΛβ‘Λββ§βΊΛ LOVELY
main.js FINISHED https://gist.github.com/ayoisaiah/4bee72b8427140d5417a676325647c6d
Pomodoro Timer with Python
basics
What is Pygame?
a toolbox, a library, for game development in Python
what is a Python GUI?
(Graphical User Interface), an interface that is drawn on the screen for the user to interact with. API (Application Programming Interface)
what does it mean a boilerplate code/language?
Boilerplate language is standardized text that can be used repeatedly in similar documents without major changes
what is the function of a debugger?
A debugger is a software tool that can help the software development process by identifying coding errors at various stages of the operating system or application development
pip install pygame
inspired by https://pomofocus.io/
βββPygame eventsβββ ββare generated by various user interactions or system events, such as key presses, mouse movements, window resizing, and custom events.ββ
import pygame import sys from button import Button
pygame for game development, sys for system-related functionality, and a custom Button class from a file named button.py.
sys module: to handle exiting or controlling other settings ex. pygame.quit()
sys.exit() (function is often used to exit the program, and it's commonly used in conjunction
with pygame.quit() to gracefully exit a Pygame application)
pygame.init()
Initialize the Pygame library.
WIDTH, HEIGHT = 900, 600 SCREEN = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Pomodoro Timer")
setting the window dimensions (width and height), screen to the specified size for the pygame window, and the window caption
CLOCK = pygame.time.Clock()
create a clock object for the frame rate
BACKDROP = pygame.image.load("assets/backdrop.png") WHITE_BUTTON = pygame.image.load("assets/button.png")
backdrop to load images for the background, white button to add a white button, indeed, from the file paths
FONT = pygame.font.Font("assets/moms-typewriter.ttf", 120) timer_text = FONT.render("25:00", True, "white") timer_text_rect = timer_text.get_rect(center=(WIDTH/2, HEIGHT/2-25))
Font used, a custom one
font pygame module for loading and rendering fonts provides no way to directly draw text on an existing Surface: instead you must use Font. render() to create an image (Surface) of the text, then blit this image onto another Surface
timer_text_rect is a variable that represent the rectangle that include the rendered text
surface (timer_text). get_rect() is provided by the class ββSurfaceββ of Pygame
START_STOP_BUTTON = Button(WHITE_BUTTON, (WIDTH/2, HEIGHT/2+100), 170, 60, "START", pygame.font.Font("assets/moms-typewriter.ttf", 20), "#c97676", "#9ab034") POMODORO_BUTTON = Button(None, (WIDTH/2-150, HEIGHT/2-140), 120, 30, "Pomodoro", pygame.font.Font("assets/moms-typewriter.ttf", 20), "#FFFFFF", "#9ab034") SHORT_BREAK_BUTTON = Button(None, (WIDTH/2, HEIGHT/2-140), 120, 30, "Short Break", pygame.font.Font("assets/moms-typewriter.ttf", 20), "#FFFFFF", "#9ab034") LONG_BREAK_BUTTON = Button(None, (WIDTH/2+150, HEIGHT/2-140), 120, 30, "Long Break", pygame.font.Font("assets/moms-typewriter.ttf", 20), "#FFFFFF", "#9ab034")
these lines are to create the instances of the Button class for the starting stopping button and the other three buttons for the breaks (+ all properties like position, size, label, font and color). There are two color codes because the first is the color of the button while the second is the color when you hover on it with the mouse
POMODORO_LENGTH = 1500 # 1500 secs / 25 mins SHORT_BREAK_LENGTH = 300 # 300 secs / 5 mins LONG_BREAK_LENGTH = 900 # 900 secs / 15 mins current_seconds = POMODORO_LENGTH pygame.time.set_timer(pygame.USEREVENT, 1000) started = False
defining here constants for the three different timers in seconds, current_seconds to set the initial timer value, current_seconds to keep track of the remaining time on the timer while it counts down
pygame.time.set_timer(pygame.USEREVENT, 1000)
this sets up a recurring event using pygame mechanism. Pygame supports the creation of user-defined events, and pygame.USEREVENT is a constant representing a custom event type. In this case, the event is set to occur every 1000 milliseconds (1 second)
pygame.time.set_timer(eventid, milliseconds)
function to create a repeating timer event
started = False
started is a boolean flag that indicates if the timer is currently running or not. As the timer is not initially running it is set to False. This flag is used later in the main loop to determine whether to decrement the timer or not based on the user interaction.
while True: for event in pygame.event.get():
start an infinite loop.
pygame.event.get():
for loop that repeats over each event in the list of pygame.event.get()
The loop allows the program to handle each event individually and take specific actions based on the type of event
if event.type == pygame.QUIT: pygame.quit() sys.exit()
the first conditional statement checks if the current event in the loop is a quit event (ex. user closes the program). pygame.quit() is used to clean up resources and shut down the environment of pygame when the user quits the program. sys.exit() - SystemExit.
if event.type == pygame.USEREVENT and started: current_seconds -= 1
current_seconds represents the remained time on the timer in seconds, by decrementing one the program is simulating the countdown.
SCREEN.fill("#ba4949") SCREEN.blit(BACKDROP, BACKDROP.get_rect(center=(WIDTH/2, HEIGHT/2)))
the screen get filled with a red color and draw the background image
START_STOP_BUTTON.update(SCREEN) START_STOP_BUTTON.change_color(pygame.mouse.get_pos()) POMODORO_BUTTON.update(SCREEN) POMODORO_BUTTON.change_color(pygame.mouse.get_pos()) SHORT_BREAK_BUTTON.update(SCREEN) SHORT_BREAK_BUTTON.change_color(pygame.mouse.get_pos()) LONG_BREAK_BUTTON.update(SCREEN) LONG_BREAK_BUTTON.change_color(pygame.mouse.get_pos())
update and draw each button on the screen, it handle color changes when the mouse hovers over them
if current_seconds >= 0: display_seconds = current_seconds % 60 display_minutes = int(current_seconds / 60) % 60 timer_text = FONT.render(f"{display_minutes:02}:{display_seconds:02}", True, "white") SCREEN.blit(timer_text, timer_text_rect)
updating of the timer. current_second (the other one) is -=1, that makes the timer goes negative. To if current_seconds %60 (% modulus operator) convert to a time format that we can understand. :02 add padding, la parte dei secondi sarΓ di due cifre, anche quando sarΓ minore di 10 (09, 08, 07β¦).
I still didnβt understand the utility of button.py as a file there.
different types of pomodoro timer browser app
same structure
https://pomofocus.io/
https://studywithme.io/aesthetic-pomodoro-timer/
https://pomodoro-tracker.com/
Light dark system and auto colour theme, time and day format, categories for the savings of pomodoro sessions
https://pomodor.app/timer
https://www.toptal.com/project-managers/tomato-timer
it gives the opportunity to the user to customise some settings (timer indication in the title? Browser notification? Auto start of pomodoros and breaks? Pomodoro goal per day, sound selection, volume and customisation of the timers in minutes + sound test, and reset the timer
https://pomodorotimer.online/
Possibility to add tasks to focus on. Downloadable native app
https://lifeat.io/feature/pomodoro
Timer + calendar, all kind of customisation, reflections, personal and group pomodoro
https://nesto.cc/
Dynamic backgrounds, support for mobile and desktop devices
https://www.wonderspace.app/
Twitch app, lo-fi music
https://pomodorokitty.com/
Thereβs a cat here, simple
https://cuckoo.team/
Productivity timer for remote teams, shareable link
https://github.com/meribold/muccadoro
Android studio, APK
Java parser
engine's SDK into your Android project, software development kit. An SDK is a set of tools to build software for a particular platform.
some other stuff, timer
To set the timer duration (seconds)
$timerDuration = 30
Display then a message when the timer starts
Write-Host "Timer running for $timerDuration seconds..."
Start the timer countdown. -NoNewline keeps the countdown on the same line. 'r' is an escape sequence, a carriage return that allows you to overwrite the existing countdown message with the updated one, creating the effect of a dynamic countdown. $i is the loop counter variable. Start-Sleep pause the script for 1 second so the delay make the countdown visible in the console
for ($i = $timerDuration; $i -gt 0; $i--) { Write-Host -NoNewline "Time remaining: $i seconds... " Start-Sleep -Seconds 1 Write-Host -NoNewline "`r" }
Display a message when the timer has finished
Write-Host "The end!"
Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ Κ β’α΄₯β’Κ