diff --git a/amethyst/build.gradle b/amethyst/build.gradle
index f0e902af9..c7e5bb8e6 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 8b30f6ee1..dfd0f2df5 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 c55c867c9..839486698 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 000000000..a075ef96b
--- /dev/null
+++ b/amethyst/src/main/res/xml/file_paths.xml
@@ -0,0 +1,6 @@
+
+
+
+