ios: Ability to add stickers (#1593)

* ios: Ability to add stickers

* fix text alignment for correct input field height

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
Stanislav Dmitrenko
2022-12-17 12:01:49 +03:00
committed by GitHub
parent e410fc7736
commit b4de9c266b
6 changed files with 129 additions and 15 deletions
@@ -190,7 +190,7 @@ struct ComposeView: View {
Image(systemName: "paperclip")
.resizable()
}
.disabled(composeState.editing || composeState.voiceMessageRecordingState != .noRecording)
.disabled(composeState.editing || !(composeState.noPreview || composeState.linkPreview != nil))
.frame(width: 25, height: 25)
.padding(.bottom, 12)
.padding(.leading, 12)
@@ -209,6 +209,7 @@ struct ComposeView: View {
},
finishVoiceMessageRecording: { finishVoiceMessageRecording() },
allowVoiceMessagesToContact: { allowVoiceMessagesToContact() },
onImageAdded: { image in chosenImages = [image] },
keyboardVisible: $keyboardVisible
)
.padding(.trailing, 12)
@@ -0,0 +1,96 @@
//
// NativeTextEditor.swift
// SimpleX (iOS)
//
// Created by Avently on 15.12.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct NativeTextEditor: UIViewRepresentable {
@Binding var text: String
let height: CGFloat
let font: UIFont
@FocusState.Binding var focused: Bool
let alignment: TextAlignment
let onImageAdded: (UIImage) -> Void
func makeUIView(context: Context) -> UITextView {
let field = CustomUITextField()
field.allowsEditingTextAttributes = true
field.text = text
field.font = font
field.textAlignment = alignment == .leading ? .left : .right
field.autocapitalizationType = .sentences
field.setOnTextChangedListener { newText, image in
text = newText
if let image = image {
onImageAdded(image)
}
}
field.setOnFocusChangedListener { focused = $0 }
field.delegate = field
field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4)
return field
}
func updateUIView(_ field: UITextView, context: Context) {
field.text = text
field.font = font
field.textAlignment = alignment == .leading ? .left : .right
}
}
private class CustomUITextField: UITextView, UITextViewDelegate {
var onTextChanged: (String, UIImage?) -> Void = { newText, image in }
var onFocusChanged: (Bool) -> Void = { focused in }
func setOnTextChangedListener(onTextChanged: @escaping (String, UIImage?) -> Void) {
self.onTextChanged = onTextChanged
}
func setOnFocusChangedListener(onFocusChanged: @escaping (Bool) -> Void) {
self.onFocusChanged = onFocusChanged
}
func textViewDidChange(_ textView: UITextView) {
var image: UIImage? = nil
textView.attributedText.enumerateAttribute(
NSAttributedString.Key.attachment,
in: NSRange(location: 0, length: textView.attributedText.length),
options: [],
using: { value, range, _ in
if let attachment = (value as? NSTextAttachment)?.image {
image = attachment
let newText = NSMutableAttributedString(attributedString: textView.attributedText)
newText.replaceCharacters(in: range, with: "")
textView.attributedText = newText
}
}
)
onTextChanged(textView.text, image)
}
func textViewDidBeginEditing(_ textView: UITextView) {
onFocusChanged(true)
}
func textViewDidEndEditing(_ textView: UITextView) {
onFocusChanged(false)
}
}
struct NativeTextEditor_Previews: PreviewProvider{
static var previews: some View {
@FocusState var keyboardVisible: Bool
return NativeTextEditor(
text: Binding.constant("Hello, world!"),
height: 100,
font: UIFont.preferredFont(forTextStyle: .body),
focused: $keyboardVisible,
alignment: TextAlignment.leading,
onImageAdded: { _ in }
)
}
}
@@ -18,11 +18,13 @@ struct SendMessageView: View {
var startVoiceMessageRecording: (() -> Void)? = nil
var finishVoiceMessageRecording: (() -> Void)? = nil
var allowVoiceMessagesToContact: (() -> Void)? = nil
var onImageAdded: (UIImage) -> Void
@State private var holdingVMR = false
@Namespace var namespace
@FocusState.Binding var keyboardVisible: Bool
@State private var teHeight: CGFloat = 42
@State private var teFont: Font = .body
@State private var teUiFont: UIFont = UIFont.preferredFont(forTextStyle: .body)
var maxHeight: CGFloat = 360
var minHeight: CGFloat = 37
@@ -44,19 +46,25 @@ struct SendMessageView: View {
.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(.vertical, 8)
.padding(.top, 8)
.padding(.bottom, 6)
.matchedGeometryEffect(id: "te", in: namespace)
.background(GeometryReader(content: updateHeight))
TextEditor(text: $composeState.message)
.focused($keyboardVisible)
.font(teFont)
.textInputAutocapitalization(.sentences)
.multilineTextAlignment(alignment)
.padding(.horizontal, 5)
.allowsTightening(false)
.frame(height: teHeight)
NativeTextEditor(
text: $composeState.message,
height: teHeight,
font: teUiFont,
focused: $keyboardVisible,
alignment: alignment,
onImageAdded: onImageAdded
)
.allowsTightening(false)
.frame(height: teHeight)
}
}
@@ -195,11 +203,11 @@ struct SendMessageView: View {
private func updateHeight(_ g: GeometryProxy) -> Color {
DispatchQueue.main.async {
teHeight = min(max(g.frame(in: .local).size.height, minHeight), maxHeight)
teFont = isShortEmoji(composeState.message)
? composeState.message.count < 4
? largeEmojiFont
: mediumEmojiFont
: .body
(teFont, teUiFont) = isShortEmoji(composeState.message)
? composeState.message.count < 4
? (largeEmojiFont, largeEmojiUIFont)
: (mediumEmojiFont, mediumEmojiUIFont)
: (.body, UIFont.preferredFont(forTextStyle: .body))
}
return Color.clear
}
@@ -220,6 +228,7 @@ struct SendMessageView_Previews: PreviewProvider {
SendMessageView(
composeState: $composeStateNew,
sendMessage: {},
onImageAdded: { _ in },
keyboardVisible: $keyboardVisible
)
}
@@ -229,6 +238,7 @@ struct SendMessageView_Previews: PreviewProvider {
SendMessageView(
composeState: $composeStateEditing,
sendMessage: {},
onImageAdded: { _ in },
keyboardVisible: $keyboardVisible
)
}
+2
View File
@@ -28,4 +28,6 @@ func isShortEmoji(_ str: String) -> Bool {
}
let largeEmojiFont = Font.custom("Emoji", size: 48, relativeTo: .largeTitle)
let largeEmojiUIFont: UIFont = UIFont(name: "Emoji", size: 48) ?? UIFont.systemFont(ofSize: 48)
let mediumEmojiFont = Font.custom("Emoji", size: 36, relativeTo: .largeTitle)
let mediumEmojiUIFont: UIFont = UIFont(name: "Emoji", size: 36) ?? UIFont.systemFont(ofSize: 36)
+1
View File
@@ -87,6 +87,7 @@ struct TerminalView: View {
composeState: $composeState,
sendMessage: sendMessage,
showVoiceMessageButton: false,
onImageAdded: { _ in },
keyboardVisible: $keyboardVisible
)
.padding(.horizontal, 12)
@@ -155,6 +155,7 @@
64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; };
64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; };
64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; };
D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -370,6 +371,7 @@
64DAE1502809D9F5000DA960 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = "<group>"; };
64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = "<group>"; };
D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -702,6 +704,7 @@
6454036E2822A9750090DDFF /* ComposeFileView.swift */,
3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */,
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */,
D72A9087294BD7A70047C86D /* NativeTextEditor.swift */,
);
path = ComposeMessage;
sourceTree = "<group>";
@@ -955,6 +958,7 @@
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */,
5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */,
5C10D88A28F187F300E58BF0 /* FullScreenImageView.swift in Sources */,
D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */,
5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */,
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */,
5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */,