1 /*
2  * Copyright (c) 2022-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 "core/components_ng/pattern/calendar/calendar_month_pattern.h"
17 
18 #include "base/geometry/offset.h"
19 #include "base/i18n/localization.h"
20 #include "base/utils/utils.h"
21 #include "core/common/ace_application_info.h"
22 #include "core/components_ng/base/frame_node.h"
23 #include "core/components_ng/pattern/button/button_layout_property.h"
24 #include "core/components_ng/pattern/button/button_pattern.h"
25 #include "core/components_ng/pattern/linear_layout/linear_layout_pattern.h"
26 #include "core/components_ng/pattern/calendar_picker/calendar_dialog_view.h"
27 #include "core/components_ng/pattern/swiper/swiper_event_hub.h"
28 #include "core/components/slider/slider_theme.h"
29 #include "core/pipeline_ng/pipeline_context.h"
30 
31 namespace OHOS::Ace::NG {
32 namespace {
33 constexpr int32_t CALENDAR_WEEK_DAYS = 7;
34 constexpr int32_t DAILY_FOUR_ROWSPACE = 4;
35 constexpr int32_t DAILY_FIVE_ROWSPACE = 5;
36 constexpr Dimension CALENDAR_DISTANCE_ADJUST_FOCUSED_EVENT = 4.0_vp;
37 constexpr int32_t MONDAY_INDEX = 1;
38 constexpr int32_t TUESDAY_INDEX = 2;
39 constexpr int32_t WEDNESDAY_INDEX = 3;
40 constexpr int32_t THURSDAY_INDEX = 4;
41 constexpr int32_t FRIDAY_INDEX = 5;
42 constexpr int32_t SATURDAY_INDEX = 6;
43 } // namespace
OnAttachToFrameNode()44 void CalendarMonthPattern::OnAttachToFrameNode()
45 {
46     auto host = GetHost();
47     CHECK_NULL_VOID(host);
48     host->GetRenderContext()->SetClipToFrame(true);
49 }
50 
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)51 bool CalendarMonthPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
52 {
53     if (IsCalendarDialog()) {
54         SetColRowSpace();
55     }
56 
57     return !(config.skipMeasure || dirty->SkipMeasureContent());
58 }
59 
~CalendarMonthPattern()60 CalendarMonthPattern::~CalendarMonthPattern()
61 {
62     auto host = GetHost();
63     CHECK_NULL_VOID(host);
64     auto pipeline = host->GetContext();
65     CHECK_NULL_VOID(pipeline);
66     auto accessibilityManager = pipeline->GetAccessibilityManager();
67     CHECK_NULL_VOID(accessibilityManager);
68     accessibilityManager->DeregisterAccessibilitySAObserverCallback(host->GetAccessibilityId());
69 }
70 
GetDaySize(const RefPtr<CalendarTheme> & theme)71 Dimension CalendarMonthPattern::GetDaySize(const RefPtr<CalendarTheme>& theme)
72 {
73     auto pipeline = GetHost()->GetContext();
74     CHECK_NULL_RETURN(pipeline, theme->GetCalendarPickerDayWidthOrHeight());
75     auto fontSizeScale = pipeline->GetFontScale();
76     if (fontSizeScale < theme->GetCalendarPickerLargeScale() || CalendarDialogView::CheckOrientationChange()) {
77         return theme->GetCalendarPickerDayWidthOrHeight();
78     } else {
79         return theme->GetCalendarPickerDayLargeWidthOrHeight();
80     }
81 }
82 
SetColRowSpace()83 void CalendarMonthPattern::SetColRowSpace()
84 {
85     auto host = GetHost();
86     CHECK_NULL_VOID(host);
87     auto contentConstraint = host->GetLayoutProperty()->GetLayoutConstraint();
88     if (!contentConstraint.has_value()) {
89         return;
90     }
91     auto constraint = contentConstraint.value();
92 
93     auto dataSize = GetMonthData().days.size();
94     if (dataSize <= 0) {
95         return;
96     }
97 
98     auto pipelineContext = host->GetContext();
99     CHECK_NULL_VOID(pipelineContext);
100     RefPtr<CalendarTheme> theme = pipelineContext->GetTheme<CalendarTheme>();
101     CHECK_NULL_VOID(theme);
102     auto width = constraint.selfIdealSize.Width().value() - CALENDAR_DISTANCE_ADJUST_FOCUSED_EVENT.ConvertToPx() * 2;
103     auto height = constraint.selfIdealSize.Height().value() - CALENDAR_DISTANCE_ADJUST_FOCUSED_EVENT.ConvertToPx();
104     auto paintProperty = GetPaintProperty<CalendarPaintProperty>();
105     CHECK_NULL_VOID(paintProperty);
106 
107     auto calendarDaySize = GetDaySize(theme);
108     auto space = (width - calendarDaySize.ConvertToPx() * CALENDAR_WEEK_DAYS) / (CALENDAR_WEEK_DAYS - 1);
109     if (Positive(space)) {
110         Dimension colSpace = 0.0_px;
111         colSpace.SetValue(space);
112         paintProperty->UpdateColSpace(colSpace);
113     }
114 
115     auto rowCount = dataSize / CALENDAR_WEEK_DAYS;
116     space = (height - calendarDaySize.ConvertToPx() * (rowCount + 1)) / rowCount;
117     if (!Positive(space)) {
118         return;
119     }
120     Dimension rowSpace = 0.0_px;
121     rowSpace.SetValue(space);
122     paintProperty->UpdateWeekAndDayRowSpace(rowSpace);
123     switch (rowCount) {
124         case 4:
125             paintProperty->UpdateDailyFourRowSpace(rowSpace);
126             break;
127         case 6:
128             paintProperty->UpdateDailySixRowSpace(rowSpace);
129             break;
130         case 5:
131         default:
132             paintProperty->UpdateDailyFiveRowSpace(rowSpace);
133             break;
134     }
135 }
136 
OnModifyDone()137 void CalendarMonthPattern::OnModifyDone()
138 {
139     Pattern::OnModifyDone();
140     InitClickEvent();
141     InitTouchEvent();
142     InitHoverEvent();
143     InitAccessibilityHoverEvent();
144     InitializeCalendarAccessibility();
145 }
146 
SetVirtualNodeUserSelected(int32_t index)147 void CalendarMonthPattern::SetVirtualNodeUserSelected(int32_t index)
148 {
149     if (accessibilityPropertyVec_.size() < 1) {
150         return;
151     }
152     auto host = GetHost();
153     CHECK_NULL_VOID(host);
154     auto layoutProperty = host->GetLayoutProperty();
155     CHECK_NULL_VOID(layoutProperty);
156     auto textDirection = layoutProperty->GetNonAutoLayoutDirection();
157     auto remainderWeek = index % CALENDAR_WEEK_DAYS;
158     int32_t selectedIndex = (textDirection == TextDirection::RTL ?
159         CALENDAR_WEEK_DAYS - remainderWeek * 2 + index - 1 : index);
160     std::string selectMessage;
161     for (int i = 0; i < static_cast<int32_t>(accessibilityPropertyVec_.size()); i++) {
162         if (i == selectedIndex &&
163             obtainedMonth_.days[i].month.month == obtainedMonth_.month &&
164             obtainedMonth_.days[i].month.year == obtainedMonth_.year) {
165             selectMessage += accessibilityPropertyVec_[index]->GetAccessibilityText();
166             continue;
167         }
168         accessibilityPropertyVec_[i]->SetUserSelected(false);
169     }
170     if (!obtainedMonth_.days.empty()) {
171         for (auto& day : obtainedMonth_.days) {
172             day.focused = false;
173         }
174         auto calendarEventHub = GetEventHub<CalendarEventHub>();
175         CHECK_NULL_VOID(calendarEventHub);
176         if (selectedIndex >= 0 && selectedIndex < static_cast<int32_t>(obtainedMonth_.days.size())) {
177             obtainedMonth_.days[selectedIndex].focused = true;
178             auto json = JsonUtil::Create(true);
179             json->Put("day", obtainedMonth_.days[selectedIndex].day);
180             json->Put("month", obtainedMonth_.days[selectedIndex].month.month);
181             json->Put("year", obtainedMonth_.days[selectedIndex].month.year);
182             calendarEventHub->UpdateSelectedChangeEvent(json->ToString());
183         }
184     }
185     host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
186     if (!selectMessage.empty()) {
187         host->OnAccessibilityEvent(AccessibilityEventType::ANNOUNCE_FOR_ACCESSIBILITY, selectedTxt_ + selectMessage);
188         selectedIndex_ = index;
189     }
190 }
191 
InitVirtualButtonClickEvent(RefPtr<FrameNode> frameNode,int32_t index)192 void CalendarMonthPattern::InitVirtualButtonClickEvent(RefPtr<FrameNode> frameNode, int32_t index)
193 {
194     CHECK_NULL_VOID(frameNode);
195     auto gesture = frameNode->GetOrCreateGestureEventHub();
196     CHECK_NULL_VOID(gesture);
197     auto clickCallback = [weak = WeakClaim(this), index](GestureEvent& info) {
198         auto calendarPattern = weak.Upgrade();
199         CHECK_NULL_VOID(calendarPattern);
200         calendarPattern->SetVirtualNodeUserSelected(index);
201     };
202     auto clickListener = MakeRefPtr<ClickEvent>(std::move(clickCallback));
203     gesture->AddClickAfterEvent(clickListener);
204 }
205 
InitClickEvent()206 void CalendarMonthPattern::InitClickEvent()
207 {
208     if (clickListener_) {
209         return;
210     }
211 
212     auto host = GetHost();
213     CHECK_NULL_VOID(host);
214     auto gesture = host->GetOrCreateGestureEventHub();
215     CHECK_NULL_VOID(gesture);
216     auto obtainedMonth = obtainedMonth_;
217     auto clickCallback = [weak = WeakClaim(this), obtainedMonth](GestureEvent& info) {
218         auto calendarPattern = weak.Upgrade();
219         CHECK_NULL_VOID(calendarPattern);
220         auto localLocation = info.GetFingerList().begin()->localLocation_;
221         calendarPattern->OnClick(localLocation, calendarPattern->obtainedMonth_);
222     };
223     clickListener_ = MakeRefPtr<ClickEvent>(std::move(clickCallback));
224     gesture->AddClickEvent(clickListener_);
225 }
226 
InitTouchEvent()227 void CalendarMonthPattern::InitTouchEvent()
228 {
229     if (touchListener_) {
230         return;
231     }
232 
233     auto host = GetHost();
234     CHECK_NULL_VOID(host);
235     auto gesture = host->GetOrCreateGestureEventHub();
236     CHECK_NULL_VOID(gesture);
237     auto touchCallback = [weak = WeakClaim(this)](const TouchEventInfo& info) {
238         auto calendarPattern = weak.Upgrade();
239         CHECK_NULL_VOID(calendarPattern);
240         if (info.GetTouches().front().GetTouchType() == TouchType::DOWN) {
241             calendarPattern->OnTouchEvent(info.GetTouches().front().GetLocalLocation(), true);
242         }
243         if (info.GetTouches().front().GetTouchType() == TouchType::UP ||
244             info.GetTouches().front().GetTouchType() == TouchType::CANCEL) {
245             calendarPattern->OnTouchEvent(info.GetTouches().front().GetLocalLocation(), false);
246         }
247     };
248     touchListener_ = MakeRefPtr<TouchEventImpl>(std::move(touchCallback));
249     gesture->AddTouchEvent(touchListener_);
250 }
251 
InitHoverEvent()252 void CalendarMonthPattern::InitHoverEvent()
253 {
254     if (hoverListener_) {
255         return;
256     }
257 
258     auto host = GetHost();
259     CHECK_NULL_VOID(host);
260     auto eventHub = GetEventHub<CalendarEventHub>();
261     CHECK_NULL_VOID(eventHub);
262     auto inputHub = eventHub->GetOrCreateInputEventHub();
263     CHECK_NULL_VOID(inputHub);
264     auto hoverCallback = [weak = WeakClaim(this)](bool state) {
265         auto calendarPattern = weak.Upgrade();
266         CHECK_NULL_VOID(calendarPattern);
267         calendarPattern->SetHoverState(state);
268         if (!state) {
269             Offset localLocation;
270             calendarPattern->OnHoverEvent(localLocation, false);
271         }
272     };
273     hoverListener_ = MakeRefPtr<InputEvent>(std::move(hoverCallback));
274     inputHub->AddOnHoverEvent(hoverListener_);
275     auto mouseCallback = [weak = WeakClaim(this)](MouseInfo& info) {
276         auto calendarPattern = weak.Upgrade();
277         CHECK_NULL_VOID(calendarPattern);
278         calendarPattern->OnHoverEvent(info.GetLocalLocation(), calendarPattern->GetHoverState());
279     };
280     inputHub->SetMouseEvent(std::move(mouseCallback));
281 }
282 
OnClick(Offset & localLocation,const ObtainedMonth & obtainedMonth)283 void CalendarMonthPattern::OnClick(Offset& localLocation, const ObtainedMonth& obtainedMonth)
284 {
285     auto host = GetHost();
286     CHECK_NULL_VOID(host);
287     auto pattern = host->GetPattern<CalendarMonthPattern>();
288     CHECK_NULL_VOID(pattern);
289     auto index = JudgeArea(localLocation);
290     pattern->obtainedMonth_ = obtainedMonth;
291     if (!obtainedMonth_.days.empty()) {
292         for (auto& day : pattern->obtainedMonth_.days) {
293             day.focused = false;
294         }
295         auto calendarEventHub = GetEventHub<CalendarEventHub>();
296         CHECK_NULL_VOID(calendarEventHub);
297         if (index >= 0 && index < static_cast<int32_t>(obtainedMonth.days.size())) {
298             pattern->obtainedMonth_.days[index].focused = true;
299             auto json = JsonUtil::Create(true);
300             json->Put("day", obtainedMonth.days[index].day);
301             json->Put("month", obtainedMonth.days[index].month.month);
302             json->Put("year", obtainedMonth.days[index].month.year);
303             calendarEventHub->UpdateSelectedChangeEvent(json->ToString());
304         }
305         host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
306     }
307 }
308 
OnTouchEvent(const Offset & localLocation,bool isPressed)309 void CalendarMonthPattern::OnTouchEvent(const Offset& localLocation, bool isPressed)
310 {
311     if (!isCalendarDialog_ || obtainedMonth_.days.empty()) {
312         return;
313     }
314     auto index = JudgeArea(localLocation);
315     if (!((index < 0 || index >= static_cast<int32_t>(obtainedMonth_.days.size()))) && isPressed) {
316         obtainedMonth_.days[index].isPressing = true;
317     } else {
318         for (auto& day : obtainedMonth_.days) {
319             day.isPressing = false;
320         }
321     }
322 
323     auto host = GetHost();
324     CHECK_NULL_VOID(host);
325     host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
326 }
327 
OnHoverEvent(const Offset & localLocation,bool state)328 void CalendarMonthPattern::OnHoverEvent(const Offset& localLocation, bool state)
329 {
330     if (!isCalendarDialog_ || obtainedMonth_.days.empty()) {
331         return;
332     }
333     int32_t index = JudgeArea(localLocation);
334     if (index < 0 || index >= static_cast<int32_t>(obtainedMonth_.days.size())) {
335         return;
336     }
337     for (auto& day : obtainedMonth_.days) {
338         day.isHovering = false;
339     }
340     if (state) {
341         obtainedMonth_.days[index].isHovering = true;
342     }
343 
344     auto host = GetHost();
345     CHECK_NULL_VOID(host);
346     host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
347 }
348 
JudgeArea(const Offset & offset)349 int32_t CalendarMonthPattern::JudgeArea(const Offset& offset)
350 {
351     auto host = GetHost();
352     CHECK_NULL_RETURN(host, false);
353     auto paintProperty = host->GetPaintProperty<CalendarPaintProperty>();
354     CHECK_NULL_RETURN(paintProperty, false);
355     auto pipelineContext = host->GetContext();
356     CHECK_NULL_RETURN(pipelineContext, false);
357     RefPtr<CalendarTheme> theme = pipelineContext->GetTheme<CalendarTheme>();
358     CHECK_NULL_RETURN(theme, false);
359     auto topPadding = isCalendarDialog_ ? 0.0 : theme->GetCalendarTheme().topPadding.ConvertToPx();
360     auto weekHeight = paintProperty->GetWeekHeight().value_or(theme->GetCalendarTheme().weekHeight).ConvertToPx();
361     auto weekAndDayRowSpace =
362         paintProperty->GetWeekAndDayRowSpace().value_or(theme->GetCalendarTheme().weekAndDayRowSpace).ConvertToPx();
363     auto dayHeight = paintProperty->GetDayHeight().value_or(theme->GetCalendarTheme().dayHeight).ConvertToPx();
364     auto dayWidth = paintProperty->GetDayWidth().value_or(theme->GetCalendarTheme().dayWidth).ConvertToPx();
365     const static int32_t columnsOfData = 7;
366     auto colSpace = paintProperty->GetColSpaceValue({}).ConvertToPx() <= 0
367                         ? theme->GetCalendarTheme().colSpace.ConvertToPx()
368                         : paintProperty->GetColSpaceValue({}).ConvertToPx();
369     auto dailyFourRowSpace = NonPositive(paintProperty->GetDailyFourRowSpaceValue({}).ConvertToPx())
370                                  ? theme->GetCalendarTheme().dailySixRowSpace.ConvertToPx()
371                                  : paintProperty->GetDailyFourRowSpaceValue({}).ConvertToPx();
372     auto dailyFiveRowSpace = paintProperty->GetDailyFiveRowSpaceValue({}).ConvertToPx() <= 0
373                                  ? theme->GetCalendarTheme().dailyFiveRowSpace.ConvertToPx()
374                                  : paintProperty->GetDailyFiveRowSpaceValue({}).ConvertToPx();
375     auto dailySixRowSpace = paintProperty->GetDailySixRowSpaceValue({}).ConvertToPx() <= 0
376                                 ? theme->GetCalendarTheme().dailySixRowSpace.ConvertToPx()
377                                 : paintProperty->GetDailySixRowSpaceValue({}).ConvertToPx();
378     auto rows = (static_cast<int32_t>(obtainedMonth_.days.size()) / columnsOfData);
379     auto rowSpace = dailySixRowSpace;
380     switch (rows) {
381         case 4: {
382             rowSpace = dailyFourRowSpace;
383             break;
384         }
385         case 5: {
386             rowSpace = dailyFiveRowSpace;
387             break;
388         }
389         default:
390             break;
391     }
392     auto browHeight = weekHeight + topPadding + weekAndDayRowSpace;
393     auto maxHeight = host->GetGeometryNode()->GetFrameSize().Height();
394     auto maxWidth = host->GetGeometryNode()->GetFrameSize().Width();
395     if ((offset.GetX() < 0) || (offset.GetX() > maxWidth) || (offset.GetY() < browHeight) ||
396         (offset.GetY() > maxHeight) || LessOrEqual(dayHeight, 0.0) || LessOrEqual(dayWidth, 0.0)) {
397         return -1;
398     }
399     auto height = offset.GetY() - browHeight;
400     int32_t y =
401         height < (dayHeight + rowSpace / 2) ? 0 : (height - dayHeight - rowSpace / 2) / (dayHeight + rowSpace) + 1;
402     int32_t x = offset.GetX() < (dayWidth + colSpace / 2)
403                     ? 0
404                     : (offset.GetX() - dayWidth - colSpace / 2) / (dayWidth + colSpace) + 1;
405     auto textDirection = host->GetLayoutProperty()->GetNonAutoLayoutDirection();
406     if (textDirection == TextDirection::RTL) {
407         x = columnsOfData - x - 1;
408     }
409     return (y * columnsOfData + x);
410 }
411 
412 class CalendarAccessibilitySAObserverCallback : public AccessibilitySAObserverCallback {
413 public:
CalendarAccessibilitySAObserverCallback(const WeakPtr<CalendarMonthPattern> & weakCalendarPattern,int64_t accessibilityId)414     CalendarAccessibilitySAObserverCallback(
415         const WeakPtr<CalendarMonthPattern> &weakCalendarPattern, int64_t accessibilityId)
416         : AccessibilitySAObserverCallback(accessibilityId), weakCalendarPattern_(weakCalendarPattern)
417     {}
418 
419     ~CalendarAccessibilitySAObserverCallback() override = default;
420 
OnState(bool state)421     bool OnState(bool state) override
422     {
423         auto calendarPattern = weakCalendarPattern_.Upgrade();
424         CHECK_NULL_RETURN(calendarPattern, false);
425         if (state) {
426             calendarPattern->InitCurrentVirtualNode();
427         } else {
428             calendarPattern->ClearCalendarVirtualNode();
429         }
430         return true;
431     }
432 private:
433     WeakPtr<CalendarMonthPattern> weakCalendarPattern_;
434 };
435 
InitializeCalendarAccessibility()436 void CalendarMonthPattern::InitializeCalendarAccessibility()
437 {
438     auto host = GetHost();
439     CHECK_NULL_VOID(host);
440     auto pipeline = host->GetContext();
441     CHECK_NULL_VOID(pipeline);
442     auto accessibilityManager = pipeline->GetAccessibilityManager();
443     CHECK_NULL_VOID(accessibilityManager);
444     accessibilitySAObserverCallback_ = std::make_shared<CalendarAccessibilitySAObserverCallback>(
445         WeakClaim(this), host->GetAccessibilityId());
446     accessibilityManager->RegisterAccessibilitySAObserverCallback(host->GetAccessibilityId(),
447         accessibilitySAObserverCallback_);
448     if (margin_ == 0) {
449         auto paintProperty = host->GetPaintProperty<CalendarPaintProperty>();
450         CHECK_NULL_VOID(paintProperty);
451         dayHeight_ = paintProperty->GetDayHeight().value_or(Dimension(0.0f)).ConvertToPx();
452         dayWidth_ = paintProperty->GetDayWidth().value_or(Dimension(0.0f)).ConvertToPx();
453         RefPtr<CalendarTheme> theme = pipeline->GetTheme<CalendarTheme>();
454         CHECK_NULL_VOID(theme);
455         auto sliderTheme = pipeline->GetTheme<SliderTheme>();
456         CHECK_NULL_VOID(sliderTheme);
457         margin_ = theme->GetDialogMargin().ConvertToPx();
458         selectedTxt_ = sliderTheme->GetSelectedTxt();
459         disabledDesc_ = sliderTheme->GetDisabelDesc();
460         deviceOrientation_ = SystemProperties::GetDeviceOrientation();
461     }
462 }
463 
InitCurrentVirtualNode()464 void CalendarMonthPattern::InitCurrentVirtualNode()
465 {
466     auto deviceOrientation = SystemProperties::GetDeviceOrientation();
467     if ((!isInitVirtualNode_ || buttonAccessibilityNodeVec_.size() != obtainedMonth_.days.size() ||
468         deviceOrientation_ != deviceOrientation) &&
469         monthState_ == MonthState::CUR_MONTH) {
470         isInitVirtualNode_ = InitCalendarVirtualNode();
471     } else {
472         FireModifyAccessibilityVirtualNode(obtainedMonth_);
473     }
474 }
475 
ClearFocusCalendarDay()476 void CalendarMonthPattern::ClearFocusCalendarDay()
477 {
478     focusedCalendarDay_.index = 0;
479     deviceOrientation_ = SystemProperties::GetDeviceOrientation();
480     auto lineNode = lineNode_.Upgrade();
481     CHECK_NULL_VOID(lineNode);
482     auto lineNodeProp = lineNode->GetLayoutProperty();
483     CHECK_NULL_VOID(lineNodeProp);
484     if (monthState_ == MonthState::CUR_MONTH) {
485         lineNodeProp->UpdateVisibility(VisibleType::VISIBLE);
486     } else {
487         lineNodeProp->UpdateVisibility(VisibleType::GONE);
488     }
489 }
490 
ClearCalendarVirtualNode()491 void CalendarMonthPattern::ClearCalendarVirtualNode()
492 {
493     auto host = GetHost();
494     CHECK_NULL_VOID(host);
495     buttonAccessibilityNodeVec_.clear();
496     accessibilityPropertyVec_.clear();
497     auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
498     CHECK_NULL_VOID(accessibilityProperty);
499     accessibilityProperty->SaveAccessibilityVirtualNode(nullptr);
500 }
501 
SetLineNodeSize(RefPtr<FrameNode> lineNode)502 void CalendarMonthPattern::SetLineNodeSize(RefPtr<FrameNode> lineNode)
503 {
504     auto host = GetHost();
505     CHECK_NULL_VOID(host);
506     auto hostLayoutProperty = host->GetLayoutProperty();
507     CHECK_NULL_VOID(hostLayoutProperty);
508     auto width = hostLayoutProperty->GetLayoutConstraint()->selfIdealSize.Width().value_or(Infinity<float>());
509     auto height = hostLayoutProperty->GetLayoutConstraint()->selfIdealSize.Height().value_or(Infinity<float>());
510     CHECK_NULL_VOID(lineNode);
511     auto layoutProperty = lineNode->GetLayoutProperty<LayoutProperty>();
512     CHECK_NULL_VOID(layoutProperty);
513     layoutProperty->UpdateUserDefinedIdealSize(CalcSize(CalcLength(width), CalcLength(height)));
514 }
515 
SetFocusNode(int32_t index,bool isDeviceOrientation)516 void CalendarMonthPattern::SetFocusNode(int32_t index, bool isDeviceOrientation)
517 {
518     auto host = GetHost();
519     CHECK_NULL_VOID(host);
520     auto layoutProperty = host->GetLayoutProperty();
521     CHECK_NULL_VOID(layoutProperty);
522     auto textDirection = layoutProperty->GetNonAutoLayoutDirection();
523     auto remainderWeek = index % CALENDAR_WEEK_DAYS;
524     int32_t selectedIndex = (textDirection == TextDirection::RTL ?
525         CALENDAR_WEEK_DAYS - remainderWeek * 2 + index - 1 : index);
526     if (isDeviceOrientation) {
527         selectedIndex = index;
528     }
529     if (selectedIndex >= static_cast<int32_t>(buttonAccessibilityNodeVec_.size()) || selectedIndex < 0) {
530         return;
531     }
532     buttonAccessibilityNodeVec_[selectedIndex]->OnAccessibilityEvent(AccessibilityEventType::REQUEST_FOCUS);
533     selectedIndex_ = selectedIndex;
534 }
535 
InitCalendarVirtualNode()536 bool CalendarMonthPattern::InitCalendarVirtualNode()
537 {
538     auto host = GetHost();
539     CHECK_NULL_RETURN(host, false);
540     buttonAccessibilityNodeVec_.clear();
541     accessibilityPropertyVec_.clear();
542     auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
543     CHECK_NULL_RETURN(accessibilityProperty, false);
544     accessibilityProperty->SaveAccessibilityVirtualNode(nullptr);
545     auto lineNode = FrameNode::CreateFrameNode(V2::COLUMN_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
546         AceType::MakeRefPtr<LinearLayoutPattern>(true));
547     for (auto& day : obtainedMonth_.days) {
548         auto buttonNode = AddButtonNodeIntoVirtual(day);
549         lineNode->AddChild(buttonNode);
550         buttonAccessibilityNodeVec_.emplace_back(buttonNode);
551         ChangeVirtualNodeContent(day);
552     }
553     SetLineNodeSize(lineNode);
554     SetCalendarAccessibilityLevel(AccessibilityProperty::Level::NO_STR);
555     accessibilityProperty->SetAccessibilityText(" ");
556     auto virtualFrameNode = AceType::DynamicCast<NG::FrameNode>(lineNode);
557     CHECK_NULL_RETURN(virtualFrameNode, false);
558     virtualFrameNode->SetAccessibilityNodeVirtual();
559     virtualFrameNode->SetAccessibilityVirtualNodeParent(AceType::DynamicCast<NG::UINode>(host));
560     virtualFrameNode->SetFirstAccessibilityVirtualNode();
561     FrameNode::ProcessOffscreenNode(virtualFrameNode);
562     accessibilityProperty->SaveAccessibilityVirtualNode(lineNode);
563     lineNode_ = lineNode;
564     host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
565     auto deviceOrientation = SystemProperties::GetDeviceOrientation();
566     if (deviceOrientation_ != deviceOrientation && !isFirstEnter_) {
567         SetFocusNode(focusedCalendarDay_.index, true);
568         deviceOrientation_ = deviceOrientation;
569     }
570     if (!isFirstEnter_) {
571         return true;
572     }
573     for (auto &day : obtainedMonth_.days) {
574         if (day.isSelected) {
575             SetFocusNode(day.index);
576             isFirstEnter_ = false;
577             focusedCalendarDay_ = day;
578             break;
579         }
580     }
581     return true;
582 }
583 
GetXYByIndex(const int32_t index)584 std::pair<int32_t, int32_t> GetXYByIndex(const int32_t index)
585 {
586     if (index < CALENDAR_WEEK_DAYS - 1) {
587         return {index, 0};
588     } else {
589         return {(index % CALENDAR_WEEK_DAYS), (index / CALENDAR_WEEK_DAYS)};
590     }
591 }
592 
ChangeVirtualNodeState(const CalendarDay & calendarDay)593 void CalendarMonthPattern::ChangeVirtualNodeState(const CalendarDay& calendarDay)
594 {
595     if (selectedIndex_ >= static_cast<int32_t>(accessibilityPropertyVec_.size()) || selectedIndex_ < 0) {
596         return;
597     }
598     if (calendarDay.index != selectedIndex_ && calendarDay.month.month == obtainedMonth_.month) {
599         accessibilityPropertyVec_[selectedIndex_]->SetUserSelected(true);
600     }
601 }
602 
AddButtonNodeIntoVirtual(const CalendarDay & calendarDay)603 RefPtr<FrameNode> CalendarMonthPattern::AddButtonNodeIntoVirtual(const CalendarDay& calendarDay)
604 {
605     auto buttonNode = FrameNode::CreateFrameNode(
606         V2::BUTTON_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<ButtonPattern>());
607     CHECK_NULL_RETURN(buttonNode, nullptr);
608     auto buttonAccessibilityProperty = buttonNode->GetAccessibilityProperty<AccessibilityProperty>();
609     CHECK_NULL_RETURN(buttonAccessibilityProperty, nullptr);
610     auto host = GetHost();
611     CHECK_NULL_RETURN(host, nullptr);
612     auto pipeline = host->GetContext();
613     if (pipeline) {
614         UpdateAccessibilityButtonNode(buttonNode, calendarDay.index);
615     } else {
616         UpdateButtonNodeWithoutTheme(buttonNode, calendarDay.index);
617     }
618     accessibilityPropertyVec_.emplace_back(buttonAccessibilityProperty);
619     InitVirtualButtonClickEvent(buttonNode, calendarDay.index);
620     buttonAccessibilityProperty->SetOnAccessibilityFocusCallback([weak = WeakClaim(this), calendarDay](bool focus) {
621         if (focus) {
622             auto calendar = weak.Upgrade();
623             CHECK_NULL_VOID(calendar);
624             calendar->focusedCalendarDay_ = calendarDay;
625             calendar->ChangeVirtualNodeState(calendarDay);
626         }
627     });
628     return buttonNode;
629 }
630 
GetRowSpace(RefPtr<CalendarPaintProperty> paintProperty,RefPtr<CalendarTheme> theme,size_t daySize)631 float GetRowSpace(RefPtr<CalendarPaintProperty> paintProperty, RefPtr<CalendarTheme> theme, size_t daySize)
632 {
633     auto dailyFourRowSpace = NonPositive(paintProperty->GetDailyFourRowSpaceValue({}).ConvertToPx())
634                                  ? theme->GetCalendarTheme().dailySixRowSpace.ConvertToPx()
635                                  : paintProperty->GetDailyFourRowSpaceValue({}).ConvertToPx();
636     auto dailyFiveRowSpace = paintProperty->GetDailyFiveRowSpaceValue({}).ConvertToPx() <= 0
637                                  ? theme->GetCalendarTheme().dailyFiveRowSpace.ConvertToPx()
638                                  : paintProperty->GetDailyFiveRowSpaceValue({}).ConvertToPx();
639     auto dailySixRowSpace = paintProperty->GetDailySixRowSpaceValue({}).ConvertToPx() <= 0
640                                 ? theme->GetCalendarTheme().dailySixRowSpace.ConvertToPx()
641                                 : paintProperty->GetDailySixRowSpaceValue({}).ConvertToPx();
642     auto rows = (static_cast<int32_t>(daySize) / CALENDAR_WEEK_DAYS);
643     auto rowSpace = dailySixRowSpace;
644     switch (rows) {
645         case DAILY_FOUR_ROWSPACE: {
646             rowSpace = dailyFourRowSpace;
647             break;
648         }
649         case DAILY_FIVE_ROWSPACE: {
650             rowSpace = dailyFiveRowSpace;
651             break;
652         }
653         default:
654             break;
655     }
656     return rowSpace;
657 }
658 
SetCalendarAccessibilityLevel(const std::string & level)659 void CalendarMonthPattern::SetCalendarAccessibilityLevel(const std::string& level)
660 {
661     auto host = GetHost();
662     CHECK_NULL_VOID(host);
663     auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
664     CHECK_NULL_VOID(accessibilityProperty);
665     accessibilityProperty->SetAccessibilityLevel(level);
666     auto parent = host->GetParent();
667     while (parent && parent->GetTag() != V2::DIALOG_ETS_TAG) {
668         auto parentNode = AceType::DynamicCast<NG::FrameNode>(parent);
669         CHECK_NULL_VOID(parentNode);
670         auto parentAccessibilityProperty = parentNode->GetAccessibilityProperty<AccessibilityProperty>();
671         CHECK_NULL_VOID(parentAccessibilityProperty);
672         parentAccessibilityProperty->SetAccessibilityLevel(level);
673         parent = parent->GetParent();
674     }
675 }
676 
InitAccessibilityHoverEvent()677 void CalendarMonthPattern::InitAccessibilityHoverEvent()
678 {
679     auto host = GetHost();
680     CHECK_NULL_VOID(host);
681     auto eventHub = host->GetOrCreateInputEventHub();
682     CHECK_NULL_VOID(eventHub);
683     eventHub->SetAccessibilityHoverEvent([weak = WeakClaim(this)](bool isHover, AccessibilityHoverInfo& info) {
684         auto calendar = weak.Upgrade();
685         CHECK_NULL_VOID(calendar);
686         calendar->HandleAccessibilityHoverEvent(isHover, info);
687     });
688 }
689 
HandleAccessibilityHoverEvent(bool isHover,AccessibilityHoverInfo & info)690 void CalendarMonthPattern::HandleAccessibilityHoverEvent(bool isHover, AccessibilityHoverInfo& info)
691 {
692     if (isHover) {
693         isOnHover_ = true;
694     } else if (!isHover) {
695         isOnHover_ = false;
696     }
697 }
698 
GetRowSpaceWithoutTheme(RefPtr<CalendarPaintProperty> paintProperty,size_t daySize)699 float GetRowSpaceWithoutTheme(RefPtr<CalendarPaintProperty> paintProperty, size_t daySize)
700 {
701     auto dailyFourRowSpace = NonPositive(paintProperty->GetDailyFourRowSpaceValue({}).ConvertToPx())
702                                  ? 0.0f
703                                  : paintProperty->GetDailyFourRowSpaceValue({}).ConvertToPx();
704     auto dailyFiveRowSpace = paintProperty->GetDailyFiveRowSpaceValue({}).ConvertToPx() <= 0
705                                  ? 0.0f
706                                  : paintProperty->GetDailyFiveRowSpaceValue({}).ConvertToPx();
707     auto dailySixRowSpace = paintProperty->GetDailySixRowSpaceValue({}).ConvertToPx() <= 0
708                                 ? 0.0f
709                                 : paintProperty->GetDailySixRowSpaceValue({}).ConvertToPx();
710     auto rows = (static_cast<int32_t>(daySize) / CALENDAR_WEEK_DAYS);
711     auto rowSpace = dailySixRowSpace;
712     switch (rows) {
713         case DAILY_FOUR_ROWSPACE: {
714             rowSpace = dailyFourRowSpace;
715             break;
716         }
717         case DAILY_FIVE_ROWSPACE: {
718             rowSpace = dailyFiveRowSpace;
719             break;
720         }
721         default:
722             break;
723     }
724     return rowSpace;
725 }
726 
UpdateButtonNodeWithoutTheme(RefPtr<FrameNode> frameNode,int32_t index)727 void CalendarMonthPattern::UpdateButtonNodeWithoutTheme(RefPtr<FrameNode> frameNode, int32_t index)
728 {
729     auto host = GetHost();
730     CHECK_NULL_VOID(host);
731     auto paintProperty = host->GetPaintProperty<CalendarPaintProperty>();
732     CHECK_NULL_VOID(paintProperty);
733     auto dayHeight = paintProperty->GetDayHeight().value_or(Dimension(0.0f)).ConvertToPx();
734     auto dayWidth = paintProperty->GetDayWidth().value_or(Dimension(0.0f)).ConvertToPx();
735     CHECK_NULL_VOID(frameNode);
736     auto buttonLayoutProperty = frameNode->GetLayoutProperty<ButtonLayoutProperty>();
737     CHECK_NULL_VOID(buttonLayoutProperty);
738     buttonLayoutProperty->UpdateUserDefinedIdealSize(CalcSize(CalcLength(dayWidth), CalcLength(dayHeight)));
739     auto pos = GetXYByIndex(index);
740     auto colSpace = paintProperty->GetColSpaceValue({}).ConvertToPx() <= 0
741                     ? 0.0f
742                     : paintProperty->GetColSpaceValue({}).ConvertToPx();
743     Dimension buttonOffsetX = Dimension(margin_ / 2 + (colSpace + dayWidth) * pos.first);
744     auto weekHeight = paintProperty->GetWeekHeight().value_or(Dimension(0.0f)).ConvertToPx();
745     auto rowSpace = GetRowSpaceWithoutTheme(paintProperty, obtainedMonth_.days.size());
746     auto weekAndDayRowSpace =
747         paintProperty->GetWeekAndDayRowSpace().value_or(Dimension(0.0f)).ConvertToPx();
748     auto browHeight = weekHeight + weekAndDayRowSpace;
749     Dimension buttonOffsetY = Dimension(browHeight + (dayHeight + rowSpace) * pos.second);
750     auto renderContext = frameNode->GetRenderContext();
751     CHECK_NULL_VOID(renderContext);
752     renderContext->UpdatePosition(OffsetT(buttonOffsetX, buttonOffsetY));
753     frameNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
754 }
755 
UpdateAccessibilityButtonNode(RefPtr<FrameNode> frameNode,int32_t index)756 void CalendarMonthPattern::UpdateAccessibilityButtonNode(RefPtr<FrameNode> frameNode, int32_t index)
757 {
758     auto host = GetHost();
759     CHECK_NULL_VOID(host);
760     auto paintProperty = host->GetPaintProperty<CalendarPaintProperty>();
761     CHECK_NULL_VOID(paintProperty);
762     auto pipelineContext = host->GetContext();
763     CHECK_NULL_VOID(pipelineContext);
764     RefPtr<CalendarTheme> theme = pipelineContext->GetTheme<CalendarTheme>();
765     CHECK_NULL_VOID(theme);
766     auto dayHeight = paintProperty->GetDayHeight().value_or(theme->GetCalendarTheme().dayHeight).ConvertToPx();
767     auto dayWidth = paintProperty->GetDayWidth().value_or(theme->GetCalendarTheme().dayWidth).ConvertToPx();
768     CHECK_NULL_VOID(frameNode);
769     auto buttonLayoutProperty = frameNode->GetLayoutProperty<ButtonLayoutProperty>();
770     CHECK_NULL_VOID(buttonLayoutProperty);
771     buttonLayoutProperty->UpdateUserDefinedIdealSize(CalcSize(CalcLength(dayWidth), CalcLength(dayHeight)));
772     auto renderContext = frameNode->GetRenderContext();
773     CHECK_NULL_VOID(renderContext);
774     auto pos = GetXYByIndex(index);
775     auto colSpace = paintProperty->GetColSpaceValue({}).ConvertToPx() <= 0
776                     ? theme->GetCalendarTheme().colSpace.ConvertToPx()
777                     : paintProperty->GetColSpaceValue({}).ConvertToPx();
778     Dimension buttonOffsetX = Dimension(margin_ / 2 + (colSpace + dayWidth) * pos.first);
779     auto topPadding = isCalendarDialog_ ? 0.0 : theme->GetCalendarTheme().topPadding.ConvertToPx();
780     auto weekHeight = paintProperty->GetWeekHeight().value_or(theme->GetCalendarTheme().weekHeight).ConvertToPx();
781     auto rowSpace = GetRowSpace(paintProperty, theme, obtainedMonth_.days.size());
782     auto weekAndDayRowSpace =
783         paintProperty->GetWeekAndDayRowSpace().value_or(theme->GetCalendarTheme().weekAndDayRowSpace).ConvertToPx();
784     auto browHeight = weekHeight + topPadding + weekAndDayRowSpace;
785     Dimension buttonOffsetY = Dimension(browHeight + (dayHeight + rowSpace) * pos.second);
786     renderContext->UpdatePosition(OffsetT(buttonOffsetX, buttonOffsetY));
787     frameNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
788 }
789 
GetDayStr(int32_t index)790 std::string CalendarMonthPattern::GetDayStr(int32_t index)
791 {
792     auto host = GetHost();
793     CHECK_NULL_RETURN(host, "");
794     auto pipelineContext = host->GetContext();
795     CHECK_NULL_RETURN(pipelineContext, "");
796     RefPtr<CalendarTheme> theme = pipelineContext->GetTheme<CalendarTheme>();
797     CHECK_NULL_RETURN(theme, "");
798     switch (index) {
799         case MONDAY_INDEX:
800             return theme->GetCalendarTheme().monday;
801         case TUESDAY_INDEX:
802             return theme->GetCalendarTheme().tuesday;
803         case WEDNESDAY_INDEX:
804             return theme->GetCalendarTheme().wednesday;
805         case THURSDAY_INDEX:
806             return theme->GetCalendarTheme().thursday;
807         case FRIDAY_INDEX:
808             return theme->GetCalendarTheme().friday;
809         case SATURDAY_INDEX:
810             return theme->GetCalendarTheme().saturday;
811         default:
812             return theme->GetCalendarTheme().sunday;
813     }
814 }
815 
ChangeVirtualNodeContent(const CalendarDay & calendarDay)816 void CalendarMonthPattern::ChangeVirtualNodeContent(const CalendarDay& calendarDay)
817 {
818     auto host = GetHost();
819     CHECK_NULL_VOID(host);
820     auto layoutProperty = host->GetLayoutProperty();
821     CHECK_NULL_VOID(layoutProperty);
822     auto textDirection = layoutProperty->GetNonAutoLayoutDirection();
823     auto remainderWeek = calendarDay.index % CALENDAR_WEEK_DAYS;
824     int32_t index = (textDirection == TextDirection::RTL ?
825         CALENDAR_WEEK_DAYS - remainderWeek * 2 + calendarDay.index - 1 : calendarDay.index);
826     if (index >= static_cast<int32_t>(buttonAccessibilityNodeVec_.size()) || index < 0) {
827         return;
828     }
829     std::string message;
830     if (calendarDay.month.year == calendarDay_.month.year && calendarDay.month.month == calendarDay_.month.month &&
831                       calendarDay.day == calendarDay_.day) {
832         message += Localization::GetInstance()->GetEntryLetters("calendar.today");
833     }
834     message += std::to_string(calendarDay.month.year) + "/";
835     message += std::to_string(calendarDay.month.month) + "/";
836     message += std::to_string(calendarDay.day);
837     message += GetDayStr(remainderWeek);
838     auto node = buttonAccessibilityNodeVec_[index];
839     auto buttonAccessibilityProperty = node->GetAccessibilityProperty<AccessibilityProperty>();
840     CHECK_NULL_VOID(buttonAccessibilityProperty);
841     if (calendarDay.month.month != obtainedMonth_.month) {
842         buttonAccessibilityProperty->SetAccessibilityDescription(disabledDesc_);
843     } else if (index == selectedIndex_) {
844         buttonAccessibilityProperty->SetAccessibilityDescription(" ");
845     } else {
846         buttonAccessibilityProperty->SetAccessibilityDescription("");
847     }
848     buttonAccessibilityProperty->SetUserDisabled(calendarDay.month.month != obtainedMonth_.month ? true : false);
849     buttonAccessibilityProperty->SetUserSelected(false);
850     buttonAccessibilityProperty->SetAccessibilityText(message);
851 }
852 
FireModifyAccessibilityVirtualNode(const ObtainedMonth & currentData)853 void CalendarMonthPattern::FireModifyAccessibilityVirtualNode(const ObtainedMonth& currentData)
854 {
855     if (isInitVirtualNode_) {
856         auto pipeline = PipelineContext::GetCurrentContext();
857         CHECK_NULL_VOID(pipeline);
858         pipeline->AddAfterRenderTask([weak = WeakClaim(this), currentData]() {
859             auto calendarMonthPattern = weak.Upgrade();
860             CHECK_NULL_VOID(calendarMonthPattern);
861             calendarMonthPattern->ModifyAccessibilityVirtualNode(currentData);
862         });
863     }
864 }
865 
ModifyAccessibilityVirtualNode(const ObtainedMonth & currentData)866 void CalendarMonthPattern::ModifyAccessibilityVirtualNode(const ObtainedMonth& currentData)
867 {
868     if (buttonAccessibilityNodeVec_.size() < 1) {
869         return;
870     }
871     for (auto& day : currentData.days) {
872         ChangeVirtualNodeContent(day);
873     }
874 }
875 } // namespace OHOS::Ace::NG
876