1 /*
2  * Copyright (c) 2021 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/toast/toast_component.h"
17 
18 #include <atomic>
19 
20 #include "base/utils/string_utils.h"
21 #include "base/utils/system_properties.h"
22 #include "core/common/ace_application_info.h"
23 #include "core/components/align/align_component.h"
24 #include "core/components/common/layout/grid_system_manager.h"
25 #include "core/components/common/properties/shadow_config.h"
26 #include "core/components/flex/flex_component.h"
27 #include "core/components/positioned/positioned_component.h"
28 #include "core/components/stage/stage_element.h"
29 #include "core/components/tween/tween_component.h"
30 
31 namespace OHOS::Ace {
32 namespace {
33 
34 constexpr double START_FRAME_TIME = 0.0;
35 constexpr double END_FRAME_TIME = 0.9;
36 constexpr double START_FRAME_OPACITY = 0.0;
37 constexpr double MID_FRAME_OPACITY = 1.0;
38 constexpr double END_FRAME_OPACITY = 0.0;
39 constexpr float TOAST_ANIMATION_TIME = 100.0f;
40 constexpr char TOAST_TWEEN_NAME[] = "toast";
41 
42 } // namespace
43 
44 ToastComponent::ToastComponent() = default;
45 ToastComponent::~ToastComponent() = default;
46 
47 static std::atomic<int32_t> g_toastId(1);
48 
GenerateNextToastId()49 int32_t ToastComponent::GenerateNextToastId()
50 {
51     return g_toastId.fetch_add(1, std::memory_order_relaxed);
52 }
53 
InitToastAnimation()54 void ToastComponent::InitToastAnimation()
55 {
56     if (NearZero(toastDurationTime_)) {
57         return;
58     }
59     float midFrameTime = TOAST_ANIMATION_TIME / toastDurationTime_;
60     float stopFrameTime = END_FRAME_TIME - TOAST_ANIMATION_TIME / toastDurationTime_;
61     auto opacityKeyframeStart = AceType::MakeRefPtr<Keyframe<float>>(START_FRAME_TIME, START_FRAME_OPACITY);
62     auto opacityKeyframeMid = AceType::MakeRefPtr<Keyframe<float>>(midFrameTime, MID_FRAME_OPACITY);
63     opacityKeyframeMid->SetCurve(Curves::FRICTION);
64     auto opacityKeyframeStop = AceType::MakeRefPtr<Keyframe<float>>(stopFrameTime, MID_FRAME_OPACITY);
65     opacityKeyframeStop->SetCurve(Curves::LINEAR);
66     auto opacityKeyframeEnd = AceType::MakeRefPtr<Keyframe<float>>(END_FRAME_TIME, END_FRAME_OPACITY);
67     opacityKeyframeEnd->SetCurve(Curves::FRICTION);
68     auto opacityAnimation = AceType::MakeRefPtr<KeyframeAnimation<float>>();
69     opacityAnimation->AddKeyframe(opacityKeyframeStart);
70     opacityAnimation->AddKeyframe(opacityKeyframeMid);
71     opacityAnimation->AddKeyframe(opacityKeyframeStop);
72     opacityAnimation->AddKeyframe(opacityKeyframeEnd);
73     tweenOption_.SetOpacityAnimation(opacityAnimation);
74     tweenOption_.SetDuration(toastDurationTime_);
75     tweenOption_.SetFillMode(FillMode::FORWARDS);
76 }
77 
BuildToastContent(const RefPtr<TextComponent> & text,const RefPtr<ToastTheme> & toastTheme)78 void ToastComponent::BuildToastContent(const RefPtr<TextComponent>& text, const RefPtr<ToastTheme>& toastTheme)
79 {
80     if (!text || !toastTheme) {
81         return;
82     }
83     TextStyle toastTextStyle = toastTheme->GetTextStyle();
84     auto deviceType = SystemProperties::GetDeviceType();
85 #ifdef OHOS_PLATFORM
86     toastTextStyle.SetTextAlign(TextAlign::CENTER);
87 #endif
88     if (deviceType == DeviceType::WATCH) {
89         toastTextStyle.SetAdaptTextSize(toastTextStyle.GetFontSize(), toastTheme->GetMinFontSize());
90         toastTextStyle.SetMaxLines(toastTheme->GetTextMaxLines());
91         toastTextStyle.SetTextOverflow(TextOverflow::ELLIPSIS);
92         toastTextStyle.SetTextAlign(TextAlign::CENTER);
93     }
94     toastTextStyle.SetTextAlign(TextAlign::START);
95     text->SetTextStyle(toastTextStyle);
96 }
97 
BuildPackageBox(const RefPtr<PipelineContext> & context,const RefPtr<BoxComponent> & box,const RefPtr<TextComponent> & text,const RefPtr<ToastTheme> & toastTheme)98 void ToastComponent::BuildPackageBox(const RefPtr<PipelineContext>& context, const RefPtr<BoxComponent>& box,
99     const RefPtr<TextComponent>& text, const RefPtr<ToastTheme>& toastTheme)
100 {
101     if (!context || !box || !text || !toastTheme) {
102         return;
103     }
104     // create base box for background of toast
105     RefPtr<BoxComponent> baseBox = AceType::MakeRefPtr<BoxComponent>();
106     // baseBox set back decoration
107     RefPtr<Decoration> backDecoration = AceType::MakeRefPtr<Decoration>();
108     backDecoration->SetBackgroundColor(toastTheme->GetBackgroundColor());
109     backDecoration->AddShadow(ShadowConfig::DefaultShadowL);
110     Border border;
111     border.SetBorderRadius(toastTheme->GetRadius());
112     backDecoration->SetBorder(border);
113     baseBox->SetBackDecoration(backDecoration);
114     // baseBox set padding
115     baseBox->SetPadding(toastTheme->GetPadding());
116     auto deviceType = SystemProperties::GetDeviceType();
117     if (deviceType == DeviceType::WATCH) {
118         // baseBox set constraints
119         LayoutParam constraints;
120         constraints.SetMinSize(Size(
121             context->NormalizeToPx(toastTheme->GetMinWidth()), context->NormalizeToPx(toastTheme->GetMinHeight())));
122         constraints.SetMaxSize(Size(Size::INFINITE_SIZE, Size::INFINITE_SIZE));
123         baseBox->SetConstraints(constraints);
124     } else {
125         auto gridColumnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::TOAST);
126         auto parent = gridColumnInfo->GetParent();
127         if (parent) {
128             parent->BuildColumnWidth();
129         }
130         baseBox->SetGridLayoutInfo(gridColumnInfo);
131     }
132     // baseBox set child
133     if (deviceType == DeviceType::WATCH) {
134         baseBox->SetMargin(toastTheme->GetMarging());
135         baseBox->SetChild(text);
136     } else {
137         // Single line center alignment, multiple lines Left alignment.
138         std::list<RefPtr<Component>> rowChildren;
139         rowChildren.emplace_back(text);
140         RefPtr<RowComponent> row = AceType::MakeRefPtr<RowComponent>(FlexAlign::CENTER, FlexAlign::CENTER, rowChildren);
141         row->SetMainAxisSize(MainAxisSize::MIN);
142         baseBox->SetAlignment(Alignment::CENTER);
143         baseBox->SetChild(row);
144     }
145 
146     box->SetFlex(BoxFlex::FLEX_X);
147     box->SetChild(baseBox);
148 }
149 
Show(const RefPtr<PipelineContext> & context,const std::string & message,int32_t duration,const std::string & bottom,bool isRightToLeft)150 void ToastComponent::Show(const RefPtr<PipelineContext>& context, const std::string& message, int32_t duration,
151     const std::string& bottom, bool isRightToLeft)
152 {
153     if (!context) {
154         LOGE("fail to show toast due to context is null");
155         return;
156     }
157     auto stackElement = context->GetLastStack();
158     if (!stackElement) {
159         return;
160     }
161 
162     auto themeManager = context->GetThemeManager();
163     if (!themeManager) {
164         return;
165     }
166     auto toastTheme = themeManager->GetTheme<ToastTheme>();
167     if (!toastTheme) {
168         return;
169     }
170 
171     RefPtr<TextComponent> text = AceType::MakeRefPtr<TextComponent>(message);
172     text->SetTextDirection((isRightToLeft ? TextDirection::RTL : TextDirection::LTR));
173     BuildToastContent(text, toastTheme);
174     RefPtr<BoxComponent> box = AceType::MakeRefPtr<BoxComponent>();
175     BuildPackageBox(context, box, text, toastTheme);
176 
177     int32_t toastId = GenerateNextToastId();
178     auto deviceType = SystemProperties::GetDeviceType();
179     int32_t barrierfreeDuration = AceApplicationInfo::GetInstance().GetBarrierfreeDuration();
180     duration = duration > barrierfreeDuration ? duration : barrierfreeDuration;
181     // get toast animation playing time
182     toastDurationTime_ = duration;
183     Dimension bottomPosition = StringUtils::StringToDimensionWithThemeValue(bottom, true, toastTheme->GetBottom());
184     RefPtr<TweenComponent> tween =
185         AceType::MakeRefPtr<TweenComponent>(TweenComponent::AllocTweenComponentId(), TOAST_TWEEN_NAME, box);
186     InitToastAnimation();
187     tween->SetTweenOption(tweenOption_);
188     tween->SetAnimationOperation(AnimationOperation::PLAY);
189     // to prevent flicking when play animation
190     tween->SetIsFirstFrameShow(false);
191 
192     // prevent layer from clipping shadows
193     tween->SetShadow(ShadowConfig::DefaultShadowL);
194 
195     if (deviceType == DeviceType::WATCH) {
196         // center alignment
197         std::list<RefPtr<Component>> alignChildren;
198         alignChildren.emplace_back(tween);
199         RefPtr<AlignComponent> align = AceType::MakeRefPtr<AlignComponent>(alignChildren, Alignment::CENTER);
200         stackElement->PushToastComponent(align, toastId);
201     } else {
202         RefPtr<PositionedComponent> positioned = AceType::MakeRefPtr<PositionedComponent>(tween);
203         positioned->SetBottom(GreatOrEqual(bottomPosition.Value(), 0.0) ? bottomPosition : toastTheme->GetBottom());
204         stackElement->PushToastComponent(positioned, toastId);
205     }
206 
207     WeakPtr<StackElement> weak = stackElement;
208     context->GetTaskExecutor()->PostDelayedTask(
209         [weak, toastId, stopCallback = stopCallback_] {
210             auto ref = weak.Upgrade();
211             if (ref == nullptr) {
212                 return;
213             }
214             ref->PopToastComponent(toastId);
215             if (stopCallback) {
216                 LOGI("Animator stop callback.");
217                 stopCallback();
218             }
219         },
220         TaskExecutor::TaskType::UI, duration, "ArkUIToastAnimatorStopCallback");
221 }
222 
SetToastStopListenerCallback(std::function<void ()> && stopCallback)223 void ToastComponent::SetToastStopListenerCallback(std::function<void()>&& stopCallback)
224 {
225     stopCallback_ = std::move(stopCallback);
226 }
227 
228 } // namespace OHOS::Ace
229