Skip to content
Merged
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
42 changes: 42 additions & 0 deletions PermissionManager/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Infomaniak Core - Android
* Copyright (C) 2026 Infomaniak Network SA
Comment thread
NicolasBourdin88 marked this conversation as resolved.
*
* 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 <http://www.gnu.org/licenses/>.
*/
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
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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),
}
Comment thread
NicolasBourdin88 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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 {
Comment thread
NicolasBourdin88 marked this conversation as resolved.
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()
}
Comment thread
NicolasBourdin88 marked this conversation as resolved.

return fun() {
if (status.isGranted) {
action()
} else {
launchPermissionRequest()
actionFired.complete()
}
}
Comment thread
NicolasBourdin88 marked this conversation as resolved.
}
Comment thread
NicolasBourdin88 marked this conversation as resolved.
Comment thread
NicolasBourdin88 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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()
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Boolean>,
) : 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()
}
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
3 changes: 3 additions & 0 deletions gradle/core.versions.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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" }
Expand Down Expand Up @@ -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" }
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ include(
":Notifications",
":Notifications:Registration",
":Onboarding",
":PermissionManager",
":PrivacyManagement",
":RecyclerView",
":Sentry",
Expand Down
Loading