Skip to main content

Overview

Open Mushaf Native supports multiple authentic Quranic recitation traditions (Riwayas), allowing users to read the Quran in their preferred Riwaya with the appropriate Mushaf layout.

Supported Riwayas

The app currently supports two major Riwayas:

Hafs عن عاصم

The most widely used Riwaya, standard in most Muslim-majority countries

Warsh عن نافع

Common in North and West Africa, particularly in Morocco and Algeria

Riwaya Types

types/riwaya.d.ts
export type Riwaya = 'hafs' | 'warsh' | undefined;
export type RiwayaArabic = 'حفص' | 'ورش';

How to Switch Riwayas

1

First Launch

On first launch, you’ll be prompted to select your preferred Riwaya
2

Choose Riwaya

Select either حفص (Hafs) or ورش (Warsh) from the segmented control
3

Change Later

You can change your Riwaya anytime from Settings using the same segmented control

Selection Component

components/SelectRiwaya.tsx
<SegmentedControl
  options={riwayaOptions} // ['حفص', 'ورش']
  initialSelectedIndex={RiwayaByIndice(mushafRiwayaValue)}
  activeColor={primaryColor}
  textColor={primaryColor}
  onSelectionChange={(index: number) => {
    const selectedRiwaya = RiwayaByValue(index);
    setMushafRiwayaValue(selectedRiwaya);
  }}
/>

Technical Implementation

State Management

Riwaya selection is stored using Jotai atoms with persistent storage:
jotai/atoms.ts
export const mushafRiwaya = createAtomWithStorage<Riwaya | undefined>(
  'MushafRiwaya',
  undefined,
);
The Riwaya preference is saved to device storage and persists across app sessions.

Image Map Selection

The app maintains separate image collections for each Riwaya:
hooks/useImagesArray.ts
let imagesMap;
switch (mushafRiwayaValue) {
  case 'hafs':
    imagesMap = imagesMapHafs;
    break;
  case 'warsh':
    imagesMap = imagesMapWarsh;
    break;
  default:
    imagesMap = undefined;
}

Metadata Loading

Each Riwaya has its own Quran metadata (Surah info, Hizb divisions, etc.):
hooks/useQuranMetadata.ts
if (mushafRiwayaValue === 'hafs') {
  // Load Hafs metadata
  thumnDataJSON = await import('@/assets/metadata/hafs/thumun.json');
  surahDataJSON = await import('@/assets/metadata/hafs/suwar.json');
  // ... more Hafs files
} else {
  // Load Warsh metadata (default)
  thumnDataJSON = await import('@/assets/metadata/warsh/thumun.json');
  surahDataJSON = await import('@/assets/metadata/warsh/suwar.json');
  // ... more Warsh files
}

Differences Between Riwayas

Layout Variations

The number of pages may differ between Hafs and Warsh Mushafs, as they use different layouts and line breaks.
Some words are recited differently between Riwayas, reflected in the Mushaf text (e.g., مَالِكِ vs مَلِكِ).
Both Mushafs include color-coded Tajweed rules, but the specific marks may vary based on recitation rules.

Automatic Updates

When you switch Riwayas, the app automatically:
1

Reloads Metadata

Loads the appropriate Surah, Hizb, and page data for the selected Riwaya
2

Switches Images

Updates the page images to use the correct Mushaf layout
3

Clears Cache

Removes cached pages from the previous Riwaya to free up storage
4

Maintains Position

Attempts to keep you at a similar location in the Quran when switching
Changing Riwayas requires reloading assets, so there may be a brief loading period when switching.

Utility Functions

The app provides helper functions for working with Riwayas:
utils/riwaya.ts
// Get the index of a Riwaya value (for UI components)
export function RiwayaByIndice(value: Riwaya): number {
  return riwayaArray.indexOf(value);
}

// Get the Riwaya value by index (from UI selection)
export function RiwayaByValue(index: number): Riwaya {
  return riwayaArray[index];
}

Widget Support

The Android widget also respects your Riwaya selection:
widgets/widget-task-handler.tsx
const riwaya = store.get(mushafRiwaya) || 'warsh';
// Widget loads appropriate images based on Riwaya
If no Riwaya is selected, the app defaults to Warsh for backward compatibility.

Build docs developers (and LLMs) love