Overview
Open Mushaf Native uses Jotai for atomic state management. All atoms are created with createAtomWithStorage to persist state across app sessions.
Core State Atoms
Controls the visibility of the bottom navigation menu.
export const bottomMenuState = createAtomWithStorage<boolean>(
'BottomMenuState',
true,
);
Storage key: 'BottomMenuState'
Usage Example:
import { useAtom } from 'jotai';
import { bottomMenuState } from '@/jotai/atoms';
function BottomMenu() {
const [isVisible, setIsVisible] = useAtom(bottomMenuState);
return (
<View style={{ display: isVisible ? 'flex' : 'none' }}>
<MenuContent />
</View>
);
}
Controls the visibility of the top menu with automatic hiding after 5 seconds.
export const topMenuState = createAtomWithStorage<boolean>(
'TopMenuState',
false,
);
Storage key: 'TopMenuState'
Auto-hide Effect:
The top menu automatically hides after the duration specified in EXPO_PUBLIC_TOP_MENU_HIDE_DURATION_MS (default: 5000ms).
observe((get, set) => {
const duration = parseInt(
process.env.EXPO_PUBLIC_TOP_MENU_HIDE_DURATION_MS || '5000',
10,
);
if (get(topMenuState)) {
const timerId = setTimeout(() => {
set(topMenuState, false);
}, duration);
return () => clearTimeout(timerId);
}
});
Usage Example:
import { useAtom } from 'jotai';
import { topMenuState } from '@/jotai/atoms';
function TopMenu() {
const [isVisible, setIsVisible] = useAtom(topMenuState);
return (
<Animated.View style={{ opacity: isVisible ? 1 : 0 }}>
<MenuBar />
</Animated.View>
);
}
Page & Navigation Atoms
currentSavedPage
Stores the user’s current page in the Quran.
export const currentSavedPage = createAtomWithStorage<number>(
'CurrentSavedPage',
1,
);
Storage key: 'CurrentSavedPage'
Usage Example:
import { useAtom } from 'jotai';
import { currentSavedPage } from '@/jotai/atoms';
function PageIndicator() {
const [page, setPage] = useAtom(currentSavedPage);
return (
<Text>Page {page} of 604</Text>
);
}
yesterdayPage
Stores yesterday’s page for progress tracking with automatic daily reset.
type PageWithDate = {
value: number;
date: string;
};
export const yesterdayPage = createAtomWithStorage<PageWithDate>(
'YesterdayPage',
{
value: 1,
date: new Date().toDateString(),
},
);
State value type with:
value: Page number
date: Date string for reset tracking
Storage key: 'YesterdayPage'
Default: { value: 1, date: new Date().toDateString() }
Auto-reset Effect:
Automatically resets to the current saved page when the date changes:
observe((get, set) => {
(async () => {
const today = new Date().toDateString();
const saved = await get(yesterdayPage);
const lastPage = await get(currentSavedPage);
if (saved.date !== today) {
set(yesterdayPage, { value: lastPage, date: today });
}
})();
});
Usage Example:
import { useAtomValue } from 'jotai';
import { yesterdayPage, currentSavedPage } from '@/jotai/atoms';
function ProgressIndicator() {
const yesterday = useAtomValue(yesterdayPage);
const current = useAtomValue(currentSavedPage);
const progress = current - yesterday.value;
return <Text>Today's progress: +{progress} pages</Text>;
}
Mushaf Configuration Atoms
mushafRiwaya
Stores the selected Quran recitation style (riwaya).
export const mushafRiwaya = createAtomWithStorage<Riwaya | undefined>(
'MushafRiwaya',
undefined,
);
Riwaya type: 'hafs' | 'warsh' | undefined
Storage key: 'MushafRiwaya'
Usage Example:
import { useAtom } from 'jotai';
import { mushafRiwaya } from '@/jotai/atoms';
function RiwayaSelector() {
const [riwaya, setRiwaya] = useAtom(mushafRiwaya);
return (
<Picker
selectedValue={riwaya}
onValueChange={setRiwaya}
>
<Picker.Item label="Hafs" value="hafs" />
<Picker.Item label="Warsh" value="warsh" />
</Picker>
);
}
mushafContrast
Controls the contrast level of the Mushaf pages.
export const mushafContrast = createAtomWithStorage<number>(
'MushafContrast',
0.5,
);
State value type (0.0 to 1.0)
Storage key: 'MushafContrast'
Usage Example:
import { useAtom } from 'jotai';
import { mushafContrast } from '@/jotai/atoms';
function ContrastSlider() {
const [contrast, setContrast] = useAtom(mushafContrast);
return (
<Slider
value={contrast}
onValueChange={setContrast}
minimumValue={0}
maximumValue={1}
/>
);
}
Search & Feature Atoms
advancedSearch
Toggles advanced search features (lemma, root, fuzzy matching).
export const advancedSearch = createAtomWithStorage<boolean>(
'AdvancedSearch',
false,
);
Storage key: 'AdvancedSearch'
Usage Example:
import { useAtom } from 'jotai';
import { advancedSearch } from '@/jotai/atoms';
function SearchOptions() {
const [isAdvanced, setIsAdvanced] = useAtom(advancedSearch);
return (
<View>
<Switch value={isAdvanced} onValueChange={setIsAdvanced} />
{isAdvanced && <AdvancedSearchPanel />}
</View>
);
}
tafseerTab
Stores the currently selected Tafseer (exegesis) source.
export const tafseerTab = createAtomWithStorage<TafseerTabs>(
'TafseerTab',
'katheer',
);
Tafseer source: 'katheer' | 'maany' | 'earab' | 'baghawy' | 'muyassar' | 'qortoby' | 'tabary' | 'saady' | 'wahidy' | 'tanweer' | 'waseet'
Storage key: 'TafseerTab'
Default value: 'katheer' (Tafsir Ibn Kathir)
Usage Example:
import { useAtom } from 'jotai';
import { tafseerTab } from '@/jotai/atoms';
function TafseerSelector() {
const [selectedTab, setSelectedTab] = useAtom(tafseerTab);
return (
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
<Tab value="katheer">Ibn Kathir</Tab>
<Tab value="tabary">Al-Tabari</Tab>
<Tab value="qortoby">Al-Qurtubi</Tab>
</Tabs>
);
}
Audio & Sound Atoms
flipSound
Enables or disables page flip sound effects.
export const flipSound = createAtomWithStorage<boolean>(
'FlipSound',
false,
);
Usage Example:
import { useAtomValue } from 'jotai';
import { flipSound } from '@/jotai/atoms';
import { Audio } from 'expo-av';
function PageNavigator() {
const soundEnabled = useAtomValue(flipSound);
const playFlipSound = async () => {
if (soundEnabled) {
const { sound } = await Audio.Sound.createAsync(
require('@/assets/sounds/flip.mp3')
);
await sound.playAsync();
}
};
return <button onClick={playFlipSound}>Next Page</button>;
}
Notification & Progress Atoms
hizbNotification
Stores the notification preference for Hizb completion.
export const hizbNotification = createAtomWithStorage<number>(
'HizbNotification',
0,
);
State value type (0 = disabled)
Storage key: 'HizbNotification'
Usage Example:
import { useAtom } from 'jotai';
import { hizbNotification } from '@/jotai/atoms';
function NotificationSettings() {
const [hizbNotif, setHizbNotif] = useAtom(hizbNotification);
return (
<Switch
value={hizbNotif > 0}
onValueChange={(enabled) => setHizbNotif(enabled ? 1 : 0)}
/>
);
}
dailyTrackerGoal
Stores the daily reading goal in number of Hizbs.
export const dailyTrackerGoal = createAtomWithStorage<number>(
'DailyTrackerGoal',
1,
);
State value type (number of Hizbs)
Storage key: 'DailyTrackerGoal'
Usage Example:
import { useAtom } from 'jotai';
import { dailyTrackerGoal } from '@/jotai/atoms';
function GoalSetter() {
const [goal, setGoal] = useAtom(dailyTrackerGoal);
return (
<View>
<Text>Daily Goal: {goal} Hizb(s)</Text>
<Button onPress={() => setGoal(goal + 1)}>Increase</Button>
</View>
);
}
dailyTrackerCompleted
Tracks daily reading progress with automatic date-based reset.
type DailyTrackerProgress = {
value: number;
date: string;
};
export const dailyTrackerCompleted = createAtomWithStorage<DailyTrackerProgress>(
'DailyTrackerCompleted',
{
value: 0,
date: new Date().toDateString(),
},
);
State value type with:
value: Number of Hizbs completed today
date: Date string for reset tracking
Storage key: 'DailyTrackerCompleted'
Default: { value: 0, date: new Date().toDateString() }
Auto-reset Effect:
Automatically resets to 0 when a new day begins:
observe((get, set) => {
(async () => {
const stored = await get(dailyTrackerCompleted);
const today = new Date().toDateString();
if (stored.date !== today) {
set(dailyTrackerCompleted, { value: 0, date: today });
}
})();
});
Usage Example:
import { useAtom, useAtomValue } from 'jotai';
import { dailyTrackerCompleted, dailyTrackerGoal } from '@/jotai/atoms';
function DailyProgress() {
const [completed, setCompleted] = useAtom(dailyTrackerCompleted);
const goal = useAtomValue(dailyTrackerGoal);
const percentage = (completed.value / goal) * 100;
return (
<View>
<Text>Progress: {completed.value}/{goal} Hizbs</Text>
<ProgressBar value={percentage} />
</View>
);
}
showTrackerNotification
Enables or disables daily tracker notifications.
export const showTrackerNotification = createAtomWithStorage<boolean>(
'ShowTrackerNotification',
false,
);
Storage key: 'ShowTrackerNotification'
Usage Example:
import { useAtom } from 'jotai';
import { showTrackerNotification } from '@/jotai/atoms';
function NotificationToggle() {
const [enabled, setEnabled] = useAtom(showTrackerNotification);
return (
<Switch
value={enabled}
onValueChange={setEnabled}
label="Enable daily reminders"
/>
);
}
UI State Atoms
finishedTutorial
Tracks whether the user has completed the onboarding tutorial.
export const finishedTutorial = createAtomWithStorage<boolean | undefined>(
'FinishedTutorial',
undefined,
);
Storage key: 'FinishedTutorial'
Default value: undefined (first launch)
Usage Example:
import { useAtom } from 'jotai';
import { finishedTutorial } from '@/jotai/atoms';
function AppRoot() {
const [tutorialComplete, setTutorialComplete] = useAtom(finishedTutorial);
if (tutorialComplete === undefined || !tutorialComplete) {
return <TutorialScreen onComplete={() => setTutorialComplete(true)} />;
}
return <MainApp />;
}
readingBannerCollapsedState
Controls the collapsed state of the reading position banner.
export const readingBannerCollapsedState = createAtomWithStorage<boolean>(
'ReadingBannerCollapsedState',
false,
);
Storage key: 'ReadingBannerCollapsedState'
Usage Example:
import { useAtom } from 'jotai';
import { readingBannerCollapsedState } from '@/jotai/atoms';
function ReadingPositionBanner() {
const [isCollapsed, setIsCollapsed] = useAtom(readingBannerCollapsedState);
return (
<View>
{!isCollapsed && <BannerContent />}
<Button onPress={() => setIsCollapsed(!isCollapsed)}>
{isCollapsed ? 'Show' : 'Hide'}
</Button>
</View>
);
}
Version Tracking Atoms
currentAppVersion
Stores the current app version for migration and update tracking.
export const currentAppVersion = createAtomWithStorage<string | undefined>(
'CurrentAppVersion',
undefined,
);
State value type (semver string)
Storage key: 'CurrentAppVersion'
Usage Example:
import { useAtom } from 'jotai';
import { currentAppVersion } from '@/jotai/atoms';
import { getAppVersion } from '@/utils/getVersion';
function VersionCheck() {
const [storedVersion, setStoredVersion] = useAtom(currentAppVersion);
const currentVersion = getAppVersion();
useEffect(() => {
if (storedVersion !== currentVersion) {
// Perform migrations or show update dialog
setStoredVersion(currentVersion);
}
}, [storedVersion, currentVersion]);
return <Text>Version: {currentVersion}</Text>;
}