• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 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_tabs_feature.h"
17 
18 #include "bridge/declarative_frontend/jsview/js_scroller.h"
19 #include "bridge/declarative_frontend/jsview/js_tabs_controller.h"
20 
21 namespace OHOS::Ace::Framework {
22 namespace {
23 struct ScrollInfo {
24     bool isTouching = false;
25     bool isScrolling = false;
26     bool isAtTop = false;
27     bool isAtBottom = false;
28     std::optional<WeakPtr<JSScroller>> parentScroller;
29 };
30 
31 using ScrollInfoMap = std::map<WeakPtr<JSScroller>, ScrollInfo>;
32 using BindInfoMap = std::map<WeakPtr<JSTabsController>, ScrollInfoMap>;
33 
34 const auto INDEX_ZERO = 0;
35 const auto INDEX_ONE = 1;
36 const auto INDEX_TWO = 2;
37 const auto SHOW_TAB_BAR_DELAY = 2000;
38 const auto SCROLL_RATIO = 2.0f;
39 
40 BindInfoMap bindInfoMap_;
41 
HandleOnTouchEvent(WeakPtr<JSScroller> jsScrollerWeak,const TouchEventInfo & info)42 void HandleOnTouchEvent(WeakPtr<JSScroller> jsScrollerWeak, const TouchEventInfo& info)
43 {
44     auto touches = info.GetTouches();
45     if (touches.empty()) {
46         return;
47     }
48     auto touchType = touches.front().GetTouchType();
49     if (touchType != TouchType::DOWN && touchType != TouchType::UP && touchType != TouchType::CANCEL) {
50         return;
51     }
52 
53     for (auto& bindInfo : bindInfoMap_) {
54         auto& scrollInfoMap = bindInfo.second;
55         auto scrollInfoIter = scrollInfoMap.find(jsScrollerWeak);
56         if (scrollInfoIter == scrollInfoMap.end()) {
57             continue;
58         }
59         auto& scrollInfo = scrollInfoIter->second;
60         scrollInfo.isTouching = touchType == TouchType::DOWN;
61         auto jsTabsController = bindInfo.first.Upgrade();
62         if (!jsTabsController) {
63             continue;
64         }
65         auto tabsController = jsTabsController->GetTabsController().Upgrade();
66         if (touchType == TouchType::DOWN) {
67             if (!scrollInfo.isAtTop && !scrollInfo.isAtBottom && tabsController) {
68                 tabsController->CancelShowTabBar();
69             }
70         } else if (touchType == TouchType::UP || touchType == TouchType::CANCEL) {
71             if (!scrollInfo.isScrolling && tabsController) {
72                 tabsController->StartShowTabBar(SHOW_TAB_BAR_DELAY);
73             }
74         }
75     }
76 }
77 
HandleOnReachEvent(WeakPtr<JSScroller> jsScrollerWeak,bool isTopEvent)78 void HandleOnReachEvent(WeakPtr<JSScroller> jsScrollerWeak, bool isTopEvent)
79 {
80     for (auto& bindInfo : bindInfoMap_) {
81         auto& scrollInfoMap = bindInfo.second;
82         auto scrollInfoIter = scrollInfoMap.find(jsScrollerWeak);
83         if (scrollInfoIter == scrollInfoMap.end()) {
84             continue;
85         }
86         auto& scrollInfo = scrollInfoIter->second;
87         if (isTopEvent) {
88             scrollInfo.isAtTop = true;
89         } else {
90             scrollInfo.isAtBottom = true;
91         }
92     }
93 }
94 
HandleOnScrollStartEvent(WeakPtr<JSScroller> jsScrollerWeak)95 void HandleOnScrollStartEvent(WeakPtr<JSScroller> jsScrollerWeak)
96 {
97     for (auto& bindInfo : bindInfoMap_) {
98         auto& scrollInfoMap = bindInfo.second;
99         auto scrollInfoIter = scrollInfoMap.find(jsScrollerWeak);
100         if (scrollInfoIter == scrollInfoMap.end()) {
101             continue;
102         }
103         auto& scrollInfo = scrollInfoIter->second;
104         scrollInfo.isScrolling = true;
105         auto jsTabsController = bindInfo.first.Upgrade();
106         if (!jsTabsController) {
107             continue;
108         }
109         auto tabsController = jsTabsController->GetTabsController().Upgrade();
110         if (!scrollInfo.isAtTop && !scrollInfo.isAtBottom && !scrollInfo.isTouching && tabsController) {
111             tabsController->CancelShowTabBar();
112         }
113     }
114 }
115 
HandleOnScrollStopEvent(WeakPtr<JSScroller> jsScrollerWeak)116 void HandleOnScrollStopEvent(WeakPtr<JSScroller> jsScrollerWeak)
117 {
118     for (auto& bindInfo : bindInfoMap_) {
119         auto& scrollInfoMap = bindInfo.second;
120         auto scrollInfoIter = scrollInfoMap.find(jsScrollerWeak);
121         if (scrollInfoIter == scrollInfoMap.end()) {
122             continue;
123         }
124         auto& scrollInfo = scrollInfoIter->second;
125         scrollInfo.isScrolling = false;
126         auto jsTabsController = bindInfo.first.Upgrade();
127         if (!jsTabsController) {
128             continue;
129         }
130         auto tabsController = jsTabsController->GetTabsController().Upgrade();
131         if (!scrollInfo.parentScroller.has_value() && !scrollInfo.isTouching && tabsController) {
132             // start show tab bar when parent scrollable component stop scroll.
133             tabsController->StartShowTabBar(SHOW_TAB_BAR_DELAY);
134         }
135     }
136 }
137 
HandleOnDidScrollEvent(WeakPtr<JSScroller> jsScrollerWeak,Dimension dimension,ScrollSource source,bool isAtTop,bool isAtBottom)138 void HandleOnDidScrollEvent(
139     WeakPtr<JSScroller> jsScrollerWeak, Dimension dimension, ScrollSource source, bool isAtTop, bool isAtBottom)
140 {
141     for (auto& bindInfo : bindInfoMap_) {
142         auto& scrollInfoMap = bindInfo.second;
143         auto scrollInfoIter = scrollInfoMap.find(jsScrollerWeak);
144         if (scrollInfoIter == scrollInfoMap.end()) {
145             continue;
146         }
147         auto& scrollInfo = scrollInfoIter->second;
148         if ((scrollInfo.isAtTop && isAtTop) || (scrollInfo.isAtBottom && isAtBottom)) {
149             continue;
150         }
151         auto jsTabsController = bindInfo.first.Upgrade();
152         if (!jsTabsController) {
153             scrollInfo.isAtTop = isAtTop;
154             scrollInfo.isAtBottom = isAtBottom;
155             continue;
156         }
157         auto tabsController = jsTabsController->GetTabsController().Upgrade();
158         if (tabsController) {
159             auto offset = dimension.ConvertToPx() / SCROLL_RATIO;
160             if (NonPositive(offset) ||
161                 !(source == ScrollSource::SCROLLER || source == ScrollSource::SCROLLER_ANIMATION)) {
162                 tabsController->UpdateTabBarHiddenOffset(offset);
163             }
164 
165             auto isChildReachTop = !scrollInfo.isAtTop && isAtTop;
166             auto isChildReachBottom = !scrollInfo.isAtBottom && isAtBottom;
167             auto isParentAtTop = true;
168             auto isParentAtBottom = true;
169             if (scrollInfo.parentScroller.has_value()) {
170                 auto iter = scrollInfoMap.find(scrollInfo.parentScroller.value());
171                 isParentAtTop = iter == scrollInfoMap.end() || iter->second.isAtTop;
172                 isParentAtBottom = iter == scrollInfoMap.end() || iter->second.isAtBottom;
173             }
174             if ((isChildReachTop && isParentAtTop) || (isChildReachBottom && isParentAtBottom)) {
175                 tabsController->StartShowTabBar();
176             }
177         }
178         scrollInfo.isAtTop = isAtTop;
179         scrollInfo.isAtBottom = isAtBottom;
180     }
181 }
182 
CreateObserver(WeakPtr<JSScroller> jsScrollerWeak)183 ScrollerObserver CreateObserver(WeakPtr<JSScroller> jsScrollerWeak)
184 {
185     ScrollerObserver observer;
186 
187     auto touchEvent = [jsScrollerWeak](const TouchEventInfo& info) {
188         HandleOnTouchEvent(jsScrollerWeak, info);
189     };
190     observer.onTouchEvent = AceType::MakeRefPtr<NG::TouchEventImpl>(std::move(touchEvent));
191 
192     auto reachStartEvent = [jsScrollerWeak]() {
193         HandleOnReachEvent(jsScrollerWeak, true);
194     };
195     observer.onReachStartEvent = std::move(reachStartEvent);
196 
197     auto reachEndEvent = [jsScrollerWeak]() {
198         HandleOnReachEvent(jsScrollerWeak, false);
199     };
200     observer.onReachEndEvent = std::move(reachEndEvent);
201 
202     auto scrollStartEvent = [jsScrollerWeak]() {
203         HandleOnScrollStartEvent(jsScrollerWeak);
204     };
205     observer.onScrollStartEvent = std::move(scrollStartEvent);
206 
207     auto scrollStopEvent = [jsScrollerWeak]() {
208         HandleOnScrollStopEvent(jsScrollerWeak);
209     };
210     observer.onScrollStopEvent = std::move(scrollStopEvent);
211 
212     auto didScrollEvent = [jsScrollerWeak](Dimension dimension, ScrollSource source, bool isAtTop, bool isAtBottom) {
213         HandleOnDidScrollEvent(jsScrollerWeak, dimension, source, isAtTop, isAtBottom);
214     };
215     observer.onDidScrollEvent = std::move(didScrollEvent);
216 
217     return observer;
218 }
219 
HandleOnChangeEvent(WeakPtr<JSTabsController> jsTabsControllerWeak,int32_t index)220 void HandleOnChangeEvent(WeakPtr<JSTabsController> jsTabsControllerWeak, int32_t index)
221 {
222     auto bindInfoIter = bindInfoMap_.find(jsTabsControllerWeak);
223     if (bindInfoIter == bindInfoMap_.end()) {
224         return;
225     }
226     for (const auto& scrollInfo : bindInfoIter->second) {
227         auto jsScroller = scrollInfo.first.Upgrade();
228         if (jsScroller) {
229             auto scroller = jsScroller->GetController().Upgrade();
230             if (scroller) {
231                 scroller->StopAnimate();
232             }
233         }
234     }
235     auto jsTabsController = jsTabsControllerWeak.Upgrade();
236     CHECK_NULL_VOID(jsTabsController);
237     auto tabsController = jsTabsController->GetTabsController().Upgrade();
238     CHECK_NULL_VOID(tabsController);
239     tabsController->StartShowTabBar();
240 }
241 
HandleBindTabsToScrollable(const JSRef<JSObject> & jsTabsControllerVal,const JSRef<JSObject> & jsScrollerVal,const std::optional<JSRef<JSObject>> & parentJsScrollerVal)242 void HandleBindTabsToScrollable(const JSRef<JSObject>& jsTabsControllerVal, const JSRef<JSObject>& jsScrollerVal,
243     const std::optional<JSRef<JSObject>>& parentJsScrollerVal)
244 {
245     auto* jsTabsController = jsTabsControllerVal->Unwrap<JSTabsController>();
246     CHECK_NULL_VOID(jsTabsController);
247     auto jsTabsControllerWeak = AceType::WeakClaim(jsTabsController);
248     auto* jsScroller = jsScrollerVal->Unwrap<JSScroller>();
249     CHECK_NULL_VOID(jsScroller);
250     auto jsScrollerWeak = AceType::WeakClaim(jsScroller);
251 
252     ScrollInfoMap scrollInfoMap;
253     auto bindInfoIter = bindInfoMap_.find(jsTabsControllerWeak);
254     if (bindInfoIter != bindInfoMap_.end()) {
255         scrollInfoMap = bindInfoIter->second;
256         if (scrollInfoMap.find(jsScrollerWeak) != scrollInfoMap.end()) {
257             return;
258         }
259     }
260     jsTabsController->SetOnChangeImpl([jsTabsControllerWeak](int32_t index) {
261         HandleOnChangeEvent(jsTabsControllerWeak, index);
262     });
263     auto observer = CreateObserver(jsScrollerWeak);
264     jsScroller->SetObserver(observer);
265     ScrollInfo scrollInfo;
266     if (parentJsScrollerVal.has_value()) {
267         auto* parentJsScroller = parentJsScrollerVal.value()->Unwrap<JSScroller>();
268         if (parentJsScroller) {
269             scrollInfo.parentScroller = AceType::WeakClaim(parentJsScroller);
270         }
271     }
272     scrollInfoMap[jsScrollerWeak] = scrollInfo;
273     bindInfoMap_[jsTabsControllerWeak] = scrollInfoMap;
274 }
275 
HandleUnbindTabsFromScrollable(const JSRef<JSObject> & jsTabsControllerVal,const JSRef<JSObject> & jsScrollerVal,const std::optional<JSRef<JSObject>> & parentJsScrollerVal)276 void HandleUnbindTabsFromScrollable(const JSRef<JSObject>& jsTabsControllerVal, const JSRef<JSObject>& jsScrollerVal,
277     const std::optional<JSRef<JSObject>>& parentJsScrollerVal)
278 {
279     auto* jsTabsController = jsTabsControllerVal->Unwrap<JSTabsController>();
280     CHECK_NULL_VOID(jsTabsController);
281     auto tabsController = jsTabsController->GetTabsController().Upgrade();
282     auto jsTabsControllerWeak = AceType::WeakClaim(jsTabsController);
283     auto* jsScroller = jsScrollerVal->Unwrap<JSScroller>();
284     CHECK_NULL_VOID(jsScroller);
285     auto jsScrollerWeak = AceType::WeakClaim(jsScroller);
286 
287     auto bindInfoIter = bindInfoMap_.find(jsTabsControllerWeak);
288     if (bindInfoIter == bindInfoMap_.end()) {
289         return;
290     }
291     auto& scrollInfoMap = bindInfoIter->second;
292     if (scrollInfoMap.find(jsScrollerWeak) != scrollInfoMap.end()) {
293         scrollInfoMap.erase(jsScrollerWeak);
294         if (scrollInfoMap.empty()) {
295             bindInfoMap_.erase(jsTabsControllerWeak);
296         }
297         if (tabsController) {
298             tabsController->StartShowTabBar();
299         }
300     }
301 
302     if (parentJsScrollerVal.has_value()) {
303         // unbind nested scrollable component.
304         auto* parentJsScroller = parentJsScrollerVal.value()->Unwrap<JSScroller>();
305         CHECK_NULL_VOID(parentJsScroller);
306         auto parentJsScrollerWeak = AceType::WeakClaim(parentJsScroller);
307 
308         auto needRemoveParent = true;
309         for (const auto& scrollInfo : scrollInfoMap) {
310             if (scrollInfo.second.parentScroller.has_value() &&
311                 scrollInfo.second.parentScroller.value() == parentJsScrollerWeak) {
312                 needRemoveParent = false;
313             }
314         }
315         if (needRemoveParent) {
316             scrollInfoMap.erase(parentJsScrollerWeak);
317             if (scrollInfoMap.empty()) {
318                 bindInfoMap_.erase(jsTabsControllerWeak);
319             }
320             if (tabsController) {
321                 tabsController->StartShowTabBar();
322             }
323         }
324     }
325 }
326 
327 } // namespace
328 
BindTabsToScrollable(const JSCallbackInfo & info)329 void JSTabsFeature::BindTabsToScrollable(const JSCallbackInfo& info)
330 {
331     if (info.Length() <= INDEX_ONE) {
332         return;
333     }
334     if (!info[INDEX_ZERO]->IsObject() || !info[INDEX_ONE]->IsObject()) {
335         return;
336     }
337 
338     HandleBindTabsToScrollable(info[INDEX_ZERO], info[INDEX_ONE], std::nullopt);
339 }
340 
UnbindTabsFromScrollable(const JSCallbackInfo & info)341 void JSTabsFeature::UnbindTabsFromScrollable(const JSCallbackInfo& info)
342 {
343     if (info.Length() <= INDEX_ONE) {
344         return;
345     }
346     if (!info[INDEX_ZERO]->IsObject() || !info[INDEX_ONE]->IsObject()) {
347         return;
348     }
349 
350     HandleUnbindTabsFromScrollable(info[INDEX_ZERO], info[INDEX_ONE], std::nullopt);
351 }
352 
BindTabsToNestedScrollable(const JSCallbackInfo & info)353 void JSTabsFeature::BindTabsToNestedScrollable(const JSCallbackInfo& info)
354 {
355     if (info.Length() <= INDEX_TWO) {
356         return;
357     }
358     if (!info[INDEX_ZERO]->IsObject() || !info[INDEX_ONE]->IsObject() || !info[INDEX_TWO]->IsObject()) {
359         return;
360     }
361 
362     HandleBindTabsToScrollable(info[INDEX_ZERO], info[INDEX_ONE], std::nullopt);
363     HandleBindTabsToScrollable(info[INDEX_ZERO], info[INDEX_TWO], info[INDEX_ONE]);
364 }
365 
UnbindTabsFromNestedScrollable(const JSCallbackInfo & info)366 void JSTabsFeature::UnbindTabsFromNestedScrollable(const JSCallbackInfo& info)
367 {
368     if (info.Length() <= INDEX_TWO) {
369         return;
370     }
371     if (!info[INDEX_ZERO]->IsObject() || !info[INDEX_ONE]->IsObject() || !info[INDEX_TWO]->IsObject()) {
372         return;
373     }
374 
375     HandleUnbindTabsFromScrollable(info[INDEX_ZERO], info[INDEX_TWO], info[INDEX_ONE]);
376 }
377 
378 } // namespace OHOS::Ace::Framework
379