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,
215 ScrollSource source, bool isAtTop, bool isAtBottom) {
216 HandleOnDidScrollEvent(jsScrollerWeak, dimension, state, isAtTop, isAtBottom);
217 };
218 observer.onDidScrollEvent = std::move(didScrollEvent);
219
220 return observer;
221 }
222
HandleBindTabsToScrollable(const JSRef<JSObject> & jsTabsControllerVal,const JSRef<JSObject> & jsScrollerVal,const std::optional<JSRef<JSObject>> & parentJsScrollerVal)223 void HandleBindTabsToScrollable(const JSRef<JSObject>& jsTabsControllerVal, const JSRef<JSObject>& jsScrollerVal,
224 const std::optional<JSRef<JSObject>>& parentJsScrollerVal)
225 {
226 auto* jsTabsController = jsTabsControllerVal->Unwrap<JSTabsController>();
227 CHECK_NULL_VOID(jsTabsController);
228 auto tabsController = jsTabsController->GetTabsController();
229 CHECK_NULL_VOID(tabsController);
230 auto tabsControllerWeak = AceType::WeakClaim(AceType::RawPtr(tabsController));
231 auto* jsScroller = jsScrollerVal->Unwrap<JSScroller>();
232 CHECK_NULL_VOID(jsScroller);
233 auto jsScrollerWeak = AceType::WeakClaim(jsScroller);
234
235 ScrollInfoMap scrollInfoMap;
236 auto bindInfoIter = bindInfoMap_.find(tabsControllerWeak);
237 if (bindInfoIter != bindInfoMap_.end()) {
238 scrollInfoMap = bindInfoIter->second;
239 if (scrollInfoMap.find(jsScrollerWeak) != scrollInfoMap.end()) {
240 return;
241 }
242 }
243 auto observer = CreateObserver(jsScrollerWeak);
244 jsScroller->SetObserver(observer);
245 ScrollInfo scrollInfo;
246 if (parentJsScrollerVal.has_value()) {
247 auto* parentJsScroller = parentJsScrollerVal.value()->Unwrap<JSScroller>();
248 if (parentJsScroller) {
249 scrollInfo.parentScroller = AceType::WeakClaim(parentJsScroller);
250 }
251 }
252 scrollInfoMap[jsScrollerWeak] = scrollInfo;
253 bindInfoMap_[tabsControllerWeak] = scrollInfoMap;
254 }
255
HandleUnbindTabsFromScrollable(const JSRef<JSObject> & jsTabsControllerVal,const JSRef<JSObject> & jsScrollerVal,const std::optional<JSRef<JSObject>> & parentJsScrollerVal)256 void HandleUnbindTabsFromScrollable(const JSRef<JSObject>& jsTabsControllerVal, const JSRef<JSObject>& jsScrollerVal,
257 const std::optional<JSRef<JSObject>>& parentJsScrollerVal)
258 {
259 auto* jsTabsController = jsTabsControllerVal->Unwrap<JSTabsController>();
260 CHECK_NULL_VOID(jsTabsController);
261 auto tabsController = jsTabsController->GetTabsController();
262 CHECK_NULL_VOID(tabsController);
263 auto tabsControllerWeak = AceType::WeakClaim(AceType::RawPtr(tabsController));
264 auto* jsScroller = jsScrollerVal->Unwrap<JSScroller>();
265 CHECK_NULL_VOID(jsScroller);
266 auto jsScrollerWeak = AceType::WeakClaim(jsScroller);
267
268 auto bindInfoIter = bindInfoMap_.find(tabsControllerWeak);
269 if (bindInfoIter == bindInfoMap_.end()) {
270 return;
271 }
272 auto& scrollInfoMap = bindInfoIter->second;
273 if (scrollInfoMap.find(jsScrollerWeak) != scrollInfoMap.end()) {
274 scrollInfoMap.erase(jsScrollerWeak);
275 if (scrollInfoMap.empty()) {
276 bindInfoMap_.erase(tabsControllerWeak);
277 }
278 tabsController->StartShowTabBar();
279 }
280
281 if (parentJsScrollerVal.has_value()) {
282 // unbind nested scrollable component.
283 auto* parentJsScroller = parentJsScrollerVal.value()->Unwrap<JSScroller>();
284 CHECK_NULL_VOID(parentJsScroller);
285 auto parentJsScrollerWeak = AceType::WeakClaim(parentJsScroller);
286
287 auto needRemoveParent = true;
288 for (const auto& scrollInfo : scrollInfoMap) {
289 if (scrollInfo.second.parentScroller.has_value() &&
290 scrollInfo.second.parentScroller.value() == parentJsScrollerWeak) {
291 needRemoveParent = false;
292 }
293 }
294 if (needRemoveParent) {
295 scrollInfoMap.erase(parentJsScrollerWeak);
296 if (scrollInfoMap.empty()) {
297 bindInfoMap_.erase(tabsControllerWeak);
298 }
299 tabsController->StartShowTabBar();
300 }
301 }
302 }
303
304 } // namespace
305
BindTabsToScrollable(const JSCallbackInfo & info)306 void JSTabsFeature::BindTabsToScrollable(const JSCallbackInfo& info)
307 {
308 if (info.Length() <= INDEX_ONE) {
309 return;
310 }
311 if (!info[INDEX_ZERO]->IsObject() || !info[INDEX_ONE]->IsObject()) {
312 return;
313 }
314
315 HandleBindTabsToScrollable(info[INDEX_ZERO], info[INDEX_ONE], std::nullopt);
316 }
317
UnbindTabsFromScrollable(const JSCallbackInfo & info)318 void JSTabsFeature::UnbindTabsFromScrollable(const JSCallbackInfo& info)
319 {
320 if (info.Length() <= INDEX_ONE) {
321 return;
322 }
323 if (!info[INDEX_ZERO]->IsObject() || !info[INDEX_ONE]->IsObject()) {
324 return;
325 }
326
327 HandleUnbindTabsFromScrollable(info[INDEX_ZERO], info[INDEX_ONE], std::nullopt);
328 }
329
BindTabsToNestedScrollable(const JSCallbackInfo & info)330 void JSTabsFeature::BindTabsToNestedScrollable(const JSCallbackInfo& info)
331 {
332 if (info.Length() <= INDEX_TWO) {
333 return;
334 }
335 if (!info[INDEX_ZERO]->IsObject() || !info[INDEX_ONE]->IsObject() || !info[INDEX_TWO]->IsObject()) {
336 return;
337 }
338
339 HandleBindTabsToScrollable(info[INDEX_ZERO], info[INDEX_ONE], std::nullopt);
340 HandleBindTabsToScrollable(info[INDEX_ZERO], info[INDEX_TWO], info[INDEX_ONE]);
341 }
342
UnbindTabsFromNestedScrollable(const JSCallbackInfo & info)343 void JSTabsFeature::UnbindTabsFromNestedScrollable(const JSCallbackInfo& info)
344 {
345 if (info.Length() <= INDEX_TWO) {
346 return;
347 }
348 if (!info[INDEX_ZERO]->IsObject() || !info[INDEX_ONE]->IsObject() || !info[INDEX_TWO]->IsObject()) {
349 return;
350 }
351
352 HandleUnbindTabsFromScrollable(info[INDEX_ZERO], info[INDEX_TWO], info[INDEX_ONE]);
353 }
354
355 } // namespace OHOS::Ace::Framework
356