1 /*
2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "core/components_ng/pattern/text_field/text_field_manager.h"
17
18 #include "base/geometry/dimension.h"
19 #include "base/memory/ace_type.h"
20 #include "base/utils/utils.h"
21 #include "core/common/ime/text_input_type.h"
22 #include "core/components_ng/event/focus_hub.h"
23 #include "core/components_ng/pattern/navigation/navigation_pattern.h"
24 #include "core/components_ng/pattern/scrollable/scrollable_pattern.h"
25 #include "core/components_ng/pattern/text/text_base.h"
26 #include "core/components_ng/pattern/text_field/text_field_pattern.h"
27
28 namespace OHOS::Ace::NG {
29 namespace {
30 constexpr Dimension RESERVE_BOTTOM_HEIGHT = 24.0_vp;
31 } // namespace
32
ClearOnFocusTextField()33 void TextFieldManagerNG::ClearOnFocusTextField()
34 {
35 onFocusTextField_ = nullptr;
36 }
37
ClearOnFocusTextField(int32_t id)38 void TextFieldManagerNG::ClearOnFocusTextField(int32_t id)
39 {
40 if (onFocusTextFieldId == id) {
41 onFocusTextField_ = nullptr;
42 focusFieldIsInline = false;
43 optionalPosition_ = std::nullopt;
44 usingCustomKeyboardAvoid_ = false;
45 }
46 }
47
OnBackPressed()48 bool TextFieldManagerNG::OnBackPressed()
49 {
50 auto pattern = onFocusTextField_.Upgrade();
51 CHECK_NULL_RETURN(pattern, false);
52 auto textBasePattern = AceType::DynamicCast<TextBase>(pattern);
53 CHECK_NULL_RETURN(textBasePattern, false);
54 return textBasePattern->OnBackPressed();
55 }
56
SetClickPosition(const Offset & position)57 void TextFieldManagerNG::SetClickPosition(const Offset& position)
58 {
59 auto pipeline = PipelineContext::GetCurrentContextSafely();
60 CHECK_NULL_VOID(pipeline);
61 auto rootHeight = pipeline->GetRootHeight();
62 if (GreatOrEqual(position.GetY(), rootHeight)) {
63 auto pattern = onFocusTextField_.Upgrade();
64 CHECK_NULL_VOID(pattern);
65 auto host = pattern->GetHost();
66 CHECK_NULL_VOID(host);
67 auto parent = host->GetAncestorNodeOfFrame();
68 while (parent) {
69 if (parent->GetTag() == "Panel" || parent->GetTag() == "SheetPage") {
70 return;
71 }
72 parent = parent->GetAncestorNodeOfFrame();
73 }
74 }
75 if (LessOrEqual(position.GetY(), 0.0f)) {
76 return;
77 }
78 auto rootWidth = pipeline->GetRootWidth();
79 if (GreatOrEqual(position.GetX(), rootWidth) || LessNotEqual(position.GetX(), 0.0f)) {
80 return;
81 }
82 position_ = position;
83 optionalPosition_ = position;
84 }
85
FindScrollableOfFocusedTextField(const RefPtr<FrameNode> & textField)86 RefPtr<FrameNode> TextFieldManagerNG::FindScrollableOfFocusedTextField(const RefPtr<FrameNode>& textField)
87 {
88 CHECK_NULL_RETURN(textField, {});
89 auto parent = textField->GetAncestorNodeOfFrame();
90 while (parent) {
91 auto pattern = parent->GetPattern<ScrollablePattern>();
92 if (pattern) {
93 return parent;
94 }
95 parent = parent->GetAncestorNodeOfFrame();
96 }
97 return {};
98 }
99
ScrollToSafeAreaHelper(const SafeAreaInsets::Inset & bottomInset,bool isShowKeyboard)100 bool TextFieldManagerNG::ScrollToSafeAreaHelper(
101 const SafeAreaInsets::Inset& bottomInset, bool isShowKeyboard)
102 {
103 auto node = onFocusTextField_.Upgrade();
104 CHECK_NULL_RETURN(node, false);
105 auto frameNode = node->GetHost();
106 CHECK_NULL_RETURN(frameNode, false);
107 auto textBase = DynamicCast<TextBase>(node);
108 CHECK_NULL_RETURN(textBase, false);
109 textBase->OnVirtualKeyboardAreaChanged();
110
111 auto scrollableNode = FindScrollableOfFocusedTextField(frameNode);
112 CHECK_NULL_RETURN(scrollableNode, false);
113 auto scrollPattern = scrollableNode->GetPattern<ScrollablePattern>();
114 CHECK_NULL_RETURN(scrollPattern && scrollPattern->IsScrollToSafeAreaHelper(), false);
115 if (scrollPattern->GetAxis() == Axis::HORIZONTAL) {
116 return false;
117 }
118
119 auto scrollableRect = scrollableNode->GetTransformRectRelativeToWindow();
120 if (isShowKeyboard) {
121 CHECK_NULL_RETURN(scrollableRect.Top() < bottomInset.start, false);
122 }
123
124 auto caretRect = textBase->GetCaretRect() + frameNode->GetPositionToWindowWithTransform();
125 auto diffTop = caretRect.Top() - scrollableRect.Top();
126 // caret height larger scroll's content region
127 if (isShowKeyboard && diffTop <= 0 && LessNotEqual(bottomInset.start,
128 (caretRect.Bottom() + RESERVE_BOTTOM_HEIGHT.ConvertToPx()))) {
129 return false;
130 }
131
132 // caret above scroll's content region
133 if (diffTop < 0) {
134 TAG_LOGI(ACE_KEYBOARD, "scrollRect:%{public}s caretRect:%{public}s totalOffset()=%{public}f diffTop=%{public}f",
135 scrollableRect.ToString().c_str(), caretRect.ToString().c_str(), scrollPattern->GetTotalOffset(), diffTop);
136 scrollPattern->ScrollTo(scrollPattern->GetTotalOffset() + diffTop);
137 return true;
138 }
139
140 // caret inner scroll's content region
141 if (isShowKeyboard && LessNotEqual((caretRect.Bottom() + RESERVE_BOTTOM_HEIGHT.ConvertToPx()), bottomInset.start)) {
142 return false;
143 }
144
145 // caret below safeArea
146 float diffBot = 0.0f;
147 if (isShowKeyboard) {
148 if (LessNotEqual(scrollableRect.Bottom(), bottomInset.start)) {
149 diffBot = scrollableRect.Bottom() - caretRect.Bottom() - RESERVE_BOTTOM_HEIGHT.ConvertToPx();
150 } else {
151 diffBot = bottomInset.start - caretRect.Bottom() - RESERVE_BOTTOM_HEIGHT.ConvertToPx();
152 }
153 } else {
154 diffBot = scrollableRect.Bottom() - caretRect.Bottom() - RESERVE_BOTTOM_HEIGHT.ConvertToPx();
155 }
156 CHECK_NULL_RETURN(diffBot < 0, false);
157 TAG_LOGI(ACE_KEYBOARD, "scrollRect:%{public}s caretRect:%{public}s totalOffset()=%{public}f diffBot=%{public}f",
158 scrollableRect.ToString().c_str(), caretRect.ToString().c_str(), scrollPattern->GetTotalOffset(), diffBot);
159 scrollPattern->ScrollTo(scrollPattern->GetTotalOffset() - diffBot);
160 return true;
161 }
162
ScrollTextFieldToSafeArea()163 bool TextFieldManagerNG::ScrollTextFieldToSafeArea()
164 {
165 auto pipeline = PipelineContext::GetCurrentContext();
166 CHECK_NULL_RETURN(pipeline, false);
167 auto keyboardInset = pipeline->GetSafeAreaManager()->GetKeyboardInset();
168 bool isShowKeyboard = keyboardInset.IsValid();
169 if (isShowKeyboard) {
170 auto bottomInset = pipeline->GetSafeArea().bottom_.Combine(keyboardInset);
171 CHECK_NULL_RETURN(bottomInset.IsValid(), false);
172 return ScrollToSafeAreaHelper(bottomInset, isShowKeyboard);
173 } else if (pipeline->GetSafeAreaManager()->KeyboardSafeAreaEnabled()) {
174 // hide keyboard only scroll when keyboard avoid mode is resize
175 return ScrollToSafeAreaHelper({0, 0}, isShowKeyboard);
176 }
177 return false;
178 }
179
SetHeight(float height)180 void TextFieldManagerNG::SetHeight(float height)
181 {
182 height_ = height + RESERVE_BOTTOM_HEIGHT.ConvertToPx();
183 }
184
UpdateScrollableParentViewPort(const RefPtr<FrameNode> & node)185 void TextFieldManagerNG::UpdateScrollableParentViewPort(const RefPtr<FrameNode>& node)
186 {
187 CHECK_NULL_VOID(node);
188 auto scrollableNode = FindScrollableOfFocusedTextField(node);
189 CHECK_NULL_VOID(scrollableNode);
190 auto scrollPattern = scrollableNode->GetPattern<ScrollablePattern>();
191 CHECK_NULL_VOID(scrollPattern);
192 if (scrollPattern->GetAxis() == Axis::HORIZONTAL) {
193 return;
194 }
195 auto scrollableRect = scrollableNode->GetTransformRectRelativeToWindow();
196 scrollableNode->SetViewPort(scrollableRect);
197 }
198
AvoidKeyBoardInNavigation()199 void TextFieldManagerNG::AvoidKeyBoardInNavigation()
200 {
201 auto node = onFocusTextField_.Upgrade();
202 auto pipeline = PipelineContext::GetCurrentContext();
203 CHECK_NULL_VOID(pipeline);
204 auto manager = pipeline->GetSafeAreaManager();
205 auto avoidKeyboardOffset = manager ? manager->GetKeyboardOffset() : 0.0f;
206 if (!node) {
207 auto navNode = weakNavNode_.Upgrade();
208 CHECK_NULL_VOID(navNode);
209 SetNavContentAvoidKeyboardOffset(navNode, avoidKeyboardOffset);
210 return;
211 }
212 auto frameNode = node->GetHost();
213 CHECK_NULL_VOID(frameNode);
214 auto preNavNode = weakNavNode_.Upgrade();
215 if (preNavNode) {
216 SetNavContentAvoidKeyboardOffset(preNavNode, 0.0f);
217 }
218 auto navNode = FindNavNode(frameNode);
219 CHECK_NULL_VOID(navNode);
220 weakNavNode_ = navNode;
221 SetNavContentAvoidKeyboardOffset(navNode, avoidKeyboardOffset);
222 }
223
AvoidKeyboardInSheet(const RefPtr<FrameNode> & textField)224 void TextFieldManagerNG::AvoidKeyboardInSheet(const RefPtr<FrameNode>& textField)
225 {
226 CHECK_NULL_VOID(textField);
227 auto parent = textField->GetAncestorNodeOfFrame();
228 bool findSheet = false;
229 while (parent) {
230 if (parent->GetHostTag() == V2::SHEET_PAGE_TAG) {
231 findSheet = true;
232 break;
233 }
234 parent = parent->GetAncestorNodeOfFrame();
235 }
236 CHECK_NULL_VOID(parent);
237 auto sheetNodePattern = parent->GetPattern<SheetPresentationPattern>();
238 CHECK_NULL_VOID(sheetNodePattern);
239 TAG_LOGI(ACE_KEYBOARD, "Force AvoidKeyboard in sheet");
240 sheetNodePattern->AvoidSafeArea(true);
241 }
242
FindNavNode(const RefPtr<FrameNode> & textField)243 RefPtr<FrameNode> TextFieldManagerNG::FindNavNode(const RefPtr<FrameNode>& textField)
244 {
245 CHECK_NULL_RETURN(textField, nullptr);
246 auto parent = textField->GetAncestorNodeOfFrame();
247 RefPtr<FrameNode> ret = nullptr;
248 while (parent) {
249 // when the sheet showed in navdestination, sheet replaced navdestination to do avoid keyboard.
250 if (parent->GetHostTag() == V2::SHEET_WRAPPER_TAG) {
251 auto sheetNode = parent->GetChildAtIndex(0);
252 CHECK_NULL_RETURN(sheetNode, nullptr);
253 return AceType::DynamicCast<FrameNode>(sheetNode);
254 }
255 if (parent->GetHostTag() == V2::NAVDESTINATION_VIEW_ETS_TAG ||
256 parent->GetHostTag() == V2::NAVBAR_ETS_TAG) {
257 ret = parent;
258 break;
259 }
260 parent = parent->GetAncestorNodeOfFrame();
261 }
262 CHECK_NULL_RETURN(ret, nullptr);
263
264 // return navdestination or navBar if the closest ancestor navigation can expandKeyboard
265 // if can't, recursively find the ancestor navigation can expandKeyboard.
266 auto navigationNode = ret->GetAncestorNodeOfFrame();
267 while (navigationNode) {
268 if (navigationNode->GetHostTag() == V2::NAVIGATION_VIEW_ETS_TAG) {
269 break;
270 }
271 navigationNode = navigationNode->GetAncestorNodeOfFrame();
272 }
273 CHECK_NULL_RETURN(navigationNode, nullptr);
274 auto layoutProperty = navigationNode->GetLayoutProperty<NavigationLayoutProperty>();
275 CHECK_NULL_RETURN(layoutProperty, nullptr);
276 auto& opts = layoutProperty->GetSafeAreaExpandOpts();
277
278 // if the extended keyboard area is set for the navigation, top navdestination or navbar need to avoid keyboard,
279 // otherwise don't aovid, following parent navigation.
280 bool isExpandKeyboard = opts && (opts->type & SAFE_AREA_TYPE_KEYBOARD) && (opts->edges & SAFE_AREA_EDGE_BOTTOM);
281 if (isExpandKeyboard) {
282 return ret;
283 }
284 auto mayAvoidNavContentNode = FindNavNode(navigationNode);
285 if (mayAvoidNavContentNode) {
286 return mayAvoidNavContentNode;
287 }
288 SetNavContentAvoidKeyboardOffset(ret, 0.0f);
289 return nullptr;
290 }
291
SetNavContentAvoidKeyboardOffset(RefPtr<FrameNode> navNode,float avoidKeyboardOffset)292 void TextFieldManagerNG::SetNavContentAvoidKeyboardOffset(RefPtr<FrameNode> navNode, float avoidKeyboardOffset)
293 {
294 auto navDestinationNode = AceType::DynamicCast<NavDestinationGroupNode>(navNode);
295 if (navDestinationNode) {
296 TAG_LOGI(ACE_KEYBOARD, "navNode id:%{public}d, avoidKeyboardOffset:%{public}f", navNode->GetId(),
297 avoidKeyboardOffset);
298 auto pattern = navDestinationNode->GetPattern<NavDestinationPattern>();
299 if (pattern) {
300 avoidKeyboardOffset = pattern->NeedIgnoreKeyboard() ? 0.0f : avoidKeyboardOffset;
301 pattern->SetAvoidKeyboardOffset(avoidKeyboardOffset);
302 }
303 }
304 auto navBarNode = AceType::DynamicCast<NavBarNode>(navNode);
305 if (navBarNode) {
306 auto pattern = navBarNode->GetPattern<NavBarPattern>();
307 if (pattern) {
308 pattern->SetAvoidKeyboardOffset(avoidKeyboardOffset);
309 }
310 }
311 navNode->MarkDirtyNode(PROPERTY_UPDATE_LAYOUT);
312 }
313
AddTextFieldInfo(const TextFieldInfo & textFieldInfo)314 void TextFieldManagerNG::AddTextFieldInfo(const TextFieldInfo& textFieldInfo)
315 {
316 if (textFieldInfo.nodeId == -1 || textFieldInfo.autoFillContainerNodeId == -1) {
317 return;
318 }
319
320 auto containerNodeIter = textFieldInfoMap_.find(textFieldInfo.autoFillContainerNodeId);
321 if (containerNodeIter != textFieldInfoMap_.end()) {
322 auto& innerTextFieldMap = containerNodeIter->second;
323 innerTextFieldMap[textFieldInfo.nodeId] = textFieldInfo;
324 } else {
325 std::unordered_map<int32_t, TextFieldInfo> innerTextFieldInfoMap;
326 innerTextFieldInfoMap[textFieldInfo.nodeId] = textFieldInfo;
327 textFieldInfoMap_[textFieldInfo.autoFillContainerNodeId] = innerTextFieldInfoMap;
328 }
329 }
330
RemoveTextFieldInfo(const int32_t & autoFillContainerNodeId,const int32_t & nodeId)331 void TextFieldManagerNG::RemoveTextFieldInfo(const int32_t& autoFillContainerNodeId, const int32_t& nodeId)
332 {
333 auto containerNodeIter = textFieldInfoMap_.find(autoFillContainerNodeId);
334 if (containerNodeIter != textFieldInfoMap_.end()) {
335 auto& innerTextFieldInfoMap = containerNodeIter->second;
336 auto textFieldNodeIter = innerTextFieldInfoMap.find(nodeId);
337 if (textFieldNodeIter != innerTextFieldInfoMap.end()) {
338 innerTextFieldInfoMap.erase(textFieldNodeIter);
339 }
340 }
341 }
342
UpdateTextFieldInfo(const TextFieldInfo & textFieldInfo)343 void TextFieldManagerNG::UpdateTextFieldInfo(const TextFieldInfo& textFieldInfo)
344 {
345 if (textFieldInfo.nodeId == -1 || textFieldInfo.autoFillContainerNodeId == -1) {
346 return;
347 }
348 auto containerNodeIter = textFieldInfoMap_.find(textFieldInfo.autoFillContainerNodeId);
349 if (containerNodeIter != textFieldInfoMap_.end()) {
350 auto& innerTextFieldInfoMap = containerNodeIter->second;
351 auto textFieldNodeIter = innerTextFieldInfoMap.find(textFieldInfo.nodeId);
352 if (textFieldNodeIter != innerTextFieldInfoMap.end()) {
353 innerTextFieldInfoMap.erase(textFieldNodeIter);
354 }
355 innerTextFieldInfoMap[textFieldInfo.nodeId] = textFieldInfo;
356 } else {
357 AddTextFieldInfo(textFieldInfo);
358 }
359 }
360
HasAutoFillPasswordNodeInContainer(const int32_t & autoFillContainerNodeId,const int32_t & nodeId)361 bool TextFieldManagerNG::HasAutoFillPasswordNodeInContainer(
362 const int32_t& autoFillContainerNodeId, const int32_t& nodeId)
363 {
364 auto containerNodeIter = textFieldInfoMap_.find(autoFillContainerNodeId);
365 if (containerNodeIter == textFieldInfoMap_.end()) {
366 return false;
367 }
368
369 auto& innerTextFieldInfoMap = containerNodeIter->second;
370 auto textFieldNodeIter = innerTextFieldInfoMap.find(nodeId);
371 if (textFieldNodeIter == innerTextFieldInfoMap.end()) {
372 return false;
373 }
374
375 for (const auto& textField : innerTextFieldInfoMap) {
376 auto textFieldId = textField.first;
377 auto textFieldInfo = textField.second;
378 if (textFieldId == nodeId) {
379 continue;
380 }
381
382 auto isPasswordType = IsAutoFillPasswordType(textFieldInfo);
383 if (isPasswordType && textFieldInfo.enableAutoFill) {
384 return true;
385 }
386 }
387
388 return false;
389 }
390
IsAutoFillPasswordType(const TextFieldInfo & textFieldInfo)391 bool TextFieldManagerNG::IsAutoFillPasswordType(const TextFieldInfo& textFieldInfo)
392 {
393 return textFieldInfo.inputType == TextInputType::VISIBLE_PASSWORD ||
394 textFieldInfo.inputType == TextInputType::NEW_PASSWORD ||
395 textFieldInfo.inputType == TextInputType::NUMBER_PASSWORD ||
396 textFieldInfo.contentType == TextContentType::VISIBLE_PASSWORD ||
397 textFieldInfo.contentType == TextContentType::NEW_PASSWORD;
398 }
399
~TextFieldManagerNG()400 TextFieldManagerNG::~TextFieldManagerNG()
401 {
402 textFieldInfoMap_.clear();
403 }
404 } // namespace OHOS::Ace::NG
405