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