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