1 /*
2  * Copyright (c) 2021-2022 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 "core/components/refresh/render_refresh.h"
17 
18 #include "base/i18n/localization.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 constexpr int32_t ANIMATION_DURATION = 350; // Default animation duration
24 constexpr int32_t MAX_ALPHA = 255;
25 constexpr int32_t BASE_YEAR = 1900;
26 constexpr double DEFAULT_TIME_BOX_BOTTOM_SIZE = 8.0; // Default box height for time
27 constexpr double PERCENT = 0.01;                     // Percent
28 constexpr double HALF = 0.5;
29 const char REFRESH_LAST_UPDATED[] = "refresh.last_updated"; // I18n for last updated
30 const char LAST_UPDATE_FORMAT[] = "yyyy/M/d HH:mm";         // Date format for last updated
31 const char PULL_DOWN_START[] = "start";                     // Pull-down event, state is start
32 const char PULL_DOWN_END[] = "end";                         // Pull-down event, state is end
33 
34 } // namespace
35 
RenderRefresh()36 RenderRefresh::RenderRefresh()
37 {
38     loadingComponent_ = AceType::MakeRefPtr<LoadingProgressComponent>();
39     loading_ = AceType::DynamicCast<RenderLoadingProgress>(loadingComponent_->CreateRenderNode());
40     loading_->SetLoadingMode(MODE_DRAG);
41 
42     loadingBackgroundBoxComponent_ = AceType::MakeRefPtr<BoxComponent>();
43     loadingBackgroundBoxComponent_->SetEnableDebugBoundary(true);
44 
45     decoration_ = AceType::MakeRefPtr<Decoration>();
46     loadingBackgroundBox_ = AceType::DynamicCast<RenderBox>(loadingBackgroundBoxComponent_->CreateRenderNode());
47 
48     loadingBoxComponent_ = AceType::MakeRefPtr<BoxComponent>();
49     loadingBoxComponent_->SetFlex(BoxFlex::FLEX_X);
50 
51     loadingBox_ = AceType::DynamicCast<RenderBox>(loadingBoxComponent_->CreateRenderNode());
52 
53     loadingBackgroundBox_->AddChild(loading_);
54     loadingBox_->AddChild(loadingBackgroundBox_);
55 
56     timeBoxComponent_ = AceType::MakeRefPtr<BoxComponent>();
57     timeBoxComponent_->SetFlex(BoxFlex::FLEX_X);
58     timeBox_ = AceType::DynamicCast<RenderBox>(timeBoxComponent_->CreateRenderNode());
59 
60     lastTimeText_ = Localization::GetInstance()->GetEntryLetters(REFRESH_LAST_UPDATED);
61     timeText_ = StringUtils::FormatString(lastTimeText_.c_str(), "");
62     timeComponent_ = AceType::MakeRefPtr<TextComponent>(timeText_);
63 
64     displayComponent_ = AceType::MakeRefPtr<DisplayComponent>();
65 
66     display_ = AceType::DynamicCast<RenderDisplay>(displayComponent_->CreateRenderNode());
67     time_ = AceType::DynamicCast<RenderText>(timeComponent_->CreateRenderNode());
68     display_->AddChild(timeBox_);
69     timeBox_->AddChild(time_);
70 }
71 
Update(const RefPtr<Component> & component)72 void RenderRefresh::Update(const RefPtr<Component>& component)
73 {
74     const RefPtr<RefreshComponent> refresh = AceType::DynamicCast<RefreshComponent>(component);
75     if (!refresh) {
76         LOGW("RefreshComponent is null");
77         return;
78     }
79     if (refresh->GetOnStateChange()) {
80         onStateChange_ = *refresh->GetOnStateChange();
81     }
82     if (refresh->GetOnRefreshing()) {
83         onRefreshing_ = *refresh->GetOnRefreshing();
84     }
85 
86     refreshComponent_ = AceType::WeakClaim(AceType::RawPtr(component));
87     refreshing_ = refresh->IsRefreshing();
88     showLastTime_ = refresh->IsShowLastTime();
89     isUseOffset_ = refresh->IsUseOffset();
90     refreshType_ = refresh->GetRefreshType();
91     progressColor_ = refresh->GetProgressColor();
92     backgroundColor_ = refresh->GetBackgroundColor();
93     frictionRatio_ = refresh->GetFriction() * PERCENT;
94     isRefresh_ = refresh->GetIsRefresh();
95     if (!refresh->GetChangeEvent().IsEmpty()) {
96         changeEvent_ =
97             AceAsyncEvent<void(const std::string&)>::Create(refresh->GetChangeEvent(), context_);
98     }
99 
100     loadingComponent_->SetProgressColor(progressColor_);
101     loadingComponent_->SetDiameter(Dimension(GetLoadingDiameter()));
102 
103     // Set the progress background color
104     decoration_->SetBackgroundColor(backgroundColor_);
105 
106     if (refresh->GetTextDirection() == TextDirection::LTR) {
107         SetTextDirection(TextDirection::LTR);
108         time_->SetTextDirection(TextDirection::LTR);
109     } else {
110         SetTextDirection(TextDirection::RTL);
111         time_->SetTextDirection(TextDirection::RTL);
112     }
113 
114     if (!isInitialized_) {
115         loading_->Attach(GetContext());
116         loadingBackgroundBox_->Attach(GetContext());
117         loadingBox_->Attach(GetContext());
118         AddChild(loadingBox_);
119 
120         timeBox_->Attach(GetContext());
121         display_->Attach(GetContext());
122         time_->Attach(GetContext());
123         timeText_ = StringUtils::FormatString(lastTimeText_.c_str(), GetFormatDateTime().c_str());
124         timeComponent_->SetData(timeText_);
125         timeComponent_->SetTextStyle(refresh->GetTextStyle());
126         time_->Update(timeComponent_);
127         display_->Update(displayComponent_);
128         timeBox_->Update(timeBoxComponent_);
129         AddChild(display_);
130 
131         // Just run on the first time
132         isInitialized_ = true;
133     }
134 
135     loading_->Update(loadingComponent_);
136     loadingBackgroundBox_->Update(loadingBackgroundBoxComponent_);
137     loadingBox_->Update(loadingBoxComponent_);
138 
139     refreshEvent_ = AceAsyncEvent<void(const std::string&)>::Create(refresh->GetRefreshEventId(), context_);
140     pullDownEvent_ = AceAsyncEvent<void(const std::string&)>::Create(refresh->GetPulldownEventId(), context_);
141     CalcLoadingParams(component);
142     Initialize();
143     MarkNeedLayout();
144 }
145 
CalcLoadingParams(const RefPtr<Component> & component)146 void RenderRefresh::CalcLoadingParams(const RefPtr<Component>& component)
147 {
148     auto refresh = AceType::DynamicCast<RefreshComponent>(component);
149     if (refresh == nullptr) {
150         LOGW("RefreshComponent is null");
151         return;
152     }
153     auto context = context_.Upgrade();
154     if (context == nullptr) {
155         LOGW("context is nullptr!");
156         return;
157     }
158     scale_ = context->GetDipScale();
159     triggerLoadingDistance_ = NormalizeToPx(refresh->GetLoadingDistance());
160     triggerShowTimeDistance_ = NormalizeToPx(refresh->GetShowTimeDistance());
161     if (showLastTime_) {
162         timeDistance_ = NormalizeToPx(Dimension(DEFAULT_TIME_BOX_BOTTOM_SIZE, DimensionUnit::VP));
163         triggerRefreshDistance_ = triggerShowTimeDistance_;
164     } else {
165         triggerRefreshDistance_ = NormalizeToPx(refresh->GetRefreshDistance());
166         inspectorOffset_ = refresh->GetRefreshDistance();
167     }
168     loadingDiameter_ = NormalizeToPx(refresh->GetProgressDiameter());
169     maxScrollOffset_ = NormalizeToPx(refresh->GetMaxDistance());
170     indicatorOffset_ = NormalizeToPx(refresh->GetIndicatorOffset());
171     timeOffset_ = NormalizeToPx(refresh->GetTimeOffset());
172     loading_->SetDiameter(refresh->GetProgressDiameter());
173     loading_->SetDragRange(triggerLoadingDistance_, triggerRefreshDistance_);
174     loadingBox_->SetHeight(loadingDiameter_);
175     decoration_->SetBorderRadius(Radius(loadingDiameter_ * HALF));
176     loadingBackgroundBox_->SetBackDecoration(decoration_);
177     loadingBackgroundBox_->SetWidth(loadingDiameter_);
178 }
179 
Initialize()180 void RenderRefresh::Initialize()
181 {
182     LOGI("RenderRefresh Initialize state:%{public}d", refreshing_);
183     if (!dragDetector_) {
184         dragDetector_ = AceType::MakeRefPtr<VerticalDragRecognizer>();
185         dragDetector_->SetOnDragUpdate([weakFresh = AceType::WeakClaim(this)](const DragUpdateInfo& info) {
186             auto refresh = weakFresh.Upgrade();
187             if (refresh) {
188                 refresh->HandleDragUpdate(info.GetMainDelta());
189             }
190         });
191         dragDetector_->SetOnDragEnd([weakFresh = AceType::WeakClaim(this)](const DragEndInfo& info) {
192             auto refresh = weakFresh.Upgrade();
193             if (refresh) {
194                 refresh->HandleDragEnd();
195             }
196         });
197 
198         dragDetector_->SetOnDragCancel([weakFresh = AceType::WeakClaim(this)]() {
199             auto refresh = weakFresh.Upgrade();
200             if (refresh) {
201                 refresh->HandleDragCancel();
202             }
203         });
204     }
205     if (!animator_) {
206         animator_ = CREATE_ANIMATOR(GetContext());
207     }
208     if (!refreshController_) {
209         refreshController_ = AceType::MakeRefPtr<RefreshController>();
210         refreshController_->SetRefresh(AceType::WeakClaim(this));
211     }
212     InitAccessibilityEventListener();
213 }
214 
InitAccessibilityEventListener()215 void RenderRefresh::InitAccessibilityEventListener()
216 {
217     auto refNode = accessibilityNode_.Upgrade();
218     if (!refNode) {
219         return;
220     }
221     refNode->AddSupportAction(AceAction::ACTION_SCROLL_FORWARD);
222     auto weakPtr = AceType::WeakClaim(this);
223     refNode->SetActionScrollForward([weakPtr]() {
224         auto refresh = weakPtr.Upgrade();
225         if (refresh) {
226             refresh->SetRefreshStatus(true);
227             refresh->MarkNeedLayout();
228         }
229         return true;
230     });
231 }
232 
UpdateTouchRect()233 void RenderRefresh::UpdateTouchRect()
234 {
235     touchRect_.SetSize(viewPort_);
236     touchRect_.SetOffset(GetPosition());
237     touchRectList_.emplace_back(touchRect_);
238     SetTouchRectList(touchRectList_);
239 }
240 
HandleDragUpdate(double delta)241 void RenderRefresh::HandleDragUpdate(double delta)
242 {
243     if (isRefresh_) {
244         return;
245     }
246     if (NearZero(delta)) {
247         return;
248     }
249     if (refreshStatus_ == RefreshStatus::REFRESH && delta > 0.0) {
250         return;
251     }
252     Offset deltaOffset(0, delta);
253     if (refreshStatus_ == RefreshStatus::DRAG || refreshStatus_ == RefreshStatus::OVER_DRAG ||
254         refreshStatus_ == RefreshStatus::DONE) {
255         deltaOffset.SetY(GetOffset(delta));
256     }
257     scrollableOffset_ += deltaOffset;
258     scrollableOffset_.SetY(std::clamp(scrollableOffset_.GetY(), 0.0, maxScrollOffset_));
259     MarkNeedLayout();
260 }
261 
HandleDragEnd()262 void RenderRefresh::HandleDragEnd()
263 {
264     if (NearEqual(scrollableOffset_.GetY(), 0.0f)) {
265         ResetStatus();
266         return;
267     }
268     if (refreshStatus_ == RefreshStatus::INACTIVE) {
269         return;
270     }
271     if (refreshStatus_ == RefreshStatus::DRAG || refreshStatus_ == RefreshStatus::DONE) {
272         double start = scrollableOffset_.GetY();
273         double end = 0.0;
274         StartAnimation(start, end, false);
275         return;
276     }
277     if (refreshStatus_ == RefreshStatus::OVER_DRAG) {
278         double start = scrollableOffset_.GetY();
279         double end = triggerRefreshDistance_;
280         loading_->SetLoadingMode(MODE_LOOP);
281         StartAnimation(start, end, false);
282     }
283 }
284 
HandleDragCancel()285 void RenderRefresh::HandleDragCancel()
286 {
287     ResetStatus();
288 }
289 
ResetStatus()290 void RenderRefresh::ResetStatus()
291 {
292     refreshing_ = false;
293     if (changeEvent_) {
294         changeEvent_("false");
295     }
296     refreshStatus_ = RefreshStatus::INACTIVE;
297     scrollableOffset_.Reset();
298     loading_->SetLoadingMode(MODE_DRAG);
299     loading_->SetDragDistance(scrollableOffset_.GetY());
300     MarkNeedLayout();
301 }
302 
UpdateScrollOffset(double value)303 void RenderRefresh::UpdateScrollOffset(double value)
304 {
305     scrollableOffset_.SetY(value);
306     MarkNeedLayout();
307 }
308 
FireRefreshEvent() const309 void RenderRefresh::FireRefreshEvent() const
310 {
311     if (refreshEvent_) {
312         LOGI("RefreshEvent, refreshing = %{public}d.", refreshing_);
313         std::string param =
314             std::string(R"("refresh",{"refreshing":)").append(refreshing_ ? "true" : "false").append("},null");
315         refreshEvent_(param);
316     }
317     if (onRefreshing_) {
318         onRefreshing_();
319     }
320 }
321 
FirePullDownEvent(const std::string & state) const322 void RenderRefresh::FirePullDownEvent(const std::string& state) const
323 {
324     if (pullDownEvent_) {
325         LOGI("PullDown Event, state is %{public}s", state.c_str());
326         std::string param = std::string(R"("pulldown",{"state":")").append(state).append("\"},null");
327         pullDownEvent_(param);
328     }
329 }
330 
StartAnimation(double start,double end,bool isFinished)331 void RenderRefresh::StartAnimation(double start, double end, bool isFinished)
332 {
333     animator_->ClearInterpolators();
334     animator_->ClearStopListeners();
335     translate_ = AceType::MakeRefPtr<CurveAnimation<double>>(start, end, Curves::FRICTION);
336     auto weak = AceType::WeakClaim(this);
337     translate_->AddListener(Animation<double>::ValueCallback([weak](double value) {
338         auto scroll = weak.Upgrade();
339         if (scroll) {
340             scroll->UpdateScrollOffset(value);
341         }
342     }));
343     animator_->SetDuration(ANIMATION_DURATION);
344     animator_->AddInterpolator(translate_);
345     animator_->AddStopListener([weak, isFinished]() {
346         auto refresh = weak.Upgrade();
347         if (refresh) {
348             refresh->HandleStopListener(isFinished);
349         }
350     });
351 
352     animator_->Play();
353 }
354 
HandleStopListener(const bool isFinished)355 void RenderRefresh::HandleStopListener(const bool isFinished)
356 {
357     // Update the last loading time
358     if (isFinished) {
359         timeComponent_->SetData(timeText_);
360         time_->Update(timeComponent_);
361         return;
362     }
363 
364     if (NearEqual(scrollableOffset_.GetY(), triggerRefreshDistance_)) {
365         if (refreshing_) {
366             loading_->SetLoadingMode(MODE_LOOP);
367         }
368         refreshing_ = true;
369         if (changeEvent_) {
370             changeEvent_("true");
371         }
372         FireRefreshEvent();
373     } else if (NearEqual(scrollableOffset_.GetY(), 0.0f)) {
374         ResetStatus();
375     } else {
376         loading_->SetLoadingMode(MODE_DRAG);
377     }
378 }
379 
GetNextStatus()380 RefreshStatus RenderRefresh::GetNextStatus()
381 {
382     RefreshStatus nextStatus;
383     auto context = context_.Upgrade();
384     switch (refreshStatus_) {
385         case RefreshStatus::INACTIVE:
386             if (refreshing_) {
387                 StartAnimation(0.0, triggerRefreshDistance_, false);
388                 nextStatus = RefreshStatus::REFRESH;
389                 break;
390             }
391             if (LessOrEqual(scrollableOffset_.GetY(), 0.0)) {
392                 nextStatus = RefreshStatus::INACTIVE;
393                 break;
394             }
395             // No break here, continue next case
396             FirePullDownEvent(PULL_DOWN_START);
397             [[fallthrough]];
398         case RefreshStatus::DRAG:
399             if (LessOrEqual(scrollableOffset_.GetY(), 0.0)) {
400                 FirePullDownEvent(PULL_DOWN_END);
401                 nextStatus = RefreshStatus::INACTIVE;
402             } else if (scrollableOffset_.GetY() < triggerRefreshDistance_) {
403                 nextStatus = RefreshStatus::DRAG;
404             } else {
405                 nextStatus = RefreshStatus::OVER_DRAG;
406             }
407             break;
408         case RefreshStatus::OVER_DRAG:
409             if (!refreshEvent_ && !context->GetIsDeclarative()) {
410                 nextStatus = RefreshStatus::DONE;
411                 break;
412             }
413             if (scrollableOffset_.GetY() > triggerRefreshDistance_) {
414                 nextStatus = RefreshStatus::OVER_DRAG;
415                 break;
416             }
417             // No break here, continue get next status.
418             [[fallthrough]];
419         case RefreshStatus::REFRESH:
420             if (!refreshing_) {
421                 timeText_ = StringUtils::FormatString(lastTimeText_.c_str(), GetFormatDateTime().c_str());
422                 StartAnimation(scrollableOffset_.GetY(), 0.0, true);
423                 nextStatus = RefreshStatus::DONE;
424                 break;
425             }
426             nextStatus = RefreshStatus::REFRESH;
427             break;
428         case RefreshStatus::DONE:
429             if (scrollableOffset_.GetY() > 0.0) {
430                 nextStatus = RefreshStatus::DONE;
431             } else {
432                 FirePullDownEvent(PULL_DOWN_END);
433                 nextStatus = RefreshStatus::INACTIVE;
434             }
435             break;
436         default:
437             nextStatus = RefreshStatus::INACTIVE;
438             break;
439     }
440     if (onStateChange_ && (refreshStatus_ != nextStatus)) {
441         onStateChange_(static_cast<int>(nextStatus));
442     }
443     return nextStatus;
444 }
445 
GetFriction(double percentage) const446 double RenderRefresh::GetFriction(double percentage) const
447 {
448     if (NearEqual(percentage, 1.0)) {
449         return 0.0;
450     } else {
451         return frictionRatio_ * std::pow(1.0 - percentage, SQUARE);
452     }
453 }
454 
GetOffset(double delta) const455 double RenderRefresh::GetOffset(double delta) const
456 {
457     double height = GetLayoutSize().Height();
458     if (!NearZero(height)) {
459         double friction = GetFriction(std::abs(scrollableOffset_.GetY() / height));
460         return friction * delta;
461     }
462     return delta;
463 }
464 
MaxScrollableHeight() const465 double RenderRefresh::MaxScrollableHeight() const
466 {
467     return GetLayoutParam().GetMaxSize().Height();
468 }
469 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)470 void RenderRefresh::OnTouchTestHit(
471     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
472 {
473     if (!dragDetector_ || hasScrollableChild_) {
474         return;
475     }
476     dragDetector_->SetCoordinateOffset(coordinateOffset);
477     result.emplace_back(dragDetector_);
478 }
479 
GetLoadingDiameter() const480 double RenderRefresh::GetLoadingDiameter() const
481 {
482     double diameter = 0.0;
483     if (scrollableOffset_.GetY() < triggerLoadingDistance_) {
484         return diameter;
485     } else if (scrollableOffset_.GetY() < triggerRefreshDistance_) {
486         double maxDistance = triggerRefreshDistance_ - triggerLoadingDistance_;
487         double actualDistance = scrollableOffset_.GetY() - triggerLoadingDistance_;
488 
489         // Get the diameter by actual distance
490         diameter = ((actualDistance * loadingDiameter_ * HALF) / maxDistance) + loadingDiameter_ * HALF;
491     } else {
492         diameter = loadingDiameter_;
493     }
494     return diameter;
495 }
496 
GetLoadingOffset() const497 Offset RenderRefresh::GetLoadingOffset() const
498 {
499     auto pipelineContext = GetContext().Upgrade();
500     if (!pipelineContext) {
501         LOGE("pipelineContext update with nullptr");
502         return Offset::Zero();
503     }
504 
505     if (scrollableOffset_.GetY() < triggerLoadingDistance_) {
506         return Offset::Zero();
507     }
508     if (!isUseOffset_) {
509         return scrollableOffset_ * HALF - Offset(0.0, GetLoadingDiameter() * HALF);
510     }
511     double factor =
512         (scrollableOffset_.GetY() - triggerLoadingDistance_) / (triggerRefreshDistance_ - triggerLoadingDistance_);
513     return Offset(0.0, indicatorOffset_ * factor);
514 }
515 
GetShowTimeOffset() const516 Offset RenderRefresh::GetShowTimeOffset() const
517 {
518     auto pipelineContext = GetContext().Upgrade();
519     if (!pipelineContext) {
520         LOGE("pipelineContext update with nullptr");
521         return Offset::Zero();
522     }
523 
524     double bottomOffset = timeBox_->GetLayoutSize().Height() +
525                           pipelineContext->NormalizeToPx(Dimension(DEFAULT_TIME_BOX_BOTTOM_SIZE, DimensionUnit::VP));
526     return scrollableOffset_ - Offset(0.0, bottomOffset + timeOffset_);
527 }
528 
GetOpacity() const529 double RenderRefresh::GetOpacity() const
530 {
531     double factor = 0.0;
532     if (scrollableOffset_.GetY() < triggerRefreshDistance_ - timeDistance_) {
533         return factor;
534     } else if (scrollableOffset_.GetY() < triggerRefreshDistance_) {
535         double actualDistance = scrollableOffset_.GetY() - triggerRefreshDistance_ + timeDistance_;
536 
537         // Get the factor, timeDistance_ never be zero
538         if (!NearZero(timeDistance_)) {
539             factor = actualDistance / timeDistance_;
540         }
541     } else {
542         factor = 1.0;
543     }
544     return factor;
545 }
546 
GetFormatDateTime()547 std::string RenderRefresh::GetFormatDateTime()
548 {
549     auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
550     auto local = std::localtime(&now);
551     if (local == nullptr) {
552         LOGE("Get localtime failed.");
553         return "";
554     }
555 
556     // This is for i18n date time
557     DateTime dateTime;
558     dateTime.year = static_cast<uint32_t>(local->tm_year + BASE_YEAR);
559     dateTime.month = static_cast<uint32_t>(local->tm_mon);
560     dateTime.day = static_cast<uint32_t>(local->tm_mday);
561     dateTime.hour = static_cast<uint32_t>(local->tm_hour);
562     dateTime.minute = static_cast<uint32_t>(local->tm_min);
563     std::string time = Localization::GetInstance()->FormatDateTime(dateTime, LAST_UPDATE_FORMAT);
564     return time;
565 }
566 
UpdateScrollableOffset(double delta)567 void RenderRefresh::UpdateScrollableOffset(double delta)
568 {
569     if (NearZero(delta)) {
570         LOGW("Delta is near zero!");
571         return;
572     }
573     if (refreshStatus_ == RefreshStatus::REFRESH) {
574         LOGW("The refresh status is refreshing!");
575         return;
576     }
577     Offset offset = Offset(0.0, GetOffset(delta));
578     scrollableOffset_ = scrollableOffset_ + offset;
579     scrollableOffset_.SetY(std::min(scrollableOffset_.GetY(), maxScrollOffset_));
580     MarkNeedLayout();
581 }
582 
OnHiddenChanged(bool hidden)583 void RenderRefresh::OnHiddenChanged(bool hidden)
584 {
585     if (hidden) {
586         return;
587     }
588     ResetStatus();
589 }
590 
PerformLayout()591 void RenderRefresh::PerformLayout()
592 {
593     const auto& children = GetChildren();
594     if (children.empty()) {
595         LOGW("Refresh has no child!");
596         return;
597     }
598     auto context = context_.Upgrade();
599     if (context == nullptr) {
600         LOGW("context is nullptr!");
601         return;
602     }
603 
604     RefreshStatus nextState = GetNextStatus();
605     if (nextState != RefreshStatus::REFRESH && refreshStatus_ == RefreshStatus::REFRESH) {
606         loading_->SetLoadingMode(MODE_EXIT);
607     }
608     refreshStatus_ = nextState;
609     LayoutParam innerLayout = GetLayoutParam();
610     innerLayout.SetMinSize(Size(0.0, 0.0));
611     if (!NearEqual(scale_, context->GetDipScale())) {
612         // Notify loading to updated when window size changed.
613         CalcLoadingParams(refreshComponent_.Upgrade());
614         loading_->Layout(innerLayout);
615     }
616 
617     loading_->SetDragDistance(scrollableOffset_.GetY());
618     loadingBox_->SetPosition(GetLoadingOffset());
619 
620     display_->UpdateOpacity(GetOpacity() * MAX_ALPHA);
621     display_->SetPosition(GetShowTimeOffset());
622     loadingBox_->SetHidden(scrollableOffset_.GetY() < triggerLoadingDistance_);
623     loadingBox_->SetVisible(scrollableOffset_.GetY() > triggerLoadingDistance_);
624 
625     columnChild_ = children.back();
626     columnChild_->Layout(innerLayout);
627     if (refreshType_ == RefreshType::PULL_DOWN && GreatNotEqual(scrollableOffset_.GetY(), 0.0)) {
628         columnChild_->SetPosition(scrollableOffset_);
629     } else {
630         columnChild_->SetPosition(Offset::Zero());
631     }
632 
633     display_->Layout(innerLayout);
634     loadingBox_->Layout(innerLayout);
635     timeBox_->Layout(innerLayout);
636     SetLayoutSize(GetLayoutParam().GetMaxSize());
637 }
638 
639 } // namespace OHOS::Ace
640