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