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 "frameworks/bridge/declarative_frontend/jsview/js_text_clock.h"
17
18 #include <regex>
19 #include <string>
20
21 #include "base/log/ace_scoring_log.h"
22 #include "base/utils/string_utils.h"
23 #include "bridge/declarative_frontend/jsview/js_view_abstract.h"
24 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
25 #include "bridge/declarative_frontend/jsview/js_utils.h"
26 #include "bridge/declarative_frontend/jsview/models/text_clock_model_impl.h"
27 #include "bridge/declarative_frontend/ark_theme/theme_apply/js_text_clock_theme.h"
28 #include "core/components/common/properties/text_style.h"
29 #include "core/components/common/properties/text_style_parser.h"
30 #include "core/components_ng/base/view_stack_processor.h"
31 #include "core/components_ng/pattern/text_clock/text_clock_model.h"
32 #include "core/components_ng/pattern/text_clock/text_clock_model_ng.h"
33
34 namespace OHOS::Ace {
35
36 std::unique_ptr<TextClockModel> TextClockModel::instance_ = nullptr;
37 std::mutex TextClockModel::mutex_;
38 const char TEXTCLOCK_DATE_TIME_OPTIONS_HOUR[] = "hour";
39 const std::string TEXTCLOCK_DATE_TIME_OPTIONS_TWO_DIGIT_VAL = "2-digit";
40 const std::string TEXTCLOCK_DATE_TIME_OPTIONS_NUMERIC_VAL = "numeric";
41
GetInstance()42 TextClockModel* TextClockModel::GetInstance()
43 {
44 if (!instance_) {
45 std::lock_guard<std::mutex> lock(mutex_);
46 if (!instance_) {
47 #ifdef NG_BUILD
48 instance_.reset(new NG::TextClockModelNG());
49 #else
50 if (Container::IsCurrentUseNewPipeline()) {
51 instance_.reset(new NG::TextClockModelNG());
52 } else {
53 instance_.reset(new Framework::TextClockModelImpl());
54 }
55 #endif
56 }
57 }
58 return instance_.get();
59 }
60 } // namespace OHOS::Ace
61
62 namespace OHOS::Ace::Framework {
63
64 namespace {
65 const std::vector<FontStyle> FONT_STYLES = { FontStyle::NORMAL, FontStyle::ITALIC };
66 const std::string DEFAULT_FORMAT_API_TEN = "hms";
67 constexpr int32_t HOURS_WEST_LOWER_LIMIT = -14;
68 constexpr int32_t HOURS_WEST_UPPER_LIMIT = 12;
69 constexpr float HOURS_WEST[] = { 9.5f, 3.5f, -3.5f, -4.5f, -5.5f, -5.75f, -6.5f, -9.5f, -10.5f, -12.75f };
70
HoursWestIsValid(int32_t hoursWest)71 bool HoursWestIsValid(int32_t hoursWest)
72 {
73 return !(hoursWest < HOURS_WEST_LOWER_LIMIT || hoursWest > HOURS_WEST_UPPER_LIMIT);
74 }
75
GetHoursWest(float hoursWest)76 float GetHoursWest(float hoursWest)
77 {
78 if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN)) {
79 for (float i : HOURS_WEST) {
80 if (NearEqual(hoursWest, i)) {
81 return hoursWest;
82 }
83 }
84 }
85
86 return int32_t(hoursWest);
87 }
88 } // namespace
89
Create(const JSCallbackInfo & info)90 void JSTextClock::Create(const JSCallbackInfo& info)
91 {
92 auto controller = TextClockModel::GetInstance()->Create();
93 if (info.Length() < 1 || !info[0]->IsObject()) {
94 SetFontDefault();
95 JSTextClockTheme::ApplyTheme();
96 return;
97 }
98 JSTextClockTheme::ApplyTheme();
99 JSRef<JSObject> optionsObject = JSRef<JSObject>::Cast(info[0]);
100 JSRef<JSVal> hourWestVal = optionsObject->GetProperty("timeZoneOffset");
101 if (hourWestVal->IsNumber() && HoursWestIsValid(hourWestVal->ToNumber<int32_t>())) {
102 float hourWest = GetHoursWest(hourWestVal->ToNumber<float>());
103 TextClockModel::GetInstance()->SetHoursWest(hourWest);
104 } else {
105 TextClockModel::GetInstance()->SetHoursWest(NAN);
106 }
107 auto controllerObj = optionsObject->GetProperty("controller");
108 if (!controllerObj->IsUndefined() && !controllerObj->IsNull() && controllerObj->IsObject()) {
109 auto* jsController = JSRef<JSObject>::Cast(controllerObj)->Unwrap<JSTextClockController>();
110 if (jsController != nullptr) {
111 jsController->SetInstanceId(Container::CurrentId());
112 if (controller) {
113 jsController->AddController(controller);
114 }
115 }
116 return;
117 }
118 }
119
JSBind(BindingTarget globalObj)120 void JSTextClock::JSBind(BindingTarget globalObj)
121 {
122 JSClass<JSTextClock>::Declare("TextClock");
123 MethodOptions opt = MethodOptions::NONE;
124 JSClass<JSTextClock>::StaticMethod("create", &JSTextClock::Create, opt);
125 JSClass<JSTextClock>::StaticMethod("format", &JSTextClock::SetFormat, opt);
126 JSClass<JSTextClock>::StaticMethod("onDateChange", &JSTextClock::JsOnDateChange, opt);
127 JSClass<JSTextClock>::StaticMethod("onClick", &JSInteractableView::JsOnClick);
128 JSClass<JSTextClock>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
129 JSClass<JSTextClock>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
130 JSClass<JSTextClock>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
131 JSClass<JSTextClock>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
132 JSClass<JSTextClock>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
133 JSClass<JSTextClock>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
134 JSClass<JSTextClock>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
135 JSClass<JSTextClock>::StaticMethod("fontColor", &JSTextClock::SetTextColor, opt);
136 JSClass<JSTextClock>::StaticMethod("fontSize", &JSTextClock::SetFontSize, opt);
137 JSClass<JSTextClock>::StaticMethod("fontWeight", &JSTextClock::SetFontWeight, opt);
138 JSClass<JSTextClock>::StaticMethod("fontStyle", &JSTextClock::SetFontStyle, opt);
139 JSClass<JSTextClock>::StaticMethod("fontFamily", &JSTextClock::SetFontFamily, opt);
140 JSClass<JSTextClock>::StaticMethod("textShadow", &JSTextClock::SetTextShadow, opt);
141 JSClass<JSTextClock>::StaticMethod("fontFeature", &JSTextClock::SetFontFeature, opt);
142 JSClass<JSTextClock>::StaticMethod("dateTimeOptions", &JSTextClock::SetDateTimeOptions, opt);
143 JSClass<JSTextClock>::InheritAndBind<JSViewAbstract>(globalObj);
144 }
145
SetFontDefault()146 void JSTextClock::SetFontDefault()
147 {
148 RefPtr<TextTheme> textTheme = GetTheme<TextTheme>();
149 TextClockModel::GetInstance()->InitFontDefault(textTheme->GetTextStyle());
150 }
151
SetTextColor(const JSCallbackInfo & info)152 void JSTextClock::SetTextColor(const JSCallbackInfo& info)
153 {
154 if (info.Length() < 1) {
155 return;
156 }
157 Color textColor;
158 if (!ParseJsColor(info[0], textColor) && !JSTextClockTheme::ObtainTextColor(textColor)) {
159 auto pipelineContext = PipelineContext::GetCurrentContext();
160 CHECK_NULL_VOID(pipelineContext);
161 auto theme = pipelineContext->GetTheme<TextTheme>();
162 textColor = theme->GetTextStyle().GetTextColor();
163 }
164
165 TextClockModel::GetInstance()->SetTextColor(textColor);
166 }
167
SetFontSize(const JSCallbackInfo & info)168 void JSTextClock::SetFontSize(const JSCallbackInfo& info)
169 {
170 if (info.Length() < 1) {
171 return;
172 }
173 auto pipelineContext = PipelineContext::GetCurrentContext();
174 CHECK_NULL_VOID(pipelineContext);
175 auto theme = pipelineContext->GetTheme<TextTheme>();
176 CHECK_NULL_VOID(theme);
177
178 CalcDimension fontSize;
179 if (!ParseJsDimensionFpNG(info[0], fontSize, false)) {
180 fontSize = theme->GetTextStyle().GetFontSize();
181 }
182
183 if (fontSize.IsNegative() || fontSize.Unit() == DimensionUnit::PERCENT) {
184 auto pipelineContext = PipelineContext::GetCurrentContext();
185 CHECK_NULL_VOID(pipelineContext);
186 auto theme = pipelineContext->GetTheme<TextTheme>();
187 CHECK_NULL_VOID(theme);
188 fontSize = theme->GetTextStyle().GetFontSize();
189 }
190
191 TextClockModel::GetInstance()->SetFontSize(fontSize);
192 }
193
SetFontWeight(const JSCallbackInfo & info)194 void JSTextClock::SetFontWeight(const JSCallbackInfo& info)
195 {
196 if (info.Length() < 1) {
197 return;
198 }
199 RefPtr<TextTheme> textTheme = GetTheme<TextTheme>();
200 CHECK_NULL_VOID(textTheme);
201 const auto& fontWeight = info[0];
202 if (fontWeight->IsUndefined()) {
203 TextClockModel::GetInstance()->SetFontWeight(textTheme->GetTextStyle().GetFontWeight());
204 return;
205 }
206
207 if (!fontWeight->IsNull()) {
208 std::string weight;
209 if (fontWeight->IsNumber()) {
210 weight = std::to_string(fontWeight->ToNumber<int32_t>());
211 } else {
212 ParseJsString(fontWeight, weight);
213 }
214 TextClockModel::GetInstance()->SetFontWeight(ConvertStrToFontWeight(weight));
215 } else {
216 TextClockModel::GetInstance()->SetFontWeight(textTheme->GetTextStyle().GetFontWeight());
217 }
218 }
219
SetFontStyle(int32_t value)220 void JSTextClock::SetFontStyle(int32_t value)
221 {
222 if (value < 0 || value >= static_cast<int32_t>(FONT_STYLES.size())) {
223 return;
224 }
225 TextClockModel::GetInstance()->SetItalicFontStyle(FONT_STYLES[value]);
226 }
227
SetFontFamily(const JSCallbackInfo & info)228 void JSTextClock::SetFontFamily(const JSCallbackInfo& info)
229 {
230 if (info.Length() < 1) {
231 return;
232 }
233 std::vector<std::string> fontFamilies;
234 if (!ParseJsFontFamilies(info[0], fontFamilies)) {
235 return;
236 }
237 TextClockModel::GetInstance()->SetFontFamily(fontFamilies);
238 }
239
SetFormat(const JSCallbackInfo & info)240 void JSTextClock::SetFormat(const JSCallbackInfo& info)
241 {
242 if (info.Length() < 1) {
243 return;
244 }
245 if (!info[0]->IsString()) {
246 if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN)) {
247 TextClockModel::GetInstance()->SetFormat("");
248 } else {
249 TextClockModel::GetInstance()->SetFormat(DEFAULT_FORMAT_API_TEN);
250 }
251 return;
252 }
253
254 auto format = info[0]->ToString();
255 if (Container::LessThanAPITargetVersion(PlatformVersion::VERSION_ELEVEN)) {
256 std::regex pattern(
257 R"(^([Yy]*[_|\W\s]*[M]*[_|\W\s]*[d]*[_|\W\s]*[D]*[_|\W\s]*[Hh]*[_|\W\s]*[m]*[_|\W\s]*[s]*[_|\W\s]*[S]*)$)");
258 if (format.empty() || !StringUtils::IsAscii(format) || !std::regex_match(format, pattern)) {
259 TextClockModel::GetInstance()->SetFormat(DEFAULT_FORMAT_API_TEN);
260 return;
261 }
262 }
263
264 TextClockModel::GetInstance()->SetFormat(format);
265 }
266
SetTextShadow(const JSCallbackInfo & info)267 void JSTextClock::SetTextShadow(const JSCallbackInfo& info)
268 {
269 if (info.Length() < 1) {
270 return;
271 }
272 std::vector<Shadow> shadows;
273 ParseTextShadowFromShadowObject(info[0], shadows);
274 if (!shadows.empty()) {
275 TextClockModel::GetInstance()->SetTextShadow(shadows);
276 }
277 }
278
SetFontFeature(const JSCallbackInfo & info)279 void JSTextClock::SetFontFeature(const JSCallbackInfo& info)
280 {
281 if (info.Length() < 1) {
282 return;
283 }
284 if (!info[0]->IsString()) {
285 return;
286 }
287
288 std::string fontFeatureSettings = info[0]->ToString();
289 TextClockModel::GetInstance()->SetFontFeature(ParseFontFeatureSettings(fontFeatureSettings));
290 }
291
JsOnDateChange(const JSCallbackInfo & info)292 void JSTextClock::JsOnDateChange(const JSCallbackInfo& info)
293 {
294 if (!info[0]->IsFunction()) {
295 return;
296 }
297
298 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(info[0]));
299 WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
300 auto onChange = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](
301 const std::string& value) {
302 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
303 ACE_SCORING_EVENT("TextClock.onDateChange");
304 PipelineContext::SetCallBackNode(node);
305 auto newJSVal = JSRef<JSVal>::Make(ToJSValue(value));
306 func->ExecuteJS(1, &newJSVal);
307 };
308 TextClockModel::GetInstance()->SetOnDateChange(std::move(onChange));
309 }
310
JSBind(BindingTarget globalObj)311 void JSTextClockController::JSBind(BindingTarget globalObj)
312 {
313 JSClass<JSTextClockController>::Declare("TextClockController");
314 JSClass<JSTextClockController>::Method("start", &JSTextClockController::Start);
315 JSClass<JSTextClockController>::Method("stop", &JSTextClockController::Stop);
316 JSClass<JSTextClockController>::Bind(
317 globalObj, JSTextClockController::Constructor, JSTextClockController::Destructor);
318 }
319
Constructor(const JSCallbackInfo & args)320 void JSTextClockController::Constructor(const JSCallbackInfo& args)
321 {
322 auto scroller = Referenced::MakeRefPtr<JSTextClockController>();
323 scroller->IncRefCount();
324 args.SetReturnValue(Referenced::RawPtr(scroller));
325 }
326
Destructor(JSTextClockController * scroller)327 void JSTextClockController::Destructor(JSTextClockController* scroller)
328 {
329 if (scroller != nullptr) {
330 scroller->DecRefCount();
331 }
332 }
333
Start()334 void JSTextClockController::Start()
335 {
336 ContainerScope scope(instanceId_);
337 if (!controller_.empty()) {
338 for (auto& i : controller_) {
339 i->Start();
340 }
341 }
342 }
343
Stop()344 void JSTextClockController::Stop()
345 {
346 ContainerScope scope(instanceId_);
347 if (!controller_.empty()) {
348 for (auto& i : controller_) {
349 i->Stop();
350 }
351 }
352 }
353
removeController(const RefPtr<TextClockController> & controller)354 void JSTextClockController::removeController(const RefPtr<TextClockController>& controller)
355 {
356 for (auto it = controller_.begin(); it != controller_.end(); it++) {
357 if (controller == *it) {
358 controller_.erase(it);
359 break;
360 }
361 }
362 }
363
SetDateTimeOptions(const JSCallbackInfo & info)364 void JSTextClock::SetDateTimeOptions(const JSCallbackInfo& info)
365 {
366 JSRef<JSObject> paramObject;
367 ZeroPrefixType hourType = ZeroPrefixType::AUTO;
368 if (info.Length() >= 1 && info[0]->IsObject()) {
369 paramObject = JSRef<JSObject>::Cast(info[0]);
370 auto hourValue = paramObject->GetProperty(TEXTCLOCK_DATE_TIME_OPTIONS_HOUR);
371 if (hourValue->IsString()) {
372 std::string hour = hourValue->ToString();
373 if (hour == TEXTCLOCK_DATE_TIME_OPTIONS_TWO_DIGIT_VAL) {
374 hourType = ZeroPrefixType::SHOW;
375 } else if (hour == TEXTCLOCK_DATE_TIME_OPTIONS_NUMERIC_VAL) {
376 hourType = ZeroPrefixType::HIDE;
377 }
378 }
379 }
380 TextClockModel::GetInstance()->SetDateTimeOptions(hourType);
381 }
382 } // namespace OHOS::Ace::Framework
383