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