mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 23:46:41 +02:00
chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace: - bauntown (community website) - maerchenzauber (AI story generation) - memoro (voice memo app) - news (news aggregation) - nutriphi (nutrition tracking) - reader (reading app) - uload (URL shortener) - wisekeep (AI wisdom extraction) Update CLAUDE.md documentation: - Add presi to active projects - Document archived projects section - Update workspace configuration Archived apps can be re-activated by moving back to apps/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b97149ac12
commit
61d181fbc2
3148 changed files with 437 additions and 46640 deletions
|
|
@ -1,416 +0,0 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Audio } from 'expo-av';
|
||||
import { AudioService, AudioGenerationProgress } from '~/services/audioService';
|
||||
import { useTexts } from './useTexts';
|
||||
import { useStore } from '~/store/store';
|
||||
import { AudioChunk } from '~/types/database';
|
||||
|
||||
export interface AudioState {
|
||||
isPlaying: boolean;
|
||||
isLoading: boolean;
|
||||
currentPosition: number;
|
||||
duration: number;
|
||||
currentChunk?: AudioChunk;
|
||||
sound?: Audio.Sound;
|
||||
playbackRate: number;
|
||||
}
|
||||
|
||||
export const useAudio = () => {
|
||||
const { settings, updateSettings } = useStore();
|
||||
const { updateText } = useTexts();
|
||||
|
||||
const [audioState, setAudioState] = useState<AudioState>({
|
||||
isPlaying: false,
|
||||
isLoading: false,
|
||||
currentPosition: 0,
|
||||
duration: 0,
|
||||
playbackRate: settings.playbackRate || 1.0,
|
||||
});
|
||||
|
||||
const [generationProgress, setGenerationProgress] = useState<AudioGenerationProgress | null>(
|
||||
null
|
||||
);
|
||||
const [downloadProgress, setDownloadProgress] = useState<{
|
||||
completed: number;
|
||||
total: number;
|
||||
currentChunk: string;
|
||||
} | null>(null);
|
||||
|
||||
const { setCurrentText, setIsPlaying, setCurrentPosition } = useStore();
|
||||
const audioService = AudioService.getInstance();
|
||||
|
||||
// Initialize audio session
|
||||
useEffect(() => {
|
||||
const initializeAudio = async () => {
|
||||
try {
|
||||
await Audio.setAudioModeAsync({
|
||||
allowsRecordingIOS: false,
|
||||
playsInSilentModeIOS: true,
|
||||
shouldDuckAndroid: true,
|
||||
staysActiveInBackground: true,
|
||||
playThroughEarpieceAndroid: false,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error initializing audio:', error);
|
||||
}
|
||||
};
|
||||
|
||||
initializeAudio();
|
||||
}, []);
|
||||
|
||||
// Clean up audio when component unmounts
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (audioState.sound) {
|
||||
audioState.sound.unloadAsync();
|
||||
}
|
||||
};
|
||||
}, [audioState.sound]);
|
||||
|
||||
// Generate audio for a text
|
||||
const generateAudio = useCallback(
|
||||
async (
|
||||
textId: string,
|
||||
content: string,
|
||||
voice: string = 'de-DE',
|
||||
speed: number = 1.0,
|
||||
currentText?: any
|
||||
) => {
|
||||
try {
|
||||
setGenerationProgress({
|
||||
chunksCompleted: 0,
|
||||
totalChunks: 1,
|
||||
currentChunk: 'Starting...',
|
||||
isComplete: false,
|
||||
});
|
||||
|
||||
// Import migration helper
|
||||
const { generateVersionId } = await import('~/utils/audioMigration');
|
||||
const newVersionId = generateVersionId();
|
||||
|
||||
const result = await audioService.generateAudioForText(
|
||||
textId,
|
||||
content,
|
||||
voice,
|
||||
speed,
|
||||
1000,
|
||||
setGenerationProgress,
|
||||
newVersionId
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
// Get current text to append to audioVersions
|
||||
if (!currentText) {
|
||||
throw new Error('Text must be provided to generate audio');
|
||||
}
|
||||
|
||||
// Import migration helper for existing code
|
||||
const { migrateAudioData } = await import('~/utils/audioMigration');
|
||||
|
||||
// Migrate old data if needed
|
||||
const migratedData = migrateAudioData(currentText.data);
|
||||
const newAudioVersion = {
|
||||
id: newVersionId,
|
||||
chunks: result.chunks || [],
|
||||
settings: { voice, speed },
|
||||
totalSize: result.chunks?.reduce((sum, chunk) => sum + chunk.size, 0) || 0,
|
||||
hasLocalCache: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Append new version to audioVersions
|
||||
const updatedAudioVersions = [...(migratedData.audioVersions || []), newAudioVersion];
|
||||
|
||||
// Update text with new audio version
|
||||
await updateText(textId, {
|
||||
data: {
|
||||
...migratedData,
|
||||
audioVersions: updatedAudioVersions,
|
||||
currentAudioVersion: newVersionId,
|
||||
// Keep legacy audio field for backward compatibility
|
||||
audio: {
|
||||
hasLocalCache: false,
|
||||
chunks: result.chunks || [],
|
||||
totalSize: newAudioVersion.totalSize,
|
||||
lastGenerated: newAudioVersion.createdAt,
|
||||
settings: { voice, speed },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error generating audio:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setGenerationProgress(null);
|
||||
}
|
||||
},
|
||||
[audioService, updateText]
|
||||
);
|
||||
|
||||
// Download audio chunks to local storage
|
||||
const downloadAudio = useCallback(
|
||||
async (textId: string, chunks: AudioChunk[]) => {
|
||||
try {
|
||||
setDownloadProgress({
|
||||
completed: 0,
|
||||
total: chunks.length,
|
||||
currentChunk: 'Starting download...',
|
||||
});
|
||||
|
||||
const result = await audioService.downloadAudioChunks(textId, chunks, setDownloadProgress);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
// Update text to mark as locally cached
|
||||
await updateText(textId, {
|
||||
data: {
|
||||
audio: {
|
||||
hasLocalCache: true,
|
||||
chunks: result.localChunks || chunks,
|
||||
totalSize: result.localChunks?.reduce((sum, chunk) => sum + chunk.size, 0) || 0,
|
||||
lastGenerated: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error downloading audio:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setDownloadProgress(null);
|
||||
}
|
||||
},
|
||||
[audioService, updateText]
|
||||
);
|
||||
|
||||
// Play audio from local cache
|
||||
const playAudio = useCallback(
|
||||
async (textId: string, chunks: AudioChunk[], startPosition: number = 0) => {
|
||||
try {
|
||||
setAudioState((prev) => ({ ...prev, isLoading: true }));
|
||||
|
||||
// Stop current audio if playing
|
||||
if (audioState.sound) {
|
||||
audioState.sound.unloadAsync();
|
||||
}
|
||||
|
||||
// Calculate total duration from all chunks
|
||||
const totalDuration = chunks.reduce((sum, chunk) => sum + chunk.duration, 0) * 1000; // Convert to milliseconds
|
||||
|
||||
const result = await audioService.playAudioFromSupabase(textId, chunks, startPosition);
|
||||
|
||||
if (!result.sound) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
const currentChunk = result.chunk;
|
||||
const allChunks = result.chunks || chunks;
|
||||
|
||||
// Set up playback status update
|
||||
result.sound.setOnPlaybackStatusUpdate((status) => {
|
||||
if (status.isLoaded) {
|
||||
// Calculate the actual position across all chunks
|
||||
const chunkPosition = status.positionMillis || 0;
|
||||
const overallPosition = currentChunk
|
||||
? currentChunk.start + chunkPosition
|
||||
: chunkPosition;
|
||||
|
||||
setAudioState((prev) => ({
|
||||
...prev,
|
||||
isPlaying: status.isPlaying,
|
||||
currentPosition: overallPosition,
|
||||
duration: totalDuration, // Keep using total duration
|
||||
}));
|
||||
|
||||
// Update global store
|
||||
setIsPlaying(status.isPlaying);
|
||||
setCurrentPosition(overallPosition);
|
||||
}
|
||||
});
|
||||
|
||||
setAudioState((prev) => ({
|
||||
...prev,
|
||||
sound: result.sound,
|
||||
isLoading: false,
|
||||
isPlaying: true,
|
||||
duration: totalDuration, // Set total duration of all chunks
|
||||
currentChunk: currentChunk,
|
||||
}));
|
||||
|
||||
setCurrentText(textId);
|
||||
|
||||
// Start playing
|
||||
await result.sound.playAsync();
|
||||
|
||||
// Apply saved playback rate
|
||||
if (audioState.playbackRate !== 1.0) {
|
||||
await result.sound.setRateAsync(audioState.playbackRate, true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error playing audio:', error);
|
||||
setAudioState((prev) => ({ ...prev, isLoading: false }));
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[
|
||||
audioState.sound,
|
||||
audioState.playbackRate,
|
||||
audioService,
|
||||
setCurrentText,
|
||||
setIsPlaying,
|
||||
setCurrentPosition,
|
||||
]
|
||||
);
|
||||
|
||||
// Pause audio
|
||||
const pauseAudio = useCallback(async () => {
|
||||
if (audioState.sound) {
|
||||
await audioState.sound.pauseAsync();
|
||||
setAudioState((prev) => ({ ...prev, isPlaying: false }));
|
||||
setIsPlaying(false);
|
||||
}
|
||||
}, [audioState.sound, setIsPlaying]);
|
||||
|
||||
// Resume audio
|
||||
const resumeAudio = useCallback(async () => {
|
||||
if (audioState.sound) {
|
||||
await audioState.sound.playAsync();
|
||||
setAudioState((prev) => ({ ...prev, isPlaying: true }));
|
||||
setIsPlaying(true);
|
||||
}
|
||||
}, [audioState.sound, setIsPlaying]);
|
||||
|
||||
// Stop audio
|
||||
const stopAudio = useCallback(async () => {
|
||||
if (audioState.sound) {
|
||||
await audioState.sound.pauseAsync();
|
||||
await audioState.sound.unloadAsync();
|
||||
setAudioState((prev) => ({
|
||||
...prev,
|
||||
sound: undefined,
|
||||
isPlaying: false,
|
||||
currentPosition: 0,
|
||||
duration: 0,
|
||||
}));
|
||||
setCurrentText(null);
|
||||
setIsPlaying(false);
|
||||
setCurrentPosition(0);
|
||||
}
|
||||
}, [audioState.sound, setCurrentText, setIsPlaying, setCurrentPosition]);
|
||||
|
||||
// Seek to position
|
||||
const seekTo = useCallback(
|
||||
async (position: number) => {
|
||||
if (audioState.sound) {
|
||||
await audioState.sound.setPositionAsync(position);
|
||||
}
|
||||
},
|
||||
[audioState.sound]
|
||||
);
|
||||
|
||||
// Seek forward by seconds
|
||||
const seekForward = useCallback(
|
||||
async (seconds: number = 15) => {
|
||||
if (audioState.sound && audioState.duration > 0) {
|
||||
const newPosition = Math.min(
|
||||
audioState.currentPosition + seconds * 1000,
|
||||
audioState.duration
|
||||
);
|
||||
await audioState.sound.setPositionAsync(newPosition);
|
||||
}
|
||||
},
|
||||
[audioState.sound, audioState.currentPosition, audioState.duration]
|
||||
);
|
||||
|
||||
// Seek backward by seconds
|
||||
const seekBackward = useCallback(
|
||||
async (seconds: number = 15) => {
|
||||
if (audioState.sound) {
|
||||
const newPosition = Math.max(audioState.currentPosition - seconds * 1000, 0);
|
||||
await audioState.sound.setPositionAsync(newPosition);
|
||||
}
|
||||
},
|
||||
[audioState.sound, audioState.currentPosition]
|
||||
);
|
||||
|
||||
// Set playback speed
|
||||
const setPlaybackSpeed = useCallback(
|
||||
async (rate: number) => {
|
||||
if (audioState.sound) {
|
||||
try {
|
||||
await audioState.sound.setRateAsync(rate, true);
|
||||
setAudioState((prev) => ({ ...prev, playbackRate: rate }));
|
||||
// Persist to store
|
||||
updateSettings({ playbackRate: rate });
|
||||
} catch (error) {
|
||||
console.error('Error setting playback rate:', error);
|
||||
}
|
||||
} else {
|
||||
// If no sound is playing, just update the state for next playback
|
||||
setAudioState((prev) => ({ ...prev, playbackRate: rate }));
|
||||
// Persist to store
|
||||
updateSettings({ playbackRate: rate });
|
||||
}
|
||||
},
|
||||
[audioState.sound, updateSettings]
|
||||
);
|
||||
|
||||
// Clear audio cache
|
||||
const clearCache = useCallback(
|
||||
async (textId: string, chunks: AudioChunk[]) => {
|
||||
await audioService.clearAudioCache(textId, chunks);
|
||||
|
||||
// Update text to mark as not cached
|
||||
await updateText(textId, {
|
||||
data: {
|
||||
audio: {
|
||||
hasLocalCache: false,
|
||||
chunks,
|
||||
totalSize: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
[audioService, updateText]
|
||||
);
|
||||
|
||||
// Get cache size
|
||||
const getCacheSize = useCallback(async () => {
|
||||
return await audioService.getCacheSize();
|
||||
}, [audioService]);
|
||||
|
||||
// Check if audio is cached
|
||||
const isAudioCached = useCallback(
|
||||
async (textId: string, chunks: AudioChunk[]) => {
|
||||
return await audioService.isAudioCached(textId, chunks);
|
||||
},
|
||||
[audioService]
|
||||
);
|
||||
|
||||
return {
|
||||
audioState,
|
||||
generationProgress,
|
||||
downloadProgress,
|
||||
generateAudio,
|
||||
downloadAudio,
|
||||
playAudio,
|
||||
pauseAudio,
|
||||
resumeAudio,
|
||||
stopAudio,
|
||||
seekTo,
|
||||
seekForward,
|
||||
seekBackward,
|
||||
setPlaybackSpeed,
|
||||
clearCache,
|
||||
getCacheSize,
|
||||
isAudioCached,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { supabase } from '~/utils/supabase';
|
||||
import { useStore } from '~/store/store';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
|
||||
export const useAuth = () => {
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { setUser } = useStore();
|
||||
|
||||
useEffect(() => {
|
||||
// Get initial session
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session?.user) {
|
||||
setUser({
|
||||
id: session.user.id,
|
||||
email: session.user.email!,
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
// Listen for auth changes
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session?.user) {
|
||||
setUser({
|
||||
id: session.user.id,
|
||||
email: session.user.email!,
|
||||
});
|
||||
} else {
|
||||
setUser(null);
|
||||
}
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
}, [setUser]);
|
||||
|
||||
const signUp = async (email: string, password: string) => {
|
||||
try {
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
return { data, error: null };
|
||||
} catch (error) {
|
||||
return {
|
||||
data: null,
|
||||
error: error instanceof Error ? error.message : 'Fehler bei der Registrierung',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const signIn = async (email: string, password: string) => {
|
||||
try {
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
return { data, error: null };
|
||||
} catch (error) {
|
||||
return {
|
||||
data: null,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Anmelden',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const signOut = async () => {
|
||||
try {
|
||||
const { error } = await supabase.auth.signOut();
|
||||
if (error) throw error;
|
||||
return { error: null };
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Abmelden',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const resetPassword = async (email: string) => {
|
||||
try {
|
||||
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: 'reader://reset-password',
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
return { data, error: null };
|
||||
} catch (error) {
|
||||
return {
|
||||
data: null,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Zurücksetzen',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
session,
|
||||
user: session?.user ?? null,
|
||||
loading,
|
||||
signUp,
|
||||
signIn,
|
||||
signOut,
|
||||
resetPassword,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { supabase } from '~/utils/supabase';
|
||||
import { Text, TextData } from '~/types/database';
|
||||
|
||||
export const useTexts = () => {
|
||||
const [texts, setTexts] = useState<Text[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTexts();
|
||||
|
||||
// Realtime Subscription
|
||||
const subscription = supabase
|
||||
.channel('texts_changes')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: '*',
|
||||
schema: 'public',
|
||||
table: 'texts',
|
||||
},
|
||||
(payload) => {
|
||||
if (payload.eventType === 'INSERT') {
|
||||
// Check if text already exists to avoid duplicates
|
||||
setTexts((prev) => {
|
||||
const exists = prev.some((text) => text.id === payload.new.id);
|
||||
if (exists) return prev;
|
||||
return [payload.new as Text, ...prev];
|
||||
});
|
||||
} else if (payload.eventType === 'UPDATE') {
|
||||
setTexts((prev) =>
|
||||
prev.map((text) => (text.id === payload.new.id ? (payload.new as Text) : text))
|
||||
);
|
||||
} else if (payload.eventType === 'DELETE') {
|
||||
setTexts((prev) => prev.filter((text) => text.id !== payload.old.id));
|
||||
}
|
||||
}
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const fetchTexts = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data, error } = await supabase
|
||||
.from('texts')
|
||||
.select('*')
|
||||
.order('updated_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
setTexts(data || []);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const createText = async (title: string, content: string, initialData?: Partial<TextData>) => {
|
||||
try {
|
||||
// Get current user
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (!user) {
|
||||
throw new Error('Benutzer nicht eingeloggt');
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('texts')
|
||||
.insert({
|
||||
title,
|
||||
content,
|
||||
user_id: user.id, // Explicitly set user_id
|
||||
data: {
|
||||
tts: { speed: 1.0, voice: 'de-DE-Neural2-A' },
|
||||
tags: [],
|
||||
stats: { playCount: 0, totalTime: 0, completed: false },
|
||||
...initialData,
|
||||
},
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// Refresh the texts list to ensure we have the latest data
|
||||
await fetchTexts();
|
||||
|
||||
return { data, error: null };
|
||||
} catch (err) {
|
||||
return {
|
||||
data: null,
|
||||
error: err instanceof Error ? err.message : 'Fehler beim Erstellen',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const updateText = async (textId: string, updates: Partial<Text>) => {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('texts')
|
||||
.update(updates)
|
||||
.eq('id', textId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
return { data, error: null };
|
||||
} catch (err) {
|
||||
return {
|
||||
data: null,
|
||||
error: err instanceof Error ? err.message : 'Fehler beim Aktualisieren',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const deleteText = async (textId: string) => {
|
||||
try {
|
||||
const { error } = await supabase.from('texts').delete().eq('id', textId);
|
||||
|
||||
if (error) throw error;
|
||||
return { error: null };
|
||||
} catch (err) {
|
||||
return {
|
||||
error: err instanceof Error ? err.message : 'Fehler beim Löschen',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const updatePosition = async (textId: string, position: number) => {
|
||||
const text = texts.find((t) => t.id === textId);
|
||||
if (!text) return { error: 'Text nicht gefunden' };
|
||||
|
||||
return updateText(textId, {
|
||||
data: {
|
||||
...text.data,
|
||||
tts: {
|
||||
...text.data.tts,
|
||||
lastPosition: position,
|
||||
lastPlayed: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getTextsByTag = (tag: string) => {
|
||||
return texts.filter((text) => text.data.tags?.includes(tag));
|
||||
};
|
||||
|
||||
const getAllTags = () => {
|
||||
const tagSet = new Set<string>();
|
||||
texts.forEach((text) => {
|
||||
text.data.tags?.forEach((tag) => tagSet.add(tag));
|
||||
});
|
||||
return Array.from(tagSet).sort();
|
||||
};
|
||||
|
||||
return {
|
||||
texts,
|
||||
loading,
|
||||
error,
|
||||
createText,
|
||||
updateText,
|
||||
deleteText,
|
||||
updatePosition,
|
||||
getTextsByTag,
|
||||
getAllTags,
|
||||
refetch: fetchTexts,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
import { useStore } from '~/store/store';
|
||||
|
||||
export interface ThemeColors {
|
||||
// Background colors
|
||||
background: string;
|
||||
surface: string;
|
||||
surfaceSecondary: string;
|
||||
|
||||
// Text colors
|
||||
text: string;
|
||||
textSecondary: string;
|
||||
textTertiary: string;
|
||||
|
||||
// Border colors
|
||||
border: string;
|
||||
borderSecondary: string;
|
||||
|
||||
// Primary colors
|
||||
primary: string;
|
||||
primaryLight: string;
|
||||
primaryDark: string;
|
||||
|
||||
// Status colors
|
||||
success: string;
|
||||
successLight: string;
|
||||
warning: string;
|
||||
warningLight: string;
|
||||
error: string;
|
||||
errorLight: string;
|
||||
|
||||
// Tab bar colors
|
||||
tabBarBackground: string;
|
||||
tabBarBorder: string;
|
||||
tabBarActive: string;
|
||||
tabBarInactive: string;
|
||||
}
|
||||
|
||||
const lightTheme: ThemeColors = {
|
||||
// Background colors
|
||||
background: 'bg-gray-50',
|
||||
surface: 'bg-white',
|
||||
surfaceSecondary: 'bg-gray-100',
|
||||
|
||||
// Text colors
|
||||
text: 'text-gray-900',
|
||||
textSecondary: 'text-gray-600',
|
||||
textTertiary: 'text-gray-500',
|
||||
|
||||
// Border colors
|
||||
border: 'border-gray-200',
|
||||
borderSecondary: 'border-gray-300',
|
||||
|
||||
// Primary colors
|
||||
primary: 'bg-blue-600',
|
||||
primaryLight: 'bg-blue-50',
|
||||
primaryDark: 'bg-blue-700',
|
||||
|
||||
// Status colors
|
||||
success: 'bg-green-600',
|
||||
successLight: 'bg-green-100',
|
||||
warning: 'bg-orange-600',
|
||||
warningLight: 'bg-orange-100',
|
||||
error: 'bg-red-600',
|
||||
errorLight: 'bg-red-50',
|
||||
|
||||
// Tab bar colors
|
||||
tabBarBackground: '#ffffff',
|
||||
tabBarBorder: '#e5e7eb',
|
||||
tabBarActive: '#3B82F6',
|
||||
tabBarInactive: '#6b7280',
|
||||
};
|
||||
|
||||
const darkTheme: ThemeColors = {
|
||||
// Background colors
|
||||
background: 'bg-gray-900',
|
||||
surface: 'bg-gray-800',
|
||||
surfaceSecondary: 'bg-gray-700',
|
||||
|
||||
// Text colors
|
||||
text: 'text-white',
|
||||
textSecondary: 'text-gray-300',
|
||||
textTertiary: 'text-gray-400',
|
||||
|
||||
// Border colors
|
||||
border: 'border-gray-600',
|
||||
borderSecondary: 'border-gray-500',
|
||||
|
||||
// Primary colors
|
||||
primary: 'bg-blue-600',
|
||||
primaryLight: 'bg-blue-900',
|
||||
primaryDark: 'bg-blue-700',
|
||||
|
||||
// Status colors
|
||||
success: 'bg-green-600',
|
||||
successLight: 'bg-green-900',
|
||||
warning: 'bg-orange-600',
|
||||
warningLight: 'bg-orange-900',
|
||||
error: 'bg-red-600',
|
||||
errorLight: 'bg-red-900',
|
||||
|
||||
// Tab bar colors
|
||||
tabBarBackground: '#1f2937',
|
||||
tabBarBorder: '#374151',
|
||||
tabBarActive: '#3B82F6',
|
||||
tabBarInactive: '#9ca3af',
|
||||
};
|
||||
|
||||
export const useTheme = () => {
|
||||
const { settings } = useStore();
|
||||
const isDark = settings.theme === 'dark';
|
||||
|
||||
const colors = isDark ? darkTheme : lightTheme;
|
||||
|
||||
return {
|
||||
isDark,
|
||||
colors,
|
||||
theme: settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
// Text color utilities
|
||||
export const useTextColors = () => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return {
|
||||
primary: colors.text,
|
||||
secondary: colors.textSecondary,
|
||||
tertiary: colors.textTertiary,
|
||||
primaryText: colors.text.replace('text-', 'text-'),
|
||||
secondaryText: colors.textSecondary.replace('text-', 'text-'),
|
||||
tertiaryText: colors.textTertiary.replace('text-', 'text-'),
|
||||
};
|
||||
};
|
||||
|
||||
// Background color utilities
|
||||
export const useBackgroundColors = () => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return {
|
||||
main: colors.background,
|
||||
surface: colors.surface,
|
||||
surfaceSecondary: colors.surfaceSecondary,
|
||||
};
|
||||
};
|
||||
|
||||
// Border color utilities
|
||||
export const useBorderColors = () => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return {
|
||||
main: colors.border,
|
||||
secondary: colors.borderSecondary,
|
||||
};
|
||||
};
|
||||
|
||||
// Status color utilities
|
||||
export const useStatusColors = () => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return {
|
||||
success: colors.success,
|
||||
successLight: colors.successLight,
|
||||
warning: colors.warning,
|
||||
warningLight: colors.warningLight,
|
||||
error: colors.error,
|
||||
errorLight: colors.errorLight,
|
||||
};
|
||||
};
|
||||
|
||||
// Primary color utilities
|
||||
export const usePrimaryColors = () => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return {
|
||||
main: colors.primary,
|
||||
light: colors.primaryLight,
|
||||
dark: colors.primaryDark,
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue