mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-20 15:41:31 +00:00
Merge branch 'master' into ep/desktop-text-selection
This commit is contained in:
@@ -98,12 +98,13 @@ struct CIImageView: View {
|
||||
if img.imageData == nil {
|
||||
Image(uiImage: img)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: w)
|
||||
.scaledToFill()
|
||||
.frame(width: w, height: w * heightRatio(img.size))
|
||||
.clipped()
|
||||
} else {
|
||||
SwiftyGif(image: img)
|
||||
.frame(width: w, height: w * img.size.height / img.size.width)
|
||||
.scaledToFit()
|
||||
SwiftyGif(image: img, contentMode: .scaleAspectFill)
|
||||
.frame(width: w, height: w * heightRatio(img.size))
|
||||
.clipped()
|
||||
}
|
||||
if !blurred || !showDownloadButton(chatItem.file?.fileStatus) {
|
||||
loadingIndicator()
|
||||
|
||||
@@ -21,7 +21,8 @@ struct CILinkView: View {
|
||||
if let uiImage = imageFromBase64(linkPreview.image) {
|
||||
Image(uiImage: uiImage)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.aspectRatio(1 / heightRatio(uiImage.size), contentMode: .fill)
|
||||
.clipped()
|
||||
.modifier(PrivacyBlur(blurred: $blurred))
|
||||
.if(!blurred) { v in
|
||||
v.simultaneousGesture(TapGesture().onEnded {
|
||||
|
||||
@@ -187,7 +187,8 @@ struct CIVideoView: View {
|
||||
ZStack(alignment: .center) {
|
||||
let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete || (file.fileStatus == .sndStored && file.fileProtocol == .local)
|
||||
VideoPlayerView(player: player, url: url, showControls: false)
|
||||
.frame(width: w, height: w * preview.size.height / preview.size.width)
|
||||
.frame(width: w, height: w * heightRatio(preview.size))
|
||||
.clipped()
|
||||
.onChange(of: m.stopPreviousRecPlay) { playingUrl in
|
||||
if playingUrl != url {
|
||||
player.pause()
|
||||
@@ -315,8 +316,9 @@ struct CIVideoView: View {
|
||||
return ZStack(alignment: .topTrailing) {
|
||||
Image(uiImage: img)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: w)
|
||||
.scaledToFill()
|
||||
.frame(width: w, height: w * heightRatio(img.size))
|
||||
.clipped()
|
||||
.modifier(PrivacyBlur(blurred: $blurred))
|
||||
if !blurred || !showDownloadButton(chatItem.file?.fileStatus) {
|
||||
fileStatusIcon()
|
||||
|
||||
@@ -29,6 +29,7 @@ struct VideoPlayerView: UIViewRepresentable {
|
||||
func makeUIView(context: UIViewRepresentableContext<VideoPlayerView>) -> UIView {
|
||||
let controller = AVPlayerViewController()
|
||||
controller.showsPlaybackControls = showControls
|
||||
controller.videoGravity = .resizeAspectFill
|
||||
if #available(iOS 16.0, *) {
|
||||
controller.speeds = []
|
||||
}
|
||||
|
||||
@@ -178,8 +178,8 @@
|
||||
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a */; };
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
|
||||
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
@@ -545,8 +545,8 @@
|
||||
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7.a"; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a"; sourceTree = "<group>"; };
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
|
||||
@@ -708,8 +708,8 @@
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -795,8 +795,8 @@
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */,
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */,
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.10-BhxwGbk3jTNAJLd7P8xtH7.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
||||
@@ -402,6 +402,11 @@ extension UIImage {
|
||||
}
|
||||
}
|
||||
|
||||
// Max image height/width ratio for chat item display, taller images are cropped
|
||||
public func heightRatio(_ size: CGSize) -> CGFloat {
|
||||
size.width > 0 ? min(size.height / size.width, 2.33) : 1
|
||||
}
|
||||
|
||||
public func imageFromBase64(_ base64Encoded: String?) -> UIImage? {
|
||||
if let base64Encoded {
|
||||
if let img = imageCache.object(forKey: base64Encoded as NSString) {
|
||||
|
||||
+7
@@ -21,12 +21,19 @@ import java.net.URI
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sqrt
|
||||
|
||||
private const val MAX_IMAGE_DIMENSION = 4320
|
||||
|
||||
actual fun base64ToBitmap(base64ImageString: String): ImageBitmap {
|
||||
val imageString = base64ImageString
|
||||
.removePrefix("data:image/png;base64,")
|
||||
.removePrefix("data:image/jpg;base64,")
|
||||
return try {
|
||||
val imageBytes = Base64.decode(imageString, Base64.NO_WRAP)
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
|
||||
if (options.outWidth <= 0 || options.outHeight <= 0 || options.outWidth > MAX_IMAGE_DIMENSION || options.outHeight > MAX_IMAGE_DIMENSION || options.outHeight > options.outWidth * 256) {
|
||||
return errorBitmap.asImageBitmap()
|
||||
}
|
||||
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size).asImageBitmap()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "base64ToBitmap error: $e")
|
||||
|
||||
+4
-4
@@ -3790,7 +3790,7 @@ object MsgReactionSerializer : KSerializer<MsgReaction> {
|
||||
when(val t = json["type"]?.jsonPrimitive?.content ?: "") {
|
||||
"emoji" -> {
|
||||
val msgReaction = try {
|
||||
val emoji = Json.decodeFromString<MREmojiChar>(json["emoji"].toString())
|
||||
val emoji = decoder.json.decodeFromString<MREmojiChar>(json["emoji"].toString())
|
||||
MsgReaction.Emoji(emoji)
|
||||
} catch (e: Throwable) {
|
||||
MsgReaction.Unknown(t, json)
|
||||
@@ -4232,7 +4232,7 @@ object MsgContentSerializer : KSerializer<MsgContent> {
|
||||
when (t) {
|
||||
"text" -> MsgContent.MCText(text)
|
||||
"link" -> {
|
||||
val preview = Json.decodeFromString<LinkPreview>(json["preview"].toString())
|
||||
val preview = decoder.json.decodeFromString<LinkPreview>(json["preview"].toString())
|
||||
MsgContent.MCLink(text, preview)
|
||||
}
|
||||
"image" -> {
|
||||
@@ -4250,11 +4250,11 @@ object MsgContentSerializer : KSerializer<MsgContent> {
|
||||
}
|
||||
"file" -> MsgContent.MCFile(text)
|
||||
"report" -> {
|
||||
val reason = Json.decodeFromString<ReportReason>(json["reason"].toString())
|
||||
val reason = decoder.json.decodeFromString<ReportReason>(json["reason"].toString())
|
||||
MsgContent.MCReport(text, reason)
|
||||
}
|
||||
"chat" -> {
|
||||
val chatLink = Json.decodeFromString<MsgChatLink>(json["chatLink"].toString())
|
||||
val chatLink = decoder.json.decodeFromString<MsgChatLink>(json["chatLink"].toString())
|
||||
MsgContent.MCChat(text, chatLink)
|
||||
}
|
||||
else -> MsgContent.MCUnknown(t, text, json)
|
||||
|
||||
+4
-1
@@ -437,7 +437,10 @@ fun PriorityLayout(
|
||||
) { measureable, constraints ->
|
||||
// Find important element which should tell what max width other elements can use
|
||||
// Expecting only one such element. Can be less than one but not more
|
||||
val imagePlaceable = measureable.firstOrNull { it.layoutId == priorityLayoutId }?.measure(constraints)
|
||||
// Max image height for chat item display, taller images are cropped
|
||||
val maxImageHeight = (constraints.maxWidth * 2.33f).toInt().coerceAtMost(constraints.maxHeight)
|
||||
val imageConstraints = constraints.copy(maxHeight = maxImageHeight)
|
||||
val imagePlaceable = measureable.firstOrNull { it.layoutId == priorityLayoutId }?.measure(imageConstraints)
|
||||
val placeables: List<Placeable> = measureable.map {
|
||||
if (it.layoutId == priorityLayoutId)
|
||||
imagePlaceable!!
|
||||
|
||||
+15
-1
@@ -25,13 +25,27 @@ private val base64BitmapCache = Collections.synchronizedMap(object : LinkedHashM
|
||||
override fun removeEldestEntry(eldest: Map.Entry<String, ImageBitmap>): Boolean = size > 200
|
||||
})
|
||||
|
||||
private const val MAX_IMAGE_DIMENSION = 4320
|
||||
|
||||
actual fun base64ToBitmap(base64ImageString: String): ImageBitmap {
|
||||
base64BitmapCache[base64ImageString]?.let { return it }
|
||||
val imageString = base64ImageString
|
||||
.removePrefix("data:image/png;base64,")
|
||||
.removePrefix("data:image/jpg;base64,")
|
||||
return try {
|
||||
ImageIO.read(ByteArrayInputStream(Base64.getMimeDecoder().decode(imageString))).toComposeImageBitmap().also {
|
||||
val bytes = Base64.getMimeDecoder().decode(imageString)
|
||||
val stream = ImageIO.createImageInputStream(ByteArrayInputStream(bytes))
|
||||
val reader = ImageIO.getImageReaders(stream).next()
|
||||
reader.setInput(stream)
|
||||
val width = reader.getWidth(0)
|
||||
val height = reader.getHeight(0)
|
||||
if (width <= 0 || height <= 0 || width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION || height > width * 256) {
|
||||
reader.dispose()
|
||||
return errorBitmap()
|
||||
}
|
||||
val image = reader.read(0)
|
||||
reader.dispose()
|
||||
image.toComposeImageBitmap().also {
|
||||
base64BitmapCache[base64ImageString] = it
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
|
||||
@@ -8,6 +8,8 @@ revision: 09.09.2024
|
||||
|
||||
You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases).
|
||||
|
||||
If you cannot access GitHub, you can download SimpleX Chat apps from our mirror at [git.simplex.chat](https://git.simplex.chat/simplex-chat/simplex-chat/releases)
|
||||
|
||||
- [desktop](#desktop-app)
|
||||
- [mobile](#mobile-apps)
|
||||
- [terminal](#terminal-console-app) (console)
|
||||
|
||||
@@ -38,6 +38,27 @@
|
||||
</description>
|
||||
|
||||
<releases>
|
||||
<release version="6.4.11" date="2026-03-30">
|
||||
<url type="details">https://simplex.chat/blog/20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.html</url>
|
||||
<description>
|
||||
<p>New in v6.4.11:</p>
|
||||
<ul>
|
||||
<li>improve image, video and link messages.</li>
|
||||
</ul>
|
||||
<p>New in v6.4-6.4.10:</p>
|
||||
<ul>
|
||||
<li>new UX to connect.</li>
|
||||
<li>review new group members.</li>
|
||||
<li>chat with group admins.</li>
|
||||
<li>new UI languages: Catalan, Indonesian, Romanian and Vietnamese.</li>
|
||||
<li>Linux app builds for aarch64 CPUs</li>
|
||||
<li>UI support for bot commands.</li>
|
||||
<li>support markdown hyperlinks, such as [click here](https://example.com).</li>
|
||||
<li>option to remove tracking parameters from the links.</li>
|
||||
<li>better information about network errors.</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="6.4.10" date="2026-01-29">
|
||||
<url type="details">https://simplex.chat/blog/20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.html</url>
|
||||
<description>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 6.5.0.10
|
||||
version: 6.5.0.11
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
|
||||
Reference in New Issue
Block a user