/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ListManager } from '@hadss/scroll_components';
import type { BlogData, ImagePixelMap } from '../model/types';
import type { http } from '@kit.NetworkKit';
import { HttpGet } from '../model/mock';
import { type image } from '@kit.ImageKit';
@Observed
export class CombineItemViewModel {
@Track public dataArray: BlogData[] = [];
public myListManager: ListManager;
private networkTime: number = 200;
private isLoadingMore: boolean = false;
private readonly fetchesMore: Map<number, Map<number, http.HttpRequest>> = new Map();
constructor(myListManager: ListManager) {
this.myListManager = myListManager;
}
loadData(callBack?: (isSuccess: boolean) => void): void {
generateRandomBlogData().then((data: BlogData[]) => {
this.dataArray = data;
this.myListManager.setDataSource(data);
if (callBack) {
callBack(true);
}
});
}
public loadDataMore(callBack?: (isSuccess: boolean) => void): void {
if (!this.isLoadingMore) {
this.isLoadingMore = true;
setTimeout(() => {
generateRandomBlogData().then((data: BlogData[]) => {
this.myListManager.nodeAdapter.pushData(data);
this.isLoadingMore = false;
if (callBack) {
callBack(true);
}
});
}, this.networkTime);
}
}
// [Start Prefetch_3_2]
public registerPrefetch(): void {
this.myListManager?.registerFetchCallback(this.fetchCallback);
this.myListManager?.registerCancelCallback(this.cancelCallback);
}
// [End Prefetch_3_2]
// [Start Prefetch_2]
public fetchCallback: (item: ESObject, fetchId: number) => Promise<void> = (item: ESObject, fetchId: number) => {
/*
* Simulate a simple pre-loading scenario ,This is where you can migrate the preloaded scenarios for your project
* 1、When the component is not displayed or created, the image is obtained. PixelMap,
* when the component is displayed, load the image directly. PixelMap and show no need to wait for the network
* 2、If the image is displayed but the pixelMap is not obtained, the data is bound to the Image component first,
* and the image can be refreshed immediately after the pixelMap is obtained
*/
let data = item as BlogData;
this.fetchesMore.set(fetchId, new Map());
return new Promise(resolve => {
this.loadImagesInBatches(data.imagePixelMapArray, fetchId, 0, resolve);
});
};
private loadImagesInBatches(images: ImagePixelMap[], fetchId: number, startIndex: number, resolve: () => void): void {
if (startIndex >= images.length) {
const fetchMap = this.fetchesMore.get(fetchId);
if (fetchMap && fetchMap.size === 0) {
this.fetchesMore.delete(fetchId);
}
resolve();
return;
}
const batchSize = 1;
const endIndex = Math.min(startIndex + batchSize, images.length);
for (let i = startIndex; i < endIndex; i++) {
const pixMap = images[i];
if (pixMap.imagePixelMap) {
continue;
}
let httpRequest = HttpGet(pixMap.imageUrl, (error: number, pixelMap?: image.PixelMap) => {
let fetchMap = this.fetchesMore.get(fetchId);
if (fetchMap) {
fetchMap.delete(i);
if (fetchMap?.size === 0) {
this.fetchesMore.delete(fetchId);
}
}
if (error === -1) {
return;
}
if (pixelMap) {
/*
* You need to create an array to store the pixelMap, because there is a change in the internal data
* of the array to make the component respond, which is more complicated, the developer can choose
* the preloading scheme by himself: Define the ImagePixelMap class to store the pixelMap,
* encapsulate the Image component as an ObservedImage, add @ObjectLink imagePixelMap: ImagePixelMap property
* to bind the data object, In the preloaded callback fetchCallback, after the image pixelMap is obtained,
* set ImagePixelMap.imagePixelMap, and the image can be refreshed by ObservedImage
*/
pixMap.imagePixelMap = pixelMap;
}
});
let fetchMap = this.fetchesMore.get(fetchId);
if (fetchMap) {
fetchMap.set(i, httpRequest);
}
}
setTimeout(() => {
this.loadImagesInBatches(images, fetchId, endIndex, resolve);
}, 80);
}
public cancelCallback: (fetchId: number) => void = (fetchId: number): void => {
const fetchMap = this.fetchesMore.get(fetchId);
if (fetchMap) {
Array.from(fetchMap.values()).forEach(element => {
element.destroy();
});
fetchMap.clear();
}
this.fetchesMore.delete(fetchId);
};
// [End Prefetch_2]
}
@Concurrent
async function generateRandomBlogData(count?: number): Promise<BlogData[]> {
const module = await import('../model/mock');
return module.generateRandomBlogData(count ?? 20, false, true);
}