Mac build Support with major bug fixes

This commit is contained in:
Anoy Roy Chowdhury
2025-07-23 01:40:59 +05:30
parent 12db63d90f
commit 74251e2e23
23 changed files with 5521 additions and 60 deletions
+8
View File
@@ -106,6 +106,14 @@
}
}
.hide-scroll::-webkit-scrollbar {
display: none;
}
.hide-scroll {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 6px;
+4 -1
View File
@@ -154,7 +154,10 @@ export default function Home() {
}, [darkMode]);
const getDateString = (date) => {
return date.toISOString().split("T")[0];
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const getCurrentDayTasks = () => {
+1 -1
View File
@@ -178,7 +178,7 @@ export function TaskList({
};
return (
<div className="h-full overflow-y-auto overflow-x-hidden p-4 px-0">
<div className="h-full overflow-y-auto hide-scroll overflow-x-hidden p-4 px-0">
<AnimatePresence>
{/* Habits Section */}
{habitTasks.length > 0 && (
+224 -56
View File
@@ -45,10 +45,12 @@ export function TimerModal({
const [isOvertimeStarted, setIsOvertimeStarted] = useState(false);
const [isMuted, setIsMuted] = useState(false);
// Audio refs
const playingAudioRef = useRef(null);
const breakAudioRef = useRef(null);
const overtimeAudioRef = useRef(null);
// Audio context and buffer refs for true seamless looping
const audioContextRef = useRef(null);
const audioBuffersRef = useRef({});
const audioSourcesRef = useRef({});
const gainNodesRef = useRef({});
const isInitializedRef = useRef(false);
const presets = [
{ value: "5", label: "5 min", seconds: 5 * 60 },
@@ -57,58 +59,202 @@ export function TimerModal({
{ value: "50", label: "50 min", seconds: 50 * 60 },
];
// Initialize audio elements
// Initialize Web Audio API with aggressive looping
useEffect(() => {
playingAudioRef.current = new Audio("/music/playing.mp3");
breakAudioRef.current = new Audio("/music/break.mp3");
overtimeAudioRef.current = new Audio("/music/overtime.mp3");
const initAudio = async () => {
try {
// Create audio context
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioContextRef.current = new AudioContext();
// Set audio properties
[
playingAudioRef.current,
breakAudioRef.current,
overtimeAudioRef.current,
].forEach((audio) => {
audio.loop = true;
audio.volume = 0.3; // Set a reasonable default volume
});
const loadAudioBuffer = async (url, key) => {
try {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContextRef.current.decodeAudioData(
arrayBuffer
);
// Cleanup function
return () => {
[
playingAudioRef.current,
breakAudioRef.current,
overtimeAudioRef.current,
].forEach((audio) => {
if (audio) {
audio.pause();
audio.currentTime = 0;
// Trim silence from beginning and end
const trimmedBuffer = trimSilence(audioBuffer);
audioBuffersRef.current[key] = trimmedBuffer;
// Create gain node for this audio
gainNodesRef.current[key] = audioContextRef.current.createGain();
gainNodesRef.current[key].gain.value = 0.2;
gainNodesRef.current[key].connect(
audioContextRef.current.destination
);
console.log(
`Audio ${key} loaded: ${trimmedBuffer.duration.toFixed(3)}s`
);
} catch (error) {
console.error(`Failed to load audio ${key}:`, error);
// Fallback to HTML5 audio
createFallbackAudio(url, key);
}
};
// Load all audio files
await Promise.all([
loadAudioBuffer("/music/playing.mp3", "playing"),
loadAudioBuffer("/music/break.mp3", "break"),
loadAudioBuffer("/music/overtime.mp3", "overtime"),
]);
isInitializedRef.current = true;
console.log("Web Audio API initialized successfully");
} catch (error) {
console.error("Web Audio API initialization failed:", error);
initFallbackAudio();
}
};
// Trim silence from audio buffer
const trimSilence = (buffer) => {
const threshold = 0; // Silence threshold
const channelData = buffer.getChannelData(0);
// Find start of audio (first non-silent sample)
let start = 0;
for (let i = 0; i < channelData.length; i++) {
if (Math.abs(channelData[i]) > threshold) {
start = i;
break;
}
});
}
// Find end of audio (last non-silent sample)
let end = channelData.length - 1;
for (let i = channelData.length - 1; i >= 0; i--) {
if (Math.abs(channelData[i]) > threshold) {
end = i;
break;
}
}
// Create new buffer with trimmed audio
const trimmedLength = end - start + 1;
const trimmedBuffer = audioContextRef.current.createBuffer(
buffer.numberOfChannels,
trimmedLength,
buffer.sampleRate
);
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
const originalData = buffer.getChannelData(channel);
const trimmedData = trimmedBuffer.getChannelData(channel);
for (let i = 0; i < trimmedLength; i++) {
trimmedData[i] = originalData[start + i];
}
}
return trimmedBuffer;
};
// Fallback to HTML5 audio if Web Audio API fails
const createFallbackAudio = (url, key) => {
const audio = new Audio(url);
audio.loop = true;
audio.volume = 0.2;
audio.preload = "auto";
audioBuffersRef.current[key] = { audio, isFallback: true };
};
const initFallbackAudio = () => {
console.log("Using HTML5 Audio fallback");
createFallbackAudio("/music/playing.mp3", "playing");
createFallbackAudio("/music/break.mp3", "break");
createFallbackAudio("/music/overtime.mp3", "overtime");
isInitializedRef.current = true;
};
initAudio();
return () => {
// Cleanup
stopAllAudio();
if (
audioContextRef.current &&
audioContextRef.current.state !== "closed"
) {
audioContextRef.current.close();
}
};
}, []);
// Audio control function
const playAudio = (audioRef, shouldPlay = !isMuted) => {
if (shouldPlay && audioRef.current) {
// Stop all other audio first
stopAllAudio();
audioRef.current.currentTime = 0;
audioRef.current.play().catch((error) => {
console.log("Audio playback failed:", error);
});
// Play audio with aggressive seamless looping
const playAudio = (audioKey, shouldPlay = !isMuted) => {
if (
!shouldPlay ||
!isInitializedRef.current ||
!audioBuffersRef.current[audioKey]
)
return;
// Stop all other audio first
stopAllAudio();
const buffer = audioBuffersRef.current[audioKey];
// Handle fallback audio
if (buffer.isFallback) {
buffer.audio.currentTime = 0;
buffer.audio.play().catch(console.error);
return;
}
// Resume audio context if suspended
if (audioContextRef.current.state === "suspended") {
audioContextRef.current.resume();
}
// Create and start buffer source with perfect looping
const startSeamlessLoop = () => {
if (audioSourcesRef.current[audioKey]) {
audioSourcesRef.current[audioKey].stop();
}
const source = audioContextRef.current.createBufferSource();
source.buffer = buffer;
source.loop = true;
source.loopStart = 0;
source.loopEnd = buffer.duration;
// Connect to gain node
source.connect(gainNodesRef.current[audioKey]);
// Start immediately
source.start(0);
audioSourcesRef.current[audioKey] = source;
console.log(`Started seamless loop for ${audioKey}`);
};
startSeamlessLoop();
};
const stopAllAudio = () => {
[
playingAudioRef.current,
breakAudioRef.current,
overtimeAudioRef.current,
].forEach((audio) => {
if (audio) {
audio.pause();
audio.currentTime = 0;
Object.keys(audioSourcesRef.current).forEach((key) => {
const source = audioSourcesRef.current[key];
if (source) {
try {
source.stop();
source.disconnect();
} catch (error) {
// Source might already be stopped
}
delete audioSourcesRef.current[key];
}
});
// Stop fallback audio
Object.keys(audioBuffersRef.current).forEach((key) => {
const buffer = audioBuffersRef.current[key];
if (buffer && buffer.isFallback) {
buffer.audio.pause();
buffer.audio.currentTime = 0;
}
});
};
@@ -118,22 +264,46 @@ export function TimerModal({
setIsMuted(newMutedState);
if (newMutedState) {
// Muting - stop all audio
stopAllAudio();
} else {
// Unmuting - resume appropriate audio based on current state
// Resume appropriate audio
if (isRunning) {
if (timeLeft === 0 && isOvertimeStarted) {
playAudio(overtimeAudioRef);
playAudio("overtime");
} else if (isBreak) {
playAudio(breakAudioRef);
playAudio("break");
} else if (timeLeft > 0) {
playAudio(playingAudioRef);
playAudio("playing");
}
}
}
};
// Handle user interaction requirement for audio
const handleFirstUserInteraction = () => {
if (
audioContextRef.current &&
audioContextRef.current.state === "suspended"
) {
audioContextRef.current.resume();
}
};
// Add click listener for first user interaction
useEffect(() => {
document.addEventListener("click", handleFirstUserInteraction, {
once: true,
});
document.addEventListener("touchstart", handleFirstUserInteraction, {
once: true,
});
return () => {
document.removeEventListener("click", handleFirstUserInteraction);
document.removeEventListener("touchstart", handleFirstUserInteraction);
};
}, []);
// Main countdown timer effect
useEffect(() => {
if (isRunning && timeLeft > 0) {
@@ -147,10 +317,9 @@ export function TimerModal({
// Overtime counter effect
useEffect(() => {
if (isRunning && timeLeft === 0) {
// Start overtime audio when entering overtime mode
if (!isOvertimeStarted) {
setIsOvertimeStarted(true);
playAudio(overtimeAudioRef); // Uses default mute check
playAudio("overtime");
}
const overtimeInterval = setInterval(() => {
@@ -193,11 +362,11 @@ export function TimerModal({
useEffect(() => {
if (isRunning && !isMuted) {
if (timeLeft === 0) {
playAudio(overtimeAudioRef);
playAudio("overtime");
} else if (isBreak) {
playAudio(breakAudioRef);
playAudio("break");
} else {
playAudio(playingAudioRef);
playAudio("playing");
}
} else {
stopAllAudio();
@@ -205,7 +374,6 @@ export function TimerModal({
setIsOvertimeStarted(false);
}
}
// The dependency `timeLeft > 0` is a boolean that only changes when the timer hits zero.
}, [isRunning, isBreak, timeLeft > 0, isMuted]);
const formatTime = (seconds) => {
+16 -1
View File
@@ -1,4 +1,19 @@
const isProd = process.env.NODE_ENV === "production";
const internalHost = process.env.TAURI_DEV_HOST || "localhost";
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
// Ensure Next.js uses SSG instead of SSR
// https://nextjs.org/docs/pages/building-your-application/deploying/static-exports
output: "export",
// Note: This feature is required to use the Next.js Image component in SSG mode.
// See https://nextjs.org/docs/messages/export-image-api for different workarounds.
images: {
unoptimized: true,
},
// Configure assetPrefix or else the server won't properly resolve your assets.
assetPrefix: isProd ? undefined : `http://${internalHost}:3000`,
};
export default nextConfig;
+184
View File
@@ -25,6 +25,7 @@
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@tauri-apps/cli": "^2.7.1",
"postcss": "^8.5",
"tailwindcss": "^3.4.17"
}
@@ -1243,6 +1244,189 @@
"tslib": "^2.8.0"
}
},
"node_modules/@tauri-apps/cli": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.7.1.tgz",
"integrity": "sha512-RcGWR4jOUEl92w3uvI0h61Llkfj9lwGD1iwvDRD2isMrDhOzjeeeVn9aGzeW1jubQ/kAbMYfydcA4BA0Cy733Q==",
"dev": true,
"license": "Apache-2.0 OR MIT",
"bin": {
"tauri": "tauri.js"
},
"engines": {
"node": ">= 10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/tauri"
},
"optionalDependencies": {
"@tauri-apps/cli-darwin-arm64": "2.7.1",
"@tauri-apps/cli-darwin-x64": "2.7.1",
"@tauri-apps/cli-linux-arm-gnueabihf": "2.7.1",
"@tauri-apps/cli-linux-arm64-gnu": "2.7.1",
"@tauri-apps/cli-linux-arm64-musl": "2.7.1",
"@tauri-apps/cli-linux-riscv64-gnu": "2.7.1",
"@tauri-apps/cli-linux-x64-gnu": "2.7.1",
"@tauri-apps/cli-linux-x64-musl": "2.7.1",
"@tauri-apps/cli-win32-arm64-msvc": "2.7.1",
"@tauri-apps/cli-win32-ia32-msvc": "2.7.1",
"@tauri-apps/cli-win32-x64-msvc": "2.7.1"
}
},
"node_modules/@tauri-apps/cli-darwin-arm64": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.7.1.tgz",
"integrity": "sha512-j2NXQN6+08G03xYiyKDKqbCV2Txt+hUKg0a8hYr92AmoCU8fgCjHyva/p16lGFGUG3P2Yu0xiNe1hXL9ZuRMzA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-darwin-x64": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.7.1.tgz",
"integrity": "sha512-CdYAefeM35zKsc91qIyKzbaO7FhzTyWKsE8hj7tEJ1INYpoh1NeNNyL/NSEA3Nebi5ilugioJ5tRK8ZXG8y3gw==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.7.1.tgz",
"integrity": "sha512-dnvyJrTA1UJxJjQ8q1N/gWomjP8Twij1BUQu2fdcT3OPpqlrbOk5R1yT0oD/721xoKNjroB5BXCsmmlykllxNg==",
"cpu": [
"arm"
],
"dev": true,
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.7.1.tgz",
"integrity": "sha512-/HXY0t4FHkpFzjeYS5c16mlA6z0kzn5uKLWptTLTdFSnYpr8FCnOP4Sdkvm2TDQPF2ERxXtNCd+WR/jQugbGnA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.7.1.tgz",
"integrity": "sha512-GeW5lVI2GhhnaYckiDzstG2j2Jwlud5d2XefRGwlOK+C/bVGLT1le8MNPYK8wgRlpeK8fG1WnJJYD6Ke7YQ8bg==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.7.1.tgz",
"integrity": "sha512-DprxKQkPxIPYwUgg+cscpv2lcIUhn2nxEPlk0UeaiV9vATxCXyytxr1gLcj3xgjGyNPlM0MlJyYaPy1JmRg1cA==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-linux-x64-musl": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.7.1.tgz",
"integrity": "sha512-KLlq3kOK7OUyDR757c0zQjPULpGZpLhNB0lZmZpHXvoOUcqZoCXJHh4dT/mryWZJp5ilrem5l8o9ngrDo0X1AA==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.7.1.tgz",
"integrity": "sha512-dH7KUjKkSypCeWPiainHyXoES3obS+JIZVoSwSZfKq2gWgs48FY3oT0hQNYrWveE+VR4VoR3b/F3CPGbgFvksA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.7.1.tgz",
"integrity": "sha512-1oeibfyWQPVcijOrTg709qhbXArjX3x1MPjrmA5anlygwrbByxLBcLXvotcOeULFcnH2FYUMMLLant8kgvwE5A==",
"cpu": [
"ia32"
],
"dev": true,
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+3 -1
View File
@@ -6,7 +6,8 @@
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"tauri": "tauri"
},
"dependencies": {
"@radix-ui/react-select": "^2.2.5",
@@ -26,6 +27,7 @@
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@tauri-apps/cli": "^2.7.1",
"postcss": "^8.5",
"tailwindcss": "^3.4.17"
}
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/gen/schemas
+4976
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
rust-version = "1.77.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.3.1", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tauri = { version = "2.7.0", features = [] }
tauri-plugin-log = "2"
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+16
View File
@@ -0,0 +1,16 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)?;
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
+6
View File
@@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
app_lib::run();
}
+37
View File
@@ -0,0 +1,37 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "priospace",
"version": "0.1.0",
"identifier": "com.anoyrc.priospace",
"build": {
"frontendDist": "../out",
"devUrl": "http://localhost:3000",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
},
"app": {
"windows": [
{
"title": "priospace",
"width": 490,
"height": 900,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
+3
View File
@@ -10,6 +10,9 @@ const config = {
],
theme: {
extend: {
screens: {
sm: "460px",
},
fontFamily: {
nunito: "var(--font-nunito)",
sans: "var(--font-nunito)",