• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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