From b40c9e246f6bca26c195ba6b174229c4508f057c Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Fri, 24 Apr 2026 15:25:09 +0700 Subject: [PATCH] add chat typing preview --- .../ChatSystem/prefab_ChatSystemUI.prefab | 9 ++- Assets/Scripts/ChatInputHandler.cs | 77 ++++--------------- Assets/Scripts/TypingPreviewController.cs | 59 ++++++++++++++ .../Scripts/TypingPreviewController.cs.meta | 2 + 4 files changed, 81 insertions(+), 66 deletions(-) create mode 100644 Assets/Scripts/TypingPreviewController.cs create mode 100644 Assets/Scripts/TypingPreviewController.cs.meta diff --git a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab index dbdc989313..7c6f597b49 100644 --- a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab +++ b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab @@ -238,10 +238,11 @@ MonoBehaviour: inputBarRoot: {fileID: 3282421669272709967} keyboardBottomPadding: 0 followMobileKeyboard: 1 - typingPreviewRoot: {fileID: 4018402239503345071} - typingPreviewText: {fileID: 5673195965320428126} - typingPreviewRect: {fileID: 3282421669272709967} - previewVerticalOffset: 8 + typingPreview: + typingPreviewRoot: {fileID: 4018402239503345071} + typingPreviewText: {fileID: 5673195965320428126} + typingPreviewRect: {fileID: 3282421669272709967} + previewVerticalOffset: 8 _spriteMap: {fileID: 11400000, guid: f634ecf63ca3d004f82af9b17c966fc9, type: 2} channelButtons: - channel: 0 diff --git a/Assets/Scripts/ChatInputHandler.cs b/Assets/Scripts/ChatInputHandler.cs index 7ed8df6ca6..01480922db 100644 --- a/Assets/Scripts/ChatInputHandler.cs +++ b/Assets/Scripts/ChatInputHandler.cs @@ -3,14 +3,11 @@ using UnityEngine; using TMPro; using CSNetwork.GPDataType; using System.Collections.Generic; -using BrewMonster; using BrewMonster.Network; using BrewMonster.Scripts.Chat; using BrewMonster.Scripts.Chat.EmotionData; -using BrewMonster.Scripts.Managers; using BrewMonster.UI; using CSNetwork; -using PerfectWorld.Scripts.Managers; namespace BrewMonster.Scripts.ChatUI { @@ -31,23 +28,8 @@ namespace BrewMonster.Scripts.ChatUI public TMP_InputField inputField; public ChatSystemSO chatSystem; - [Header("Mobile Keyboard Follow")] - [Tooltip("Rect chứa input/chat composer cần neo lên trên keyboard.")] - [SerializeField] private RectTransform inputBarRoot; - [Tooltip("Padding đáy thêm cho vùng safe-area/gesture.")] - [SerializeField] private float keyboardBottomPadding = 0f; - [Tooltip("Bật để input bar bám theo keyboard khi mobile keyboard mở.")] - [SerializeField] private bool followMobileKeyboard = true; - [Header("Typing Preview")] - [Tooltip("Root của box xem trước nội dung đang gõ (nằm trên input bar).")] - [SerializeField] private GameObject typingPreviewRoot; - [Tooltip("Text hiển thị nội dung đang gõ realtime.")] - [SerializeField] private TMP_Text typingPreviewText; - [Tooltip("Rect của preview box để neo theo input bar.")] - [SerializeField] private RectTransform typingPreviewRect; - [Tooltip("Khoảng cách dọc cộng thêm cho preview box.")] - [SerializeField] private float previewVerticalOffset = 8f; + [SerializeField] private TypingPreviewController typingPreview = new(); [Header("Emoji")] [Tooltip("SO ánh xạ emotion → TMP sprite tag. Gán EmotionLibrarySpriteMap đã build từ Emotion Atlas Converter.")] @@ -129,7 +111,6 @@ namespace BrewMonster.Scripts.ChatUI private float m_dwTickFarCry = 0; private float m_dwTickFarCry2 = 0; private Vector2 _inputBarBaseAnchoredPos; - private Vector2 _typingPreviewBaseAnchoredPos; private bool _cachedAnchors; private void Awake() @@ -193,13 +174,14 @@ namespace BrewMonster.Scripts.ChatUI } UpdateChatDropdownInteractable(); - CacheInitialAnchors(); UpdateTypingPreviewFromInput(); } private void Update() { - UpdateKeyboardAnchoredLayout(); + // Keyboard open/close does not always trigger input value changed, + // so refresh preview gate every frame. + UpdateTypingPreviewFromInput(); } // ===================================================== @@ -1015,40 +997,6 @@ namespace BrewMonster.Scripts.ChatUI inputField.ActivateInputField(); } - void CacheInitialAnchors() - { - if (_cachedAnchors) return; - if (inputBarRoot != null) - _inputBarBaseAnchoredPos = inputBarRoot.anchoredPosition; - if (typingPreviewRect != null) - _typingPreviewBaseAnchoredPos = typingPreviewRect.anchoredPosition; - _cachedAnchors = true; - } - - void UpdateKeyboardAnchoredLayout() - { - if (!followMobileKeyboard) - return; - - CacheInitialAnchors(); - float keyboardHeight = GetVisibleKeyboardHeight(); - float yOffset = keyboardHeight + keyboardBottomPadding; - - if (inputBarRoot != null) - { - var inputPos = _inputBarBaseAnchoredPos; - inputPos.y += yOffset; - inputBarRoot.anchoredPosition = inputPos; - } - - if (typingPreviewRect != null) - { - var previewPos = _typingPreviewBaseAnchoredPos; - previewPos.y += yOffset + previewVerticalOffset; - typingPreviewRect.anchoredPosition = previewPos; - } - } - float GetVisibleKeyboardHeight() { if (!TouchScreenKeyboard.visible) @@ -1060,14 +1008,19 @@ namespace BrewMonster.Scripts.ChatUI void UpdateTypingPreviewFromInput() { - if (typingPreviewRoot == null || typingPreviewText == null || inputField == null) + if (inputField == null) return; - string body = ExtractMessageBodyFromVisual(inputField.text ?? "")?.Trim() ?? ""; - bool shouldShow = inputField.isFocused && !string.IsNullOrEmpty(body); - typingPreviewRoot.SetActive(shouldShow); - if (shouldShow) - typingPreviewText.text = body; + string body = ExtractMessageBodyFromVisual(inputField.text ?? ""); + typingPreview?.UpdatePreview(inputField.isFocused, body, CanShowTypingPreviewNow()); + } + + bool CanShowTypingPreviewNow() + { + if (!Application.isMobilePlatform) + return false; + + return TouchScreenKeyboard.visible; } /// diff --git a/Assets/Scripts/TypingPreviewController.cs b/Assets/Scripts/TypingPreviewController.cs new file mode 100644 index 0000000000..e623afadd7 --- /dev/null +++ b/Assets/Scripts/TypingPreviewController.cs @@ -0,0 +1,59 @@ +using TMPro; +using UnityEngine; + +namespace BrewMonster.Scripts.ChatUI +{ + /// + /// Controls typing preview visibility/content and keyboard-anchored position. + /// Kept standalone so other chat/input UIs can reuse the same behavior. + /// + [System.Serializable] + public class TypingPreviewController + { + [Tooltip("Root của box xem trước nội dung đang gõ.")] + [SerializeField] private GameObject typingPreviewRoot; + [Tooltip("Text hiển thị nội dung đang gõ realtime.")] + [SerializeField] private TextMeshProUGUI typingPreviewText; + [Tooltip("Rect của preview box để neo theo keyboard.")] + [SerializeField] private RectTransform typingPreviewRect; + [Tooltip("Khoảng cách dọc cộng thêm cho preview box.")] + [SerializeField] private float previewVerticalOffset = 8f; + + private Vector2 _baseAnchoredPos; + private bool _cachedAnchor; + + public void CacheInitialAnchor() + { + if (_cachedAnchor) + return; + + if (typingPreviewRect != null) + _baseAnchoredPos = typingPreviewRect.anchoredPosition; + + _cachedAnchor = true; + } + + public void ApplyKeyboardOffset(float yOffset) + { + if (typingPreviewRect == null) + return; + + CacheInitialAnchor(); + var previewPos = _baseAnchoredPos; + previewPos.y += yOffset + previewVerticalOffset; + typingPreviewRect.anchoredPosition = previewPos; + } + + public void UpdatePreview(bool isInputFocused, string body, bool canShowPreview) + { + if (typingPreviewRoot == null || typingPreviewText == null) + return; + + string trimmedBody = (body ?? string.Empty).Trim(); + bool shouldShow = canShowPreview && isInputFocused && !string.IsNullOrEmpty(trimmedBody); + typingPreviewRoot.SetActive(shouldShow); + if (shouldShow) + typingPreviewText.text = trimmedBody; + } + } +} diff --git a/Assets/Scripts/TypingPreviewController.cs.meta b/Assets/Scripts/TypingPreviewController.cs.meta new file mode 100644 index 0000000000..d2f7801a37 --- /dev/null +++ b/Assets/Scripts/TypingPreviewController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a9934ac12c563f746a3ba562e3de5bff \ No newline at end of file