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 "bridge/declarative_frontend/jsview/js_list_item.h"
17 
18 #include <cstdint>
19 #include <functional>
20 
21 #include "base/log/ace_scoring_log.h"
22 #include "bridge/declarative_frontend/engine/functions/js_drag_function.h"
23 #include "bridge/declarative_frontend/engine/functions/js_function.h"
24 #include "bridge/declarative_frontend/jsview/js_utils.h"
25 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
26 #include "bridge/declarative_frontend/jsview/models/list_item_model_impl.h"
27 #include "core/common/container.h"
28 #include "core/components_ng/base/view_abstract_model.h"
29 #include "core/components_ng/base/view_stack_processor.h"
30 #include "core/components_ng/event/gesture_event_hub.h"
31 #include "core/components_ng/pattern/list/list_item_model.h"
32 #include "core/components_ng/pattern/list/list_item_model_ng.h"
33 
34 namespace OHOS::Ace {
35 
36 std::unique_ptr<ListItemModel> ListItemModel::instance_ = nullptr;
37 std::mutex ListItemModel::mutex_;
38 
GetInstance()39 ListItemModel* ListItemModel::GetInstance()
40 {
41     if (!instance_) {
42         std::lock_guard<std::mutex> lock(mutex_);
43         if (!instance_) {
44 #ifdef NG_BUILD
45             instance_.reset(new NG::ListItemModelNG());
46 #else
47             if (Container::IsCurrentUseNewPipeline()) {
48                 instance_.reset(new NG::ListItemModelNG());
49             } else {
50                 instance_.reset(new Framework::ListItemModelImpl());
51             }
52 #endif
53         }
54     }
55     return instance_.get();
56 }
57 
58 } // namespace OHOS::Ace
59 
60 namespace OHOS::Ace::Framework {
61 
Create(const JSCallbackInfo & args)62 void JSListItem::Create(const JSCallbackInfo& args)
63 {
64     if (Container::IsCurrentUsePartialUpdate()) {
65         CreateForPartialUpdate(args);
66         return;
67     }
68     std::string type;
69     if (args.Length() >= 1 && args[0]->IsString()) {
70         type = args[0]->ToString();
71     }
72 
73     ListItemModel::GetInstance()->Create();
74     if (!type.empty()) {
75         ListItemModel::GetInstance()->SetType(type);
76     }
77     args.ReturnSelf();
78 }
79 
Pop()80 void JSListItem::Pop()
81 {
82     JSContainerBase::Pop();
83     ListItemModel::GetInstance()->OnDidPop();
84 }
85 
CreateForPartialUpdate(const JSCallbackInfo & args)86 void JSListItem::CreateForPartialUpdate(const JSCallbackInfo& args)
87 {
88     const int32_t ARGS_LENGTH = 2;
89     auto len = args.Length();
90     if (len < ARGS_LENGTH) {
91         ListItemModel::GetInstance()->Create();
92         return;
93     }
94     JSRef<JSVal> arg0 = args[0];
95     if (!arg0->IsFunction()) {
96         ListItemModel::GetInstance()->Create();
97         return;
98     }
99 
100     JSRef<JSVal> arg1 = args[1];
101     if (!arg1->IsBoolean()) {
102         return;
103     }
104     const bool isLazy = arg1->ToBoolean();
105 
106     V2::ListItemStyle listItemStyle = V2::ListItemStyle::NONE;
107     if (len > ARGS_LENGTH && args[ARGS_LENGTH]->IsObject()) {
108         JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[ARGS_LENGTH]);
109         JSRef<JSVal> styleObj = obj->GetProperty("style");
110         listItemStyle = styleObj->IsNumber() ? static_cast<V2::ListItemStyle>(styleObj->ToNumber<int32_t>())
111                                              : V2::ListItemStyle::NONE;
112     }
113 
114     if (!isLazy) {
115         if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE)) {
116             ListItemModel::GetInstance()->Create(nullptr, listItemStyle);
117         } else {
118             ListItemModel::GetInstance()->Create();
119         }
120     } else {
121         RefPtr<JsFunction> jsDeepRender = AceType::MakeRefPtr<JsFunction>(args.This(), JSRef<JSFunc>::Cast(arg0));
122         auto listItemDeepRenderFunc = [execCtx = args.GetExecutionContext(),
123                                           jsDeepRenderFunc = std::move(jsDeepRender)](int32_t nodeId) {
124             ACE_SCOPED_TRACE("JSListItem::ExecuteDeepRender");
125             JAVASCRIPT_EXECUTION_SCOPE(execCtx);
126             JSRef<JSVal> jsParams[2];
127             jsParams[0] = JSRef<JSVal>::Make(ToJSValue(nodeId));
128             jsParams[1] = JSRef<JSVal>::Make(ToJSValue(true));
129             jsDeepRenderFunc->ExecuteJS(2, jsParams);
130         }; // listItemDeepRenderFunc lambda
131         ListItemModel::GetInstance()->Create(std::move(listItemDeepRenderFunc), listItemStyle);
132         ListItemModel::GetInstance()->SetIsLazyCreating(isLazy);
133     }
134 }
135 
SetSticky(int32_t sticky)136 void JSListItem::SetSticky(int32_t sticky)
137 {
138     ListItemModel::GetInstance()->SetSticky(static_cast<V2::StickyMode>(sticky));
139 }
140 
SetEditable(const JSCallbackInfo & args)141 void JSListItem::SetEditable(const JSCallbackInfo& args)
142 {
143     if (args[0]->IsBoolean()) {
144         uint32_t value = args[0]->ToBoolean() ? V2::EditMode::DELETABLE | V2::EditMode::MOVABLE : V2::EditMode::SHAM;
145         ListItemModel::GetInstance()->SetEditMode(value);
146         return;
147     }
148 
149     if (args[0]->IsNumber()) {
150         auto value = args[0]->ToNumber<uint32_t>();
151         ListItemModel::GetInstance()->SetEditMode(value);
152         return;
153     }
154 }
155 
SetSelectable(const JSCallbackInfo & info)156 void JSListItem::SetSelectable(const JSCallbackInfo& info)
157 {
158     if (info.Length() < 1) {
159         return;
160     }
161     bool selectable = true;
162     if (info[0]->IsBoolean()) {
163         selectable = info[0]->ToBoolean();
164     }
165     ListItemModel::GetInstance()->SetSelectable(selectable);
166 }
167 
SetSelected(const JSCallbackInfo & info)168 void JSListItem::SetSelected(const JSCallbackInfo& info)
169 {
170     if (info.Length() < 1) {
171         return;
172     }
173     bool select = false;
174     if (info[0]->IsBoolean()) {
175         select = info[0]->ToBoolean();
176     }
177     ListItemModel::GetInstance()->SetSelected(select);
178 
179     if (info.Length() > 1 && info[1]->IsFunction()) {
180         auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(info[1]));
181         auto targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
182         auto changeEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](
183                                bool param) {
184             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
185             ACE_SCORING_EVENT("ListItem.ChangeEvent");
186             auto newJSVal = JSRef<JSVal>::Make(ToJSValue(param));
187             PipelineContext::SetCallBackNode(node);
188             func->ExecuteJS(1, &newJSVal);
189         };
190         ListItemModel::GetInstance()->SetSelectChangeEvent(std::move(changeEvent));
191     }
192 }
193 
JsParseDeleteArea(const JsiExecutionContext & context,const JSRef<JSVal> & jsValue,bool isStartArea)194 void JSListItem::JsParseDeleteArea(const JsiExecutionContext& context, const JSRef<JSVal>& jsValue, bool isStartArea)
195 {
196     auto deleteAreaObj = JSRef<JSObject>::Cast(jsValue);
197 
198     std::function<void()> builderAction;
199     auto builderObject = deleteAreaObj->GetProperty("builder");
200     if (builderObject->IsFunction()) {
201         auto builderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(builderObject));
202         builderAction = [builderFunc]() { builderFunc->Execute(); };
203     }
204 
205     auto onAction = deleteAreaObj->GetProperty("onAction");
206     std::function<void()> onActionCallback;
207     if (onAction->IsFunction()) {
208         onActionCallback = [execCtx = context, func = JSRef<JSFunc>::Cast(onAction)]() {
209             func->Call(JSRef<JSObject>());
210             return;
211         };
212     }
213     auto onEnterActionArea = deleteAreaObj->GetProperty("onEnterActionArea");
214     std::function<void()> onEnterActionAreaCallback;
215     if (onEnterActionArea->IsFunction()) {
216         onEnterActionAreaCallback = [execCtx = context,
217                                         func = JSRef<JSFunc>::Cast(onEnterActionArea)]() {
218             func->Call(JSRef<JSObject>());
219             return;
220         };
221     }
222     auto onExitActionArea = deleteAreaObj->GetProperty("onExitActionArea");
223     std::function<void()> onExitActionAreaCallback;
224     if (onExitActionArea->IsFunction()) {
225         onExitActionAreaCallback = [execCtx = context,
226                                        func = JSRef<JSFunc>::Cast(onExitActionArea)]() {
227             func->Call(JSRef<JSObject>());
228             return;
229         };
230     }
231     auto actionAreaDistance = deleteAreaObj->GetProperty("actionAreaDistance");
232     CalcDimension length;
233     if (!ParseJsDimensionVp(actionAreaDistance, length)) {
234         auto listItemTheme = GetTheme<ListItemTheme>();
235         length = listItemTheme->GetDeleteDistance();
236     }
237     auto onStateChange = deleteAreaObj->GetProperty("onStateChange");
238     std::function<void(SwipeActionState state)> onStateChangeCallback;
239     if (onStateChange->IsFunction()) {
240         onStateChangeCallback = [execCtx = context,
241                                     func = JSRef<JSFunc>::Cast(onStateChange)](SwipeActionState state) {
242             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
243             auto params = ConvertToJSValues(state);
244             func->Call(JSRef<JSObject>(), params.size(), params.data());
245             return;
246         };
247     }
248 
249     ListItemModel::GetInstance()->SetDeleteArea(std::move(builderAction), std::move(onActionCallback),
250         std::move(onEnterActionAreaCallback), std::move(onExitActionAreaCallback), std::move(onStateChangeCallback),
251         length, isStartArea);
252 }
253 
SetSwiperAction(const JSCallbackInfo & args)254 void JSListItem::SetSwiperAction(const JSCallbackInfo& args)
255 {
256     if (!args[0]->IsObject()) {
257         return;
258     }
259     JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
260     ParseSwiperAction(obj, args.GetExecutionContext());
261 }
262 
ParseSwiperAction(const JSRef<JSObject> & obj,const JsiExecutionContext & context)263 void JSListItem::ParseSwiperAction(const JSRef<JSObject>& obj, const JsiExecutionContext& context)
264 {
265     std::function<void()> startAction;
266     auto startObject = obj->GetProperty("start");
267     if (startObject->IsObject()) {
268         if (startObject->IsFunction()) {
269             auto builderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(startObject));
270             startAction = [builderFunc]() { builderFunc->Execute(); };
271             ListItemModel::GetInstance()->SetDeleteArea(
272                 std::move(startAction), nullptr, nullptr, nullptr, nullptr, Dimension(0, DimensionUnit::VP), true);
273         } else {
274             JsParseDeleteArea(context, startObject, true);
275         }
276     } else {
277         ListItemModel::GetInstance()->SetDeleteArea(
278             nullptr, nullptr, nullptr, nullptr, nullptr, Dimension(0, DimensionUnit::VP), true);
279     }
280 
281     std::function<void()> endAction;
282     auto endObject = obj->GetProperty("end");
283     if (endObject->IsObject()) {
284         if (endObject->IsFunction()) {
285             auto builderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(endObject));
286             endAction = [builderFunc]() { builderFunc->Execute(); };
287             ListItemModel::GetInstance()->SetDeleteArea(
288                 std::move(endAction), nullptr, nullptr, nullptr, nullptr, Dimension(0, DimensionUnit::VP), false);
289         } else {
290             JsParseDeleteArea(context, endObject, false);
291         }
292     } else {
293         ListItemModel::GetInstance()->SetDeleteArea(
294             nullptr, nullptr, nullptr, nullptr, nullptr, Dimension(0, DimensionUnit::VP), false);
295     }
296 
297     auto edgeEffect = obj->GetProperty("edgeEffect");
298     V2::SwipeEdgeEffect swipeEdgeEffect = V2::SwipeEdgeEffect::Spring;
299     if (edgeEffect->IsNumber()) {
300         swipeEdgeEffect = static_cast<V2::SwipeEdgeEffect>(edgeEffect->ToNumber<int32_t>());
301     }
302 
303     auto onOffsetChangeFunc = obj->GetProperty("onOffsetChange");
304     std::function<void(int32_t offset)> onOffsetChangeCallback;
305     if (onOffsetChangeFunc->IsFunction()) {
306         onOffsetChangeCallback = [execCtx = context,
307                                      func = JSRef<JSFunc>::Cast(onOffsetChangeFunc)](int32_t offset) {
308             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
309             auto params = ConvertToJSValues(offset);
310             func->Call(JSRef<JSObject>(), params.size(), params.data());
311             return;
312         };
313     }
314 
315     // use SetDeleteArea to update builder function
316     ListItemModel::GetInstance()->SetSwiperAction(nullptr, nullptr, std::move(onOffsetChangeCallback), swipeEdgeEffect);
317 }
318 
SelectCallback(const JSCallbackInfo & args)319 void JSListItem::SelectCallback(const JSCallbackInfo& args)
320 {
321     if (!args[0]->IsFunction()) {
322         return;
323     }
324 
325     RefPtr<JsMouseFunction> jsOnSelectFunc = AceType::MakeRefPtr<JsMouseFunction>(JSRef<JSFunc>::Cast(args[0]));
326     auto onSelect = [execCtx = args.GetExecutionContext(), func = std::move(jsOnSelectFunc)](bool isSelected) {
327         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
328         func->SelectExecute(isSelected);
329     };
330     ListItemModel::GetInstance()->SetSelectCallback(std::move(onSelect));
331 }
332 
JsBorderRadius(const JSCallbackInfo & info)333 void JSListItem::JsBorderRadius(const JSCallbackInfo& info)
334 {
335     JSViewAbstract::JsBorderRadius(info);
336     CalcDimension borderRadius;
337     if (!JSViewAbstract::ParseJsDimensionVp(info[0], borderRadius)) {
338         return;
339     }
340     ListItemModel::GetInstance()->SetBorderRadius(borderRadius);
341 }
342 
JsOnDragStart(const JSCallbackInfo & info)343 void JSListItem::JsOnDragStart(const JSCallbackInfo& info)
344 {
345     if (!info[0]->IsFunction()) {
346         return;
347     }
348     RefPtr<JsDragFunction> jsOnDragStartFunc = AceType::MakeRefPtr<JsDragFunction>(JSRef<JSFunc>::Cast(info[0]));
349     WeakPtr<NG::FrameNode> frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
350     auto onDragStart = [execCtx = info.GetExecutionContext(), func = std::move(jsOnDragStartFunc),
351                            targetNode = frameNode](
352                            const RefPtr<DragEvent>& info, const std::string& extraParams) -> NG::DragDropBaseInfo {
353         NG::DragDropBaseInfo itemInfo;
354         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, itemInfo);
355         PipelineContext::SetCallBackNode(targetNode);
356         auto ret = func->Execute(info, extraParams);
357         if (!ret->IsObject()) {
358             return itemInfo;
359         }
360         auto node = ParseDragNode(ret);
361         if (node) {
362             itemInfo.node = node;
363             return itemInfo;
364         }
365 
366         auto builderObj = JSRef<JSObject>::Cast(ret);
367 #if defined(PIXEL_MAP_SUPPORTED)
368         auto pixmap = builderObj->GetProperty("pixelMap");
369         itemInfo.pixelMap = CreatePixelMapFromNapiValue(pixmap);
370 #endif
371         auto extraInfo = builderObj->GetProperty("extraInfo");
372         ParseJsString(extraInfo, itemInfo.extraInfo);
373         node = ParseDragNode(builderObj->GetProperty("builder"));
374         itemInfo.node = node;
375         return itemInfo;
376     };
377 #ifdef NG_BUILD
378     ViewAbstractModel::GetInstance()->SetOnDragStart(std::move(onDragStart));
379 #else
380     if (Container::IsCurrentUseNewPipeline()) {
381         ViewAbstractModel::GetInstance()->SetOnDragStart(std::move(onDragStart));
382     } else {
383         ListItemModel::GetInstance()->SetOnDragStart(std::move(onDragStart));
384     }
385 #endif
386 }
387 
JSBind(BindingTarget globalObj)388 void JSListItem::JSBind(BindingTarget globalObj)
389 {
390     JSClass<JSListItem>::Declare("ListItem");
391     JSClass<JSListItem>::StaticMethod("createInternal", &JSListItem::Create);
392     JSClass<JSListItem>::StaticMethod("create", &JSListItem::Create);
393     JSClass<JSListItem>::StaticMethod("pop", &JSListItem::Pop);
394 
395     JSClass<JSListItem>::StaticMethod("sticky", &JSListItem::SetSticky);
396     JSClass<JSListItem>::StaticMethod("editable", &JSListItem::SetEditable);
397     JSClass<JSListItem>::StaticMethod("selectable", &JSListItem::SetSelectable);
398     JSClass<JSListItem>::StaticMethod("onSelect", &JSListItem::SelectCallback);
399     JSClass<JSListItem>::StaticMethod("borderRadius", &JSListItem::JsBorderRadius);
400     JSClass<JSListItem>::StaticMethod("swipeAction", &JSListItem::SetSwiperAction);
401     JSClass<JSListItem>::StaticMethod("selected", &JSListItem::SetSelected);
402 
403     JSClass<JSListItem>::StaticMethod("onClick", &JSInteractableView::JsOnClick);
404     JSClass<JSListItem>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
405     JSClass<JSListItem>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
406     JSClass<JSListItem>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
407     JSClass<JSListItem>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
408     JSClass<JSListItem>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
409     JSClass<JSListItem>::StaticMethod("onHover", &JSInteractableView::JsOnHover);
410     JSClass<JSListItem>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
411     JSClass<JSListItem>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
412     JSClass<JSListItem>::StaticMethod("remoteMessage", &JSInteractableView::JsCommonRemoteMessage);
413     JSClass<JSListItem>::StaticMethod("onDragStart", &JSListItem::JsOnDragStart);
414 
415     JSClass<JSListItem>::InheritAndBind<JSContainerBase>(globalObj);
416 }
417 
418 } // namespace OHOS::Ace::Framework
419