1 /*
2 * Copyright (c) 2023 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_calendar_picker.h"
17
18 #include "base/log/ace_scoring_log.h"
19 #include "base/utils/date_util.h"
20 #include "bridge/common/utils/engine_helper.h"
21 #include "bridge/declarative_frontend/engine/functions/js_function.h"
22 #include "bridge/declarative_frontend/jsview/js_interactable_view.h"
23 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
24 #include "bridge/declarative_frontend/jsview/models/calendar_picker_model_impl.h"
25 #include "core/components/dialog/dialog_theme.h"
26 #include "core/components_ng/pattern/calendar_picker/calendar_picker_model_ng.h"
27 #include "core/pipeline_ng/pipeline_context.h"
28
29 namespace OHOS::Ace {
30 std::unique_ptr<CalendarPickerModel> CalendarPickerModel::instance_ = nullptr;
31 std::mutex CalendarPickerModel::mutex_;
GetInstance()32 CalendarPickerModel* CalendarPickerModel::GetInstance()
33 {
34 if (!instance_) {
35 std::lock_guard<std::mutex> lock(mutex_);
36 if (!instance_) {
37 #ifdef NG_BUILD
38 instance_.reset(new NG::CalendarPickerModelNG());
39 #else
40 if (Container::IsCurrentUseNewPipeline()) {
41 instance_.reset(new NG::CalendarPickerModelNG());
42 } else {
43 instance_.reset(new Framework::CalendarPickerModelImpl());
44 }
45 #endif
46 }
47 }
48 return instance_.get();
49 }
50 } // namespace OHOS::Ace
51
52 namespace OHOS::Ace::Framework {
JSBind(BindingTarget globalObj)53 void JSCalendarPicker::JSBind(BindingTarget globalObj)
54 {
55 JSClass<JSCalendarPicker>::Declare("CalendarPicker");
56 JSClass<JSCalendarPicker>::StaticMethod("create", &JSCalendarPicker::Create, MethodOptions::NONE);
57 JSClass<JSCalendarPicker>::StaticMethod("edgeAlign", &JSCalendarPicker::SetEdgeAlign);
58 JSClass<JSCalendarPicker>::StaticMethod("textStyle", &JSCalendarPicker::SetTextStyle);
59 JSClass<JSCalendarPicker>::StaticMethod("onChange", &JSCalendarPicker::SetOnChange);
60 JSClass<JSCalendarPicker>::InheritAndBind<JSViewAbstract>(globalObj);
61 }
62
SetEdgeAlign(const JSCallbackInfo & info)63 void JSCalendarPicker::SetEdgeAlign(const JSCallbackInfo& info)
64 {
65 NG::CalendarEdgeAlign alignType = NG::CalendarEdgeAlign::EDGE_ALIGN_END;
66 DimensionOffset offset;
67 if (!info[0]->IsNumber()) {
68 CalendarPickerModel::GetInstance()->SetEdgeAlign(alignType, offset);
69 return;
70 }
71 alignType = static_cast<NG::CalendarEdgeAlign>(info[0]->ToNumber<int32_t>());
72
73 if (!info[1]->IsObject()) {
74 CalendarPickerModel::GetInstance()->SetEdgeAlign(alignType, offset);
75 return;
76 }
77 auto offsetObj = JSRef<JSObject>::Cast(info[1]);
78 CalcDimension dx;
79 auto dxValue = offsetObj->GetProperty("dx");
80 ParseJsDimensionVp(dxValue, dx);
81 CalcDimension dy;
82 auto dyValue = offsetObj->GetProperty("dy");
83 ParseJsDimensionVp(dyValue, dy);
84 offset = DimensionOffset(dx, dy);
85
86 CalendarPickerModel::GetInstance()->SetEdgeAlign(alignType, offset);
87 }
88
SetTextStyle(const JSCallbackInfo & info)89 void JSCalendarPicker::SetTextStyle(const JSCallbackInfo& info)
90 {
91 auto pipeline = PipelineBase::GetCurrentContext();
92 CHECK_NULL_VOID(pipeline);
93 RefPtr<CalendarTheme> calendarTheme = pipeline->GetTheme<CalendarTheme>();
94 CHECK_NULL_VOID(calendarTheme);
95 NG::PickerTextStyle textStyle;
96 textStyle.fontSize = calendarTheme->GetEntryFontSize();
97 textStyle.textColor = calendarTheme->GetEntryFontColor();
98 textStyle.fontWeight = FontWeight::NORMAL;
99 if (!info[0]->IsObject()) {
100 CalendarPickerModel::GetInstance()->SetTextStyle(textStyle);
101 return;
102 }
103 JSCalendarPicker::ParseTextStyle(info[0], textStyle);
104 CalendarPickerModel::GetInstance()->SetTextStyle(textStyle);
105 }
106
SetOnChange(const JSCallbackInfo & info)107 void JSCalendarPicker::SetOnChange(const JSCallbackInfo& info)
108 {
109 if (!info[0]->IsFunction()) {
110 return;
111 }
112
113 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(info[0]));
114 auto onChange = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const std::string& info) {
115 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
116 std::vector<std::string> keys = { "year", "month", "day" };
117 ACE_SCORING_EVENT("CalendarPicker.onChange");
118 func->Execute(keys, info);
119 };
120 CalendarPickerModel::GetInstance()->SetOnChange(std::move(onChange));
121 }
122
ParseSelectedDateObject(const JSCallbackInfo & info,const JSRef<JSObject> & selectedObject)123 void JSCalendarPicker::ParseSelectedDateObject(const JSCallbackInfo& info, const JSRef<JSObject>& selectedObject)
124 {
125 JSRef<JSVal> changeEventVal = selectedObject->GetProperty("changeEvent");
126 if (!changeEventVal->IsFunction()) {
127 return;
128 }
129 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(changeEventVal));
130 auto changeEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const std::string& info) {
131 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
132 ACE_SCORING_EVENT("DatePicker.SelectedDateTimeChangeEvent");
133 auto selectedJson = JsonUtil::ParseJsonString(info);
134 if (!selectedJson || selectedJson->IsNull()) {
135 return;
136 }
137
138 std::tm dateTime = { 0 };
139 auto year = selectedJson->GetValue("year");
140 if (year && year->IsNumber()) {
141 dateTime.tm_year = year->GetInt() - 1900; // local date start from 1900
142 }
143 auto month = selectedJson->GetValue("month");
144 if (month && month->IsNumber()) {
145 dateTime.tm_mon = month->GetInt();
146 }
147 auto day = selectedJson->GetValue("day");
148 if (day && day->IsNumber()) {
149 dateTime.tm_mday = day->GetInt();
150 }
151
152 auto milliseconds = Date::GetMilliSecondsByDateTime(dateTime);
153 auto dateObj = JSDate::New(milliseconds);
154 func->ExecuteJS(1, &dateObj);
155 };
156 CalendarPickerModel::GetInstance()->SetChangeEvent(std::move(changeEvent));
157 }
158
Create(const JSCallbackInfo & info)159 void JSCalendarPicker::Create(const JSCallbackInfo& info)
160 {
161 NG::CalendarSettingData settingData;
162 RefPtr<CalendarTheme> calendarTheme = GetTheme<CalendarTheme>();
163 CHECK_NULL_VOID(calendarTheme);
164 CalcDimension dayRadius;
165 if (info[0]->IsObject()) {
166 auto obj = JSRef<JSObject>::Cast(info[0]);
167 if (!ParseJsDimensionVp(obj->GetProperty("hintRadius"), dayRadius)) {
168 dayRadius = calendarTheme->GetCalendarDayRadius();
169 }
170 auto selected = obj->GetProperty("selected");
171 if (selected->IsObject()) {
172 JSRef<JSObject> selectedDateObj = JSRef<JSObject>::Cast(selected);
173 JSRef<JSVal> changeEventVal = selectedDateObj->GetProperty("changeEvent");
174 if (!changeEventVal->IsUndefined() && changeEventVal->IsFunction()) {
175 ParseSelectedDateObject(info, selectedDateObj);
176 settingData.selectedDate = ParseDate(selectedDateObj->GetProperty("value"));
177 } else {
178 settingData.selectedDate = ParseDate(selectedDateObj);
179 }
180 }
181 } else {
182 dayRadius = calendarTheme->GetCalendarDayRadius();
183 }
184 settingData.dayRadius = dayRadius;
185 CalendarPickerModel::GetInstance()->Create(settingData);
186 }
187
ParseTextStyle(const JSRef<JSObject> & paramObj,NG::PickerTextStyle & textStyle)188 void JSCalendarPicker::ParseTextStyle(const JSRef<JSObject>& paramObj, NG::PickerTextStyle& textStyle)
189 {
190 auto fontColor = paramObj->GetProperty("color");
191 auto fontStyle = paramObj->GetProperty("font");
192
193 Color color;
194 if (ParseJsColor(fontColor, color)) {
195 textStyle.textColor = color;
196 }
197
198 if (!fontStyle->IsObject()) {
199 return;
200 }
201 JSRef<JSObject> fontObj = JSRef<JSObject>::Cast(fontStyle);
202 auto fontSize = fontObj->GetProperty("size");
203 auto fontWeight = fontObj->GetProperty("weight");
204 if (fontSize->IsNull() || fontSize->IsUndefined()) {
205 textStyle.fontSize = Dimension(-1);
206 } else {
207 CalcDimension size;
208 if (!ParseJsDimensionFp(fontSize, size) || size.Unit() == DimensionUnit::PERCENT) {
209 textStyle.fontSize = Dimension(-1);
210 } else {
211 textStyle.fontSize = size;
212 }
213 }
214
215 if (!fontWeight->IsNull() && !fontWeight->IsUndefined()) {
216 std::string weight;
217 if (fontWeight->IsNumber()) {
218 weight = std::to_string(fontWeight->ToNumber<int32_t>());
219 } else {
220 ParseJsString(fontWeight, weight);
221 }
222 textStyle.fontWeight = ConvertStrToFontWeight(weight);
223 }
224 }
225
ParseDate(const JSRef<JSVal> & dateVal)226 PickerDate JSCalendarPicker::ParseDate(const JSRef<JSVal>& dateVal)
227 {
228 auto pickerDate = PickerDate::Current();
229 if (!dateVal->IsObject()) {
230 return pickerDate;
231 }
232 auto dateObj = JSRef<JSObject>::Cast(dateVal);
233 auto yearFuncJsVal = dateObj->GetProperty("getFullYear");
234 auto monthFuncJsVal = dateObj->GetProperty("getMonth");
235 auto dateFuncJsVal = dateObj->GetProperty("getDate");
236 if (!(yearFuncJsVal->IsFunction() && monthFuncJsVal->IsFunction() && dateFuncJsVal->IsFunction())) {
237 return pickerDate;
238 }
239 auto yearFunc = JSRef<JSFunc>::Cast(yearFuncJsVal);
240 auto monthFunc = JSRef<JSFunc>::Cast(monthFuncJsVal);
241 auto dateFunc = JSRef<JSFunc>::Cast(dateFuncJsVal);
242 JSRef<JSVal> year = yearFunc->Call(dateObj);
243 JSRef<JSVal> month = monthFunc->Call(dateObj);
244 JSRef<JSVal> date = dateFunc->Call(dateObj);
245
246 if (year->IsNumber() && month->IsNumber() && date->IsNumber()) {
247 pickerDate.SetYear(year->ToNumber<int32_t>());
248 pickerDate.SetMonth(month->ToNumber<int32_t>() + 1); // 0-11 means 1 to 12 months
249 pickerDate.SetDay(date->ToNumber<int32_t>());
250 }
251 return pickerDate;
252 }
253
JSBind(BindingTarget globalObj)254 void JSCalendarPickerDialog::JSBind(BindingTarget globalObj)
255 {
256 JSClass<JSCalendarPickerDialog>::Declare("CalendarPickerDialog");
257 JSClass<JSCalendarPickerDialog>::StaticMethod("show", &JSCalendarPickerDialog::Show);
258 JSClass<JSCalendarPickerDialog>::Bind<>(globalObj);
259 }
260
Show(const JSCallbackInfo & info)261 void JSCalendarPickerDialog::Show(const JSCallbackInfo& info)
262 {
263 auto scopedDelegate = EngineHelper::GetCurrentDelegate();
264 CHECK_NULL_VOID(scopedDelegate);
265 if (!info[0]->IsObject()) {
266 return;
267 }
268
269 if (Container::IsCurrentUseNewPipeline()) {
270 auto paramObject = JSRef<JSObject>::Cast(info[0]);
271 auto dialogEvent = ChangeDialogEvent(info);
272 auto dialogCancelEvent = DialogCancelEvent(info);
273 CalendarPickerDialogShow(paramObject, dialogEvent, dialogCancelEvent);
274 }
275 }
276
ChangeDialogEvent(const JSCallbackInfo & info)277 std::map<std::string, NG::DialogEvent> JSCalendarPickerDialog::ChangeDialogEvent(const JSCallbackInfo& info)
278 {
279 std::map<std::string, NG::DialogEvent> dialogEvent;
280 if (!info[0]->IsObject()) {
281 return dialogEvent;
282 }
283 auto paramObject = JSRef<JSObject>::Cast(info[0]);
284 auto onChange = paramObject->GetProperty("onChange");
285 if (!onChange->IsUndefined() && onChange->IsFunction()) {
286 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onChange));
287 auto changeId = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const std::string& info) {
288 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
289 std::vector<std::string> keys = { "year", "month", "day" };
290 ACE_SCORING_EVENT("CalendarDialog.onChange");
291 func->Execute(keys, info);
292 };
293 dialogEvent["changeId"] = changeId;
294 }
295 auto onAccept = paramObject->GetProperty("onAccept");
296 if (!onAccept->IsUndefined() && onAccept->IsFunction()) {
297 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onAccept));
298 auto acceptId = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const std::string& info) {
299 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
300 std::vector<std::string> keys = { "year", "month", "day" };
301 ACE_SCORING_EVENT("CalendarDialog.onAccept");
302 func->Execute(keys, info);
303 };
304 dialogEvent["acceptId"] = acceptId;
305 }
306 return dialogEvent;
307 }
308
DialogCancelEvent(const JSCallbackInfo & info)309 std::map<std::string, NG::DialogGestureEvent> JSCalendarPickerDialog::DialogCancelEvent(const JSCallbackInfo& info)
310 {
311 std::map<std::string, NG::DialogGestureEvent> dialogCancelEvent;
312 if (!info[0]->IsObject()) {
313 return dialogCancelEvent;
314 }
315 auto paramObject = JSRef<JSObject>::Cast(info[0]);
316 auto onCancel = paramObject->GetProperty("onCancel");
317 if (!onCancel->IsUndefined() && onCancel->IsFunction()) {
318 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onCancel));
319 auto cancelId = [execCtx = info.GetExecutionContext(),
320 func = std::move(jsFunc)](const GestureEvent& /* info */) {
321 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
322 ACE_SCORING_EVENT("CalendarDialog.onCancel");
323 func->Execute();
324 };
325 dialogCancelEvent["cancelId"] = cancelId;
326 }
327 return dialogCancelEvent;
328 }
329
ParseDate(const JSRef<JSVal> & dateVal)330 PickerDate JSCalendarPickerDialog::ParseDate(const JSRef<JSVal>& dateVal)
331 {
332 auto pickerDate = PickerDate();
333 if (!dateVal->IsObject()) {
334 return pickerDate;
335 }
336 auto dateObj = JSRef<JSObject>::Cast(dateVal);
337
338 auto yearFuncJsVal = dateObj->GetProperty("getFullYear");
339 auto monthFuncJsVal = dateObj->GetProperty("getMonth");
340 auto dateFuncJsVal = dateObj->GetProperty("getDate");
341 if (!(yearFuncJsVal->IsFunction() && monthFuncJsVal->IsFunction() && dateFuncJsVal->IsFunction())) {
342 return pickerDate;
343 }
344 auto yearFunc = JSRef<JSFunc>::Cast(yearFuncJsVal);
345 auto monthFunc = JSRef<JSFunc>::Cast(monthFuncJsVal);
346 auto dateFunc = JSRef<JSFunc>::Cast(dateFuncJsVal);
347 JSRef<JSVal> year = yearFunc->Call(dateObj);
348 JSRef<JSVal> month = monthFunc->Call(dateObj);
349 JSRef<JSVal> date = dateFunc->Call(dateObj);
350
351 if (year->IsNumber() && month->IsNumber() && date->IsNumber()) {
352 pickerDate.SetYear(year->ToNumber<int32_t>());
353 pickerDate.SetMonth(month->ToNumber<int32_t>() + 1); // 0-11 means 1 to 12 months
354 pickerDate.SetDay(date->ToNumber<int32_t>());
355 }
356 return pickerDate;
357 }
358
CalendarPickerDialogShow(const JSRef<JSObject> & paramObj,const std::map<std::string,NG::DialogEvent> & dialogEvent,const std::map<std::string,NG::DialogGestureEvent> & dialogCancelEvent)359 void JSCalendarPickerDialog::CalendarPickerDialogShow(const JSRef<JSObject>& paramObj,
360 const std::map<std::string, NG::DialogEvent>& dialogEvent,
361 const std::map<std::string, NG::DialogGestureEvent>& dialogCancelEvent)
362 {
363 auto container = Container::Current();
364 CHECK_NULL_VOID(container);
365 auto pipelineContext = AccessibilityManager::DynamicCast<NG::PipelineContext>(container->GetPipelineContext());
366 CHECK_NULL_VOID(pipelineContext);
367 auto executor = pipelineContext->GetTaskExecutor();
368 CHECK_NULL_VOID(executor);
369
370 auto theme = GetTheme<DialogTheme>();
371 CHECK_NULL_VOID(theme);
372 auto calendarTheme = pipelineContext->GetTheme<CalendarTheme>();
373 NG::CalendarSettingData settingData;
374 auto selectedDate = paramObj->GetProperty("selected");
375 auto parseSelectedDate = ParseDate(selectedDate);
376
377 if (selectedDate->IsObject()) {
378 settingData.selectedDate = parseSelectedDate;
379 }
380
381 CalcDimension radius;
382 if (ParseJsDimensionVp(paramObj->GetProperty("hintRadius"), radius)) {
383 settingData.dayRadius = radius;
384 }
385
386 DialogProperties properties;
387 if (SystemProperties::GetDeviceType() == DeviceType::PHONE) {
388 properties.alignment = DialogAlignment::BOTTOM;
389 } else {
390 properties.alignment = DialogAlignment::CENTER;
391 }
392 properties.customStyle = false;
393 properties.offset = DimensionOffset(Offset(0, -theme->GetMarginBottom().ConvertToPx()));
394 NG::BorderRadiusProperty dialogRadius;
395 dialogRadius.SetRadius(calendarTheme->GetDialogBorderRadius());
396 properties.borderRadius = dialogRadius;
397
398 auto context = AccessibilityManager::DynamicCast<NG::PipelineContext>(pipelineContext);
399 auto overlayManager = context ? context->GetOverlayManager() : nullptr;
400 executor->PostTask(
401 [properties, settingData, dialogEvent, dialogCancelEvent, weak = WeakPtr<NG::OverlayManager>(overlayManager)] {
402 auto overlayManager = weak.Upgrade();
403 CHECK_NULL_VOID(overlayManager);
404 overlayManager->ShowCalendarDialog(properties, settingData, dialogEvent, dialogCancelEvent);
405 },
406 TaskExecutor::TaskType::UI);
407 }
408 } // namespace OHOS::Ace::Framework
409