From b0f55d6de5e745bfae534765c05cb5d88c3568b3 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 25 Oct 2023 10:45:36 +0400 Subject: [PATCH 1/3] core: update simplexmq (check snd queue) (#3280) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- stack.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal.project b/cabal.project index eb557e52f5..5c19fcd446 100644 --- a/cabal.project +++ b/cabal.project @@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 55a6157880396be899c010f880a42322cf65258a + tag: d920a2504b6d4653748da7d297cb13cd0a0f1f48 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 6c41ff2a1a..26188aa774 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."55a6157880396be899c010f880a42322cf65258a" = "1fhhyi2060pp72izrqki6gazb759hcv9wypxf39jkwpqpvrn81hv"; + "https://github.com/simplex-chat/simplexmq.git"."d920a2504b6d4653748da7d297cb13cd0a0f1f48" = "0r53wn01z044h6myvd458n3hiqsz64kpv59khgybzwdw5mmqnp34"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/kazu-yamamoto/http2.git"."804fa283f067bd3fd89b8c5f8d25b3047813a517" = "1j67wp7rfybfx3ryx08z6gqmzj85j51hmzhgx47ihgmgr47sl895"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "0kiwhvml42g9anw4d2v0zd1fpc790pj9syg5x3ik4l97fnkbbwpp"; diff --git a/stack.yaml b/stack.yaml index e6f6f49b76..f0fcbab1de 100644 --- a/stack.yaml +++ b/stack.yaml @@ -49,7 +49,7 @@ extra-deps: # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - ../simplexmq - github: simplex-chat/simplexmq - commit: 55a6157880396be899c010f880a42322cf65258a + commit: d920a2504b6d4653748da7d297cb13cd0a0f1f48 - github: kazu-yamamoto/http2 commit: 804fa283f067bd3fd89b8c5f8d25b3047813a517 # - ../direct-sqlcipher From 743597e8488e2a9e0bf918af4a0c96fecb96a3d6 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 25 Oct 2023 16:56:59 +0800 Subject: [PATCH 2/3] ios: making message text view working better (#3279) * ios: making message text view working better * style for ternaries --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../ComposeMessage/NativeTextEditor.swift | 74 ++++++++++++++++--- .../Chat/ComposeMessage/SendMessageView.swift | 39 +++------- 2 files changed, 75 insertions(+), 38 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift index 51deced72c..3eead5b0af 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift @@ -14,20 +14,28 @@ import PhotosUI struct NativeTextEditor: UIViewRepresentable { @Binding var text: String @Binding var disableEditing: Bool - let height: CGFloat - let font: UIFont + @Binding var height: CGFloat @Binding var focused: Bool let alignment: TextAlignment let onImagesAdded: ([UploadContent]) -> Void + private let minHeight: CGFloat = 37 + + private let defaultHeight: CGFloat = { + let field = CustomUITextField(height: Binding.constant(0)) + field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4) + return min(max(field.sizeThatFits(CGSizeMake(field.frame.size.width, CGFloat.greatestFiniteMagnitude)).height, 37), 360).rounded(.down) + }() + func makeUIView(context: Context) -> UITextView { - let field = CustomUITextField() + let field = CustomUITextField(height: _height) field.text = text - field.font = font field.textAlignment = alignment == .leading ? .left : .right field.autocapitalizationType = .sentences field.setOnTextChangedListener { newText, images in if !disableEditing { + // Speed up the process of updating layout, reduce jumping content on screen + if !isShortEmoji(newText) { updateHeight(field) } text = newText } else { field.text = text @@ -39,24 +47,72 @@ struct NativeTextEditor: UIViewRepresentable { field.setOnFocusChangedListener { focused = $0 } field.delegate = field field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4) + updateFont(field) + updateHeight(field) return field } func updateUIView(_ field: UITextView, context: Context) { field.text = text - field.font = font field.textAlignment = alignment == .leading ? .left : .right + updateFont(field) + updateHeight(field) + } + + private func updateHeight(_ field: UITextView) { + let maxHeight = min(360, field.font!.lineHeight * 12) + // When having emoji in text view and then removing it, sizeThatFits shows previous size (too big for empty text view), so using work around with default size + let newHeight = field.text == "" + ? defaultHeight + : min(max(field.sizeThatFits(CGSizeMake(field.frame.size.width, CGFloat.greatestFiniteMagnitude)).height, minHeight), maxHeight).rounded(.down) + + if field.frame.size.height != newHeight { + field.frame.size = CGSizeMake(field.frame.size.width, newHeight) + (field as! CustomUITextField).invalidateIntrinsicContentHeight(newHeight) + } + } + + private func updateFont(_ field: UITextView) { + field.font = isShortEmoji(field.text) + ? (field.text.count < 4 ? largeEmojiUIFont : mediumEmojiUIFont) + : UIFont.preferredFont(forTextStyle: .body) } } private class CustomUITextField: UITextView, UITextViewDelegate { + var height: Binding + var newHeight: CGFloat = 0 var onTextChanged: (String, [UploadContent]) -> Void = { newText, image in } var onFocusChanged: (Bool) -> Void = { focused in } + init(height: Binding) { + self.height = height + super.init(frame: .zero, textContainer: nil) + } + + required init?(coder: NSCoder) { + fatalError("Not implemented") + } + + // This func here needed because using frame.size.height in intrinsicContentSize while loading a screen with text (for example. when you have a draft), + // produces incorrect height because at that point intrinsicContentSize has old value of frame.size.height even if it was set to new value right before the call + // (who knows why...) + func invalidateIntrinsicContentHeight(_ newHeight: CGFloat) { + self.newHeight = newHeight + invalidateIntrinsicContentSize() + } + + override var intrinsicContentSize: CGSize { + if height.wrappedValue != newHeight { + DispatchQueue.main.asyncAfter(deadline: .now(), execute: { self.height.wrappedValue = self.newHeight }) + } + return CGSizeMake(0, newHeight) + } + func setOnTextChangedListener(onTextChanged: @escaping (String, [UploadContent]) -> Void) { self.onTextChanged = onTextChanged } - + func setOnFocusChangedListener(onFocusChanged: @escaping (Bool) -> Void) { self.onFocusChanged = onFocusChanged } @@ -144,14 +200,14 @@ private class CustomUITextField: UITextView, UITextViewDelegate { struct NativeTextEditor_Previews: PreviewProvider{ static var previews: some View { - return NativeTextEditor( + NativeTextEditor( text: Binding.constant("Hello, world!"), disableEditing: Binding.constant(false), - height: 100, - font: UIFont.preferredFont(forTextStyle: .body), + height: Binding.constant(100), focused: Binding.constant(false), alignment: TextAlignment.leading, onImagesAdded: { _ in } ) + .fixedSize(horizontal: false, vertical: true) } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index 6eed51788e..8f7b23c888 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -32,15 +32,12 @@ struct SendMessageView: View { var sendButtonColor = Color.accentColor @State private var teHeight: CGFloat = 42 @State private var teFont: Font = .body - @State private var teUiFont: UIFont = UIFont.preferredFont(forTextStyle: .body) @State private var sendButtonSize: CGFloat = 29 @State private var sendButtonOpacity: CGFloat = 1 @State private var showCustomDisappearingMessageDialogue = false @State private var showCustomTimePicker = false @State private var selectedDisappearingMessageTime: Int? = customDisappearingMessageTimeDefault.get() @State private var progressByTimeout = false - var maxHeight: CGFloat = 360 - var minHeight: CGFloat = 37 @AppStorage(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false var body: some View { @@ -57,30 +54,16 @@ struct SendMessageView: View { .frame(maxWidth: .infinity) } else { let alignment: TextAlignment = isRightToLeft(composeState.message) ? .trailing : .leading - Text(composeState.message) - .lineLimit(10) - .font(teFont) - .multilineTextAlignment(alignment) -// put text on top (after NativeTextEditor) and set color to precisely align it on changes -// .foregroundColor(.red) - .foregroundColor(.clear) - .padding(.horizontal, 10) - .padding(.top, 8) - .padding(.bottom, 6) - .matchedGeometryEffect(id: "te", in: namespace) - .background(GeometryReader(content: updateHeight)) - NativeTextEditor( text: $composeState.message, disableEditing: $composeState.inProgress, - height: teHeight, - font: teUiFont, + height: $teHeight, focused: $keyboardVisible, alignment: alignment, onImagesAdded: onMediaAdded ) .allowsTightening(false) - .frame(height: teHeight) + .fixedSize(horizontal: false, vertical: true) } } @@ -100,11 +83,13 @@ struct SendMessageView: View { .frame(height: teHeight, alignment: .bottom) } } - + .padding(.vertical, 1) + .overlay( RoundedRectangle(cornerSize: CGSize(width: 20, height: 20)) .strokeBorder(.secondary, lineWidth: 0.3, antialiased: true) - .frame(height: teHeight) + ) } + .onChange(of: composeState.message, perform: { text in updateFont(text) }) .onChange(of: composeState.inProgress) { inProgress in if inProgress { DispatchQueue.main.asyncAfter(deadline: .now() + 3) { @@ -415,16 +400,12 @@ struct SendMessageView: View { .padding([.bottom, .trailing], 4) } - private func updateHeight(_ g: GeometryProxy) -> Color { + private func updateFont(_ text: String) { DispatchQueue.main.async { - teHeight = min(max(g.frame(in: .local).size.height, minHeight), maxHeight) - (teFont, teUiFont) = isShortEmoji(composeState.message) - ? composeState.message.count < 4 - ? (largeEmojiFont, largeEmojiUIFont) - : (mediumEmojiFont, mediumEmojiUIFont) - : (.body, UIFont.preferredFont(forTextStyle: .body)) + teFont = isShortEmoji(text) + ? (text.count < 4 ? largeEmojiFont : mediumEmojiFont) + : .body } - return Color.clear } } From 4a8da196ad0851a5183bee5a4239e2e8bccf23e7 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:55:06 +0100 Subject: [PATCH 3/3] core: more permissive display name validation, only allow single quotes in CLI for the names with spaces (#3282) --- src/Simplex/Chat.hs | 18 ++++++++++++------ tests/ValidNames.hs | 22 +++++++++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index c70c842c04..4b6359f135 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -5839,7 +5839,7 @@ chatCommandP = mcTextP = MCText . safeDecodeUtf8 <$> A.takeByteString msgContentP = "text " *> mcTextP <|> "json " *> jsonP ciDeleteMode = "broadcast" $> CIDMBroadcast <|> "internal" $> CIDMInternal - displayName = safeDecodeUtf8 <$> (quoted "'\"" <|> takeNameTill isSpace) + displayName = safeDecodeUtf8 <$> (quoted "'" <|> takeNameTill isSpace) where takeNameTill p = A.peekChar' >>= \c -> @@ -5954,14 +5954,20 @@ timeItToView s action = do pure a mkValidName :: String -> String -mkValidName = reverse . dropWhile isSpace . fst . foldl' addChar ("", '\NUL') +mkValidName = reverse . dropWhile isSpace . fst3 . foldl' addChar ("", '\NUL', 0 :: Int) where - addChar (r, prev) c = if notProhibited && validChar then (c' : r, c') else (r, prev) + fst3 (x, _, _) = x + addChar (r, prev, punct) c = if validChar then (c' : r, c', punct') else (r, prev, punct) where c' = if isSpace c then ' ' else c + punct' + | isPunctuation c = punct + 1 + | isSpace c = punct + | otherwise = 0 validChar - | prev == '\NUL' || isSpace prev = validFirstChar - | isPunctuation prev = validFirstChar || isSpace c + | c == '\'' = False + | prev == '\NUL' = c > ' ' && c /= '#' && c /= '@' && validFirstChar + | isSpace prev = validFirstChar || (punct == 0 && isPunctuation c) + | isPunctuation prev = validFirstChar || isSpace c || (punct < 3 && isPunctuation c) | otherwise = validFirstChar || isSpace c || isMark c || isPunctuation c validFirstChar = isLetter c || isNumber c || isSymbol c - notProhibited = c `notElem` ("@#'\"`" :: String) diff --git a/tests/ValidNames.hs b/tests/ValidNames.hs index 40cda01431..0700d80846 100644 --- a/tests/ValidNames.hs +++ b/tests/ValidNames.hs @@ -14,14 +14,26 @@ testMkValidName = do mkValidName "John Doe" `shouldBe` "John Doe" mkValidName "J.Doe" `shouldBe` "J.Doe" mkValidName "J. Doe" `shouldBe` "J. Doe" - mkValidName "J..Doe" `shouldBe` "J.Doe" - mkValidName "J ..Doe" `shouldBe` "J Doe" - mkValidName "J . . Doe" `shouldBe` "J Doe" + mkValidName "J..Doe" `shouldBe` "J..Doe" + mkValidName "J ..Doe" `shouldBe` "J ..Doe" + mkValidName "J ... Doe" `shouldBe` "J ... Doe" + mkValidName "J .... Doe" `shouldBe` "J ... Doe" + mkValidName "J . . Doe" `shouldBe` "J . Doe" mkValidName "@alice" `shouldBe` "alice" mkValidName "#alice" `shouldBe` "alice" mkValidName " alice" `shouldBe` "alice" mkValidName "alice " `shouldBe` "alice" mkValidName "John Doe" `shouldBe` "John Doe" mkValidName "'John Doe'" `shouldBe` "John Doe" - mkValidName "\"John Doe\"" `shouldBe` "John Doe" - mkValidName "`John Doe`" `shouldBe` "John Doe" + mkValidName "\"John Doe\"" `shouldBe` "John Doe\"" + mkValidName "`John Doe`" `shouldBe` "`John Doe`" + mkValidName "John \"Doe\"" `shouldBe` "John \"Doe\"" + mkValidName "John `Doe`" `shouldBe` "John `Doe`" + mkValidName "alice/bob" `shouldBe` "alice/bob" + mkValidName "alice / bob" `shouldBe` "alice / bob" + mkValidName "alice /// bob" `shouldBe` "alice /// bob" + mkValidName "alice //// bob" `shouldBe` "alice /// bob" + mkValidName "alice >>= bob" `shouldBe` "alice >>= bob" + mkValidName "alice@example.com" `shouldBe` "alice@example.com" + mkValidName "alice <> bob" `shouldBe` "alice <> bob" + mkValidName "alice -> bob" `shouldBe` "alice -> bob"