Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
<!-- required for posting notifications on Android 13+ (Api 33+) -->
<uses-permission-sdk-23 android:name="android.permission.POST_NOTIFICATIONS"/>

<!-- required for Focus Mode (Do Not Disturb) to modify notification policy -->
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>

<!-- required for foreground service types on Android 14+ (Api 34+) -->
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ object Constants {
const val PREF_SHOW_SIDELINES = "showSidelines"
const val PREF_SHOW_LINE_DIVIDERS = "showLineDividers"
const val PREF_APP_THEME = "appTheme"
const val PREF_FOCUS_MODE = "focusMode"

// Themes
const val THEME_LIGHT = "light"
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ import com.quran.labs.androidquran.ui.listener.AudioBarListener
import com.quran.labs.androidquran.ui.util.ToastCompat.makeText
import com.quran.labs.androidquran.ui.util.TranslationsSpinnerAdapter
import com.quran.labs.androidquran.util.AudioUtils
import com.quran.labs.androidquran.util.FocusModeManager
import com.quran.labs.androidquran.util.OrientationLockUtils
import com.quran.labs.androidquran.util.QuranAppUtils
import com.quran.labs.androidquran.util.QuranFileUtils
Expand Down Expand Up @@ -211,6 +212,8 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat
private var lastSelectedTranslationAyah: QuranAyahInfo? = null
private var lastActivatedLocalTranslations: Array<LocalTranslation> = emptyArray()

private lateinit var focusModeManager: FocusModeManager

@Inject lateinit var bookmarksDao: BookmarksDao
@Inject lateinit var recentPagePresenter: RecentPagePresenter
@Inject lateinit var quranSettings: QuranSettings
Expand Down Expand Up @@ -282,6 +285,8 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat
savedInstanceState?.getBoolean(LAST_FOLDING_STATE, isFoldableDeviceOpenAndVertical)
?: isFoldableDeviceOpenAndVertical

focusModeManager = FocusModeManager(this)

lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this@PagerActivity)
Expand Down Expand Up @@ -894,6 +899,10 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat
audioPresenter.bind(this)
recentPagePresenter.bind(currentPageFlow)

