Created
September 6, 2023 14:13
-
-
Save chiragthummar/35aa1d15882c4d5a105a3f89be821214 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.jetpackcomposeplayground | |
import android.content.Context | |
import android.graphics.Bitmap | |
import android.graphics.Picture | |
import android.net.Uri | |
import android.os.Environment | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.foundation.layout.padding | |
import androidx.compose.ui.draw.drawWithCache | |
import androidx.compose.ui.graphics.drawscope.draw | |
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas | |
import androidx.compose.ui.graphics.nativeCanvas | |
import androidx.compose.ui.unit.dp | |
import java.io.File | |
import android.Manifest | |
import android.content.Intent | |
import android.content.Intent.createChooser | |
import android.media.MediaScannerConnection | |
import android.os.Build | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.layout.Arrangement | |
import androidx.compose.foundation.layout.aspectRatio | |
import androidx.compose.material.icons.Icons | |
import androidx.compose.material.icons.filled.Share | |
import androidx.compose.material3.FloatingActionButton | |
import androidx.compose.material3.Icon | |
import androidx.compose.material3.Scaffold | |
import androidx.compose.material3.SnackbarHost | |
import androidx.compose.material3.SnackbarHostState | |
import androidx.compose.material3.SnackbarResult | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.rememberCoroutineScope | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.graphics.Brush | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.layout.ContentScale | |
import androidx.compose.ui.platform.LocalContext | |
import androidx.compose.ui.res.painterResource | |
import androidx.compose.ui.tooling.preview.Preview | |
import androidx.compose.ui.unit.sp | |
import androidx.core.content.ContextCompat.startActivity | |
import com.google.accompanist.permissions.ExperimentalPermissionsApi | |
import com.google.accompanist.permissions.rememberMultiplePermissionsState | |
import kotlin.coroutines.resume | |
import kotlinx.coroutines.Dispatchers | |
import kotlinx.coroutines.launch | |
import kotlinx.coroutines.suspendCancellableCoroutine | |
@OptIn(ExperimentalPermissionsApi::class) | |
@Preview | |
@Composable | |
fun BitmapFromComposableSnippet() { | |
val context = LocalContext.current | |
val coroutineScope = rememberCoroutineScope() | |
val snackbarHostState = remember { SnackbarHostState() } | |
val picture = remember { Picture() } | |
val writeStorageAccessState = rememberMultiplePermissionsState( | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { | |
// No permissions are needed on Android 10+ to add files in the shared storage | |
emptyList() | |
} else { | |
listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) | |
} | |
) | |
// This logic should live in your ViewModel - trigger a side effect to invoke URI sharing. | |
// checks permissions granted, and then saves the bitmap from a Picture that is already capturing content | |
// and shares it with the default share sheet. | |
fun shareBitmapFromComposable() { | |
if (writeStorageAccessState.allPermissionsGranted) { | |
coroutineScope.launch(Dispatchers.IO) { | |
val bitmap = createBitmapFromPicture(picture) | |
val uri = bitmap.saveToDisk(context) | |
shareBitmap(context, uri) | |
} | |
} else if (writeStorageAccessState.shouldShowRationale) { | |
coroutineScope.launch { | |
val result = snackbarHostState.showSnackbar( | |
message = "The storage permission is needed to save the image", | |
actionLabel = "Grant Access" | |
) | |
if (result == SnackbarResult.ActionPerformed) { | |
writeStorageAccessState.launchMultiplePermissionRequest() | |
} | |
} | |
} else { | |
writeStorageAccessState.launchMultiplePermissionRequest() | |
} | |
} | |
Scaffold( | |
snackbarHost = { SnackbarHost(snackbarHostState) }, | |
floatingActionButton = { | |
FloatingActionButton(onClick = { | |
shareBitmapFromComposable() | |
}) { | |
Icon(Icons.Default.Share, "share") | |
} | |
} | |
) { padding -> | |
// [START android_compose_draw_into_bitmap] | |
Column( | |
modifier = Modifier | |
.padding(padding) | |
.fillMaxSize() | |
.drawWithCache { | |
// Example that shows how to redirect rendering to an Android Picture and then | |
// draw the picture into the original destination | |
val width = this.size.width.toInt() | |
val height = this.size.height.toInt() | |
onDrawWithContent { | |
val pictureCanvas = | |
androidx.compose.ui.graphics.Canvas( | |
picture.beginRecording( | |
width, | |
height | |
) | |
) | |
draw(this, this.layoutDirection, pictureCanvas, this.size) { | |
[email protected]() | |
} | |
picture.endRecording() | |
drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } | |
} | |
} | |
) { | |
ScreenContentToCapture() | |
} | |
// [END android_compose_draw_into_bitmap] | |
} | |
} | |
@Composable | |
private fun ScreenContentToCapture() { | |
Column( | |
horizontalAlignment = Alignment.CenterHorizontally, | |
verticalArrangement = Arrangement.Center, | |
modifier = Modifier | |
.fillMaxSize() | |
.background( | |
Brush.linearGradient( | |
listOf( | |
Color(0xFFF5D5C0), | |
Color(0xFFF8E8E3) | |
) | |
) | |
) | |
) { | |
Image( | |
painterResource(id = R.drawable.banner), | |
contentDescription = null, | |
modifier = Modifier | |
.aspectRatio(1f) | |
.padding(32.dp), | |
contentScale = ContentScale.Crop | |
) | |
Text( | |
"Into the Ocean depths", | |
fontSize = 18.sp | |
) | |
} | |
} | |
private fun createBitmapFromPicture(picture: Picture): Bitmap { | |
val bitmap = Bitmap.createBitmap( | |
picture.width, | |
picture.height, | |
Bitmap.Config.ARGB_8888 | |
) | |
val canvas = android.graphics.Canvas(bitmap) | |
canvas.drawColor(android.graphics.Color.WHITE) | |
canvas.drawPicture(picture) | |
return bitmap | |
} | |
private suspend fun Bitmap.saveToDisk(context: Context): Uri { | |
val file = File( | |
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), | |
"screenshot-${System.currentTimeMillis()}.png" | |
) | |
file.writeBitmap(this, Bitmap.CompressFormat.PNG, 100) | |
return scanFilePath(context, file.path) ?: throw Exception("File could not be saved") | |
} | |
private suspend fun scanFilePath(context: Context, filePath: String): Uri? { | |
return suspendCancellableCoroutine { continuation -> | |
MediaScannerConnection.scanFile( | |
context, | |
arrayOf(filePath), | |
arrayOf("image/png") | |
) { _, scannedUri -> | |
if (scannedUri == null) { | |
continuation.cancel(Exception("File $filePath could not be scanned")) | |
} else { | |
continuation.resume(scannedUri) | |
} | |
} | |
} | |
} | |
private fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) { | |
outputStream().use { out -> | |
bitmap.compress(format, quality, out) | |
out.flush() | |
} | |
} | |
private fun shareBitmap(context: Context, uri: Uri) { | |
val intent = Intent(Intent.ACTION_SEND).apply { | |
type = "image/png" | |
putExtra(Intent.EXTRA_STREAM, uri) | |
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) | |
} | |
startActivity(context, createChooser(intent, "Share your image"), null) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello, thanks a lot for your code.
Do you have any suggestion for capture whole lazyColumn content( in my case messages of a chat page )?