Skip to main content

Overview

The Reading Tracker (الورد) helps you build a consistent Quran reading habit by tracking your daily progress and comparing it against customizable goals. The system automatically calculates progress based on Hizb divisions of the Quran.

How It Works

1

Set Daily Goal

Choose how many Ahzab (portions) you want to read daily
2

Read Quran

Navigate through the Mushaf normally as you read
3

Automatic Tracking

Progress is calculated automatically based on pages read since yesterday
4

Goal Completion

Receive notification when you complete your daily goal
5

Daily Reset

Progress resets automatically at midnight for a fresh start

Understanding Hizb Divisions

The Quran is divided into 60 Ahzab (حزب in singular) for easy tracking:
  • 1 Hizb = 1/60th of the Quran
  • 1 Hizb = Approximately 10 pages
  • 8 Thumns (أثمان) = 1 Hizb
The tracker uses the Thumns system internally (8 Thumns = 1 Hizb) for more precise progress calculation.

Progress Calculation

Automatic Tracking

components/MushafPage.tsx
useEffect(() => {
  if (typeof currentPage === 'number') {
    // Calculate thumns read between yesterday's page and current page
    const numberOfThumn = calculateThumnsBetweenPages(
      yesterdayPageValue.value,
      currentPage,
      thumnData,
    );

    // Update the progress state (convert thumns to ahzab)
    setdailyTrackerCompletedValue({
      value: numberOfThumn / 8,
      date: new Date().toDateString(),
    });
  }
}, [currentPage, yesterdayPageValue, thumnData]);

Progress Calculation Logic

utils/hizbProgress.ts
export function calculateThumnsBetweenPages(
  startPage: number,
  endPage: number,
  thumnData: ThumnData[],
): number {
  // Count how many Thumns (eighths) are between two pages
  // Returns the number of Thumns completed
}
Progress is calculated by comparing your current page with the last page you read yesterday, counting all Thumns in between.

Goal Management

Setting Goals

app/tracker.tsx
const incrementDailyGoal = () => setdailyTrackerGoalValue((prev) => prev + 1);
const decrementDailyGoal = () =>
  setdailyTrackerGoalValue((prev) => Math.max(1, prev - 1));
Goals are adjusted in full Hizb increments:
<ThemedView style={styles.controls}>
  <TouchableOpacity onPress={decrementDailyGoal}>
    <Feather name="minus" size={20} color={primaryColor} />
  </TouchableOpacity>
  
  <ThemedText style={styles.controlValue}>
    {getHizbText(dailyTrackerGoalValue)}
  </ThemedText>
  
  <TouchableOpacity onPress={incrementDailyGoal}>
    <Feather name="plus" size={20} color={primaryColor} />
  </TouchableOpacity>
</ThemedView>

Arabic Pluralization

The tracker uses proper Arabic grammar for displaying counts:
app/tracker.tsx
const getHizbText = (count: number) => {
  const hizbCount = count;

  if (hizbCount === 0) return '0 أحزاب';
  if (hizbCount === 1) return 'حزب واحد';
  if (hizbCount === 2) return 'حزبين';
  if (hizbCount >= 3 && hizbCount <= 10) return `${hizbCount} أحزاب`;
  return `${hizbCount} حزباً`;
};

Progress Display

Visual Progress Bar

app/tracker.tsx
const dailyProgress =
  dailyTrackerGoalValue > 0
    ? Math.min(
        100,
        (dailyTrackerCompletedValue.value / 8 / (dailyTrackerGoalValue / 8)) *
          100,
      )
    : 0;

<ThemedView style={styles.progressContainer}>
  <ThemedView
    style={[
      styles.progressBar,
      { width: `${dailyProgress}%`, backgroundColor: primaryColor },
    ]}
  />
  <ThemedText style={styles.progressText}>
    {dailyProgress.toFixed(1)}%
  </ThemedText>
</ThemedView>

Progress Information

<ThemedText style={styles.infoText}>
  قراءة{' '}
  {Number.isInteger(dailyTrackerCompletedValue.value)
    ? getHizbText(dailyTrackerCompletedValue.value)
    : `${dailyTrackerCompletedValue.value.toFixed(1)} حزباً`}{' '}
  من أصل {getHizbText(dailyTrackerGoalValue)}
