android: remove unnecessary READ_EXTERNAL_STORAGE permission request, open image picker in Gallery; IMG timestamp format (#610)

This commit is contained in:
JRoberts
2022-05-07 16:25:04 +04:00
committed by GitHub
parent fcb5c69281
commit 235bce8e2a
14 changed files with 67 additions and 73 deletions
@@ -8,7 +8,6 @@
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@@ -851,13 +851,16 @@ class CIFile(
val filePath: String? = null,
val fileStatus: CIFileStatus
) {
val stored: Boolean = when (fileStatus) {
val loaded: Boolean = when (fileStatus) {
CIFileStatus.SndStored -> true
CIFileStatus.SndTransfer -> true
CIFileStatus.SndComplete -> true
CIFileStatus.SndCancelled -> true
CIFileStatus.RcvInvitation -> false
CIFileStatus.RcvAccepted -> false
CIFileStatus.RcvTransfer -> false
CIFileStatus.RcvCancelled -> false
CIFileStatus.RcvComplete -> true
else -> false
}
companion object {
@@ -73,7 +73,7 @@ fun SendMsgView(
.size(36.dp)
.padding(4.dp),
color = HighOrLowlight,
strokeWidth = 4.dp
strokeWidth = 3.dp
)
} else {
Icon(
@@ -149,7 +149,7 @@ fun PreviewSendMsgViewEditing() {
fun PreviewSendMsgViewInProgress() {
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
val textStyle = remember { mutableStateOf(smallFont) }
val composeStateInProgress = ComposeState(inProgress = true)
val composeStateInProgress = ComposeState(preview = ComposePreview.FilePreview("test.txt"), inProgress = true)
SimpleXTheme {
SendMsgView(
composeState = remember { mutableStateOf(composeStateInProgress) },
@@ -89,7 +89,7 @@ fun CIFileView(
String.format(generalGetString(R.string.file_will_be_received_when_contact_is_online), MAX_FILE_SIZE)
)
CIFileStatus.RcvComplete -> {
val filePath = getStoredFilePath(context, file)
val filePath = getLoadedFilePath(context, file)
if (filePath != null) {
saveFileLauncher.launch(file.fileName)
} else {
@@ -86,10 +86,10 @@ fun CIImageView(
Box(contentAlignment = Alignment.TopEnd) {
val context = LocalContext.current
val imageBitmap: Bitmap? = getStoredImage(context, file)
val imageBitmap: Bitmap? = getLoadedImage(context, file)
if (imageBitmap != null) {
imageView(imageBitmap, onClick = {
if (getStoredFilePath(context, file) != null) {
if (getLoadedFilePath(context, file) != null) {
ModalManager.shared.showCustomModal { close -> ImageFullScreenView(imageBitmap, close) }
}
})
@@ -88,7 +88,7 @@ fun ChatItemView(
showMenu.value = false
})
if (cItem.content.msgContent is MsgContent.MCImage || cItem.content.msgContent is MsgContent.MCFile) {
val filePath = getStoredFilePath(context, cItem.file)
val filePath = getLoadedFilePath(context, cItem.file)
if (filePath != null) {
ItemAction(stringResource(R.string.save_verb), Icons.Outlined.SaveAlt, onClick = {
saveFileLauncher.launch(cItem.file?.fileName)
@@ -19,7 +19,8 @@ import androidx.annotation.CallSuper
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalContext
@@ -126,19 +127,17 @@ class CustomTakePicturePreview: ActivityResultContract<Void?, Bitmap?>() {
}
}
}
//class GetGalleryContent: ActivityResultContracts.GetContent() {
// override fun createIntent(context: Context, input: String): Intent {
// return super.createIntent(context, input).apply {
// Log.e(TAG, "########################################################### in GetGalleryContent")
// uri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri))
// putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.DIRECTORY_PICTURES)
// }
// }
//}
class GetGalleryContent: ActivityResultContracts.GetContent() {
override fun createIntent(context: Context, input: String): Intent {
super.createIntent(context, input)
return Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
}
}
@Composable
fun rememberGetContentLauncher(cb: (Uri?) -> Unit): ManagedActivityResultLauncher<String, Uri?> =
// rememberLauncherForActivityResult(contract = GetGalleryContent(), cb)
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent(), cb)
fun rememberGalleryLauncher(cb: (Uri?) -> Unit): ManagedActivityResultLauncher<String, Uri?> =
rememberLauncherForActivityResult(contract = GetGalleryContent(), cb)
@Composable
fun rememberCameraLauncher(cb: (Bitmap?) -> Unit): ManagedActivityResultLauncher<Void?, Bitmap?> =
@@ -148,6 +147,10 @@ fun rememberCameraLauncher(cb: (Bitmap?) -> Unit): ManagedActivityResultLauncher
fun rememberPermissionLauncher(cb: (Boolean) -> Unit): ManagedActivityResultLauncher<String, Boolean> =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission(), cb)
@Composable
fun rememberGetContentLauncher(cb: (Uri?) -> Unit): ManagedActivityResultLauncher<String, Uri?> =
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent(), cb)
@Composable
fun GetImageBottomSheet(
imageBitmap: MutableState<Bitmap?>,
@@ -157,8 +160,7 @@ fun GetImageBottomSheet(
hideBottomSheet: () -> Unit
) {
val context = LocalContext.current
val isCameraSelected = remember { mutableStateOf(false) }
val galleryLauncher = rememberGetContentLauncher { uri: Uri? ->
val galleryLauncher = rememberGalleryLauncher { uri: Uri? ->
if (uri != null) {
val source = ImageDecoder.createSource(context.contentResolver, uri)
val bitmap = ImageDecoder.decodeBitmap(source)
@@ -174,8 +176,7 @@ fun GetImageBottomSheet(
}
val permissionLauncher = rememberPermissionLauncher { isGranted: Boolean ->
if (isGranted) {
if (isCameraSelected.value) cameraLauncher.launch(null)
else galleryLauncher.launch("image/*")
cameraLauncher.launch(null)
hideBottomSheet()
} else {
Toast.makeText(context, generalGetString(R.string.toast_permission_denied), Toast.LENGTH_SHORT).show()
@@ -195,14 +196,6 @@ fun GetImageBottomSheet(
}
}
}
val filesPermissionLauncher = rememberPermissionLauncher { isGranted: Boolean ->
if (isGranted) {
filesLauncher.launch("*/*")
hideBottomSheet()
} else {
Toast.makeText(context, generalGetString(R.string.toast_permission_denied), Toast.LENGTH_SHORT).show()
}
}
Box(
modifier = Modifier
@@ -225,34 +218,18 @@ fun GetImageBottomSheet(
hideBottomSheet()
}
else -> {
isCameraSelected.value = true
permissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
ActionButton(null, stringResource(R.string.from_gallery_button), icon = Icons.Outlined.Collections) {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) -> {
galleryLauncher.launch("image/*")
hideBottomSheet()
}
else -> {
isCameraSelected.value = false
permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}
}
galleryLauncher.launch("image/*")
hideBottomSheet()
}
if (fileUri != null && onFileChange != null) {
ActionButton(null, stringResource(R.string.choose_file), icon = Icons.Outlined.InsertDriveFile) {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) -> {
filesLauncher.launch("*/*")
hideBottomSheet()
}
else -> {
filesPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}
}
filesLauncher.launch("*/*")
hideBottomSheet()
}
}
}
@@ -34,7 +34,7 @@ fun rememberSaveFileLauncher(cxt: Context, ciFile: CIFile?): ManagedActivityResu
contract = ActivityResultContracts.CreateDocument(),
onResult = { destination ->
if (destination != null) {
val filePath = getStoredFilePath(cxt, ciFile)
val filePath = getLoadedFilePath(cxt, ciFile)
if (filePath != null) {
val contentResolver = cxt.contentResolver
val file = File(filePath)
@@ -29,6 +29,8 @@ import chat.simplex.app.model.CIFile
import kotlinx.coroutines.*
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.log2
import kotlin.math.pow
@@ -224,8 +226,8 @@ fun getAppFilePath(context: Context, fileName: String): String {
return "${getAppFilesDirectory(context)}/$fileName"
}
fun getStoredFilePath(context: Context, file: CIFile?): String? {
return if (file?.filePath != null && file.stored) {
fun getLoadedFilePath(context: Context, file: CIFile?): String? {
return if (file?.filePath != null && file.loaded) {
val filePath = getAppFilePath(context, file.filePath)
if (File(filePath).exists()) filePath else null
} else {
@@ -234,8 +236,8 @@ fun getStoredFilePath(context: Context, file: CIFile?): String? {
}
// https://developer.android.com/training/data-storage/shared/documents-files#bitmap
fun getStoredImage(context: Context, file: CIFile?): Bitmap? {
val filePath = getStoredFilePath(context, file)
fun getLoadedImage(context: Context, file: CIFile?): Bitmap? {
val filePath = getLoadedFilePath(context, file)
return if (filePath != null) {
try {
val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath))
@@ -271,7 +273,8 @@ fun getFileSize(context: Context, uri: Uri): Long? {
fun saveImage(context: Context, image: Bitmap): String? {
return try {
val dataResized = resizeImageToDataSize(image, maxDataSize = MAX_IMAGE_SIZE)
val fileToSave = uniqueCombine(context, "image_${System.currentTimeMillis()}.jpg")
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
val fileToSave = uniqueCombine(context, "IMG_${timestamp}.jpg")
val file = File(getAppFilePath(context, fileToSave))
val output = FileOutputStream(file)
dataResized.writeTo(output)