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/calendar/render_calendar.h"
17
18 #include "base/i18n/localization.h"
19 #include "base/log/event_report.h"
20 #include "core/common/font_manager.h"
21
22 namespace OHOS::Ace {
23 namespace {
24
25 constexpr int32_t CALENDAR_MIN_WIDTH = 350;
26 constexpr int32_t CALENDAR_MIN_HEIGHT = 230;
27 constexpr int32_t DAYS_PER_WEEK = 7;
28
29 } // namespace
30
RenderCalendar()31 RenderCalendar::RenderCalendar()
32 {
33 weekNumbers_ = Localization::GetInstance()->GetWeekdays(true);
34 }
35
Update(const RefPtr<Component> & component)36 void RenderCalendar::Update(const RefPtr<Component>& component)
37 {
38 auto calendarMonth = AceType::DynamicCast<CalendarMonthComponent>(component);
39 if (!calendarMonth) {
40 LOGE("calendar month component is null");
41 EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
42 return;
43 }
44
45 auto context = context_.Upgrade();
46 if (!context) {
47 return;
48 }
49
50 selectedChangeEvent_ =
51 AceAsyncEvent<void(const std::string&)>::Create(calendarMonth->GetSelectedChangeEvent(), context_);
52 indexOfContainer_ = calendarMonth->GetIndexOfContainer();
53 calendarController_ = calendarMonth->GetCalendarController();
54 dataAdapter_ = calendarController_->GetDataAdapter();
55 colCount_ = static_cast<int32_t>(weekNumbers_.size());
56 dataAdapter_->RegisterDataListener(AceType::Claim(this));
57 type_ = calendarMonth->GetCalendarType();
58 if (type_ != CalendarType::SIMPLE) {
59 clickDetector_ = AceType::MakeRefPtr<ClickRecognizer>();
60 clickDetector_->SetOnClick([weak = WeakClaim(this)](const ClickInfo& info) {
61 auto calendar = weak.Upgrade();
62 if (calendar) {
63 calendar->HandleClick(info.GetLocalLocation());
64 }
65 });
66 }
67 OnDataChanged(dataAdapter_->GetDayOfMonthCache()[indexOfContainer_]);
68
69 auto fontManager = context->GetFontManager();
70 if (fontManager) {
71 fontManager->AddVariationNode(WeakClaim(this));
72 }
73 MarkNeedLayout();
74 }
75
OnPaintFinish()76 void RenderCalendar::OnPaintFinish()
77 {
78 UpdateAccessibility();
79 }
80
UpdateAccessibility()81 void RenderCalendar::UpdateAccessibility()
82 {
83 auto node = GetAccessibilityNode().Upgrade();
84 if (!node) {
85 return;
86 }
87 if (!calendarController_) {
88 return;
89 }
90 auto today = calendarController_->GetDataAdapter()->GetToday();
91 DateTime dateTime;
92 dateTime.year = static_cast<uint32_t>(today.month.year);
93 dateTime.month = static_cast<uint32_t>(today.month.month);
94 dateTime.day = static_cast<uint32_t>(today.day);
95 auto dateText = Localization::GetInstance()->FormatDateTime(dateTime, "yyyyMMdd");
96 node->SetText(dateText);
97 }
98
PerformLayout()99 void RenderCalendar::PerformLayout()
100 {
101 auto context = context_.Upgrade();
102 if (!context) {
103 LOGE("calendar perform layout error");
104 return;
105 }
106 Size calendarMinSize(CALENDAR_MIN_WIDTH, CALENDAR_MIN_HEIGHT);
107 LayoutParam innerLayout = GetLayoutParam();
108 Size maxSize = innerLayout.GetMaxSize();
109 Size layoutSize;
110 if (maxSize.IsInfinite()) {
111 Size minSize = innerLayout.GetMinSize();
112 layoutSize = Size(
113 std::max(minSize.Width(), calendarMinSize.Width()), std::max(minSize.Height(), calendarMinSize.Height()));
114 } else {
115 layoutSize = Size(
116 std::max(maxSize.Width(), calendarMinSize.Width()), std::max(maxSize.Height(), calendarMinSize.Height()));
117 }
118 if (SystemProperties::GetDeviceType() == DeviceType::TV && !cardCalendar_) {
119 SetLayoutSize(layoutSize);
120 } else if (maxSize.IsInfinite()) {
121 SetLayoutSize({ context->GetRootWidth(), context->GetRootHeight() });
122 maxWidth_ = context->GetRootWidth();
123 maxHeight_ = context->GetRootHeight();
124 } else {
125 SetLayoutSize(maxSize);
126 auto constrainSize = innerLayout.Constrain(maxSize);
127 maxWidth_ = constrainSize.Width();
128 maxHeight_ = constrainSize.Height();
129 }
130 }
131
OnDataChanged(const CalendarDaysOfMonth & daysOfMonth)132 void RenderCalendar::OnDataChanged(const CalendarDaysOfMonth& daysOfMonth)
133 {
134 firstDayIndex_ = daysOfMonth.firstDayIndex;
135 if (currentMonth_ == daysOfMonth.month) {
136 if (SystemProperties::GetDeviceType() == DeviceType::TV && IsValid(focusIndex_) &&
137 calendarDays_[focusIndex_].focused) {
138 calendarDays_ = daysOfMonth.days;
139 calendarDays_[focusIndex_].focused = true;
140 } else if (isV2Component_ && IsValid(touchIndex_) && calendarDays_[touchIndex_].touched) {
141 calendarDays_ = daysOfMonth.days;
142 calendarDays_[touchIndex_].touched = true;
143 } else {
144 calendarDays_ = daysOfMonth.days;
145 }
146 // the number of rows will be 5 or 6, and week number height is half of the date number
147 rowCount_ = colCount_ ? static_cast<int32_t>(daysOfMonth.days.size()) / colCount_ : 0;
148 UpdateBreakInformation();
149 isNeedRepaint_ = true;
150 MarkNeedLayout();
151 return;
152 }
153 calendarDays_ = daysOfMonth.days;
154 currentMonth_ = daysOfMonth.month;
155 lastDayIndex_ = daysOfMonth.lastDayIndex;
156 if ((!calendarController_->FirstSetToday() || currentMonth_ != calendarController_->GetCurrentMonth()) &&
157 IsValid(firstDayIndex_)) {
158 calendarDays_[firstDayIndex_].touched = true;
159 touchIndex_ = firstDayIndex_;
160 }
161 UpdateBreakInformation();
162 if (GetCalendarController()->GetFirstEnter()) {
163 selectedDayNumber_ = daysOfMonth.today - firstDayIndex_ + 1;
164 GetCalendarController()->SetFirstEnter(false);
165 OnStatusChanged(RenderStatus::FOCUS);
166 }
167 // the number of rows will be 5 or 6, and week number height is half of the date number
168 rowCount_ = colCount_ ? static_cast<int32_t>(daysOfMonth.days.size()) / colCount_ : 0;
169 calendarController_->JumpMonth();
170 hasRequestFocus_ = false;
171 cardCalendar_ ? MarkNeedLayout() : MarkNeedRender();
172 isNeedRepaint_ = true;
173 }
174
OnSelectedDay(int32_t selected)175 void RenderCalendar::OnSelectedDay(int32_t selected)
176 {
177 if (isV2Component_ && calendarController_->IsCrossMonth() &&
178 calendarController_->GetCrossMonthDay().month == currentMonth_ && IsValid(touchIndex_)) {
179 calendarDays_[touchIndex_].touched = false;
180 if (IsToday(calendarDays_[touchIndex_])) {
181 isNeedRepaint_ = true;
182 }
183 touchIndex_ = selected + firstDayIndex_ - 1;
184 calendarDays_[touchIndex_].touched = true;
185 if (IsToday(calendarDays_[touchIndex_])) {
186 isNeedRepaint_ = true;
187 }
188 MarkNeedRender();
189 return;
190 } else if (SystemProperties::GetDeviceType() == DeviceType::TV) {
191 selectedDayNumber_ = selected;
192 CalendarDay day;
193 if (selected < 0) {
194 day.index = lastDayIndex_;
195 } else {
196 day.index = selectedDayNumber_ + firstDayIndex_ - 1;
197 }
198 if (calendarController_->GetFirstLoad()) {
199 calendarController_->SetFirstLoad(false);
200 } else {
201 OnDateSelected(day);
202 }
203
204 if (calendarController_->IsNeedFocus()) {
205 calendarController_->RequestFocus();
206 calendarController_->SetNeedFocus(false);
207 }
208 hasRequestFocus_ = true;
209 MarkNeedRender();
210 }
211 }
212
OnStatusChanged(RenderStatus renderStatus)213 void RenderCalendar::OnStatusChanged(RenderStatus renderStatus)
214 {
215 int32_t calendarDaysSize = static_cast<int32_t>(calendarDays_.size());
216 if (renderStatus == RenderStatus::FOCUS) {
217 int32_t focusedIndex = selectedDayNumber_ + firstDayIndex_ - 1;
218 if (selectedDayNumber_ < 0) {
219 focusedIndex = lastDayIndex_;
220 }
221 focusRow_ = focusedIndex / DAYS_PER_WEEK;
222 focusCol_ = focusedIndex % DAYS_PER_WEEK;
223 selectedDayNumber_ = focusedIndex - firstDayIndex_ + 1;
224 focusIndex_ = focusedIndex;
225 if (focusIndex_ >= 0 && focusIndex_ < calendarDaysSize) {
226 calendarDays_[focusedIndex].focused = true;
227 if (SystemProperties::GetDeviceType() == DeviceType::TV) {
228 OnDateSelected(calendarDays_[focusedIndex]);
229 }
230 }
231 } else if (renderStatus == RenderStatus::BLUR) {
232 focusRow_ = -1;
233 focusCol_ = -1;
234 if (focusIndex_ >= 0 && focusIndex_ < calendarDaysSize) {
235 calendarDays_[focusIndex_].focused = false;
236 }
237 }
238 MarkNeedRender();
239 }
240
GetIndexByGrid(int32_t row,int32_t column)241 int32_t RenderCalendar::GetIndexByGrid(int32_t row, int32_t column)
242 {
243 return (row * colCount_) + column;
244 }
245
OnDateSelected(const CalendarDay & date)246 void RenderCalendar::OnDateSelected(const CalendarDay& date)
247 {
248 if (SystemProperties::GetDeviceType() == DeviceType::TV) {
249 auto calendarCache = calendarController_->GetDataAdapter()->GetCalendarCache()[indexOfContainer_];
250 if (selectedChangeEvent_ && date.index >= 0 && date.index < static_cast<int32_t>(calendarCache.size())) {
251 if (SystemProperties::GetDeviceType() == DeviceType::WATCH &&
252 calendarDays_[date.index].month != calendarController_->GetCurrentMonth()) {
253 return;
254 }
255 std::string result = std::string("\"selectedchange\",").append(calendarCache[date.index]).append(",null");
256 selectedChangeEvent_(result);
257 }
258 } else if (date.index >= 0 && date.index < static_cast<int32_t>(calendarDays_.size())) {
259 auto result = JsonUtil::ParseJsonString(calendarDays_[date.index].ToString());
260 if (selectedChangeEvent_) {
261 selectedChangeEvent_(result->ToString());
262 }
263 }
264 }
265
FocusChanged(int32_t oldIndex,int32_t newIndex)266 void RenderCalendar::FocusChanged(int32_t oldIndex, int32_t newIndex)
267 {
268 int32_t calendarDaysSize = static_cast<int32_t>(calendarDays_.size());
269 if (oldIndex < 0 || oldIndex >= calendarDaysSize) {
270 LOGW("lost focus index is out of calendar days array");
271 return;
272 }
273 calendarDays_[oldIndex].focused = false;
274
275 auto pipelineContext = GetContext().Upgrade();
276 if (!pipelineContext) {
277 LOGE("pipeline context is null");
278 return;
279 }
280 pipelineContext->CancelFocusAnimation();
281 if (newIndex < 0) {
282 calendarController_->GoToPrevMonth(-1);
283 return;
284 }
285 if (newIndex >= calendarDaysSize) {
286 calendarController_->GoToNextMonth(1);
287 return;
288 }
289
290 auto& onFocusDay = calendarDays_[newIndex];
291 if (onFocusDay.month < currentMonth_) {
292 calendarController_->GoToPrevMonth(onFocusDay.day);
293 return;
294 } else if (calendarDays_[newIndex].month > currentMonth_) {
295 calendarController_->GoToNextMonth(onFocusDay.day);
296 return;
297 }
298
299 onFocusDay.focused = true;
300 if (SystemProperties::GetDeviceType() == DeviceType::TV) {
301 OnDateSelected(onFocusDay);
302 }
303 }
304
OnFocusChanged(bool focusStatus)305 void RenderCalendar::OnFocusChanged(bool focusStatus)
306 {
307 calendarFocusStatus_ = focusStatus;
308 }
309
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)310 void RenderCalendar::OnTouchTestHit(
311 const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
312 {
313 if (type_ == CalendarType::SIMPLE) {
314 return;
315 }
316 clickDetector_->SetCoordinateOffset(coordinateOffset);
317 result.emplace_back(clickDetector_);
318 }
319
HandleClick(const Offset & offset)320 void RenderCalendar::HandleClick(const Offset& offset)
321 {
322 if (SystemProperties::GetDeviceType() == DeviceType::TV) {
323 return;
324 }
325 auto swiper = calendarController_->GetRenderSwiper();
326 if (swiper && swiper->GetMoveStatus()) {
327 return;
328 }
329 auto index = JudgeArea(offset);
330 if (index < 0 || index >= static_cast<int32_t>(calendarDays_.size())) {
331 return;
332 }
333 CalendarDay day;
334 day.index = index;
335 OnDateSelected(day);
336
337 if (!isV2Component_) {
338 return;
339 }
340
341 if (calendarDays_[index].month > calendarController_->GetCurrentMonth()) {
342 calendarController_->SetCrossMonthDay(calendarDays_[index]);
343 calendarController_->SetHasMoved(true);
344 calendarController_->GoToNextMonth(calendarDays_[index].day);
345 } else if (calendarDays_[index].month < calendarController_->GetCurrentMonth()) {
346 calendarController_->SetCrossMonthDay(calendarDays_[index]);
347 calendarController_->SetHasMoved(true);
348 calendarController_->GoToPrevMonth(calendarDays_[index].day);
349 } else {
350 if (IsValid(touchIndex_)) {
351 calendarDays_[touchIndex_].touched = false;
352 }
353 if (IsToday(calendarDays_[touchIndex_]) || IsToday(calendarDays_[index])) {
354 isNeedRepaint_ = true;
355 }
356 touchIndex_ = index;
357 calendarDays_[touchIndex_].touched = true;
358 MarkNeedRender();
359 return;
360 }
361 }
362
JudgeArea(const Offset & offset)363 int32_t RenderCalendar::JudgeArea(const Offset& offset)
364 {
365 const static int32_t rowsOfData = 5;
366 auto rowSpace = rowCount_ == rowsOfData ? dailyFiveRowSpace_ : dailySixRowSpace_;
367 auto topPadding = NormalizeToPx(calendarTheme_.topPadding);
368 auto browHeight = weekHeight_ + topPadding + NormalizeToPx(calendarTheme_.weekAndDayRowSpace);
369 auto maxHeight = browHeight + rowCount_ * dayHeight_ + (rowCount_ - 1) * rowSpace +
370 NormalizeToPx(calendarTheme_.weekAndDayRowSpace);
371 auto maxWidth = dayWidth_ * DAYS_PER_WEEK + colSpace_ * 6;
372 if ((offset.GetX() < 0) || (offset.GetX() > maxWidth) || (offset.GetY() < browHeight) ||
373 (offset.GetY() > maxHeight) || LessOrEqual(dayHeight_, 0.0) || LessOrEqual(dayWidth_, 0.0)) {
374 return -1;
375 }
376 auto boundaryColOffset = cardCalendar_ ? 0.0 : NormalizeToPx(calendarTheme_.boundaryColOffset);
377 auto height = offset.GetY() - browHeight - boundaryColOffset;
378 auto boundaryRowOffset = NormalizeToPx(calendarTheme_.boundaryRowOffset);
379 int32_t y =
380 height < (dayHeight_ + rowSpace / 2) ? 0 : (height - dayHeight_ - rowSpace / 2) / (dayHeight_ + rowSpace) + 1;
381 int32_t x = offset.GetX() < (dayWidth_ + colSpace_ / 2)
382 ? 0
383 : (offset.GetX() - dayWidth_ - colSpace_ / 2 - boundaryRowOffset) / (dayWidth_ + colSpace_) + 1;
384 if (AceApplicationInfo::GetInstance().IsRightToLeft()) {
385 x = DAYS_PER_WEEK - x - 1;
386 }
387 return (y * colCount_ + x);
388 }
389
UpdateCardCalendarAttr(CardCalendarAttr && attr)390 void RenderCalendar::UpdateCardCalendarAttr(CardCalendarAttr&& attr)
391 {
392 auto context = GetContext().Upgrade();
393 if (!context) {
394 return;
395 }
396 if (showHoliday_ != attr.showHoliday || showLunar_ != attr.showLunar) {
397 isNeedRepaint_ = true;
398 }
399 textDirection_ = attr.textDirection;
400 showHoliday_ = attr.showHoliday;
401 showLunar_ = attr.showLunar;
402 needSlide_ = attr.needSlide;
403 offDays_ = attr.offDays;
404 axis_ = attr.axis;
405 isV2Component_ = attr.isV2Component;
406 if (attr.calendarTheme) {
407 calendarTheme_ =
408 context->IsJsCard() ? attr.calendarTheme->GetCardCalendarTheme() : attr.calendarTheme->GetCalendarTheme();
409 }
410
411 type_ = attr.type;
412 UpdateBreakInformation();
413 MarkNeedLayout();
414 }
415
UpdateBreakInformation()416 void RenderCalendar::UpdateBreakInformation()
417 {
418 if ((SystemProperties::GetDeviceType() != DeviceType::WATCH && type_ != CalendarType::SIMPLE) ||
419 calendarController_->GetCurrentIndex() != indexOfContainer_ || firstDayIndex_ < 0) {
420 return;
421 }
422 std::vector<int32_t> holidays;
423 std::vector<int32_t> workDays;
424 auto holidaysValue = dataAdapter_->GetHolidays();
425 auto workDayValue = dataAdapter_->GetWorkDays();
426 StringUtils::StringSplitter(holidaysValue, ',', holidays);
427 StringUtils::StringSplitter(workDayValue, ',', workDays);
428 for (auto& day : calendarDays_) {
429 day.dayMark = "";
430 day.dayMarkValue = "";
431 }
432 for (auto holiday : holidays) {
433 auto index = holiday + firstDayIndex_ - 1;
434 if (index >= 0 && index < (int32_t)calendarDays_.size()) {
435 calendarDays_[index].dayMark = "off";
436 calendarDays_[index].dayMarkValue = "休";
437 }
438 }
439
440 for (auto workDay : workDays) {
441 auto index = workDay + firstDayIndex_ - 1;
442 if (index >= 0 && index < (int32_t)calendarDays_.size()) {
443 calendarDays_[index].dayMark = "work";
444 calendarDays_[index].dayMarkValue = "班";
445 }
446 }
447 }
448
OnSwiperMove()449 void RenderCalendar::OnSwiperMove()
450 {
451 if (calendarController_->IsCrossMonth() && calendarController_->GetCrossMonthDay().month == currentMonth_) {
452 calendarController_->SetCrossMonth(false);
453 return;
454 }
455
456 if (calendarController_->FirstSetToday() && currentMonth_ == calendarController_->GetCurrentMonth()) {
457 if (IsValid(touchIndex_)) {
458 calendarDays_[touchIndex_].touched = false;
459 isNeedRepaint_ = true;
460 MarkNeedRender();
461 }
462 return;
463 }
464
465 if (IsValid(touchIndex_) && touchIndex_ != firstDayIndex_) {
466 if (IsToday(calendarDays_[touchIndex_])) {
467 isNeedRepaint_ = true;
468 }
469 calendarDays_[touchIndex_].touched = false;
470 touchIndex_ = firstDayIndex_;
471 calendarDays_[touchIndex_].touched = true;
472 MarkNeedRender();
473 }
474 }
475
IsValid(int32_t index) const476 bool RenderCalendar::IsValid(int32_t index) const
477 {
478 return index >= 0 && index < static_cast<int32_t>(calendarDays_.size());
479 }
480
IsToday(const CalendarDay & day) const481 bool RenderCalendar::IsToday(const CalendarDay& day) const
482 {
483 auto today = dataAdapter_->GetToday();
484 return today.month == day.month && today.day == day.day;
485 }
486
487 } // namespace OHOS::Ace
488