MainActivity.kt
· 9.3 KiB · Kotlin
Surowy
Playground
package com.ogletree.composecameraexample
import android.content.ContentResolver
import android.content.ContentValues
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.ogletree.composecameraexample.ui.theme.ComposeCameraExampleTheme
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.min
class MainActivity : ComponentActivity() {
enum class CameraPermissionStatus { NoPermission, PermissionGranted, PermissionDenied }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val cameraPermissionStatusState = mutableStateOf(CameraPermissionStatus.NoPermission)
val photoUriState: MutableState<Uri?> = mutableStateOf(null)
val hasPhotoState = mutableStateOf(value = false)
val resolver = applicationContext.contentResolver
val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()){ isGranted: Boolean ->
if (isGranted) {
cameraPermissionStatusState.value = CameraPermissionStatus.PermissionGranted
} else {
cameraPermissionStatusState.value = CameraPermissionStatus.PermissionDenied
}
}
val takePhotoLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { isSaved ->
hasPhotoState.value = isSaved
}
val takePhoto: () -> Unit = {
hasPhotoState.value = false
val values = ContentValues().apply {
val title = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
put(MediaStore.Images.Media.TITLE, "Compose Camera Example Image - $title")
put(MediaStore.Images.Media.DISPLAY_NAME, title)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
}
val uri = resolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values
)
takePhotoLauncher.launch(uri)
photoUriState.value = uri
}
// Ideally these would be cached instead of reloaded
val getThumbnail: (Uri?) -> ImageBitmap? = { uri ->
val targetSize = 256f
println("URI is $uri")
uri?.let {
println("Opening Input Stream")
resolver.openInputStream(it)
}?.let {
BitmapFactory.decodeStream(it)
}?.let {
val height = it.height.toFloat()
val width = it.width.toFloat()
val scaleFactor = min(targetSize / height, targetSize / width)
Bitmap.createScaledBitmap(it, (scaleFactor * width).toInt() , (scaleFactor * height).toInt(), true)
}?.let {
val rotation = getImageRotation(resolver, uri)
Bitmap.createBitmap(it, 0, 0, it.width, it.height, Matrix().apply { postRotate(rotation.toFloat()) }, true)
}?.asImageBitmap()
}
val getFullImage: (Uri?) -> ImageBitmap? = { uri ->
uri?.let {
resolver.openInputStream(it)
}?.let {
BitmapFactory.decodeStream(it)
}?.let {
val rotation = getImageRotation(resolver, uri)
Bitmap.createBitmap(it, 0, 0, it.width, it.height, Matrix().apply { postRotate(rotation.toFloat()) }, true)
}?.asImageBitmap()
}
setContent {
val cameraPermissionStatus by remember { cameraPermissionStatusState }
val hasPhoto by remember { hasPhotoState }
var shouldShowFullImage by remember { mutableStateOf(false) }
ComposeCameraExampleTheme {
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
TakePhotoButton(
cameraPermissionStatus = cameraPermissionStatus,
requestPermissionLauncher = requestPermissionLauncher,
takePhoto = takePhoto
)
if (hasPhoto) {
val bitmap = getThumbnail(photoUriState.value)
println("Is bitmap null: $bitmap")
if (bitmap != null) {
Image(
bitmap = bitmap,
contentDescription = "Thumbnail of Save Photo",
modifier = Modifier.clickable {
shouldShowFullImage = true
}
)
}
}
}
if (shouldShowFullImage && hasPhoto) {
val bitmap =getFullImage(photoUriState.value)
if (bitmap != null){
Box(modifier = Modifier.fillMaxSize().clickable {
shouldShowFullImage = false
}){
Image(
bitmap = bitmap,
contentDescription = "Full image of Save Photo",
modifier = Modifier.align(Alignment.Center)
)
Surface(
modifier = Modifier
.background(MaterialTheme.colors.background)
.align(Alignment.Center)
.padding(8.dp)
) {
Text(
text = "Click to Close",
style = MaterialTheme.typography.h4.copy(
fontWeight = FontWeight.ExtraBold
)
)
}
}
} else {
shouldShowFullImage = false
}
}
}
}
}
}
private fun getImageRotation(resolver: ContentResolver, uri: Uri): Int {
val cursor = resolver.query(uri, arrayOf(MediaStore.Images.Media.ORIENTATION), null, null, null)
var result = 0
cursor?.apply {
moveToFirst()
val index = getColumnIndex(MediaStore.Images.Media.ORIENTATION)
result = getInt(index)
close()
}
println("Rotation = $result")
return result
}
}
@Composable
fun TakePhotoButton(
cameraPermissionStatus: MainActivity.CameraPermissionStatus,
requestPermissionLauncher: ActivityResultLauncher<String>,
takePhoto: () -> Unit
) {
OutlinedButton(
onClick = {
when (cameraPermissionStatus) {
MainActivity.CameraPermissionStatus.NoPermission ->
requestPermissionLauncher.launch(android.Manifest.permission.CAMERA)
MainActivity.CameraPermissionStatus.PermissionGranted ->
takePhoto()
MainActivity.CameraPermissionStatus.PermissionDenied -> {}
}
}
) {
when (cameraPermissionStatus) {
MainActivity.CameraPermissionStatus.NoPermission ->
Text(text = "Request Camera Permissions")
MainActivity.CameraPermissionStatus.PermissionDenied ->
Text(text = "Camera Permissions Have Been Denied")
MainActivity.CameraPermissionStatus.PermissionGranted ->
Text(text = "Take Photo")
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
ComposeCameraExampleTheme {
Greeting("Android")
}
}
| 1 | package com.ogletree.composecameraexample |
| 2 | |
| 3 | import android.content.ContentResolver |
| 4 | import android.content.ContentValues |
| 5 | import android.graphics.Bitmap |
| 6 | import android.graphics.BitmapFactory |
| 7 | import android.graphics.Matrix |
| 8 | import android.net.Uri |
| 9 | import android.os.Bundle |
| 10 | import android.provider.MediaStore |
| 11 | import androidx.activity.ComponentActivity |
| 12 | import androidx.activity.compose.setContent |
| 13 | import androidx.activity.result.ActivityResultLauncher |
| 14 | import androidx.activity.result.contract.ActivityResultContracts |
| 15 | import androidx.compose.foundation.Image |
| 16 | import androidx.compose.foundation.background |
| 17 | import androidx.compose.foundation.clickable |
| 18 | import androidx.compose.foundation.layout.* |
| 19 | import androidx.compose.material.MaterialTheme |
| 20 | import androidx.compose.material.OutlinedButton |
| 21 | import androidx.compose.material.Surface |
| 22 | import androidx.compose.material.Text |
| 23 | import androidx.compose.runtime.* |
| 24 | import androidx.compose.ui.Alignment |
| 25 | import androidx.compose.ui.Modifier |
| 26 | import androidx.compose.ui.graphics.ImageBitmap |
| 27 | import androidx.compose.ui.graphics.asImageBitmap |
| 28 | import androidx.compose.ui.text.font.FontWeight |
| 29 | import androidx.compose.ui.tooling.preview.Preview |
| 30 | import androidx.compose.ui.unit.dp |
| 31 | import com.ogletree.composecameraexample.ui.theme.ComposeCameraExampleTheme |
| 32 | import java.text.SimpleDateFormat |
| 33 | import java.util.* |
| 34 | import kotlin.math.min |
| 35 | |
| 36 | |
| 37 | class MainActivity : ComponentActivity() { |
| 38 | enum class CameraPermissionStatus { NoPermission, PermissionGranted, PermissionDenied } |
| 39 | |
| 40 | override fun onCreate(savedInstanceState: Bundle?) { |
| 41 | super.onCreate(savedInstanceState) |
| 42 | |
| 43 | val cameraPermissionStatusState = mutableStateOf(CameraPermissionStatus.NoPermission) |
| 44 | val photoUriState: MutableState<Uri?> = mutableStateOf(null) |
| 45 | val hasPhotoState = mutableStateOf(value = false) |
| 46 | val resolver = applicationContext.contentResolver |
| 47 | |
| 48 | val requestPermissionLauncher = |
| 49 | registerForActivityResult(ActivityResultContracts.RequestPermission()){ isGranted: Boolean -> |
| 50 | if (isGranted) { |
| 51 | cameraPermissionStatusState.value = CameraPermissionStatus.PermissionGranted |
| 52 | } else { |
| 53 | cameraPermissionStatusState.value = CameraPermissionStatus.PermissionDenied |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | val takePhotoLauncher = |
| 58 | registerForActivityResult(ActivityResultContracts.TakePicture()) { isSaved -> |
| 59 | hasPhotoState.value = isSaved |
| 60 | } |
| 61 | |
| 62 | val takePhoto: () -> Unit = { |
| 63 | hasPhotoState.value = false |
| 64 | |
| 65 | val values = ContentValues().apply { |
| 66 | val title = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) |
| 67 | put(MediaStore.Images.Media.TITLE, "Compose Camera Example Image - $title") |
| 68 | put(MediaStore.Images.Media.DISPLAY_NAME, title) |
| 69 | put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") |
| 70 | put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()) |
| 71 | } |
| 72 | |
| 73 | val uri = resolver.insert( |
| 74 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, |
| 75 | values |
| 76 | ) |
| 77 | takePhotoLauncher.launch(uri) |
| 78 | photoUriState.value = uri |
| 79 | |
| 80 | } |
| 81 | |
| 82 | // Ideally these would be cached instead of reloaded |
| 83 | val getThumbnail: (Uri?) -> ImageBitmap? = { uri -> |
| 84 | val targetSize = 256f |
| 85 | println("URI is $uri") |
| 86 | uri?.let { |
| 87 | println("Opening Input Stream") |
| 88 | resolver.openInputStream(it) |
| 89 | }?.let { |
| 90 | BitmapFactory.decodeStream(it) |
| 91 | }?.let { |
| 92 | val height = it.height.toFloat() |
| 93 | val width = it.width.toFloat() |
| 94 | val scaleFactor = min(targetSize / height, targetSize / width) |
| 95 | Bitmap.createScaledBitmap(it, (scaleFactor * width).toInt() , (scaleFactor * height).toInt(), true) |
| 96 | }?.let { |
| 97 | val rotation = getImageRotation(resolver, uri) |
| 98 | Bitmap.createBitmap(it, 0, 0, it.width, it.height, Matrix().apply { postRotate(rotation.toFloat()) }, true) |
| 99 | }?.asImageBitmap() |
| 100 | |
| 101 | } |
| 102 | |
| 103 | val getFullImage: (Uri?) -> ImageBitmap? = { uri -> |
| 104 | uri?.let { |
| 105 | resolver.openInputStream(it) |
| 106 | }?.let { |
| 107 | BitmapFactory.decodeStream(it) |
| 108 | }?.let { |
| 109 | val rotation = getImageRotation(resolver, uri) |
| 110 | Bitmap.createBitmap(it, 0, 0, it.width, it.height, Matrix().apply { postRotate(rotation.toFloat()) }, true) |
| 111 | }?.asImageBitmap() |
| 112 | |
| 113 | } |
| 114 | |
| 115 | setContent { |
| 116 | val cameraPermissionStatus by remember { cameraPermissionStatusState } |
| 117 | val hasPhoto by remember { hasPhotoState } |
| 118 | var shouldShowFullImage by remember { mutableStateOf(false) } |
| 119 | ComposeCameraExampleTheme { |
| 120 | Box(modifier = Modifier.fillMaxSize()) { |
| 121 | Column( |
| 122 | modifier = Modifier.fillMaxSize(), |
| 123 | verticalArrangement = Arrangement.Center, |
| 124 | horizontalAlignment = Alignment.CenterHorizontally |
| 125 | ) { |
| 126 | TakePhotoButton( |
| 127 | cameraPermissionStatus = cameraPermissionStatus, |
| 128 | requestPermissionLauncher = requestPermissionLauncher, |
| 129 | takePhoto = takePhoto |
| 130 | ) |
| 131 | if (hasPhoto) { |
| 132 | val bitmap = getThumbnail(photoUriState.value) |
| 133 | println("Is bitmap null: $bitmap") |
| 134 | if (bitmap != null) { |
| 135 | Image( |
| 136 | bitmap = bitmap, |
| 137 | contentDescription = "Thumbnail of Save Photo", |
| 138 | modifier = Modifier.clickable { |
| 139 | shouldShowFullImage = true |
| 140 | } |
| 141 | ) |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | if (shouldShowFullImage && hasPhoto) { |
| 147 | val bitmap =getFullImage(photoUriState.value) |
| 148 | if (bitmap != null){ |
| 149 | Box(modifier = Modifier.fillMaxSize().clickable { |
| 150 | shouldShowFullImage = false |
| 151 | }){ |
| 152 | Image( |
| 153 | bitmap = bitmap, |
| 154 | contentDescription = "Full image of Save Photo", |
| 155 | modifier = Modifier.align(Alignment.Center) |
| 156 | ) |
| 157 | Surface( |
| 158 | modifier = Modifier |
| 159 | .background(MaterialTheme.colors.background) |
| 160 | .align(Alignment.Center) |
| 161 | .padding(8.dp) |
| 162 | ) { |
| 163 | Text( |
| 164 | text = "Click to Close", |
| 165 | style = MaterialTheme.typography.h4.copy( |
| 166 | fontWeight = FontWeight.ExtraBold |
| 167 | ) |
| 168 | ) |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | |
| 173 | } else { |
| 174 | shouldShowFullImage = false |
| 175 | } |
| 176 | |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | private fun getImageRotation(resolver: ContentResolver, uri: Uri): Int { |
| 184 | val cursor = resolver.query(uri, arrayOf(MediaStore.Images.Media.ORIENTATION), null, null, null) |
| 185 | var result = 0 |
| 186 | |
| 187 | cursor?.apply { |
| 188 | moveToFirst() |
| 189 | val index = getColumnIndex(MediaStore.Images.Media.ORIENTATION) |
| 190 | result = getInt(index) |
| 191 | close() |
| 192 | } |
| 193 | println("Rotation = $result") |
| 194 | return result |
| 195 | } |
| 196 | |
| 197 | } |
| 198 | |
| 199 | @Composable |
| 200 | fun TakePhotoButton( |
| 201 | cameraPermissionStatus: MainActivity.CameraPermissionStatus, |
| 202 | requestPermissionLauncher: ActivityResultLauncher<String>, |
| 203 | takePhoto: () -> Unit |
| 204 | ) { |
| 205 | OutlinedButton( |
| 206 | onClick = { |
| 207 | when (cameraPermissionStatus) { |
| 208 | MainActivity.CameraPermissionStatus.NoPermission -> |
| 209 | requestPermissionLauncher.launch(android.Manifest.permission.CAMERA) |
| 210 | |
| 211 | MainActivity.CameraPermissionStatus.PermissionGranted -> |
| 212 | takePhoto() |
| 213 | |
| 214 | |
| 215 | MainActivity.CameraPermissionStatus.PermissionDenied -> {} |
| 216 | |
| 217 | } |
| 218 | } |
| 219 | ) { |
| 220 | when (cameraPermissionStatus) { |
| 221 | MainActivity.CameraPermissionStatus.NoPermission -> |
| 222 | Text(text = "Request Camera Permissions") |
| 223 | |
| 224 | MainActivity.CameraPermissionStatus.PermissionDenied -> |
| 225 | Text(text = "Camera Permissions Have Been Denied") |
| 226 | |
| 227 | MainActivity.CameraPermissionStatus.PermissionGranted -> |
| 228 | Text(text = "Take Photo") |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | |
| 234 | @Composable |
| 235 | fun Greeting(name: String) { |
| 236 | Text(text = "Hello $name!") |
| 237 | } |
| 238 | |
| 239 | @Preview(showBackground = true) |
| 240 | @Composable |
| 241 | fun DefaultPreview() { |
| 242 | ComposeCameraExampleTheme { |
| 243 | Greeting("Android") |
| 244 | } |
| 245 | } |