diff --git a/commons/base/Index.ets b/commons/base/Index.ets index 07abac6905bccb771f74e699693e36c9969f09d7..82d93528ffc980315b45628ff04beccdd057de89 100644 --- a/commons/base/Index.ets +++ b/commons/base/Index.ets @@ -1,7 +1,7 @@ export { BreakpointConstants } from './src/main/ets/constants/BreakpointConstants'; export { CommonConstants } from './src/main/ets/constants/CommonConstants'; export { BreakpointType } from './src/main/ets/utils/BreakpointType'; -export { WindowUtil } from './src/main/ets/utils/WindowUtil'; +export { WindowUtil, WindowInfo, ImmersiveType } from './src/main/ets/utils/WindowUtil'; export { AvPlayerUtil } from './src/main/ets/utils/AvPlayerUtil'; export { DisplayUtil } from './src/main/ets/utils/DisplayUtil'; export { DeviceScreen } from './src/main/ets/utils/DeviceScreen'; diff --git a/commons/base/src/main/ets/constants/BreakpointConstants.ets b/commons/base/src/main/ets/constants/BreakpointConstants.ets index 8727d8f517dd24955fc96825c36d9f72bb3b8a4d..9176234446c2d8c48f307d232db26162eb1370c2 100644 --- a/commons/base/src/main/ets/constants/BreakpointConstants.ets +++ b/commons/base/src/main/ets/constants/BreakpointConstants.ets @@ -17,6 +17,11 @@ * Constants for breakpoint. */ export class BreakpointConstants { + /** + * Breakpoints that represent xsmall device types. + */ + static readonly BREAKPOINT_XS: string = 'xs'; + /** * Breakpoints that represent small device types. */ @@ -32,6 +37,11 @@ export class BreakpointConstants { */ static readonly BREAKPOINT_LG: string = 'lg'; + /** + * Breakpoints that represent xlarge device types. + */ + static readonly BREAKPOINT_XL: string = 'xl'; + /** * Grid row column list. */ diff --git a/commons/base/src/main/ets/constants/CommonConstants.ets b/commons/base/src/main/ets/constants/CommonConstants.ets index 8e29116ccee0f65c4eb8688da37fb0c3b72b1dab..c2ac99d17199c7eb930ddf47e706ea710e429677 100644 --- a/commons/base/src/main/ets/constants/CommonConstants.ets +++ b/commons/base/src/main/ets/constants/CommonConstants.ets @@ -97,6 +97,11 @@ export class CommonConstants { */ static readonly LIST_SPACE: string = '12vp'; + /** + * List space for TV. + */ + static readonly LIST_SPACE_FOR_TV: string = '24vp'; + /** * Video grid column list. */ @@ -128,6 +133,11 @@ export class CommonConstants { */ static readonly FULL_PERCENT: string = '100%'; + /** + * Ninety percent. + */ + static readonly NINETY_PERCENT: string = '90%'; + /** * One hundred for progress. */ @@ -178,6 +188,11 @@ export class CommonConstants { */ static readonly SCREEN_DPI_CONSTANT: number = 160; + /** + * Font weight 400. + */ + static readonly FONT_WEIGHT_400: number = 400; + /** * Font weight 500. */ @@ -188,10 +203,15 @@ export class CommonConstants { */ static readonly FONT_WEIGHT_700: number = 700; + /** + * Font family HarmonyHeiTi. + */ + static readonly FONT_HARMONY_HEITI: string = 'HarmonyHeiTi'; + /** * Text opacity. */ - static readonly TEXT_OPACITY: number[] = [0.4, 0.5, 0.6, 1, 0.8]; + static readonly TEXT_OPACITY: number[] = [0.4, 0.5, 0.6, 1, 0.8, 0.9]; /** * Divider opacity. @@ -218,6 +238,11 @@ export class CommonConstants { */ static readonly PREVIOUS_ONE_ROW_RATIO: number = 1.56; + /** + * Comment image aspect ratio. + */ + static readonly COMMENT_IMAGE_RATIO: number = 1.56; + /** * Default window width. */ @@ -236,5 +261,5 @@ export class CommonConstants { /** * Page names. */ - static readonly PAGE_NAMES: string[] = ['home', 'videoDetail']; + static readonly PAGE_NAMES: string[] = ['home', 'videoDetail', 'videoPlayForTV']; } \ No newline at end of file diff --git a/commons/base/src/main/ets/utils/BreakpointType.ets b/commons/base/src/main/ets/utils/BreakpointType.ets index 09a22310a3e0f94d435caa66a3847fb916130d06..6f56b216d6e7cebe58e936c985debbbf64c833e3 100644 --- a/commons/base/src/main/ets/utils/BreakpointType.ets +++ b/commons/base/src/main/ets/utils/BreakpointType.ets @@ -22,25 +22,34 @@ import { BreakpointConstants } from '../constants/BreakpointConstants'; // [Start break_point_type] // [Start dd_break_point_type] export class BreakpointType { + xs: T; sm: T; md: T; lg: T; + xl: T; - constructor(sm: T, md: T, lg: T) { + constructor(xs: T, sm: T, md: T, lg: T, xl: T) { + this.xs = xs; this.sm = sm; this.md = md; this.lg = lg; + this.xl = xl; } - getValue(currentWidthBreakpoint: string): T { - if (currentWidthBreakpoint === 'md') { + getValue(currentWidthBreakpoint: WidthBreakpoint): T { + if (currentWidthBreakpoint === WidthBreakpoint.WIDTH_XS) { + return this.xs; + } + if (currentWidthBreakpoint === WidthBreakpoint.WIDTH_MD) { return this.md; } - if (currentWidthBreakpoint === 'lg') { - return this.lg; - } else { + if (currentWidthBreakpoint === WidthBreakpoint.WIDTH_SM) { return this.sm; } + if (currentWidthBreakpoint === WidthBreakpoint.WIDTH_LG) { + return this.lg; + } + return this.xl; } } // [End dd_break_point_type] diff --git a/commons/base/src/main/ets/utils/WindowUtil.ets b/commons/base/src/main/ets/utils/WindowUtil.ets index 13de82f7284a0fbd911b4bf3067bf9f4d0a382b8..16ef24e0d4d034e5e51f2c16f6d1e81790e4868c 100644 --- a/commons/base/src/main/ets/utils/WindowUtil.ets +++ b/commons/base/src/main/ets/utils/WindowUtil.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * 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 @@ -13,206 +13,365 @@ * limitations under the License. */ -/* -* 最佳实践: 一多断点开发实践 -*/ - -import { BusinessError, deviceInfo } from '@kit.BasicServicesKit'; -import { display, window } from '@kit.ArkUI'; +// [Start WindowUtil] +import { display, UIContext, window } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; -import { CommonConstants } from '../constants/CommonConstants'; +import { StartOptions, AbilityConstant, Want, common } from '@kit.AbilityKit'; +import { resourceManager } from '@kit.LocalizationKit'; +import { sensor } from '@kit.SensorServiceKit'; + +export enum ImmersiveType { + NORMAL, + IMMERSIVE, + FULLSCREEN_IMMERSIVE +} -@Observed export class WindowUtil { - private windowStage?: window.WindowStage; - private mainWindowClass?: window.Window; - - static getInstance(): WindowUtil | undefined { - if (!AppStorage.get(CommonConstants.WINDOW_UTIL)) { - AppStorage.setOrCreate(CommonConstants.WINDOW_UTIL, new WindowUtil()); - } else { - hilog.info(0x0000, 'WindowUtil', '%{public}s', `AppStorage does not have windowUtil.`); - } - return AppStorage.get(CommonConstants.WINDOW_UTIL); + public uiContext?: UIContext; + public mainWindow: window.Window; + public mainWindowInfo: WindowInfo = new WindowInfo(); + // [Start windowStatusChange] + public onStatusTypeChange: (statusType: window.WindowStatusType) => void = (statusType: window.WindowStatusType) => { + this.mainWindowInfo.windowStatusType = statusType; } + // [StartExclude windowStatusChange] + // [Start WindowSizeChange] + public onWindowSizeChange: (windowSize: window.Size) => void = (windowSize: window.Size) => { + this.mainWindowInfo.windowSize = windowSize; + this.mainWindowInfo.widthBp = this.uiContext!.getWindowWidthBreakpoint(); + this.mainWindowInfo.heightBp = this.uiContext!.getWindowHeightBreakpoint(); + }; + // [StartExclude WindowSizeChange] + // [Start onAvoidAreaChange] + public onAvoidAreaChange: (avoidOptions: window.AvoidAreaOptions) => void = + (avoidOptions: window.AvoidAreaOptions) => { + if (avoidOptions.type === window.AvoidAreaType.TYPE_SYSTEM) { + this.mainWindowInfo.AvoidSystem = avoidOptions.area; + } else if (avoidOptions.type === window.AvoidAreaType.TYPE_CUTOUT) { + this.mainWindowInfo.AvoidCutout = avoidOptions.area; + } else if (avoidOptions.type === window.AvoidAreaType.TYPE_SYSTEM_GESTURE) { + this.mainWindowInfo.AvoidSystemGesture = avoidOptions.area; + } else if (avoidOptions.type === window.AvoidAreaType.TYPE_KEYBOARD) { + this.mainWindowInfo.AvoidKeyboard = avoidOptions.area; + } else if (avoidOptions.type === window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) { + this.mainWindowInfo.AvoidNavigationIndicator = avoidOptions.area; + } + }; + // [StartExclude onAvoidAreaChange] - setWindowStage(windowStage: window.WindowStage): void { - this.windowStage = windowStage; - this.windowStage.getMainWindow((err, windowClass: window.Window) => { - this.mainWindowClass = windowClass; - if (err.code) { - hilog.error(0x0000, 'WindowUtil', `Failed to obtain the main window. Code:${err.code}, message:${err.message}`, - JSON.stringify(err) ?? ''); - return; + constructor(mainWindow: window.Window) { + this.mainWindow = mainWindow; + } + + setImmersiveType(type: ImmersiveType) { + try { + if (type === ImmersiveType.NORMAL) { + this.mainWindow.setWindowLayoutFullScreen(false) + .then(() => { + hilog.info(0x0000, 'testLog', `Succeeded in setting immersive mode.`); + }) + .catch((err: BusinessError) => { + hilog.error(0x0000, 'testLog', `Failed to set immersive mode. Code: ${err.code}, message: ${err.message}`); + }); + this.setSystemBarEnabled(true); + // Only used after the function loadContent() or setUIContent(). + this.mainWindow.setWindowDecorVisible(true); + this.recover(); + } else if (type === ImmersiveType.IMMERSIVE) { + this.mainWindow.setWindowLayoutFullScreen(true) + .then(() => { + hilog.info(0x0000, 'testLog', `Succeeded in setting immersive mode.`); + }) + .catch((err: BusinessError) => { + hilog.error(0x0000, 'testLog', `Failed to set immersive mode. Code: ${err.code}, message: ${err.message}`); + }); + this.setSystemBarEnabled(true); + this.mainWindow.setWindowDecorVisible(false); + this.recover(); + } else if (type === ImmersiveType.FULLSCREEN_IMMERSIVE) { + this.mainWindow.setWindowLayoutFullScreen(true) + .then(() => { + hilog.info(0x0000, 'testLog', `Succeeded in setting immersive mode.`); + }) + .catch((err: BusinessError) => { + hilog.error(0x0000, 'testLog', `Failed to set immersive mode. Code: ${err.code}, message: ${err.message}`); + }); + if (this.mainWindow.getWindowStatus() === window.WindowStatusType.MAXIMIZE || + (this.mainWindow.getWindowStatus() === window.WindowStatusType.FLOATING && + this.mainWindow.getWindowDecorHeight() !== 0)) { + this.mainWindow.maximize() + .then(() => { + hilog.info(0x0000, 'testLog', `Succeeded in maximizing the window.`); + }) + .catch((err: BusinessError) => { + hilog.error(0x0000, 'testLog', `Failed to maximize the window. Code: ${err.code}, message: ${err.message}`); + }); + } + this.setSystemBarEnabled(false); + // [Start setWindowDecorVisible] + this.mainWindow.setWindowDecorVisible(false); + // [End setWindowDecorVisible] } - }); + this.mainWindowInfo.isImmersive = type; + } catch (error) { + let err = error as BusinessError; + hilog.error(0x0000, 'TestLog', `Failed to set immersive type. Code: ${err.code}, message: ${err.message}`); + } } - setMainWindowOrientation(orientation: window.Orientation): void { - // Setting orientation. - this.mainWindowClass!.setPreferredOrientation(orientation) - .then(() => { - hilog.info(0x0000, 'WindowUtil', '%{public}s', `Succeed in setting the orientation.`); - }) - .catch((err: BusinessError) => { - hilog.error(0x0000, 'WindowUtil', `Failed to set the orientation. Code: ${err.code}, message: ${err.message}`, - JSON.stringify(err) ?? ''); - }); + setUIContext() { + try { + this.uiContext = this.mainWindow.getUIContext(); + } catch (error) { + let err = error as BusinessError; + hilog.error(0x0000, 'TestLog', `Failed to set UI context. Code: ${err.code}, message: ${err.message}`); + } } - disableWindowSystemBar(): void { - // Set the status bar and navigation bar to be invisible in full-screen mode. - this.mainWindowClass!.setWindowSystemBarEnable([]) + setSystemBarEnabled(isVisible: boolean): void { + this.mainWindow.setSpecificSystemBarEnabled('status', isVisible) .then(() => { - hilog.info(0x0000, 'WindowUtil', '%{public}s', `Succeed in setting the window system bar disable.`); + hilog.info(0x0000, 'testLog', `Succeeded in setting status bar to be invisible.`); }) .catch((err: BusinessError) => { - hilog.error(0x0000, 'WindowUtil', - `Failed to set the window system bar disable. Code: ${err.code}, message: ${err.message}`, - JSON.stringify(err) ?? ''); + hilog.error(0x0000, 'testLog', + `Failed to set status bar to be invisible. Code: ${err.code}, message: ${err.message}`); }); - } - - enableWindowSystemBar(): void { - this.mainWindowClass!.setWindowSystemBarEnable(['status', 'navigation']) + this.mainWindow.setSpecificSystemBarEnabled('navigationIndicator', isVisible) .then(() => { - hilog.info(0x0000, 'WindowUtil', '%{public}s', `Succeed in setting the window system bar enable.`); + hilog.info(0x0000, 'testLog', `Succeeded in setting navigation indicator to be invisible.`); }) .catch((err: BusinessError) => { - hilog.error(0x0000, 'WindowUtil', `Failed to set the orientation. Code: ${err.code}, message: ${err.message}`, - JSON.stringify(err) ?? ''); + hilog.error(0x0000, 'testLog', + `Failed to set navigation indicator to be invisible. Code: ${err.code}, message: ${err.message}`); }); } - setFullScreen(): void { - // Set full-screen display. - this.mainWindowClass!.setWindowLayoutFullScreen(true) + recover(): void { + try { + if (this.mainWindow.getWindowStatus() === window.WindowStatusType.FULL_SCREEN) { + this.mainWindow.recover() + .then(() => { + hilog.info(0x0000, 'testLog', `Succeeded in revocering the window.`); + }) + .catch((err: BusinessError) => { + hilog.error(0x0000, 'testLog', `Failed to revocer the window. Code: ${err.code}, message: ${err.message}`); + }); + } + } catch (error) { + let err = error as BusinessError; + hilog.error(0x0000, 'TestLog', `Failed to recover. Code: ${err.code}, message: ${err.message}`); + } + } + + // [Start setPreferredOrientation] + setWindowOrientation(orientation: window.Orientation): void { + this.mainWindow.setPreferredOrientation(orientation) .then(() => { - hilog.info(0x0000, 'WindowUtil', '%{public}s', `Succeed in setting the window layout full screen.`); + hilog.info(0x0000, 'testLog', `Succeeded in setting window orientation.`); + // Update window orientation. + this.mainWindowInfo.orientation = orientation; }) .catch((err: BusinessError) => { - hilog.error(0x0000, 'WindowUtil', - `Failed to set the window layout full screen. Code: ${err.code}, message: ${err.message}`, - JSON.stringify(err) ?? ''); + hilog.error(0x0000, 'testLog', `Failed to set window orientation. Code: ${err.code}, message: ${err.message}`); }); } + // [End setPreferredOrientation] - maximize(): void { - if (canIUse('SystemCapability.Window.SessionManager')) { - try { - if (this.mainWindowClass!.getWindowStatus() === window.WindowStatusType.FLOATING) { - this.mainWindowClass!.maximize() - .then(() => { - hilog.info(0x0000, 'WindowUtil', '%{public}s', `Succeed in maximizing the window.`); - }) - .catch((err: BusinessError) => { - hilog.error(0x0000, 'WindowUtil', - `Failed to maximize the window. Code: ${err.code}, message: ${err.message}`, - JSON.stringify(err) ?? ''); - }); - } - } catch (error) { - let err = error as BusinessError; - hilog.error(0x00, 'WindowUtil', `getWindowStatus failed, code = ${err.code}, message = ${err.message}`); + // [Start getWindowAvoidArea] + // [Start updateBreakpoint] + // [EndExclude windowStatusChange] + // [EndExclude WindowSizeChange] + // [EndExclude onAvoidAreaChange] + updateWindowInfo(): void { + try { + // [StartExclude getWindowAvoidArea] + // [StartExclude onAvoidAreaChange] + // [StartExclude WindowSizeChange] + // [StartExclude updateBreakpoint] + // First time get window status. + this.mainWindowInfo.windowStatusType = this.mainWindow.getWindowStatus(); + this.mainWindow.on('windowStatusChange', this.onStatusTypeChange); + // [Start getWindowProperties] + // [StartExclude windowStatusChange] + // First time get window size. + let width: number = this.mainWindow.getWindowProperties().windowRect.width; + let height: number = this.mainWindow.getWindowProperties().windowRect.height; + let windowSize: window.Size = { + width: width, + height: height } + this.mainWindowInfo.windowSize = windowSize; + // [End getWindowProperties] + // [EndExclude updateBreakpoint] + // First time get width/height breakpoint. + this.mainWindowInfo.widthBp = this.uiContext!.getWindowWidthBreakpoint(); + this.mainWindowInfo.heightBp = this.uiContext!.getWindowHeightBreakpoint(); + // [StartExclude updateBreakpoint] + // [EndExclude WindowSizeChange] + // Register for window size change monitoring, update window size and width/height breakpoint. + this.mainWindow.on('windowSizeChange', this.onWindowSizeChange); + // [StartExclude WindowSizeChange] + + // [EndExclude getWindowAvoidArea] + // First time get avoid area infos. + this.mainWindowInfo.AvoidSystem = this.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); + this.mainWindowInfo.AvoidNavigationIndicator = + this.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR); + this.mainWindowInfo.AvoidCutout = this.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_CUTOUT); + this.mainWindowInfo.AvoidSystemGesture = + this.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM_GESTURE); + this.mainWindowInfo.AvoidKeyboard = this.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_KEYBOARD); + // [StartExclude getWindowAvoidArea] + // [EndExclude onAvoidAreaChange] + this.mainWindow.on('avoidAreaChange', this.onAvoidAreaChange); + // [EndExclude windowStatusChange] + // [EndExclude updateBreakpoint] + // [EndExclude WindowSizeChange] + // [EndExclude getWindowAvoidArea] + } catch (error) { + let err = error as BusinessError; + hilog.error(0x0000, `TestLog`, `Failed to update window info. Code: ${err.code}, message: ${err.message}`); } } + // [End onAvoidAreaChange] + // [End windowStatusChange] + // [End updateBreakpoint] + // [End WindowSizeChange] + // [End getWindowAvoidArea] - recover(): void { - if (canIUse('SystemCapability.Window.SessionManager')) { - try { - if (this.mainWindowClass!.getWindowStatus() === window.WindowStatusType.FULL_SCREEN) { - this.mainWindowClass!.recover() - .then(() => { - hilog.info(0x0000, 'WindowUtil', '%{public}s', `Succeed in rovering the window.`); - }) - .catch((err: BusinessError) => { - hilog.error(0x0000, 'WindowUtil', - `Failed to rover the window. Code: ${err.code}, message: ${err.message}`, - JSON.stringify(err) ?? ''); - }); - } - } catch (error) { - let err = error as BusinessError; - hilog.error(0x00, 'WindowUtil', `getWindowStatus failed, code = ${err.code}, message = ${err.message}`); - } + release(): void { + try { + this.mainWindow.off('windowStatusChange'); + this.mainWindow.off('windowSizeChange'); + this.mainWindow.off('avoidAreaChange'); + } catch (error) { + let err = error as BusinessError; + hilog.error(0x0000, 'TestLog', `Failed to off. Code: ${err.code}, message: ${err.message}`); } + + } + + // [Start setSplitScreen] + setSplitScreen(bundleName: string, abilityName: string, moduleName: string): void { + // [StartExclude setSplitScreen] + // [Start getContext] + let context = this.uiContext?.getHostContext() as common.UIAbilityContext; + // [End getContext] + // [EndExclude setSplitScreen] + // Create StartOptions and set them to the main window mode. + let option: StartOptions = { windowMode: AbilityConstant.WindowMode.WINDOW_MODE_SPLIT_PRIMARY }; + let want: Want = { bundleName: bundleName, abilityName: abilityName, moduleName: moduleName }; + context.startAbility(want, option).catch((err: BusinessError) => { + hilog.error(0x0000, 'TestLog', `Failed to start ability. Code: ${err.code}, message: ${err.message}`); + }); } + // [End setSplitScreen] - getMainWindow(): window.Window | undefined { - return this.mainWindowClass; + // [Start cancelSplitScreen] + cancelSplitScreen(): void { + let context = this.uiContext?.getHostContext() as common.UIAbilityContext; + context.terminateSelf().catch((err: BusinessError) => { + hilog.error(0x0000, 'TestLog', `Failed to terminate self. Code: ${err.code}, message: ${err.message}`); + }); } + // [End cancelSplitScreen] - offWindowSizeChange(): void { - try { - this.mainWindowClass!.off('windowSizeChange'); - } catch (err) { - hilog.error(0x0000, 'WindowUtil', `Failed to off window size change. Code: ${err.code}, message: ${err.message}`, - JSON.stringify(err) ?? ''); + // [Start setWindowLimits] + setWindowLimits(maxWidth: number, maxHeight: number, minWidth: number, minHeight: number): void { + let windowLimits: window.WindowLimits = { + maxWidth: maxWidth, + maxHeight: maxHeight, + minWidth: minWidth, + minHeight: minHeight } + this.mainWindow.setWindowLimits(windowLimits).then((data: window.WindowLimits) => { + hilog.info(0x0000, 'testLog', `Succeeded in changing the window limits. Cause: ${JSON.stringify(data)}`); + }).catch((err: BusinessError) => { + hilog.error(0x0000, 'testLog', + `Failed to change the window limits. Cause code: ${err.code}, message: ${err.message}`); + }); } + // [End setWindowLimits] - // [Start update_widthbp] - // [Start dd_update_widthbp] - updateWidthBp(): void { - try { - let mainWindow: window.WindowProperties = this.mainWindowClass!.getWindowProperties(); - let windowWidth: number = mainWindow.windowRect.width; - let windowWidthVp = windowWidth / (display.getDefaultDisplaySync().densityDPI / 160); - if (deviceInfo.deviceType === CommonConstants.DEVICE_TYPE) { - windowWidthVp -= 2 * CommonConstants.WINDOW_FLOATING_MARGIN; - } - let widthBp: string = ''; - let videoGridColumn: string = CommonConstants.VIDEO_GRID_COLUMNS[0]; - if (windowWidthVp < 320) { - widthBp = 'xs'; - videoGridColumn = CommonConstants.VIDEO_GRID_COLUMNS[0]; - } else if (windowWidthVp >= 320 && windowWidthVp < 600) { - widthBp = 'sm'; - videoGridColumn = CommonConstants.VIDEO_GRID_COLUMNS[0]; - } else if (windowWidthVp >= 600 && windowWidthVp < 840) { - widthBp = 'md'; - videoGridColumn = CommonConstants.VIDEO_GRID_COLUMNS[1]; - } else { - widthBp = 'lg'; - videoGridColumn = CommonConstants.VIDEO_GRID_COLUMNS[2]; + // [Start resize] + resize(width: number, height: number): void { + this.mainWindow.resize(width, height, (err: BusinessError) => { + const errCode: number = err.code; + if (errCode) { + hilog.error(0x0000, 'testLog', + `Failed to change the window size. Cause code: ${err.code}, message: ${err.message}`); + return; } - AppStorage.setOrCreate('currentWidthBreakpoint', widthBp); - AppStorage.setOrCreate('videoGridColumn', videoGridColumn); + hilog.info(0x0000, 'testLog', 'Succeeded in changing the window size.'); + }); + } + // [End resize] + + queryOrientationByDisplay(): void { + // [Start queryOrientationByDisplay] + try { + let displayClass: display.Display | null = display.getDefaultDisplaySync(); } catch (error) { let err = error as BusinessError; - hilog.error(0x00, 'WindowUtil', `getDefaultDisplaySync failed, code = ${err.code}, message = ${err.message}`); + hilog.error(0x0000, 'testLog', `Failed to query display orientation. Code: ${err.code}, message: ${err.message}`); } + // [End queryOrientationByDisplay] } - // [End dd_update_widthbp] - // [End update_heightbp] + queryOrientationByResourceManager(): void { + // [Start queryByByRM] + let info: resourceManager.Direction | undefined = + this.uiContext?.getHostContext()?.resourceManager.getConfigurationSync().direction; + hilog.info(0x0000, 'testLog', `The Orientation is ${info}`); + // [End queryByByRM] + } - // [Start update_heightbp] - // [Start dd_update_heightbp] - updateHeightBp(): void { + // [Start queryDegree] + queryDegree(): void { try { - let mainWindow: window.WindowProperties = this.mainWindowClass!.getWindowProperties(); - let windowHeight: number = mainWindow.windowRect.height; - let windowWidth: number = mainWindow.windowRect.width; - let windowWidthVp = windowWidth / (display.getDefaultDisplaySync().densityDPI / 160); - let windowHeightVp = windowHeight / (display.getDefaultDisplaySync().densityDPI / 160); - let heightBp: string = ''; - let aspectRatio: number = windowHeightVp / windowWidthVp; - if (aspectRatio < 0.8) { - heightBp = 'sm'; - } else if (aspectRatio >= 0.8 && aspectRatio < 1.2) { - heightBp = 'md'; - } else { - heightBp = 'lg'; - } - AppStorage.setOrCreate('currentHeightBreakpoint', heightBp); + sensor.on(sensor.SensorId.GRAVITY, (data: sensor.GravityResponse) => { + let degree: number = this.getCalDegree(data.x, data.y, data.z); + }) } catch (error) { let err = error as BusinessError; - hilog.error(0x00, 'WindowUtil', `getDefaultDisplaySync failed, code = ${err.code}, message = ${err.message}`); + hilog.error(0x0000, 'TestLog', `Failed to query degree. Code: ${err.code}, message: ${err.message}`); } } - // [End dd_update_heightbp] - // [End update_heightbp] -} \ No newline at end of file + getCalDegree(x: number, y: number, z: number): number { + let degree: number = 0; + // three is Effective Delta Angle Threshold Coefficient. + if ((x * x + y * y) * 3 < z * z) { + return degree; + } + degree = 90 - (Number)(Math.round(Math.atan2(y, -x) / Math.PI * 180)); + return degree >= 0 ? degree % 360 : degree % 360 + 360; + } + // [End queryDegree] +} + +// [Start WindowInfo] +@Observed +export class WindowInfo { + // Window status. + public windowStatusType: window.WindowStatusType = window.WindowStatusType.UNDEFINED; + // Is the window an immersive layout. + public isImmersive: ImmersiveType = ImmersiveType.NORMAL; + // Window orientation. + public orientation: window.Orientation = window.Orientation.UNSPECIFIED; + // Window size. + public windowSize: window.Size = { width: 0, height: 0 }; + // Width/height breakpoint. + public widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_XS; + public heightBp: HeightBreakpoint = HeightBreakpoint.HEIGHT_SM; + // Avoid area infos. + public AvoidSystem?: window.AvoidArea; + public AvoidNavigationIndicator?: window.AvoidArea; + public AvoidCutout?: window.AvoidArea; + public AvoidSystemGesture?: window.AvoidArea; + public AvoidKeyboard?: window.AvoidArea; +} +// [End WindowInfo] +// [End WindowUtil] diff --git a/features/videoDetail/src/main/ets/constants/DetailConstants.ets b/features/videoDetail/src/main/ets/constants/DetailConstants.ets index 531116d10911d096aac4664d967b879af33c166b..3d879d693497b8fe89779f3fa715d0610a457214 100644 --- a/features/videoDetail/src/main/ets/constants/DetailConstants.ets +++ b/features/videoDetail/src/main/ets/constants/DetailConstants.ets @@ -17,11 +17,35 @@ export class DetailConstants { /** * Initial comment image height. */ - static readonly INITIAL_COMMENT_IMAGE_HEIGHT: string = '150vp'; + static readonly INITIAL_COMMENT_IMAGE_HEIGHT: string = '204vp'; /** * Initial comment image width. */ - static readonly INITIAL_COMMENT_IMAGE_WIDTH: string = '219vp'; + static readonly INITIAL_COMMENT_IMAGE_WIDTH: string = '299vp'; + /** + * XSmall comment image height. + */ + static readonly XSMALL_COMMENT_IMAGE_HEIGHT: string = '227vp'; + /** + * XSmall comment image width. + */ + static readonly XSMALL_COMMENT_IMAGE_WIDTH: string = '227vp'; + /** + * Small comment image height. + */ + static readonly SMALL_COMMENT_IMAGE_HEIGHT: string = '227vp'; + /** + * Small comment image width. + */ + static readonly SMALL_COMMENT_IMAGE_WIDTH: string = '227vp'; + /** + * Medium comment image height. + */ + static readonly MEDIUM_COMMENT_IMAGE_HEIGHT: string = '254vp'; + /** + * Medium comment image width. + */ + static readonly MEDIUM_COMMENT_IMAGE_WIDTH: string = '254vp'; /** * Initial related video height. */ @@ -33,23 +57,23 @@ export class DetailConstants { /** * Comment image min height number. */ - static readonly COMMENT_IMAGE_MIN_HEIGHT_NUMBER: number = 150; + static readonly COMMENT_IMAGE_MIN_HEIGHT_NUMBER: number = 204; /** * Comment image min width number. */ - static readonly COMMENT_IMAGE_MIN_WIDTH_NUMBER: number = 219; + static readonly COMMENT_IMAGE_MIN_WIDTH_NUMBER: number = 299; /** * Comment image max height number. */ - static readonly COMMENT_IMAGE_MAX_HEIGHT_NUMBER: number = 182; + static readonly COMMENT_IMAGE_MAX_HEIGHT_NUMBER: number = 236; /** * Comment image max width number. */ - static readonly COMMENT_IMAGE_MAX_WIDTH_NUMBER: number = 266; + static readonly COMMENT_IMAGE_MAX_WIDTH_NUMBER: number = 346; /** * Side bar min width number. */ - static readonly SIDE_BAR_MIN_WIDTH_NUMBER: number = 320; + static readonly SIDE_BAR_MIN_WIDTH_NUMBER: number = 400; /** * Offset for scrolling to top. */ @@ -88,8 +112,8 @@ export class DetailConstants { /** * Icon list. */ - static readonly ICON_LIST: Resource[] = [$r('app.media.ic_public_favor'), $r('app.media.ic_public_comments'), - $r('app.media.ic_public_thumb_sup'), $r('app.media.ic_public_download'), $r('app.media.ic_public_share')]; + static readonly ICON_LIST: Resource[] = [$r('app.media.heart'), $r('app.media.ellipsis_message'), + $r('app.media.hand_thumbsup'), $r('app.media.arrow_down'), $r('app.media.arrowshape_turn_up_right')]; /** * Shadow radius. */ @@ -165,7 +189,7 @@ export class DetailConstants { /** * Episode list lanes list. */ - static readonly EPISODE_LIST_LANES: number[] = [8, 4]; + static readonly EPISODE_LIST_LANES: number[] = [8, 5]; /** * Episodes. */ diff --git a/features/videoDetail/src/main/ets/view/AllComments.ets b/features/videoDetail/src/main/ets/view/AllComments.ets index e581b0c8678495f2ae6dd227eb1048a513577d56..bae6631816086ba090f6385bd996562c6d0446e3 100644 --- a/features/videoDetail/src/main/ets/view/AllComments.ets +++ b/features/videoDetail/src/main/ets/view/AllComments.ets @@ -13,14 +13,17 @@ * limitations under the License. */ -import { BreakpointConstants, CommonConstants } from '@ohos/commons'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { CommonConstants, WindowInfo, WindowUtil } from '@ohos/commons'; import { BreakpointType } from '@ohos/commons'; import { DetailConstants } from '../constants/DetailConstants'; import { UserInfo, UserViewModel } from '../viewmodel/UserViewModel'; @Component export struct AllComments { - @StorageLink('currentWidthBreakpoint') currentWidthBreakpoint: string = BreakpointConstants.BREAKPOINT_LG; + @StorageLink('windowUtil') windowUtil: WindowUtil | undefined = undefined; + @ObjectLink mainWindowInfo: WindowInfo; @Link commentImgHeight: string; @Link commentImgWidth: string; private commentsList: UserInfo[] = new UserViewModel().getRelatedVideoList(); @@ -67,10 +70,10 @@ export struct AllComments { Row() { // [Start get_comment_image_src] Image(item.getCommentImageSrc()) - .width(new BreakpointType('227vp', '254vp', this.commentImgWidth).getValue(this.currentWidthBreakpoint)) - .height(new BreakpointType('227vp', '254vp', this.commentImgHeight).getValue(this.currentWidthBreakpoint)) - .borderRadius('8vp') - .aspectRatio(1.46) + .width(new BreakpointType(DetailConstants.XSMALL_COMMENT_IMAGE_WIDTH, DetailConstants.SMALL_COMMENT_IMAGE_WIDTH, DetailConstants.MEDIUM_COMMENT_IMAGE_WIDTH, this.commentImgWidth, this.commentImgWidth).getValue(this.mainWindowInfo.widthBp)) + .height(new BreakpointType(DetailConstants.XSMALL_COMMENT_IMAGE_HEIGHT, DetailConstants.SMALL_COMMENT_IMAGE_HEIGHT, DetailConstants.MEDIUM_COMMENT_IMAGE_HEIGHT, this.commentImgHeight, this.commentImgHeight).getValue(this.mainWindowInfo.widthBp)) + .borderRadius($r('app.float.comment_image_border_radius')) + .aspectRatio(CommonConstants.COMMENT_IMAGE_RATIO) // [End get_comment_image_src] } .padding({ left: $r('app.float.all_comments_img_row_padding') }) @@ -89,18 +92,18 @@ export struct AllComments { Blank() - IconImage({ image: $r('app.media.ic_public_comments') }) + IconImage({ image: $r('app.media.ellipsis_message') }) .margin({ right: $r('app.float.all_comments_icon_margin') }) - IconImage({ image: $r("app.media.ic_public_thumb_sup") }) + IconImage({ image: $r("app.media.hand_thumbsup") }) .margin({ right: $r('app.float.all_comments_icon_margin') }) - IconImage({ image: $r('app.media.ic_public_share') }) + IconImage({ image: $r('app.media.arrowshape_turn_up_right') }) } .margin({ top: $r('app.float.all_comments_icon_row_top') }) .width(CommonConstants.FULL_PERCENT) .justifyContent(FlexAlign.Start) Divider() - .color(Color.Black) + .color($r('app.color.color_black')) .opacity(CommonConstants.DIVIDER_OPACITY) .margin({ top: $r('app.float.all_comments_divider_margin') @@ -118,8 +121,9 @@ export struct AllComments { .width(CommonConstants.FULL_PERCENT) } .width(CommonConstants.FULL_PERCENT) - .backgroundColor(Color.White) + .backgroundColor($r('app.color.color_white')) .justifyContent(FlexAlign.Start) + .padding({ top: this.getTopPadding() }) } @Builder @@ -129,6 +133,27 @@ export struct AllComments { .lineHeight($r('app.float.all_comments_title_text_line')) .fontWeight(CommonConstants.FONT_WEIGHT_500) } + + getTopPadding() { + if (this.mainWindowInfo.widthBp !== WidthBreakpoint.WIDTH_LG && this.mainWindowInfo.widthBp !== WidthBreakpoint.WIDTH_XL) { + return 0; + } + let decorHeight: number | undefined = undefined; + let statusHeight: number | undefined = this.mainWindowInfo.AvoidSystem?.topRect.height; + try { + decorHeight = this.windowUtil?.mainWindow.getTitleButtonRect().height; + } catch (error) { + let err = error as BusinessError; + hilog.error(0x0000, 'AllComments', `Failed to get decorHeight, errCode = ${err.code}, errMessage = ${err.message}.`); + } + if (!!decorHeight) { + return decorHeight; + } else if (!!statusHeight) { + return this.getUIContext().px2vp(statusHeight); + } else { + return 0; + } + } } @Component diff --git a/features/videoDetail/src/main/ets/view/FooterEpisodes.ets b/features/videoDetail/src/main/ets/view/FooterEpisodes.ets index c30be49d8eba1ff9309a4eb0f012c9a5b2ed51a7..c392b9423684fc63ea780ea3d6b64b5a3cce6a78 100644 --- a/features/videoDetail/src/main/ets/view/FooterEpisodes.ets +++ b/features/videoDetail/src/main/ets/view/FooterEpisodes.ets @@ -14,15 +14,13 @@ */ import { display } from '@kit.ArkUI'; -import { deviceInfo } from '@kit.BasicServicesKit'; -import { BreakpointConstants, CommonConstants } from '@ohos/commons'; +import { CommonConstants, WindowInfo } from '@ohos/commons'; import { DetailConstants } from '../constants/DetailConstants'; @Component export struct FooterEpisodes { - @StorageLink('currentWidthBreakpoint') currentWidthBreakpoint: string = BreakpointConstants.BREAKPOINT_LG; - @StorageLink('currentHeightBreakpoint') currentHeightBreakpoint: string = BreakpointConstants.BREAKPOINT_LG; @StorageLink('isFullScreen') isFullScreen: boolean = false; + @ObjectLink mainWindowInfo: WindowInfo; @Link isShowingSideBar: boolean; @Link foldStatus: display.FoldStatus; @@ -31,26 +29,28 @@ export struct FooterEpisodes { // Selection bottom bar. Row() { Text(DetailConstants.PLAYER_TEXT_LIST[0]) - .fontSize($r('app.float.title_selected_font')) + .fontSize(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.title_selected_font_sm') : $r('app.float.title_selected_font')) .fontColor(Color.White) - .fontWeight(CommonConstants.FONT_WEIGHT_500) - .lineHeight($r('app.float.title_selected_line')) + .fontWeight(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? CommonConstants.FONT_WEIGHT_400 : CommonConstants.FONT_WEIGHT_500) + .lineHeight(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.title_selected_line_sm') : $r('app.float.title_selected_line')) .width($r('app.float.title_selected_width')) - .margin({ right: $r('app.float.title_selected_margin') }) + .margin({ right: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.title_selected_margin_sm') : $r('app.float.title_selected_margin') }) Text(DetailConstants.PLAYER_TEXT_LIST[1]) - .fontSize($r('app.float.title_font')) + .fontSize(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.title_selected_font_sm') : $r('app.float.title_font')) .fontColor(Color.White) - .fontWeight(FontWeight.Normal) - .lineHeight($r('app.float.title_line')) + .fontWeight(CommonConstants.FONT_WEIGHT_400) + .lineHeight(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.title_selected_line_sm') : $r('app.float.title_line')) .width($r('app.float.title_width')) .opacity(CommonConstants.TEXT_OPACITY[2]) } .margin({ - top: $r('app.float.title_row_top'), - bottom: $r('app.float.title_row_bottom') + top: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.title_row_top_sm') : $r('app.float.title_row_top'), + bottom: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.title_row_bottom_sm') : $r('app.float.title_row_bottom') }) .width(CommonConstants.FULL_PERCENT) - .height($r('app.float.title_row_height')) + .height(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.title_row_height_sm') : $r('app.float.title_row_height')) + .justifyContent(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? FlexAlign.Center : FlexAlign.Start) + List({ space: CommonConstants.LIST_SPACE }) { ForEach(DetailConstants.PLAYER_EPISODE, (item: string, index: number) => { @@ -58,7 +58,7 @@ export struct FooterEpisodes { Row() { Text(item) .fontSize($r('app.float.title_font')) - .fontColor(index === 1 ? $r('app.color.font_selected') : Color.White) + .fontColor(index === 1 ? $r('app.color.font_selected') : $r('app.color.color_white')) .fontWeight(FontWeight.Normal) Image($r('app.media.video_playing')) @@ -66,7 +66,7 @@ export struct FooterEpisodes { .width($r('app.float.playing_size')) .position({ x: $r('app.float.playing_position_x'), - y: $r('app.float.playing_position_y') + y: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.playing_position_y_sm') : $r('app.float.playing_position_y') }) .visibility(index === 1 ? Visibility.Visible : Visibility.None) } @@ -74,7 +74,7 @@ export struct FooterEpisodes { .borderRadius($r('app.float.episode_row_radius')) .backgroundColor($r('app.color.episode_row_background')) .width(CommonConstants.FULL_PERCENT) - .height($r('app.float.episode_row_height')) + .height(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.episode_row_height_sm') : $r('app.float.episode_row_height')) } }, (item: string, index: number) => index + JSON.stringify(item)) } @@ -83,16 +83,16 @@ export struct FooterEpisodes { .width(CommonConstants.FULL_PERCENT) .layoutWeight(1) .padding({ bottom: $r('app.float.episode_list_bottom') }) - .lanes(this.currentWidthBreakpoint === BreakpointConstants.BREAKPOINT_MD ? DetailConstants.EPISODE_LIST_LANES[0] : - DetailConstants.EPISODE_LIST_LANES[1], $r('app.float.episode_list_lanes_space')) + .lanes(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? DetailConstants.EPISODE_LIST_LANES[1] : + DetailConstants.EPISODE_LIST_LANES[0], $r('app.float.episode_list_lanes_space')) } .layoutWeight(1) .width(CommonConstants.FULL_PERCENT) .visibility(this.isShowingFooter()) - .backgroundColor(Color.Black) + .backgroundColor($r('app.color.color_black')) .padding({ - left: $r('app.float.episode_col_padding'), - right: $r('app.float.episode_col_padding') + left: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.episode_col_padding_sm') : $r('app.float.episode_col_padding'), + right: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.episode_col_padding_sm') : $r('app.float.episode_col_padding') }) } @@ -100,11 +100,14 @@ export struct FooterEpisodes { if (!this.isShowingSideBar || !this.isFullScreen) { return Visibility.None; } - if (deviceInfo.deviceType === CommonConstants.DEVICE_TYPE) { - return this.currentWidthBreakpoint !== BreakpointConstants.BREAKPOINT_LG ? Visibility.Visible : Visibility.None; + if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD && this.mainWindowInfo.heightBp !== + HeightBreakpoint.HEIGHT_SM) { + return Visibility.Visible; + } + if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_LG) { + return Visibility.Visible; } - if (this.currentWidthBreakpoint === BreakpointConstants.BREAKPOINT_MD && this.currentHeightBreakpoint !== - BreakpointConstants.BREAKPOINT_SM) { + if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM) { return Visibility.Visible; } return Visibility.None; diff --git a/features/videoDetail/src/main/ets/view/RelatedList.ets b/features/videoDetail/src/main/ets/view/RelatedList.ets index fdd57d2f5fbc85a1d2e4675bf3da4e66f9867a2a..e5e56921c246b7f701757c304d2afbebf6284af6 100644 --- a/features/videoDetail/src/main/ets/view/RelatedList.ets +++ b/features/videoDetail/src/main/ets/view/RelatedList.ets @@ -13,7 +13,7 @@ * limitations under the License. */ -import { BreakpointConstants, CommonConstants } from '@ohos/commons'; +import { CommonConstants, WindowInfo } from '@ohos/commons'; import { DetailConstants } from '../constants/DetailConstants'; import { CurrentOffsetUtil } from '../utils/CurrentOffsetUtil'; import { RelatedVideo, RelatedVideoViewModel } from '../viewmodel/RelatedVideoViewModel'; @@ -21,16 +21,19 @@ import { AllComments } from './AllComments'; @Component export struct RelatedList { - @StorageLink('currentWidthBreakpoint') currentWidthBreakpoint: string = 'lg'; @StorageLink('isFullScreen') isFullScreen: boolean = false; - @State commentImgHeight: string = DetailConstants.INITIAL_COMMENT_IMAGE_HEIGHT; - @State commentImgWidth: string = DetailConstants.INITIAL_COMMENT_IMAGE_WIDTH; + @ObjectLink mainWindowInfo: WindowInfo; @Link relatedVideoHeight: number; @Link videoHeight: number; + // The current vertical sliding distance of the list. + @Link currentYOffset: number; + @Link isHidingSelfComment: boolean; + @State commentImgHeight: string = DetailConstants.INITIAL_COMMENT_IMAGE_HEIGHT; + @State commentImgWidth: string = DetailConstants.INITIAL_COMMENT_IMAGE_WIDTH; public screenHeight: number = 0; private relatedVideoList: RelatedVideo[] = new RelatedVideoViewModel().getRelatedVideoList(); private peripheralVideoList: RelatedVideo[] = new RelatedVideoViewModel().getPeripheralVideoList(); - private episodes: string[] = DetailConstants.EPISODES_LIST; + private episodes: string[] = DetailConstants.PLAYER_EPISODE; private currentIndex: number = 2; private iconList: Resource[] = DetailConstants.ICON_LIST; private scroller: Scroller = new Scroller(); @@ -41,18 +44,19 @@ export struct RelatedList { Column() { this.RelatedVideoComponent() this.VideoIntroduction() - AllComments({commentImgHeight: this.commentImgHeight, commentImgWidth: this.commentImgWidth}) - .visibility(this.currentWidthBreakpoint === 'lg' ? Visibility.None : Visibility.Visible) + AllComments({commentImgHeight: this.commentImgHeight, commentImgWidth: this.commentImgWidth, mainWindowInfo: this.mainWindowInfo}) + .visibility(this.mainWindowInfo.heightBp !== HeightBreakpoint.HEIGHT_LG && (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL) ? Visibility.None : Visibility.Visible) } - .width('100%') + .width(CommonConstants.FULL_PERCENT) .alignItems(HorizontalAlign.Start) - .padding({ bottom: '10vp' }) + .padding({ bottom: $r('app.float.related_list_bottom') }) } + .align(Alignment.Top) .layoutWeight(1) .scrollBar(BarState.Off) .visibility(!this.isFullScreen ? Visibility.Visible : Visibility.None) .onScrollFrameBegin((offset: number) => { - if (this.currentWidthBreakpoint === 'lg') { + if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL) { if ((offset > 0) && (this.videoHeight > 53)) { // Video zoom-out logic. // Percentage of screen height by sliding. @@ -72,6 +76,40 @@ export struct RelatedList { return { offsetRemain: 0 }; } return { offsetRemain: offset }; + } else if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_MD) { + if (offset > 0) { + this.currentYOffset += offset; + this.isHidingSelfComment = true; + if (this.videoHeight > 53) { + // Video zoom-out logic. + // Percentage of screen height by sliding. + let offsetPercent = (Math.abs(offset) * 100) / this.screenHeight; + // Video shrinkage percentage. + let heightOffset = offsetPercent < this.videoHeight - 53 ? offsetPercent : this.videoHeight - 53; + this.videoHeight = this.videoHeight - heightOffset; + // Returns the actual offset 0. + return { offsetRemain: 0 }; + } + } else if (offset < 0) { + if (this.isHidingSelfComment) { + this.getUIContext().animateTo({ + duration: 300 + }, () => { + this.currentYOffset = 0; + this.isHidingSelfComment = false; + }); + } + if ((this.videoHeight < 100) && CurrentOffsetUtil.scrollToTop(JSON.stringify(this.scroller.currentOffset()))) { + // Video magnification logic. + let offsetPercent = (Math.abs(offset) * 100) / this.screenHeight; + let heightOffset = offsetPercent < 100 - this.videoHeight ? offsetPercent : 100 - this.videoHeight; + this.videoHeight = this.videoHeight + heightOffset; + // Returns the actual offset 0. + return { offsetRemain: 0 }; + } + return { offsetRemain: offset }; + } + return { offsetRemain: offset }; } else { if ((offset > 0) && (this.videoHeight === 100) && (this.relatedVideoHeight > 0)) { // Related list shrinking logic. @@ -138,9 +176,9 @@ export struct RelatedList { Text(item.getName()) .fontSize($r('app.float.related_name_font')) .lineHeight($r('app.float.related_name_line')) - .fontWeight(500) + .fontWeight(CommonConstants.FONT_WEIGHT_500) .opacity(CommonConstants.TEXT_OPACITY[2]) - .fontColor(index === 0 ? $r('app.color.episodes_font') : Color.Black) + .fontColor(index === 0 ? $r('app.color.episodes_font') : $r('app.color.color_black')) } } .margin({ @@ -157,7 +195,7 @@ export struct RelatedList { bottom: $r('app.float.related_row_bottom') }) .height($r('app.float.related_row_height')) - .width('100%') + .width(CommonConstants.FULL_PERCENT) } .height(this.relatedVideoHeight + DetailConstants.LENGTH_UNIT) } @@ -174,18 +212,18 @@ export struct RelatedList { this.IntroductionContent($r('app.string.play')) } .padding({ - left: '24vp', - right: '24vp' + left: $r('app.float.video_describe_content_padding'), + right: $r('app.float.video_describe_content_padding') }) - .width('100%') + .width(CommonConstants.FULL_PERCENT) .alignItems(HorizontalAlign.Start) // [EndExclude video_introduction] Row() { ForEach(this.iconList, (item: Resource, index: number) => { Image(item) - .height('24vp') - .width('24vp') + .height($r('app.float.icon_size')) + .width($r('app.float.icon_size')) }, (item: Resource, index: number) => index + JSON.stringify(item)) } .justifyContent(FlexAlign.SpaceBetween) @@ -197,12 +235,12 @@ export struct RelatedList { right: $r('app.float.sub_title_row_padding') }) .height($r('app.float.sub_title_row_height_detail')) - .width('100%') + .width(CommonConstants.FULL_PERCENT) this.SubTitle($r('app.string.anthology')) // [EndExclude video_introduction] - List({ space: '12vp' }) { + List({ space: CommonConstants.LIST_SPACE }) { ForEach(this.episodes, (item: string, index: number) => { ListItem() { // [StartExclude video_introduction] @@ -217,7 +255,7 @@ export struct RelatedList { .fontSize($r('app.float.episodes_text_font')) .fontWeight(FontWeight.Normal) .height($r('app.float.episodes_text_height')) - .fontColor((index + 1) === this.currentIndex ? $r('app.color.episodes_font') : Color.Black) + .fontColor((index + 1) === this.currentIndex ? $r('app.color.episodes_font') : $r('app.color.color_black')) } .justifyContent(FlexAlign.Center) .backgroundColor($r('app.color.episodes_background')) @@ -232,12 +270,12 @@ export struct RelatedList { .listDirection(Axis.Horizontal) .padding({ left: $r('app.float.sub_title_row_padding') }) .margin({ bottom: $r('app.float.episodes_list_margin') }) - .width('100%') + .width(CommonConstants.FULL_PERCENT) this.SubTitleAndMore($r('app.string.Peripheral')) // [StartExclude video_introduction] - List({ space: '12vp' }) { + List({ space: CommonConstants.LIST_SPACE }) { ForEach(this.peripheralVideoList, (item: RelatedVideo, index: number) => { ListItem() { Column() { @@ -250,8 +288,8 @@ export struct RelatedList { Text(item.getName()) .lineHeight($r('app.float.peripheral_name_line')) .fontSize($r('app.float.peripheral_name_font')) - .opacity(0.6) - .fontWeight(500) + .opacity(CommonConstants.TEXT_OPACITY[2]) + .fontWeight(CommonConstants.FONT_WEIGHT_500) } } }, (item: RelatedVideo, index: number) => index + JSON.stringify(item)) @@ -263,12 +301,12 @@ export struct RelatedList { left: $r('app.float.sub_title_row_padding'), right: $r('app.float.sub_title_row_padding') }) - .width('100%') + .width(CommonConstants.FULL_PERCENT) // [EndExclude video_introduction] } - .width('100%') - .backgroundColor(Color.White) - .visibility(this.currentWidthBreakpoint === 'lg' ? Visibility.Visible : Visibility.None) + .width(CommonConstants.FULL_PERCENT) + .backgroundColor($r('app.color.color_white')) + .visibility(this.mainWindowInfo.heightBp !== HeightBreakpoint.HEIGHT_LG && (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL) ? Visibility.Visible : Visibility.None) } // [StartExclude video_introduction] @@ -278,14 +316,14 @@ export struct RelatedList { Text(subtitle) .fontSize($r('app.float.sub_title_font')) .lineHeight($r('app.float.sub_title_text_line')) - .fontWeight(500) + .fontWeight(CommonConstants.FONT_WEIGHT_500) } .padding({ left: $r('app.float.sub_title_row_padding'), right: $r('app.float.sub_title_row_padding') }) .height($r('app.float.sub_title_row_height_detail')) - .width('100%') + .width(CommonConstants.FULL_PERCENT) } // [EndExclude video_introduction] @@ -296,7 +334,7 @@ export struct RelatedList { Text(subtitle) .fontSize($r('app.float.sub_title_font')) .lineHeight($r('app.float.sub_title_text_line')) - .fontWeight(500) + .fontWeight(CommonConstants.FONT_WEIGHT_500) // [EndExclude video_introduction] Blank() // [StartExclude video_introduction] @@ -315,7 +353,7 @@ export struct RelatedList { right: $r('app.float.sub_title_row_padding') }) .height($r('app.float.sub_title_row_height_detail')) - .width('100%') + .width(CommonConstants.FULL_PERCENT) .alignItems(VerticalAlign.Center) } // [End video_introduction] @@ -325,7 +363,7 @@ export struct RelatedList { Text(content) .fontSize($r('app.float.introduction_content_font')) .lineHeight($r('app.float.introduction_content_line')) - .fontWeight(500) - .opacity(0.6) + .fontWeight(CommonConstants.FONT_WEIGHT_500) + .opacity(CommonConstants.TEXT_OPACITY[2]) } } \ No newline at end of file diff --git a/features/videoDetail/src/main/ets/view/RelatedListForTv.ets b/features/videoDetail/src/main/ets/view/RelatedListForTv.ets new file mode 100644 index 0000000000000000000000000000000000000000..5729079244a9ce3644d27f84413176b2e490651e --- /dev/null +++ b/features/videoDetail/src/main/ets/view/RelatedListForTv.ets @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2023 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 "@ohos/commons" +import { DetailConstants } from "../constants/DetailConstants" +import { RelatedVideo, RelatedVideoViewModel } from "../viewmodel/RelatedVideoViewModel"; + +@Component +export struct RelatedListForTv { + private relatedVideoList: RelatedVideo[] = new RelatedVideoViewModel().getRelatedVideoList(); + private peripheralVideoList: RelatedVideo[] = new RelatedVideoViewModel().getPeripheralVideoList(); + + build() { + Column() { + RelatedVideoComponent({ + imageWidth: $r('app.float.related_img_width_for_tv'), + imageHeight: $r('app.float.related_img_height_for_tv'), + imageBackgroundWidth: $r('app.float.related_img_background_width_for_tv'), + imageBackgroundHeight: $r('app.float.related_img_background_height_for_tv'), + imageBorderWidth: $r('app.float.related_img_border_width_for_tv'), + imageBorderHeight: $r('app.float.related_img_border_height_for_tv'), + dataSource: this.relatedVideoList, + title: DetailConstants.SUB_TITLES[1], + isPlaying: true + }); + RelatedVideoComponent({ + imageWidth: $r('app.float.peripheral_img_width_for_tv'), + imageHeight: $r('app.float.peripheral_img_height_for_tv'), + imageBackgroundWidth: $r('app.float.peripheral_img_background_width_for_tv'), + imageBackgroundHeight: $r('app.float.peripheral_img_background_height_for_tv'), + imageBorderWidth: $r('app.float.peripheral_img_border_width_for_tv'), + imageBorderHeight: $r('app.float.peripheral_img_border_height_for_tv'), + dataSource: this.peripheralVideoList, + title: DetailConstants.SUB_TITLES[5], + isPlaying: false + }); + } + .margin({ bottom: 90 }) + } +} + +@Component +struct RelatedVideoComponent { + @Prop imageWidth: Resource; + @Prop imageHeight: Resource; + @Prop imageBackgroundWidth: Resource; + @Prop imageBackgroundHeight: Resource; + @Prop imageBorderWidth: Resource; + @Prop imageBorderHeight: Resource; + @Prop dataSource: RelatedVideo[]; + @Prop title: ResourceStr; + @Prop isPlaying: boolean; + + build() { + Column() { + Text(this.title) + .lineHeight($r('app.float.related_video_title_line_height')) + .fontSize($r('app.float.related_video_title_font_size')) + .fontWeight(CommonConstants.FONT_WEIGHT_700) + .fontFamily(CommonConstants.FONT_HARMONY_HEITI) + .fontColor(Color.White) + .opacity(CommonConstants.TEXT_OPACITY[5]) + .margin({ + top: $r('app.float.related_video_title_margin_top'), + bottom: $r('app.float.related_video_title_margin_bottom'), + left: $r('app.float.related_video_title_margin_left') + }) + + Row() { + List({ space: CommonConstants.LIST_SPACE_FOR_TV }) { + ForEach(this.dataSource, (item: RelatedVideo, index: number) => { + ListItem() { + Column() { + Stack() { + Column() + .width(this.imageBorderWidth) + .height(this.imageBorderHeight) + .borderRadius($r('app.float.related_img_border_radius_for_tv')) + .linearGradient({ + direction: GradientDirection.Right, + colors: [[$r('app.color.related_choose_left_for_tv'), 0], [$r('app.color.related_choose_right_for_tv'), 1]] + }) + .visibility(this.isPlaying && index === 0 ? Visibility.Visible : Visibility.Hidden) + + Column() + .width(this.imageBackgroundWidth) + .height(this.imageBackgroundHeight) + .borderRadius($r('app.float.related_img_radius_for_tv')) + .backgroundColor(Color.Black) + .visibility(this.isPlaying && index === 0 ? Visibility.Visible : Visibility.Hidden) + + Image(item.getImageSrc()) + .height(this.imageHeight) + .width(this.imageWidth) + .objectFit(ImageFit.Cover) + .borderRadius($r('app.float.related_img_radius_for_tv')) + .hoverEffect(HoverEffect.Scale) + } + .margin({ bottom: $r('app.float.related_img_margin') }) + + Text(item.getName()) + .lineHeight($r('app.float.related_name_line_for_tv')) + .fontSize($r('app.float.related_name_font_for_tv')) + .fontWeight(CommonConstants.FONT_WEIGHT_400) + .fontFamily(CommonConstants.FONT_HARMONY_HEITI) + .opacity(CommonConstants.TEXT_OPACITY[3]) + .fontColor(index === 0 && this.isPlaying ? $r('app.color.episodes_font') : $r('app.color.color_white')) + } + .alignItems(HorizontalAlign.Start) + .margin({ + top: $r('app.float.related_video_image_margin'), + left: $r('app.float.related_video_image_margin'), + right: $r('app.float.related_video_image_margin') + }) + } + }, (item: RelatedVideo, index: number) => index + JSON.stringify(item)) + } + .listDirection(Axis.Horizontal) + .scrollBar(BarState.Off) + } + .padding({ + left: $r('app.float.related_row_left_for_tv') + }) + .width(CommonConstants.FULL_PERCENT) + } + .alignItems(HorizontalAlign.Start) + } +} \ No newline at end of file diff --git a/features/videoDetail/src/main/ets/view/SelfComment.ets b/features/videoDetail/src/main/ets/view/SelfComment.ets index 68823b17ba97d6498a3078f115b897a007ef971e..784e6c85f9358276738a37baee818a811a5b750c 100644 --- a/features/videoDetail/src/main/ets/view/SelfComment.ets +++ b/features/videoDetail/src/main/ets/view/SelfComment.ets @@ -13,15 +13,39 @@ * limitations under the License. */ -import { deviceInfo } from '@kit.BasicServicesKit'; -import { CommonConstants } from '@ohos/commons'; +import { window } from '@kit.ArkUI'; +import { CommonConstants, WindowInfo } from '@ohos/commons'; import { DetailConstants } from '../constants/DetailConstants'; import { UserInfo, UserViewModel } from '../viewmodel/UserViewModel'; @Component export struct SelfComment { + @ObjectLink mainWindowInfo: WindowInfo; + @Link @Watch('setSelfCommentHeight') currentYOffset: number; + @State selfCommentHeight: number = 64; + @State isShowingKeyboard: boolean = false; private selfInfo: UserInfo = new UserViewModel().getSelfInfo(); + setSelfCommentHeight() { + if (this.currentYOffset === 0) { + this.selfCommentHeight = 64; + } else if (this.currentYOffset > 0 && this.currentYOffset <= 100) { + this.selfCommentHeight = 64 * (1 - this.currentYOffset / 100); + } else if (this.currentYOffset > 100) { + this.selfCommentHeight = 0; + } else { + this.selfCommentHeight = 64; + } + } + + aboutToAppear(): void { + window.getLastWindow(this.getUIContext().getHostContext()).then(currentWindow => { + currentWindow.on('keyboardHeightChange', () => { + this.currentYOffset = 0; + }); + }); + } + build() { Row() { Image(this.selfInfo.getImageSrc()) @@ -78,15 +102,13 @@ export struct SelfComment { color: $r('app.color.shadow_color'), offsetY: DetailConstants.SHADOW_OFFSET_Y }) - .height(deviceInfo.deviceType === CommonConstants.DEVICE_TYPE ? $r('app.float.self_comment_row_height') : - $r('app.float.self_comment_row_height_more')) + .height(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_MD ? this.selfCommentHeight : $r('app.float.self_comment_row_height')) .width(CommonConstants.FULL_PERCENT) .alignItems(VerticalAlign.Center) - .backgroundColor(Color.White) + .backgroundColor($r('app.color.color_white')) .padding({ - right: $r('app.float.self_comment_row_padding_right'), - bottom: deviceInfo.deviceType === CommonConstants.DEVICE_TYPE ? 0 : - $r('app.float.self_comment_row_padding_bottom') + right: $r('app.float.self_comment_row_padding_right') }) + .clip(true) } } \ No newline at end of file diff --git a/features/videoDetail/src/main/ets/view/SideEpisodes.ets b/features/videoDetail/src/main/ets/view/SideEpisodes.ets index 96ea6683185332ba29faa73201eacc9799528cd2..3cf4a3befd33bf9510448b5f9e71a5b74e048252 100644 --- a/features/videoDetail/src/main/ets/view/SideEpisodes.ets +++ b/features/videoDetail/src/main/ets/view/SideEpisodes.ets @@ -13,15 +13,17 @@ * limitations under the License. */ +import { BusinessError } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; import { display } from '@kit.ArkUI'; -import { BreakpointConstants, CommonConstants } from '@ohos/commons'; +import { CommonConstants, WindowInfo, WindowUtil } from '@ohos/commons'; import { DetailConstants } from '../constants/DetailConstants'; @Component export struct SideEpisodes { - @StorageLink('currentWidthBreakpoint') currentWidthBreakpoint: string = BreakpointConstants.BREAKPOINT_LG; - @StorageLink('currentHeightBreakpoint') currentHeightBreakpoint: string = BreakpointConstants.BREAKPOINT_LG; + @StorageLink('windowUtil') windowUtil: WindowUtil | undefined = undefined; @StorageLink('isFullScreen') isFullScreen: boolean = false; + @ObjectLink mainWindowInfo: WindowInfo; @Link isShowingSideBar: boolean; @Link foldStatus: display.FoldStatus; @@ -39,8 +41,10 @@ export struct SideEpisodes { Blank() } .width(CommonConstants.FULL_PERCENT) - .margin({ bottom: this.currentWidthBreakpoint === BreakpointConstants.BREAKPOINT_SM ? - $r('app.float.side_row_margin_sm') : $r('app.float.side_row_margin') }) + .margin({ + top: $r('app.float.side_row_margin'), + bottom: $r('app.float.side_row_margin') + }) List({ space: CommonConstants.LIST_SPACE }) { ForEach(DetailConstants.PLAYER_EPISODE, (item: string, index: number) => { @@ -48,27 +52,25 @@ export struct SideEpisodes { Row() { Text(item) .fontSize($r('app.float.title_font')) - .fontColor(index === 1 ? $r('app.color.font_selected') : Color.White) + .fontColor(index === 1 ? $r('app.color.font_selected') : $r('app.color.color_white')) .fontWeight(FontWeight.Normal) Image($r('app.media.video_playing')) .height($r('app.float.playing_size')) .width($r('app.float.playing_size')) .position({ - x: this.currentWidthBreakpoint === BreakpointConstants.BREAKPOINT_LG ? - $r('app.float.playing_position_x_side_lg') : $r('app.float.playing_position_x_side'), - y: this.currentWidthBreakpoint === BreakpointConstants.BREAKPOINT_LG ? - $r('app.float.playing_position_y_side_lg') : $r('app.float.playing_position_y_side') + x: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD ? + $r('app.float.playing_position_x_side') : $r('app.float.playing_position_x_side_lg'), + y: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD ? + $r('app.float.playing_position_y_side') : $r('app.float.playing_position_y_side_lg') }) .visibility(index === 1 ? Visibility.Visible : Visibility.None) } .justifyContent(FlexAlign.Center) .borderRadius($r('app.float.episode_row_radius')) .backgroundColor($r('app.color.episode_row_background')) - .width(this.currentWidthBreakpoint === BreakpointConstants.BREAKPOINT_LG ? - $r('app.float.episode_row_width_lg') : $r('app.float.episode_row_width_other')) - .height(this.currentWidthBreakpoint === BreakpointConstants.BREAKPOINT_LG ? - $r('app.float.episode_row_height_lg') : $r('app.float.episode_row_height_other')) + .width(CommonConstants.FULL_PERCENT) + .height(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD ? $r('app.float.episode_row_height_other') : $r('app.float.episode_row_height_lg')) } }, (item: string, index: number) => index + JSON.stringify(item)) } @@ -84,13 +86,46 @@ export struct SideEpisodes { }) } .justifyContent(FlexAlign.Start) - .backgroundColor(Color.Black) - .layoutWeight(this.currentWidthBreakpoint !== BreakpointConstants.BREAKPOINT_LG ? 2 : 0) + .backgroundColor($r('app.color.color_black')) + .layoutWeight(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD ? 2 : 0) .height(CommonConstants.FULL_PERCENT) - .width(this.currentWidthBreakpoint === BreakpointConstants.BREAKPOINT_LG ? $r('app.float.side_col_width') : 0) - .visibility((this.isShowingSideBar && this.isFullScreen && (this.currentWidthBreakpoint === - BreakpointConstants.BREAKPOINT_LG || (this.currentWidthBreakpoint === BreakpointConstants.BREAKPOINT_MD && - this.currentHeightBreakpoint === BreakpointConstants.BREAKPOINT_SM))) ? Visibility.Visible : Visibility.None) + .width(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD ? CommonConstants.FULL_PERCENT : $r('app.float.side_col_width')) + .visibility(this.isShowingSide()) + .padding({ top: this.getTopPadding() }) + } + + isShowingSide(): Visibility { + if (!this.isShowingSideBar || !this.isFullScreen) { + return Visibility.None; + } + if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG && this.mainWindowInfo.heightBp !== HeightBreakpoint.HEIGHT_LG) { + return Visibility.Visible; + } + if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_SM) { + return Visibility.Visible; + } + if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL) { + return Visibility.Visible; + } + return Visibility.None; + } + + getTopPadding() { + let decorHeight: number | undefined = undefined; + let statusHeight: number | undefined = this.mainWindowInfo.AvoidSystem?.topRect.height; + try { + decorHeight = this.windowUtil?.mainWindow.getTitleButtonRect().height; + } catch (error) { + let err = error as BusinessError; + hilog.error(0x0000, 'AllComments', `Failed to get decorHeight, errCode = ${err.code}, errMessage = ${err.message}.`); + } + if (!!decorHeight) { + return decorHeight; + } else if (!!statusHeight) { + return this.getUIContext().px2vp(statusHeight); + } else { + return 0; + } } } diff --git a/features/videoDetail/src/main/ets/view/VideoDetail.ets b/features/videoDetail/src/main/ets/view/VideoDetail.ets index f7022b84bc864c118e22a9acab9363cfe7ef883c..d336dcb793486d33287e641a2fa7c359f96e8aa5 100644 --- a/features/videoDetail/src/main/ets/view/VideoDetail.ets +++ b/features/videoDetail/src/main/ets/view/VideoDetail.ets @@ -16,9 +16,10 @@ /* * 最佳实践: 一多交互事件开发实践,一多断点开发实践 */ - +import { ConfigurationConstant } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; import { KeyCode } from '@kit.InputKit'; -import { deviceInfo } from '@kit.BasicServicesKit'; import { display, window } from '@kit.ArkUI'; import { AvPlayerUtil, @@ -28,7 +29,8 @@ import { BreakpointConstants, CommonConstants, DisplayUtil, - VideoNavPathStack + VideoNavPathStack, + WindowInfo } from '@ohos/commons'; import { SelfComment } from './SelfComment'; import { AllComments } from './AllComments'; @@ -37,44 +39,45 @@ import { DetailConstants } from '../constants/DetailConstants'; @Component export struct VideoDetail { - @StorageLink('currentWidthBreakpoint') currentWidthBreakpoint: string = 'lg'; - @StorageLink('currentHeightBreakpoint') currentHeightBreakpoint: string = 'lg'; - @StorageLink('windowWidth') windowWidth: number = 0; - @StorageLink('isHalfFolded') @Watch('onHalfFoldedChange') isHalfFolded: boolean = false; + @StorageLink('windowUtil') windowUtil: WindowUtil | undefined = undefined; @StorageLink('avplayerState') avplayerState: string = ''; + @StorageLink('isHalfFolded') @Watch('onHalfFoldedChange') isHalfFolded: boolean = false; @StorageLink('isFullScreen') @Watch('onFullScreenChange') isFullScreen: boolean = false; + @ObjectLink mainWindowInfo: WindowInfo; @Consume('pageInfo') pageInfo: VideoNavPathStack; @State commentImgHeight: string = DetailConstants.INITIAL_COMMENT_IMAGE_HEIGHT; @State commentImgWidth: string = DetailConstants.INITIAL_COMMENT_IMAGE_WIDTH; @State relatedVideoHeight: number = DetailConstants.INITIAL_RELATED_VIDEO_HEIGHT; @State videoHeight: number = DetailConstants.INITIAL_VIDEO_HEIGHT; + @State currentYOffset: number = 0; + @State isHidingSelfComment: boolean = false; private avPlayerUtil?: AvPlayerUtil = AvPlayerUtil.getInstance(this.getUIContext()); public screenHeight: number = 0; - private windowUtil?: WindowUtil = WindowUtil.getInstance(); private mainWindow?: window.Window; // [Start on_window_size_change] + // Set rotation for video private onWindowSizeChange: (windowSize: window.Size) => void = (windowSize: window.Size) => { // [StartExclude on_window_size_change] - if (this.pageInfo.getPageName() !== 'videoDetail') { + if (this.pageInfo.getPageName() !== CommonConstants.PAGE_NAMES[1]) { return; } // [EndExclude on_window_size_change] - if (((this.currentWidthBreakpoint === 'md' && this.currentHeightBreakpoint !== 'sm') || - this.currentWidthBreakpoint === 'lg') && !this.isHalfFolded) { - this.windowUtil?.setMainWindowOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED); + if (((this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD && this.mainWindowInfo.heightBp !== HeightBreakpoint.HEIGHT_SM) || + this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL) && !this.isHalfFolded) { + this.windowUtil?.setWindowOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED); } // [StartExclude on_window_size_change] - else if (this.currentWidthBreakpoint === 'md' && this.currentHeightBreakpoint === 'sm') { + else if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_SM) { if (this.isFullScreen) { - this.windowUtil?.setMainWindowOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE_RESTRICTED); + this.windowUtil?.setWindowOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE_RESTRICTED); } else { - this.windowUtil?.setMainWindowOrientation(window.Orientation.PORTRAIT); + this.windowUtil?.setWindowOrientation(window.Orientation.PORTRAIT); } - } else if (this.currentWidthBreakpoint === 'sm' && this.currentHeightBreakpoint === 'lg') { + } else if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_LG) { if (this.isFullScreen) { - this.windowUtil?.setMainWindowOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE_RESTRICTED); + this.windowUtil?.setWindowOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE_RESTRICTED); } else { - this.windowUtil?.setMainWindowOrientation(window.Orientation.PORTRAIT); + this.windowUtil?.setWindowOrientation(window.Orientation.PORTRAIT); } } // [EndExclude on_window_size_change] @@ -82,10 +85,10 @@ export struct VideoDetail { private onHalfFoldedChange(): void { if (this.isHalfFolded) { - this.windowUtil?.setMainWindowOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE_RESTRICTED); + this.windowUtil?.setWindowOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE_RESTRICTED); } else { - if (this.currentWidthBreakpoint === 'md' && this.currentHeightBreakpoint === 'md') { - this.windowUtil?.setMainWindowOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED); + if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_MD) { + this.windowUtil?.setWindowOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED); } } } @@ -93,31 +96,36 @@ export struct VideoDetail { // [Start on_full_screen_change] private onFullScreenChange(): void { // Large folding screen (X series) in unfolded state and tablet state, supporting rotation && large folding screen (X series) in hover state requires landscape display and does not support rotation. - if (((this.currentWidthBreakpoint === 'md' && this.currentHeightBreakpoint !== 'sm') || - this.currentWidthBreakpoint === 'lg') && !this.isHalfFolded) { - this.windowUtil?.setMainWindowOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED); + if (((this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD && this.mainWindowInfo.heightBp !== HeightBreakpoint.HEIGHT_SM) || (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_MD) || + this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL) && !this.isHalfFolded) { + this.windowUtil?.setWindowOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED); } // Phone and large folding screen (X series) in portrait mode. - else if (this.currentWidthBreakpoint === 'sm' && this.currentHeightBreakpoint === 'lg') { + else if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_LG) { // In full-screen mode, the layout is displayed in landscape mode. Otherwise, the layout is displayed in portrait mode. if (this.isFullScreen) { - this.windowUtil?.setMainWindowOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE_RESTRICTED); + this.windowUtil?.setWindowOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE_RESTRICTED); } else { - this.windowUtil?.setMainWindowOrientation(window.Orientation.PORTRAIT); + this.windowUtil?.setWindowOrientation(window.Orientation.PORTRAIT); } } // When the mobile phone and large folding screen (X series) are folded in landscape mode and the playback is not in full screen mode, the vertical display layout is displayed. - else if (this.currentWidthBreakpoint === 'md' && this.currentHeightBreakpoint === 'sm' && !this.isFullScreen) { - this.windowUtil?.setMainWindowOrientation(window.Orientation.PORTRAIT); + else if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_SM && !this.isFullScreen) { + this.windowUtil?.setWindowOrientation(window.Orientation.PORTRAIT); } - // The navigation bar is not hidden on a 2in1 device. - if (deviceInfo.deviceType !== '2in1') { - // The navigation bar is hidden in full-screen playback. Otherwise, the navigation bar is displayed. - if (this.isFullScreen) { - this.windowUtil!.disableWindowSystemBar(); - } else { - this.windowUtil!.enableWindowSystemBar(); - } + + // The navigation bar is hidden in full-screen playback. Otherwise, the navigation bar is displayed. + try { + this.windowUtil!.setSystemBarEnabled(!this.isFullScreen); + // Set the color of the three keys. + let colorMode : ConfigurationConstant.ColorMode = this.isFullScreen ? ConfigurationConstant.ColorMode.COLOR_MODE_DARK : ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET; + let style: window.DecorButtonStyle = { + colorMode: colorMode + }; + this.windowUtil?.mainWindow.setDecorButtonStyle(style); + } catch (error) { + let err = error as BusinessError; + hilog.error(0x0000, 'videoDetail', `Failed to set DecorButtonStyle, errCode = ${err.code}, errMessage = ${err.message}.`); } } // [End on_full_screen_change] @@ -127,10 +135,10 @@ export struct VideoDetail { DisplayUtil.getFoldCreaseRegion(this.getUIContext()); this.screenHeight = DeviceScreen.getDeviceHeight(); // [EndExclude on_window_size_change] - this.mainWindow = this.windowUtil!.getMainWindow(); + this.mainWindow = this.windowUtil!.mainWindow; this.mainWindow?.on('windowSizeChange', this.onWindowSizeChange); - if (this.currentWidthBreakpoint !== 'sm') { - this.windowUtil!.setMainWindowOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED); + if (!(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_LG)) { + this.windowUtil!.setWindowOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED); } } // [End on_window_size_change] @@ -138,12 +146,12 @@ export struct VideoDetail { async aboutToDisappear() { this.isFullScreen = false; this.avPlayerUtil?.offTimeUpdate(); - await this.avPlayerUtil?.release(); + this.avPlayerUtil?.release(); - if (this.currentWidthBreakpoint === 'lg') { - this.windowUtil!.setMainWindowOrientation(window.Orientation.LANDSCAPE); + if (this.mainWindowInfo.heightBp !== HeightBreakpoint.HEIGHT_LG && (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL)) { + this.windowUtil!.setWindowOrientation(window.Orientation.LANDSCAPE); } else { - this.windowUtil!.setMainWindowOrientation(window.Orientation.PORTRAIT); + this.windowUtil!.setWindowOrientation(window.Orientation.PORTRAIT); } } @@ -153,14 +161,16 @@ export struct VideoDetail { columns: { sm: BreakpointConstants.GRID_ROW_COLUMNS[2], md: BreakpointConstants.GRID_ROW_COLUMNS[0], - lg: BreakpointConstants.GRID_ROW_COLUMNS[0] + lg: BreakpointConstants.GRID_ROW_COLUMNS[0], + xl: BreakpointConstants.GRID_ROW_COLUMNS[0] } }) { GridCol({ span: { sm: BreakpointConstants.GRID_COLUMN_SPANS[5], md: BreakpointConstants.GRID_COLUMN_SPANS[0], - lg: BreakpointConstants.GRID_COLUMN_SPANS[0] + lg: BreakpointConstants.GRID_COLUMN_SPANS[0], + xl: BreakpointConstants.GRID_COLUMN_SPANS[0] } }) { // [Start side_bar_container] @@ -168,29 +178,29 @@ export struct VideoDetail { Column() { // Sidebar area. Scroll() { - AllComments({ commentImgHeight: $commentImgHeight, commentImgWidth: $commentImgWidth }) - .visibility(this.currentWidthBreakpoint === 'lg' ? Visibility.Visible : Visibility.None) + AllComments({ commentImgHeight: $commentImgHeight, commentImgWidth: $commentImgWidth, mainWindowInfo: this.mainWindowInfo }) + .visibility(this.mainWindowInfo.heightBp !== HeightBreakpoint.HEIGHT_LG && (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL) ? Visibility.Visible : Visibility.None) } .align(Alignment.Top) .scrollBar(BarState.Off) .layoutWeight(1) - .width('100%') - .padding({ bottom: '12vp' }) + .width(CommonConstants.FULL_PERCENT) + .padding({ bottom: $r('app.float.all_comments_padding_bottom') }) - SelfComment() - .visibility(this.currentWidthBreakpoint === 'lg' ? Visibility.Visible : Visibility.None) + SelfComment({ currentYOffset: this.currentYOffset, mainWindowInfo: this.mainWindowInfo }) + .visibility(this.mainWindowInfo.heightBp !== HeightBreakpoint.HEIGHT_LG && (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL) ? Visibility.Visible : Visibility.None) } .justifyContent(FlexAlign.Start) - .height('100%') - .width('100%') + .height(CommonConstants.FULL_PERCENT) + .width(CommonConstants.FULL_PERCENT) .backgroundColor(Color.White) .onAreaChange((newValue: Area) => { if (newValue.width !== 0) { // Handling when the width of the sidebar changes. - let height: number = 150 + (Number(newValue.width) - 320) / - (this.getUIContext().px2vp(this.windowWidth) * 0.4 - 320) * (182 - 150); - let width: number = 219 + (Number(newValue.width) - 320) / - (this.getUIContext().px2vp(this.windowWidth) * 0.4 - 320) * (266 - 219); + let height: number = 204 + (Number(newValue.width) - 400) / + (this.getUIContext().px2vp(this.mainWindowInfo.windowSize.width) * 0.4 - 400) * (236 - 204); + let width: number = 299 + (Number(newValue.width) - 400) / + (this.getUIContext().px2vp(this.mainWindowInfo.windowSize.width) * 0.4 - 400) * (346 - 299); this.commentImgHeight = JSON.stringify(height); this.commentImgWidth = JSON.stringify(width); } @@ -201,34 +211,37 @@ export struct VideoDetail { VideoDetailView({ screenHeight: this.screenHeight, relatedVideoHeight: this.relatedVideoHeight, - videoHeight: this.videoHeight + videoHeight: this.videoHeight, + currentYOffset: this.currentYOffset, + isHidingSelfComment: this.isHidingSelfComment, + mainWindowInfo: this.mainWindowInfo }) .layoutWeight(1) - SelfComment() - .visibility(this.currentWidthBreakpoint === 'lg' || this.isFullScreen ? Visibility.None : Visibility.Visible) + SelfComment({ currentYOffset: this.currentYOffset, mainWindowInfo: this.mainWindowInfo }) + .visibility((this.mainWindowInfo.heightBp !== HeightBreakpoint.HEIGHT_LG && (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL)) || this.isFullScreen ? Visibility.None : Visibility.Visible) } - .height('100%') - .width('100%') + .height(CommonConstants.FULL_PERCENT) + .width(CommonConstants.FULL_PERCENT) } - .showSideBar(this.currentWidthBreakpoint === 'lg' && !this.isFullScreen ? true : false) + .showSideBar((this.mainWindowInfo.heightBp !== HeightBreakpoint.HEIGHT_LG && (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL)) && !this.isFullScreen ? true : false) .showControlButton(false) .autoHide(false) .sideBarPosition(SideBarPosition.End) .sideBarWidth($r('app.float.side_bar_min_width')) .minSideBarWidth($r('app.float.side_bar_min_width')) - .maxSideBarWidth(this.getUIContext().px2vp(this.windowWidth * 0.4)) + .maxSideBarWidth(this.getUIContext().px2vp(this.mainWindowInfo.windowSize.width * 0.4)) // [End side_bar_container] } - .height('100%') + .height(CommonConstants.FULL_PERCENT) } - .width('100%') - .height('100%') + .width(CommonConstants.FULL_PERCENT) + .height(CommonConstants.FULL_PERCENT) .onBreakpointChange((breakPoints) => { - if (breakPoints !== 'lg' && + if (breakPoints !== BreakpointConstants.BREAKPOINT_LG && breakPoints !== BreakpointConstants.BREAKPOINT_XL && this.videoHeight < DetailConstants.INITIAL_VIDEO_HEIGHT) { this.relatedVideoHeight = 0; - } else if (breakPoints === 'lg') { + } else if (breakPoints === BreakpointConstants.BREAKPOINT_LG || breakPoints === BreakpointConstants.BREAKPOINT_XL) { this.relatedVideoHeight = DetailConstants.INITIAL_RELATED_VIDEO_HEIGHT; } else { Logger.info(`No specific function`); @@ -241,7 +254,7 @@ export struct VideoDetail { this.avPlayerUtil!.playerStateControl(); } if (canIUse('SystemCapability.Window.SessionManager')) { - if (this.currentWidthBreakpoint === 'md' && display.isFoldable()) { + if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD && display.isFoldable()) { this.isHalfFolded = false; } } diff --git a/features/videoDetail/src/main/ets/view/VideoDetailForTv.ets b/features/videoDetail/src/main/ets/view/VideoDetailForTv.ets new file mode 100644 index 0000000000000000000000000000000000000000..4817dd18916f69e8b03241722139ae0fea711baa --- /dev/null +++ b/features/videoDetail/src/main/ets/view/VideoDetailForTv.ets @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023 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 { KeyCode } from '@kit.InputKit'; +import { + WindowUtil, + WindowInfo, CommonConstants, DeviceScreen, + VideoNavPathStack, + AvPlayerUtil +} from '@ohos/commons'; +import { RelatedListForTv } from './RelatedListForTv'; +import { VideoDetailViewForTv } from './VideoDetailViewForTv'; + +@Component +export struct VideoDetailForTv { + @StorageLink('avplayerState') avplayerState: string = ''; + @StorageLink('windowUtil') windowUtil: WindowUtil | undefined = undefined; + @ObjectLink mainWindowInfo: WindowInfo; + @Consume('pageInfo') pageInfo: VideoNavPathStack; + @Provide('isShowingEpisodes') isShowingEpisodes: boolean = false; + @State isPlaying: boolean = false; + private scroller: Scroller = new Scroller(); + private avPlayerUtil?: AvPlayerUtil = AvPlayerUtil.getInstance(this.getUIContext()); + public screenHeight: number = 0; + + aboutToAppear() { + this.screenHeight = DeviceScreen.getDeviceHeight(); + } + + aboutToDisappear() { + this.isPlaying = false; + } + + build() { + NavDestination() { + Scroll(this.scroller) { + Column() { + VideoDetailViewForTv({ screenHeight: this.screenHeight, isPlaying: this.isPlaying }) + RelatedListForTv() + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + } + .width(CommonConstants.FULL_PERCENT) + .justifyContent(FlexAlign.Start) + .layoutWeight(1) + } + .scrollable(ScrollDirection.Vertical) + .scrollBar(BarState.Off) + .height(CommonConstants.FULL_PERCENT) + } + .backgroundColor($r('app.color.video_detail_background_for_tv')) + .hideTitleBar(true) + .onKeyEvent((event?: KeyEvent) => { + // If the key type is pressed, the subsequent code will not be executed, and the specific key logic will be executed when released. + if (!event || event.type !== KeyType.Down) { + return; + } + // Space key controls pause/play. + if (event.keyCode === KeyCode.KEYCODE_DPAD_CENTER) { + this.avPlayerUtil!.playerStateControl(); + } + // Press back to exit. + if (event.keyCode === KeyCode.KEYCODE_BACK) { + this.pageInfo.setPageName(CommonConstants.PAGE_NAMES[0]); + this.pageInfo.pop(); + } + // Right-click fast forward. + if (event.keyCode === KeyCode.KEYCODE_DPAD_RIGHT) { + this.avPlayerUtil!.fastForward(); + } + // Left click to go back quickly. + if (event.keyCode === KeyCode.KEYCODE_DPAD_LEFT) { + this.avPlayerUtil!.rewind(); + } + // Down click to determine whether showing episodes. + if (event.keyCode === KeyCode.KEYCODE_DPAD_DOWN) { + this.isShowingEpisodes = !this.isShowingEpisodes; + } + }) + } +} \ No newline at end of file diff --git a/features/videoDetail/src/main/ets/view/VideoDetailView.ets b/features/videoDetail/src/main/ets/view/VideoDetailView.ets index c3313aa1e43cff930c50f7ddb0bb255301bec9eb..3d12710efbf06e38d1119438e376f84f99c279c5 100644 --- a/features/videoDetail/src/main/ets/view/VideoDetailView.ets +++ b/features/videoDetail/src/main/ets/view/VideoDetailView.ets @@ -16,7 +16,7 @@ import { display } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; -import { BreakpointConstants, CommonConstants } from '@ohos/commons'; +import { CommonConstants, WindowInfo } from '@ohos/commons'; import { Logger } from '@ohos/commons'; import { RelatedList } from './RelatedList'; import { CurrentOffsetUtil } from '../utils/CurrentOffsetUtil'; @@ -27,12 +27,15 @@ import { SideEpisodes } from './SideEpisodes'; @Component export struct VideoDetailView { - @StorageLink('currentWidthBreakpoint') currentWidthBreakpoint: string = BreakpointConstants.BREAKPOINT_LG; @StorageLink('creaseRegion') creaseRegion: number[] = []; @StorageLink('isHalfFolded') isHalfFolded: boolean = false; @StorageLink('isFullScreen') isFullScreen: boolean = false; + @ObjectLink mainWindowInfo: WindowInfo; @Link relatedVideoHeight: number; @Link videoHeight: number; + // The current vertical sliding distance of the list. + @Link currentYOffset: number; + @Link isHidingSelfComment: boolean; @State isShowingSideBar: boolean = false; @State foldStatus: display.FoldStatus | undefined = undefined public screenHeight: number = 0; @@ -57,7 +60,8 @@ export struct VideoDetailView { VideoPlayer({ videoHeight: this.videoHeight, isShowingSideBar: this.isShowingSideBar, - foldStatus: this.foldStatus + foldStatus: this.foldStatus, + mainWindowInfo: this.mainWindowInfo }) Blank() @@ -68,25 +72,30 @@ export struct VideoDetailView { FooterEpisodes({ isShowingSideBar: this.isShowingSideBar, - foldStatus: this.foldStatus + foldStatus: this.foldStatus, + mainWindowInfo: this.mainWindowInfo }) } .layoutWeight(3) SideEpisodes({ isShowingSideBar: this.isShowingSideBar, - foldStatus: this.foldStatus + foldStatus: this.foldStatus, + mainWindowInfo: this.mainWindowInfo }) } .justifyContent(FlexAlign.Start) - .backgroundColor(Color.Black) + .backgroundColor($r('app.color.color_black')) .height(this.isFullScreen ? CommonConstants.FULL_PERCENT : 'auto') .width(CommonConstants.FULL_PERCENT) RelatedList({ relatedVideoHeight: this.relatedVideoHeight, videoHeight: this.videoHeight, - screenHeight: this.screenHeight + screenHeight: this.screenHeight, + currentYOffset: this.currentYOffset, + isHidingSelfComment: this.isHidingSelfComment, + mainWindowInfo: this.mainWindowInfo }) } .width(CommonConstants.FULL_PERCENT) @@ -99,7 +108,7 @@ export struct VideoDetailView { if (this.isFullScreen) { return { offsetRemain: offset }; } - if (this.currentWidthBreakpoint === BreakpointConstants.BREAKPOINT_LG) { + if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL) { if ((offset > 0) && (this.videoHeight > DetailConstants.MIN_VIDEO_PERCENT)) { // Video zoom-out logic. // Percentage of screen height by sliding. @@ -118,6 +127,36 @@ export struct VideoDetailView { } else { Logger.info(`No specific function`); } + } else if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_MD) { + if (offset > 0) { + this.currentYOffset += offset; + this.isHidingSelfComment = true; + if (this.videoHeight > DetailConstants.MIN_VIDEO_PERCENT) { + // Video zoom-out logic. + // Percentage of screen height by sliding. + let offsetPercent = (Math.abs(offset) * DetailConstants.MAX_VIDEO_PERCENT) / this.screenHeight; + // Video shrinkage percentage. + let heightOffset = offsetPercent < this.videoHeight - DetailConstants.MIN_VIDEO_PERCENT ? offsetPercent : + this.videoHeight - DetailConstants.MIN_VIDEO_PERCENT; + this.videoHeight = this.videoHeight - heightOffset; + } + } else if (offset < 0) { + if (this.isHidingSelfComment) { + this.getUIContext().animateTo({ + duration: 300 + }, () => { + this.currentYOffset = 0; + this.isHidingSelfComment = false; + }); + } + if ((this.videoHeight < DetailConstants.MAX_VIDEO_PERCENT) && (CurrentOffsetUtil.scrollToTop(JSON.stringify(this.scroller.currentOffset())))) { + // Video magnification logic. + let offsetPercent = (Math.abs(offset) * DetailConstants.MAX_VIDEO_PERCENT) / this.screenHeight; + let heightOffset = offsetPercent < DetailConstants.MAX_VIDEO_PERCENT - this.videoHeight ? offsetPercent : + DetailConstants.MAX_VIDEO_PERCENT - this.videoHeight; + this.videoHeight = this.videoHeight + heightOffset; + } + } } else { if ((offset > 0) && (this.videoHeight === DetailConstants.MAX_VIDEO_PERCENT) && (this.relatedVideoHeight > 0)) { // Related list shrinking logic. diff --git a/features/videoDetail/src/main/ets/view/VideoDetailViewForTv.ets b/features/videoDetail/src/main/ets/view/VideoDetailViewForTv.ets new file mode 100644 index 0000000000000000000000000000000000000000..aeda1ad13d096706fe4ee102aee439b139140a8a --- /dev/null +++ b/features/videoDetail/src/main/ets/view/VideoDetailViewForTv.ets @@ -0,0 +1,182 @@ +import { CommonConstants, VideoNavPathStack } from "@ohos/commons"; +import { DetailConstants } from "../constants/DetailConstants"; +import { VideoPlayerForTv } from "./VideoPlayerForTv"; + +@Component +export struct VideoDetailViewForTv { + @Consume('pageInfo') pageInfo: VideoNavPathStack; + @Consume('isShowingEpisodes') isShowingEpisodes: boolean; + @Prop screenHeight: number; + @Link isPlaying: boolean; + @State episodesIndex: number = -1; + private episodes: string[] = DetailConstants.PLAYER_EPISODE; + + build() { + Stack() { + this.videoBackground(); + + VideoPlayerForTv({ episodesIndex: this.episodesIndex }) + .visibility(this.isPlaying ? Visibility.Visible : Visibility.None) + + this.titleBar(); + + this.videoDetailContent(); + } + .width(CommonConstants.FULL_PERCENT) + .height(this.isPlaying ? this.screenHeight : this.screenHeight * 0.9) + } + + @Builder + videoBackground() { + Image($r('app.media.video_background_for_tv')) + .width(CommonConstants.FULL_PERCENT) + .height(CommonConstants.FULL_PERCENT) + .objectFit(ImageFit.Cover) + .syncLoad(true) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + } + + @Builder + titleBar() { + Row() { + this.subTitle($r('app.string.released')) + + this.episodesChooseIcon(); + } + .width(CommonConstants.FULL_PERCENT) + .height($r('app.float.video_title_height_for_tv')) + .justifyContent(this.isPlaying ? FlexAlign.SpaceBetween : FlexAlign.Start) + .alignItems(VerticalAlign.Top) + .padding({ + left: $r('app.float.video_title_padding_left_for_tv'), + right: $r('app.float.video_title_padding_right_for_tv'), + top: $r('app.float.video_title_padding_top_for_tv') + }) + .position({ + top: $r('app.float.video_title_bar_position_top') + }) + } + + @Builder + videoDetailContent() { + Column() { + this.videoDescribe(); + this.videoEpisodes(); + } + .position({ bottom: $r('app.float.video_detail_content_position_bottom') }) + } + + @Builder + episodesChooseIcon() { + Row({ space: $r('app.float.episodes_choose_icon_space') }) { + Image($r('app.media.chevron_down_circle')) + .width($r('app.float.episodes_image_width')) + .height($r('app.float.episodes_image_height')) + .fillColor($r('app.color.color_white')) + .opacity(CommonConstants.TEXT_OPACITY[2]) + + Text($r('app.string.anthology')) + .lineHeight($r('app.float.episodes_choose_line_height')) + .fontSize($r('app.float.episodes_choose_font_size')) + .fontWeight(CommonConstants.FONT_WEIGHT_400) + .fontColor($r('app.color.color_white')) + .fontFamily(CommonConstants.FONT_HARMONY_HEITI) + .opacity(CommonConstants.TEXT_OPACITY[2]) + } + .visibility(this.isPlaying ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.isShowingEpisodes = !this.isShowingEpisodes; + }) + } + + @Builder + subTitle(title: ResourceStr) { + Text(title) + .lineHeight($r('app.float.video_title_line_height_for_tv')) + .fontSize($r('app.float.video_title_font_size_for_tv')) + .fontWeight(CommonConstants.FONT_WEIGHT_700) + .fontColor($r('app.color.color_white')) + .fontFamily(CommonConstants.FONT_HARMONY_HEITI) + .opacity(CommonConstants.TEXT_OPACITY[5]) + .margin({ bottom: $r('app.float.video_title_margin_bottom_for_tv') }) + } + + @Builder + videoDescribeContent(content: ResourceStr) { + Text(content) + .lineHeight($r('app.float.introduction_content_line')) + .fontSize($r('app.float.introduction_content_font_for_tv')) + .fontWeight(CommonConstants.FONT_WEIGHT_500) + .fontColor($r('app.color.color_white')) + .fontFamily(CommonConstants.FONT_HARMONY_HEITI) + } + + @Builder + videoDescribe() { + Column() { + this.subTitle($r('app.string.introduction')) + + this.videoDescribeContent($r('app.string.product')) + + this.videoDescribeContent($r('app.string.play')) + } + .width(CommonConstants.FULL_PERCENT) + .padding({ + left: $r('app.float.video_title_padding_left_for_tv'), + bottom: $r('app.float.video_describe_margin_bottom_for_tv') }) + .alignItems(HorizontalAlign.Start) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + } + + @Builder + videoEpisodes() { + Column() { + this.subTitle($r('app.string.anthology')) + + List({ space: CommonConstants.LIST_SPACE }) { + ForEach(this.episodes, (item: string, index: number) => { + ListItem() { + Row() { + Text(item) + .fontSize($r('app.float.episodes_text_font')) + .fontWeight(FontWeight.Normal) + .height($r('app.float.episodes_text_height')) + .fontColor(this.episodesIndex === index ? $r('app.color.font_selected') : Color.White) + + Image($r('app.media.video_playing')) + .height($r('app.float.playing_size')) + .width($r('app.float.playing_size')) + .position({ + left: $r('app.float.playing_position_left_side'), + bottom: $r('app.float.playing_position_bottom_side') + }) + .visibility(this.episodesIndex === index ? Visibility.Visible : Visibility.None) + } + .justifyContent(FlexAlign.Center) + .backgroundColor($r('app.color.episodes_background')) + .height($r('app.float.episodes_list_height')) + .width($r('app.float.episodes_list_width')) + .borderRadius($r('app.float.episodes_list_radius_for_tv')) + .borderWidth($r('app.float.video_episodes_border_for_tv')) + .borderColor($r('app.color.episodes_border_for_tv')) + .onClick(() => { + this.isPlaying = true; + this.episodesIndex = index; + this.isShowingEpisodes = false; + }) + } + }, (item: string, index: number) => index + JSON.stringify(item)) + } + .scrollBar(BarState.Off) + .listDirection(Axis.Horizontal) + .height($r('app.float.episodes_list_height')) + .width(CommonConstants.FULL_PERCENT) + } + .width(CommonConstants.FULL_PERCENT) + .padding({ + left: $r('app.float.video_title_padding_left_for_tv'), + bottom: $r('app.float.video_describe_margin_bottom_for_tv') }) + .alignItems(HorizontalAlign.Start) + .visibility(this.isPlaying ? (this.isShowingEpisodes ? Visibility.Visible: Visibility.None) : Visibility.Visible) + } +} \ No newline at end of file diff --git a/features/videoDetail/src/main/ets/view/VideoPlayer.ets b/features/videoDetail/src/main/ets/view/VideoPlayer.ets index 7fa2e8a0abdc206eb60860139004bca352789b5e..387699ceb66c63c9fc39dc7875d4749461a3ade8 100644 --- a/features/videoDetail/src/main/ets/view/VideoPlayer.ets +++ b/features/videoDetail/src/main/ets/view/VideoPlayer.ets @@ -13,17 +13,21 @@ * limitations under the License. */ -import { display } from '@kit.ArkUI'; +import { display, window } from '@kit.ArkUI'; import { deviceInfo } from '@kit.BasicServicesKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; -import { AvPlayerUtil, BreakpointConstants, CommonConstants, VideoNavPathStack, WindowUtil } from '@ohos/commons'; +import { AvPlayerUtil, BreakpointConstants, CommonConstants, + ImmersiveType, + VideoNavPathStack, + WindowInfo, + WindowUtil } from '@ohos/commons'; import { DetailConstants } from '../constants/DetailConstants'; +import { ConfigurationConstant } from '@kit.AbilityKit'; @Component export struct VideoPlayer { - @StorageLink('currentWidthBreakpoint') currentWidthBreakpoint: string = 'lg'; - @StorageLink('currentHeightBreakpoint') currentHeightBreakpoint: string = 'lg'; + @StorageLink('windowUtil') windowUtil: WindowUtil | undefined = undefined; @StorageLink('currentTime') currentTime: string = CommonConstants.INITIAL_TIME; @StorageLink('totalTime') totalTime: string = CommonConstants.INITIAL_TIME; @StorageLink('isHalfFolded') isHalfFolded: boolean = false; @@ -31,11 +35,11 @@ export struct VideoPlayer { @StorageLink('progress') progress: number = 0; @StorageLink('avplayerState') avplayerState: string = ''; @StorageLink('isFullScreen') isFullScreen: boolean = false; + @ObjectLink mainWindowInfo: WindowInfo; @Link videoHeight: number; @Link isShowingSideBar: boolean; @Link foldStatus: display.FoldStatus; @Consume('pageInfo') pageInfo: VideoNavPathStack; - private windowUtil?: WindowUtil = WindowUtil.getInstance(); private avPlayerUtil?: AvPlayerUtil; private xComponentController: XComponentController = new XComponentController(); private onFoldStatusChange: Callback = (data: display.FoldStatus) => { @@ -43,7 +47,7 @@ export struct VideoPlayer { if (canIUse('SystemCapability.Window.SessionManager')) { if (data === display.FoldStatus.FOLD_STATUS_HALF_FOLDED) { let orientation: display.Orientation = display.getDefaultDisplaySync().orientation; - if (orientation === display.Orientation.LANDSCAPE || orientation === display.Orientation.LANDSCAPE_INVERTED) { + if ((orientation === display.Orientation.LANDSCAPE || orientation === display.Orientation.LANDSCAPE_INVERTED) && this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD) { this.isHalfFolded = true; // Full-screen playback. if (!this.isFullScreen) { @@ -109,18 +113,18 @@ export struct VideoPlayer { AppStorage.setOrCreate('detailSurfaceId', this.xComponentController.getXComponentSurfaceId()); }) .width(this.isFullScreen ? -1 : this.videoHeight + '%') - .aspectRatio(1.78) + .aspectRatio(CommonConstants.VIDEO_ASPECT_RATIO) // [EndExclude stack_aligncontent] // [EndExclude stack_isfullscreen] // [EndExclude click_interaction] } .justifyContent(FlexAlign.Center) - .height(this.isHalfFolded ? this.creaseRegion[0] : (this.isFullScreen ? '100%' : 'auto')) - .width('100%') + .height(this.isHalfFolded ? this.creaseRegion[0] : (this.isFullScreen ? CommonConstants.FULL_PERCENT : 'auto')) + .width(CommonConstants.FULL_PERCENT) // [StartExclude stack_isfullscreen] } - .width('100%') + .width(CommonConstants.FULL_PERCENT) .onClick(() => { // [StartExclude click_interaction] if (this.isShowingSideBar) { @@ -134,9 +138,10 @@ export struct VideoPlayer { .priorityGesture( TapGesture({ count: 2 }) .onAction((event: GestureEvent) => { - if (event && deviceInfo.deviceType === '2in1') { + if (event && (this.mainWindowInfo.windowStatusType === window.WindowStatusType.FLOATING || this.mainWindowInfo.windowStatusType === window.WindowStatusType.MAXIMIZE)) { this.isFullScreen = true; - this.windowUtil!.maximize(); + this.windowUtil?.setImmersiveType(ImmersiveType.FULLSCREEN_IMMERSIVE); + // this.windowUtil!.maximize(); } }) ) @@ -148,8 +153,8 @@ export struct VideoPlayer { Row() { TimeText({ time: this.currentTime }) .margin({ - left: '36vp', - right: '2vp' + left: $r('app.float.current_time_text_left'), + right: $r('app.float.current_time_text_right') }) Slider({ @@ -162,25 +167,25 @@ export struct VideoPlayer { this.avPlayerUtil?.sliderChange(value, mode); }) .layoutWeight(1) - .selectedColor('#ED6F21') + .selectedColor($r('app.color.time_slider_select_color')) TimeText({ time: this.totalTime }) .margin({ - left: '2vp', - right: '36vp' + left: $r('app.float.total_time_text_left'), + right: $r('app.float.total_time_text_right') }) } - .width('100%') - .height('40vp') + .width(CommonConstants.FULL_PERCENT) + .height($r('app.float.time_row_height')) .alignItems(VerticalAlign.Center) .visibility(this.isFullScreen ? Visibility.Visible : Visibility.None) Row() { Row() { Image(this.avplayerState === 'playing' ? $r('app.media.ic_public_pause') : $r('app.media.ic_public_play')) - .height('24vp') - .width('24vp') - .margin({ left: '24vp' }) + .height($r('app.float.icon_size')) + .width($r('app.float.icon_size')) + .margin({ left: $r('app.float.icon_margin') }) .onClick(() => { this.avPlayerUtil?.playerStateControl(); }) @@ -188,41 +193,42 @@ export struct VideoPlayer { ImgIcon({ img: $r('app.media.ic_public_view_list_white') }) } .margin({ - top: this.currentWidthBreakpoint === 'sm' ? '0' : '14vp', - bottom: this.currentWidthBreakpoint === 'sm' ? '15vp' : '24vp' + top: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.icon_row_top_sm') : $r('app.float.icon_row_top'), + bottom: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.icon_row_bottom_sm') : $r('app.float.icon_row_bottom') }) Blank() Row() { - TextButton({ content: '选集' }) + TextButton({ content: $r('app.string.selections') }) .onClick(() => { this.isShowingSideBar = !this.isShowingSideBar; }) - TextButton({ content: '超清' }) - TextButton({ content: '超清' }) + TextButton({ content: $r('app.string.super') }) + TextButton({ content: $r('app.string.super') }) } .margin({ - top: this.currentWidthBreakpoint === 'sm' ? '4vp' : '16vp', - bottom: this.currentWidthBreakpoint === 'sm' ? '19vp' : '28vp' + top: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.button_row_top_sm') : $r('app.float.button_row_top'), + bottom: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? $r('app.float.button_row_bottom_sm') : $r('app.float.button_row_bottom') }) } - .height('60vp') - .width('100%') + .height($r('app.float.icon_button_row_height')) + .width(CommonConstants.FULL_PERCENT) .visibility(this.isFullScreen ? Visibility.Visible : Visibility.None) // [EndExclude stack_aligncontent] // [EndExclude stack_isfullscreen] } - .height(this.isFullScreen ? '100%' : 'auto') - .width('100%') + .height(this.isFullScreen ? CommonConstants.FULL_PERCENT : 'auto') + .width(CommonConstants.FULL_PERCENT) .justifyContent(FlexAlign.End) .visibility(this.isFullScreen && !this.isShowingSideBar ? Visibility.Visible : Visibility.None) .priorityGesture( TapGesture({ count: 2 }) .onAction((event: GestureEvent) => { - if (event && deviceInfo.deviceType === '2in1') { - this.windowUtil!.maximize(); + if (event && this.mainWindowInfo.windowStatusType === window.WindowStatusType.FLOATING || this.mainWindowInfo.windowStatusType === window.WindowStatusType.MAXIMIZE) { + // this.windowUtil!.maximize(); + this.windowUtil?.setImmersiveType(ImmersiveType.FULLSCREEN_IMMERSIVE); this.windowUtil!.recover(); } }) @@ -233,8 +239,8 @@ export struct VideoPlayer { Row() { TimeText({ time: this.currentTime }) .margin({ - left: '24vp', - right: '2vp' + left: $r('app.float.current_time_text_left'), + right: $r('app.float.current_time_text_right') }) Slider({ @@ -247,46 +253,46 @@ export struct VideoPlayer { this.avPlayerUtil?.sliderChange(value, mode); }) .layoutWeight(1) - .selectedColor('#ED6F21') + .selectedColor($r('app.color.time_slider_select_color')) TimeText({ time: this.totalTime }) .margin({ - left: '2vp', - right: '8vp' + left: $r('app.float.total_time_text_left'), + right: $r('app.float.total_time_text_right_full') }) Image($r('app.media.ic_public_enlarge')) - .height('24vp') - .width('24vp') - .margin({ right: '24vp' }) - .fillColor(Color.White) + .height($r('app.float.enlarge_size')) + .width($r('app.float.enlarge_size')) + .margin({ right: $r('app.float.enlarge_margin') }) + .fillColor($r('app.color.color_white')) .onClick(() => { this.isFullScreen = true; }) Image($r('app.media.ic_public_fullscreen')) - .height('24vp') - .width('24vp') - .margin({ right: '24vp' }) - .fillColor(Color.White) - .visibility(deviceInfo.deviceType === '2in1' ? Visibility.Visible : Visibility.None) + .height($r('app.float.full_screen_size')) + .width($r('app.float.full_screen_size')) + .margin({ right: $r('app.float.full_screen_margin') }) + .fillColor($r('app.color.color_white')) + .visibility(this.mainWindowInfo.windowStatusType === window.WindowStatusType.FLOATING || this.mainWindowInfo.windowStatusType === window.WindowStatusType.MAXIMIZE ? Visibility.Visible : Visibility.None) .onClick(() => { this.isFullScreen = true; - this.windowUtil!.maximize(); + this.windowUtil?.setImmersiveType(ImmersiveType.FULLSCREEN_IMMERSIVE); }) } - .width('100%') - .height('40vp') + .width(CommonConstants.FULL_PERCENT) + .height($r('app.float.time_row_height')) .alignItems(VerticalAlign.Center) .visibility(!this.isFullScreen ? Visibility.Visible : Visibility.None) // [EndExclude stack_aligncontent] // [EndExclude stack_isfullscreen] Image($r('app.media.ic_public_back')) - .height('24vp') - .width('24vp') + .height($r('app.float.back_size')) + .width($r('app.float.back_size')) .position({ - x: this.currentWidthBreakpoint === 'lg' ? '24vp' : '32vp', + x: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_XL ? $r('app.float.back_position_x_lg_xl') : $r('app.float.back_position_x'), y: $r('app.float.back_position_y') }) .fillColor(Color.White) @@ -297,15 +303,15 @@ export struct VideoPlayer { if (this.isFullScreen) { this.isFullScreen = false; } else { - this.pageInfo.setPageName('home'); + this.pageInfo.setPageName(CommonConstants.PAGE_NAMES[0]); this.pageInfo.pop(); } }) } - .height('auto') + .height(this.isFullScreen && this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM && this.mainWindowInfo.heightBp === HeightBreakpoint.HEIGHT_MD && this.isShowingSideBar ? $r('app.float.video_player_height') : 'auto') .layoutWeight(this.isFullScreen ? 1 : 0) - .width('100%') - .backgroundColor(Color.Black) + .width(CommonConstants.FULL_PERCENT) + .backgroundColor($r('app.color.color_black')) .focusable(false) // [End stack_isfullscreen] diff --git a/features/videoDetail/src/main/ets/view/VideoPlayerForTv.ets b/features/videoDetail/src/main/ets/view/VideoPlayerForTv.ets new file mode 100644 index 0000000000000000000000000000000000000000..3c77dc6b8d1b5d93c6fba4e90a7606988227140b --- /dev/null +++ b/features/videoDetail/src/main/ets/view/VideoPlayerForTv.ets @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2023 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 { AvPlayerUtil, CommonConstants, WindowUtil } from '@ohos/commons'; + +@Component +export struct VideoPlayerForTv { + @StorageLink('currentTime') currentTime: string = CommonConstants.INITIAL_TIME; + @StorageLink('totalTime') totalTime: string = CommonConstants.INITIAL_TIME; + @StorageLink('progress') progress: number = 0; + @StorageLink('avplayerState') avplayerState: string = ''; + @StorageLink('windowUtil') windowUtil: WindowUtil | undefined = undefined; + @Link @Watch('episodesIndexChange') episodesIndex: number; + @Consume('isShowingEpisodes') isShowingEpisodes: boolean; + private avPlayerUtil?: AvPlayerUtil; + private xComponentController: XComponentController = new XComponentController(); + + aboutToAppear(): void { + this.avPlayerUtil = AvPlayerUtil.getInstance(this.getUIContext()); + } + + aboutToDisappear(): void { + this.avPlayerUtil?.offTimeUpdate(); + this.avPlayerUtil?.release(); + } + + episodesIndexChange() { + this.avPlayerUtil?.offTimeUpdate(); + this.avPlayerUtil?.release(); + this.avPlayerUtil?.createAvPlayer(this.xComponentController.getXComponentSurfaceId()); + AppStorage.setOrCreate('detailSurfaceId', this.xComponentController.getXComponentSurfaceId()); + } + + build() { + Stack({ alignContent: Alignment.Center }) { + Column() { + XComponent({ + id: 'videoForTV', + type: XComponentType.SURFACE, + controller: this.xComponentController + }) + .width(-1) + .aspectRatio(CommonConstants.VIDEO_ASPECT_RATIO) + } + .justifyContent(FlexAlign.Center) + .height(CommonConstants.FULL_PERCENT) + .width(CommonConstants.FULL_PERCENT) + .onClick(() => { + this.avPlayerUtil?.playerStateControl(); + }) + + Row() { + TimeText({ time: this.currentTime }) + .margin({ + left: '56vp', + right: '12vp' + }) + + Slider({ + min: 0, + max: 100, + step: 1, + value: this.progress + }) + .onChange((value: number, mode: SliderChangeMode) => { + this.avPlayerUtil?.sliderChange(value, mode); + }) + .layoutWeight(1) + .selectedColor('#ED6F21') + + TimeText({ time: this.totalTime }) + .margin({ + left: '12vp', + right: '56vp' + }) + } + .width('100%') + .height('32vp') + .alignItems(VerticalAlign.Center) + .position({ + bottom: '36vp' + }) + .visibility(this.isShowingEpisodes ? Visibility.None : Visibility.Visible) + } + .height('auto') + .layoutWeight(1) + .width('100%') + .backgroundColor(Color.Black) + .focusable(false) + } +} + +@Component +struct TimeText { + @Link time: string; + + build() { + Text(this.time) + .fontSize($r('app.float.time_font_for_tv')) + .fontColor(Color.White) + .lineHeight($r('app.float.time_text_line_for_tv')) + .width($r('app.float.time_text_width_for_tv')) + } +} \ No newline at end of file diff --git a/features/videoDetail/src/main/resources/base/element/color.json b/features/videoDetail/src/main/resources/base/element/color.json index ab978fc369b3773bbacdc41391fcaa76fadcdb01..2d5a70b77dc5226284b7ad8bdf83dd0c4d14e884 100644 --- a/features/videoDetail/src/main/resources/base/element/color.json +++ b/features/videoDetail/src/main/resources/base/element/color.json @@ -8,6 +8,10 @@ "name": "episodes_background", "value": "#0D000000" }, + { + "name": "episodes_border_for_tv", + "value": "#26FFFFFF" + }, { "name": "shadow_color", "value": "#2600001E" @@ -23,6 +27,30 @@ { "name": "font_selected", "value": "#ED6F21" + }, + { + "name": "color_black", + "value": "#000000" + }, + { + "name": "color_white", + "value": "#FFFFFF" + }, + { + "name": "related_choose_left_for_tv", + "value": "#FFFFFFFF" + }, + { + "name": "related_choose_right_for_tv", + "value": "#1AFFFFFF" + }, + { + "name": "video_detail_background_for_tv", + "value": "#FF202224" + }, + { + "name": "time_slider_select_color", + "value": "#ED6F21" } ] } \ No newline at end of file diff --git a/features/videoDetail/src/main/resources/base/element/float.json b/features/videoDetail/src/main/resources/base/element/float.json index 9f11426850dd10679c67b0f8767fdc929d30e625..eb5481f1a8f96af7333307ccddf270624ebe604c 100644 --- a/features/videoDetail/src/main/resources/base/element/float.json +++ b/features/videoDetail/src/main/resources/base/element/float.json @@ -6,7 +6,7 @@ }, { "name": "side_bar_min_width", - "value": "320vp" + "value": "400vp" }, { "name": "all_comments_title_text_font", @@ -132,14 +132,46 @@ "name": "related_img_height", "value": "76vp" }, + { + "name": "related_img_height_for_tv", + "value": "176vp" + }, + { + "name": "related_img_background_height_for_tv", + "value": "180vp" + }, + { + "name": "related_img_border_height_for_tv", + "value": "184vp" + }, { "name": "related_img_width", "value": "135vp" }, + { + "name": "related_img_width_for_tv", + "value": "312vp" + }, + { + "name": "related_img_background_width_for_tv", + "value": "316vp" + }, + { + "name": "related_img_border_width_for_tv", + "value": "320vp" + }, { "name": "related_img_radius", "value": "8vp" }, + { + "name": "related_img_radius_for_tv", + "value": "20vp" + }, + { + "name": "related_img_border_radius_for_tv", + "value": "22vp" + }, { "name": "related_img_margin", "value": "8vp" @@ -148,10 +180,18 @@ "name": "related_name_font", "value": "14fp" }, + { + "name": "related_name_font_for_tv", + "value": "16fp" + }, { "name": "related_name_line", "value": "19vp" }, + { + "name": "related_name_line_for_tv", + "value": "21vp" + }, { "name": "related_list_top", "value": "8vp" @@ -164,6 +204,10 @@ "name": "related_row_left", "value": "24vp" }, + { + "name": "related_row_left_for_tv", + "value": "56vp" + }, { "name": "related_row_bottom", "value": "10vp" @@ -172,10 +216,18 @@ "name": "related_row_height", "value": "121vp" }, + { + "name": "related_row_height_for_tv", + "value": "205vp" + }, { "name": "introduction_content_font", "value": "14fp" }, + { + "name": "introduction_content_font_for_tv", + "value": "18fp" + }, { "name": "introduction_content_line", "value": "28vp" @@ -220,6 +272,10 @@ "name": "episodes_list_radius", "value": "8vp" }, + { + "name": "episodes_list_radius_for_tv", + "value": "12vp" + }, { "name": "episodes_list_margin", "value": "12vp" @@ -228,10 +284,34 @@ "name": "peripheral_img_height", "value": "230vp" }, + { + "name": "peripheral_img_height_for_tv", + "value": "299vp" + }, + { + "name": "peripheral_img_background_height_for_tv", + "value": "303vp" + }, + { + "name": "peripheral_img_border_height_for_tv", + "value": "307vp" + }, { "name": "peripheral_img_width", "value": "164vp" }, + { + "name": "peripheral_img_width_for_tv", + "value": "213vp" + }, + { + "name": "peripheral_img_background_width_for_tv", + "value": "217vp" + }, + { + "name": "peripheral_img_border_width_for_tv", + "value": "221vp" + }, { "name": "peripheral_img_margin", "value": "4vp" @@ -332,14 +412,26 @@ "name": "time_font", "value": "9fp" }, + { + "name": "time_font_for_tv", + "value": "12fp" + }, { "name": "time_text_line", "value": "12vp" }, + { + "name": "time_text_line_for_tv", + "value": "16vp" + }, { "name": "time_text_width", "value": "38vp" }, + { + "name": "time_text_width_for_tv", + "value": "50vp" + }, { "name": "current_time_text_left", "value": "24vp" @@ -354,6 +446,10 @@ }, { "name": "total_time_text_right", + "value": "24vp" + }, + { + "name": "total_time_text_right_full", "value": "8vp" }, { @@ -364,6 +460,14 @@ "name": "enlarge_margin", "value": "24vp" }, + { + "name": "full_screen_size", + "value": "24vp" + }, + { + "name": "full_screen_margin", + "value": "24vp" + }, { "name": "time_row_height", "value": "40vp" @@ -377,7 +481,7 @@ "value": "24vp" }, { - "name": "back_position_x_lg", + "name": "back_position_x_lg_xl", "value": "32vp" }, { @@ -412,6 +516,10 @@ "name": "icon_margin", "value": "24vp" }, + { + "name": "icon_row_top_sm", + "value": "14vp" + }, { "name": "icon_row_top", "value": "14vp" @@ -456,10 +564,18 @@ "name": "title_selected_font", "value": "24fp" }, + { + "name": "title_selected_font_sm", + "value": "16fp" + }, { "name": "title_selected_line", "value": "33vp" }, + { + "name": "title_selected_line_sm", + "value": "32vp" + }, { "name": "title_selected_width", "value": "48vp" @@ -468,6 +584,10 @@ "name": "title_selected_margin", "value": "24vp" }, + { + "name": "title_selected_margin_sm", + "value": "36vp" + }, { "name": "title_font", "value": "18fp" @@ -484,14 +604,26 @@ "name": "title_row_top", "value": "25vp" }, + { + "name": "title_row_top_sm", + "value": "8vp" + }, { "name": "title_row_bottom", "value": "12vp" }, + { + "name": "title_row_bottom_sm", + "value": "8vp" + }, { "name": "title_row_height", "value": "56vp" }, + { + "name": "title_row_height_sm", + "value": "32vp" + }, { "name": "playing_size", "value": "10vp" @@ -504,6 +636,10 @@ "name": "playing_position_y", "value": "36vp" }, + { + "name": "playing_position_y_sm", + "value": "26vp" + }, { "name": "episode_row_radius", "value": "8vp" @@ -512,6 +648,10 @@ "name": "episode_row_height", "value": "54vp" }, + { + "name": "episode_row_height_sm", + "value": "44vp" + }, { "name": "episode_list_bottom", "value": "12vp" @@ -524,6 +664,10 @@ "name": "episode_col_padding", "value": "24vp" }, + { + "name": "episode_col_padding_sm", + "value": "12vp" + }, { "name": "side_title_width_1", "value": "65vp" @@ -560,6 +704,14 @@ "name": "playing_position_y_side", "value": "28vp" }, + { + "name": "playing_position_left_side", + "value": "8vp" + }, + { + "name": "playing_position_bottom_side", + "value": "8vp" + }, { "name": "episode_row_width_lg", "value": "69vp" @@ -590,7 +742,7 @@ }, { "name": "side_col_width", - "value": "360vp" + "value": "400vp" }, { "name": "side_text_font", @@ -599,6 +751,142 @@ { "name": "side_text_line", "value": "32vp" + }, + { + "name": "video_title_line_height_for_tv", + "value": "35vp" + }, + { + "name": "video_title_font_size_for_tv", + "value": "26fp" + }, + { + "name": "video_title_height_for_tv", + "value": "86vp" + }, + { + "name": "video_title_padding_left_for_tv", + "value": "56vp" + }, + { + "name": "video_title_padding_right_for_tv", + "value": "56vp" + }, + { + "name": "video_title_padding_top_for_tv", + "value": "25vp" + }, + { + "name": "video_title_padding_bottom_for_tv", + "value": "25vp" + }, + { + "name": "video_title_margin_bottom_for_tv", + "value": "16vp" + }, + { + "name": "video_describe_margin_bottom_for_tv", + "value": "35vp" + }, + { + "name": "video_describe_content_padding", + "value": "24vp" + }, + { + "name": "video_episodes_border_for_tv", + "value": "1vp" + }, + { + "name": "episodes_choose_line_height", + "value": "32vp" + }, + { + "name": "episodes_choose_font_size", + "value": "24fp" + }, + { + "name": "episodes_choose_icon_space", + "value": "12vp" + }, + { + "name": "related_video_title_font_size", + "value": "26fp" + }, + { + "name": "related_video_title_line_height", + "value": "35vp" + }, + { + "name": "related_video_title_margin_top", + "value": "35vp" + }, + { + "name": "related_video_title_margin_bottom", + "value": "12vp" + }, + { + "name": "related_video_title_margin_left", + "value": "56vp" + }, + { + "name": "related_video_image_margin", + "value": "4vp" + }, + { + "name": "comment_image_border_radius", + "value": "8vp" + }, + { + "name": "comment_image_aspect_ratio", + "value": "8vp" + }, + { + "name": "all_comments_padding_bottom", + "value": "12vp" + }, + { + "name": "video_title_bar_position_top", + "value": "0vp" + }, + { + "name": "video_detail_content_position_bottom", + "value": "0vp" + }, + { + "name": "episodes_image_height", + "value": "32vp" + }, + { + "name": "episodes_image_width", + "value": "32vp" + }, + { + "name": "time_text_total_margin_right_full", + "value": "8vp" + }, + { + "name": "video_play_select_margin_top_sm", + "value": "4vp" + }, + { + "name": "video_play_select_margin_top", + "value": "16vp" + }, + { + "name": "video_play_select_margin_bottom_sm", + "value": "19vp" + }, + { + "name": "video_play_select_margin_bottom", + "value": "28vp" + }, + { + "name": "video_play_area_height", + "value": "60vp" + }, + { + "name": "video_player_height", + "value": "130vp" } ] } \ No newline at end of file diff --git a/features/videoDetail/src/main/resources/base/media/arrow_down.svg b/features/videoDetail/src/main/resources/base/media/arrow_down.svg new file mode 100644 index 0000000000000000000000000000000000000000..d0a26e31f5cb0930efecbd1e9b8f85e55b8f42e8 --- /dev/null +++ b/features/videoDetail/src/main/resources/base/media/arrow_down.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/features/videoDetail/src/main/resources/base/media/arrowshape_turn_up_right.svg b/features/videoDetail/src/main/resources/base/media/arrowshape_turn_up_right.svg new file mode 100644 index 0000000000000000000000000000000000000000..bf8d7512db2564f2895733dff83fa1c3ebb87904 --- /dev/null +++ b/features/videoDetail/src/main/resources/base/media/arrowshape_turn_up_right.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/features/videoDetail/src/main/resources/base/media/chevron_down_circle.svg b/features/videoDetail/src/main/resources/base/media/chevron_down_circle.svg new file mode 100644 index 0000000000000000000000000000000000000000..f798c73c51bdb8a86f0fc5d9f4cf5426b196ecd1 --- /dev/null +++ b/features/videoDetail/src/main/resources/base/media/chevron_down_circle.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/features/videoDetail/src/main/resources/base/media/ellipsis_message.svg b/features/videoDetail/src/main/resources/base/media/ellipsis_message.svg new file mode 100644 index 0000000000000000000000000000000000000000..c1880513bd4e1ab9fbe01646032618bd281736d5 --- /dev/null +++ b/features/videoDetail/src/main/resources/base/media/ellipsis_message.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/features/videoDetail/src/main/resources/base/media/hand_thumbsup.svg b/features/videoDetail/src/main/resources/base/media/hand_thumbsup.svg new file mode 100644 index 0000000000000000000000000000000000000000..a0b97eeb25d04581715923f7eb225e23da169fcb --- /dev/null +++ b/features/videoDetail/src/main/resources/base/media/hand_thumbsup.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/features/videoDetail/src/main/resources/base/media/heart.svg b/features/videoDetail/src/main/resources/base/media/heart.svg new file mode 100644 index 0000000000000000000000000000000000000000..fa899b0fcfceec7178e51a6d90a4acc0c49683d3 --- /dev/null +++ b/features/videoDetail/src/main/resources/base/media/heart.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/features/videoDetail/src/main/resources/base/media/video_background_for_tv.png b/features/videoDetail/src/main/resources/base/media/video_background_for_tv.png new file mode 100644 index 0000000000000000000000000000000000000000..8d37d7b441be77c6154454f815f0a44cac0657e6 Binary files /dev/null and b/features/videoDetail/src/main/resources/base/media/video_background_for_tv.png differ diff --git a/products/phone/src/main/ets/entryability/EntryAbility.ets b/products/phone/src/main/ets/entryability/EntryAbility.ets index 05c32337132c71d1b69a6a73bedb7c2e00ec7668..23342d6d7fc17c6272254d480f8fa63ce8cad510 100644 --- a/products/phone/src/main/ets/entryability/EntryAbility.ets +++ b/products/phone/src/main/ets/entryability/EntryAbility.ets @@ -21,20 +21,12 @@ import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; import { display, window } from '@kit.ArkUI'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { BusinessError } from '@kit.BasicServicesKit'; -import { WindowUtil } from '@ohos/commons'; +import { ImmersiveType, WindowUtil } from '@ohos/commons'; // [Start multi_entryAbility] export default class EntryAbility extends UIAbility { - // [Start EntryAbility] - private windowObj?: window.Window; - private windowUtil?: WindowUtil = WindowUtil.getInstance(); - private onWindowSizeChange: (windowSize: window.Size) => void = (windowSize: window.Size) => { - this.windowUtil!.updateHeightBp(); - this.windowUtil!.updateWidthBp(); - // [StartExclude EntryAbility] - AppStorage.setOrCreate('windowWidth', windowSize.width); - // [EndExclude EntryAbility] - }; + // private windowUtil?: WindowUtil = WindowUtil.getInstance(); + windowUtil?: WindowUtil; // [StartExclude multi_entryAbility] // [StartExclude EntryAbility] @@ -52,29 +44,15 @@ export default class EntryAbility extends UIAbility { // [StartExclude EntryAbility] // Main window is created, set main page for this ability hilog.info(0x0000, 'EntryAbility', '%{public}s', 'Ability onWindowStageCreate'); - // [EndExclude EntryAbility] - this.windowUtil!.setWindowStage(windowStage); - // [Start window_util_set] - let windowUtil: WindowUtil | undefined = WindowUtil.getInstance(); - if (windowUtil !== undefined) { - windowUtil.setWindowStage(windowStage); - // windowUtil.setMainWindowPortrait(); + + try { + this.windowUtil = new WindowUtil(windowStage.getMainWindowSync()); + } catch (error) { + let err = error as BusinessError; + hilog.error(0x0000, 'TestLog', `Failed to get main window. Code: ${err.code}, message: ${err.message}`); } + AppStorage.setOrCreate('windowUtil', this.windowUtil); // [End window_util_set] - windowStage.getMainWindow().then((data: window.Window) => { - this.windowObj = data; - this.windowUtil!.updateWidthBp(); - this.windowUtil!.updateHeightBp(); - try { - AppStorage.setOrCreate('windowWidth', data.getWindowProperties().windowRect.width); - } catch (error) { - let err = error as BusinessError; - hilog.error(0x00, 'EntryAbility', `getWindowProperties failed, code = ${err.code}, message = ${err.message}`); - } - this.windowObj.on('windowSizeChange', this.onWindowSizeChange); - }).catch((error: BusinessError) => { - hilog.error(0x00, 'EntryAbility', `getWindowProperties failed, code = ${error.code}, message = ${error.message}`); - }) // [StartExclude EntryAbility] // [StartExclude multi_entryAbility] windowStage.loadContent('pages/Index', (err, data) => { @@ -84,6 +62,9 @@ export default class EntryAbility extends UIAbility { } hilog.info(0x0000, 'EntryAbility', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); + this.windowUtil!.setUIContext(); + this.windowUtil!.setImmersiveType(ImmersiveType.IMMERSIVE); + this.windowUtil!.updateWindowInfo(); }); // [EndExclude EntryAbility] // [EndExclude multi_entryAbility] @@ -107,42 +88,6 @@ export default class EntryAbility extends UIAbility { hilog.info(0x0000, 'EntryAbility', '%{public}s', 'Ability onBackground'); } - // [Start update_width_bp] - // products/phone/src/main/ets/entryability/EntryAbility.ets - private updateWidthBp(): void { - if (this.windowObj === undefined) { - return; - } - try { - let mainWindow: window.WindowProperties = this.windowObj.getWindowProperties(); - let windowWidth: number = mainWindow.windowRect.width; - let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels; - let widthBp: string = ''; - let videoGridColumn: string = '1fr 1fr'; - if (windowWidthVp < 320) { - widthBp = 'xs'; - videoGridColumn = '1fr 1fr'; - } else if (windowWidthVp >= 320 && windowWidthVp < 600) { - widthBp = 'sm'; - videoGridColumn = '1fr 1fr'; - } else if (windowWidthVp >= 600 && windowWidthVp < 840) { - widthBp = 'md'; - videoGridColumn = '1fr 1fr 1fr'; - } else if (windowWidthVp >= 840 && windowWidthVp < 1440) { - widthBp = 'lg'; - videoGridColumn = '1fr 1fr 1fr 1fr'; - } else { - widthBp = 'xl'; - videoGridColumn = '1fr 1fr 1fr 1fr'; - } - AppStorage.setOrCreate('currentWidthBreakpoint', widthBp); - AppStorage.setOrCreate('videoGridColumn', videoGridColumn); - } catch (error) { - let err = error as BusinessError; - hilog.error(0x00, 'EntryAbility', `getDefaultDisplaySync failed, code = ${err.code}, message = ${err.message}`); - } - } - // [End update_width_bp] // [EndExclude multi_entryAbility] }