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/checkable/render_radio.h"
17 
18 #include "base/log/event_report.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 constexpr int DEFAULT_RADIO_ANIMATION_DURATION = 300;
24 constexpr double DEFAULT_MID_TIME_SLOT = 0.5;
25 constexpr double DEFAULT_END_TIME_SLOT = 1.0;
26 constexpr double DEFAULT_SHRINK_TIME_SLOT = 0.9;
27 
28 } // namespace
29 
Update(const RefPtr<Component> & component)30 void RenderRadio::Update(const RefPtr<Component>& component)
31 {
32     RenderCheckable::Update(component);
33     const auto& radio = AceType::DynamicCast<RadioComponent<std::string>>(component);
34     component_ = radio;
35     if (!radio) {
36         LOGE("cast to radio component failed");
37         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
38         return;
39     }
40     radio->SetGroupValueUpdateHandler([weak = AceType::WeakClaim(this)](const std::string& groupValue) {
41         auto renderRadio = weak.Upgrade();
42         if (renderRadio && renderRadio->UpdateGroupValue(groupValue)) {
43             renderRadio->MarkNeedRender();
44         }
45     });
46 
47     radioInnerSizeRatio_ = radio->GetRadioInnerSizeRatio();
48 
49     if (radio->GetUpdateType() == UpdateType::ALL) {
50         radioValue_ = radio->GetValue();
51         groupValue_ = radio->GetGroupValue();
52     }
53     bool originChecked = radio->GetOriginChecked();
54     if (!groupValue_.empty()) {
55         checked_ = radioValue_ == groupValue_;
56     } else {
57         checked_ = originChecked;
58     }
59 
60     ApplyRestoreInfo();
61 
62     auto theme = GetTheme<RadioTheme>();
63     if (theme) {
64         borderWidth_ = theme->GetBorderWidth();
65     }
66     UpdateUIStatus();
67     // set check animation engine
68     if (!onController_) {
69         onController_ = CREATE_ANIMATOR(GetContext());
70         onController_->AddStartListener(Animator::StatusCallback([weak = AceType::WeakClaim(this)]() {
71             auto radio = weak.Upgrade();
72             if (radio) {
73                 radio->OnAnimationStart();
74             }
75         }));
76     }
77     // set uncheck animation engine
78     if (!offController_) {
79         offController_ = CREATE_ANIMATOR(GetContext());
80         offController_->AddStopListener(Animator::StatusCallback([weak = AceType::WeakClaim(this)]() {
81             auto radio = weak.Upgrade();
82             if (radio) {
83                 radio->OffAnimationEnd();
84             }
85         }));
86     }
87     groupValueChangedListener_ = radio->GetGroupValueChangedListener();
88     pointScale_ = radio->GetRadioInnerSizeRatio();
89     UpdateAccessibilityAttr();
90 }
91 
UpdateAccessibilityAttr() const92 void RenderRadio::UpdateAccessibilityAttr() const
93 {
94     auto accessibilityNode = GetAccessibilityNode().Upgrade();
95     if (!accessibilityNode) {
96         return;
97     }
98     accessibilityNode->SetCheckedState(checked_);
99     if (accessibilityNode->GetClicked()) {
100         accessibilityNode->SetClicked(false);
101         auto context = context_.Upgrade();
102         if (context) {
103             AccessibilityEvent radioEvent;
104             radioEvent.nodeId = accessibilityNode->GetNodeId();
105             radioEvent.eventType = "click";
106             context->SendEventToAccessibility(radioEvent);
107         }
108     }
109 }
110 
UpdateGroupValue(const std::string & groupValue)111 bool RenderRadio::UpdateGroupValue(const std::string& groupValue)
112 {
113     groupValue_ = groupValue;
114 
115     bool needRender = false;
116 
117     if (!checked_ && isClicked_) {
118         checked_ = true;
119         isClicked_ = false;
120         needRender = true;
121         UpdateUIStatus();
122     } else if (checked_) {
123         checked_ = false;
124         needRender = true;
125         UpdateAnimation(false);
126         offController_->Play();
127     }
128     std::string checked = checked_ ? "true" : "false";
129     std::string result = std::string("\"change\",{\"checked\":")
130                              .append(checked)
131                              .append(",\"value\":\"")
132                              .append(groupValue_)
133                              .append("\"},null");
134     auto context = context_.Upgrade();
135     if (context && context->GetFrontend() && context->GetFrontendType() == FrontendType::JS_CARD) {
136         auto json = JsonUtil::Create(true);
137         json->Put("checkedItem", groupValue_.c_str());
138         result = json->ToString();
139     }
140     OnHandleChangedResult(result);
141     if (changeEvent_) {
142         changeEvent_(result);
143     }
144     if (valueChangeEvent_) {
145         valueChangeEvent_(groupValue_);
146     }
147     return needRender;
148 }
149 
PerformLayout()150 void RenderRadio::PerformLayout()
151 {
152     RenderCheckable::PerformLayout();
153     outCircleRadius_ = drawSize_.Width() / 2.0;
154     innerCircleRadius_ = outCircleRadius_ * radioInnerSizeRatio_;
155 }
156 
HandleClick()157 void RenderRadio::HandleClick()
158 {
159     if (!checked_) {
160         isClicked_ = true;
161         if (groupValueChangedListener_) {
162             LOGE("groupValueChangedListener_");
163             groupValueChangedListener_(radioValue_);
164         }
165     }
166     UpdateAnimation(true);
167     onController_->Play();
168 
169     if (clickEvent_) {
170         clickEvent_();
171     }
172     if (onClick_) {
173         onClick_();
174     }
175     if (onChange_) {
176         onChange_(checked_);
177     }
178 }
179 
OffAnimationEnd()180 void RenderRadio::OffAnimationEnd()
181 {
182     // after the animation stopped, set the stage back to unchecked
183     UpdateUIStatus();
184     UpdateAccessibilityAttr();
185 }
186 
OnAnimationStart()187 void RenderRadio::OnAnimationStart()
188 {
189     // set the stage to checked and then start animation
190     UpdateAccessibilityAttr();
191 }
192 
UpdateAnimation(bool isOn)193 void RenderRadio::UpdateAnimation(bool isOn)
194 {
195     if (!onController_ || !offController_) {
196         LOGE("update animation failed due to controller is null");
197         return;
198     }
199     RefPtr<KeyframeAnimation<double>> shrinkEngine = AceType::MakeRefPtr<KeyframeAnimation<double>>();
200     RefPtr<KeyframeAnimation<double>> selectEngine = AceType::MakeRefPtr<KeyframeAnimation<double>>();
201     onController_->ClearInterpolators();
202     offController_->ClearInterpolators();
203     auto shrinkFrameStart = AceType::MakeRefPtr<Keyframe<double>>(0.0, 1.0);
204     auto shrinkFrameMid = AceType::MakeRefPtr<Keyframe<double>>(DEFAULT_MID_TIME_SLOT, DEFAULT_SHRINK_TIME_SLOT);
205     shrinkFrameMid->SetCurve(Curves::FRICTION);
206     auto shrinkFrameEnd = AceType::MakeRefPtr<Keyframe<double>>(DEFAULT_END_TIME_SLOT, 1.0);
207     shrinkFrameEnd->SetCurve(Curves::FRICTION);
208     shrinkEngine->AddKeyframe(shrinkFrameStart);
209     shrinkEngine->AddKeyframe(shrinkFrameMid);
210     shrinkEngine->AddKeyframe(shrinkFrameEnd);
211     shrinkEngine->AddListener(Animation<double>::ValueCallback([weak = AceType::WeakClaim(this)](double value) {
212         auto radio = weak.Upgrade();
213         if (radio) {
214             radio->totalScale_ = value;
215             radio->MarkNeedRender();
216         }
217     }));
218 
219     auto selectFrameStart = AceType::MakeRefPtr<Keyframe<double>>(0.0, isOn ? 1.0 : radioInnerSizeRatio_);
220     auto selectFrameMid = AceType::MakeRefPtr<Keyframe<double>>(DEFAULT_MID_TIME_SLOT, 0.0);
221     selectFrameMid->SetCurve(Curves::FRICTION);
222     auto selectFrameEnd =
223         AceType::MakeRefPtr<Keyframe<double>>(DEFAULT_END_TIME_SLOT, isOn ? radioInnerSizeRatio_ : 1.0);
224     selectFrameEnd->SetCurve(Curves::FRICTION);
225     selectEngine->AddKeyframe(selectFrameStart);
226     selectEngine->AddKeyframe(selectFrameMid);
227     selectEngine->AddKeyframe(selectFrameEnd);
228     selectEngine->AddListener(Animation<double>::ValueCallback([weak = AceType::WeakClaim(this)](double value) {
229         auto radio = weak.Upgrade();
230         if (radio) {
231             radio->pointScale_ = value;
232             radio->MarkNeedRender();
233         }
234     }));
235     if (isOn) {
236         onController_->AddInterpolator(shrinkEngine);
237         onController_->AddInterpolator(selectEngine);
238         onController_->SetDuration(DEFAULT_RADIO_ANIMATION_DURATION);
239     } else {
240         offController_->AddInterpolator(shrinkEngine);
241         offController_->AddInterpolator(selectEngine);
242         offController_->SetDuration(DEFAULT_RADIO_ANIMATION_DURATION);
243     }
244 }
245 
ProvideRestoreInfo()246 std::string RenderRadio::ProvideRestoreInfo()
247 {
248     auto jsonObj = JsonUtil::Create(true);
249     jsonObj->Put("checked", checked_);
250     jsonObj->Put("radioValue", radioValue_.c_str());
251     jsonObj->Put("groupValue", groupValue_.c_str());
252     return jsonObj->ToString();
253 }
254 
ApplyRestoreInfo()255 void RenderRadio::ApplyRestoreInfo()
256 {
257     if (GetRestoreInfo().empty()) {
258         return;
259     }
260     auto info = JsonUtil::ParseJsonString(GetRestoreInfo());
261     if (!info->IsValid() || !info->IsObject()) {
262         LOGW("RenderRadio:: restore info is invalid");
263         return;
264     }
265 
266     auto jsonchecked = info->GetValue("checked");
267     auto jsonradioValue = info->GetValue("radioValue");
268     auto jsongroupValue = info->GetValue("groupValue");
269 
270     checked_ = jsonchecked->GetBool();
271     radioValue_ = jsonradioValue->GetString();
272     groupValue_ = jsongroupValue->GetString();
273     SetRestoreInfo("");
274 }
275 
276 } // namespace OHOS::Ace
277