1/** 2 * Copyright (c) 2021-2022 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 */ 15import image from '@ohos.multimedia.image'; 16import { 17 EventConstants, 18 localEventManager, 19 Log, 20 Trace, 21 CheckEmptyUtils, 22 windowManager, 23 SnapShotInfo, 24 CommonConstants 25} from '@ohos/common'; 26import RecentMissionAppIcon from './RecentMissionAppIcon'; 27import RecentMissionAppName from './RecentMissionAppName'; 28import { RecentMissionsViewModel } from '../../viewmodel/RecentMissionsViewModel'; 29import { RecentsStyleConstants } from '../../common/constants/RecentsStyleConstants'; 30import { RecentMissionStartAppHandler } from './../RecentMissionStartAppHandler'; 31import { BusinessError } from '@ohos.base'; 32 33const TAG = 'Recent-RecentMissionCard'; 34 35interface AppParams { 36 bundleName: string; 37 moduleName: string; 38 appIconId: number; 39 borderRadius?: number; 40 isSingleLayout?: boolean; 41 appIconSize?: number; 42 appIconHeight?: number; 43 position?: Position 44} 45 46/** 47 * Common card component for recent missions. 48 */ 49@Component 50export default struct RecentMissionCard { 51 @Link isClickSubComponent: boolean; 52 @State missionId: number = 0; 53 @State appIconId: number = 0; 54 @State appLabelId: number = 0; 55 @State appName: string = ''; 56 @State bundleName: string = ''; 57 @State moduleName: string = ''; 58 @State abilityName: string = ''; 59 @State lockedState: boolean = false; 60 @State cardMargin: number = RecentsStyleConstants.SINGLE_LIST_LAYOUT_MARGIN; 61 @State cardLayoutWeight: number = RecentsStyleConstants.SINGLE_LIST_APP_INFO_LAYOUT_WEIGHT; 62 // recentImage default is Resource type, to avoid toolchain typeCheck 63 @State recentImage?: image.PixelMap = undefined; 64 private mIsSingleLayout: boolean = true; 65 private snapShotTime: string = ''; 66 private mRecentMissionsViewModel?: RecentMissionsViewModel; 67 private mRecentMissionStartAppHandler?: RecentMissionStartAppHandler; 68 private mReleaseFlag = true; 69 70 aboutToAppear(): void { 71 Log.showInfo(TAG, 'aboutToAppear start'); 72 // remove this if toolchain fix requireNApi bug 73 this.mReleaseFlag = false; 74 this.mRecentMissionsViewModel = RecentMissionsViewModel.getInstance(); 75 this.mIsSingleLayout = this.mRecentMissionsViewModel.getRecentMissionsRowType() === 'single' ? true : false; 76 this.cardMargin = this.mRecentMissionsViewModel.getRecentMissionsRowType() === 'single' ? 77 RecentsStyleConstants.SINGLE_LIST_LAYOUT_MARGIN * 78 RecentsStyleConstants.DPI_RATIO : RecentsStyleConstants.DOUBLE_LIST_LAYOUT_MARGIN; 79 this.cardLayoutWeight = this.mRecentMissionsViewModel.getRecentMissionsRowType() === 'single' ? 80 RecentsStyleConstants.SINGLE_LIST_APP_INFO_LAYOUT_WEIGHT : 81 RecentsStyleConstants.DOUBLE_LIST_APP_INFO_LAYOUT_WEIGHT; 82 this.mRecentMissionsViewModel.getMissionSnapShot(this.missionId, this.recentMissionsSnapshotCallback); 83 this.mRecentMissionStartAppHandler = RecentMissionStartAppHandler.getInstance(); 84 } 85 86 aboutToDisappear(): void { 87 Log.showInfo(TAG, `aboutToDisappear start ${this.missionId}`); 88 Log.showDebug(TAG, `typeof this.recentImage ${typeof this.recentImage}`); 89 this.mReleaseFlag = true; 90 // typeof Resource or pixelMap is Object 91 if (!CheckEmptyUtils.isEmpty(this.recentImage)) { 92 this.recentImage?.release().catch((err: BusinessError) => { 93 Log.showError(TAG, `image.PixelMap release err: ${JSON.stringify(err)}`); 94 }) 95 Log.showInfo(TAG, `aboutToDisappear end ${this.missionId}`); 96 } 97 } 98 99 /** 100 * The callback of Recent missions snapshot. 101 * 102 * @param {number} missionId 103 * @param {any} snapShotInfo 104 */ 105 recentMissionsSnapshotCallback = (missionId: number, snapShotInfo: SnapShotInfo): void => { 106 Log.showDebug(TAG, `recentMissionsSnapshotCallback missionId: ${this.missionId}`); 107 if (!this.mReleaseFlag && missionId === this.missionId) { 108 this.recentImage = snapShotInfo.snapShotImage; 109 let width = snapShotInfo.snapShotImageWidth; 110 let height = snapShotInfo.snapShotImageHeight; 111 Log.showDebug(TAG, `recentMissionsSnapshotCallback recentImage: ${JSON.stringify(this.recentImage)}, 112 width: ${JSON.stringify(width)}, height: ${JSON.stringify(height)}`); 113 } else { 114 snapShotInfo.snapShotImage.release().catch((err: BusinessError) => { 115 Log.showError(TAG, `image.PixelMap release err :${JSON.stringify(err)}`) 116 }) 117 } 118 } 119 120 build() { 121 Column() { 122 Row() { 123 Row() { 124 RecentMissionAppIcon({ 125 iconSize: this.mIsSingleLayout ? 126 RecentsStyleConstants.SINGLE_LIST_DEFAULT_APP_ICON_SIZE * RecentsStyleConstants.DPI_RATIO : 127 RecentsStyleConstants.DOUBLE_LIST_DEFAULT_APP_ICON_SIZE, 128 appIcon: this.appIconId, 129 bundleName: this.bundleName, 130 moduleName: this.moduleName, 131 labelId: this.appLabelId, 132 useCache: false 133 }) 134 RecentMissionAppName({ 135 appName: this.appName, 136 nameSize: this.mIsSingleLayout ? 137 RecentsStyleConstants.DEFAULT_APP_NAME_SIZE * RecentsStyleConstants.DPI_RATIO : 138 RecentsStyleConstants.DEFAULT_APP_NAME_SIZE, 139 bundleName: this.bundleName, 140 moduleName: this.moduleName, 141 labelId: this.appLabelId, 142 nameMargin: this.cardMargin / 2 143 }) 144 } 145 .layoutWeight(this.cardLayoutWeight) 146 .margin({ left: RecentsStyleConstants.SINGLE_LIST_APP_INFO_LEFT_MARGIN }) 147 148 .onClick((event) => { 149 this.isClickSubComponent = true; 150 Log.showDebug(TAG, 'onClick start launcher ability'); 151 localEventManager.sendLocalEventSticky(EventConstants.EVENT_OPEN_FOLDER_TO_CLOSE, null); 152 windowManager.destroyWindow(windowManager.FORM_MANAGER_WINDOW_NAME); 153 windowManager.destroyWindow(windowManager.FORM_SERVICE_WINDOW_NAME); 154 if (!windowManager.recentMode || !windowManager.isSplitWindowMode(windowManager.recentMode)) { 155 windowManager.hideWindow(windowManager.RECENT_WINDOW_NAME); 156 } 157 Trace.start(Trace.CORE_METHOD_START_APP_ANIMATION); 158 this.setStartAppInfo(event?.target?.area?.globalPosition); 159 RecentMissionsViewModel.getInstance().moveMissionToFront(this.missionId); 160 }) 161 if (this.lockedState) { 162 Column() { 163 Image(RecentsStyleConstants.DEFAULT_LOCKED_IMAGE) 164 .width(this.mIsSingleLayout ? 165 RecentsStyleConstants.SINGLE_LIST_DEFAULT_APP_ICON_SIZE_NEW : 166 RecentsStyleConstants.DOUBLE_LIST_DEFAULT_APP_ICON_SIZE - 10) 167 .height(this.mIsSingleLayout ? 168 RecentsStyleConstants.SINGLE_LIST_DEFAULT_APP_ICON_SIZE_NEW : 169 RecentsStyleConstants.DOUBLE_LIST_DEFAULT_APP_ICON_SIZE - 10) 170 .margin({ 171 right: this.mIsSingleLayout ? (RecentsStyleConstants.SINGLE_LIST_LOCKED_IMAGE_RIGHT_MARGIN) : 172 (RecentsStyleConstants.DOUBLE_LIST_LOCKED_IMAGE_RIGHT_MARGIN) 173 }) 174 .onClick((event) => { 175 this.isClickSubComponent = true; 176 Log.showDebug(TAG, `click set recent mission: ${this.missionId} Locked status: ${!this.lockedState}`); 177 RecentMissionsViewModel.getInstance().setRecentMissionLock(this.missionId,!this.lockedState); 178 this.setStartAppInfo(event?.target?.area?.globalPosition); 179 this.isClickSubComponent = false; 180 }) 181 } 182 } 183 } 184 .margin({ 185 top: this.mIsSingleLayout ? RecentsStyleConstants.SINGLE_LIST_APP_INFO_TOP_MARGIN : 186 RecentsStyleConstants.DOUBLE_LIST_APP_INFO_BOTTOM_MARGIN, 187 bottom: this.mIsSingleLayout ? RecentsStyleConstants.SINGLE_LIST_APP_INFO_BOTTOM_MARGIN : 188 RecentsStyleConstants.DOUBLE_LIST_APP_INFO_BOTTOM_MARGIN 189 }) 190 191 Image(this.recentImage) 192 .objectFit(ImageFit.Fill) 193 .borderRadius(RecentsStyleConstants.RECENT_IMAGE_RADIUS) 194 .width(this.mIsSingleLayout ? 195 RecentsStyleConstants.SINGLE_LIST_APP_IMAGE_WIDTH : 196 RecentsStyleConstants.DOUBLE_LIST_APP_IMAGE_WIDTH) 197 .height(this.mIsSingleLayout ? 198 RecentsStyleConstants.SINGLE_LIST_APP_IMAGE_HEIGHT : 199 RecentsStyleConstants.DOUBLE_LIST_APP_IMAGE_HEIGHT) 200 .onClick((event) => { 201 this.isClickSubComponent = true; 202 Log.showDebug(TAG, 'onClick start launcher ability'); 203 localEventManager.sendLocalEventSticky(EventConstants.EVENT_OPEN_FOLDER_TO_CLOSE, null); 204 windowManager.destroyWindow(windowManager.FORM_MANAGER_WINDOW_NAME); 205 windowManager.destroyWindow(windowManager.FORM_SERVICE_WINDOW_NAME); 206 if (!windowManager.recentMode || !windowManager.isSplitWindowMode(windowManager.recentMode)) { 207 windowManager.hideWindow(windowManager.RECENT_WINDOW_NAME); 208 } 209 Trace.start(Trace.CORE_METHOD_START_APP_ANIMATION); 210 this.setStartAppInfo(event?.target?.area?.globalPosition); 211 RecentMissionsViewModel.getInstance().moveMissionToFront(this.missionId); 212 }) 213 } 214 .width(this.mIsSingleLayout ? 215 RecentsStyleConstants.SINGLE_LIST_APP_IMAGE_WIDTH : 216 RecentsStyleConstants.DOUBLE_LIST_APP_IMAGE_WIDTH) 217 .height(this.mIsSingleLayout ? 218 RecentsStyleConstants.SINGLE_LIST_MISSION_HEIGHT : 219 RecentsStyleConstants.DOUBLE_LIST_MISSION_HEIGHT) 220 .backgroundColor(RecentsStyleConstants.DEFAULT_BG_COLOR) 221 .gesture( 222 PanGesture({ fingers: 1, direction: PanDirection.Vertical, distance: 5 }) 223 .onActionEnd((e) => { 224 let offsetWidth = (this.mIsSingleLayout ? 225 RecentsStyleConstants.SINGLE_LIST_APP_IMAGE_WIDTH * RecentsStyleConstants.DPI_RATIO : 226 RecentsStyleConstants.DOUBLE_LIST_APP_IMAGE_WIDTH) / 2; 227 if (e.offsetY < -50 && e.offsetX <= offsetWidth && -offsetWidth <= e.offsetX) { 228 RecentMissionsViewModel.getInstance().deleteRecentMission(false, this.missionId); 229 } else if (e.offsetY > 50 && e.offsetX <= offsetWidth && -offsetWidth <= e.offsetX) { 230 Log.showDebug(TAG, `gesture set recent mission: ${this.missionId} Locked status: ${!this.lockedState}`); 231 RecentMissionsViewModel.getInstance().setRecentMissionLock(this.missionId,!this.lockedState); 232 } 233 })) 234 } 235 236 /** 237 * set start app info 238 */ 239 private setStartAppInfo(position?: Position) { 240 AppStorage.setOrCreate('startAppTypeFromPageDesktop', CommonConstants.OVERLAY_TYPE_APP_ICON); 241 let appParams: AppParams = { 242 bundleName: this.bundleName, 243 moduleName: this.moduleName, 244 appIconId: this.appIconId, 245 borderRadius: RecentsStyleConstants.RECENT_IMAGE_RADIUS, 246 isSingleLayout: this.mIsSingleLayout, 247 appIconSize:this.mIsSingleLayout ? RecentsStyleConstants.SINGLE_LIST_APP_IMAGE_WIDTH : 248 RecentsStyleConstants.DOUBLE_LIST_APP_IMAGE_WIDTH, 249 appIconHeight: this.mIsSingleLayout ? RecentsStyleConstants.SINGLE_LIST_MISSION_HEIGHT : 250 RecentsStyleConstants.DOUBLE_LIST_MISSION_HEIGHT, 251 position: position 252 } 253 AppStorage.setOrCreate('startAppItemInfo', appParams); 254 this.mRecentMissionStartAppHandler?.setAppIconSize(appParams.appIconSize, appParams.appIconHeight); 255 this.mRecentMissionStartAppHandler?.setAppIconInfo(); 256 } 257}