From a887c00ac97d0c08afcac947ce60986263128d6b Mon Sep 17 00:00:00 2001 From: abdulrahmansaid Date: Mon, 12 May 2025 16:12:18 +0300 Subject: [PATCH 1/2] Add permissions --- .../metacto/catalogapp/di/ViewModelsModule.kt | 2 + .../main/components/MainContent.kt | 32 ++++- .../permissions/PermissionsContract.kt | 18 +++ .../permissions/PermissionsScreen.kt | 28 ++++ .../permissions/PermissionsViewModel.kt | 25 ++++ .../components/PermissionsContent.kt | 127 ++++++++++++++++++ .../iosApp/Configuration/Info.plist | 12 +- 7 files changed, 238 insertions(+), 6 deletions(-) create mode 100644 samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsContract.kt create mode 100644 samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsScreen.kt create mode 100644 samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsViewModel.kt create mode 100644 samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/components/PermissionsContent.kt diff --git a/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/di/ViewModelsModule.kt b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/di/ViewModelsModule.kt index 5e9f5adc..fb4e5bab 100644 --- a/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/di/ViewModelsModule.kt +++ b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/di/ViewModelsModule.kt @@ -9,6 +9,7 @@ import com.metacto.catalogapp.presentation.main.MainViewModel import com.metacto.catalogapp.presentation.mediaManager.MediaManagerViewModel import com.metacto.catalogapp.presentation.notifications.NotificationsSamplesViewModel import com.metacto.core.ui.di.commonViewModel +import com.metacto.catalogapp.presentation.permissions.PermissionsViewModel import org.koin.dsl.module val viewModelsModule = module { @@ -19,5 +20,6 @@ val viewModelsModule = module { commonViewModel { FilesSamplesViewModel() } commonViewModel { ImagePickerSamplesViewModel() } commonViewModel { MainViewModel() } + commonViewModel { PermissionsViewModel() } single { AppViewModel() } } diff --git a/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/main/components/MainContent.kt b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/main/components/MainContent.kt index 9a9440e0..69adddeb 100644 --- a/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/main/components/MainContent.kt +++ b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/main/components/MainContent.kt @@ -3,6 +3,7 @@ package com.metacto.catalogapp.presentation.main.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -13,6 +14,7 @@ import com.metacto.catalogapp.presentation.main.MainContract.Event import com.metacto.catalogapp.presentation.main.MainContract.State import com.metacto.catalogapp.presentation.mediaManager.MediaManagerScreen import com.metacto.catalogapp.presentation.notifications.NotificationsSamplesScreen +import com.metacto.catalogapp.presentation.permissions.PermissionsScreen import com.metacto.catalogapp.presentation.theme.spacings import com.metacto.core.ui.components.buttons.PrimaryFilledButton import com.metacto.core.ui.components.containers.ScreenColumn @@ -46,7 +48,9 @@ internal fun MainContent( onClick = { navManage.navigate(FilesSamplesScreen()) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .padding(top = spacings.spacing16), ) PrimaryFilledButton( @@ -54,7 +58,9 @@ internal fun MainContent( onClick = { navManage.navigate(ImagePickerSamplesScreen()) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .padding(top = spacings.spacing16), ) PrimaryFilledButton( @@ -62,7 +68,9 @@ internal fun MainContent( onClick = { navManage.navigate(ImagePreloaderScreen()) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .padding(top = spacings.spacing16), ) PrimaryFilledButton( @@ -70,7 +78,21 @@ internal fun MainContent( onClick = { navManage.navigate(MediaManagerScreen()) }, - modifier = Modifier.fillMaxWidth() - ) + modifier = Modifier + .fillMaxWidth() + .padding(top = spacings.spacing16), + + ) + + PrimaryFilledButton( + text = "Permissions samples", + onClick = { + navManage.navigate(PermissionsScreen()) + }, + modifier = Modifier + .fillMaxWidth() + .padding(top = spacings.spacing16), + + ) } } \ No newline at end of file diff --git a/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsContract.kt b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsContract.kt new file mode 100644 index 00000000..831965a3 --- /dev/null +++ b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsContract.kt @@ -0,0 +1,18 @@ +package com.metacto.catalogapp.presentation.permissions + +import com.metacto.core.ui.base.ViewEvent +import com.metacto.core.ui.base.ViewSideEffect +import com.metacto.core.ui.base.ViewState + +class PermissionsContract { + + data class State( + val isInitialized: Boolean = false + ) : ViewState + + sealed class Event : ViewEvent { + data object Init : Event() + } + + sealed class Effect : ViewSideEffect +} diff --git a/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsScreen.kt b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsScreen.kt new file mode 100644 index 00000000..2ea76eb4 --- /dev/null +++ b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsScreen.kt @@ -0,0 +1,28 @@ +package com.metacto.catalogapp.presentation.permissions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import com.metacto.catalogapp.presentation.permissions.PermissionsContract.Event +import com.metacto.catalogapp.presentation.permissions.components.PermissionsContent +import com.metacto.core.ui.base.BaseScreen +import com.metacto.core.ui.base.SIDE_EFFECTS_KEY +import com.metacto.core.ui.base.rememberViewModel + +internal class PermissionsScreen : BaseScreen() { + @Composable + override fun Content() { + // Get the view model + val viewModel = rememberViewModel() + + // Init view model + LaunchedEffect(SIDE_EFFECTS_KEY) { + viewModel.setEvent(Event.Init) + } + + // Render content + PermissionsContent( + state = viewModel.viewState.value, + onEvent = viewModel::setEvent + ) + } +} diff --git a/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsViewModel.kt b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsViewModel.kt new file mode 100644 index 00000000..c730e931 --- /dev/null +++ b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/PermissionsViewModel.kt @@ -0,0 +1,25 @@ +package com.metacto.catalogapp.presentation.permissions + +import com.metacto.catalogapp.presentation.base.BaseViewModel +import com.metacto.catalogapp.presentation.permissions.PermissionsContract.Effect +import com.metacto.catalogapp.presentation.permissions.PermissionsContract.Event +import com.metacto.catalogapp.presentation.permissions.PermissionsContract.State + +class PermissionsViewModel : BaseViewModel() { + + override fun setInitialState() = State() + + override fun handleEvents(event: Event): Any = when (event) { + Event.Init -> init() + } + + private fun init() { + // Validate if already initialized + if (currentState.isInitialized) return + + // Init + + // Update the flag + setState { copy(isInitialized = true) } + } +} diff --git a/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/components/PermissionsContent.kt b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/components/PermissionsContent.kt new file mode 100644 index 00000000..dd3c81ab --- /dev/null +++ b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/permissions/components/PermissionsContent.kt @@ -0,0 +1,127 @@ +package com.metacto.catalogapp.presentation.permissions.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.metacto.catalogapp.presentation.components.containers.AppScreenColumn +import com.metacto.catalogapp.presentation.permissions.PermissionsContract.Event +import com.metacto.catalogapp.presentation.permissions.PermissionsContract.State +import com.metacto.catalogapp.presentation.theme.colors +import com.metacto.catalogapp.presentation.theme.spacings +import com.metacto.catalogapp.presentation.theme.typography +import com.metacto.core.ui.components.buttons.PrimaryFilledButton +import com.metacto.core.ui.globalState.ICoreGlobalState +import com.metacto.core.ui.globalState.models.SnackBarParams +import com.metacto.core.ui.globalState.models.SnackBarType +import com.metacto.core.ui.navigation.NavManager +import com.metacto.core.ui.permissions.IPermissionManager +import com.metacto.core.ui.permissions.enums.Permission +import com.metacto.core.ui.permissions.enums.PermissionState +import kotlinx.coroutines.launch +import org.koin.compose.koinInject + +@Composable +internal fun PermissionsContent( + state: State, + onEvent: (Event) -> Unit +) { + // Di + val navManager = koinInject() + val permissionManager = koinInject() + val coreGlobalState = koinInject() + + + // State + val coroutine = rememberCoroutineScope() + + // Container column + AppScreenColumn( + title = "Permissions", + isScrollable = true, + showToolbar = true, + showBack = true, + onBackClick = { + navManager.goBack() + }, + verticalArrangement = Arrangement.spacedBy(spacings.spacing16) + ) { + Permission.entries.map { permission -> + Column( + verticalArrangement = Arrangement.spacedBy(spacings.spacing8) + ) { + Text( + permission.name, + style = typography.primary.bold._16, + color = colors.black, + ) + + PrimaryFilledButton( + text = "request ${permission.name.lowercase()}", + onClick = { + coroutine.launch { + permissionManager.requestPermission(permission) + } + }, + modifier = Modifier.fillMaxWidth() + + ) + + PrimaryFilledButton( + text = "is ${permission.name.lowercase()} granted", + onClick = { + coroutine.launch { + val isGranted = permissionManager.isPermissionGranted(permission) + if (isGranted) { + coreGlobalState.snackBar( + SnackBarParams( + message = "${permission.name.lowercase()} is granted", + type = SnackBarType.SUCCESS, + ) + ) + } else { + coreGlobalState.snackBar( + SnackBarParams( + message = "${permission.name.lowercase()} is not granted", + type = SnackBarType.ERROR, + ) + ) + } + + } + }, + modifier = Modifier.fillMaxWidth() + ) + + PrimaryFilledButton( + text = "get ${permission.name.lowercase()} state", + onClick = { + coroutine.launch { + val status = permissionManager.getPermissionState(permission) + if(status == PermissionState.Granted) { + coreGlobalState.snackBar( + SnackBarParams( + message = "${permission.name.lowercase()} state is $status", + type = SnackBarType.SUCCESS, + ) + ) + } else { + coreGlobalState.snackBar( + SnackBarParams( + message = "${permission.name.lowercase()} state is $status", + type = SnackBarType.ERROR, + ) + ) + } + } + }, + modifier = Modifier.fillMaxWidth() + ) + + } + } + } +} diff --git a/samples/catalog-ios-app/iosApp/Configuration/Info.plist b/samples/catalog-ios-app/iosApp/Configuration/Info.plist index edb30e37..1d74d4cf 100644 --- a/samples/catalog-ios-app/iosApp/Configuration/Info.plist +++ b/samples/catalog-ios-app/iosApp/Configuration/Info.plist @@ -18,7 +18,15 @@ en - CFBundleName + NSMotionUsageDescription + This app needs access to motion data to [explain your specific use case here, e.g., "track your fitness activities", "detect device orientation", etc.] + NSLocationWhenInUseUsageDescription + This app needs location access to [your explanation] + NSBluetoothAlwaysUsageDescription + This app needs Bluetooth access to connect to and communicate with nearby Bluetooth devices. + NSBluetoothPeripheralUsageDescription + This app needs Bluetooth access to connect to and communicate with nearby Bluetooth devices. + CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) @@ -68,6 +76,8 @@ audio fetch remote-notification + bluetooth-central + bluetooth-peripheral UILaunchScreen From 1d4b1e0b3cc664988b23b1b7eb697db7fea9adb4 Mon Sep 17 00:00:00 2001 From: abdulrahmansaid Date: Wed, 11 Jun 2025 21:16:55 +0300 Subject: [PATCH 2/2] resolve conflict --- .../presentation/main/components/MainContent.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/main/components/MainContent.kt b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/main/components/MainContent.kt index aeebfc18..4ddf40eb 100644 --- a/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/main/components/MainContent.kt +++ b/samples/catalog-app-shared/src/commonMain/kotlin/com/metacto/catalogapp/presentation/main/components/MainContent.kt @@ -95,8 +95,6 @@ internal fun MainContent( .padding(top = spacings.spacing16), ) - modifier = Modifier.fillMaxWidth() - ) // Phone number samples PrimaryFilledButton( @@ -104,7 +102,10 @@ internal fun MainContent( onClick = { navManage.navigate(PhoneNumberScreen()) }, - modifier = Modifier.fillMaxWidth() - ) + modifier = Modifier + .fillMaxWidth() + .padding(top = spacings.spacing16), + + ) } } \ No newline at end of file