diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml
index 3c2b3d945..113ffa93e 100644
--- a/apps/android/app/src/main/AndroidManifest.xml
+++ b/apps/android/app/src/main/AndroidManifest.xml
@@ -8,7 +8,6 @@
-
diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
index 56e69929b..acf8469c8 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
@@ -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 {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt
index 74fee4740..08f2672a1 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt
@@ -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) },
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt
index 35a0c22a7..6553a2d7f 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt
@@ -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 {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt
index 918c429ea..e3f342dea 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt
@@ -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) }
}
})
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt
index 1d43b0afd..8aed21baf 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt
@@ -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)
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt
index 095c3ef2e..14b7adbb8 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt
@@ -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() {
}
}
}
-//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 =
-// rememberLauncherForActivityResult(contract = GetGalleryContent(), cb)
- rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent(), cb)
+fun rememberGalleryLauncher(cb: (Uri?) -> Unit): ManagedActivityResultLauncher =
+ rememberLauncherForActivityResult(contract = GetGalleryContent(), cb)
@Composable
fun rememberCameraLauncher(cb: (Bitmap?) -> Unit): ManagedActivityResultLauncher =
@@ -148,6 +147,10 @@ fun rememberCameraLauncher(cb: (Bitmap?) -> Unit): ManagedActivityResultLauncher
fun rememberPermissionLauncher(cb: (Boolean) -> Unit): ManagedActivityResultLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission(), cb)
+@Composable
+fun rememberGetContentLauncher(cb: (Uri?) -> Unit): ManagedActivityResultLauncher =
+ rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent(), cb)
+
@Composable
fun GetImageBottomSheet(
imageBitmap: MutableState,
@@ -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()
}
}
}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt
index e05633a1a..6c66b2bbb 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt
@@ -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)
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt
index 17c307f7d..478bc7b5f 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt
@@ -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)
diff --git a/apps/ios/Shared/FileUtils.swift b/apps/ios/Shared/FileUtils.swift
index 8978ad7cc..abb150965 100644
--- a/apps/ios/Shared/FileUtils.swift
+++ b/apps/ios/Shared/FileUtils.swift
@@ -26,17 +26,17 @@ func getAppFilePath(_ fileName: String) -> URL {
getAppFilesDirectory().appendingPathComponent(fileName)
}
-func getStoredFilePath(_ file: CIFile?) -> String? {
+func getLoadedFilePath(_ file: CIFile?) -> String? {
if let file = file,
- file.stored,
+ file.loaded,
let savedFile = file.filePath {
return getAppFilePath(savedFile).path
}
return nil
}
-func getStoredImage(_ file: CIFile?) -> UIImage? {
- if let filePath = getStoredFilePath(file) {
+func getLoadedImage(_ file: CIFile?) -> UIImage? {
+ if let filePath = getLoadedFilePath(file) {
return UIImage(contentsOfFile: filePath)
}
return nil
@@ -63,13 +63,22 @@ func saveFileFromURL(_ url: URL) -> String? {
func saveImage(_ uiImage: UIImage) -> String? {
if let imageDataResized = resizeImageToDataSize(uiImage, maxDataSize: maxImageSize) {
- let millisecondsSince1970 = Int64((Date().timeIntervalSince1970 * 1000.0).rounded())
- let fileName = uniqueCombine("image_\(millisecondsSince1970).jpg")
+ let timestamp = Date().getFormattedDate("yyyyMMdd_HHmmss")
+ let fileName = uniqueCombine("IMG_\(timestamp).jpg")
return saveFile(imageDataResized, fileName)
}
return nil
}
+extension Date {
+ func getFormattedDate(_ format: String) -> String {
+ let df = DateFormatter()
+ df.dateFormat = format
+ df.locale = Locale(identifier: "US")
+ return df.string(from: self)
+ }
+}
+
private func saveFile(_ data: Data, _ fileName: String) -> String? {
let filePath = getAppFilePath(fileName)
do {
diff --git a/apps/ios/Shared/Model/Shared/ChatTypes.swift b/apps/ios/Shared/Model/Shared/ChatTypes.swift
index 3ab6dc2c8..6f3981b69 100644
--- a/apps/ios/Shared/Model/Shared/ChatTypes.swift
+++ b/apps/ios/Shared/Model/Shared/ChatTypes.swift
@@ -655,15 +655,18 @@ struct CIFile: Decodable {
CIFile(fileId: fileId, fileName: fileName, fileSize: fileSize, filePath: filePath, fileStatus: fileStatus)
}
- var stored: Bool {
+ var loaded: Bool {
get {
switch self.fileStatus {
case .sndStored: return true
case .sndTransfer: return true
case .sndComplete: return true
case .sndCancelled: return true
+ case .rcvInvitation: return false
+ case .rcvAccepted: return false
+ case .rcvTransfer: return false
+ case .rcvCancelled: return false
case .rcvComplete: return true
- default: return false
}
}
}
diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift
index ec691f0cf..21fc16c5b 100644
--- a/apps/ios/Shared/Views/Chat/ChatView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatView.swift
@@ -148,7 +148,7 @@ struct ChatView: View {
} label: { Label("Reply", systemImage: "arrowshape.turn.up.left") }
Button {
var shareItems: [Any] = [ci.content.text]
- if case .image = ci.content.msgContent, let image = getStoredImage(ci.file) {
+ if case .image = ci.content.msgContent, let image = getLoadedImage(ci.file) {
shareItems.append(image)
}
showShareSheet(items: shareItems)
@@ -156,14 +156,14 @@ struct ChatView: View {
Button {
if case let .image(text, _) = ci.content.msgContent,
text == "",
- let image = getStoredImage(ci.file) {
+ let image = getLoadedImage(ci.file) {
UIPasteboard.general.image = image
} else {
UIPasteboard.general.string = ci.content.text
}
} label: { Label("Copy", systemImage: "doc.on.doc") }
if case .image = ci.content.msgContent,
- let image = getStoredImage(ci.file) {
+ let image = getLoadedImage(ci.file) {
Button {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
} label: { Label("Save", systemImage: "square.and.arrow.down") }
diff --git a/apps/ios/Shared/Views/Helpers/CIFileView.swift b/apps/ios/Shared/Views/Helpers/CIFileView.swift
index cebdfa64d..f6ced00a1 100644
--- a/apps/ios/Shared/Views/Helpers/CIFileView.swift
+++ b/apps/ios/Shared/Views/Helpers/CIFileView.swift
@@ -78,7 +78,7 @@ struct CIFileView: View {
)
case .rcvComplete:
logger.debug("CIFileView processFile - in .rcvComplete")
- if let filePath = getStoredFilePath(file){
+ if let filePath = getLoadedFilePath(file){
let url = URL(fileURLWithPath: filePath)
showShareSheet(items: [url])
}
diff --git a/apps/ios/Shared/Views/Helpers/CIImageView.swift b/apps/ios/Shared/Views/Helpers/CIImageView.swift
index ec15883cb..c4af213e6 100644
--- a/apps/ios/Shared/Views/Helpers/CIImageView.swift
+++ b/apps/ios/Shared/Views/Helpers/CIImageView.swift
@@ -18,7 +18,7 @@ struct CIImageView: View {
var body: some View {
VStack(alignment: .center, spacing: 6) {
- if let uiImage = getStoredImage(file) {
+ if let uiImage = getLoadedImage(file) {
imageView(uiImage)
.fullScreenCover(isPresented: $showFullScreenImage) {
ZStack {