1 /*
2 * Copyright (c) 2025 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/rich_editor_drag/preview_menu_controller.h"
17
18 #include <cstddef>
19 #include <optional>
20
21 #include "ui/base/geometry/dimension.h"
22 #include "ui/base/referenced.h"
23 #include "ui/base/utils/utils.h"
24 #include "ui/properties/color.h"
25 #include "ui/properties/dirty_flag.h"
26 #include "ui/properties/flex.h"
27
28 #include "core/common/ace_engine.h"
29 #include "core/components/common/layout/constants.h"
30 #include "core/components/common/properties/placement.h"
31 #include "core/components/common/properties/text_style.h"
32 #include "core/components/select/select_theme.h"
33 #include "core/components/text_overlay/text_overlay_theme.h"
34 #include "core/components/theme/icon_theme.h"
35 #include "core/components_ng/base/view_abstract_model.h"
36 #include "core/components_ng/base/view_stack_processor.h"
37 #include "core/components_ng/pattern/flex/flex_layout_pattern.h"
38 #include "core/components_ng/pattern/linear_layout/linear_layout_pattern.h"
39 #include "core/components_ng/pattern/overlay/overlay_manager.h"
40 #include "core/components_ng/pattern/select_overlay/expanded_menu_plugin_loader.h"
41 #include "core/components_ng/pattern/text/text_pattern.h"
42 #include "core/components_ng/pattern/ui_extension/preview_ui_extension_component/preview_ui_extension_adapter.h"
43 #include "core/components_ng/property/measure_property.h"
44 #include "core/components_ng/property/menu_property.h"
45 #include "core/components_v2/inspector/inspector_constants.h"
46 #include "core/pipeline/base/constants.h"
47
48 namespace OHOS::Ace::NG {
49 namespace {
50 constexpr Dimension PREVIEW_MAX_WIDTH = 360.0_vp;
51 constexpr Dimension MENU_WIDTH = 224.0_vp;
52 constexpr Dimension AVATAR_SIZE = 40.0_vp;
53 constexpr Dimension PREVIEW_MIN_HEIGHT = 64.0_vp;
54 constexpr Dimension FAILED_TEXT_LINE_SPACING = 8.0_vp;
55 constexpr float MAX_HEIGHT_PROPORTIONS = 0.65;
56 constexpr int32_t MAX_LINES = 4;
57 const std::string CALENDAR_ABILITY_NAME = "AgendaPreviewUIExtensionAbility";
58 const std::string UIEXTENSION_PARAM = "ability.want.params.uiExtensionType";
59 const std::string UIEXTENSION_PARAM_VALUE = "sys/commonUI";
60 constexpr float PERCENT_FULL = 1.0;
61 } // namespace
PreviewMenuController(const WeakPtr<TextPattern> & pattern)62 PreviewMenuController::PreviewMenuController(const WeakPtr<TextPattern>& pattern)
63 {
64 pattern_ = pattern;
65 menuParam_.type = MenuType::CONTEXT_MENU;
66 menuParam_.contextMenuRegisterType = ContextMenuRegisterType::CUSTOM_TYPE;
67 menuParam_.previewMode = MenuPreviewMode::CUSTOM;
68
69 menuParam_.isShowHoverImage = true;
70 menuParam_.hoverImageAnimationOptions = { 1.0f, 1.0f };
71 menuParam_.previewAnimationOptions = { 1.0f, 1.0f };
72
73 auto textPattern = pattern_.Upgrade();
74 CHECK_NULL_VOID(textPattern);
75 auto textNode = textPattern->GetHost();
76 CHECK_NULL_VOID(textNode);
77 auto context = textNode->GetContext();
78 CHECK_NULL_VOID(context);
79 auto theme = context->GetTheme<SelectTheme>();
80 CHECK_NULL_VOID(theme);
81 auto margin = theme->GetMenuLargeMargin();
82
83 MarginProperty marginproperty;
84 marginproperty.start = CalcLength(margin);
85 marginproperty.end = CalcLength(margin);
86 menuParam_.layoutRegionMargin = marginproperty;
87 menuParam_.onDisappear = [weak = WeakClaim(this), weakPattern = pattern,
88 mainId = Container::CurrentIdSafelyWithCheck()]() {
89 ContainerScope scope(mainId);
90 auto controller = weak.Upgrade();
91 CHECK_NULL_VOID(controller);
92 controller->ClosePreviewMenu();
93 auto textPattern = weakPattern.Upgrade();
94 CHECK_NULL_VOID(textPattern);
95 textPattern->ResetAISelected(AIResetSelectionReason::CLOSE_CONTEXT_MENU);
96 textPattern->DragNodeDetachFromParent();
97 if (SystemProperties::GetTextTraceEnabled()) {
98 auto host = textPattern->GetHost();
99 CHECK_NULL_VOID(host);
100 TAG_LOGI(AceLogTag::ACE_TEXT, "PreviewMenuController menu onDisappear[id:%{public}d]", host->GetId());
101 }
102 };
103 }
104
CreateAIEntityMenu()105 void PreviewMenuController::CreateAIEntityMenu()
106 {
107 auto textPattern = pattern_.Upgrade();
108 CHECK_NULL_VOID(textPattern);
109 auto* stack = ViewStackProcessor::GetInstance();
110 auto nodeId = ElementRegister::GetInstance()->MakeUniqueId();
111 auto menuNode = textPattern->CreateAIEntityMenu();
112 if (!menuNode) {
113 menuNode = FrameNode::GetOrCreateFrameNode(
114 V2::FLEX_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr<FlexLayoutPattern>(); });
115 auto flexLayoutProperty = menuNode->GetLayoutProperty<FlexLayoutProperty>();
116 CHECK_NULL_VOID(flexLayoutProperty);
117 flexLayoutProperty->UpdateFlexDirection(FlexDirection::COLUMN);
118 flexLayoutProperty->UpdateMainAxisAlign(FlexAlign::FLEX_START);
119 flexLayoutProperty->UpdateCrossAxisAlign(FlexAlign::FLEX_START);
120 }
121 auto layoutProperty = menuNode->GetLayoutProperty();
122 CHECK_NULL_VOID(layoutProperty);
123 std::optional<CalcLength> width = CalcLength(MENU_WIDTH);
124 layoutProperty->UpdateUserDefinedIdealSize(CalcSize(width, std::nullopt));
125 stack->Push(menuNode);
126 }
127
GetDisappearCallback()128 std::function<void()> PreviewMenuController::GetDisappearCallback()
129 {
130 return [weak = WeakClaim(this), pattern = pattern_, mainId = Container::CurrentIdSafelyWithCheck()]() {
131 ContainerScope scope(mainId);
132 auto controller = weak.Upgrade();
133 CHECK_NULL_VOID(controller);
134 auto textPattern = pattern.Upgrade();
135 CHECK_NULL_VOID(textPattern);
136 if (SystemProperties::GetTextTraceEnabled()) {
137 auto host = textPattern->GetHost();
138 CHECK_NULL_VOID(host);
139 TAG_LOGI(AceLogTag::ACE_TEXT, "CreateAIEntityMenu onMenuDisappear id:%{public}d", host->GetId());
140 }
141 auto targetNode = textPattern->MoveDragNode();
142 CHECK_NULL_VOID(targetNode);
143 controller->BindContextMenu(targetNode, false);
144 };
145 }
146
GetLinkingCallback(const std::string & appName)147 std::function<void()> PreviewMenuController::GetLinkingCallback(const std::string& appName)
148 {
149 return [appName, mainId = Container::CurrentIdSafelyWithCheck()]() {
150 ContainerScope scope(mainId);
151 auto context = PipelineContext::GetCurrentContextSafelyWithCheck();
152 CHECK_NULL_VOID(context);
153 auto fontManager = context->GetFontManager();
154 CHECK_NULL_VOID(fontManager);
155 fontManager->StartAbilityOnInstallAppInStore(appName);
156 if (SystemProperties::GetTextTraceEnabled()) {
157 TAG_LOGI(AceLogTag::ACE_TEXT, "preview failed onLinkingCallback");
158 }
159 };
160 }
161
CreatePreviewMenu(TextDataDetectType type,const std::string & content,std::function<void ()> disappearCallback,std::map<std::string,std::string> AIparams,std::function<void ()> aiSpanClickCallabck)162 void PreviewMenuController::CreatePreviewMenu(TextDataDetectType type, const std::string& content,
163 std::function<void()> disappearCallback, std::map<std::string, std::string> AIparams,
164 std::function<void()> aiSpanClickCallabck)
165 {
166 auto* stack = ViewStackProcessor::GetInstance();
167 auto nodeId = ElementRegister::GetInstance()->MakeUniqueId();
168 auto frameNode = FrameNode::GetOrCreateFrameNode(
169 V2::COLUMN_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr<LinearLayoutPattern>(true); });
170 stack->Push(frameNode);
171 auto flexLayoutProperty = frameNode->GetLayoutProperty<LinearLayoutProperty>();
172 CHECK_NULL_VOID(flexLayoutProperty);
173 flexLayoutProperty->UpdateMainAxisAlign(FlexAlign::CENTER);
174 auto flexRenderContext = frameNode->GetRenderContext();
175 CHECK_NULL_VOID(flexRenderContext);
176 auto context = frameNode->GetContext();
177 CHECK_NULL_VOID(context);
178 auto theme = context->GetTheme<SelectTheme>();
179 CHECK_NULL_VOID(theme);
180 auto bgColor = theme->GetBackgroundColor();
181 flexRenderContext->UpdateBackgroundColor(bgColor);
182 auto previewNode = CreatePreview(type);
183 CHECK_NULL_VOID(previewNode);
184 previewNode->MountToParent(frameNode);
185
186 // Currently, only the calendar integration extension can directly invoke the default style for other types.
187 if (type != TextDataDetectType::DATE_TIME) {
188 MountErrorNode(previewNode, type, content, disappearCallback, aiSpanClickCallabck);
189 previewNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
190 return;
191 }
192 MountUIExtensionNode(previewNode, content, std::move(disappearCallback), type, AIparams);
193 return;
194 }
195
MountUIExtensionNode(const RefPtr<FrameNode> & previewNode,const std::string & content,std::function<void ()> && disappearCallback,TextDataDetectType type,const std::map<std::string,std::string> & AIparams)196 void PreviewMenuController::MountUIExtensionNode(const RefPtr<FrameNode>& previewNode, const std::string& content,
197 std::function<void()>&& disappearCallback, TextDataDetectType type,
198 const std::map<std::string, std::string>& AIparams)
199 {
200 #if !defined(ACE_UNITTEST) && !defined(PREVIEW) && defined(OHOS_STANDARD_SYSTEM)
201 const auto& extensionAdapter = PreviewUIExtensionAdapter::GetInstance();
202 CHECK_NULL_VOID(extensionAdapter);
203 UIExtensionConfig config;
204 std::string bundleName;
205 std::string abilityName;
206 std::map<std::string, std::string> params;
207 CreateWantConfig(type, bundleName, abilityName, params, AIparams);
208 PreviewNodeClickCallback(type, previewNode, AIparams);
209 auto wantWrap = WantWrap::CreateWantWrap(bundleName, abilityName);
210 CHECK_NULL_VOID(wantWrap);
211 wantWrap->SetWantParam(params);
212 config.wantWrap = wantWrap;
213 auto UIExtensionNode = extensionAdapter->CreatePreviewUIExtensionNode(config);
214 auto&& error = GetErrorCallback(previewNode, type, content, std::move(disappearCallback));
215 if (UIExtensionNode) {
216 auto layoutProperty = UIExtensionNode->GetLayoutProperty();
217 std::optional<CalcLength> height = CalcLength(Dimension(PERCENT_FULL, DimensionUnit::PERCENT));
218 std::optional<CalcLength> weight = CalcLength(Dimension(PERCENT_FULL, DimensionUnit::PERCENT));
219 layoutProperty->UpdateUserDefinedIdealSize(CalcSize(weight, height));
220 extensionAdapter->UpdatePreviewUIExtensionConfig(UIExtensionNode, config);
221 extensionAdapter->SetOnError(UIExtensionNode, std::move(error));
222 UIExtensionNode->MarkModifyDone();
223 UIExtensionNode->MountToParent(previewNode);
224 } else {
225 error(-1, "", "");
226 }
227 #endif
228 }
229
PreviewNodeClickCallback(TextDataDetectType type,const RefPtr<FrameNode> & previewNode,const std::map<std::string,std::string> & AIparams)230 void PreviewMenuController::PreviewNodeClickCallback(
231 TextDataDetectType type, const RefPtr<FrameNode>& previewNode, const std::map<std::string, std::string>& AIparams)
232 {
233 CHECK_NULL_VOID(previewNode);
234 std::function<void()> clickCallback;
235 switch (type) {
236 case TextDataDetectType::DATE_TIME:
237 clickCallback = [AIparams, mainId = Container::CurrentIdSafelyWithCheck()]() {
238 ContainerScope scope(mainId);
239 auto pipeline = NG::PipelineContext::GetCurrentContextSafelyWithCheck();
240 CHECK_NULL_VOID(pipeline);
241 auto fontManager = pipeline->GetFontManager();
242 CHECK_NULL_VOID(fontManager);
243 fontManager->StartAbilityOnCalendar(AIparams);
244 };
245 break;
246 case TextDataDetectType::PHONE_NUMBER:
247 case TextDataDetectType::EMAIL:
248 case TextDataDetectType::ADDRESS:
249 case TextDataDetectType::URL:
250 default:
251 break;
252 }
253 auto eventHub = previewNode->GetOrCreateGestureEventHub();
254 CHECK_NULL_VOID(eventHub);
255 eventHub->SetUserOnClick([clickCallback, mainId = Container::CurrentIdSafelyWithCheck()](GestureEvent& info) {
256 ContainerScope scope(mainId);
257 if (clickCallback) {
258 clickCallback();
259 }
260 });
261 }
262
CreateWantConfig(TextDataDetectType type,std::string & bundleName,std::string & abilityName,std::map<std::string,std::string> & params,const std::map<std::string,std::string> & AIparams)263 void PreviewMenuController::CreateWantConfig(TextDataDetectType type, std::string& bundleName, std::string& abilityName,
264 std::map<std::string, std::string>& params, const std::map<std::string, std::string>& AIparams)
265 {
266 params[UIEXTENSION_PARAM] = UIEXTENSION_PARAM_VALUE;
267 switch (type) {
268 case TextDataDetectType::DATE_TIME: {
269 bundleName = ExpandedMenuPluginLoader::GetInstance().GetAPPName(TextDataDetectType::DATE_TIME);
270 abilityName = CALENDAR_ABILITY_NAME;
271 break;
272 }
273 case TextDataDetectType::PHONE_NUMBER:
274 case TextDataDetectType::EMAIL:
275 case TextDataDetectType::ADDRESS:
276 case TextDataDetectType::URL:
277 default:
278 break;
279 }
280 params.insert(AIparams.begin(), AIparams.end());
281 }
282
GetErrorCallback(const RefPtr<FrameNode> & previewNode,TextDataDetectType type,const std::string & content,std::function<void ()> && disappearCallback)283 AIPreviewMenuErrorCallback PreviewMenuController::GetErrorCallback(const RefPtr<FrameNode>& previewNode,
284 TextDataDetectType type, const std::string& content, std::function<void()>&& disappearCallback)
285 {
286 return [node = WeakPtr<FrameNode>(previewNode), disappearCallback, type, content,
287 mainId = Container::CurrentIdSafelyWithCheck()](
288 int32_t code, const std::string& name, const std::string& message) {
289 ContainerScope scope(mainId);
290 if (SystemProperties::GetTextTraceEnabled()) {
291 TAG_LOGI(AceLogTag::ACE_TEXT, "UIExtensionNode error callbak, mount error node!");
292 }
293 auto previewNode = node.Upgrade();
294 CHECK_NULL_VOID(previewNode);
295 previewNode->Clean();
296 PreviewMenuController::MountErrorNode(previewNode, type, content, disappearCallback, nullptr);
297 previewNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
298 };
299 }
300
CreatePreview(TextDataDetectType type)301 RefPtr<FrameNode> PreviewMenuController::CreatePreview(TextDataDetectType type)
302 {
303 RefPtr<FrameNode> node;
304 switch (type) {
305 case TextDataDetectType::PHONE_NUMBER:
306 case TextDataDetectType::EMAIL:
307 case TextDataDetectType::ADDRESS:
308 case TextDataDetectType::URL:
309 node = CreateContactAndAddressPreviewNode(type);
310 break;
311 case TextDataDetectType::DATE_TIME:
312 node = CreateLinkingPreviewNode();
313 break;
314 default:
315 break;
316 }
317 return node;
318 }
319
CreateContactAndAddressPreviewNode(TextDataDetectType type)320 RefPtr<FrameNode> PreviewMenuController::CreateContactAndAddressPreviewNode(TextDataDetectType type)
321 {
322 auto frameNode = FrameNode::GetOrCreateFrameNode(V2::FLEX_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
323 []() { return AceType::MakeRefPtr<FlexLayoutPattern>(false); });
324 CHECK_NULL_RETURN(frameNode, nullptr);
325 auto flexLayoutProperty = frameNode->GetLayoutProperty<FlexLayoutProperty>();
326 CHECK_NULL_RETURN(flexLayoutProperty, nullptr);
327 auto context = frameNode->GetContext();
328 CHECK_NULL_RETURN(context, nullptr);
329 auto theme = context->GetTheme<TextOverlayTheme>();
330 CHECK_NULL_RETURN(theme, nullptr);
331 auto padding = CalcLength(theme->GetPreviewMenuPadding());
332 flexLayoutProperty->UpdatePadding({ padding, padding, padding, padding });
333 flexLayoutProperty->UpdateFlexDirection(FlexDirection::ROW);
334 flexLayoutProperty->UpdateCrossAxisAlign(FlexAlign::CENTER);
335 std::optional<CalcLength> minHeight = CalcLength(PREVIEW_MIN_HEIGHT);
336 std::optional<CalcLength> maxHeight = CalcLength(GetPreviewMaxHeight(frameNode));
337 std::optional<CalcLength> maxWidth = CalcLength(PREVIEW_MAX_WIDTH);
338 flexLayoutProperty->UpdateCalcMinSize(CalcSize(std::nullopt, minHeight));
339 flexLayoutProperty->UpdateCalcMaxSize(CalcSize(maxWidth, maxHeight));
340
341 // Adaptive internal content height for URL and address.
342 if (type == TextDataDetectType::EMAIL || type == TextDataDetectType::PHONE_NUMBER) {
343 std::optional<CalcLength> height = CalcLength(PREVIEW_MIN_HEIGHT);
344 flexLayoutProperty->UpdateUserDefinedIdealSize(CalcSize(std::nullopt, height));
345 }
346 frameNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
347 return frameNode;
348 }
349
CreateLinkingPreviewNode()350 RefPtr<FrameNode> PreviewMenuController::CreateLinkingPreviewNode()
351 {
352 auto frameNode = FrameNode::GetOrCreateFrameNode(V2::FLEX_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
353 []() { return AceType::MakeRefPtr<FlexLayoutPattern>(false); });
354 CHECK_NULL_RETURN(frameNode, nullptr);
355 auto flexLayoutProperty = frameNode->GetLayoutProperty<FlexLayoutProperty>();
356 CHECK_NULL_RETURN(flexLayoutProperty, nullptr);
357 flexLayoutProperty->UpdateFlexDirection(FlexDirection::ROW);
358 flexLayoutProperty->UpdateCrossAxisAlign(FlexAlign::CENTER);
359 flexLayoutProperty->UpdateMainAxisAlign(FlexAlign::CENTER);
360 std::optional<CalcLength> maxHeight = CalcLength(GetPreviewMaxHeight(frameNode));
361 std::optional<CalcLength> maxWidth = CalcLength(PREVIEW_MAX_WIDTH);
362 std::optional<CalcLength> minHeight = CalcLength(PREVIEW_MIN_HEIGHT);
363 flexLayoutProperty->UpdateCalcMinSize(CalcSize(std::nullopt, minHeight));
364 flexLayoutProperty->UpdateCalcMaxSize(CalcSize(maxWidth, maxHeight));
365 std::optional<CalcLength> height = CalcLength(Dimension(PERCENT_FULL, DimensionUnit::PERCENT));
366 flexLayoutProperty->UpdateUserDefinedIdealSize(CalcSize(std::nullopt, height));
367 frameNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
368 return frameNode;
369 }
370
GetPreviewMaxHeight(const RefPtr<FrameNode> & frameNode)371 Dimension PreviewMenuController::GetPreviewMaxHeight(const RefPtr<FrameNode>& frameNode)
372 {
373 CHECK_NULL_RETURN(frameNode, PREVIEW_MAX_WIDTH);
374 auto context = frameNode->GetContext();
375 CHECK_NULL_RETURN(context, PREVIEW_MAX_WIDTH);
376 auto safeAreaManager = context->GetSafeAreaManager();
377 CHECK_NULL_RETURN(safeAreaManager, PREVIEW_MAX_WIDTH);
378 auto bottom = safeAreaManager->GetSafeAreaWithoutProcess().bottom_.Length();
379 auto top = safeAreaManager->GetSafeAreaWithoutProcess().top_.Length();
380 auto containerId = Container::CurrentId();
381 auto container = AceEngine::Get().GetContainer(containerId);
382 CHECK_NULL_RETURN(container, PREVIEW_MAX_WIDTH);
383 // Get FreeMultiWindow status of main window or host window
384 auto isFreeMultiWindow = container->IsFreeMultiWindow();
385 float height;
386 if (!isFreeMultiWindow) {
387 height = context->GetDisplayWindowRectInfo().Height();
388 } else {
389 height = context->GetDisplayAvailableRect().Height();
390 }
391 auto maxHeightValue = (height - static_cast<float>(bottom + top)) * MAX_HEIGHT_PROPORTIONS;
392
393 if (SystemProperties::GetTextTraceEnabled()) {
394 TAG_LOGI(AceLogTag::ACE_TEXT,
395 "GetPreviewMaxHeight availableRect:%{public}s GetSafeAreaWithoutProcess:%{public}s "
396 "displayWindowRect:%{public}s "
397 "isFreeMultiWindow:%{public}d maxHeightValue:%{public}f",
398 context->GetDisplayAvailableRect().ToString().c_str(),
399 safeAreaManager->GetSafeAreaWithoutProcess().ToString().c_str(),
400 context->GetDisplayWindowRectInfo().ToString().c_str(), isFreeMultiWindow, maxHeightValue);
401 }
402 return Dimension(maxHeightValue);
403 }
404
MountErrorNode(const RefPtr<FrameNode> & previewNode,TextDataDetectType type,const std::string & content,std::function<void ()> disappearCallback,std::function<void ()> aiSpanClickCallabck)405 void PreviewMenuController::MountErrorNode(const RefPtr<FrameNode>& previewNode, TextDataDetectType type,
406 const std::string& content, std::function<void()> disappearCallback, std::function<void()> aiSpanClickCallabck)
407 {
408 switch (type) {
409 case TextDataDetectType::PHONE_NUMBER:
410 case TextDataDetectType::EMAIL:
411 CreateContactErrorNode(previewNode, content, std::move(disappearCallback));
412 break;
413 case TextDataDetectType::ADDRESS:
414 case TextDataDetectType::URL:
415 CreateURLAndAddressNode(previewNode, content, type, std::move(aiSpanClickCallabck));
416 break;
417 case TextDataDetectType::DATE_TIME:
418 CreateLinkingErrorNode(previewNode, type, std::move(disappearCallback));
419 break;
420 default:
421 break;
422 }
423 }
424
CreateContactErrorNode(const RefPtr<FrameNode> & previewNode,const std::string & content,std::function<void ()> && disappearCallback)425 void PreviewMenuController::CreateContactErrorNode(
426 const RefPtr<FrameNode>& previewNode, const std::string& content, std::function<void()>&& disappearCallback)
427 {
428 CHECK_NULL_VOID(previewNode);
429 auto avatarNode = FrameNode::CreateFrameNode(
430 V2::IMAGE_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<ImagePattern>());
431 auto textNode = FrameNode::GetOrCreateFrameNode(V2::TEXT_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
432 []() { return AceType::MakeRefPtr<TextPattern>(); });
433
434 auto eventHub = previewNode->GetOrCreateGestureEventHub();
435 CHECK_NULL_VOID(eventHub);
436 eventHub->SetUserOnClick([disappearCallback, mainId = Container::CurrentIdSafelyWithCheck()](GestureEvent& info) {
437 ContainerScope scope(mainId);
438 if (disappearCallback) {
439 disappearCallback();
440 }
441 });
442
443 auto imageLayoutProperty = avatarNode->GetLayoutProperty<ImageLayoutProperty>();
444 CHECK_NULL_VOID(imageLayoutProperty);
445 auto textLayoutProperty = textNode->GetLayoutProperty<TextLayoutProperty>();
446 CHECK_NULL_VOID(textLayoutProperty);
447 UpdateImageAndTitleNodeProperty(imageLayoutProperty, textLayoutProperty, TextDataDetectType::PHONE_NUMBER, content);
448 avatarNode->MarkModifyDone();
449 avatarNode->MountToParent(previewNode);
450 textNode->MountToParent(previewNode);
451 previewNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
452 }
453
CreateURLAndAddressNode(const RefPtr<FrameNode> & previewNode,const std::string & content,TextDataDetectType type,std::function<void ()> && aiSpanClickCallabck)454 void PreviewMenuController::CreateURLAndAddressNode(const RefPtr<FrameNode>& previewNode, const std::string& content,
455 TextDataDetectType type, std::function<void()>&& aiSpanClickCallabck)
456 {
457 CHECK_NULL_VOID(previewNode);
458 auto contentNode =
459 FrameNode::GetOrCreateFrameNode(V2::COLUMN_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
460 []() { return AceType::MakeRefPtr<LinearLayoutPattern>(true); });
461 auto flexLayoutProperty = contentNode->GetLayoutProperty<LinearLayoutProperty>();
462 CHECK_NULL_VOID(flexLayoutProperty);
463 flexLayoutProperty->UpdateCrossAxisAlign(FlexAlign::FLEX_START);
464
465 auto eventHub = previewNode->GetOrCreateGestureEventHub();
466 CHECK_NULL_VOID(eventHub);
467 std::function<void()> callback;
468 if (type == TextDataDetectType::URL) {
469 callback = [content, type, mainId = Container::CurrentIdSafelyWithCheck()]() {
470 ContainerScope scope(mainId);
471 auto context = PipelineContext::GetCurrentContextSafelyWithCheck();
472 CHECK_NULL_VOID(context);
473 auto fontManager = context->GetFontManager();
474 CHECK_NULL_VOID(fontManager);
475 fontManager->OnPreviewMenuOptionClick(type, content);
476 };
477 } else if (type == TextDataDetectType::ADDRESS) {
478 callback = std::move(aiSpanClickCallabck);
479 }
480 eventHub->SetUserOnClick([callback, mainId = Container::CurrentIdSafelyWithCheck()](GestureEvent& info) {
481 ContainerScope scope(mainId);
482 if (callback) {
483 callback();
484 }
485 });
486 CreateURLAndAddressContentNode(previewNode, contentNode, content, type);
487 previewNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
488 }
489
CreateURLAndAddressContentNode(const RefPtr<FrameNode> & previewNode,const RefPtr<FrameNode> & contentNode,const std::string & content,TextDataDetectType type)490 void PreviewMenuController::CreateURLAndAddressContentNode(const RefPtr<FrameNode>& previewNode,
491 const RefPtr<FrameNode>& contentNode, const std::string& content, TextDataDetectType type)
492 {
493 auto avatarNode = FrameNode::CreateFrameNode(
494 V2::IMAGE_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<ImagePattern>());
495 auto titleNode = FrameNode::GetOrCreateFrameNode(V2::TEXT_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
496 []() { return AceType::MakeRefPtr<TextPattern>(); });
497 auto textNode = FrameNode::GetOrCreateFrameNode(V2::TEXT_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
498 []() { return AceType::MakeRefPtr<TextPattern>(); });
499 auto imageLayoutProperty = avatarNode->GetLayoutProperty<ImageLayoutProperty>();
500 CHECK_NULL_VOID(imageLayoutProperty);
501 auto titleProperty = titleNode->GetLayoutProperty<TextLayoutProperty>();
502 CHECK_NULL_VOID(titleProperty);
503 auto textLayoutProperty = textNode->GetLayoutProperty<TextLayoutProperty>();
504 CHECK_NULL_VOID(textLayoutProperty);
505 auto context = contentNode->GetContext();
506 CHECK_NULL_VOID(context);
507 auto theme = context->GetTheme<TextOverlayTheme>();
508 CHECK_NULL_VOID(theme);
509 auto title = type == TextDataDetectType::ADDRESS ? theme->GetLocationTitle() : theme->GetLinkTitle();
510 UpdateImageAndTitleNodeProperty(imageLayoutProperty, titleProperty, type, title);
511 textLayoutProperty->UpdateContent(content);
512 textLayoutProperty->UpdateMaxLines(MAX_LINES);
513 textLayoutProperty->UpdateTextOverflow(TextOverflow::ELLIPSIS);
514 textLayoutProperty->UpdateTextColor(theme->GetPreviewFailedFontColor());
515 textLayoutProperty->UpdateFontSize(theme->GetPreviewFailedFontSize());
516 auto topMargin = CalcLength(theme->GetPreviewContentSpace());
517 textLayoutProperty->UpdateMargin(
518 { std::nullopt, std::nullopt, topMargin, std::nullopt, std::nullopt, std::nullopt });
519 titleNode->MountToParent(contentNode);
520 textNode->MountToParent(contentNode);
521 avatarNode->MarkModifyDone();
522 avatarNode->MountToParent(previewNode);
523 contentNode->MountToParent(previewNode);
524 }
525
UpdateImageAndTitleNodeProperty(const RefPtr<ImageLayoutProperty> & imageLayoutProperty,const RefPtr<TextLayoutProperty> & textLayoutProperty,TextDataDetectType type,const std::string & content)526 void PreviewMenuController::UpdateImageAndTitleNodeProperty(const RefPtr<ImageLayoutProperty>& imageLayoutProperty,
527 const RefPtr<TextLayoutProperty>& textLayoutProperty, TextDataDetectType type, const std::string& content)
528 {
529 auto context = PipelineBase::GetCurrentContextSafelyWithCheck();
530 CHECK_NULL_VOID(context);
531 std::optional<CalcLength> imageSize = CalcLength(AVATAR_SIZE);
532 imageLayoutProperty->UpdateUserDefinedIdealSize(CalcSize(imageSize, imageSize));
533 auto iconTheme = context->GetTheme<IconTheme>();
534 std::string iconPath = "";
535 if (iconTheme) {
536 switch (type) {
537 case TextDataDetectType::PHONE_NUMBER:
538 case TextDataDetectType::EMAIL:
539 iconPath = iconTheme->GetIconPath(InternalResource::ResourceId::IC_PERSON_FILL_SVG);
540 break;
541 case TextDataDetectType::ADDRESS:
542 iconPath = iconTheme->GetIconPath(InternalResource::ResourceId::IC_LOACTION_SVG);
543 break;
544 case TextDataDetectType::URL:
545 iconPath = iconTheme->GetIconPath(InternalResource::ResourceId::IC_LINK_SVG);
546 break;
547 default:
548 break;
549 }
550 }
551 ImageSourceInfo imageSourceInfo;
552 imageSourceInfo.SetSrc(iconPath);
553 imageLayoutProperty->UpdateImageSourceInfo(imageSourceInfo);
554 auto overlayTheme = context->GetTheme<TextOverlayTheme>();
555 CHECK_NULL_VOID(overlayTheme);
556 auto endMargin = CalcLength(overlayTheme->GetMenuSafeSpacing());
557 imageLayoutProperty->UpdateFlexShrink(0);
558 imageLayoutProperty->UpdateMargin(
559 { std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, endMargin });
560
561 textLayoutProperty->UpdateContent(content);
562 textLayoutProperty->UpdateMaxLines(1);
563 textLayoutProperty->UpdateTextOverflow(TextOverflow::ELLIPSIS);
564 textLayoutProperty->UpdateFontWeight(FontWeight::MEDIUM);
565 auto theme = context->GetTheme<SelectTheme>();
566 CHECK_NULL_VOID(theme);
567 textLayoutProperty->UpdateTextColor(theme->GetMenuFontColor());
568 }
569
CreateLinkingErrorNode(const RefPtr<FrameNode> & previewNode,TextDataDetectType type,std::function<void ()> && disappearCallback)570 void PreviewMenuController::CreateLinkingErrorNode(
571 const RefPtr<FrameNode>& previewNode, TextDataDetectType type, std::function<void()>&& disappearCallback)
572 {
573 auto eventHub = previewNode->GetOrCreateGestureEventHub();
574 CHECK_NULL_VOID(eventHub);
575 std::function<void()> callback;
576 if (type == TextDataDetectType::URL) {
577 callback = disappearCallback;
578 } else if (type == TextDataDetectType::DATE_TIME) {
579 if (SystemProperties::GetPreviewStatus() == 0) {
580 auto name = ExpandedMenuPluginLoader::GetInstance().GetAPPName(type);
581 callback = GetLinkingCallback(name);
582 } else {
583 callback = disappearCallback;
584 }
585 } else if (type == TextDataDetectType::ADDRESS) {
586 auto name = ExpandedMenuPluginLoader::GetInstance().GetAPPName(type);
587 callback = GetLinkingCallback(name);
588 }
589 eventHub->SetUserOnClick([callback, mainId = Container::CurrentIdSafelyWithCheck()](GestureEvent& info) {
590 ContainerScope scope(mainId);
591 if (callback) {
592 callback();
593 }
594 });
595 auto textNode = FrameNode::GetOrCreateFrameNode(V2::TEXT_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
596 []() { return AceType::MakeRefPtr<TextPattern>(); });
597 auto textLayoutProperty = textNode->GetLayoutProperty<TextLayoutProperty>();
598 CHECK_NULL_VOID(textLayoutProperty);
599 UpdateLinkNodeProperty(textLayoutProperty, type);
600 textNode->MountToParent(previewNode);
601 }
602
UpdateLinkNodeProperty(const RefPtr<TextLayoutProperty> & textLayoutProperty,TextDataDetectType type)603 void PreviewMenuController::UpdateLinkNodeProperty(
604 const RefPtr<TextLayoutProperty>& textLayoutProperty, TextDataDetectType type)
605 {
606 auto context = PipelineBase::GetCurrentContextSafelyWithCheck();
607 CHECK_NULL_VOID(context);
608 auto theme = context->GetTheme<TextOverlayTheme>();
609 CHECK_NULL_VOID(theme);
610 auto content = theme->GetPreviewDisplayFailedContent(type);
611 textLayoutProperty->UpdateContent(content);
612 textLayoutProperty->UpdateFontWeight(FontWeight::REGULAR);
613 textLayoutProperty->UpdateFontSize(theme->GetPreviewFailedFontSize());
614 textLayoutProperty->UpdateTextColor(theme->GetPreviewFailedFontColor());
615 textLayoutProperty->UpdateTextAlign(TextAlign::CENTER);
616 textLayoutProperty->UpdateLineSpacing(FAILED_TEXT_LINE_SPACING);
617 textLayoutProperty->UpdateIsOnlyBetweenLines(true);
618 }
619
BindContextMenu(const RefPtr<FrameNode> & targetNode,bool isShow)620 void PreviewMenuController::BindContextMenu(const RefPtr<FrameNode>& targetNode, bool isShow)
621 {
622 #ifndef ACE_UNITTEST
623 CHECK_NULL_VOID(targetNode);
624 ViewStackProcessor::GetInstance()->Push(targetNode);
625 menuBuilder_ = [weak = WeakClaim(this), mainId = Container::CurrentIdSafelyWithCheck()]() {
626 ContainerScope scope(mainId);
627 auto controller = weak.Upgrade();
628 CHECK_NULL_VOID(controller);
629 controller->CreateAIEntityMenu();
630 };
631 previewBuilder_ = [weak = WeakClaim(this), pattern = pattern_, mainId = Container::CurrentIdSafelyWithCheck(),
632 func = GetDisappearCallback()]() {
633 ContainerScope scope(mainId);
634 auto controller = weak.Upgrade();
635 CHECK_NULL_VOID(controller);
636 auto textPattern = pattern.Upgrade();
637 CHECK_NULL_VOID(textPattern);
638 auto data = textPattern->GetSelectedAIData();
639 auto spanClickFunc = textPattern->GetPreviewMenuAISpanClickrCallback(data);
640 controller->CreatePreviewMenu(data.type, data.content, func, data.params, spanClickFunc);
641 };
642 menuParam_.isShow = isShow;
643 isShow_ = isShow;
644 ViewAbstractModel::GetInstance()->BindContextMenu(
645 ResponseType::LONG_PRESS, menuBuilder_, menuParam_, previewBuilder_);
646 ViewAbstractModel::GetInstance()->BindDragWithContextMenuParams(menuParam_);
647 ViewStackProcessor::GetInstance()->Finish();
648 #endif
649 }
650 } // namespace OHOS::Ace::NG