MainActivity.kt
· 9.3 KiB · Kotlin
Raw
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 | } |