From a03e02bfeb5b9fb09981b8a4d55ab8f047f02b09 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 2 Sep 2024 08:42:46 -0300 Subject: [PATCH 1/5] Add a button to take a picture from camera --- amethyst/build.gradle | 7 ++ amethyst/src/main/AndroidManifest.xml | 13 +++ .../amethyst/ui/actions/NewPostView.kt | 79 ++++++++++++++++++- amethyst/src/main/res/xml/file_paths.xml | 6 ++ 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 amethyst/src/main/res/xml/file_paths.xml diff --git a/amethyst/build.gradle b/amethyst/build.gradle index fd27e1f249..7d1024b755 100644 --- a/amethyst/build.gradle +++ b/amethyst/build.gradle @@ -299,5 +299,12 @@ dependencies { debugImplementation platform(libs.androidx.compose.bom) debugImplementation libs.androidx.ui.tooling debugImplementation libs.androidx.ui.test.manifest + + def camerax_version = "1.3.4" + implementation "androidx.camera:camera-core:$camerax_version" + implementation "androidx.camera:camera-camera2:$camerax_version" + implementation "androidx.camera:camera-lifecycle:$camerax_version" + implementation "androidx.camera:camera-view:$camerax_version" + implementation "androidx.camera:camera-extensions:$camerax_version" } diff --git a/amethyst/src/main/AndroidManifest.xml b/amethyst/src/main/AndroidManifest.xml index 8b30f6ee13..dfd0f2df50 100644 --- a/amethyst/src/main/AndroidManifest.xml +++ b/amethyst/src/main/AndroidManifest.xml @@ -16,6 +16,9 @@ + + + @@ -121,6 +124,16 @@ + + + + \ No newline at end of file diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 1941dc8d48..7c55855b76 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -24,9 +24,12 @@ import android.Manifest import android.graphics.Bitmap import android.net.Uri import android.os.Build +import android.os.Environment import android.util.Log import android.util.Size import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border @@ -56,6 +59,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bolt +import androidx.compose.material.icons.filled.CameraAlt import androidx.compose.material.icons.filled.CurrencyBitcoin import androidx.compose.material.icons.filled.LocationOff import androidx.compose.material.icons.filled.LocationOn @@ -117,6 +121,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import androidx.core.content.FileProvider +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage @@ -180,7 +186,11 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch +import java.io.File import java.lang.Math.round +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale @OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) @Composable @@ -195,6 +205,7 @@ fun NewPostView( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + val lifecycleOwner = LocalLifecycleOwner.current val postViewModel: NewPostViewModel = viewModel() postViewModel.wantsDirectMessage = enableMessageInterface @@ -204,6 +215,9 @@ fun NewPostView( val scope = rememberCoroutineScope() var showRelaysDialog by remember { mutableStateOf(false) } var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() } + var showCamera by remember { + mutableStateOf(true) + } LaunchedEffect(key1 = postViewModel.draftTag) { launch(Dispatchers.IO) { @@ -561,7 +575,6 @@ fun NewPostView( @Composable private fun BottomRowActions(postViewModel: NewPostViewModel) { val scrollState = rememberScrollState() - Row( modifier = Modifier @@ -578,6 +591,12 @@ private fun BottomRowActions(postViewModel: NewPostViewModel) { postViewModel.selectImage(it) } + TakePictureButton( + onPictureTaken = { + postViewModel.selectImage(it) + }, + ) + if (postViewModel.canUsePoll) { // These should be hashtag recommendations the user selects in the future. // val hashtag = stringRes(R.string.poll_hashtag) @@ -623,6 +642,64 @@ private fun BottomRowActions(postViewModel: NewPostViewModel) { } } +@OptIn(ExperimentalPermissionsApi::class) +@Composable +fun TakePictureButton(onPictureTaken: (Uri) -> Unit) { + var imageUri by remember { mutableStateOf(null) } + val launcher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.TakePicture(), + ) { success -> + if (success) { + imageUri?.let { + onPictureTaken(it) + } + } + } + val context = LocalContext.current + val cameraPermissionState = + rememberPermissionState( + Manifest.permission.CAMERA, + ) + + Box { + IconButton( + modifier = Modifier.align(Alignment.Center), + onClick = { + if (cameraPermissionState.status.isGranted) { + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) + val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + File + .createTempFile( + "JPEG_${timeStamp}_", + ".jpg", + storageDir, + ).apply { + imageUri = + FileProvider.getUriForFile( + context, + "${context.packageName}.provider", + this, + ) + } + imageUri?.let { + launcher.launch(it) + } + } else { + cameraPermissionState.launchPermissionRequest() + } + }, + ) { + Icon( + imageVector = Icons.Default.CameraAlt, + contentDescription = stringRes(id = R.string.upload_image), + modifier = Modifier.height(25.dp), + tint = MaterialTheme.colorScheme.onBackground, + ) + } + } +} + @Composable private fun PollField(postViewModel: NewPostViewModel) { val optionsList = postViewModel.pollOptions diff --git a/amethyst/src/main/res/xml/file_paths.xml b/amethyst/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000000..a075ef96b4 --- /dev/null +++ b/amethyst/src/main/res/xml/file_paths.xml @@ -0,0 +1,6 @@ + + + + From 6af2b48d23e7d4263269aefa9ce33e302759960d Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 2 Sep 2024 08:42:46 -0300 Subject: [PATCH 2/5] Add a button to take a picture from camera --- amethyst/build.gradle | 7 ++ amethyst/src/main/AndroidManifest.xml | 13 +++ .../amethyst/ui/actions/NewPostView.kt | 79 ++++++++++++++++++- amethyst/src/main/res/xml/file_paths.xml | 6 ++ 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 amethyst/src/main/res/xml/file_paths.xml diff --git a/amethyst/build.gradle b/amethyst/build.gradle index f0e902af9e..c7e5bb8e6b 100644 --- a/amethyst/build.gradle +++ b/amethyst/build.gradle @@ -299,5 +299,12 @@ dependencies { debugImplementation platform(libs.androidx.compose.bom) debugImplementation libs.androidx.ui.tooling debugImplementation libs.androidx.ui.test.manifest + + def camerax_version = "1.3.4" + implementation "androidx.camera:camera-core:$camerax_version" + implementation "androidx.camera:camera-camera2:$camerax_version" + implementation "androidx.camera:camera-lifecycle:$camerax_version" + implementation "androidx.camera:camera-view:$camerax_version" + implementation "androidx.camera:camera-extensions:$camerax_version" } diff --git a/amethyst/src/main/AndroidManifest.xml b/amethyst/src/main/AndroidManifest.xml index 8b30f6ee13..dfd0f2df50 100644 --- a/amethyst/src/main/AndroidManifest.xml +++ b/amethyst/src/main/AndroidManifest.xml @@ -16,6 +16,9 @@ + + + @@ -121,6 +124,16 @@ + + + + \ No newline at end of file diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index c55c867c91..839486698b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -24,9 +24,12 @@ import android.Manifest import android.graphics.Bitmap import android.net.Uri import android.os.Build +import android.os.Environment import android.util.Log import android.util.Size import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border @@ -56,6 +59,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bolt +import androidx.compose.material.icons.filled.CameraAlt import androidx.compose.material.icons.filled.CurrencyBitcoin import androidx.compose.material.icons.filled.LocationOff import androidx.compose.material.icons.filled.LocationOn @@ -118,6 +122,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import androidx.core.content.FileProvider +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage @@ -182,7 +188,11 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch +import java.io.File import java.lang.Math.round +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale @OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) @Composable @@ -197,6 +207,7 @@ fun NewPostView( accountViewModel: AccountViewModel, nav: INav, ) { + val lifecycleOwner = LocalLifecycleOwner.current val postViewModel: NewPostViewModel = viewModel() postViewModel.wantsDirectMessage = enableMessageInterface @@ -206,6 +217,9 @@ fun NewPostView( val scope = rememberCoroutineScope() var showRelaysDialog by remember { mutableStateOf(false) } var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() } + var showCamera by remember { + mutableStateOf(true) + } LaunchedEffect(key1 = postViewModel.draftTag) { launch(Dispatchers.IO) { @@ -563,7 +577,6 @@ fun NewPostView( @Composable private fun BottomRowActions(postViewModel: NewPostViewModel) { val scrollState = rememberScrollState() - Row( modifier = Modifier @@ -580,6 +593,12 @@ private fun BottomRowActions(postViewModel: NewPostViewModel) { postViewModel.selectImage(it) } + TakePictureButton( + onPictureTaken = { + postViewModel.selectImage(it) + }, + ) + if (postViewModel.canUsePoll) { // These should be hashtag recommendations the user selects in the future. // val hashtag = stringRes(R.string.poll_hashtag) @@ -625,6 +644,64 @@ private fun BottomRowActions(postViewModel: NewPostViewModel) { } } +@OptIn(ExperimentalPermissionsApi::class) +@Composable +fun TakePictureButton(onPictureTaken: (Uri) -> Unit) { + var imageUri by remember { mutableStateOf(null) } + val launcher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.TakePicture(), + ) { success -> + if (success) { + imageUri?.let { + onPictureTaken(it) + } + } + } + val context = LocalContext.current + val cameraPermissionState = + rememberPermissionState( + Manifest.permission.CAMERA, + ) + + Box { + IconButton( + modifier = Modifier.align(Alignment.Center), + onClick = { + if (cameraPermissionState.status.isGranted) { + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) + val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + File + .createTempFile( + "JPEG_${timeStamp}_", + ".jpg", + storageDir, + ).apply { + imageUri = + FileProvider.getUriForFile( + context, + "${context.packageName}.provider", + this, + ) + } + imageUri?.let { + launcher.launch(it) + } + } else { + cameraPermissionState.launchPermissionRequest() + } + }, + ) { + Icon( + imageVector = Icons.Default.CameraAlt, + contentDescription = stringRes(id = R.string.upload_image), + modifier = Modifier.height(25.dp), + tint = MaterialTheme.colorScheme.onBackground, + ) + } + } +} + @Composable private fun PollField(postViewModel: NewPostViewModel) { val optionsList = postViewModel.pollOptions diff --git a/amethyst/src/main/res/xml/file_paths.xml b/amethyst/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000000..a075ef96b4 --- /dev/null +++ b/amethyst/src/main/res/xml/file_paths.xml @@ -0,0 +1,6 @@ + + + + From 2e110422c80168c839387ee2ba0d7ba7d72ce0b0 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 9 Sep 2024 14:59:05 -0300 Subject: [PATCH 3/5] after enabling permissions open the camera --- .../amethyst/ui/actions/NewPostView.kt | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 839486698b..7e0d6b7cc1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.ui.actions import android.Manifest +import android.content.Context import android.graphics.Bitmap import android.net.Uri import android.os.Build @@ -644,10 +645,28 @@ private fun BottomRowActions(postViewModel: NewPostViewModel) { } } +fun getPhotoUri(context: Context): Uri { + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) + val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + return File + .createTempFile( + "JPEG_${timeStamp}_", + ".jpg", + storageDir, + ).let { + FileProvider.getUriForFile( + context, + "${context.packageName}.provider", + it, + ) + } +} + @OptIn(ExperimentalPermissionsApi::class) @Composable fun TakePictureButton(onPictureTaken: (Uri) -> Unit) { var imageUri by remember { mutableStateOf(null) } + val scope = rememberCoroutineScope() val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.TakePicture(), @@ -662,6 +681,14 @@ fun TakePictureButton(onPictureTaken: (Uri) -> Unit) { val cameraPermissionState = rememberPermissionState( Manifest.permission.CAMERA, + onPermissionResult = { + if (it) { + scope.launch(Dispatchers.IO) { + imageUri = getPhotoUri(context) + imageUri?.let { uri -> launcher.launch(uri) } + } + } + }, ) Box { @@ -669,23 +696,9 @@ fun TakePictureButton(onPictureTaken: (Uri) -> Unit) { modifier = Modifier.align(Alignment.Center), onClick = { if (cameraPermissionState.status.isGranted) { - val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) - val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) - File - .createTempFile( - "JPEG_${timeStamp}_", - ".jpg", - storageDir, - ).apply { - imageUri = - FileProvider.getUriForFile( - context, - "${context.packageName}.provider", - this, - ) - } - imageUri?.let { - launcher.launch(it) + scope.launch(Dispatchers.IO) { + imageUri = getPhotoUri(context) + imageUri?.let { uri -> launcher.launch(uri) } } } else { cameraPermissionState.launchPermissionRequest() From 1e3104d489ec8b6924460e7d5f844eeae2b7dbf4 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 9 Sep 2024 15:37:30 -0300 Subject: [PATCH 4/5] Add camera button in the stories screen --- .../amethyst/ui/actions/NewPostView.kt | 24 ---- .../amethyst/ui/actions/UploadFromGallery.kt | 24 ++++ .../screen/loggedIn/video/NewImageButton.kt | 131 ++++++++++++++++-- 3 files changed, 140 insertions(+), 39 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 7e0d6b7cc1..7fd0e1e627 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -21,11 +21,9 @@ package com.vitorpamplona.amethyst.ui.actions import android.Manifest -import android.content.Context import android.graphics.Bitmap import android.net.Uri import android.os.Build -import android.os.Environment import android.util.Log import android.util.Size import android.widget.Toast @@ -123,7 +121,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import androidx.core.content.FileProvider import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel @@ -189,11 +186,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -import java.io.File import java.lang.Math.round -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale @OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) @Composable @@ -645,23 +638,6 @@ private fun BottomRowActions(postViewModel: NewPostViewModel) { } } -fun getPhotoUri(context: Context): Uri { - val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) - val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) - return File - .createTempFile( - "JPEG_${timeStamp}_", - ".jpg", - storageDir, - ).let { - FileProvider.getUriForFile( - context, - "${context.packageName}.provider", - it, - ) - } -} - @OptIn(ExperimentalPermissionsApi::class) @Composable fun TakePictureButton(onPictureTaken: (Uri) -> Unit) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UploadFromGallery.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UploadFromGallery.kt index 8c8d6b3a80..ff5560063f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UploadFromGallery.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UploadFromGallery.kt @@ -20,8 +20,10 @@ */ package com.vitorpamplona.amethyst.ui.actions +import android.content.Context import android.net.Uri import android.os.Build +import android.os.Environment import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateFloat @@ -52,6 +54,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.core.content.FileProvider import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState @@ -60,6 +63,10 @@ import com.vitorpamplona.amethyst.ui.GetMediaActivityResultContract import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale import java.util.concurrent.atomic.AtomicBoolean @OptIn(ExperimentalPermissionsApi::class) @@ -125,6 +132,23 @@ private fun UploadBoxButton( } } +fun getPhotoUri(context: Context): Uri { + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) + val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + return File + .createTempFile( + "JPEG_${timeStamp}_", + ".jpg", + storageDir, + ).let { + FileProvider.getUriForFile( + context, + "${context.packageName}.provider", + it, + ) + } +} + val DefaultAnimationColors = listOf( Color(0xFF5851D8), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewImageButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewImageButton.kt index d73a275181..60110f49a7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewImageButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewImageButton.kt @@ -23,11 +23,19 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.video import android.Manifest import android.net.Uri import android.os.Build +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AddPhotoAlternate +import androidx.compose.material.icons.filled.CameraAlt import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon @@ -45,6 +53,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -57,6 +66,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.GallerySelect import com.vitorpamplona.amethyst.ui.actions.NewMediaModel import com.vitorpamplona.amethyst.ui.actions.NewMediaView +import com.vitorpamplona.amethyst.ui.actions.getPhotoUri import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes @@ -73,7 +83,15 @@ fun NewImageButton( nav: INav, navScrollToTop: () -> Unit, ) { - var wantsToPost by remember { mutableStateOf(false) } + val context = LocalContext.current + + var isOpen by remember { mutableStateOf(false) } + + var wantsToPostFromGallery by remember { mutableStateOf(false) } + + var wantsToPostFromCamera by remember { mutableStateOf(false) } + + var cameraUri by remember { mutableStateOf(null) } var pickedURI by remember { mutableStateOf(null) } @@ -87,7 +105,46 @@ fun NewImageButton( } } - if (wantsToPost) { + if (wantsToPostFromCamera) { + val launcher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.TakePicture(), + ) { success -> + if (success) { + cameraUri?.let { + pickedURI = it + } + } + cameraUri = null + wantsToPostFromCamera = false + } + + val cameraPermissionState = + rememberPermissionState( + Manifest.permission.CAMERA, + onPermissionResult = { + if (it) { + scope.launch(Dispatchers.IO) { + cameraUri = getPhotoUri(context) + cameraUri?.let { launcher.launch(it) } + } + } + }, + ) + + if (cameraPermissionState.status.isGranted) { + LaunchedEffect(key1 = accountViewModel) { + launch(Dispatchers.IO) { + cameraUri = getPhotoUri(context) + cameraUri?.let { launcher.launch(it) } + } + } + } else { + LaunchedEffect(key1 = accountViewModel) { cameraPermissionState.launchPermissionRequest() } + } + } + + if (wantsToPostFromGallery) { val cameraPermissionState = rememberPermissionState( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -102,7 +159,7 @@ fun NewImageButton( if (showGallerySelect) { GallerySelect( onImageUri = { uri -> - wantsToPost = false + wantsToPostFromGallery = false showGallerySelect = false pickedURI = uri }, @@ -128,18 +185,62 @@ fun NewImageButton( if (postViewModel.isUploadingImage) { ShowProgress(postViewModel) } else { - FloatingActionButton( - onClick = { wantsToPost = true }, - modifier = Size55Modifier, - shape = CircleShape, - containerColor = MaterialTheme.colorScheme.primary, - ) { - Icon( - painter = painterResource(R.drawable.ic_compose), - contentDescription = stringRes(id = R.string.new_short), - modifier = Modifier.size(26.dp), - tint = Color.White, - ) + Column { + if (isOpen) { + FloatingActionButton( + onClick = { + wantsToPostFromCamera = true + isOpen = false + }, + modifier = Size55Modifier, + shape = CircleShape, + containerColor = MaterialTheme.colorScheme.primary, + ) { + Icon( + imageVector = Icons.Default.CameraAlt, + contentDescription = stringRes(id = R.string.upload_image), + modifier = Modifier.size(26.dp), + tint = Color.White, + ) + } + + Spacer(modifier = Modifier.height(20.dp)) + + FloatingActionButton( + onClick = { + wantsToPostFromGallery = true + isOpen = false + }, + modifier = Size55Modifier, + shape = CircleShape, + containerColor = MaterialTheme.colorScheme.primary, + ) { + Icon( + imageVector = Icons.Default.AddPhotoAlternate, + contentDescription = stringRes(id = R.string.upload_image), + modifier = Modifier.size(26.dp), + tint = Color.White, + ) + } + + Spacer(modifier = Modifier.height(20.dp)) + } + + FloatingActionButton( + onClick = { + isOpen = !isOpen + }, + modifier = Size55Modifier, + shape = CircleShape, + containerColor = MaterialTheme.colorScheme.primary, + ) { + Icon( + painter = painterResource(R.drawable.ic_compose), + contentDescription = stringRes(id = R.string.new_short), + modifier = Modifier.size(26.dp), + tint = Color.White, + ) + } } } } From 0fde6ffd30cfd0d92b9ea346aa2d50edfac3b450 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 9 Sep 2024 16:03:35 -0300 Subject: [PATCH 5/5] Move camera dependencies to version catalogs --- amethyst/build.gradle | 11 +++++------ gradle/libs.versions.toml | 8 +++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/amethyst/build.gradle b/amethyst/build.gradle index c7e5bb8e6b..e0fd1eae72 100644 --- a/amethyst/build.gradle +++ b/amethyst/build.gradle @@ -300,11 +300,10 @@ dependencies { debugImplementation libs.androidx.ui.tooling debugImplementation libs.androidx.ui.test.manifest - def camerax_version = "1.3.4" - implementation "androidx.camera:camera-core:$camerax_version" - implementation "androidx.camera:camera-camera2:$camerax_version" - implementation "androidx.camera:camera-lifecycle:$camerax_version" - implementation "androidx.camera:camera-view:$camerax_version" - implementation "androidx.camera:camera-extensions:$camerax_version" + implementation libs.androidx.camera.core + implementation libs.androidx.camera.camera2 + implementation libs.androidx.camera.lifecycle + implementation libs.androidx.camera.view + implementation libs.androidx.camera.extensions } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f50fd11a88..6e1690e456 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,6 +50,7 @@ zoomable = "1.6.2" zxing = "3.5.3" zxingAndroidEmbedded = "4.3.0" windowCoreAndroid = "1.3.0" +androidxCamera = "1.3.4" [libraries] abedElazizShe-image-compressor = { group = "com.github.AbedElazizShe", name = "LightCompressor", version.ref = "lightcompressor" } @@ -59,6 +60,11 @@ androidx-activity-compose = { group = "androidx.activity", name = "activity-comp androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } androidx-benchmark-junit4 = { group = "androidx.benchmark", name = "benchmark-junit4", version.ref = "benchmarkJunit4" } androidx-biometric-ktx = { group = "androidx.biometric", name = "biometric-ktx", version.ref = "biometricKtx" } +androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidxCamera" } +androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidxCamera" } +androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "androidxCamera" } +androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidxCamera" } +androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidxCamera" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -132,4 +138,4 @@ googleServices = { id = "com.google.gms.google-services", version.ref = "gms" } jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } jetbrainsKotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } jetbrainsComposeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -serialization = { id = 'org.jetbrains.kotlin.plugin.serialization', version.ref = 'kotlinxSerializationPlugin' } \ No newline at end of file +serialization = { id = 'org.jetbrains.kotlin.plugin.serialization', version.ref = 'kotlinxSerializationPlugin' }