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