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