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 94602694..d1131955 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 @@ -10,6 +10,7 @@ import com.metacto.catalogapp.presentation.mediaManager.MediaManagerViewModel import com.metacto.catalogapp.presentation.notifications.NotificationsSamplesViewModel import com.metacto.catalogapp.presentation.phoneNumber.PhoneNumberViewModel import com.metacto.core.ui.di.commonViewModel +import com.metacto.catalogapp.presentation.permissions.PermissionsViewModel import org.koin.dsl.module val viewModelsModule = module { @@ -21,5 +22,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 1a439eae..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 @@ -15,6 +15,7 @@ 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.phoneNumber.PhoneNumberScreen +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 @@ -48,7 +49,9 @@ internal fun MainContent( onClick = { navManage.navigate(FilesSamplesScreen()) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .padding(top = spacings.spacing16), ) PrimaryFilledButton( @@ -56,7 +59,9 @@ internal fun MainContent( onClick = { navManage.navigate(ImagePickerSamplesScreen()) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .padding(top = spacings.spacing16), ) PrimaryFilledButton( @@ -64,7 +69,9 @@ internal fun MainContent( onClick = { navManage.navigate(ImagePreloaderScreen()) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .padding(top = spacings.spacing16), ) PrimaryFilledButton( @@ -72,8 +79,22 @@ 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), + + ) // Phone number samples PrimaryFilledButton( @@ -81,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 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