diff --git a/PermissionManager/build.gradle.kts b/PermissionManager/build.gradle.kts
new file mode 100644
index 000000000..b85ce67e1
--- /dev/null
+++ b/PermissionManager/build.gradle.kts
@@ -0,0 +1,42 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+plugins {
+ alias(core.plugins.android.library)
+ alias(core.plugins.kotlin.android)
+ alias(core.plugins.compose.compiler)
+}
+
+val coreCompileSdk: Int by rootProject.extra
+val coreMinSdk: Int by rootProject.extra
+val javaVersion: JavaVersion by rootProject.extra
+
+android {
+ namespace = "com.infomaniak.core.permissionmanager"
+ compileSdk = coreCompileSdk
+
+ defaultConfig {
+ minSdk = coreMinSdk
+ }
+
+ compileOptions {
+ sourceCompatibility = javaVersion
+ targetCompatibility = javaVersion
+ }
+
+ buildFeatures {
+ compose = true
+ }
+
+ kotlin {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.fromTarget(javaVersion.toString()))
+ }
+ }
+}
+
+dependencies {
+ implementation(platform(core.compose.bom))
+ implementation(core.compose.runtime)
+ implementation(core.compose.ui)
+ implementation(core.accompanist.permissions)
+}
diff --git a/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/PermissionManagerState.kt b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/PermissionManagerState.kt
new file mode 100644
index 000000000..e8e47e0bc
--- /dev/null
+++ b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/PermissionManagerState.kt
@@ -0,0 +1,45 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.permissionmanager
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.remember
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.rememberPermissionState
+
+@OptIn(ExperimentalPermissionsApi::class)
+@Composable
+fun rememberPermissionManagerState(permissionType: PermissionType): PermissionManagerState {
+ val permission = permissionType.permission
+
+ return if (permission == null) {
+ remember { UnsupportedApiPermissionManagerState }
+ } else {
+ val permissionState = rememberPermissionState(permission)
+ remember(permission) { SupportedApiPermissionManagerState(permissionState) }
+ }
+}
+
+@Stable
+sealed interface PermissionManagerState {
+ fun launchPermissionRequestIfNeeded()
+
+ @Composable
+ fun dropIfDenied(action: () -> Unit): () -> Unit
+}
diff --git a/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/PermissionType.kt b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/PermissionType.kt
new file mode 100644
index 000000000..71fd665ed
--- /dev/null
+++ b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/PermissionType.kt
@@ -0,0 +1,26 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.permissionmanager
+
+import android.Manifest
+import android.os.Build.VERSION.SDK_INT
+
+enum class PermissionType(val permission: String?) {
+ Notification(permission = if (SDK_INT >= 33) Manifest.permission.POST_NOTIFICATIONS else null),
+ WriteExternalStorage(permission = if (SDK_INT >= 29) null else Manifest.permission.WRITE_EXTERNAL_STORAGE),
+}
diff --git a/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/SupportedApiPermissionManagerState.kt b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/SupportedApiPermissionManagerState.kt
new file mode 100644
index 000000000..36fb6c8b2
--- /dev/null
+++ b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/SupportedApiPermissionManagerState.kt
@@ -0,0 +1,62 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.permissionmanager
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.PermissionState
+import com.google.accompanist.permissions.isGranted
+import kotlinx.coroutines.CompletableJob
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.first
+
+@Stable
+@OptIn(ExperimentalPermissionsApi::class)
+internal class SupportedApiPermissionManagerState(
+ private val permissionState: PermissionState,
+) : PermissionManagerState {
+ override fun launchPermissionRequestIfNeeded() {
+ if (permissionState.status.isGranted) return
+ permissionState.launchPermissionRequest()
+ }
+
+ @Composable
+ override fun dropIfDenied(action: () -> Unit): () -> Unit = with(permissionState) {
+ val isGrantedFlow = remember(permission) { snapshotFlow { status.isGranted } }
+ val actionFired: CompletableJob = remember(permission) { Job() }
+
+ LaunchedEffect(permission) {
+ actionFired.join()
+ isGrantedFlow.first { it }
+ action()
+ }
+
+ return fun() {
+ if (status.isGranted) {
+ action()
+ } else {
+ launchPermissionRequest()
+ actionFired.complete()
+ }
+ }
+ }
+}
diff --git a/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/UnsupportedApiPermissionManagerState.kt b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/UnsupportedApiPermissionManagerState.kt
new file mode 100644
index 000000000..d92d762ea
--- /dev/null
+++ b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/UnsupportedApiPermissionManagerState.kt
@@ -0,0 +1,29 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.permissionmanager
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+
+@Stable
+internal object UnsupportedApiPermissionManagerState : PermissionManagerState {
+ override fun launchPermissionRequestIfNeeded() = Unit
+
+ @Composable
+ override fun dropIfDenied(action: () -> Unit): () -> Unit = action
+}
diff --git a/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/rationale/RationalePermissionManagerState.kt b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/rationale/RationalePermissionManagerState.kt
new file mode 100644
index 000000000..6b96f0958
--- /dev/null
+++ b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/rationale/RationalePermissionManagerState.kt
@@ -0,0 +1,49 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.permissionmanager.rationale
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.rememberPermissionState
+import com.infomaniak.core.permissionmanager.PermissionType
+
+@OptIn(ExperimentalPermissionsApi::class)
+@Composable
+fun rememberRationalePermissionManagerState(permissionType: PermissionType): RationalePermissionManagerState {
+ val permission = permissionType.permission
+
+ return if (permission == null) {
+ remember { UnsupportedApiRationalePermissionManagerState }
+ } else {
+ val permissionState = rememberPermissionState(permission)
+ val shouldShowRationale = rememberSaveable(permission) { mutableStateOf(false) }
+ remember(permission) { SupportedApiRationalePermissionManagerState(permissionState, shouldShowRationale) }
+ }
+}
+
+@Stable
+sealed interface RationalePermissionManagerState {
+ val shouldShowRationale: Boolean
+
+ fun requestPermissionOrShowRationaleIfNeeded()
+ fun dismissAndAskPermission()
+}
diff --git a/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/rationale/SupportedApiRationalePermissionManagerState.kt b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/rationale/SupportedApiRationalePermissionManagerState.kt
new file mode 100644
index 000000000..2c4f32a51
--- /dev/null
+++ b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/rationale/SupportedApiRationalePermissionManagerState.kt
@@ -0,0 +1,50 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.permissionmanager.rationale
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.PermissionState
+import com.google.accompanist.permissions.isGranted
+import com.google.accompanist.permissions.shouldShowRationale
+
+@Stable
+@OptIn(ExperimentalPermissionsApi::class)
+internal class SupportedApiRationalePermissionManagerState(
+ private val permissionState: PermissionState,
+ private val _shouldShowRationale: MutableState,
+) : RationalePermissionManagerState {
+ override val shouldShowRationale: Boolean by _shouldShowRationale
+
+ override fun requestPermissionOrShowRationaleIfNeeded() {
+ if (permissionState.status.isGranted) return
+
+ if (permissionState.status.shouldShowRationale) {
+ _shouldShowRationale.value = true
+ } else {
+ permissionState.launchPermissionRequest()
+ }
+ }
+
+ override fun dismissAndAskPermission() {
+ _shouldShowRationale.value = false
+ permissionState.launchPermissionRequest()
+ }
+}
diff --git a/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/rationale/UnsupportedApiRationalePermissionManagerState.kt b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/rationale/UnsupportedApiRationalePermissionManagerState.kt
new file mode 100644
index 000000000..ffb038a3b
--- /dev/null
+++ b/PermissionManager/src/main/kotlin/com/infomaniak/core/permissionmanager/rationale/UnsupportedApiRationalePermissionManagerState.kt
@@ -0,0 +1,28 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.permissionmanager.rationale
+
+import androidx.compose.runtime.Stable
+
+@Stable
+internal object UnsupportedApiRationalePermissionManagerState : RationalePermissionManagerState {
+ override val shouldShowRationale: Boolean get() = false
+
+ override fun requestPermissionOrShowRationaleIfNeeded() = Unit
+ override fun dismissAndAskPermission() = Unit
+}
diff --git a/gradle/core.versions.toml b/gradle/core.versions.toml
index 512a06d89..390675476 100644
--- a/gradle/core.versions.toml
+++ b/gradle/core.versions.toml
@@ -1,4 +1,5 @@
[versions]
+accompanistPermissions = "0.37.3"
activityCompose = "1.10.1" # TODO: Update to 1.11.0 or later after we bump compileSdk to 36
adaptive = "1.2.0"
agp = "8.13.0" # Delete this to-do on agp 9+ after searching and checking the project for the following: TODO[AGP9
@@ -59,6 +60,7 @@ swipeRefreshLayout = "1.2.0"
workManager = "2.11.0"
[libraries]
+accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" }
activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
android-login = { module = "com.github.infomaniak:android-login", version.ref = "androidLogin" }
androidx-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "adaptive" }
@@ -141,6 +143,7 @@ infomaniak-core-network-ktor = { module = "com.infomaniak.core:Network.Ktor" }
infomaniak-core-notifications = { module = "com.infomaniak.core:Notifications" }
infomaniak-core-notifications-registration = { module = "com.infomaniak.core:Notifications.Registration" }
infomaniak-core-onboarding = { module = "com.infomaniak.core:Onboarding" }
+infomaniak-core-permissionmanager = { module = "com.infomaniak.core:PermissionManager" }
infomaniak-core-privacymanagement = { module = "com.infomaniak.core:PrivacyManagement" }
infomaniak-core-recyclerview = { module = "com.infomaniak.core:RecyclerView" }
infomaniak-core-sentry = { module = "com.infomaniak.core:Sentry" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 9cf95d0cf..7be4eb6ab 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -56,6 +56,7 @@ include(
":Notifications",
":Notifications:Registration",
":Onboarding",
+ ":PermissionManager",
":PrivacyManagement",
":RecyclerView",
":Sentry",