1 /*
2  * Copyright (c) 2021-2022 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 "frameworks/bridge/declarative_frontend/jsview/action_sheet/js_action_sheet.h"
17 
18 #include <string>
19 #include <vector>
20 
21 #include "base/log/ace_scoring_log.h"
22 #include "bridge/common/utils/engine_helper.h"
23 #include "bridge/declarative_frontend/engine/functions/js_function.h"
24 #include "bridge/declarative_frontend/jsview/models/action_sheet_model_impl.h"
25 #include "core/common/container.h"
26 #include "core/components_ng/base/view_stack_processor.h"
27 #include "core/components_ng/pattern/action_sheet/action_sheet_model_ng.h"
28 
29 namespace OHOS::Ace {
30 std::unique_ptr<ActionSheetModel> ActionSheetModel::instance_ = nullptr;
31 std::mutex ActionSheetModel::mutex_;
32 
GetInstance()33 ActionSheetModel* ActionSheetModel::GetInstance()
34 {
35     if (!instance_) {
36         std::lock_guard<std::mutex> lock(mutex_);
37         if (!instance_) {
38 #ifdef NG_BUILD
39             instance_.reset(new NG::ActionSheetModelNG());
40 #else
41             if (Container::IsCurrentUseNewPipeline()) {
42                 instance_.reset(new NG::ActionSheetModelNG());
43             } else {
44                 instance_.reset(new Framework::ActionSheetModelImpl());
45             }
46 #endif
47         }
48     }
49     return instance_.get();
50 }
51 } // namespace OHOS::Ace
52 
53 namespace OHOS::Ace::Framework {
54 namespace {
55 const DimensionOffset ACTION_SHEET_OFFSET_DEFAULT = DimensionOffset(0.0_vp, -40.0_vp);
56 const DimensionOffset ACTION_SHEET_OFFSET_DEFAULT_TOP = DimensionOffset(0.0_vp, 40.0_vp);
57 const std::vector<DialogAlignment> DIALOG_ALIGNMENT = { DialogAlignment::TOP, DialogAlignment::CENTER,
58     DialogAlignment::BOTTOM, DialogAlignment::DEFAULT, DialogAlignment::TOP_START, DialogAlignment::TOP_END,
59     DialogAlignment::CENTER_START, DialogAlignment::CENTER_END, DialogAlignment::BOTTOM_START,
60     DialogAlignment::BOTTOM_END };
61 } // namespace
62 
SetParseStyle(ButtonInfo & buttonInfo,const int32_t styleValue)63 static void SetParseStyle(ButtonInfo& buttonInfo, const int32_t styleValue)
64 {
65     if (styleValue >= static_cast<int32_t>(DialogButtonStyle::DEFAULT) &&
66         styleValue <= static_cast<int32_t>(DialogButtonStyle::HIGHTLIGHT)) {
67         buttonInfo.dlgButtonStyle = static_cast<DialogButtonStyle>(styleValue);
68     }
69 }
70 
ParseSheetInfo(const JsiExecutionContext & execContext,JSRef<JSVal> val)71 ActionSheetInfo ParseSheetInfo(const JsiExecutionContext& execContext, JSRef<JSVal> val)
72 {
73     ActionSheetInfo sheetInfo;
74     if (!val->IsObject()) {
75         LOGW("param is not an object.");
76         return sheetInfo;
77     }
78 
79     auto obj = JSRef<JSObject>::Cast(val);
80     auto titleVal = obj->GetProperty("title");
81     std::string title;
82     if (JSActionSheet::ParseJsString(titleVal, title)) {
83         sheetInfo.title = title;
84     }
85 
86     auto iconVal = obj->GetProperty("icon");
87     std::string icon;
88     if (JSActionSheet::ParseJsMedia(iconVal, icon)) {
89         sheetInfo.icon = icon;
90     }
91 
92     auto actionValue = obj->GetProperty("action");
93     if (actionValue->IsFunction()) {
94         auto frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
95         auto actionFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(actionValue));
96         auto eventFunc = [execContext, func = std::move(actionFunc), node = frameNode](const GestureEvent&) {
97             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execContext);
98             ACE_SCORING_EVENT("SheetInfo.action");
99             auto pipelineContext = PipelineContext::GetCurrentContextSafely();
100             CHECK_NULL_VOID(pipelineContext);
101             pipelineContext->UpdateCurrentActiveNode(node);
102             func->ExecuteJS();
103         };
104         ActionSheetModel::GetInstance()->SetAction(eventFunc, sheetInfo);
105     }
106     return sheetInfo;
107 }
108 
ParseTitleAndMessage(DialogProperties & properties,JSRef<JSObject> obj)109 void ParseTitleAndMessage(DialogProperties& properties, JSRef<JSObject> obj)
110 {
111     // Parse title.
112     auto titleValue = obj->GetProperty("title");
113     std::string title;
114     if (JSActionSheet::ParseJsString(titleValue, title)) {
115         properties.title = title;
116     }
117 
118     // Parse subtitle.
119     auto subtitleValue = obj->GetProperty("subtitle");
120     std::string subtitle;
121     if (JSActionSheet::ParseJsString(subtitleValue, subtitle)) {
122         properties.subtitle = subtitle;
123     }
124 
125     // Parses message.
126     auto messageValue = obj->GetProperty("message");
127     std::string message;
128     if (JSActionSheet::ParseJsString(messageValue, message)) {
129         properties.content = message;
130     }
131 }
132 
ParseConfirmButton(const JsiExecutionContext & execContext,DialogProperties & properties,JSRef<JSObject> obj)133 void ParseConfirmButton(const JsiExecutionContext& execContext, DialogProperties& properties, JSRef<JSObject> obj)
134 {
135     auto confirmVal = obj->GetProperty("confirm");
136     if (!confirmVal->IsObject()) {
137         return;
138     }
139     JSRef<JSObject> confirmObj = JSRef<JSObject>::Cast(confirmVal);
140     std::string buttonValue;
141     if (JSActionSheet::ParseJsString(confirmObj->GetProperty("value"), buttonValue)) {
142         ButtonInfo buttonInfo = { .text = buttonValue };
143         JSRef<JSVal> actionValue = confirmObj->GetProperty("action");
144         if (actionValue->IsFunction()) {
145             auto frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
146             auto actionFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(actionValue));
147             auto gestureEvent = [execContext, func = std::move(actionFunc), node = frameNode](GestureEvent&) {
148                 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execContext);
149                 ACE_SCORING_EVENT("ActionSheet.confirm.action");
150                 auto pipelineContext = PipelineContext::GetCurrentContextSafely();
151                 CHECK_NULL_VOID(pipelineContext);
152                 pipelineContext->UpdateCurrentActiveNode(node);
153                 func->ExecuteJS();
154             };
155             actionFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(actionValue));
156             auto eventFunc = [execContext, func = std::move(actionFunc), node = frameNode]() {
157                 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execContext);
158                 ACE_SCORING_EVENT("ActionSheet.confirm.action");
159                 auto pipelineContext = PipelineContext::GetCurrentContextSafely();
160                 CHECK_NULL_VOID(pipelineContext);
161                 pipelineContext->UpdateCurrentActiveNode(node);
162                 func->Execute();
163             };
164             ActionSheetModel::GetInstance()->SetConfirm(gestureEvent, eventFunc, buttonInfo, properties);
165         }
166         auto enabledValue = confirmObj->GetProperty("enabled");
167         if (enabledValue->IsBoolean()) {
168             buttonInfo.enabled = enabledValue->ToBoolean();
169         }
170         auto defaultFocusValue = confirmObj->GetProperty("defaultFocus");
171         if (defaultFocusValue->IsBoolean()) {
172             buttonInfo.defaultFocus = defaultFocusValue->ToBoolean();
173         }
174         auto style = confirmObj->GetProperty("style");
175         if (style->IsNumber()) {
176             SetParseStyle(buttonInfo, style->ToNumber<int32_t>());
177         }
178         if (!buttonInfo.defaultFocus) {
179             buttonInfo.isPrimary = true;
180         }
181         if (buttonInfo.IsValid()) {
182             properties.buttons.clear();
183             properties.buttons.emplace_back(buttonInfo);
184         }
185     }
186 }
187 
ParseShadow(DialogProperties & properties,JSRef<JSObject> obj)188 void ParseShadow(DialogProperties& properties, JSRef<JSObject> obj)
189 {
190     // Parse shadow.
191     auto shadowValue = obj->GetProperty("shadow");
192     Shadow shadow;
193     if ((shadowValue->IsObject() || shadowValue->IsNumber()) && JSActionSheet::ParseShadowProps(shadowValue, shadow)) {
194         properties.shadow = shadow;
195     }
196 }
197 
ParseBorderWidthAndColor(DialogProperties & properties,JSRef<JSObject> obj)198 void ParseBorderWidthAndColor(DialogProperties& properties, JSRef<JSObject> obj)
199 {
200     auto borderWidthValue = obj->GetProperty("borderWidth");
201     NG::BorderWidthProperty borderWidth;
202     if (JSActionSheet::ParseBorderWidthProps(borderWidthValue, borderWidth)) {
203         properties.borderWidth = borderWidth;
204         auto colorValue = obj->GetProperty("borderColor");
205         NG::BorderColorProperty borderColor;
206         if (JSActionSheet::ParseBorderColorProps(colorValue, borderColor)) {
207             properties.borderColor = borderColor;
208         } else {
209             borderColor.SetColor(Color::BLACK);
210             properties.borderColor = borderColor;
211         }
212     }
213 }
214 
ParseRadius(DialogProperties & properties,JSRef<JSObject> obj)215 void ParseRadius(DialogProperties& properties, JSRef<JSObject> obj)
216 {
217     auto cornerRadiusValue = obj->GetProperty("cornerRadius");
218     NG::BorderRadiusProperty radius;
219     if (JSActionSheet::ParseBorderRadius(cornerRadiusValue, radius)) {
220         properties.borderRadius = radius;
221     }
222 }
223 
UpdateDialogAlignment(DialogAlignment & alignment)224 void UpdateDialogAlignment(DialogAlignment& alignment)
225 {
226     bool isRtl = AceApplicationInfo::GetInstance().IsRightToLeft();
227     if (alignment == DialogAlignment::TOP_START) {
228         if (isRtl) {
229             alignment = DialogAlignment::TOP_END;
230         }
231     } else if (alignment == DialogAlignment::TOP_END) {
232         if (isRtl) {
233             alignment = DialogAlignment::TOP_START;
234         }
235     } else if (alignment == DialogAlignment::CENTER_START) {
236         if (isRtl) {
237             alignment = DialogAlignment::CENTER_END;
238         }
239     } else if (alignment == DialogAlignment::CENTER_END) {
240         if (isRtl) {
241             alignment = DialogAlignment::CENTER_START;
242         }
243     } else if (alignment == DialogAlignment::BOTTOM_START) {
244         if (isRtl) {
245             alignment = DialogAlignment::BOTTOM_END;
246         }
247     } else if (alignment == DialogAlignment::BOTTOM_END) {
248         if (isRtl) {
249             alignment = DialogAlignment::BOTTOM_START;
250         }
251     }
252 }
253 
ParseDialogAlignment(DialogProperties & properties,JSRef<JSObject> obj)254 void ParseDialogAlignment(DialogProperties& properties, JSRef<JSObject> obj)
255 {
256     // Parse alignment
257     auto alignmentValue = obj->GetProperty("alignment");
258     if (alignmentValue->IsNumber()) {
259         auto alignment = alignmentValue->ToNumber<int32_t>();
260         if (alignment >= 0 && alignment <= static_cast<int32_t>(DIALOG_ALIGNMENT.size())) {
261             properties.alignment = DIALOG_ALIGNMENT[alignment];
262             UpdateDialogAlignment(properties.alignment);
263         }
264         if (alignment == static_cast<int32_t>(DialogAlignment::TOP) ||
265             alignment == static_cast<int32_t>(DialogAlignment::TOP_START) ||
266             alignment == static_cast<int32_t>(DialogAlignment::TOP_END)) {
267             properties.offset = ACTION_SHEET_OFFSET_DEFAULT_TOP;
268         }
269     }
270 }
271 
ParseOffset(DialogProperties & properties,JSRef<JSObject> obj)272 void ParseOffset(DialogProperties& properties, JSRef<JSObject> obj)
273 {
274     // Parse offset
275     auto offsetValue = obj->GetProperty("offset");
276     if (offsetValue->IsObject()) {
277         auto offsetObj = JSRef<JSObject>::Cast(offsetValue);
278         CalcDimension dx;
279         auto dxValue = offsetObj->GetProperty("dx");
280         JSActionSheet::ParseJsDimensionVp(dxValue, dx);
281         CalcDimension dy;
282         auto dyValue = offsetObj->GetProperty("dy");
283         JSActionSheet::ParseJsDimensionVp(dyValue, dy);
284         properties.offset = DimensionOffset(dx, dy);
285         bool isRtl = AceApplicationInfo::GetInstance().IsRightToLeft();
286         Dimension offsetX = isRtl ? properties.offset.GetX() * (-1) : properties.offset.GetX();
287         properties.offset.SetX(offsetX);
288     }
289 }
290 
ParseMaskRect(DialogProperties & properties,JSRef<JSObject> obj)291 void ParseMaskRect(DialogProperties& properties, JSRef<JSObject> obj)
292 {
293     // Parse maskRect.
294     auto maskRectValue = obj->GetProperty("maskRect");
295     DimensionRect maskRect;
296     if (JSViewAbstract::ParseJsDimensionRect(maskRectValue, maskRect)) {
297         properties.maskRect = maskRect;
298         bool isRtl = AceApplicationInfo::GetInstance().IsRightToLeft();
299         auto offset = maskRect.GetOffset();
300         Dimension offsetX = isRtl ? offset.GetX() * (-1) : offset.GetX();
301         offset.SetX(offsetX);
302         properties.maskRect->SetOffset(offset);
303     }
304 }
305 
Show(const JSCallbackInfo & args)306 void JSActionSheet::Show(const JSCallbackInfo& args)
307 {
308     auto scopedDelegate = EngineHelper::GetCurrentDelegateSafely();
309     if (!scopedDelegate) {
310         // this case usually means there is no foreground container, need to figure out the reason.
311         LOGE("scopedDelegate is null, please check");
312         return;
313     }
314     if (!args[0]->IsObject()) {
315         LOGE("args is not an object, can't show ActionSheet.");
316         return;
317     }
318 
319     DialogProperties properties {
320         .type = DialogType::ACTION_SHEET, .alignment = DialogAlignment::BOTTOM, .offset = ACTION_SHEET_OFFSET_DEFAULT
321     };
322     auto obj = JSRef<JSObject>::Cast(args[0]);
323     auto execContext = args.GetExecutionContext();
324     auto dialogNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
325 
326     ParseTitleAndMessage(properties, obj);
327     ParseConfirmButton(execContext, properties, obj);
328     ParseShadow(properties, obj);
329     ParseBorderWidthAndColor(properties, obj);
330     ParseRadius(properties, obj);
331     ParseDialogAlignment(properties, obj);
332     ParseOffset(properties, obj);
333     ParseMaskRect(properties, obj);
334 
335     auto onLanguageChange = [execContext, obj, parseContent = ParseTitleAndMessage, parseButton = ParseConfirmButton,
336                                 parseShadow = ParseShadow, parseBorderProps = ParseBorderWidthAndColor,
337                                 parseRadius = ParseRadius, parseAlignment = ParseDialogAlignment,
338                                 parseOffset = ParseOffset,  parseMaskRect = ParseMaskRect,
339                                 node = dialogNode](DialogProperties& dialogProps) {
340         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execContext);
341         ACE_SCORING_EVENT("ActionSheet.property.onLanguageChange");
342         auto pipelineContext = PipelineContext::GetCurrentContextSafely();
343         CHECK_NULL_VOID(pipelineContext);
344         pipelineContext->UpdateCurrentActiveNode(node);
345         parseContent(dialogProps, obj);
346         parseButton(execContext, dialogProps, obj);
347         parseShadow(dialogProps, obj);
348         parseBorderProps(dialogProps, obj);
349         parseRadius(dialogProps, obj);
350         ParseDialogAlignment(dialogProps, obj);
351         parseOffset(dialogProps, obj);
352         parseMaskRect(dialogProps, obj);
353         // Parse sheets
354         auto sheetsVal = obj->GetProperty("sheets");
355         if (sheetsVal->IsArray()) {
356             std::vector<ActionSheetInfo> sheetsInfo;
357             auto sheetsArr = JSRef<JSArray>::Cast(sheetsVal);
358             for (size_t index = 0; index < sheetsArr->Length(); ++index) {
359                 sheetsInfo.emplace_back(ParseSheetInfo(execContext, sheetsArr->GetValueAt(index)));
360             }
361             dialogProps.sheetsInfo = std::move(sheetsInfo);
362         }
363     };
364     properties.onLanguageChange = std::move(onLanguageChange);
365 
366     // Parse auto autoCancel.
367     auto autoCancelValue = obj->GetProperty("autoCancel");
368     if (autoCancelValue->IsBoolean()) {
369         properties.autoCancel = autoCancelValue->ToBoolean();
370     }
371 
372     // Parse cancel.
373     auto cancelValue = obj->GetProperty("cancel");
374     if (cancelValue->IsFunction()) {
375         auto cancelFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(cancelValue));
376         auto eventFunc = [execContext, func = std::move(cancelFunc), node = dialogNode]() {
377             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execContext);
378             ACE_SCORING_EVENT("ActionSheet.cancel");
379             auto pipelineContext = PipelineContext::GetCurrentContextSafely();
380             CHECK_NULL_VOID(pipelineContext);
381             pipelineContext->UpdateCurrentActiveNode(node);
382             func->Execute();
383         };
384         ActionSheetModel::GetInstance()->SetCancel(eventFunc, properties);
385     }
386 
387     std::function<void(const int32_t& info)> onWillDismissFunc = nullptr;
388     ParseDialogCallback(obj, onWillDismissFunc);
389     ActionSheetModel::GetInstance()->SetOnWillDismiss(std::move(onWillDismissFunc), properties);
390 
391     // Parse sheets
392     auto sheetsVal = obj->GetProperty("sheets");
393     if (sheetsVal->IsArray()) {
394         std::vector<ActionSheetInfo> sheetsInfo;
395         auto sheetsArr = JSRef<JSArray>::Cast(sheetsVal);
396         for (size_t index = 0; index < sheetsArr->Length(); ++index) {
397             sheetsInfo.emplace_back(ParseSheetInfo(execContext, sheetsArr->GetValueAt(index)));
398         }
399         properties.sheetsInfo = std::move(sheetsInfo);
400     }
401 
402     // Parses gridCount.
403     auto gridCountValue = obj->GetProperty("gridCount");
404     if (gridCountValue->IsNumber()) {
405         properties.gridCount = gridCountValue->ToNumber<int32_t>();
406     }
407 
408     // Parse showInSubWindowValue.
409     auto showInSubWindowValue = obj->GetProperty("showInSubWindow");
410     if (showInSubWindowValue->IsBoolean()) {
411 #if defined(PREVIEW)
412         LOGW("[Engine Log] Unable to use the SubWindow in the Previewer. Perform this operation on the "
413              "emulator or a real device instead.");
414 #else
415         properties.isShowInSubWindow = showInSubWindowValue->ToBoolean();
416 #endif
417     }
418 
419     // Parse isModalValue.
420     auto isModalValue = obj->GetProperty("isModal");
421     if (isModalValue->IsBoolean()) {
422         LOGI("Parse isModalValue");
423         properties.isModal = isModalValue->ToBoolean();
424     }
425 
426     auto backgroundColorValue = obj->GetProperty("backgroundColor");
427     Color backgroundColor;
428     if (JSViewAbstract::ParseJsColor(backgroundColorValue, backgroundColor)) {
429         properties.backgroundColor = backgroundColor;
430     }
431 
432     auto backgroundBlurStyle = obj->GetProperty("backgroundBlurStyle");
433     if (backgroundBlurStyle->IsNumber()) {
434         auto blurStyle = backgroundBlurStyle->ToNumber<int32_t>();
435         if (blurStyle >= static_cast<int>(BlurStyle::NO_MATERIAL) &&
436             blurStyle <= static_cast<int>(BlurStyle::COMPONENT_ULTRA_THICK)) {
437             properties.backgroundBlurStyle = blurStyle;
438         }
439     }
440     // Parse transition.
441     properties.transitionEffect = ParseJsTransitionEffect(args);
442     JSViewAbstract::SetDialogProperties(obj, properties);
443     JSViewAbstract::SetDialogHoverModeProperties(obj, properties);
444     ActionSheetModel::GetInstance()->ShowActionSheet(properties);
445     args.SetReturnValue(args.This());
446 }
447 
JSBind(BindingTarget globalObj)448 void JSActionSheet::JSBind(BindingTarget globalObj)
449 {
450     JSClass<JSActionSheet>::Declare("ActionSheet");
451     JSClass<JSActionSheet>::StaticMethod("show", &JSActionSheet::Show);
452     JSClass<JSActionSheet>::InheritAndBind<JSViewAbstract>(globalObj);
453 }
454 } // namespace OHOS::Ace::Framework
455