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