if (QuranSettings.getInstance(this).isFocusModeEnabled()) {
focusModeManager.enableFocusMode()
Comment on lines +902 to +903
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses QuranSettings.getInstance(this) even though quranSettings is already injected in the activity. Using the injected instance keeps behavior consistent and easier to test. Also, enableFocusMode() returns false when notification policy access is missing, but the result is ignored—consider handling the failure (e.g., prompt the user or disable the setting) so Focus Mode doesn't silently do nothing.

Suggested change
if (QuranSettings.getInstance(this).isFocusModeEnabled()) {
focusModeManager.enableFocusMode()
if (quranSettings.isFocusModeEnabled()) {
val focusModeEnabled = focusModeManager.enableFocusMode()
if (!focusModeEnabled) {
Toast.makeText(this, "Focus mode is unavailable on this device.", Toast.LENGTH_LONG).show()
}

Copilot uses AI. Check for mistakes.
}

if (shouldReconnect) {
foregroundDisposable.add(
Completable.timer(500, TimeUnit.MILLISECONDS)
Expand Down Expand Up @@ -1091,6 +1100,8 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat
recentPagePresenter.unbind()
quranSettings.wasShowingTranslation = pagerAdapter.isShowingTranslation

focusModeManager.restorePreviousDndState()

Comment on lines 1100 to +1104
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restorePreviousDndState() is called from onPause(), which can run for transient interruptions (e.g., permission dialogs/overlays). That can cause Focus Mode to toggle off while the user is still in a reading session. Consider restoring DND in onStop()/onDestroy() (paired with enabling in onStart()/onResume()) so DND only restores when the activity is no longer visible.

Copilot uses AI. Check for mistakes.
super.onPause()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.quran.labs.androidquran.ui.fragment

import android.content.Intent
import android.provider.Settings
import android.widget.Toast
Comment on lines +4 to +5
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

android.provider.Settings is imported but not used in this fragment (permission navigation is handled inside FocusModeManager). Please remove the unused import to avoid warnings.

Copilot uses AI. Check for mistakes.
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
Expand All @@ -21,6 +23,8 @@ import com.quran.labs.androidquran.R
import com.quran.labs.androidquran.data.Constants
import com.quran.labs.androidquran.pageselect.PageSelectActivity
import com.quran.labs.androidquran.ui.TranslationManagerActivity
import com.quran.labs.androidquran.util.FocusModeManager
import com.quran.labs.androidquran.util.QuranSettings
import com.quran.labs.androidquran.util.OrientationLockUtils
Comment on lines +27 to 28
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

com.quran.labs.androidquran.util.QuranSettings is imported but not referenced in this fragment. Please remove the unused import to keep the file clean.

Suggested change
import com.quran.labs.androidquran.util.QuranSettings
import com.quran.labs.androidquran.util.OrientationLockUtils

Copilot uses AI. Check for mistakes.
import com.quran.labs.androidquran.util.QuranUtils
import com.quran.labs.androidquran.util.ThemeUtil
Expand Down Expand Up @@ -75,6 +79,27 @@ class QuranSettingsFragment : PreferenceFragmentCompat() {
true
}

// handle Focus Mode preference
val focusModePref: Preference? = findPreference(Constants.PREF_FOCUS_MODE)
focusModePref?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
if (newValue == true) {
val focusModeManager = FocusModeManager(requireContext())
if (!focusModeManager.isPermissionGranted()) {
// Show explanation and open settings
Toast.makeText(
requireContext(),
R.string.focus_mode_permission_required,
Toast.LENGTH_LONG
).show()
focusModeManager.requestPermission(requireActivity())
// Don't actually enable yet - user needs to grant permission first
(preference as? CheckBoxPreference)?.isChecked = false
return@OnPreferenceChangeListener false
}
}
true
}

// handle translation manager click
val translationPref: Preference? = findPreference(Constants.PREF_TRANSLATION_MANAGER)
translationPref?.setOnPreferenceClickListener {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.quran.labs.androidquran.util

import android.app.Activity
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.provider.Settings

class FocusModeManager(private val context: Context) {

private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private var previousInterruptionFilter: Int = NotificationManager.INTERRUPTION_FILTER_ALL
private var focusModeActivatedByApp: Boolean = false

fun isPermissionGranted(): Boolean = notificationManager.isNotificationPolicyAccessGranted

fun requestPermission(activity: Activity) {
val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)
activity.startActivity(intent)
}

fun saveCurrentDndState() {
previousInterruptionFilter = notificationManager.currentInterruptionFilter
}

fun enableFocusMode(): Boolean {
if (!isPermissionGranted()) return false
if (focusModeActivatedByApp) return true

previousInterruptionFilter = notificationManager.currentInterruptionFilter
notificationManager.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS)
focusModeActivatedByApp = true
return true
Comment on lines +11 to +33
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Focus Mode state is tracked only in-memory (previousInterruptionFilter / focusModeActivatedByApp). If the process is killed or the app crashes after setting DND to Alarms Only, the user's DND state may be left altered with no automatic restoration. Consider persisting the previous filter + a "changed by app" flag (e.g., in SharedPreferences) and restoring on next app start / when leaving reading.

Copilot uses AI. Check for mistakes.
}

fun restorePreviousDndState() {
if (!isPermissionGranted()) return
if (!focusModeActivatedByApp) return

notificationManager.setInterruptionFilter(previousInterruptionFilter)
focusModeActivatedByApp = false
}

fun isFocusModeActive(): Boolean {
return focusModeActivatedByApp
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.quran.labs.androidquran.util
import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build
import com.quran.labs.androidquran.data.Constants

object NotificationChannelUtil {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public boolean navigateWithVolumeKeys() {
return prefs.getBoolean(Constants.PREF_USE_VOLUME_KEY_NAV, false);
}

public boolean isFocusModeEnabled() {
return prefs.getBoolean(Constants.PREF_FOCUS_MODE, false);
}

public boolean shouldStream() {
return prefs.getBoolean(Constants.PREF_PREFER_STREAMING, false);
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/preferences_keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<string translatable="false" name="prefs_prefer_streaming">preferStreaming</string>
<string translatable="false" name="prefs_download_amount">preferredDownloadAmount</string>
<string translatable="false" name="prefs_volume_key_navigation">volumeKeyNavigation</string>
<string translatable="false" name="prefs_focus_mode">focusMode</string>
<string translatable="false" name="prefs_app_location">appLocation</string>
<string translatable="false" name="prefs_display_category_key">displayCategoryKey</string>
<string translatable="false" name="prefs_download_category_key">downloadCategoryKey</string>
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@
<string name="prefs_volume_key_navigation_title">Volume key navigation</string>
<string name="prefs_volume_key_navigation_summary">Navigate between pages using volume keys</string>
<string name="prefs_category_reading">Reading Preferences</string>
<string name="prefs_focus_mode_title">Focus Mode</string>
<string name="prefs_focus_mode_summary">Enable Do Not Disturb while reading</string>
<string name="focus_mode_permission_required">Focus Mode requires Do Not Disturb access. Please grant permission in settings.</string>
<string name="prefs_category_translation">Translation Preferences</string>
<string name="prefs_category_display_settings">Display Settings</string>
<string name="prefs_use_arabic_title">Arabic mode (الوضع العربي)</string>
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/res/xml/quran_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@
android:summary="@string/prefs_volume_key_navigation_summary"
android:title="@string/prefs_volume_key_navigation_title"
app:iconSpaceReserved="false"/>

<CheckBoxPreference
android:defaultValue="false"
android:key="@string/prefs_focus_mode"
android:persistent="true"
android:summary="@string/prefs_focus_mode_summary"
android:title="@string/prefs_focus_mode_title"
app:iconSpaceReserved="false"/>
</PreferenceCategory>

<PreferenceCategory
Expand Down
Loading