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