Skip to content

Commit

Permalink
feat: permission request screen
Browse files Browse the repository at this point in the history
  • Loading branch information
SuhasDissa committed Apr 25, 2024
1 parent 626b47e commit acda51c
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 54 deletions.
73 changes: 73 additions & 0 deletions app/src/main/java/com/bnyro/clock/domain/model/Permission.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.bnyro.clock.domain.model

import android.Manifest
import android.app.Activity
import android.app.AlarmManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat
import androidx.core.net.toUri
import com.bnyro.clock.BuildConfig
import com.bnyro.clock.R

sealed class Permission(
@StringRes
val titleRes: Int,
@StringRes
val descriptionRes: Int,
@DrawableRes
val iconRes: Int
) {
abstract fun hasPermission(context: Context): Boolean
abstract fun requestPermission(activity: Activity)

object NotificationPermission :
Permission(
titleRes = R.string.notification_permission_title,
descriptionRes = R.string.notification_permission_description,
iconRes = R.drawable.ic_alarm
) {
override fun hasPermission(context: Context): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return true
return ActivityCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
}

override fun requestPermission(activity: Activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
1
)
}
}

object AlarmPermission : Permission(
titleRes = R.string.alarm_permission_title,
descriptionRes = R.string.alarm_permission_description,
iconRes = R.drawable.ic_alarm
) {

override fun hasPermission(context: Context): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
return alarmManager.canScheduleExactAlarms()
}

override fun requestPermission(activity: Activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return
val intent = Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
data = "package:${BuildConfig.APPLICATION_ID}".toUri()
}
activity.startActivity(intent)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ import com.bnyro.clock.presentation.screens.settings.model.SettingsModel

