From a607bf761799d5ef329486e30201ee5e15a335a3 Mon Sep 17 00:00:00 2001 From: bison Date: Thu, 19 Jun 2025 00:06:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=89=80=E6=9C=89=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ets/common/constants/CommonConstants.ets | 166 +++++++ entry/src/main/ets/common/util/Logger.ets | 41 ++ entry/src/main/ets/common/util/Utils.ets | 35 ++ entry/src/main/ets/model/mock.ets | 179 +++++++ entry/src/main/ets/model/types.ets | 41 ++ .../main/ets/pages/CombineWaterFlowPage.ets | 460 ++++++++++++++++++ entry/src/main/ets/pages/Index.ets | 62 ++- .../src/main/ets/pages/MultiFlowItemPage.ets | 176 +++++++ entry/src/main/ets/pages/SharedPoolPage.ets | 203 ++++++++ .../main/ets/pages/SharedPoolSecondPage.ets | 179 +++++++ .../main/ets/pages/StandardWaterFlowPage.ets | 264 ++++++++++ .../main/ets/pages/StickyWaterFlowPage.ets | 393 +++++++++++++++ entry/src/main/ets/pages/TabBarPage.ets | 69 +++ entry/src/main/ets/workers/FetchAgent.ets | 91 ++++ entry/src/main/ets/workers/GetNetworkData.ets | 114 +++++ .../resources/base/profile/main_pages.json | 6 +- 16 files changed, 2477 insertions(+), 2 deletions(-) create mode 100644 entry/src/main/ets/common/constants/CommonConstants.ets create mode 100644 entry/src/main/ets/common/util/Logger.ets create mode 100644 entry/src/main/ets/common/util/Utils.ets create mode 100644 entry/src/main/ets/model/mock.ets create mode 100644 entry/src/main/ets/model/types.ets create mode 100644 entry/src/main/ets/pages/CombineWaterFlowPage.ets create mode 100644 entry/src/main/ets/pages/MultiFlowItemPage.ets create mode 100644 entry/src/main/ets/pages/SharedPoolPage.ets create mode 100644 entry/src/main/ets/pages/SharedPoolSecondPage.ets create mode 100644 entry/src/main/ets/pages/StandardWaterFlowPage.ets create mode 100644 entry/src/main/ets/pages/StickyWaterFlowPage.ets create mode 100644 entry/src/main/ets/pages/TabBarPage.ets create mode 100644 entry/src/main/ets/workers/FetchAgent.ets create mode 100644 entry/src/main/ets/workers/GetNetworkData.ets diff --git a/entry/src/main/ets/common/constants/CommonConstants.ets b/entry/src/main/ets/common/constants/CommonConstants.ets new file mode 100644 index 0000000..e368295 --- /dev/null +++ b/entry/src/main/ets/common/constants/CommonConstants.ets @@ -0,0 +1,166 @@ +/* + * 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. + */ + +/** + * Common constants for all features. + */ +export class CommonConstants { + /** + * Water flow layout columns template. + */ + static readonly WATER_FLOW_COLUMNS_TEMPLATE: string = '1fr 1fr'; + /** + * Width the percentage of the 100. + */ + static readonly FULL_WIDTH: string = '100%'; + /** + * Height the percentage of the 100. + */ + static readonly FULL_HEIGHT: string = '100%'; + /** + * cached_count + */ + static readonly CACHED_COUNT: number = 6; + /** + * quick return to top function event + */ + static readonly EVENT_QUICK_TOP_FUNC: string = 'event_quick_top_func'; + /** + * hide item menu event. + */ + static readonly EVENT_HIDE_ITEM_MENU: string = 'event_hide_item_menu'; + /** + * remove item event + */ + static readonly EVENT_REMOVE_ITEM: string = 'event_remove_item'; + /** + * Index content width. + */ + static readonly INDEX_CONTENT_WIDTH: string = '91.1%'; + /** + * Index divider height. + */ + static readonly DIVIDER_HEIGHT: number = 0.5; + /** + * Index divider width. + */ + static readonly DIVIDER_WIDTH: string = '93%'; + /** + * Font width 80%. + */ + static readonly EIGHTY_FONT_WIDTH: string = '80%'; + /** + * Drawer divider width. + */ + static readonly DIVIDER_DRAWER_WIDTH: string = '90%'; + /** + * Card title height. + */ + static readonly CARD_TITLE_HEIGHT: number = 20; + /** + * Card text height. + */ + static readonly CARD_TEXT_HEIGHT: number = 48; + /** + * Index title height. + */ + static readonly INDEX_TITLE_HEIGHT: number = 112; + /** + * Flow height. + */ + static readonly FLOW_HEIGHT: Length = 244; + /** + * Border radius index list. + */ + static readonly BORDER_RADIUS_INDEX_LIST: number = 18; + /** + * List content height. + */ + static readonly LIST_CONTENT_HEIGHT: string = '110%'; + /** + * Translate top. + */ + static readonly TRANSLATE_TOP: number = -40; + static readonly PADDING: number = 16; + static readonly ROWS_GAP: number = 12; + static readonly COLUMNS_GAP: number = 10; + /** + * Arr. + */ + static readonly ARR: number[] = [1, 2, 3, 4, 5, 6]; + /** + * colors. + */ + static readonly COLORS: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]; + /** + * Routes. + */ + static readonly ROUTES: Route[] = [ + { + title: $r('app.string.scenario1'), + child: [ + { + text: $r('app.string.scenario_waterFlow'), + to: 'StandardWaterFlowPage' + } + ] + }, + { + title: $r('app.string.scenario2'), + child: [ + { + text: $r('app.string.scenario_waterFlow2'), + to: 'CombineWaterFlowPage' + } + ] + }, + { + title: $r('app.string.scenario3'), + child: [ + { + text: $r('app.string.scenario_waterFlow3'), + to: 'TabBarPage' + } + ] + }, + { + title: $r('app.string.scenario4'), + child: [ + { + text: $r('app.string.scenario_waterFlow4'), + to: 'StickyWaterFlowPage' + } + ] + } + ]; +} + +/** + * Route type define. + */ +export interface Route { + title: string | Resource, + child: Array +} + +/** + * ChildRoute type define. + */ +export interface ChildRoute { + text: string | Resource, + to: string +} + + diff --git a/entry/src/main/ets/common/util/Logger.ets b/entry/src/main/ets/common/util/Logger.ets new file mode 100644 index 0000000..bb11f87 --- /dev/null +++ b/entry/src/main/ets/common/util/Logger.ets @@ -0,0 +1,41 @@ +/* + * 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 { hilog } from "@kit.PerformanceAnalysisKit"; + +export class Logger { + static tag = 'WaterFlowSample'; + static domain = 0xfa0a; + + static info(info: string, ...arg: ESObject[]) { + hilog.info(Logger.domain, Logger.tag, info, ...arg); + } + + static debug(info: string, ...arg: ESObject[]) { + hilog.debug(Logger.domain, Logger.tag, info, ...arg); + } + + static error(info: string, ...arg: ESObject[]) { + hilog.error(Logger.domain, Logger.tag, info, ...arg); + } + + static warn(info: string, ...arg: ESObject[]) { + hilog.warn(Logger.domain, Logger.tag, info, ...arg); + } + + static fatal(info: string, ...arg: ESObject[]) { + hilog.fatal(Logger.domain, Logger.tag, info, ...arg); + } +} diff --git a/entry/src/main/ets/common/util/Utils.ets b/entry/src/main/ets/common/util/Utils.ets new file mode 100644 index 0000000..6d9b619 --- /dev/null +++ b/entry/src/main/ets/common/util/Utils.ets @@ -0,0 +1,35 @@ +/* + * 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. + */ + +// [Start Utils] +import { RecycledPool } from '@hadss/scroll_components'; + +export class Utils { + // [StartExclude Utils] + private constructor() { + } + // [EndExclude Utils] + private static utils_: Utils; + nodePool: RecycledPool | null = null; + // [StartExclude Utils] + static getInstance() { + if (Utils.utils_ === undefined) { + Utils.utils_ = new Utils(); + } + return Utils.utils_; + } + // [EndExclude Utils] +} +// [End Utils] \ No newline at end of file diff --git a/entry/src/main/ets/model/mock.ets b/entry/src/main/ets/model/mock.ets new file mode 100644 index 0000000..8266f73 --- /dev/null +++ b/entry/src/main/ets/model/mock.ets @@ -0,0 +1,179 @@ +/* + * 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 { BlogData, User } from "../model/types"; +import { collections, MessageEvents, worker } from "@kit.ArkTS"; +import { sceneMockData } from "../workers/GetNetworkData"; + +const IMAGES = [ + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/d/v3/Rl6Z4LI7QXmSBJwPdI0f7w/NV54hLP8S8CmSeamBgFN9w.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/b3/v3/_-sFheFMR8C4yT1OLCTWcg/DqGBYPLBS0mcnhVsoTFOmw.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/f6/v3/2QgjOIy2Q_65C--t0AaThg/6iRAKTeoRnu0T0WxhTECRw.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/28/v3/804UGQS0Se6XzgIAGHv4Wg/FtN7zRQUQuG25HIGPwmShw.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/fd/v3/LJJ0rNvaTjeHDMdm1O9ODw/4JFmSgudQ_uRvttdVGlnqA.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/05/v3/y44v1Z6bShO-zsJmHVY-eg/34_FWLSPSYKjzAMS7reozg.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/4f/v3/hayg9LgaSxKV0H3ZLPlwJg/lXD9fhObTCW1i0tGA1n_yg.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/47/v3/iQWIeqMsTm-oytN9x0N12Q/OIoyti5vSjGR7uyCP3lKJA.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/04/v3/Kh7sLUNWRVW0Lu2PjHFF0g/4fJVwfsMT5SB6lW5wVwa1g.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/d3/v3/Chth8Vi1TdutekSVrzlmTg/V-j53IY9QP6RN7Ft0RbDrA.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/f3/v3/WI8L6FQ9SFm9FruCVWqOZw/Bgoku8BbRIaAcaXnBlkqjg.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/f6/v3/2QgjOIy2Q_65C--t0AaThg/6iRAKTeoRnu0T0WxhTECRw.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/ac/v3/F609IcNNTGGWYUtKYFCLTA/SNFRbgzxRmC2QS3-Rb-msw.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/e9/v3/bje5By14Say27qEV2Ga9_w/9tYMSW7hR527EIIn5f60mg.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/9a/v3/YJTfU1sdS0arE6kBjeOvUg/eE711PUTQeGwjTgcuIpfug.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/78/v3/kZ1U1fgyQIyq17iRVbGkbQ/7yEHTwC7Ri6mp8X_C2CnsA.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/4f/v3/hayg9LgaSxKV0H3ZLPlwJg/lXD9fhObTCW1i0tGA1n_yg.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/93/v3/zSOgMcKxT1CnOv7uois9ew/BtP71mQGQumwZ8Df_Flygg.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/cf/v3/iAPHU1RiQ3C6N1VcaqPsmA/A9YzlO-KSqSgH57-DW-H4A.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/68/v3/9NmVtlQeRJuo32B9D_4oaA/OEeS8ybIS9W0bQxQEXAySg.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/f1/v3/oaTbF6rrRnifVmUz6Kx8pg/vE11Pq99SeiZ-lJj6y1oeQ.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/ac/v3/F609IcNNTGGWYUtKYFCLTA/SNFRbgzxRmC2QS3-Rb-msw.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/68/v3/9NmVtlQeRJuo32B9D_4oaA/OEeS8ybIS9W0bQxQEXAySg.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/96/v3/3-X0DsxbRH6AlbimBxnPog/HDC3_rmpRRussgr3MeNHWg.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/b3/v3/_-sFheFMR8C4yT1OLCTWcg/DqGBYPLBS0mcnhVsoTFOmw.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/7b/v3/O-qz81abT7ygFvSOGTnqtA/LUYXmSmGRhWD7S7oCrcb6A.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/bb/v3/-Xaw2zTeRYGQL1Y02xOCgA/SZqcqvfTTbec-rU0XS4VuQ.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/9d/v3/BdpN9f4GQEquwXR0Q9JzsA/X5vM2p69SnWEKFsJb4TtaQ.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/ac/v3/F609IcNNTGGWYUtKYFCLTA/SNFRbgzxRmC2QS3-Rb-msw.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/f8/v3/NlvkqPP4TqOp5tR-9v8IKg/w-urENTfRcOj9bJ44ZwooA.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/a2/v3/2B0wltJbSASAHTK6Cq6riw/HJd3q3OpSqSg6I3IDdlTwA.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/55/v3/B5QRS67VS-6uutH-uyfFsQ/f4PJOe6oRACFGW3B1NDyhw.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/96/v3/3-X0DsxbRH6AlbimBxnPog/HDC3_rmpRRussgr3MeNHWg.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/78/v3/kZ1U1fgyQIyq17iRVbGkbQ/7yEHTwC7Ri6mp8X_C2CnsA.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/f8/v3/NlvkqPP4TqOp5tR-9v8IKg/w-urENTfRcOj9bJ44ZwooA.png', + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/a4/v3/sWmOw1KVTlaMNDBa_hBWuw/sF8e_Z1MR7Cd1-ZIjZl5JA.png', +] + +const getRandomNumber = (min: number, max: number): number => { + // 生成[min, max]范围内的整数 + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +const EMOJIS = ["😊", "🎉", "🚀", "❤️", "🔥", "💡", "🌟", "📱", "💻", "🌍"]; + +const USERS: User[] = [ + { name: "科技前沿", avatar: IMAGES[getRandomNumber(0, IMAGES.length - 1)] }, + { name: "数码达人", avatar: IMAGES[getRandomNumber(0, IMAGES.length - 1)] }, + { name: "生活分享家", avatar: IMAGES[getRandomNumber(0, IMAGES.length - 1)] }, + { name: "新闻速递", avatar: IMAGES[getRandomNumber(0, IMAGES.length - 1)] }, + { name: "旅行日记", avatar: IMAGES[getRandomNumber(0, IMAGES.length - 1)] } +]; + + +// Generate random user +function generateRandomUser(): User { + const count = getRandomNumber(0, USERS.length - 1); + return USERS[count]; +} + +// Generate random image array +function generateRandomImages(): Array { + const count = Math.floor(Math.random() * 9) + 1; + const shuffled = IMAGES.sort(() => 0.5 - Math.random()); + return shuffled.slice(0, count); +} + +// Time formatting function +function formatTime(timestamp: number): string { + const date = new Date(timestamp); + const diff = (Date.now() - timestamp) / 1000; + + if (diff < 60) { + return "刚刚"; + } + if (diff < 3600) { + return `${Math.floor(diff / 60)}分钟前`; + } + if (diff < 86400) { + return `${Math.floor(diff / 3600)}小时前`; + } + return `${date.getMonth() + 1}-${date.getDate()}`; +} + +// Generate random text content (with emoji) +function generateRandomContent(): string { + const sentences = [ + "HarmonyOS最新功能体验分享 ", + "今天发现一个有趣的技术点:", + "推荐大家试试这个开发技巧 ", + "行业动态:", + "生活小贴士:" + ]; + + const sentence = sentences[Math.floor(Math.random() * sentences.length)]; + const emojiCount = getRandomNumber(1, EMOJIS.length); + let emojis = ""; + + for (let j = 0; j < emojiCount; j++) { + emojis += EMOJIS[Math.floor(Math.random() * EMOJIS.length)]; + } + + return (sentence + emojis + " ").repeat(3); +} + +export async function generateImages(): Promise> { + let mockData = new collections.Array() + let dataWorker: worker.ThreadWorker = + new worker.ThreadWorker('entry/ets/workers/GetNetworkData.ets', { name: 'GetNetworkData' }) + return new Promise(resolve => { + dataWorker.onmessage = (e: MessageEvents) => { + switch (e.data.type) { + case 'HttpDown': + resolve(mockData) + dataWorker.terminate(); + break; + case 'HttpError': + resolve(mockData) + dataWorker.terminate(); + break; + default: + dataWorker.terminate(); + break; + } + } + dataWorker.postMessageWithSharedSendable(mockData) + }) +} + + +export function generateRandomBlogData(count: number, onlyImage?: boolean): Array { + const blogData: BlogData[] = []; + const oneWeekAgo = new Date().getTime() - 7 * 24 * 60 * 60 * 1000; + + for (let i = 0; i < count; i++) { + // Random blog type (1:text, 2:image, 3:hybrid) + const type = Math.floor(Math.random() * 3) + 1; + + const content = type !== 2 ? generateRandomContent() : ""; + let images: string[] = []; + if (onlyImage) { + images = type !== 1 ? generateRandomImages() : [IMAGES[Math.floor(Math.random() * IMAGES.length)]]; + } else { + images = type !== 1 ? generateRandomImages() : []; + } + let a = new BlogData(); + a.id = i + 1; + a.user = generateRandomUser(); + a.content = content; + a.images = images; + a.time = formatTime(oneWeekAgo + Math.random() * 7 * 24 * 60 * 60 * 1000); + a.likes = getRandomNumber(0, 1000); + a.comments = getRandomNumber(0, 500); + a.reposts = getRandomNumber(0, 500); + + blogData.push(a); + } + return blogData; +} diff --git a/entry/src/main/ets/model/types.ets b/entry/src/main/ets/model/types.ets new file mode 100644 index 0000000..ea7ccad --- /dev/null +++ b/entry/src/main/ets/model/types.ets @@ -0,0 +1,41 @@ +/* + * 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. + */ + +@Observed +export class BlogData { + id: number = -1 + user: User = { name: '', avatar: '' } + content: string = ''; + images: string[] = []; + time: string = ''; + likes: number = 0; + comments: number = 0; + reposts: number = 0; + callback: Function | undefined = undefined; + fetchUrl: string | ImageContent = ImageContent.EMPTY; +} + +export interface User { + name: string; + avatar: string; +} + +export interface Params { + blogItem: BlogData +} + +export interface TextParams { + originalText: string +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/CombineWaterFlowPage.ets b/entry/src/main/ets/pages/CombineWaterFlowPage.ets new file mode 100644 index 0000000..ada6232 --- /dev/null +++ b/entry/src/main/ets/pages/CombineWaterFlowPage.ets @@ -0,0 +1,460 @@ +/* + * 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 { NodeItem, PartReuse, RecyclerView, WaterFlowManager } from '@hadss/scroll_components'; +import { PullToRefresh } from '@ohos/pulltorefresh'; +import { BlogData, Params } from '../model/types'; +import { collections, JSON, MessageEvents, taskpool, util, worker } from '@kit.ArkTS'; +import { CommonConstants } from '../common/constants/CommonConstants'; +import { Logger } from '../common/util/Logger'; +import { ComponentContent } from '@kit.ArkUI'; +import { sceneMockData } from '../workers/GetNetworkData'; +import { generateImages } from '../model/mock'; +import { common } from '@kit.AbilityKit'; + +@Concurrent +async function generateRandomBlogData(): Promise { + let array: collections.Array = await generateImages(); + const module = await import('../model/mock'); + let data = module.generateRandomBlogData(array.length); + for (let index = 0; index < array.length; index++) { + data[index].content = array[index].title; + data[index].images = [array[index].thumbnails]; + } + return data; +} + +class MyWaterFlowManager extends WaterFlowManager { + onWillCreateItem(index: number, data: BlogData) { + + let node: NodeItem | null = this.dequeueReusableNodeByType('BlogItemContainer'); + node?.setData({ blogItem: data }); + return node; + } +} + +enum FooterState { + Loading = 0, + End = 1 +} + +class MyParams { + footerState: FooterState = FooterState.Loading; + + constructor(footerState: FooterState) { + this.footerState = footerState; + } +} + +@Builder +function buildText(params: MyParams) { + Column() { + if (params.footerState == FooterState.Loading) { + Row() { + LoadingProgress().width(30).height(30) + Text("加载中...") + .fontSize(12) + .height(30) + .margin({ top: 2 }) + } + } else if (params.footerState == FooterState.End) { + Text("到底啦...") + .fontSize(12) + .height(30) + .align(Alignment.Center) + .margin({ top: 2 }) + } else { + Text("加载中...") + .fontSize(12) + .height(30) + .align(Alignment.Center) + .margin({ top: 2 }) + } + } + .width('100%') + .alignItems(HorizontalAlign.Center) +} + +@Entry +@Component +struct WaterFlowPage { + @State footerState: FooterState = FooterState.Loading + waterFlowView: WaterFlowManager = new MyWaterFlowManager({ + defaultNodeItem: "BlogItemContainer", + context: this.getUIContext() + }); + scroller: Scroller = new Scroller(); + @State data: Array = []; + private fetches: Map = new Map(); + footerContent: ComponentContent = + new ComponentContent(this.getUIContext(), wrapBuilder<[MyParams]>(buildText), + new MyParams(this.footerState)); + @State isRefreshing: boolean = false; + fetchAgent = new worker.ThreadWorker('entry/ets/workers/FetchAgent.ets', { name: 'fetchAgent' }); + cachePath = (this.getUIContext().getHostContext() as common.Context).getApplicationContext().cacheDir; + + // [Start PreCreate] + aboutToAppear(): void { + // [StartExclude PreCreate] + this.initView(); + // [Start Prefetch_1] + this.waterFlowView.registerFetchCallback(this.fetchCallback); + this.waterFlowView.registerCancelCallback(this.cancelCallback); + + taskpool.execute(generateRandomBlogData).then((data: ESObject) => { + this.data = data; + this.waterFlowView.setDataSource(data); + }) + // [end Prefetch_1] + // [EndExclude PreCreate] + + // register components + this.waterFlowView.registerNodeItem('BlogItemContainer', wrapBuilder(BlogItemContainer)); + this.waterFlowView.registerNodeItem('AdaptiveTextComponent', wrapBuilder(AdaptiveTextComponentContainer)); + this.waterFlowView.registerNodeItem('GridImageViewContainer', wrapBuilder(GridImageViewContainer)); + + this.waterFlowView.preCreate('BlogItemContainer', 30); + this.waterFlowView.preCreate('AdaptiveTextComponent', 30); + this.waterFlowView.preCreate('GridImageViewContainer', 30); + + // [StartExclude PreCreate] + Logger.info('WaterFlowPage==> nodePool : ' + JSON.stringify(util.getHash(this.waterFlowView.getRecyclePool()))); + + this.workerOnMessage(); + // [EndExclude PreCreate] + } + + // [End PreCreate] + + aboutToDisappear() { + this.fetchAgent.terminate(); + } + + initView() { + // [Start Prefetch_3] + this.waterFlowView.setViewStyle({ + scroller: this.scroller + })// [StartExclude Prefetch_3] + .width(CommonConstants.FULL_WIDTH) + .height(CommonConstants.FULL_HEIGHT) + .columnsTemplate(CommonConstants.WATER_FLOW_COLUMNS_TEMPLATE) + .columnsGap(CommonConstants.COLUMNS_GAP) + .rowsGap(CommonConstants.ROWS_GAP) + .padding({ + top: CommonConstants.PADDING, + left: CommonConstants.PADDING, + right: CommonConstants.PADDING, + })// [EndExclude Prefetch_3] + .onScrollIndex((start: number, end: number) => { + if (end > 0) { + this.waterFlowView.visibleAreaChanged(start, end); + } + }) + .onVisibleAreaChange([0.0, 1.0], (isVisible: boolean) => { + if (isVisible) { + this.waterFlowView.nodeAdapter.prefetcher?.start(); + } else { + this.waterFlowView.nodeAdapter.prefetcher?.stop(); + } + }) + // [End Prefetch_3] + } + + workerOnMessage() { + this.fetchAgent.onmessage = (e: MessageEvents) => { + let data = this.fetches.get(e.data.fetchId) + if (data === undefined) { + return; + } + + switch (e.data.type) { + case 'SUCCESS': + if (data.callback) { + data.callback(e.data.fetchUrl); + } else { + data.fetchUrl = e.data.fetchUrl + } + this.fetches.delete(e.data.fetchId) + break; + case 'FAIL': + this.fetches.delete(e.data.fetchId) + break; + default: + break; + } + } + } + + // [Start Prefetch_2] + fetchCallback: (item: ESObject, fetchId: number) => Promise = (item: ESObject, fetchId: number) => { + let data = item as BlogData; + if (data.images.length == 0) { + return Promise.resolve(); + } + + this.fetches.set(fetchId, data); + this.fetchAgent.postMessageWithSharedSendable({ + type: 'fetch', + cachePath: this.cachePath, + url: data.images[0], + fetchId: fetchId + }); + return Promise.resolve(); + } + cancelCallback: (fetchId: number) => void = (fetchId: number) => { + this.fetches.delete(fetchId); + this.fetchAgent.postMessageWithSharedSendable({ type: 'cancel', fetchId: fetchId }); + } + + // [End Prefetch_2] + + @Builder + getWaterFlow() { + RecyclerView({ + viewManager: this.waterFlowView + }) + } + + build() { + Column() { + // Header navigation bar + Row() { + Text($r('app.string.scenario_waterFlow2')) + .fontSize($r('app.float.title_font_size')) + .fontWeight(FontWeight.Bold) + .fontColor($r('app.color.text_color')) + } + .padding(CommonConstants.PADDING) + .width(CommonConstants.FULL_WIDTH) + .backgroundColor(Color.White) + + // [Start Load_More] + PullToRefresh({ + data: $data, + scroller: this.scroller, + customList: () => { + this.getWaterFlow() + }, + // [StartExclude Load_More] + onRefresh: () => { + return new Promise((resolve) => { + resolve('刷新成功'); + generateRandomBlogData().then((data: ESObject) => { + this.data = data; + this.waterFlowView.setDataSource(data); + }); + }) + }, + // [EndExclude Load_More] + onLoadMore: () => { + return new Promise((resolve) => { + resolve(''); + generateRandomBlogData().then((data: ESObject) => { + this.waterFlowView.nodeAdapter.pushData(data); + }); + }) + } + }) + .layoutWeight(1) + // [End Load_More] + } + .height(CommonConstants.FULL_HEIGHT) + .backgroundColor('#F5F5F5') + } +} + +@Builder +function BlogItemContainer($$: Params) { + BlogItem({ blogItem: $$.blogItem }) +} + +@Component +struct BlogItem { + @State blogItem: BlogData = new BlogData() + + aboutToReuse(params: ESObject): void { + this.blogItem = params.blogItem; + } + + aboutToRecycle(): void { + this.blogItem.callback = undefined; + this.blogItem.fetchUrl = ImageContent.EMPTY; + } + + build() { + Column({ space: 12 }) { + HeaderComponent({ blogItem: this.blogItem }) + if (this.blogItem?.content.length > 0) { + PartReuse({ + type: 'AdaptiveTextComponent', + builder: wrapBuilder(AdaptiveTextComponentContainer), + data: { blogItem: this.blogItem } + }) + } + // 图片展示 + if (this.blogItem?.images && this.blogItem.images.length > 0) { + PartReuse({ + type: 'GridImageViewContainer', + builder: wrapBuilder(GridImageViewContainer), + data: { blogItem: this.blogItem } + }) + } + + BottomContent({ blogItem: this.blogItem }) + } + .padding(12) + .backgroundColor(Color.White) + .borderRadius(12) + } +} + + +@Component +export struct HeaderComponent { + @ObjectLink blogItem: BlogData + + build() { + // 用户信息 + Row({ space: 8 }) { + Image(this.blogItem.user.avatar) + .width(40) + .height(40) + .borderRadius(20) + Column() { + Text(this.blogItem.user.name) + .fontSize(16) + .fontColor('#333') + Text(this.blogItem.time) + .fontSize(12) + .fontColor('#999') + }.alignItems(HorizontalAlign.Start) + .layoutWeight(1) + } + } +} + +@Builder +export function AdaptiveTextComponentContainer($$: Params) { + AdaptiveTextComponent({ blogItem: $$.blogItem }) +} + +@Component +export struct AdaptiveTextComponent { + @State private showFullText: boolean = false; + @State originalText: string = '这里是一段非常长的示例文本...'; // 替换为实际文本 + @State blogItem: BlogData = new BlogData(); + + aboutToReuse(params: ESObject): void { + this.blogItem = params.blogItem; + } + + build() { + Stack() { + // Text container + Text(this.blogItem.content) + .fontSize(16) + .lineHeight(20) + .maxLines(this.showFullText ? undefined : 3) + .textOverflow({ overflow: TextOverflow.None }) + .width('auto') + .height('auto') + + } + .width('auto') + .height('auto') + } +} + +@Builder +function GridImageViewContainer($$: Params) { + GridImageView({ blogItem: $$.blogItem }) +} + +@Component +struct GridImageView { + @State blogItem: BlogData = new BlogData() + @State fetchUrl: string | ImageContent = ImageContent.EMPTY + callback = (fetchUrl: string) => { + this.blogItem.fetchUrl = fetchUrl; + this.fetchUrl = fetchUrl; + } + + aboutToAppear() { + this.blogItem.callback = this.callback; + this.fetchUrl = this.blogItem.fetchUrl; + } + + aboutToRecycle() { + this.blogItem.callback = undefined; + this.fetchUrl = ImageContent.EMPTY; + } + + aboutToReuse(params: ESObject): void { + this.blogItem = params.blogItem; + this.blogItem.callback = this.callback; + this.fetchUrl = this.blogItem.fetchUrl; + } + + build() { + // [Start Prefetch_4] + // Preload images using prefetch + Image(this.fetchUrl) + .sourceSize({ width: 100, height: 100 }) + .width(CommonConstants.FULL_WIDTH) + .aspectRatio(1) + .objectFit(ImageFit.Cover) + } + // [End Prefetch_4] +} + +@Component +export struct BottomContent { + @ObjectLink blogItem: BlogData + + build() { + // Interactive toolbar + Row({ space: 20 }) { + Row() { + Image('https://img2.baidu.com/it/u=3148587158,2445495738&fm=253&fmt=auto&app=138&f=PNG?w=500&h=500') + .width(20) + .height(20) + Text(this.blogItem.likes.toString()) + .margin({ left: 4 }) + } + .backgroundColor(Color.Transparent) + + Row() { + Image('https://img.ixintu.com/download/jpg/20200805/8a9cb0d17145f80ea2cf8d060070276e_512_512.jpg%21con') + .width(20) + .height(20) + Text(this.blogItem.comments.toString()) + .margin({ left: 4 }) + } + .backgroundColor(Color.Transparent) + + Row() { + Image('https://gd-hbimg.huaban.com/f5159d11bab345671e2943a4cf792048eb18f07a3e34-6EOAyU_fw658') + .width(20) + .height(20) + Text(this.blogItem.reposts.toString()) + .margin({ left: 4 }) + } + .backgroundColor(Color.Transparent) + } + .width('50%') + .justifyContent(FlexAlign.SpaceAround) + .padding({ top: 8 }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 206e4fd..1943b49 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -23,7 +23,67 @@ struct Index { build() { Column() { - Text('hello world') + Row() { + Text($r('app.string.title')) + .fontWeight(FontWeight.Bold) + .fontSize($r('app.float.large_title_font_size')) + .width(CommonConstants.FULL_WIDTH) + .fontColor($r('app.color.text_color')) + } + .padding(CommonConstants.PADDING) + .width(CommonConstants.FULL_WIDTH) + .height(CommonConstants.INDEX_TITLE_HEIGHT) + + Column() { + ForEach(this.routes, (item: Route) => { + Row() { + Text(item.title) + .width(CommonConstants.INDEX_CONTENT_WIDTH) + .fontSize($r('app.float.double_text_size')) + .fontColor($r('app.color.index_text_color')) + } + .height(CommonConstants.CARD_TITLE_HEIGHT) + + Column() { + ForEach(item.child, (itemChild: ChildRoute, index: number) => { + Column() { + Row() { + Text(itemChild.text) + .height(CommonConstants.CARD_TEXT_HEIGHT) + .fontWeight(FontWeight.Medium) + .padding({ left: $r('app.float.index_item_padding_left') }) + .fontSize($r('app.float.text_size')) + Column().layoutWeight(1) + Image($r('app.media.ic_public_arrow_right')) + .width($r('app.float.index_arrow_width')).height($r('app.float.index_arrow_height')) + .margin({ right: $r('app.float.index_item_margin_right') }) + }.justifyContent(FlexAlign.Start) + .alignItems(VerticalAlign.Center) + + Stack() { + if (item.child.length - this.one !== index) { + Row() + .height(CommonConstants.DIVIDER_HEIGHT) + .backgroundColor($r('app.color.index_divider_color')) + .width(CommonConstants.DIVIDER_WIDTH) + } + } + } + .onClick(() => { + this.getUIContext().getRouter().pushUrl({ + url: 'pages/' + itemChild.to + }); + }) + .width(CommonConstants.INDEX_CONTENT_WIDTH) + .height(CommonConstants.CARD_TEXT_HEIGHT) + }, (item: ChildRoute, index: number) => JSON.stringify(item) + index) + } + .margin({ top: $r('app.float.margin_index_top'), bottom: $r('app.float.margin_index_bottom') }) + .borderRadius(CommonConstants.BORDER_RADIUS_INDEX_LIST) + .backgroundColor(Color.White) + }, (item: Route, index: number) => JSON.stringify(item) + index) + } + .width(CommonConstants.FULL_WIDTH) } .padding({ top: $r('app.float.padding_index_top') }) .translate({ y: CommonConstants.TRANSLATE_TOP }) diff --git a/entry/src/main/ets/pages/MultiFlowItemPage.ets b/entry/src/main/ets/pages/MultiFlowItemPage.ets new file mode 100644 index 0000000..de96c48 --- /dev/null +++ b/entry/src/main/ets/pages/MultiFlowItemPage.ets @@ -0,0 +1,176 @@ +/* + * 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 { NodeItem, RecyclerView, WaterFlowManager } from '@hadss/scroll_components'; +import { common } from '@kit.AbilityKit'; +import { CommonConstants } from '../common/constants/CommonConstants'; +import { BlogData, Params } from '../model/types'; +import { collections, taskpool } from '@kit.ArkTS'; +import { sceneMockData } from '../workers/GetNetworkData'; +import { generateImages } from '../model/mock'; + +@Concurrent +async function generateRandomBlogData(): Promise { + let array: collections.Array = await generateImages(); + const module = await import('../model/mock'); + let data = module.generateRandomBlogData(array.length); + for (let index = 0; index < array.length; index++) { + data[index].content = array[index].title; + data[index].images = [array[index].thumbnails]; + } + return data; +} + +// [Start quick_start_4_c] +class MyWaterFlowManager extends WaterFlowManager { + onWillCreateItem(index: number, data: BlogData) { + let node: NodeItem + if (index % 2 === 0) { + node = this.dequeueReusableNodeByType('ImageContainer'); + } else { + node = this.dequeueReusableNodeByType('TextContainer'); + } + node?.setData({ blogItem: data }); + return node; + } +} +// [End quick_start_4_c] + + +// [Start quick_start_4_c] +@Entry +@Component +struct MultiFlowItemPage { + waterFlowView: MyWaterFlowManager = new MyWaterFlowManager({ + defaultNodeItem: 'ImageContainer', + context: this.getUIContext() + }); + scroller: Scroller = new Scroller(); + @State dataArray: BlogData[] = [] // Bind data source for data iteration + + aboutToAppear(): void { + this.initView(); + taskpool.execute(generateRandomBlogData).then((data: ESObject) => { + this.dataArray = data; + this.waterFlowView.setDataSource(data); + }) + + this.waterFlowView.registerNodeItem('ImageContainer', wrapBuilder(ImageContainer)); + this.waterFlowView.registerNodeItem('TextContainer', wrapBuilder(TextContainer)); + + // [StartExclude quick_start_4_c] + this.waterFlowView.preCreate('ImageContainer', 30); + this.waterFlowView.preCreate('TextContainer', 30); + // [EndExclude quick_start_4_c] + } + + + initView() { + this.waterFlowView.setViewStyle({ scroller: this.scroller }) + // [StartExclude quick_start_4_c] + .height(CommonConstants.FULL_HEIGHT) + .columnsTemplate(CommonConstants.WATER_FLOW_COLUMNS_TEMPLATE) + .columnsGap(CommonConstants.COLUMNS_GAP) + .rowsGap(CommonConstants.ROWS_GAP) + .padding({ + top: CommonConstants.PADDING, + left: CommonConstants.PADDING, + right: CommonConstants.PADDING, + }) + // [EndExclude quick_start_4_c] + } + + build() { + Column() { + + RecyclerView({ + viewManager: this.waterFlowView + }) + + } + .height(CommonConstants.FULL_HEIGHT) + .backgroundColor($r('app.color.home_background_color')) + } +} +// [End quick_start_4_c] + +// [Start quick_start_4_c] +@Builder +function ImageContainer($$: Params) { + ImageContainerView({ blogItem: $$.blogItem }) +} +// [End quick_start_4_c] + +@Component +struct ImageContainerView { + @State blogItem: BlogData = new BlogData(); + @State showMenu: boolean = false; + @State flowHeight: Length = 0; + private context = this.getUIContext().getHostContext() as common.UIAbilityContext; + + aboutToReuse(params: ESObject): void { + this.blogItem = params.blogItem; + } + + build() { + Stack() { + Image(this.blogItem.images[0]) + .sourceSize({ width: 100, height: 100 }) + .width(CommonConstants.FULL_WIDTH) + .aspectRatio(1) + .objectFit(ImageFit.Cover) + .onAreaChange((_oldValue: Area, newValue: Area) => { + this.flowHeight = newValue.height; + }) + } + .borderRadius(12) + .clip(true) + } + +} + +// [Start quick_start_4_c] +@Builder +function TextContainer($$: Params) { + TextContainerView({ blogItem: $$.blogItem }) +} +// [End quick_start_4_c] + +@Component +struct TextContainerView { + @State blogItem: BlogData = new BlogData(); + @State flowHeight: Length = 0; + + aboutToReuse(params: ESObject): void { + this.blogItem = params.blogItem; + } + + build() { + Stack() { + Text(this.blogItem.content) + .width(CommonConstants.FULL_WIDTH) + .onAreaChange((_oldValue: Area, newValue: Area) => { + this.flowHeight = newValue.height; + }) + .padding(CommonConstants.PADDING) + .borderRadius(12) + .borderWidth(2) + .borderColor(Color.Grey) + } + .borderRadius(12) + .clip(true) + } + +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/SharedPoolPage.ets b/entry/src/main/ets/pages/SharedPoolPage.ets new file mode 100644 index 0000000..a059f82 --- /dev/null +++ b/entry/src/main/ets/pages/SharedPoolPage.ets @@ -0,0 +1,203 @@ +/* + * 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 { NodeItem, PartReuse, RecyclerView, WaterFlowManager } from '@hadss/scroll_components'; +import { util } from '@kit.ArkTS'; +import { BlogData, Params } from '../model/types'; +import { CommonConstants } from '../common/constants/CommonConstants'; +import { Utils } from '../common/util/Utils'; +import { BottomContent, HeaderComponent } from './CombineWaterFlowPage'; +import { Logger } from '../common/util/Logger'; + +@Concurrent +async function generateRandomBlogData(): Promise { + const module = await import('../model/mock'); + return module.generateRandomBlogData(30); +} + +class MyWaterFlowManager extends WaterFlowManager { + onWillCreateItem(index: number, data: BlogData) { + let node: NodeItem | null = this.dequeueReusableNodeByType('TestBlogItemContainer'); + node?.setData({ blogItem: data }); + return node; + } +} + +@Component +export struct SharedPoolPage { + waterFlowView: MyWaterFlowManager = new MyWaterFlowManager({ + defaultNodeItem: 'TestBlogItemContainer', + context: this.getUIContext() + }); + scroller: Scroller = new Scroller(); + @State data: Array = []; + @State isRefreshing: boolean = false; + + aboutToDisappear(): void { + Utils.getInstance().nodePool?.clear() + } + + aboutToAppear(): void { + this.initView(); + generateRandomBlogData().then((data: ESObject) => { + this.data = data; + this.waterFlowView.setDataSource(data); + }); + + // [Start Share_Pool] + if (Utils.getInstance().nodePool) { + // 注册复用池 + this.waterFlowView.registerRecyclePool(Utils.getInstance().nodePool!); + } else { + Utils.getInstance().nodePool = this.waterFlowView.getRecyclePool(); + } + // [End Share_Pool] + + // 注册复用模板 + this.waterFlowView.registerNodeItem('TestBlogItemContainer', wrapBuilder(MyBlogItemContainer)); + this.waterFlowView.registerNodeItem('GridImageViewContainer', wrapBuilder(GridImageViewContainer)); + + // 预创建 + this.waterFlowView.preCreate('TestBlogItemContainer', 30); + this.waterFlowView.preCreate('GridImageViewContainer', 30); + + Logger.info('SharedPoolPage==> nodePool : ' + JSON.stringify(util.getHash(this.waterFlowView.getRecyclePool()))); + } + + + initView() { + this.waterFlowView.setViewStyle({ scroller: this.scroller }) + .width(CommonConstants.FULL_WIDTH) + .height(CommonConstants.FULL_HEIGHT) + .columnsTemplate(CommonConstants.WATER_FLOW_COLUMNS_TEMPLATE) + .columnsGap(CommonConstants.COLUMNS_GAP) + .rowsGap(CommonConstants.ROWS_GAP) + .padding({ + top: CommonConstants.PADDING, + left: CommonConstants.PADDING, + right: CommonConstants.PADDING, + }) + .onScrollIndex((_start: number, end: number) => { + if (end > this.waterFlowView.nodeAdapter.totalNodeCount - 10) { + setTimeout(() => { + generateRandomBlogData().then((data: ESObject) => { + this.waterFlowView.nodeAdapter.pushData(data) + }) + }, 100) + } + }) + } + + // [Start Refresh_2] + @Builder + getWaterFlow() { + RecyclerView({ + viewManager: this.waterFlowView + }) + } + // [end Refresh_2] + + build() { + Column() { + // Header navigation bar + Row() { + Text($r('app.string.scenario_waterFlow3')) + .fontWeight(FontWeight.Bold) + .fontSize($r('app.float.title_font_size')) + .width(CommonConstants.FULL_WIDTH) + .fontColor($r('app.color.text_color')) + } + .padding(CommonConstants.PADDING) + .width(CommonConstants.FULL_WIDTH) + .backgroundColor(Color.White) + // [Start Refresh_2] + Refresh({ refreshing: $$this.isRefreshing }) { + this.getWaterFlow() + } + .layoutWeight(1) + .onRefreshing(() => { + // [Start Refresh_1] + // modify date + generateRandomBlogData().then((data: BlogData[]) => { + this.isRefreshing = false + this.data = data; + this.waterFlowView.setDataSource(data); + }) + // [End Refresh_1] + }) + // [End Refresh_2] + + } + .height(CommonConstants.FULL_HEIGHT) + .backgroundColor('#F5F5F5') + } +} + +@Builder +function MyBlogItemContainer($$: Params) { + MyBlogItem({ blogItem: $$.blogItem }) +} + +@Component +struct MyBlogItem { + @State blogItem: BlogData = new BlogData(); + + aboutToReuse(params: ESObject): void { + this.blogItem = params.blogItem; + } + + build() { + Column({ space: 12 }) { + HeaderComponent({ blogItem: this.blogItem }) + // Image display + if (this.blogItem?.images && this.blogItem.images.length > 0) { + PartReuse({ + type: 'GridImageViewContainer', + builder: wrapBuilder(GridImageViewContainer), + data: { blogItem: this.blogItem } + }) + } + + BottomContent({ blogItem: this.blogItem }) + } + .padding(12) + .backgroundColor(Color.White) + .borderRadius(12) + } +} + +@Builder +export function GridImageViewContainer($$: Params) { + GridImageView({ blogItem: $$.blogItem }) +} + +@Component +struct GridImageView { + @State blogItem: BlogData = new BlogData(); + + aboutToReuse(params: ESObject): void { + this.blogItem = params.blogItem; + } + + build() { + Image(this.blogItem.images[0]) + .sourceSize({ width: 100, height: 100 }) + .width(CommonConstants.FULL_WIDTH) + .aspectRatio(1) + .objectFit(ImageFit.Cover) + } +} + + diff --git a/entry/src/main/ets/pages/SharedPoolSecondPage.ets b/entry/src/main/ets/pages/SharedPoolSecondPage.ets new file mode 100644 index 0000000..44b72f3 --- /dev/null +++ b/entry/src/main/ets/pages/SharedPoolSecondPage.ets @@ -0,0 +1,179 @@ +/* + * 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 { NodeItem, PartReuse, RecyclerView, WaterFlowManager } from '@hadss/scroll_components'; +import { Utils } from '../common/util/Utils'; +import { util } from '@kit.ArkTS'; +import { CommonConstants } from '../common/constants/CommonConstants'; +import { BlogData, Params } from '../model/types'; +import { BottomContent, HeaderComponent } from './CombineWaterFlowPage'; +import { Logger } from '../common/util/Logger'; +import { GridImageViewContainer } from './SharedPoolPage'; + +@Concurrent +async function generateRandomBlogData(): Promise { + const module = await import('../model/mock'); + return module.generateRandomBlogData(30); +} + +class MyWaterFlowManager extends WaterFlowManager { + onWillCreateItem(index: number, data: BlogData) { + let node: NodeItem | null = this.dequeueReusableNodeByType('TestBlogItemContainer'); + node?.setData({ blogItem: data }); + return node; + } +} +// [Start Share_Pool] +@Component +export struct SharedPoolSecondPage { + waterFlowView: MyWaterFlowManager = new MyWaterFlowManager({ + defaultNodeItem: 'TestBlogItemContainer', + context: this.getUIContext() + }); + // [StartExclude Share_Pool] + scroller: Scroller = new Scroller(); + @State data: Array = []; + @State isRefreshing: boolean = false; + + aboutToDisappear(): void { + Utils.getInstance().nodePool?.clear() + } + // [EndExclude Share_Pool] + + aboutToAppear(): void { + // [StartExclude Share_Pool] + this.initView(); + generateRandomBlogData().then((data: ESObject) => { + this.data = data; + this.waterFlowView.setDataSource(data); + }); + // [EndExclude Share_Pool] + + if (Utils.getInstance().nodePool) { + // Registration Reuse Pool + this.waterFlowView.registerRecyclePool(Utils.getInstance().nodePool!); + } else { + Utils.getInstance().nodePool = this.waterFlowView.getRecyclePool(); + } + + // [StartExclude Share_Pool] + // Reusable Registration Template + this.waterFlowView.registerNodeItem('TestBlogItemContainer', wrapBuilder(MyBlogItemContainer)); + this.waterFlowView.registerNodeItem('GridImageViewContainer', wrapBuilder(GridImageViewContainer)); + + // Pre-initialization + this.waterFlowView.preCreate('TestBlogItemContainer', 30); + this.waterFlowView.preCreate('GridImageViewContainer', 30); + + Logger.info('SharedPoolSecondPage==> nodePool : ' + + JSON.stringify(util.getHash(this.waterFlowView.getRecyclePool()))); + // [EndExclude Share_Pool] + } + + // [StartExclude Share_Pool] + initView() { + this.waterFlowView.setViewStyle({ scroller: this.scroller }) + .width(CommonConstants.FULL_WIDTH) + .height(CommonConstants.FULL_HEIGHT) + .columnsTemplate(CommonConstants.WATER_FLOW_COLUMNS_TEMPLATE) + .columnsGap(CommonConstants.COLUMNS_GAP) + .rowsGap(CommonConstants.ROWS_GAP) + .padding({ + top: CommonConstants.PADDING, + left: CommonConstants.PADDING, + right: CommonConstants.PADDING, + }) + .onScrollIndex((_start: number, end: number) => { + if (end > this.waterFlowView.nodeAdapter.totalNodeCount - 10) { + setTimeout(() => { + generateRandomBlogData().then((data: ESObject) => { + this.waterFlowView.nodeAdapter.pushData(data) + }) + }, 100) + } + }) + } + + @Builder + getWaterFlow() { + RecyclerView({ + viewManager: this.waterFlowView + }) + } + + build() { + Column() { + // Header navigation bar + Row() { + Text($r('app.string.scenario_waterFlow3')) + .fontWeight(FontWeight.Bold) + .fontSize($r('app.float.title_font_size')) + .width(CommonConstants.FULL_WIDTH) + .fontColor($r('app.color.text_color')) + } + .padding(CommonConstants.PADDING) + .width(CommonConstants.FULL_WIDTH) + .backgroundColor(Color.White) + + Refresh({ refreshing: $$this.isRefreshing }) { + this.getWaterFlow() + } + .layoutWeight(1) + .onRefreshing(() => { + generateRandomBlogData().then((data: BlogData[]) => { + this.isRefreshing = false + this.data = data; + this.waterFlowView.setDataSource(data); + }) + }) + + } + .height(CommonConstants.FULL_HEIGHT) + .backgroundColor('#F5F5F5') + } + // [EndExclude Share_Pool] +} +// [End Share_Pool] +@Builder +function MyBlogItemContainer($$: Params) { + MyBlogItem({ blogItem: $$.blogItem }) +} + +@Component +struct MyBlogItem { + @State blogItem: BlogData = new BlogData(); + + aboutToReuse(params: ESObject): void { + this.blogItem = params.blogItem; + } + + build() { + Column({ space: 12 }) { + HeaderComponent({ blogItem: this.blogItem }) + // Image display + if (this.blogItem?.images && this.blogItem.images.length > 0) { + PartReuse({ + type: 'GridImageViewContainer', + builder: wrapBuilder(GridImageViewContainer), + data: { blogItem: this.blogItem } + }) + } + BottomContent({ blogItem: this.blogItem }) + } + .padding(12) + .backgroundColor(Color.White) + .borderRadius(12) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/StandardWaterFlowPage.ets b/entry/src/main/ets/pages/StandardWaterFlowPage.ets new file mode 100644 index 0000000..df0f022 --- /dev/null +++ b/entry/src/main/ets/pages/StandardWaterFlowPage.ets @@ -0,0 +1,264 @@ +/* + * 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. + */ + +// [Start quick_start_3] +// [Start quick_start_1] +import { NodeItem, RecyclerView, WaterFlowManager } from '@hadss/scroll_components'; +// [End quick_start_1] +// [End quick_start_3] +import { LengthMetrics, typeNode, window } from '@kit.ArkUI'; +import { common } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { CommonConstants } from '../common/constants/CommonConstants'; +import { Logger } from '../common/util/Logger'; +import { BlogData, Params } from '../model/types'; + +@Concurrent +async function generateRandomBlogData(): Promise { + const module = await import('../model/mock'); + return module.generateRandomBlogData(300, true); +} + +// [Start quick_start_4_a] +// [Start quick_start_1] +class MyWaterFlowManager extends WaterFlowManager { + onWillCreateItem(index: number, data: BlogData) { + // [StartExclude quick_start_1] + let node: NodeItem | null = this.dequeueReusableNodeByType('StandardGridImageContainer'); + node?.setData({ blogItem: data }); + return node; + // [EndExclude quick_start_1] + } +} +// [End quick_start_1] +// [End quick_start_4_a] + +// [Start quick_start_4_a] +// [Start quick_start_1] +// [Start quick_start_3] +@Entry +@Component +struct StandardWaterFlowPage { + waterFlowView: MyWaterFlowManager = new MyWaterFlowManager({ + defaultNodeItem: 'StandardGridImageContainer', + context: this.getUIContext() + }); + // [StartExclude quick_start_4_a] + // [StartExclude quick_start_3] + // [StartExclude quick_start_1] + scroller: Scroller = new Scroller(); + private context = this.getUIContext().getHostContext() as common.UIAbilityContext; + private windowClass = (this.context as common.UIAbilityContext).windowStage.getMainWindowSync(); + // [EndExclude quick_start_3] + @State dataArray: BlogData[] = [] // Bind data source for data iteration + // [EndExclude quick_start_4_a] + + + // [Start Column_Change] + aboutToAppear(): void { + // [StartExclude quick_start_4_a] + // [StartExclude quick_start_3] + let orientation = window.Orientation.AUTO_ROTATION; + this.windowClass.setPreferredOrientation(orientation, (err: BusinessError) => { + const errCode: number = err.code; + if (errCode) { + Logger.error('Failed to set window orientation. Cause:' + JSON.stringify(err)); + return; + } + Logger.info('Succeed to setting window orientation'); + }) + + this.windowClass.on('windowSizeChange', (size) => { + let viewWidth = size.width; + let viewHeight = size.height; + + let node = this.waterFlowView.__VIEW_CONTROLLER__.parentLayout as typeNode.WaterFlow; + if (viewWidth > viewHeight) { + node.attribute.columnsTemplate('1fr 1fr 1fr'); + } else { + node.attribute.columnsTemplate('1fr 1fr'); + } + }) + + this.initView(); + // [StartExclude Column_Change] + // [EndExclude quick_start_3] + generateRandomBlogData().then((data: BlogData[]) => { + this.waterFlowView.setDataSource(data); + this.dataArray = data; + }); + // [EndExclude quick_start_4_a] + + this.waterFlowView.registerNodeItem('StandardGridImageContainer', wrapBuilder(StandardGridImageContainer)); + + // [StartExclude quick_start_4_a] + // [StartExclude quick_start_3] + this.waterFlowView.preCreate('StandardGridImageContainer', 30); + + // [Start Delete_2] + this.context.eventHub.on(CommonConstants.EVENT_REMOVE_ITEM, (blogItem: BlogData) => { + // [Start AnimateTo] + let foundIndex = + this.dataArray.findIndex((value: BlogData) => JSON.stringify(value) === + JSON.stringify(blogItem)) + if (foundIndex !== -1) { + this.getUIContext().animateTo({ duration: 200 }, () => { + this.waterFlowView.nodeAdapter.deleteData(foundIndex) + }) + } + // [End AnimateTo] + }) + // [End Delete_2] + // [EndExclude quick_start_3] + // [EndExclude quick_start_4_a] + // [EndExclude Column_Change] + } + // [End Column_Change] + + // [StartExclude quick_start_4_a] + // [StartExclude quick_start_3] + aboutToDisappear(): void { + this.context.eventHub.off(CommonConstants.EVENT_REMOVE_ITEM) + } + + // [Start Fading] + // [Start Column_Change] + initView() { + // [Start quick_start_2] + this.waterFlowView.setViewStyle({ scroller: this.scroller }) + .height(CommonConstants.FULL_HEIGHT) + .columnsTemplate(CommonConstants.WATER_FLOW_COLUMNS_TEMPLATE) + .columnsGap(CommonConstants.COLUMNS_GAP) + .rowsGap(CommonConstants.ROWS_GAP) + .padding({ + top: CommonConstants.PADDING, + left: CommonConstants.PADDING, + right: CommonConstants.PADDING, + }) + .fadingEdge(true, { fadingEdgeLength: LengthMetrics.vp(80) }) + // [End quick_start_2] + } + // [EndExclude quick_start_3] + // [End Column_Change] + // [End Fading] + + build() { + Column() { + // [StartExclude quick_start_3] + // Header navigation bar + Row({ space: 8 }) { + Text($r('app.string.scenario_waterFlow')) + .fontWeight(FontWeight.Bold) + .fontSize($r('app.float.title_font_size')) + .width(CommonConstants.FULL_WIDTH) + .fontColor($r('app.color.text_color')) + } + .padding(CommonConstants.PADDING) + .width(CommonConstants.FULL_WIDTH) + .backgroundColor(Color.White) + // [EndExclude quick_start_3] + + RecyclerView({ + viewManager: this.waterFlowView + }) + + } + .height(CommonConstants.FULL_HEIGHT) + .backgroundColor($r('app.color.home_background_color')) + } + // [EndExclude quick_start_1] + // [EndExclude quick_start_4_a] +} +// [End quick_start_1] +// [End quick_start_3] +// [End quick_start_4_a] + +// [Start quick_start_4_a] +// [Start quick_start_3] +@Builder +function StandardGridImageContainer($$: Params) { + GridImageView({ blogItem: $$.blogItem }) +} +// [End quick_start_3] +// [End quick_start_4_a] + +@Component +struct GridImageView { + @State blogItem: BlogData = new BlogData(); + @State showMenu: boolean = false; + @State flowHeight: Length = 0; + private context = this.getUIContext().getHostContext() as common.UIAbilityContext; + + aboutToReuse(params: ESObject): void { + this.blogItem = params.blogItem; + } + + build() { + // [Start Delete_1] + Stack() { + Image(this.blogItem.images[0]) + // [StartExclude Delete_1] + .sourceSize({ width: 100, height: 100 }) + .width(CommonConstants.FULL_WIDTH) + .aspectRatio(1) + .objectFit(ImageFit.Cover) + .onAreaChange((_oldValue: Area, newValue: Area) => { + this.flowHeight = newValue.height; + }) + // [EndExclude Delete_1] + } + // [StartExclude Delete_1] + .borderRadius(12) + .clip(true) + .bindPopup(this.showMenu, { + builder: this.popUpBuilder, + placement: Placement.Top, + mask: { color: '#33000000' }, + popupColor: Color.Yellow, + enableArrow: true, + showInSubWindow: false, + onStateChange: (e) => { + if (!e.isVisible) { + this.showMenu = false; + } + } + }) + // [EndExclude Delete_1] + .priorityGesture( + GestureGroup(GestureMode.Exclusive, + LongPressGesture().onAction(() => { + this.showMenu = true; + })) + ) + // [End Delete_1] + } + + // [Start Delete_2] + @Builder + popUpBuilder() { + Row({ space: 2 }) { + Text($r('app.string.not_interested_button_text')) + } + .width(100) + .height(50) + .padding(5) + .justifyContent(FlexAlign.Center) + .onClick(() => { + this.context.eventHub.emit(CommonConstants.EVENT_REMOVE_ITEM, this.blogItem) + this.showMenu = false; + }) + } + // [End Delete_2] +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/StickyWaterFlowPage.ets b/entry/src/main/ets/pages/StickyWaterFlowPage.ets new file mode 100644 index 0000000..5969893 --- /dev/null +++ b/entry/src/main/ets/pages/StickyWaterFlowPage.ets @@ -0,0 +1,393 @@ +/* + * 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 { display, typeNode } from '@kit.ArkUI'; +import { NodeItem, RecyclerView, WaterFlowManager } from '@hadss/scroll_components'; +import { CommonConstants } from '../common/constants/CommonConstants' +import { Logger } from '../common/util/Logger'; + +interface Params3 { + item: number +} + +class MyWaterFlowManager extends WaterFlowManager { + onWillCreateItem(index: number, data: Params3): NodeItem { + let nodeItem: NodeItem = this.dequeueReusableNodeByType('StickyBlogItemContainer') + nodeItem?.setData({ item: data }) + return nodeItem; + } +} + +@Component +struct StickyFlowItem { + @State item: number = 0; + + aboutToReuse(params: ESObject): void { + this.item = params.item + } + + build() { + if (this.item !== 1) { + RelativeContainer() { + Image($rawfile(`sections/${this.item % 4}.jpg`)) + .objectFit(ImageFit.Cover) + .width(CommonConstants.FULL_WIDTH) + .layoutWeight(1) + .borderRadius($r('app.float.sections_item_radius')) + .alignRules({ + top: { anchor: "__container__", align: VerticalAlign.Top }, + bottom: { anchor: "__container__", align: VerticalAlign.Bottom }, + left: { anchor: "__container__", align: HorizontalAlign.Start }, + right: { anchor: "__container__", align: HorizontalAlign.End } + }) + .id('image') + + Stack() { + } + .linearGradient({ + angle: 0, + colors: [[$r('app.color.linearGradient_first_color'), 0.0], + [$r('app.color.linearGradient_last_color'), 1.0]] + }) + .width(CommonConstants.FULL_WIDTH) + .height($r('app.float.sections_item_blur_height')) + .borderRadius($r('app.float.sections_item_radius')) + .hitTestBehavior(HitTestMode.None) + .alignRules({ + bottom: { anchor: "__container__", align: VerticalAlign.Bottom }, + left: { anchor: "__container__", align: HorizontalAlign.Start }, + right: { anchor: "__container__", align: HorizontalAlign.End } + }) + .id('mask') + + Text($r('app.string.want_to_eat')) + .fontSize($r('app.float.sections_item_text_size')) + .fontColor(Color.White) + .alignRules({ + bottom: { anchor: "__container__", align: VerticalAlign.Bottom }, + middle: { anchor: "__container__", align: HorizontalAlign.Center } + }) + .margin({ bottom: $r("app.float.sections_item_center_text_margin_bottom") }) + .id('text') + } + } + } +} + +@Builder +function StickyBlogItemContainer(param: Params3) { + StickyFlowItem({ item: param.item }) +} + +@Entry +@Component +struct StickyWaterFlowPage { + waterFlowView: MyWaterFlowManager = + new MyWaterFlowManager({ defaultNodeItem: 'StickyBlogItemContainer', context: this.getUIContext() }) + @State arr: number[] = CommonConstants.ARR; + @State colors: number[] = CommonConstants.COLORS; + @State scrollOffset: number = 0; + @State minSize: number = 80; + @State maxSize: number = 180; + // [Start Section] + @State sections: WaterFlowSections = new WaterFlowSections(); + // [End Section] + @State isFoldStatus: boolean = true; + scroller: Scroller = new Scroller(); + dataCount: number = 100; + private itemWidthArray: number[] = []; + private itemHeightArray: number[] = []; + // [Start Section] + oneColumnSection: SectionOptions = { + itemsCount: 3, + crossCount: 1, + columnsGap: 5, + rowsGap: 10, + margin: { + top: 8, + left: 0, + bottom: 8, + right: 0 + }, + onGetItemMainSizeByIndex: (index: number) => { + if (index === 1) { + return 100; + } else { + return 200; + } + } + }; + twoColumnSection: SectionOptions = { + itemsCount: 2, + crossCount: 2, + onGetItemMainSizeByIndex: () => { + return 250; + } + }; + + // [End Section] + + getResourceStringArray(resource: Resource): Array { + let result: Array = new Array(); + try { + result = this.getUIContext().getHostContext()?.resourceManager.getStringArrayValueSync(resource.id) as string[]; + } catch (e) { + Logger.error(`[getResourceString]getStringSync failed, error:${JSON.stringify(e)}.`); + } + return result; + } + + getSize() { + let ret = Math.floor(Math.random() * this.maxSize); + return (ret > this.minSize ? ret : this.minSize); + } + + setItemSizeArray() { + for (let i = 0; i < 100; i++) { + this.itemWidthArray.push(this.getSize()); + this.itemHeightArray.push(this.getSize()); + } + } + + aboutToAppear() { + this.setItemSizeArray(); + this.initView(); + + let dataArray: number[] = []; + for (let i = 0; i < 100; i++) { + dataArray.push(i) + } + this.waterFlowView.setDataSource(dataArray) + this.waterFlowView.registerNodeItem('StickyBlogItemContainer', wrapBuilder(StickyBlogItemContainer)) + this.waterFlowView.preCreate('StickyBlogItemContainer', 30) + + if (canIUse("SystemCapability.Window.SessionManager")) { + display.on('foldStatusChange', (data: display.FoldStatus) => { + if (data === display.FoldStatus.FOLD_STATUS_FOLDED) { + this.isFoldStatus = true; + } else if (data === display.FoldStatus.FOLD_STATUS_EXPANDED) { + this.isFoldStatus = false; + } + }); + } + } + + // [Start Section_2] + initView() { + let sectionOptions: SectionOptions[] = []; + let count = 0; + let oneOrTwo = 0; + while (count < this.dataCount) { + if (oneOrTwo++ % 2 == 0) { + sectionOptions.push(this.oneColumnSection); + count += this.oneColumnSection.itemsCount; + } else { + sectionOptions.push(this.twoColumnSection); + count += this.twoColumnSection.itemsCount; + } + } + this.sections.splice(-1, 0, sectionOptions); + // [Start Section_3] + this.waterFlowView.setViewStyle({ scroller: this.scroller, sections: this.sections })// [StartExclude Section_3] + // [StartExclude Section_2] + .columnsTemplate('1fr 1fr') + .columnsGap(CommonConstants.COLUMNS_GAP) + .rowsGap(CommonConstants.ROWS_GAP) + .width(CommonConstants.FULL_WIDTH) + .height(CommonConstants.FULL_HEIGHT) + .layoutWeight(1) + .scrollBar(BarState.Off) + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])// [StartExclude Section_3] + .onScrollIndex((_first: number, last: number) => { + if (last + 20 >= this.waterFlowView.nodeAdapter.totalNodeCount) { + let dataArray: number[] = []; + for (let i = 0; i < 100; i++) { + dataArray.push(i) + } + // update data when the page is scrolling + this.waterFlowView.nodeAdapter.pushData(dataArray) + let newSection: SectionOptions = { + itemsCount: 100, + crossCount: 2, + onGetItemMainSizeByIndex: () => { + return 100; + } + } + // update section + this.sections.push(newSection); + let waterFlow = this.waterFlowView.__VIEW_CONTROLLER__.parentLayout as typeNode.WaterFlow; + waterFlow.initialize({ + scroller: this.scroller, // it's very important to do initialize that makes section update + sections: this.sections, + }) + } + })// [StartExclude Section_2] + .onWillScroll((offset: number) => { + this.scrollOffset = this.scroller.currentOffset().yOffset + offset; + }) // [EndExclude Section_2] + // [EndExclude Section_2] + // [End Section_3] + + // [StartExclude Section_2] + this.waterFlowView.setItemViewStyle((flowItem, _index, item: number) => { + flowItem().width(CommonConstants.FULL_WIDTH) + .height(this.itemHeightArray[item % 100]) + .backgroundColor(Color.White) + }) + // [EndExclude Section_2] + + } + + // [End Section_2] + + aboutToDisappear(): void { + if (canIUse("SystemCapability.Window.SessionManager")) { + display.off('foldStatusChange'); + } + } + + build() { + + Stack({ alignContent: Alignment.TopStart }) { + RecyclerView({ + viewManager: this.waterFlowView + }) + + Stack() { + Column() { + Scroll() { + Row({ space: 0 }) { + ForEach(this.getResourceStringArray($r('app.strarray.tab_titles')), (item: string) => { + Row() { + Text(item) + .constraintSize({ minWidth: $r('app.float.tab_height') }) + .textAlign(TextAlign.Center) + .fontSize($r('app.float.tab_font_size')) + .fontColor(Color.Black) + .padding({ + left: $r('app.float.tab_text_padding_left'), + right: $r('app.float.tab_text_padding_right') + }) + .height(CommonConstants.FULL_HEIGHT) + .backgroundColor($r('app.color.sections_tab_color_normal')) + .borderRadius($r('app.float.sections_tab_radius')) + } + .padding({ + top: 0, + bottom: 0, + left: 0, + right: $r('app.float.tab_padding_right') + }) + .justifyContent(FlexAlign.Center) + .height($r('app.float.sections_sticky_tab_height')) + .align(Alignment.Center) + }, (item: string) => item.toString()) + } + } + .width(CommonConstants.FULL_WIDTH) + .scrollBar(BarState.Off) + .scrollable(ScrollDirection.Horizontal) + .backgroundColor(Color.White) + .padding({ top: $r('app.float.sections_margin') }) + + Row() { + Select([{ value: $r('app.string.distance') }, + { value: $r('app.string.less_1km') }, + { value: $r('app.string.less_5km') }, + { value: $r('app.string.more_5km') }]) + .selected(1) + .value($r('app.string.distance')) + .font({ + size: $r('app.float.sections_sticky_select_text_size'), + family: 'serif', + style: FontStyle.Normal + }) + .fontColor(Color.Black) + .selectedOptionFont({ + size: $r('app.float.sections_sticky_select_text_size'), + family: 'serif', + style: FontStyle.Normal + }) + .optionFont({ + size: $r('app.float.sections_sticky_select_text_size'), + family: 'serif', + style: FontStyle.Normal + }) + .backgroundColor($r('app.color.sections_tab_color_normal')) + Select([{ value: $r('app.string.classify') }, + { value: $r('app.string.food') }, + { value: $r('app.string.leisure') }, + { value: $r('app.string.entertainment') }]) + .selected(1) + .value($r('app.string.classify')) + .font({ + size: $r('app.float.sections_sticky_select_text_size'), + family: 'serif', + style: FontStyle.Normal + }) + .fontColor(Color.Black) + .selectedOptionFont({ + size: $r('app.float.sections_sticky_select_text_size'), + family: 'serif', + style: FontStyle.Normal + }) + .optionFont({ + size: $r('app.float.sections_sticky_select_text_size'), + family: 'serif', + style: FontStyle.Normal + }) + .backgroundColor($r('app.color.sections_tab_color_normal')) + .margin({ left: $r('app.float.sections_margin') }) + Select([{ value: $r('app.string.sort') }, + { value: $r('app.string.one') }, + { value: $r('app.string.two') }, + { value: $r('app.string.three') }]) + .selected(1) + .value($r('app.string.sort')) + .font({ + size: $r('app.float.sections_sticky_select_text_size'), + family: 'serif', + style: FontStyle.Normal + }) + .fontColor(Color.Black) + .selectedOptionFont({ + size: $r('app.float.sections_sticky_select_text_size'), + family: 'serif', + style: FontStyle.Normal + }) + .optionFont({ + size: $r('app.float.sections_sticky_select_text_size'), + family: 'serif', + style: FontStyle.Normal + }) + .backgroundColor($r('app.color.sections_tab_color_normal')) + .margin({ left: $r('app.float.sections_margin') }) + + } + .justifyContent(this.isFoldStatus ? FlexAlign.Start : FlexAlign.Center) + .backgroundColor(Color.White) + .width(CommonConstants.FULL_WIDTH) + .padding({ top: $r('app.float.sections_margin'), bottom: $r('app.float.sections_margin') }) + }.alignItems(HorizontalAlign.Start) + } + .width(CommonConstants.FULL_WIDTH) + .height(100) + .padding({ left: CommonConstants.PADDING, right: CommonConstants.PADDING }) + .backgroundColor(Color.White) + .hitTestBehavior(HitTestMode.Transparent) + .position({ x: 0, y: this.scrollOffset >= 220 ? 0 : 220 - this.scrollOffset }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TabBarPage.ets b/entry/src/main/ets/pages/TabBarPage.ets new file mode 100644 index 0000000..f053e50 --- /dev/null +++ b/entry/src/main/ets/pages/TabBarPage.ets @@ -0,0 +1,69 @@ +/* + * 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 { CommonConstants } from '../common/constants/CommonConstants'; +import { SharedPoolPage } from './SharedPoolPage'; +import { SharedPoolSecondPage } from './SharedPoolSecondPage'; +import { SymbolGlyphModifier } from '@kit.ArkUI'; + +@Entry +@Component +struct TabBarPage { + controller: TabsController = new TabsController(); + @State selectedIndex: number = 0; + @State titles: string[] = ['页面1', '页面2']; + symbols: SymbolGlyphModifier[] = []; + + @Builder + tabBuilder(title: string, targetIndex: number) { + Column() { + Image($rawfile('person_crop_circle_fill_1.svg')) + .width(24) + .height(24) + .margin({ bottom: 4 }) + .fillColor(this.selectedIndex === targetIndex ? $r('app.color.tab_selected_color') : 'rgba(0, 0, 0, 0.6)') + .objectFit(ImageFit.Contain) + Text(title) + .fontSize(10) + .fontColor(this.selectedIndex === targetIndex ? $r('app.color.tab_selected_color') : 'rgba(0, 0, 0, 0.6)') + } + .width(CommonConstants.FULL_WIDTH) + .height(50) + .justifyContent(FlexAlign.Center) + } + + build() { + Column() { + Tabs({ barPosition: BarPosition.End, controller: this.controller }) { + ForEach(this.titles, (item: string, index: number) => { + TabContent() { + if (index === 0) { + SharedPoolPage() + } else { + SharedPoolSecondPage() + } + } + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + .tabBar(this.tabBuilder(this.titles[index], index)) + }, (item: string) => JSON.stringify(item)) + } + .onChange((index: number) => { + this.selectedIndex = index; + }) + } + .width(CommonConstants.FULL_WIDTH) + .height(CommonConstants.FULL_HEIGHT) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/workers/FetchAgent.ets b/entry/src/main/ets/workers/FetchAgent.ets new file mode 100644 index 0000000..34a668c --- /dev/null +++ b/entry/src/main/ets/workers/FetchAgent.ets @@ -0,0 +1,91 @@ +/* + * 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 { ErrorEvent, HashMap, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS'; +import { rcp } from '@kit.RemoteCommunicationKit'; +import fs from '@ohos.file.fs' +import { BusinessError } from '@kit.BasicServicesKit'; + + +const workerPort: ThreadWorkerGlobalScope = worker.workerPort; + +let fetchToRequest: HashMap = new HashMap(); +let session: rcp.Session = rcp.createSession({ + requestConfiguration: { + security: { + remoteValidation: 'system', + tlsOptions: { tlsVersion: 'TlsV1.3', cipherSuite: ['TLS_AES_128_GCM_SHA256'] } + } + } +}); + +async function caches(cachePath: string, fetchId: number, data: ArrayBuffer): Promise { + const path = `file://${cachePath}/${fetchId}.jpg`; + const file = await fs.open(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE) + await fs.write(file.fd, data); + await fs.close(file); + return path; +} + +/** + * Defines the event handler to be called when the worker thread receives a message sent by the host thread. + * The event handler is executed in the worker thread. + * + * @param event message data + */ +workerPort.onmessage = (e: MessageEvents) => { + switch (e.data.type) { + case 'fetch': + const request = new rcp.Request(e.data.url, 'GET') + session.fetch(request).then(async (response: rcp.Response) => { + if (response.statusCode == 200) { + const imgData: ArrayBuffer = response.body as ArrayBuffer; + let fetchUrl = await caches(e.data.cachePath, e.data.fetchId, imgData) + fetchToRequest.remove(e.data.fetchId) + workerPort.postMessageWithSharedSendable({ type: 'SUCCESS', fetchId: e.data.fetchId, fetchUrl: fetchUrl }) + } else { + workerPort.postMessageWithSharedSendable({ type: 'FAIL', fetchId: e.data.fetchId }) + } + }).catch((err: BusinessError) => { + workerPort.postMessageWithSharedSendable({ type: 'FAIL', fetchId: e.data.fetchId }) + }) + break; + case 'cancel': + if (fetchToRequest.hasKey(e.data.fetchId)) { + session.cancel(fetchToRequest.get(e.data.fetchId as number)) + workerPort.postMessageWithSharedSendable({ type: 'FAIL', fetchId: e.data.fetchId }) + } + break; + + } +}; + +/** + * Defines the event handler to be called when the worker receives a message that cannot be deserialized. + * The event handler is executed in the worker thread. + * + * @param event message data + */ +workerPort.onmessageerror = (event: MessageEvents) => { +}; + +/** + * Defines the event handler to be called when an exception occurs during worker execution. + * The event handler is executed in the worker thread. + * + * @param event error message + */ +workerPort.onerror = (event: ErrorEvent) => { +}; \ No newline at end of file diff --git a/entry/src/main/ets/workers/GetNetworkData.ets b/entry/src/main/ets/workers/GetNetworkData.ets new file mode 100644 index 0000000..3bfed89 --- /dev/null +++ b/entry/src/main/ets/workers/GetNetworkData.ets @@ -0,0 +1,114 @@ +/* + * 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 { ArkTSUtils, collections, ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { http } from '@kit.NetworkKit'; + +const workerPort: ThreadWorkerGlobalScope = worker.workerPort; + +let pageNum = 1; +const pageSize = 100; +// TODO +const url = + `https://devecostudio-drcn.op.hicloud.com/solution/v1/getSceneMockData?scene=smoothSlide&fileName=waterFlow.json&pageNum=${pageNum}&pageSize=${pageSize}` +/** + * Defines the event handler to be called when the worker thread receives a message sent by the host thread. + * The event handler is executed in the worker thread. + * + * @param event message data + */ +workerPort.onmessage = (event: MessageEvents) => { + let data = event.data as collections.Array + FillData().then((value: collections.Array) => { + data.push(...value) + pageNum++ + if (pageNum > 5) { + pageNum = 1 + } + workerPort.postMessage({ type: 'HttpDown' }) + }, () => { + workerPort.postMessage({ type: 'HttpError', 'err': 'get null data' }) + }) +}; + +/** + * Defines the event handler to be called when the worker receives a message that cannot be deserialized. + * The event handler is executed in the worker thread. + * + * @param event message data + */ +workerPort.onmessageerror = (event: MessageEvents) => { +}; + +/** + * Defines the event handler to be called when an exception occurs during worker execution. + * The event handler is executed in the worker thread. + * + * @param event error message + */ +workerPort.onerror = (event: ErrorEvent) => { +}; + +@Sendable +export class sceneMockData { + thumbnails: string = '' + source: string = '' + width: number = 0 + height: number = 0 + type: string = '' + title: string = '' + title_en: string = '' + user_image: string = '' + nick_name: string = '' + collections_count: number = 0 + url: string = '' + index: number = 0 +} + +function FillData(): Promise> { + return new Promise((resolve, reject) => { + HttpGetUrl(url, (err: BusinessError, response?: string) => { + if (err || response === undefined) { + reject() + return + } + + let data: ESObject = ArkTSUtils.ASON.parse(response) + let array: sceneMockData[] = data['data'] + let outPutData = new collections.Array() + outPutData.push(...array) + + resolve(outPutData) + return + }) + }) +} + +export function HttpGetUrl(requestUrl: string, callBack: (err: BusinessError, responseDat?: string) => void) { + let httpRequest = http.createHttp(); + httpRequest.request(requestUrl, { + maxLimit: 10 * 1024 * 1024, + priority: 10000, + }, (error: BusinessError, data: http.HttpResponse) => { + if (error) { + callBack(error) + httpRequest.destroy(); + } else { + callBack(error, data.result as string); + httpRequest.destroy(); + } + }) +} \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 55c3f00..1b83137 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -1,5 +1,9 @@ { "src": [ - "pages/Index" + "pages/Index", + "pages/CombineWaterFlowPage", + "pages/StickyWaterFlowPage", + "pages/StandardWaterFlowPage", + "pages/TabBarPage" ] } \ No newline at end of file -- Gitee