1 /*
2  * Copyright (c) 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_group.h"
17 
18 #include "bridge/declarative_frontend/jsview/js_list_item.h"
19 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
20 #include "bridge/declarative_frontend/jsview/models/list_item_group_model_impl.h"
21 #include "bridge/declarative_frontend/view_stack_processor.h"
22 #include "core/components_v2/list/list_item_group_component.h"
23 #include "core/components_ng/pattern/list/list_item_group_model.h"
24 #include "core/components_ng/pattern/list/list_item_group_model_ng.h"
25 
26 namespace OHOS::Ace {
27 
28 std::unique_ptr<ListItemGroupModel> ListItemGroupModel::instance_ = nullptr;
29 std::mutex ListItemGroupModel::mutex_;
30 
GetInstance()31 ListItemGroupModel* ListItemGroupModel::GetInstance()
32 {
33     if (!instance_) {
34         std::lock_guard<std::mutex> lock(mutex_);
35         if (!instance_) {
36 #ifdef NG_BUILD
37             instance_.reset(new NG::ListItemGroupModelNG());
38 #else
39             if (Container::IsCurrentUseNewPipeline()) {
40                 instance_.reset(new NG::ListItemGroupModelNG());
41             } else {
42                 instance_.reset(new Framework::ListItemGroupModelImpl());
43             }
44 #endif
45         }
46     }
47     return instance_.get();
48 }
49 
50 } // namespace OHOS::Ace
51 namespace OHOS::Ace::Framework {
52 
53 namespace {
ParseChange(const JSRef<JSObject> & changeObject,const float defaultSize,int32_t & start,int32_t & deleteCount,std::vector<float> & newChildrenSize)54 bool ParseChange(const JSRef<JSObject>& changeObject, const float defaultSize, int32_t& start,
55     int32_t& deleteCount, std::vector<float>& newChildrenSize)
56 {
57     if (!JSViewAbstract::ParseJsInteger<int32_t>(changeObject->GetProperty("start"), start) || start < 0) {
58         return false;
59     }
60     if (!(changeObject->HasProperty("deleteCount"))) {
61         // If only input one parameter, set -1 to deleteCount for deleting elements after index 'start' in the array.
62         deleteCount = -1;
63     } else if (!JSViewAbstract::ParseJsInteger<int32_t>(changeObject->GetProperty("deleteCount"), deleteCount) ||
64         deleteCount < 0) {
65         deleteCount = 0;
66     }
67     auto childrenSizeValue = changeObject->GetProperty("childrenSize");
68     if (childrenSizeValue->IsArray()) {
69         auto childrenSize = JSRef<JSArray>::Cast(childrenSizeValue);
70         auto childrenSizeCount = childrenSize->Length();
71         for (size_t j = 0; j < childrenSizeCount; ++j) {
72             // -1.0: represent default size.
73             double childSize = -1.0;
74             if (!JSViewAbstract::ParseJsDouble(childrenSize->GetValueAt(j), childSize) || Negative(childSize)) {
75                 // -1.0f: represent default size.
76                 newChildrenSize.emplace_back(-1.0f);
77             } else {
78                 newChildrenSize.emplace_back(Dimension(childSize, DimensionUnit::VP).ConvertToPx());
79             }
80         }
81     }
82     return true;
83 }
84 
SyncChildrenSize(const JSRef<JSObject> & childrenSizeObj,RefPtr<NG::ListChildrenMainSize> childrenSize)85 void SyncChildrenSize(const JSRef<JSObject>& childrenSizeObj, RefPtr<NG::ListChildrenMainSize> childrenSize)
86 {
87     auto sizeArray = childrenSizeObj->GetProperty("sizeArray");
88     if (!sizeArray->IsArray()) {
89         return;
90     }
91     childrenSize->ResizeChildrenSize(0);
92     auto childrenSizeJSArray = JSRef<JSArray>::Cast(sizeArray);
93     auto length = childrenSizeJSArray->Length();
94     for (size_t i = 0; i < length; ++i) {
95         // -1.0: represent default size.
96         double childSize = -1.0;
97         if (!JSViewAbstract::ParseJsDouble(childrenSizeJSArray->GetValueAt(i), childSize) || Negative(childSize)) {
98             // -1.0f: represent default size.
99             childrenSize->SyncChildrenSize(-1.0f);
100         } else {
101             childrenSize->SyncChildrenSize(Dimension(childSize, DimensionUnit::VP).ConvertToPx());
102         }
103     }
104     childrenSize->SyncChildrenSizeOver();
105 }
106 } // namespace
107 
SetChildrenMainSize(const JSCallbackInfo & args)108 void JSListItemGroup::SetChildrenMainSize(const JSCallbackInfo& args)
109 {
110     if (args.Length() != 1 || !(args[0]->IsObject())) {
111         return;
112     }
113     SetChildrenMainSize(JSRef<JSObject>::Cast(args[0]));
114 }
115 
SetChildrenMainSize(const JSRef<JSObject> & childrenSizeObj)116 void JSListItemGroup::SetChildrenMainSize(const JSRef<JSObject>& childrenSizeObj)
117 {
118     double defaultSize = 0.0f;
119     if (!ParseJsDouble(childrenSizeObj->GetProperty("defaultMainSize"), defaultSize) || !NonNegative(defaultSize)) {
120         LOGW("JSListItemGroup input parameter defaultSize check failed.");
121         return;
122     }
123     auto listChildrenMainSize = ListItemGroupModel::GetInstance()->GetOrCreateListChildrenMainSize();
124     CHECK_NULL_VOID(listChildrenMainSize);
125     listChildrenMainSize->UpdateDefaultSize(Dimension(defaultSize, DimensionUnit::VP).ConvertToPx());
126 
127     if (listChildrenMainSize->NeedSync()) {
128         SyncChildrenSize(childrenSizeObj, listChildrenMainSize);
129     } else {
130         auto changes = childrenSizeObj->GetProperty("changeArray");
131         if (!changes->IsArray()) {
132             return;
133         }
134         auto changeArray = JSRef<JSArray>::Cast(changes);
135         auto length = changeArray->Length();
136         for (size_t i = 0; i < length; ++i) {
137             auto change = changeArray->GetValueAt(i);
138             if (!change->IsObject()) {
139                 continue;
140             }
141             auto changeObject = JSRef<JSObject>::Cast(change);
142             int32_t start = 0;
143             int32_t deleteCount = 0;
144             std::vector<float> newChildrenSize;
145             if (!ParseChange(changeObject, defaultSize, start, deleteCount, newChildrenSize)) {
146                 SyncChildrenSize(childrenSizeObj, listChildrenMainSize);
147                 break;
148             }
149             listChildrenMainSize->ChangeData(start, deleteCount, newChildrenSize);
150         }
151     }
152     auto clearFunc = childrenSizeObj->GetProperty("clearChanges");
153     if (!clearFunc->IsFunction()) {
154         return;
155     }
156     auto func = JSRef<JSFunc>::Cast(clearFunc);
157     JSRef<JSVal>::Cast(func->Call(childrenSizeObj));
158 }
159 
Create(const JSCallbackInfo & args)160 void JSListItemGroup::Create(const JSCallbackInfo& args)
161 {
162     auto listItemGroupStyle = GetListItemGroupStyle(args);
163     ListItemGroupModel::GetInstance()->Create(listItemGroupStyle);
164     if (args.Length() < 1 || !args[0]->IsObject()) {
165         NG::ListItemGroupModelNG::GetInstance()->RemoveHeader();
166         NG::ListItemGroupModelNG::GetInstance()->RemoveFooter();
167         args.ReturnSelf();
168         return;
169     }
170     JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
171 
172     Dimension space;
173     if (ConvertFromJSValue(obj->GetProperty("space"), space) && space.IsNonNegative()) {
174         ListItemGroupModel::GetInstance()->SetSpace(space);
175     }
176 
177     if (obj->HasProperty("headerComponent")) {
178         auto headerComponentObject = obj->GetProperty("headerComponent");
179         if (!ParseHeaderAndFooterContent(headerComponentObject, true)) {
180             NG::ListItemGroupModelNG::GetInstance()->RemoveHeader();
181         }
182     } else {
183         if (!SetHeaderBuilder(obj)) {
184             NG::ListItemGroupModelNG::GetInstance()->RemoveHeader();
185         }
186     }
187 
188     if (obj->HasProperty("footerComponent")) {
189         auto footerComponentObject = obj->GetProperty("footerComponent");
190         if (!ParseHeaderAndFooterContent(footerComponentObject, false)) {
191             NG::ListItemGroupModelNG::GetInstance()->RemoveFooter();
192         }
193     } else {
194         if (!SetFooterBuilder(obj)) {
195             NG::ListItemGroupModelNG::GetInstance()->RemoveFooter();
196         }
197     }
198 
199     args.ReturnSelf();
200 }
201 
SetDivider(const JSCallbackInfo & args)202 void JSListItemGroup::SetDivider(const JSCallbackInfo& args)
203 {
204     V2::ItemDivider divider;
205     if (args.Length() >= 1 && args[0]->IsObject()) {
206         JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
207         if (!ConvertFromJSValue(obj->GetProperty("strokeWidth"), divider.strokeWidth)) {
208             LOGW("Invalid strokeWidth of divider");
209             divider.strokeWidth.Reset();
210         }
211         if (!ConvertFromJSValue(obj->GetProperty("color"), divider.color)) {
212             // Failed to get color from param, using default color defined in theme
213             RefPtr<ListTheme> listTheme = GetTheme<ListTheme>();
214             if (listTheme) {
215                 divider.color = listTheme->GetDividerColor();
216             }
217         }
218         ConvertFromJSValue(obj->GetProperty("startMargin"), divider.startMargin);
219         ConvertFromJSValue(obj->GetProperty("endMargin"), divider.endMargin);
220     }
221     ListItemGroupModel::GetInstance()->SetDivider(divider);
222     args.ReturnSelf();
223 }
224 
SetAspectRatio(const JSCallbackInfo & args)225 void JSListItemGroup::SetAspectRatio(const JSCallbackInfo& args) {}
226 
ParseHeaderAndFooterContent(const JSRef<JSVal> & contentParam,bool isHeader)227 bool JSListItemGroup::ParseHeaderAndFooterContent(const JSRef<JSVal>& contentParam, bool isHeader)
228 {
229     if (!contentParam->IsObject()) {
230         return false;
231     }
232     JSRef<JSObject> contentObject = JSRef<JSObject>::Cast(contentParam);
233     JSRef<JSVal> builderNodeParam = contentObject->GetProperty("builderNode_");
234     if (!builderNodeParam->IsObject()) {
235         return false;
236     }
237     JSRef<JSObject> builderNodeObject = JSRef<JSObject>::Cast(builderNodeParam);
238     JSRef<JSVal> nodeptr = builderNodeObject->GetProperty("nodePtr_");
239     if (nodeptr.IsEmpty()) {
240         return false;
241     }
242     const auto* vm = nodeptr->GetEcmaVM();
243     auto* node = nodeptr->GetLocalHandle()->ToNativePointer(vm)->Value();
244     auto* frameNode = reinterpret_cast<NG::FrameNode*>(node);
245     CHECK_NULL_RETURN(frameNode, false);
246     RefPtr<NG::FrameNode> refPtrFrameNode = AceType::Claim(frameNode);
247     if (isHeader) {
248         NG::ListItemGroupModelNG::GetInstance()->SetHeaderComponent(refPtrFrameNode);
249     } else {
250         NG::ListItemGroupModelNG::GetInstance()->SetFooterComponent(refPtrFrameNode);
251     }
252     return true;
253 }
254 
SetHeaderBuilder(const JSRef<JSObject> & obj)255 bool JSListItemGroup::SetHeaderBuilder(const JSRef<JSObject>& obj)
256 {
257     auto headerObject = obj->GetProperty("header");
258     if (headerObject->IsFunction()) {
259         auto builderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(headerObject));
260         auto headerAction = [builderFunc]() { builderFunc->Execute(); };
261         ListItemGroupModel::GetInstance()->SetHeader(headerAction);
262         return true;
263     }
264     return false;
265 }
266 
SetFooterBuilder(const JSRef<JSObject> & obj)267 bool JSListItemGroup::SetFooterBuilder(const JSRef<JSObject>& obj)
268 {
269     auto footerObject = obj->GetProperty("footer");
270     if (footerObject->IsFunction()) {
271         auto builderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(footerObject));
272         auto footerAction = [builderFunc]() { builderFunc->Execute(); };
273         ListItemGroupModel::GetInstance()->SetFooter(footerAction);
274         return true;
275     }
276     return false;
277 }
278 
GetListItemGroupStyle(const JSCallbackInfo & args)279 V2::ListItemGroupStyle JSListItemGroup::GetListItemGroupStyle(const JSCallbackInfo& args)
280 {
281     V2::ListItemGroupStyle listItemGroupStyle = V2::ListItemGroupStyle::NONE;
282     if (args.Length() >= 1 && args[0]->IsObject()) {
283         JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
284         auto styleObject = obj->GetProperty("style");
285         listItemGroupStyle = styleObject->IsNumber()
286                                  ? static_cast<V2::ListItemGroupStyle>(styleObject->ToNumber<int32_t>())
287                                  : V2::ListItemGroupStyle::NONE;
288     }
289     return listItemGroupStyle;
290 }
291 
JSBind(BindingTarget globalObj)292 void JSListItemGroup::JSBind(BindingTarget globalObj)
293 {
294     JSClass<JSListItemGroup>::Declare("ListItemGroup");
295     JSClass<JSListItemGroup>::StaticMethod("create", &JSListItemGroup::Create);
296 
297     JSClass<JSListItemGroup>::StaticMethod("aspectRatio", &JSListItemGroup::SetAspectRatio);
298     JSClass<JSListItemGroup>::StaticMethod("childrenMainSize", &JSListItemGroup::SetChildrenMainSize);
299     JSClass<JSListItemGroup>::StaticMethod("divider", &JSListItemGroup::SetDivider);
300     JSClass<JSListItemGroup>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
301     JSClass<JSListItemGroup>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
302     JSClass<JSListItemGroup>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
303     JSClass<JSListItemGroup>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
304     JSClass<JSListItemGroup>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
305 
306     JSClass<JSListItemGroup>::Inherit<JSInteractableView>();
307     JSClass<JSListItemGroup>::InheritAndBind<JSContainerBase>(globalObj);
308 }
309 
310 } // namespace OHOS::Ace::Framework
311