@Composable
fun MainNavContainer(
settingsModel: SettingsModel, initialTab: HomeRoutes
settingsModel: SettingsModel, initialTab: HomeRoutes,
startDestination: String
) {
val navController = rememberNavController()
AppNavHost(
navController, settingsModel, initialTab = initialTab, modifier = Modifier.fillMaxSize()
navController,
settingsModel,
initialTab = initialTab,
startDestination = startDestination,
modifier = Modifier.fillMaxSize()
)
}
20 changes: 19 additions & 1 deletion app/src/main/java/com/bnyro/clock/navigation/NavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.navigation.compose.composable
import com.bnyro.clock.presentation.screens.alarm.model.AlarmModel
import com.bnyro.clock.presentation.screens.alarmpicker.AlarmPickerScreen
import com.bnyro.clock.presentation.screens.clock.model.ClockModel
import com.bnyro.clock.presentation.screens.permission.PermissionScreen
import com.bnyro.clock.presentation.screens.settings.SettingsScreen
import com.bnyro.clock.presentation.screens.settings.model.SettingsModel
import com.bnyro.clock.presentation.screens.stopwatch.model.StopwatchModel
Expand All @@ -22,14 +23,15 @@ fun AppNavHost(
navController: NavHostController,
settingsModel: SettingsModel,
initialTab: HomeRoutes,
startDestination: String,
modifier: Modifier = Modifier
) {
val alarmModel: AlarmModel = viewModel()
val timerModel: TimerModel = viewModel()
val stopwatchModel: StopwatchModel = viewModel()
val clockModel: ClockModel = viewModel()

NavHost(navController, startDestination = NavRoutes.Home.route, modifier = modifier) {
NavHost(navController, startDestination = startDestination, modifier = modifier) {
composable(NavRoutes.Home.route,
enterTransition = {
slideIntoContainer(
Expand Down Expand Up @@ -83,5 +85,21 @@ fun AppNavHost(
navController.popBackStack()
}
}

composable(NavRoutes.Permissions.route,
enterTransition = {
slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.Up,
initialOffset = { it / 4 }) + fadeIn()
},
exitTransition = {
slideOutOfContainer(
AnimatedContentTransitionScope.SlideDirection.Down,
targetOffset = { it / 4 }) + fadeOut()
}) {
PermissionScreen {
navController.navigate(NavRoutes.Home.route)
}
}
}
}
2 changes: 2 additions & 0 deletions app/src/main/java/com/bnyro/clock/navigation/NavRoutes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ sealed class NavRoutes(
val routeWithArgs = "$route/{$alarmId}"
val args = listOf(navArgument(alarmId) { NavType.LongType })
}

object Permissions : NavRoutes("permissions")
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.bnyro.clock.presentation.screens.alarm

import android.content.Intent
import android.os.Build
import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
Expand All @@ -15,23 +12,19 @@ import androidx.compose.material.icons.rounded.Add
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import com.bnyro.clock.BuildConfig
import com.bnyro.clock.R
import com.bnyro.clock.navigation.TopBarScaffold
import com.bnyro.clock.presentation.components.BlobIconBox
import com.bnyro.clock.presentation.components.ClickableIcon
import com.bnyro.clock.presentation.screens.alarm.components.AlarmFilterSection
import com.bnyro.clock.presentation.screens.alarm.components.AlarmItem
import com.bnyro.clock.presentation.screens.alarm.model.AlarmModel
import com.bnyro.clock.util.AlarmHelper

@Composable
fun AlarmScreen(
Expand All @@ -43,17 +36,6 @@ fun AlarmScreen(
val alarms by alarmModel.alarms.collectAsState()
val filters by alarmModel.filters.collectAsState()

LaunchedEffect(Unit) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) {
if (!AlarmHelper.hasPermission(context)) {
val intent = Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
data = "package:${BuildConfig.APPLICATION_ID}".toUri()
}
context.startActivity(intent)
}
}
}

TopBarScaffold(title = stringResource(R.string.alarm), onClickSettings, fab = {
if (!alarmModel.showFilter) {
FloatingActionButton(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.bnyro.clock.presentation.screens.permission

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import com.bnyro.clock.domain.model.Permission

class PermissionModel(application: Application) :
AndroidViewModel(application) {
val requiredPermissions = allPermissions.filter {
!it.hasPermission(application)
}

companion object {
val allPermissions = listOf(
Permission.AlarmPermission,
Permission.NotificationPermission
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.bnyro.clock.presentation.screens.permission

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
import com.bnyro.clock.presentation.screens.permission.components.PermissionRequestPage
import com.bnyro.clock.ui.MainActivity
import kotlinx.coroutines.launch

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PermissionScreen(onClose: () -> Unit) {
val permissionModel: PermissionModel = viewModel()
val pagerState = rememberPagerState() { permissionModel.requiredPermissions.size }
val scope = rememberCoroutineScope()
val context = LocalContext.current
HorizontalPager(
state = pagerState,
) { page ->
with(permissionModel.requiredPermissions[page]) {
PermissionRequestPage(
title = stringResource(id = titleRes),
subtitle = stringResource(id = descriptionRes),
onClickConfirm = {
requestPermission(context as MainActivity)
if (page + 1 < permissionModel.requiredPermissions.size) {
scope.launch {
pagerState.animateScrollToPage(page + 1)
}
} else {
onClose()
}
},
onClickCancel = {
if (page + 1 < permissionModel.requiredPermissions.size) {
scope.launch {
pagerState.animateScrollToPage(page + 1)
}
} else {
onClose()
}
},
icon = iconRes
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.bnyro.clock.presentation.screens.permission.components

import androidx.annotation.DrawableRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.bnyro.clock.R
import com.bnyro.clock.presentation.components.BlobIconBox

@Composable
fun PermissionRequestPage(
title: String,
subtitle: String,
onClickConfirm: () -> Unit,
onClickCancel: () -> Unit,
@DrawableRes icon: Int,
) {
Column(
Modifier.fillMaxSize()
) {
Column(
Modifier
.fillMaxWidth()
.weight(2f)
) {
BlobIconBox(icon)
}
Column(
Modifier
.fillMaxWidth()
.padding(start = 8.dp, end = 8.dp, bottom = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = title,
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = subtitle,
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Normal,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(48.dp))
Button(
onClick = onClickConfirm,
contentPadding = PaddingValues(horizontal = 48.dp, vertical = 16.dp)
) {
Text(
text = stringResource(R.string.allow),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
}
TextButton(
onClick = onClickCancel, colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
)
) {
Text(
text = stringResource(R.string.maybe_later),
style = MaterialTheme.typography.bodyLarge
)
}
}
}
}

@Preview(showBackground = true)
@Composable
fun PermissionRequestPagePreview() {
PermissionRequestPage(
title = "Enable Alarm Permissions",
subtitle = "Alarm Permissions are required to schedule alarms",
onClickConfirm = {},
onClickCancel = {},
icon = R.drawable.ic_alarm
)
}
Loading

0 comments on commit acda51c

Please sign in to comment.