</ThemedText>
The progress bar shows your completion percentage, with text that changes color based on progress (white text when >50% for better visibility).

Notifications

Goal Completion Notification

components/MushafPage.tsx
useEffect(() => {
  progressValue === 1 && setShowGoalNotification(true);
}, [progressValue]);

useEffect(() => {
  if (showTrackerNotificationValue && showGoalNotification) {
    notify(
      'تم إكمال الورد اليومي بنجاح',
      'tracker_goal_notification',
      'neutral',
    );
  }
}, [notify, showGoalNotification, showTrackerNotificationValue]);
When you reach 100% of your goal:
  • A notification appears: “تم إكمال الورد اليومي بنجاح”
  • Notification auto-dismisses after 3 seconds
  • Only shown if notifications are enabled in settings

Notification Settings

jotai/atoms.ts
export const showTrackerNotification = createAtomWithStorage<boolean>(
  'ShowTrackerNotification',
  false,
);
You can enable or disable goal completion notifications in the app settings.

Daily Reset System

Automatic Reset at Midnight

jotai/atoms.ts
type DailyTrackerProgress = {
  value: number;
  date: string;
};

export const dailyTrackerCompleted =
  createAtomWithStorage<DailyTrackerProgress>('DailyTrackerCompleted', {
    value: 0,
    date: new Date().toDateString(),
  });

observe((get, set) => {
  (async () => {
    const stored = await get(dailyTrackerCompleted);
    const today = new Date().toDateString();

    if (stored.date !== today) {
      set(dailyTrackerCompleted, { value: 0, date: today });
    }
  })();
});

Yesterday Page Tracking

jotai/atoms.ts
type PageWithDate = {
  value: number;
  date: string;
};

export const yesterdayPage = createAtomWithStorage<PageWithDate>(
  'YesterdayPage',
  {
    value: 1,
    date: new Date().toDateString(),
  },
);

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 });
    }
  })();
});
At midnight, the app compares the stored date with today’s date. If they differ, it:
  1. Resets your progress to 0
  2. Updates “yesterday’s page” to your last saved page
  3. Saves the new date to prevent multiple resets

Manual Reset

Reset Confirmation Modal

app/tracker.tsx
const performReset = async () => {
  if (typeof savedPage === 'number' && savedPage > 0) {
    setYesterdayPageValue({
      value: savedPage,
      date: new Date().toDateString(),
    });
  }

  setdailyTrackerCompletedValue({
    value: 0,
    date: new Date().toDateString(),
  });
  
  // Update Android widget
  await updateAndroidWidget();
  setConfirmModalVisible(false);
};
Users can manually reset their progress:
  • Opens a confirmation modal to prevent accidental resets
  • Updates yesterday’s page to current saved page
  • Resets completed value to 0
  • Syncs with Android widget
Manual reset is useful if you want to start tracking from a new point without waiting for midnight.

Persistent Storage

Goal Storage

jotai/atoms.ts
export const dailyTrackerGoal = createAtomWithStorage<number>(
  'DailyTrackerGoal',
  1, // Default: 1 Hizb per day
);

Progress Storage

All tracker data is persisted:
  • dailyTrackerGoal - Your daily goal (in Ahzab)
  • dailyTrackerCompleted - Progress with date stamp
  • yesterdayPage - Last page from previous day with date
  • showTrackerNotification - Notification preference

Persistent Goals

Your daily goal is saved and persists across app sessions

Date Tracking

Progress includes timestamps to enable automatic daily resets

Widget Sync

Android widget updates when you reset or complete goals

Automatic Calculation

No manual logging needed - progress calculates as you read

Integration with Reading

The tracker seamlessly integrates with normal Mushaf navigation:
components/MushafPage.tsx
const { currentPage, setCurrentPage } = useCurrentPage();

// Progress updates automatically as you change pages
useEffect(() => {
  // Calculate and update progress based on current page
}, [currentPage]);
You don’t need to do anything special to track your reading - just navigate the Mushaf normally and your progress updates automatically!

Build docs developers (and LLMs) love