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