diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..7643783a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,123 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9a9d8839..1d69610d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -97,6 +97,7 @@ dependencies { // Lifecycle implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.lifecycle.viewmodel.ktx) implementation(libs.androidx.lifecycle.livedata.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) @@ -127,6 +128,14 @@ dependencies { implementation(libs.zoomable) implementation(libs.zoomable.image.coil) + + // CameraX + implementation(libs.camera.core) + implementation(libs.camera.camera2) + implementation(libs.camera.lifecycle) + implementation(libs.camera.extensions) + implementation(libs.camera.video) + implementation(libs.camera.view) } ktlint { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8e85acf3..df16769f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,11 @@ + + + photoUri = uris } + val controller = remember { + LifecycleCameraController(context).apply { + setEnabledUseCases(CameraController.IMAGE_CAPTURE) + } + } val permissionState = rememberPermissionState( permission = Manifest.permission.RECORD_AUDIO ) + val camPermissionState = rememberPermissionState( + permission = Manifest.permission.CAMERA + ) + // add note if (isNew) { SideEffect { permissionState.launchPermissionRequest() } + if ((permissionState.status.isGranted || !permissionState.status.isGranted) && !camPermissionState.status.isGranted) { + SideEffect { + camPermissionState.launchPermissionRequest() + } + } } val speechRecognizerLauncher = rememberLauncherForActivityResult(contract = SpeechRecognizerContract(), onResult = { it?.let { @@ -236,6 +275,18 @@ fun AddEditScreen( .navigationBarsPadding() .padding(16.dp) ) { + BottomSheetOptions( + text = stringResource(R.string.take_image), + icon = painterResource(id = R.drawable.camera_icon), + onClick = { + if (camPermissionState.status.isGranted) { + openCameraBottomSheet = true + } else { + camPermissionState.launchPermissionRequest() + } + showSheet = false + } + ) BottomSheetOptions( text = stringResource(R.string.add_image), icon = painterResource(id = R.drawable.gallery_icon), @@ -345,7 +396,6 @@ fun AddEditScreen( } } } - TextField( modifier = Modifier.fillMaxWidth(), value = title, onValueChange = { newTitle -> if (isNew) { @@ -430,7 +480,6 @@ fun AddEditScreen( } } } - TextDialog( title = stringResource(R.string.are_you_sure), description = stringResource(R.string.the_text_change_will_not_be_saved), @@ -441,4 +490,84 @@ fun AddEditScreen( cancelDialogState.value = false } ) + + if (openCameraBottomSheet) { + BottomSheetScaffold(scaffoldState = scaffoldState, sheetPeekHeight = 0.dp, sheetContent = {}) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(it) + ) { + CameraPreview(controller = controller, modifier = Modifier.fillMaxSize()) + + IconButton(onClick = { + openCameraBottomSheet = false + }, modifier = Modifier.offset(16.dp, 16.dp)) { + Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "navigate back") + } + Row( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .padding(16.dp), horizontalArrangement = Arrangement.SpaceAround + ) { + IconButton(onClick = { + controller.cameraSelector = + if (controller.cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) { + CameraSelector.DEFAULT_FRONT_CAMERA + } else { + CameraSelector.DEFAULT_BACK_CAMERA + } + }) { + Icon(imageVector = Icons.Filled.Cameraswitch, contentDescription = "camera Switch") + } + IconButton(onClick = { + takePhoto(controller, context, onPhotoCaptured = { receviedUri -> + receviedUri?.let { + photoUri += it + } + }) + }) { + Icon(imageVector = Icons.Filled.PhotoCamera, contentDescription = "Click To Capture") + } + } + } + } + } +} + +fun takePhoto(controller: LifecycleCameraController, context: Context, onPhotoCaptured: (Uri?) -> Unit) { + controller.takePicture(ContextCompat.getMainExecutor(context), object : OnImageCapturedCallback() { + override fun onCaptureSuccess(image: ImageProxy) { + super.onCaptureSuccess(image) + val matrix = Matrix().apply { + postRotate(image.imageInfo.rotationDegrees.toFloat()) + if (controller.cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) { + postScale(-1f, 1f) + } + } + val bitmap = Bitmap.createBitmap(image.toBitmap(), 0, 0, image.width, image.height, matrix, true) + Toast.makeText(context, "Photo Attached Successfully", Toast.LENGTH_SHORT).show() + onPhotoCaptured(bitmap.toUri(context = context)) + } + + override fun onError(exception: ImageCaptureException) { + super.onError(exception) + Toast.makeText(context, "Something Went Wrong ! Try Again", Toast.LENGTH_SHORT).show() + } + }) +} + +fun Bitmap.toUri(context: Context, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG): Uri? { + val bytes = ByteArrayOutputStream() + compress(format, 100, bytes) + val path = MediaStore.Images.Media.insertImage( + context.contentResolver, + this, + "${System.currentTimeMillis()}", + null + ) + return Uri.parse(path) } + + diff --git a/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditViewModel.kt b/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditViewModel.kt index 095461aa..0ebc2b40 100644 --- a/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditViewModel.kt +++ b/app/src/main/java/com/aritra/notify/ui/screens/notes/addEditScreen/AddEditViewModel.kt @@ -1,6 +1,7 @@ package com.aritra.notify.ui.screens.notes.addEditScreen import android.app.Application +import android.graphics.Bitmap import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -11,6 +12,9 @@ import com.aritra.notify.domain.usecase.SaveSelectedImageUseCase import com.aritra.notify.utils.toFile import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -147,7 +151,7 @@ class AddEditViewModel @Inject constructor( _noteModel.postValue(noteModel.value?.copy(note = description)) } - /* fun updateImage(imageList: List) { - _noteModel.postValue(noteModel.value?.copy(image = imageList)) - }*/ + /* fun updateImage(imageList: List) { + _noteModel.postValue(noteModel.value?.copy(image = imageList)) + }*/ } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2b4a25bd..6bb573bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -41,9 +41,11 @@ Rate us on Google Play https://play.google.com/store/apps/details?id=com.aritra.notify Image + No Clicked Photos Clear Image Speech to Text Add image + Capture a Photo Add Box notify Request permission diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4f763b60..2b149690 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,7 @@ devtools-ksp = "1.9.0-1.0.12" zoomable = "0.6.2" zoomableImageCoil = "0.6.2" ktlint = "11.6.0" +camerax_version = "1.4.0-alpha01" [libraries] accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" } @@ -58,6 +59,7 @@ androidx-lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensi androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleVersion" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleVersion" } +androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleVersion" } androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleVersion" } androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" } androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } @@ -77,6 +79,14 @@ hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hil junit = { module = "junit:junit", version.ref = "junit" } zoomable = { module = "me.saket.telephoto:zoomable", version.ref = "zoomable" } zoomable-image-coil = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "zoomableImageCoil" } +camera-core = { module = "androidx.camera:camera-core", version.ref = "camerax_version" } +camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax_version" } +camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax_version" } +camera-video = { module = "androidx.camera:camera-video", version.ref = "camerax_version" } +camera-mlkit-vision = { module = "androidx.camera:camera-mlkit-vision", version.ref = "camerax_version" } +camera-view = { module = "androidx.camera:camera-view", version.ref = "camerax_version" } +camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "camerax_version" } + [plugins] android-application = { id = "com.android.application", version.ref = "android-application" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 83085218..a45b7ac1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Tue Jun 27 10:53:54 IST 2023 +#Tue Oct 10 18:09:43 PKT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip