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 TaskExecutor::GetPriorityTypeWithCheck(PriorityType::IMMEDIATE));
171 return;
172 }
173 if (SystemProperties::GetDeviceType() == DeviceType::TV || type_ == CalendarType::NORMAL) {
174 GetCacheData(request);
175 if (!context->GetMessageBridge()) {
176 return;
177 }
178 context->GetMessageBridge()->SendMessage(dataAdapterAction_.GetAction(request.month.year, request.month.month),
179 [weak, request](const std::string& result) {
180 auto dataAdapter = weak.Upgrade();
181 if (!dataAdapter) {
182 LOGE("date adapter is nullptr");
183 return;
184 }
185 dataAdapter->HandleDataRequestResult(request, result);
186 dataAdapter->SaveCacheData(request, result);
187 });
188 } else if (SystemProperties::GetDeviceType() == DeviceType::WATCH || type_ == CalendarType::SIMPLE) {
189 indexMap_[request.indexOfContainer] = request.month;
190 RequestDataInWatch(request);
191 }
192 }
193
GetCacheData(const CalendarDataRequest & request)194 bool CalendarDataAdapter::GetCacheData(const CalendarDataRequest& request)
195 {
196 std::string filePath = cachePath_;
197 filePath.append("/")
198 .append(std::to_string(request.month.year))
199 .append(std::to_string(request.month.month))
200 .append(Localization::GetInstance()->GetLanguageTag());
201
202 std::ifstream file(filePath, std::ifstream::in | std::ifstream::binary);
203 bool isDataCached = false;
204 if (file.is_open()) {
205 std::string result((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
206 if (!result.empty()) {
207 CalendarDaysOfMonth daysOfMonth;
208 daysOfMonth.month = request.month;
209 if (request.indexOfContainer >= 0 && request.indexOfContainer < CALENDAR_CACHE_PAGE) {
210 if (!ParseData(request.indexOfContainer, result, daysOfMonth)) {
211 return false;
212 }
213 NotifyDataChanged(daysOfMonth, request.indexOfContainer);
214 }
215 }
216 isDataCached = true;
217 }
218 return isDataCached;
219 }
220
SaveCacheData(const CalendarDataRequest & request,const std::string & result)221 void CalendarDataAdapter::SaveCacheData(const CalendarDataRequest& request, const std::string& result)
222 {
223 auto context = pipelineContext_.Upgrade();
224 auto bkTaskExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::BACKGROUND);
225
226 bkTaskExecutor.PostTask(
227 [request, result]() {
228 std::string url = cachePath_;
229 url.append("/")
230 .append(std::to_string(request.month.year))
231 .append(std::to_string(request.month.month))
232 .append(Localization::GetInstance()->GetLanguageTag());
233 std::ofstream outFile(url, std::fstream::out);
234 if (!outFile) {
235 LOGE("the file open failed");
236 return;
237 }
238 outFile.write(reinterpret_cast<const char*>(result.c_str()), result.size());
239 },
240 "ArkUICalendarSaveCacheData", TaskExecutor::GetPriorityTypeWithCheck(PriorityType::IMMEDIATE));
241 }
242
ParseCardCalendarData(const std::string & source)243 void CalendarDataAdapter::ParseCardCalendarData(const std::string& source)
244 {
245 if (source.empty()) {
246 return;
247 }
248 auto sourceJson = JsonUtil::ParseJsonString(source);
249 if (!sourceJson->IsValid() || !sourceJson->IsArray()) {
250 return;
251 }
252
253 auto child = sourceJson->GetChild();
254 while (child->IsValid()) {
255 ParseMonthData(child);
256 child = child->GetNext();
257 }
258 }
259
UpdateCardCalendarAttr(CardCalendarAttr && attr)260 void CalendarDataAdapter::UpdateCardCalendarAttr(CardCalendarAttr&& attr)
261 {
262 showLunar_ = attr.showLunar;
263 cardCalendar_ = attr.cardCalendar;
264 offDays_ = attr.offDays;
265 holidays_ = attr.holidays;
266 workDays_ = attr.workDays;
267 type_ = attr.type;
268 isV2Component_ = attr.isV2Component;
269 SetRequestDataEvent(attr.requestData);
270
271 if ((SystemProperties::GetDeviceType() == DeviceType::WATCH || type_ == CalendarType::SIMPLE) &&
272 startDayOfWeek_ != attr.startDayOfWeek) {
273 startDayOfWeek_ = attr.startDayOfWeek;
274 for (const auto& index : indexMap_) {
275 if (index.second == currentMonth_) {
276 CalendarDataRequest request(index.second, index.first);
277 RequestData(request);
278 } else {
279 AddPendingRequest(index.second, index.first);
280 }
281 }
282 }
283 startDayOfWeek_ = attr.startDayOfWeek;
284
285 if (allListeners_.size() != CALENDAR_MONTH_COUNT) {
286 calendarAttr_ = std::move(attr);
287 calendarAttr_.listenersReady = false;
288 } else {
289 for (const auto& listen : allListeners_) {
290 listen->UpdateCardCalendarAttr(std::move(attr));
291 }
292 }
293 }
294
ParseMonthData(const std::unique_ptr<JsonValue> & monthData)295 void CalendarDataAdapter::ParseMonthData(const std::unique_ptr<JsonValue>& monthData)
296 {
297 CalendarMonth calendarMonth;
298 calendarMonth.month = monthData->GetInt("month", -1);
299 calendarMonth.year = monthData->GetInt("year", -1);
300 monthCache_[calendarMonth] = monthData->ToString();
301
302 int32_t indexOfContainer = -1;
303 for (const auto& index : indexMap_) {
304 if (index.second == calendarMonth) {
305 indexOfContainer = index.first;
306 }
307 }
308 static const int32_t miniIndex = 0;
309 static const int32_t maxIndex = 2;
310 if (indexOfContainer < miniIndex || indexOfContainer > maxIndex) {
311 return;
312 }
313 auto data = monthData->GetValue("data");
314 if (!data) {
315 return;
316 }
317 auto child = data->GetChild();
318 CalendarDaysOfMonth result;
319 result.month = calendarMonth;
320 bool hasLunarInfo = true;
321 while (child->IsValid()) {
322 CalendarDay dayInfo;
323 dayInfo.day = child->GetInt("day");
324 dayInfo.index = child->GetInt("index");
325 dayInfo.month.month = child->GetInt("month");
326 dayInfo.month.year = child->GetInt("year");
327 dayInfo.lunarDay = child->GetString("lunarDay", "");
328 dayInfo.dayMark = child->GetString("dayMark", "");
329 dayInfo.dayMarkValue = child->GetString("dayMarkValue", "");
330 dayInfo.isFirstOfLunar = child->GetBool("isFirstOfLunar", false);
331 dayInfo.hasSchedule = child->GetBool("hasSchedule", false);
332 dayInfo.markLunarDay = child->GetBool("markLunarDay", false);
333 SetOffDays(dayInfo);
334 hasLunarInfo = hasLunarInfo && !dayInfo.lunarDay.empty();
335 result.days.push_back(dayInfo);
336 child = child->GetNext();
337 }
338 LOGI("current month is %{public}s, has lunar info %{public}d", calendarMonth.ToString().c_str(), hasLunarInfo);
339 dayOfMonthCache_[indexOfContainer] = result;
340 NotifyDataChanged(result, indexOfContainer);
341 }
342
SetOffDays(CalendarDay & dayInfo)343 void CalendarDataAdapter::SetOffDays(CalendarDay& dayInfo)
344 {
345 auto weekday = Date::CalculateWeekDay(dayInfo.month.year, dayInfo.month.month + 1, dayInfo.day);
346 std::vector<std::string> days;
347 StringUtils::StringSplitter(offDays_, ',', days);
348 bool setOffDay = true;
349 for (const auto& day : days) {
350 auto num = StringUtils::StringToInt(day);
351 if (num < 0 || num > 6) {
352 setOffDay = false;
353 break;
354 }
355 if (num == weekday) {
356 dayInfo.weekend = true;
357 return;
358 }
359 }
360 if (!setOffDay) {
361 if (weekday == 5 || weekday == 6) { // set default weekend
362 dayInfo.weekend = true;
363 }
364 }
365 }
366
RequestDataInWatch(const CalendarDataRequest & request)367 void CalendarDataAdapter::RequestDataInWatch(const CalendarDataRequest& request)
368 {
369 auto context = pipelineContext_.Upgrade();
370 auto weak = AceType::WeakClaim(this);
371 if (!context) {
372 return;
373 }
374 if (firstLoad_) {
375 context->SetBuildAfterCallback([weak, request]() {
376 auto dataAdapter = weak.Upgrade();
377 if (!dataAdapter) {
378 LOGW("dataAdapter is null");
379 return;
380 }
381 CalendarDaysOfMonth result;
382 dataAdapter->FillMonthData(request, result);
383 dataAdapter->NotifyDataChanged(result, request.indexOfContainer);
384 dataAdapter->firstLoad_ = false;
385 dataAdapter->RequestNextData();
386 });
387 } else {
388 context->GetTaskExecutor()->PostTask(
389 [weak, request]() {
390 auto dataAdapter = weak.Upgrade();
391 if (!dataAdapter) {
392 LOGW("dataAdapter is null");
393 return;
394 }
395 CalendarDaysOfMonth result;
396 dataAdapter->FillMonthData(request, result);
397 dataAdapter->NotifyDataChanged(result, request.indexOfContainer);
398 dataAdapter->RequestNextData();
399 },
400 TaskExecutor::TaskType::UI, "ArkUICalendarRequestDataInWatch",
401 TaskExecutor::GetPriorityTypeWithCheck(PriorityType::IMMEDIATE));
402 }
403 }
404
FillMonthData(const CalendarDataRequest & request,CalendarDaysOfMonth & result)405 void CalendarDataAdapter::FillMonthData(const CalendarDataRequest& request, CalendarDaysOfMonth& result)
406 {
407 auto currentMonth = request.month;
408 result.month = currentMonth;
409 int32_t index = 0;
410 // fill last month data
411 FillPreMonthData(currentMonth, request.indexOfContainer, index, result);
412 // fill current month data
413 FillCurrentMonthData(currentMonth, request.indexOfContainer, index, result);
414
415 result.lastDayIndex = index - 1;
416 // fill next month data
417 FillNextMonthData(currentMonth, request.indexOfContainer, index, result);
418 }
419
FillPreMonthData(const CalendarMonth & currentMonth,int32_t indexOfContainer,int32_t & index,CalendarDaysOfMonth & result)420 void CalendarDataAdapter::FillPreMonthData(
421 const CalendarMonth& currentMonth, int32_t indexOfContainer, int32_t& index, CalendarDaysOfMonth& result)
422 {
423 static const int32_t DAYS_PER_WEEK = 7;
424 auto lastMonth = CalendarMonth::GetLastMonth(currentMonth);
425 auto currentWeekDay = Date::CalculateWeekDay(currentMonth.year, currentMonth.month + 1, 1);
426 if (currentWeekDay != startDayOfWeek_) {
427 auto lastMonthDays = Date::DayOfMonth(lastMonth.year, lastMonth.month + 1);
428 auto countDays = currentWeekDay - startDayOfWeek_ >= 0 ? currentWeekDay - startDayOfWeek_
429 : currentWeekDay - startDayOfWeek_ + DAYS_PER_WEEK;
430 auto startDay = lastMonthDays - countDays + 1;
431 for (; index < countDays; ++index) {
432 CalendarDay dayInfo;
433 dayInfo.day = startDay++;
434 dayInfo.index = index;
435 dayInfo.month.month = lastMonth.month;
436 dayInfo.month.year = lastMonth.year;
437 SetOffDays(dayInfo);
438 calendarCache_[indexOfContainer].push_back(dayInfo.ToString());
439 result.days.emplace_back(dayInfo);
440 }
441 }
442 }
443
FillCurrentMonthData(const CalendarMonth & currentMonth,int32_t indexOfContainer,int32_t & index,CalendarDaysOfMonth & result)444 void CalendarDataAdapter::FillCurrentMonthData(
445 const CalendarMonth& currentMonth, int32_t indexOfContainer, int32_t& index, CalendarDaysOfMonth& result)
446 {
447 result.firstDayIndex = index;
448 auto currentMonthDays = Date::DayOfMonth(currentMonth.year, currentMonth.month + 1);
449 for (int32_t i = 0; i < currentMonthDays; i++) {
450 CalendarDay dayInfo;
451 dayInfo.day = i + 1;
452 dayInfo.index = index;
453 dayInfo.month.month = currentMonth.month;
454 dayInfo.month.year = currentMonth.year;
455 SetOffDays(dayInfo);
456 // Mark today.
457 dayInfo.today = dayInfo.month == today_.month && dayInfo.day == today_.day;
458 if (dayInfo.today) {
459 result.today = dayInfo.index;
460 }
461 calendarCache_[indexOfContainer].push_back(dayInfo.ToString());
462 result.days.emplace_back(dayInfo);
463 ++index;
464 }
465 }
466
FillNextMonthData(const CalendarMonth & currentMonth,int32_t indexOfContainer,int32_t & index,CalendarDaysOfMonth & result)467 void CalendarDataAdapter::FillNextMonthData(
468 const CalendarMonth& currentMonth, int32_t indexOfContainer, int32_t& index, CalendarDaysOfMonth& result)
469 {
470 auto nextMonth = CalendarMonth::GetNextMonth(currentMonth);
471 // The number of days the month view needs to be displayed
472 const int32_t daysOfCalendar = result.days.size() <= 35 ? 35 : 42;
473 int32_t indexOfNextMonth = 0;
474 while ((int32_t)result.days.size() < daysOfCalendar) {
475 CalendarDay dayInfo;
476 dayInfo.day = ++indexOfNextMonth;
477 dayInfo.index = index++;
478 dayInfo.month.month = nextMonth.month;
479 dayInfo.month.year = nextMonth.year;
480 SetOffDays(dayInfo);
481 calendarCache_[indexOfContainer].push_back(dayInfo.ToString());
482 result.days.emplace_back(dayInfo);
483 }
484 }
485
ParseCalendarData(std::queue<ObtainedMonth> && months)486 void CalendarDataAdapter::ParseCalendarData(std::queue<ObtainedMonth>&& months)
487 {
488 while (!months.empty()) {
489 auto month = months.front();
490 CalendarMonth calendarMonth;
491 calendarMonth.year = month.year;
492 calendarMonth.month = month.month;
493
494 int32_t indexOfContainer = -1;
495 for (const auto& index : indexMap_) {
496 if (index.second == calendarMonth) {
497 indexOfContainer = index.first;
498 }
499 }
500
501 if (hasMoved_ && indexOfContainer != requestNextIndex_) {
502 months.pop();
503 continue;
504 }
505
506 static const int32_t miniIndex = 0;
507 static const int32_t maxIndex = 2;
508 if (indexOfContainer < miniIndex || indexOfContainer > maxIndex) {
509 months.pop();
510 continue;
511 }
512 CalendarDaysOfMonth result;
513 result.month = calendarMonth;
514 result.days = month.days;
515 result.firstDayIndex = month.firstDayIndex;
516 dayOfMonthCache_[indexOfContainer] = result;
517 NotifyDataChanged(result, indexOfContainer);
518 months.pop();
519 }
520
521 if (hasMoved_) {
522 for (const auto& listener : allListeners_) {
523 listener->OnSwiperMove();
524 }
525 hasMoved_ = false;
526 }
527 }
528
NotifyDataChanged(const CalendarDaysOfMonth & data,int32_t indexOfContainer)529 void CalendarDataAdapter::NotifyDataChanged(const CalendarDaysOfMonth& data, int32_t indexOfContainer)
530 {
531 int32_t listenersSize = static_cast<int32_t>(allListeners_.size());
532 if (indexOfContainer >= 0 && indexOfContainer < listenersSize) {
533 auto& listener = allListeners_[indexOfContainer];
534 listener->OnDataChanged(data);
535 }
536 }
537
538 } // namespace OHOS::Ace
539