1 /*
2  * Copyright (c) 2021 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/calendar_data_adapter.h"
17 
18 #include "base/i18n/localization.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 constexpr int32_t CALENDAR_DAYS_FIVE_ROW_COUNT = 35;
24 constexpr int32_t CALENDAR_DAYS_SIX_ROW_COUNT = 42;
25 constexpr int32_t CALENDAR_MONTH_COUNT = 3;
26 
ParseDayNumberProp(const std::unique_ptr<JsonValue> & item,const std::string & key,int32_t & value)27 bool ParseDayNumberProp(const std::unique_ptr<JsonValue>& item, const std::string& key, int32_t& value)
28 {
29     if (!item) {
30         LOGE("item is nullptr");
31         return false;
32     }
33     if (!item->Contains(key)) {
34         LOGE("parse day number error, not find key:%{public}s", key.c_str());
35         return false;
36     }
37 
38     auto dayValue = item->GetValue(key);
39     if (dayValue->IsNumber()) {
40         value = dayValue->GetInt();
41         return true;
42     } else {
43         LOGW("parse day number type error");
44         return false;
45     }
46 }
47 
48 } // namespace
49 
50 std::string CalendarDataAdapter::cachePath_;
51 
CalendarDataAdapter(const CalendarDataAdapterAction & dataAdapterAction,const WeakPtr<PipelineContext> & pipelineContext)52 CalendarDataAdapter::CalendarDataAdapter(
53     const CalendarDataAdapterAction& dataAdapterAction, const WeakPtr<PipelineContext>& pipelineContext)
54     : dataAdapterAction_(dataAdapterAction), pipelineContext_(pipelineContext)
55 {
56     Date currentDate = Date::Current();
57     today_.day = static_cast<int32_t>(currentDate.day);
58     today_.month.year = static_cast<int32_t>(currentDate.year);
59     today_.month.month = static_cast<int32_t>(currentDate.month) - 1;
60 }
61 
ParseData(int32_t indexOfContainer,const std::string & source,CalendarDaysOfMonth & result)62 bool CalendarDataAdapter::ParseData(int32_t indexOfContainer, const std::string& source, CalendarDaysOfMonth& result)
63 {
64     static const std::string daysKey = "days";
65     auto sourceJsonValue = JsonUtil::ParseJsonString(source);
66     calendarCache_[indexOfContainer].clear();
67     if (!sourceJsonValue || !sourceJsonValue->Contains(daysKey)) {
68         LOGE("not find days");
69         return false;
70     }
71 
72     auto daysValue = sourceJsonValue->GetValue(daysKey);
73     if (!daysValue || !daysValue->IsArray() || daysValue->GetArraySize() <= 0) {
74         LOGE("calendar days not array");
75         return false;
76     }
77 
78     int daySize = daysValue->GetArraySize();
79     if (daySize != CALENDAR_DAYS_FIVE_ROW_COUNT && daySize != CALENDAR_DAYS_SIX_ROW_COUNT) {
80         LOGE("calendar days size cannot support, size = %{public}d", daySize);
81         return false;
82     }
83 
84     for (int32_t index = 0; index < daySize; ++index) {
85         auto item = daysValue->GetArrayItem(index);
86         if (!item) {
87             continue;
88         }
89         CalendarDay dayInfo;
90         if (!ParseDayNumberProp(item, CalendarDay::INDEX, dayInfo.index)) {
91             continue;
92         }
93         if (!ParseDayNumberProp(item, CalendarDay::DAY, dayInfo.day)) {
94             continue;
95         }
96         if (!ParseDayNumberProp(item, CalendarDay::MONTH, dayInfo.month.month)) {
97             continue;
98         }
99         if (!ParseDayNumberProp(item, CalendarDay::YEAR, dayInfo.month.year)) {
100             continue;
101         }
102         if (dayInfo.day == CALENDAR_FIRST_DAY_NUM_OF_MONTH && result.firstDayIndex == CALENDAR_INVALID) {
103             result.firstDayIndex = dayInfo.index;
104         }
105 
106         if (dayInfo.month == result.month) {
107             result.lastDayIndex = dayInfo.index;
108         }
109 
110         // mark weekend
111         SetOffDays(dayInfo);
112         // Mark today.
113         dayInfo.today = dayInfo.month == today_.month && dayInfo.day == today_.day;
114         if (dayInfo.today) {
115             result.today = dayInfo.index;
116         }
117         // Get lunarDay information.
118         dayInfo.lunarMonth = item->GetString(CalendarDay::LUNAR_MONTH, "");
119         dayInfo.lunarDay = item->GetString(CalendarDay::LUNAR_DAY, "");
120         // Get mark information.
121         dayInfo.dayMark = item->GetString(CalendarDay::DAY_MARK, "");
122         dayInfo.dayMarkValue = item->GetString(CalendarDay::DAY_MARK_VALUE, "");
123         result.days.push_back(dayInfo);
124         calendarCache_[indexOfContainer].push_back(item->ToString());
125     }
126     dayOfMonthCache_[indexOfContainer] = result;
127     return true;
128 }
129 
RequestData(const CalendarDataRequest & request)130 void CalendarDataAdapter::RequestData(const CalendarDataRequest& request)
131 {
132     auto context = pipelineContext_.Upgrade();
133     auto weak = AceType::WeakClaim(this);
134     if (!context) {
135         return;
136     }
137     if (cardCalendar_ || isV2Component_) {
138         indexMap_[request.indexOfContainer] = request.month;
139         requestNextIndex_ = request.indexOfContainer;
140         auto json = JsonUtil::Create(true);
141         json->Put("month", request.month.month);
142         json->Put("year", request.month.year);
143         json->Put("currentMonth", currentMonth_.month);
144         json->Put("currentYear", currentMonth_.year);
145         if (request.month == currentMonth_) {
146             json->Put("monthState", static_cast<int32_t>(MonthState::CUR_MONTH));
147         } else if (request.month > currentMonth_) {
148             json->Put("monthState", static_cast<int32_t>(MonthState::NEXT_MONTH));
149         } else if (request.month < currentMonth_) {
150             json->Put("monthState", static_cast<int32_t>(MonthState::PRE_MONTH));
151         }
152         if (requestDataEvent_) {
153             requestDataEvent_(json->ToString());
154         }
155         auto iter = monthCache_.find(request.month);
156         if (iter != monthCache_.end()) {
157             auto monthDataJson = JsonUtil::ParseJsonString(monthCache_[iter->first]);
158             ParseMonthData(monthDataJson);
159         }
160         context->GetTaskExecutor()->PostTask(
161             [weak]() {
162               auto dataAdapter = weak.Upgrade();
163               if (!dataAdapter) {
164                   LOGW("dataAdapter is null");
165                   return;
166               }
167               dataAdapter->RequestNextData();
168             },
169             TaskExecutor::TaskType::UI, "ArkUICalendarRequestData");
170         return;
171     }
172     if (SystemProperties::GetDeviceType() == DeviceType::TV || type_ == CalendarType::NORMAL) {
173         GetCacheData(request);
174         if (!context->GetMessageBridge()) {
175             return;
176         }
177         context->GetMessageBridge()->SendMessage(dataAdapterAction_.GetAction(request.month.year, request.month.month),
178             [weak, request](const std::string& result) {
179                 auto dataAdapter = weak.Upgrade();
180                 if (!dataAdapter) {
181                     LOGE("date adapter is nullptr");
182                     return;
183                 }
184                 dataAdapter->HandleDataRequestResult(request, result);
185                 dataAdapter->SaveCacheData(request, result);
186             });
187     } else if (SystemProperties::GetDeviceType() == DeviceType::WATCH || type_ == CalendarType::SIMPLE) {
188         indexMap_[request.indexOfContainer] = request.month;
189         RequestDataInWatch(request);
190     }
191 }
192 
GetCacheData(const CalendarDataRequest & request)193 bool CalendarDataAdapter::GetCacheData(const CalendarDataRequest& request)
194 {
195     std::string filePath = cachePath_;
196     filePath.append("/")
197         .append(std::to_string(request.month.year))
198         .append(std::to_string(request.month.month))
199         .append(Localization::GetInstance()->GetLanguageTag());
200 
201     std::ifstream file(filePath, std::ifstream::in | std::ifstream::binary);
202     bool isDataCached = false;
203     if (file.is_open()) {
204         std::string result((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
205         if (!result.empty()) {
206             CalendarDaysOfMonth daysOfMonth;
207             daysOfMonth.month = request.month;
208             if (request.indexOfContainer >= 0 && request.indexOfContainer < CALENDAR_CACHE_PAGE) {
209                 if (!ParseData(request.indexOfContainer, result, daysOfMonth)) {
210                     return false;
211                 }
212                 NotifyDataChanged(daysOfMonth, request.indexOfContainer);
213             }
214         }
215         isDataCached = true;
216     }
217     return isDataCached;
218 }
219 
SaveCacheData(const CalendarDataRequest & request,const std::string & result)220 void CalendarDataAdapter::SaveCacheData(const CalendarDataRequest& request, const std::string& result)
221 {
222     auto context = pipelineContext_.Upgrade();
223     auto bkTaskExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::BACKGROUND);
224 
225     bkTaskExecutor.PostTask(
226         [request, result]() {
227             std::string url = cachePath_;
228             url.append("/")
229                 .append(std::to_string(request.month.year))
230                 .append(std::to_string(request.month.month))
231                 .append(Localization::GetInstance()->GetLanguageTag());
232             std::ofstream outFile(url, std::fstream::out);
233             if (!outFile) {
234                 LOGE("the file open failed");
235                 return;
236             }
237             outFile.write(reinterpret_cast<const char*>(result.c_str()), result.size());
238         },
239         "ArkUICalendarSaveCacheData");
240 }
241 
ParseCardCalendarData(const std::string & source)242 void CalendarDataAdapter::ParseCardCalendarData(const std::string& source)
243 {
244     if (source.empty()) {
245         return;
246     }
247     auto sourceJson = JsonUtil::ParseJsonString(source);
248     if (!sourceJson->IsValid() || !sourceJson->IsArray()) {
249         return;
250     }
251 
252     auto child = sourceJson->GetChild();
253     while (child->IsValid()) {
254         ParseMonthData(child);
255         child = child->GetNext();
256     }
257 }
258 
UpdateCardCalendarAttr(CardCalendarAttr && attr)259 void CalendarDataAdapter::UpdateCardCalendarAttr(CardCalendarAttr&& attr)
260 {
261     showLunar_ = attr.showLunar;
262     cardCalendar_ = attr.cardCalendar;
263     offDays_ = attr.offDays;
264     holidays_ = attr.holidays;
265     workDays_ = attr.workDays;
266     type_ = attr.type;
267     isV2Component_ = attr.isV2Component;
268     SetRequestDataEvent(attr.requestData);
269 
270     if ((SystemProperties::GetDeviceType() == DeviceType::WATCH || type_ == CalendarType::SIMPLE) &&
271         startDayOfWeek_ != attr.startDayOfWeek) {
272         startDayOfWeek_ = attr.startDayOfWeek;
273         for (const auto& index : indexMap_) {
274             if (index.second == currentMonth_) {
275                 CalendarDataRequest request(index.second, index.first);
276                 RequestData(request);
277             } else {
278                 AddPendingRequest(index.second, index.first);
279             }
280         }
281     }
282     startDayOfWeek_ = attr.startDayOfWeek;
283 
284     if (allListeners_.size() != CALENDAR_MONTH_COUNT) {
285         calendarAttr_ = std::move(attr);
286         calendarAttr_.listenersReady = false;
287     } else {
288         for (const auto& listen : allListeners_) {
289             listen->UpdateCardCalendarAttr(std::move(attr));
290         }
291     }
292 }
293 
ParseMonthData(const std::unique_ptr<JsonValue> & monthData)294 void CalendarDataAdapter::ParseMonthData(const std::unique_ptr<JsonValue>& monthData)
295 {
296     CalendarMonth calendarMonth;
297     calendarMonth.month = monthData->GetInt("month", -1);
298     calendarMonth.year = monthData->GetInt("year", -1);
299     monthCache_[calendarMonth] = monthData->ToString();
300 
301     int32_t indexOfContainer = -1;
302     for (const auto& index : indexMap_) {
303         if (index.second == calendarMonth) {
304             indexOfContainer = index.first;
305         }
306     }
307     static const int32_t miniIndex = 0;
308     static const int32_t maxIndex = 2;
309     if (indexOfContainer < miniIndex || indexOfContainer > maxIndex) {
310         return;
311     }
312     auto data = monthData->GetValue("data");
313     if (!data) {
314         return;
315     }
316     auto child = data->GetChild();
317     CalendarDaysOfMonth result;
318     result.month = calendarMonth;
319     bool hasLunarInfo = true;
320     while (child->IsValid()) {
321         CalendarDay dayInfo;
322         dayInfo.day = child->GetInt("day");
323         dayInfo.index = child->GetInt("index");
324         dayInfo.month.month = child->GetInt("month");
325         dayInfo.month.year = child->GetInt("year");
326         dayInfo.lunarDay = child->GetString("lunarDay", "");
327         dayInfo.dayMark = child->GetString("dayMark", "");
328         dayInfo.dayMarkValue = child->GetString("dayMarkValue", "");
329         dayInfo.isFirstOfLunar = child->GetBool("isFirstOfLunar", false);
330         dayInfo.hasSchedule = child->GetBool("hasSchedule", false);
331         dayInfo.markLunarDay = child->GetBool("markLunarDay", false);
332         SetOffDays(dayInfo);
333         hasLunarInfo = hasLunarInfo && !dayInfo.lunarDay.empty();
334         result.days.push_back(dayInfo);
335         child = child->GetNext();
336     }
337     LOGI("current month  is %{public}s, has lunar info %{public}d", calendarMonth.ToString().c_str(), hasLunarInfo);
338     dayOfMonthCache_[indexOfContainer] = result;
339     NotifyDataChanged(result, indexOfContainer);
340 }
341 
SetOffDays(CalendarDay & dayInfo)342 void CalendarDataAdapter::SetOffDays(CalendarDay& dayInfo)
343 {
344     auto weekday = Date::CalculateWeekDay(dayInfo.month.year, dayInfo.month.month + 1, dayInfo.day);
345     std::vector<std::string> days;
346     StringUtils::StringSplitter(offDays_, ',', days);
347     bool setOffDay = true;
348     for (const auto& day : days) {
349         auto num = StringUtils::StringToInt(day);
350         if (num < 0 || num > 6) {
351             setOffDay = false;
352             break;
353         }
354         if (num == weekday) {
355             dayInfo.weekend = true;
356             return;
357         }
358     }
359     if (!setOffDay) {
360         if (weekday == 5 || weekday == 6) { // set default weekend
361             dayInfo.weekend = true;
362         }
363     }
364 }
365 
RequestDataInWatch(const CalendarDataRequest & request)366 void CalendarDataAdapter::RequestDataInWatch(const CalendarDataRequest& request)
367 {
368     auto context = pipelineContext_.Upgrade();
369     auto weak = AceType::WeakClaim(this);
370     if (!context) {
371         return;
372     }
373     if (firstLoad_) {
374         context->SetBuildAfterCallback([weak, request]() {
375             auto dataAdapter = weak.Upgrade();
376             if (!dataAdapter) {
377                 LOGW("dataAdapter is null");
378                 return;
379             }
380             CalendarDaysOfMonth result;
381             dataAdapter->FillMonthData(request, result);
382             dataAdapter->NotifyDataChanged(result, request.indexOfContainer);
383             dataAdapter->firstLoad_ = false;
384             dataAdapter->RequestNextData();
385         });
386     } else {
387         context->GetTaskExecutor()->PostTask(
388             [weak, request]() {
389                 auto dataAdapter = weak.Upgrade();
390                 if (!dataAdapter) {
391                     LOGW("dataAdapter is null");
392                     return;
393                 }
394                 CalendarDaysOfMonth result;
395                 dataAdapter->FillMonthData(request, result);
396                 dataAdapter->NotifyDataChanged(result, request.indexOfContainer);
397                 dataAdapter->RequestNextData();
398             },
399             TaskExecutor::TaskType::UI, "ArkUICalendarRequestDataInWatch");
400     }
401 }
402 
FillMonthData(const CalendarDataRequest & request,CalendarDaysOfMonth & result)403 void CalendarDataAdapter::FillMonthData(const CalendarDataRequest& request, CalendarDaysOfMonth& result)
404 {
405     auto currentMonth = request.month;
406     result.month = currentMonth;
407     int32_t index = 0;
408     // fill last month data
409     FillPreMonthData(currentMonth, request.indexOfContainer, index, result);
410     // fill current month data
411     FillCurrentMonthData(currentMonth, request.indexOfContainer, index, result);
412 
413     result.lastDayIndex = index - 1;
414     // fill next month data
415     FillNextMonthData(currentMonth, request.indexOfContainer, index, result);
416 }
417 
FillPreMonthData(const CalendarMonth & currentMonth,int32_t indexOfContainer,int32_t & index,CalendarDaysOfMonth & result)418 void CalendarDataAdapter::FillPreMonthData(
419     const CalendarMonth& currentMonth, int32_t indexOfContainer, int32_t& index, CalendarDaysOfMonth& result)
420 {
421     static const int32_t DAYS_PER_WEEK = 7;
422     auto lastMonth = CalendarMonth::GetLastMonth(currentMonth);
423     auto currentWeekDay = Date::CalculateWeekDay(currentMonth.year, currentMonth.month + 1, 1);
424     if (currentWeekDay != startDayOfWeek_) {
425         auto lastMonthDays = Date::DayOfMonth(lastMonth.year, lastMonth.month + 1);
426         auto countDays = currentWeekDay - startDayOfWeek_ >= 0 ? currentWeekDay - startDayOfWeek_
427                                                                : currentWeekDay - startDayOfWeek_ + DAYS_PER_WEEK;
428         auto startDay = lastMonthDays - countDays + 1;
429         for (; index < countDays; ++index) {
430             CalendarDay dayInfo;
431             dayInfo.day = startDay++;
432             dayInfo.index = index;
433             dayInfo.month.month = lastMonth.month;
434             dayInfo.month.year = lastMonth.year;
435             SetOffDays(dayInfo);
436             calendarCache_[indexOfContainer].push_back(dayInfo.ToString());
437             result.days.emplace_back(dayInfo);
438         }
439     }
440 }
441 
FillCurrentMonthData(const CalendarMonth & currentMonth,int32_t indexOfContainer,int32_t & index,CalendarDaysOfMonth & result)442 void CalendarDataAdapter::FillCurrentMonthData(
443     const CalendarMonth& currentMonth, int32_t indexOfContainer, int32_t& index, CalendarDaysOfMonth& result)
444 {
445     result.firstDayIndex = index;
446     auto currentMonthDays = Date::DayOfMonth(currentMonth.year, currentMonth.month + 1);
447     for (int32_t i = 0; i < currentMonthDays; i++) {
448         CalendarDay dayInfo;
449         dayInfo.day = i + 1;
450         dayInfo.index = index;
451         dayInfo.month.month = currentMonth.month;
452         dayInfo.month.year = currentMonth.year;
453         SetOffDays(dayInfo);
454         // Mark today.
455         dayInfo.today = dayInfo.month == today_.month && dayInfo.day == today_.day;
456         if (dayInfo.today) {
457             result.today = dayInfo.index;
458         }
459         calendarCache_[indexOfContainer].push_back(dayInfo.ToString());
460         result.days.emplace_back(dayInfo);
461         ++index;
462     }
463 }
464 
FillNextMonthData(const CalendarMonth & currentMonth,int32_t indexOfContainer,int32_t & index,CalendarDaysOfMonth & result)465 void CalendarDataAdapter::FillNextMonthData(
466     const CalendarMonth& currentMonth, int32_t indexOfContainer, int32_t& index, CalendarDaysOfMonth& result)
467 {
468     auto nextMonth = CalendarMonth::GetNextMonth(currentMonth);
469     // The number of days the month view needs to be displayed
470     const int32_t daysOfCalendar = result.days.size() <= 35 ? 35 : 42;
471     int32_t indexOfNextMonth = 0;
472     while ((int32_t)result.days.size() < daysOfCalendar) {
473         CalendarDay dayInfo;
474         dayInfo.day = ++indexOfNextMonth;
475         dayInfo.index = index++;
476         dayInfo.month.month = nextMonth.month;
477         dayInfo.month.year = nextMonth.year;
478         SetOffDays(dayInfo);
479         calendarCache_[indexOfContainer].push_back(dayInfo.ToString());
480         result.days.emplace_back(dayInfo);
481     }
482 }
483 
ParseCalendarData(std::queue<ObtainedMonth> && months)484 void CalendarDataAdapter::ParseCalendarData(std::queue<ObtainedMonth>&& months)
485 {
486     while (!months.empty()) {
487         auto month = months.front();
488         CalendarMonth calendarMonth;
489         calendarMonth.year = month.year;
490         calendarMonth.month = month.month;
491 
492         int32_t indexOfContainer = -1;
493         for (const auto& index : indexMap_) {
494             if (index.second == calendarMonth) {
495                 indexOfContainer = index.first;
496             }
497         }
498 
499         if (hasMoved_  && indexOfContainer != requestNextIndex_) {
500             months.pop();
501             continue;
502         }
503 
504         static const int32_t miniIndex = 0;
505         static const int32_t maxIndex = 2;
506         if (indexOfContainer < miniIndex || indexOfContainer > maxIndex) {
507             months.pop();
508             continue;
509         }
510         CalendarDaysOfMonth result;
511         result.month = calendarMonth;
512         result.days = month.days;
513         result.firstDayIndex = month.firstDayIndex;
514         dayOfMonthCache_[indexOfContainer] = result;
515         NotifyDataChanged(result, indexOfContainer);
516         months.pop();
517     }
518 
519     if (hasMoved_) {
520         for (const auto& listener : allListeners_) {
521             listener->OnSwiperMove();
522         }
523         hasMoved_ = false;
524     }
525 }
526 
NotifyDataChanged(const CalendarDaysOfMonth & data,int32_t indexOfContainer)527 void CalendarDataAdapter::NotifyDataChanged(const CalendarDaysOfMonth& data, int32_t indexOfContainer)
528 {
529     int32_t listenersSize = static_cast<int32_t>(allListeners_.size());
530     if (indexOfContainer >= 0 && indexOfContainer < listenersSize) {
531         auto& listener = allListeners_[indexOfContainer];
532         listener->OnDataChanged(data);
533     }
534 }
535 
536 } // namespace OHOS::Ace
537