From 100e734a0f5bc7438a3a3c04a05bbe28df5c5a1d Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Wed, 22 Apr 2026 16:32:58 +0700 Subject: [PATCH 01/10] Test preview chat for mobile --- .../Scripts/Chat/UI/ChatSystemlUI.cs | 19 +- .../Scripts/Chat/UI/MiniChatUI.cs | 2 +- .../ChatSystem/prefab_ChatSystemUI.prefab | 220 ++++++++++++++++++ Assets/Scripts/ChatInputHandler.cs | 89 +++++++ 4 files changed, 326 insertions(+), 4 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Chat/UI/ChatSystemlUI.cs b/Assets/PerfectWorld/Scripts/Chat/UI/ChatSystemlUI.cs index 17d416447b..fdb12bd4b2 100644 --- a/Assets/PerfectWorld/Scripts/Chat/UI/ChatSystemlUI.cs +++ b/Assets/PerfectWorld/Scripts/Chat/UI/ChatSystemlUI.cs @@ -156,7 +156,7 @@ namespace BrewMonster.Scripts.ChatUI void OnOpenChatPanelRequested(OpenChatPanelRequestedEvent _) { if (this == null) return; - OpenChatPanel(); + OpenChatPanel(_.focusInputOnOpen); } private bool ShouldShowMessage(ChatMessageData data) @@ -347,13 +347,18 @@ namespace BrewMonster.Scripts.ChatUI } public void OpenChatPanel() + { + OpenChatPanel(true); + } + + public void OpenChatPanel(bool focusInputOnOpen) { if (chatPanelUIGO == null) return; SetChatPanelAndBgVisible(true); RefreshVisible(); _chatInput ??= GetComponent(); - if (_chatInput != null && _chatInput.inputField != null) + if (focusInputOnOpen && _chatInput != null && _chatInput.inputField != null) _chatInput.inputField.ActivateInputField(); } @@ -392,5 +397,13 @@ namespace BrewMonster.Scripts.ChatUI /// /// Mini chat (hoặc HUD) publish để mở panel chat đầy đủ; subscribe. /// - public struct OpenChatPanelRequestedEvent { } + public struct OpenChatPanelRequestedEvent + { + public bool focusInputOnOpen; + + public OpenChatPanelRequestedEvent(bool focusInputOnOpen) + { + this.focusInputOnOpen = focusInputOnOpen; + } + } } diff --git a/Assets/PerfectWorld/Scripts/Chat/UI/MiniChatUI.cs b/Assets/PerfectWorld/Scripts/Chat/UI/MiniChatUI.cs index 101a4f021e..132a66326a 100644 --- a/Assets/PerfectWorld/Scripts/Chat/UI/MiniChatUI.cs +++ b/Assets/PerfectWorld/Scripts/Chat/UI/MiniChatUI.cs @@ -84,7 +84,7 @@ namespace BrewMonster.Scripts.ChatUI if (!string.IsNullOrEmpty(chatDialogName) && CECUIManager.Instance != null) CECUIManager.Instance.ShowUI(chatDialogName); - EventBus.Publish(new OpenChatPanelRequestedEvent()); + EventBus.Publish(new OpenChatPanelRequestedEvent(false)); } void OnChannelFilterChanged(ChatChannelFilterChangedEvent e) diff --git a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab index f1481018a6..baca285293 100644 --- a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab +++ b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab @@ -187,6 +187,7 @@ RectTransform: m_Children: - {fileID: 2739455247741079987} - {fileID: 1473246120053017968} + - {fileID: 3282421669272709967} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} @@ -234,6 +235,13 @@ MonoBehaviour: m_EditorClassIdentifier: inputField: {fileID: 3037472824809581303} chatSystem: {fileID: 11400000, guid: 43f54723aa074c74e83e5be28975bee5, type: 2} + inputBarRoot: {fileID: 6545370471477453367} + keyboardBottomPadding: 0 + followMobileKeyboard: 1 + typingPreviewRoot: {fileID: 4018402239503345071} + typingPreviewText: {fileID: 5673195965320428126} + typingPreviewRect: {fileID: 3282421669272709967} + previewVerticalOffset: 8 _spriteMap: {fileID: 11400000, guid: f634ecf63ca3d004f82af9b17c966fc9, type: 2} channelButtons: - channel: 0 @@ -1110,6 +1118,142 @@ MonoBehaviour: m_OnValueChanged: m_PersistentCalls: m_Calls: [] +--- !u!1 &2898638287858915607 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2130739312226787242} + - component: {fileID: 9133278188770223934} + - component: {fileID: 5673195965320428126} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2130739312226787242 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2898638287858915607} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3282421669272709967} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &9133278188770223934 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2898638287858915607} + m_CullTransparentMesh: 1 +--- !u!114 &5673195965320428126 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2898638287858915607} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: New Text + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} + m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 0, g: 0, b: 0, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 36 + m_fontSizeBase: 36 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &3095457626805744279 GameObject: m_ObjectHideFlags: 0 @@ -1869,6 +2013,82 @@ MonoBehaviour: m_OnClick: m_PersistentCalls: m_Calls: [] +--- !u!1 &4018402239503345071 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3282421669272709967} + - component: {fileID: 1622721548801034628} + - component: {fileID: 7848833373218203551} + m_Layer: 5 + m_Name: Typing Preview + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &3282421669272709967 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4018402239503345071} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2130739312226787242} + m_Father: {fileID: 806170753671297629} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1622721548801034628 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4018402239503345071} + m_CullTransparentMesh: 1 +--- !u!114 &7848833373218203551 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4018402239503345071} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 --- !u!1 &4042949282556349356 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/ChatInputHandler.cs b/Assets/Scripts/ChatInputHandler.cs index 88371501f1..7ed8df6ca6 100644 --- a/Assets/Scripts/ChatInputHandler.cs +++ b/Assets/Scripts/ChatInputHandler.cs @@ -31,6 +31,24 @@ 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; + [Header("Emoji")] [Tooltip("SO ánh xạ emotion → TMP sprite tag. Gán EmotionLibrarySpriteMap đã build từ Emotion Atlas Converter.")] [SerializeField] EmotionLibrarySpriteMap _spriteMap; @@ -110,6 +128,9 @@ 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() { @@ -172,6 +193,13 @@ namespace BrewMonster.Scripts.ChatUI } UpdateChatDropdownInteractable(); + CacheInitialAnchors(); + UpdateTypingPreviewFromInput(); + } + + private void Update() + { + UpdateKeyboardAnchoredLayout(); } // ===================================================== @@ -812,6 +840,8 @@ namespace BrewMonster.Scripts.ChatUI { inputField.text = ""; } + + UpdateTypingPreviewFromInput(); } private int GetEmotionSetForCurrentChannel() @@ -877,6 +907,7 @@ namespace BrewMonster.Scripts.ChatUI if (_syncingWireToDisplay) { _lastInputFieldText = newText; + UpdateTypingPreviewFromInput(); return; } @@ -884,6 +915,7 @@ namespace BrewMonster.Scripts.ChatUI { _lastInputFieldText = newText; SyncChatWireBodyFromInput(); + UpdateTypingPreviewFromInput(); return; } @@ -905,11 +937,13 @@ namespace BrewMonster.Scripts.ChatUI inputField.ForceLabelUpdate(); _lastInputFieldText = fixedText; SyncChatWireBodyFromInput(); + UpdateTypingPreviewFromInput(); return; } _lastInputFieldText = newText; SyncChatWireBodyFromInput(); + UpdateTypingPreviewFromInput(); } /// @@ -981,6 +1015,61 @@ 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) + return 0f; + + Rect area = TouchScreenKeyboard.area; + return area.height > 0f ? area.height : 0f; + } + + void UpdateTypingPreviewFromInput() + { + if (typingPreviewRoot == null || typingPreviewText == null || inputField == null) + return; + + string body = ExtractMessageBodyFromVisual(inputField.text ?? "")?.Trim() ?? ""; + bool shouldShow = inputField.isFocused && !string.IsNullOrEmpty(body); + typingPreviewRoot.SetActive(shouldShow); + if (shouldShow) + typingPreviewText.text = body; + } + /// /// C++: GetHostPlayer()->GetPack()->GetItemTotalNum(id) — đếm túi chính. /// From 1c83f867e5ce97fd1fa825cc3d5118c0db413489 Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Wed, 22 Apr 2026 17:12:39 +0700 Subject: [PATCH 02/10] update prefab --- Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab index baca285293..dbdc989313 100644 --- a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab +++ b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab @@ -235,7 +235,7 @@ MonoBehaviour: m_EditorClassIdentifier: inputField: {fileID: 3037472824809581303} chatSystem: {fileID: 11400000, guid: 43f54723aa074c74e83e5be28975bee5, type: 2} - inputBarRoot: {fileID: 6545370471477453367} + inputBarRoot: {fileID: 3282421669272709967} keyboardBottomPadding: 0 followMobileKeyboard: 1 typingPreviewRoot: {fileID: 4018402239503345071} From b40c9e246f6bca26c195ba6b174229c4508f057c Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Fri, 24 Apr 2026 15:25:09 +0700 Subject: [PATCH 03/10] 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 From 09b2a2b6153634e622c53bbddf930140895d19a1 Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Fri, 24 Apr 2026 16:27:33 +0700 Subject: [PATCH 04/10] add set focus skill --- Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillSubPool.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillSubPool.cs b/Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillSubPool.cs index 6dd920d158..dbbe068e7f 100644 --- a/Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillSubPool.cs +++ b/Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillSubPool.cs @@ -28,6 +28,7 @@ namespace BrewMonster public override void OnEnable() { + _currentSelectSkill = null; UpdateView(); _skillSetUpComboWidget.ShowSetUpContent(false); _skillSetUpComboWidget.OnClickedSkillSlot += OnClickedSkillSlot; @@ -45,6 +46,7 @@ namespace BrewMonster public override void OnDisable() { + _currentSelectComboSlot = null; if (_currentSelectSkill is LearnedSkillUI learnedOnClose) { learnedOnClose.SetFocusFrame(false); From d43f50e8266bdcfee98035228ab7f3ef4f2070c4 Mon Sep 17 00:00:00 2001 From: Tran Hai Nam Date: Fri, 24 Apr 2026 17:49:20 +0700 Subject: [PATCH 05/10] Fix move sfx not stop --- Assets/PerfectWorld/Scripts/Sound/SFXManager.cs | 5 +++++ Assets/Scripts/CECHostPlayer.cs | 11 +++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Sound/SFXManager.cs b/Assets/PerfectWorld/Scripts/Sound/SFXManager.cs index f98c969b14..1599880e6c 100644 --- a/Assets/PerfectWorld/Scripts/Sound/SFXManager.cs +++ b/Assets/PerfectWorld/Scripts/Sound/SFXManager.cs @@ -219,5 +219,10 @@ namespace BrewMonster.Scripts _moveSoundSource.Play(); } } + public async UniTaskVoid StopMoveSoundAsync() + { + if (_moveSoundSource == null) return; + _moveSoundSource.Stop(); + } } } diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 00d1f5cd6d..9745b693a7 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -4213,7 +4213,7 @@ namespace BrewMonster } else { - newId = 164; + newId = 0; } PlayMoveSound(newId); @@ -4227,7 +4227,14 @@ namespace BrewMonster { if (id == _curMoveSndId) return; _curMoveSndId = id; - SFXManager.Instance?.PlayMoveSoundAsync(id).Forget(); + if(id > 0) + { + SFXManager.Instance?.PlayMoveSoundAsync(id).Forget(); + } + else + { + SFXManager.Instance?.StopMoveSoundAsync().Forget(); + } } /// From 7bb80c2ca0d1240150c4bdc41d16f9b9d648fdb0 Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Sat, 25 Apr 2026 10:33:08 +0700 Subject: [PATCH 06/10] update verticals offset for prefab typing preview --- Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab index 7c6f597b49..2c4486ae11 100644 --- a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab +++ b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab @@ -235,14 +235,11 @@ MonoBehaviour: m_EditorClassIdentifier: inputField: {fileID: 3037472824809581303} chatSystem: {fileID: 11400000, guid: 43f54723aa074c74e83e5be28975bee5, type: 2} - inputBarRoot: {fileID: 3282421669272709967} - keyboardBottomPadding: 0 - followMobileKeyboard: 1 typingPreview: typingPreviewRoot: {fileID: 4018402239503345071} typingPreviewText: {fileID: 5673195965320428126} typingPreviewRect: {fileID: 3282421669272709967} - previewVerticalOffset: 8 + previewVerticalOffset: 100 _spriteMap: {fileID: 11400000, guid: f634ecf63ca3d004f82af9b17c966fc9, type: 2} channelButtons: - channel: 0 @@ -2051,7 +2048,7 @@ RectTransform: m_AnchorMax: {x: 1, y: 0.5} m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 100} - m_Pivot: {x: 0.5, y: 0.5} + m_Pivot: {x: 0.5, y: 1} --- !u!222 &1622721548801034628 CanvasRenderer: m_ObjectHideFlags: 0 From edc2dc4cf9e24007b6f003c6cf6295dfc0e35718 Mon Sep 17 00:00:00 2001 From: Tran Hai Nam Date: Sat, 25 Apr 2026 10:33:19 +0700 Subject: [PATCH 07/10] fix missing id in weapon sound --- Assets/PerfectWorld/Scripts/Move/CECPlayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs index 70bc99ecc1..c71f88878e 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs @@ -278,6 +278,7 @@ namespace BrewMonster {2, new List{"item/weaponattack/2hlonga", "item/weaponattack/2hlongb", "item/weaponattack/2hlongc", "item/weaponattack/2hlongd"}}, {3, new List{"item/weaponattack/1hshorta", "item/weaponattack/1hshortb", "item/weaponattack/1hshortc"}}, {4, new List{"item/weaponattack/2hlonga", "item/weaponattack/2hlongb", "item/weaponattack/2hlongc", "item/weaponattack/2hlongd"}}, + {5, new List{"item/weaponattack/1hshorta", "item/weaponattack/1hshortb"}}, {6, new List{"item/weaponattack/bow", "item/weaponattack/bowb", "item/weaponattack/drawbow"}}, {7, new List{"item/weaponattack/bow", "item/weaponattack/bowb", "item/weaponattack/drawbow"}}, {8, new List{"item/weaponattack/fista", "item/weaponattack/fistb", "item/weaponattack/fistc", "item/weaponattack/fistd"}}, @@ -297,6 +298,7 @@ namespace BrewMonster {2, new List{"item/weaponattack/hitmace", "item/weaponattack/hitmacebig"}}, {3, new List{"item/weaponattack/hithammer", "item/weaponattack/hithammerbig"}}, {4, new List{"item/weaponattack/hitaxe", "item/weaponattack/hitaxebig"}}, + {5, new List{"item/weaponattack/hithammer"}}, {6, new List{"item/weaponattack/hitthrow"}}, {7, new List{"item/weaponattack/hitthrow"}}, {8, new List{"item/weaponattack/hithand"}}, From 0c6f73b4db3517e0b4aca16010db38b1dbdfa251 Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Sat, 25 Apr 2026 10:51:54 +0700 Subject: [PATCH 08/10] update logic check height typingPreview --- .../ChatSystem/prefab_ChatSystemUI.prefab | 8 +++---- Assets/Scripts/ChatInputHandler.cs | 19 +---------------- Assets/Scripts/TypingPreviewController.cs | 21 +++++++++++++++++-- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab index 2c4486ae11..ddbe8d402a 100644 --- a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab +++ b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab @@ -2044,10 +2044,10 @@ RectTransform: - {fileID: 2130739312226787242} m_Father: {fileID: 806170753671297629} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0.5} - m_AnchorMax: {x: 1, y: 0.5} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 100} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -100} + m_SizeDelta: {x: 0, y: 450.9249} m_Pivot: {x: 0.5, y: 1} --- !u!222 &1622721548801034628 CanvasRenderer: diff --git a/Assets/Scripts/ChatInputHandler.cs b/Assets/Scripts/ChatInputHandler.cs index 01480922db..af0c5bca5b 100644 --- a/Assets/Scripts/ChatInputHandler.cs +++ b/Assets/Scripts/ChatInputHandler.cs @@ -997,30 +997,13 @@ namespace BrewMonster.Scripts.ChatUI inputField.ActivateInputField(); } - float GetVisibleKeyboardHeight() - { - if (!TouchScreenKeyboard.visible) - return 0f; - - Rect area = TouchScreenKeyboard.area; - return area.height > 0f ? area.height : 0f; - } - void UpdateTypingPreviewFromInput() { if (inputField == null) return; string body = ExtractMessageBodyFromVisual(inputField.text ?? ""); - typingPreview?.UpdatePreview(inputField.isFocused, body, CanShowTypingPreviewNow()); - } - - bool CanShowTypingPreviewNow() - { - if (!Application.isMobilePlatform) - return false; - - return TouchScreenKeyboard.visible; + typingPreview?.UpdatePreview(inputField.isFocused, body); } /// diff --git a/Assets/Scripts/TypingPreviewController.cs b/Assets/Scripts/TypingPreviewController.cs index e623afadd7..358b3569d0 100644 --- a/Assets/Scripts/TypingPreviewController.cs +++ b/Assets/Scripts/TypingPreviewController.cs @@ -44,16 +44,33 @@ namespace BrewMonster.Scripts.ChatUI typingPreviewRect.anchoredPosition = previewPos; } - public void UpdatePreview(bool isInputFocused, string body, bool canShowPreview) + public void UpdatePreview(bool isInputFocused, string body) { if (typingPreviewRoot == null || typingPreviewText == null) return; string trimmedBody = (body ?? string.Empty).Trim(); - bool shouldShow = canShowPreview && isInputFocused && !string.IsNullOrEmpty(trimmedBody); + bool keyboardVisible = IsMobileKeyboardVisible(); + bool shouldShow = keyboardVisible && isInputFocused && !string.IsNullOrEmpty(trimmedBody); typingPreviewRoot.SetActive(shouldShow); if (shouldShow) typingPreviewText.text = trimmedBody; } + + bool IsMobileKeyboardVisible() + { + if (!Application.isMobilePlatform) + return false; + return GetVisibleKeyboardHeight() > 0f || TouchScreenKeyboard.visible; + } + + float GetVisibleKeyboardHeight() + { + if (!TouchScreenKeyboard.visible) + return 0f; + + Rect area = TouchScreenKeyboard.area; + return area.height > 0f ? area.height : 0f; + } } } From b6db8ce972cc584cd205fab0d45e4d2412edab04 Mon Sep 17 00:00:00 2001 From: NguyenVanDat Date: Sat, 25 Apr 2026 11:12:01 +0700 Subject: [PATCH 09/10] force quit half when confirm log out --- .../Scripts/Network/CSNetwork/GameSession.cs | 2 ++ .../Scripts/UI/Login/BtnBackToSelectRole.cs | 17 +++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index 05c1150c86..4bcd7bb5a8 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -52,6 +52,8 @@ namespace CSNetwork private Action _createRoleCallback; private RoleInfo _selectedRole; public bool IsConnected => _networkManager?.IsConnected ?? false; + public bool IsConnectedInternet => Application.internetReachability != NetworkReachability.NotReachable; + // When true, suppress *gameplay traffic* (mostly gamedatasend C2S commands) during logout/scene transitions. // We still allow account/role flow protocols like rolelist/selectrole so "Return to Select Role" can work. private volatile bool _suppressGameplayTraffic = false; diff --git a/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs b/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs index 1c13540f72..5d5abc9835 100644 --- a/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs +++ b/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs @@ -11,17 +11,17 @@ namespace BrewMonster.UI public void OnClick() { // CECUIManager.Instance.ShowMessageBox( - // title: "Thoát", + // title: "Thoát", // message: "Đang rời khỏi Thế Giới Hoàn Mỹ", // messageBoxType: MessageBoxType.YesButton // ); - + // CECGameRun.Instance.GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutHalf()); // UnityGameSession.ReturnToSelectRole(); OnCommandRepick(); } - + // void CDlgSystem3::OnCommandRepick(const char *szCommand) // { // a_LogOutput(1, "CDlgSystem3::OnCommandRepick "); @@ -36,8 +36,8 @@ namespace BrewMonster.UI // pMsgBox->SetIsModal(false); // } // } - - + + void OnCommandRepick() { CECUIManager.Instance.ShowMessageBoxYes("Thoát", @@ -47,8 +47,13 @@ namespace BrewMonster.UI void OnClickYes() { CECGameRun.Instance.GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutHalf()); + if (!UnityGameSession.Instance.GameSession.IsConnectedInternet) + { + //force log out half + EC_Game.GetGameRun().SetLogoutFlag(1); + } } - + } } From 99cb7d0fc0ff23fe60dfcfaff7bdffa5e5695aeb Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Sat, 25 Apr 2026 11:37:57 +0700 Subject: [PATCH 10/10] add logic show emoji for typingPreview and file md --- .../ChatSystem/prefab_ChatSystemUI.prefab | 433 +++++++++++++++++- Assets/Scripts/TypingPreviewController.cs | 8 +- Assets/Scripts/typing-preview-summary.md | 60 +++ Assets/Scripts/typing-preview-summary.md.meta | 7 + 4 files changed, 481 insertions(+), 27 deletions(-) create mode 100644 Assets/Scripts/typing-preview-summary.md create mode 100644 Assets/Scripts/typing-preview-summary.md.meta diff --git a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab index ddbe8d402a..11af5afd56 100644 --- a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab +++ b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab @@ -237,7 +237,7 @@ MonoBehaviour: chatSystem: {fileID: 11400000, guid: 43f54723aa074c74e83e5be28975bee5, type: 2} typingPreview: typingPreviewRoot: {fileID: 4018402239503345071} - typingPreviewText: {fileID: 5673195965320428126} + typingPreviewText: {fileID: 1616725709352662407} typingPreviewRect: {fileID: 3282421669272709967} previewVerticalOffset: 100 _spriteMap: {fileID: 11400000, guid: f634ecf63ca3d004f82af9b17c966fc9, type: 2} @@ -1116,7 +1116,7 @@ MonoBehaviour: m_OnValueChanged: m_PersistentCalls: m_Calls: [] ---- !u!1 &2898638287858915607 +--- !u!1 &2902411919389723293 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -1124,50 +1124,51 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 2130739312226787242} - - component: {fileID: 9133278188770223934} - - component: {fileID: 5673195965320428126} + - component: {fileID: 9105702282600116830} + - component: {fileID: 8286996527838887664} + - component: {fileID: 1830860820497748550} + - component: {fileID: 4544532093443431984} m_Layer: 5 - m_Name: Text (TMP) + m_Name: Placeholder m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &2130739312226787242 +--- !u!224 &9105702282600116830 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2898638287858915607} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_GameObject: {fileID: 2902411919389723293} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 3282421669272709967} + m_Father: {fileID: 3605044702849397252} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &9133278188770223934 +--- !u!222 &8286996527838887664 CanvasRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2898638287858915607} + m_GameObject: {fileID: 2902411919389723293} m_CullTransparentMesh: 1 ---- !u!114 &5673195965320428126 +--- !u!114 &1830860820497748550 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2898638287858915607} + m_GameObject: {fileID: 2902411919389723293} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} @@ -1181,7 +1182,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: New Text + m_text: "Nh\u1EADp n\u1ED9i dung..." m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} @@ -1190,8 +1191,8 @@ MonoBehaviour: m_fontMaterials: [] m_fontColor32: serializedVersion: 2 - rgba: 4294967295 - m_fontColor: {r: 0, g: 0, b: 0, a: 1} + rgba: 2150773298 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.5} m_enableVertexGradient: 0 m_colorMode: 3 m_fontColorGradient: @@ -1214,7 +1215,7 @@ MonoBehaviour: m_enableAutoSizing: 0 m_fontSizeMin: 18 m_fontSizeMax: 72 - m_fontStyle: 0 + m_fontStyle: 2 m_HorizontalAlignment: 1 m_VerticalAlignment: 256 m_textAlignment: 65535 @@ -1224,14 +1225,14 @@ MonoBehaviour: m_lineSpacingMax: 0 m_paragraphSpacing: 0 m_charWidthMaxAdj: 0 - m_TextWrappingMode: 1 + m_TextWrappingMode: 0 m_wordWrappingRatios: 0.4 m_overflowMode: 0 m_linkedTextComponent: {fileID: 0} parentLinkedComponent: {fileID: 0} m_enableKerning: 0 m_ActiveFontFeatures: 6e72656b - m_enableExtraPadding: 0 + m_enableExtraPadding: 1 checkPaddingRequired: 0 m_isRichText: 1 m_EmojiFallbackSupport: 1 @@ -1252,6 +1253,26 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!114 &4544532093443431984 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2902411919389723293} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreLayout: 1 + m_MinWidth: -1 + m_MinHeight: -1 + m_PreferredWidth: -1 + m_PreferredHeight: -1 + m_FlexibleWidth: -1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 --- !u!1 &3095457626805744279 GameObject: m_ObjectHideFlags: 0 @@ -1635,6 +1656,142 @@ MonoBehaviour: m_OnClick: m_PersistentCalls: m_Calls: [] +--- !u!1 &3569864358642720813 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6179475653333898123} + - component: {fileID: 3165768457449138107} + - component: {fileID: 4701185761014729773} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6179475653333898123 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3569864358642720813} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3605044702849397252} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &3165768457449138107 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3569864358642720813} + m_CullTransparentMesh: 1 +--- !u!114 &4701185761014729773 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3569864358642720813} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: "\u200B" + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} + m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 36 + m_fontSizeBase: 36 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 3 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 1 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &3646889256970119758 GameObject: m_ObjectHideFlags: 0 @@ -1813,6 +1970,184 @@ MonoBehaviour: isAlert: 0 m_InputValidator: {fileID: 0} m_ShouldActivateOnSelect: 1 +--- !u!1 &3775041254222000362 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4921631315982434735} + - component: {fileID: 682447300637418577} + - component: {fileID: 7576867370938653448} + - component: {fileID: 1616725709352662407} + m_Layer: 5 + m_Name: InputField (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4921631315982434735 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3775041254222000362} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3605044702849397252} + m_Father: {fileID: 3282421669272709967} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &682447300637418577 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3775041254222000362} + m_CullTransparentMesh: 1 +--- !u!114 &7576867370938653448 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3775041254222000362} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &1616725709352662407 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3775041254222000362} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2da0c512f12947e489f739169773d7ca, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 1, g: 1, b: 1, a: 1} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 0 + m_TargetGraphic: {fileID: 7576867370938653448} + m_TextViewport: {fileID: 3605044702849397252} + m_TextComponent: {fileID: 4701185761014729773} + m_Placeholder: {fileID: 1830860820497748550} + m_VerticalScrollbar: {fileID: 0} + m_VerticalScrollbarEventHandler: {fileID: 0} + m_LayoutGroup: {fileID: 0} + m_ScrollSensitivity: 1 + m_ContentType: 0 + m_InputType: 0 + m_AsteriskChar: 42 + m_KeyboardType: 0 + m_LineType: 0 + m_HideMobileInput: 0 + m_HideSoftKeyboard: 0 + m_CharacterValidation: 0 + m_RegexValue: + m_GlobalPointSize: 36 + m_CharacterLimit: 0 + m_OnEndEdit: + m_PersistentCalls: + m_Calls: [] + m_OnSubmit: + m_PersistentCalls: + m_Calls: [] + m_OnSelect: + m_PersistentCalls: + m_Calls: [] + m_OnDeselect: + m_PersistentCalls: + m_Calls: [] + m_OnTextSelection: + m_PersistentCalls: + m_Calls: [] + m_OnEndTextSelection: + m_PersistentCalls: + m_Calls: [] + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] + m_OnTouchScreenKeyboardStatusChanged: + m_PersistentCalls: + m_Calls: [] + m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_CustomCaretColor: 0 + m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412} + m_Text: + m_CaretBlinkRate: 0.85 + m_CaretWidth: 1 + m_ReadOnly: 0 + m_RichText: 1 + m_GlobalFontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} + m_OnFocusSelectAll: 1 + m_ResetOnDeActivation: 1 + m_KeepTextSelectionVisible: 0 + m_RestoreOriginalTextOnEscape: 1 + m_isRichTextEditingAllowed: 0 + m_LineLimit: 0 + isAlert: 0 + m_InputValidator: {fileID: 0} + m_ShouldActivateOnSelect: 1 --- !u!1 &3878366334053570553 GameObject: m_ObjectHideFlags: 0 @@ -2041,13 +2376,13 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: - - {fileID: 2130739312226787242} + - {fileID: 4921631315982434735} m_Father: {fileID: 806170753671297629} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: -100} - m_SizeDelta: {x: 0, y: 450.9249} + m_AnchoredPosition: {x: 15, y: -100} + m_SizeDelta: {x: -30, y: 450.9249} m_Pivot: {x: 0.5, y: 1} --- !u!222 &1622721548801034628 CanvasRenderer: @@ -4281,6 +4616,58 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!1 &6903426946426451281 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3605044702849397252} + - component: {fileID: 2373715959292045924} + m_Layer: 5 + m_Name: Text Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3605044702849397252 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6903426946426451281} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 9105702282600116830} + - {fileID: 6179475653333898123} + m_Father: {fileID: 4921631315982434735} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -0.50001526} + m_SizeDelta: {x: -20, y: -13} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2373715959292045924 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6903426946426451281} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3312d7739989d2b4e91e6319e9a96d76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: {x: -8, y: -5, z: -8, w: -5} + m_Softness: {x: 0, y: 0} --- !u!1 &6957797720283583814 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/TypingPreviewController.cs b/Assets/Scripts/TypingPreviewController.cs index 358b3569d0..fe81ac39b4 100644 --- a/Assets/Scripts/TypingPreviewController.cs +++ b/Assets/Scripts/TypingPreviewController.cs @@ -13,7 +13,7 @@ namespace BrewMonster.Scripts.ChatUI [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; + [SerializeField] private TMP_InputField 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.")] @@ -49,12 +49,12 @@ namespace BrewMonster.Scripts.ChatUI if (typingPreviewRoot == null || typingPreviewText == null) return; - string trimmedBody = (body ?? string.Empty).Trim(); + string previewBody = body ?? string.Empty; bool keyboardVisible = IsMobileKeyboardVisible(); - bool shouldShow = keyboardVisible && isInputFocused && !string.IsNullOrEmpty(trimmedBody); + bool shouldShow = keyboardVisible && isInputFocused; typingPreviewRoot.SetActive(shouldShow); if (shouldShow) - typingPreviewText.text = trimmedBody; + typingPreviewText.text = previewBody; } bool IsMobileKeyboardVisible() diff --git a/Assets/Scripts/typing-preview-summary.md b/Assets/Scripts/typing-preview-summary.md new file mode 100644 index 0000000000..b86b53a2d8 --- /dev/null +++ b/Assets/Scripts/typing-preview-summary.md @@ -0,0 +1,60 @@ +# Typing Preview - Summary + +## Muc tieu da thuc hien + +- Tach logic `typingPreview` ra class rieng de de tai su dung. +- Lien ket lai logic giua `ChatInputHandler` va `TypingPreviewController`. +- Dieu chinh co che hien/an theo yeu cau mobile keyboard. + +## Cac file da cap nhat + +- `Assets/Scripts/TypingPreviewController.cs` +- `Assets/Scripts/ChatInputHandler.cs` + +## Noi dung da lam + +### 1) Tach typing preview thanh class rieng + +- Tao class `TypingPreviewController` de quan ly: + - Hien/an preview (`typingPreviewRoot`) + - Cap nhat text preview realtime (`typingPreviewText`) + - Dinh vi preview theo offset keyboard (`typingPreviewRect`, `previewVerticalOffset`) + +### 2) Lien ket voi ChatInputHandler + +- `ChatInputHandler` giu `SerializeField`: + - `TypingPreviewController typingPreview` +- Moi khi input thay doi text, `ChatInputHandler` lay message body (bo prefix kenh/whisper) roi day sang controller de cap nhat preview. +- Khi clear input, preview duoc cap nhat lai ngay de an dung trang thai. + +### 3) Co che mobile keyboard (cap nhat moi nhat) + +- Da chuyen logic check keyboard vao thang `TypingPreviewController`: + - `IsMobileKeyboardVisible()` + - `GetVisibleKeyboardHeight()` +- `ChatInputHandler` da bo cac ham keyboard-check khong con dung. +- `ChatInputHandler` chi can goi `typingPreview.UpdatePreview(isFocused, body)`. +- `ChatInputHandler.Update()` van refresh moi frame de bat kip luc keyboard mo/dong. +- Rule hien moi nhat: + - Keyboard mobile vua mo la preview show ngay (neu input dang focus), khong can cho co ky tu. + +## Hanh vi hien tai + +- Preview chi hien khi: + - Dang o mobile + - Keyboard dang mo + - Input dang focus +- Khong con yeu cau body phai khong rong (show ngay khi keyboard mo). +- Khi tat keyboard, preview an ngay. +- Khi mo lai keyboard, noi dung da go truoc do van con trong input va preview hien lai dung noi dung. + +## Emoji tren preview + +- Hien tai preview duoc set text truc tiep tu `body`, nen neu `body` co TMP sprite tag thi co the hien emoji. +- Luu y cau hinh component: + - `typingPreviewText` dang de kieu `TMP_InputField` trong `TypingPreviewController`. + - Neu muon on dinh cho muc dich "preview display", nen doi sang `TMP_Text` hoac `TextMeshProUGUI`. + +## Ghi chu + +- Da kiem tra lint sau cac lan sua, khong phat hien loi lint moi tren cac file da chinh. diff --git a/Assets/Scripts/typing-preview-summary.md.meta b/Assets/Scripts/typing-preview-summary.md.meta new file mode 100644 index 0000000000..8304d161ef --- /dev/null +++ b/Assets/Scripts/typing-preview-summary.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 85a85257c23e84e41ba29cbc51455178 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: