diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..702c3ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer +/oh-package-lock.json5 +BuildProfile.ets +/docs \ No newline at end of file diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000..ebeb88d --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,9 @@ +{ + "app": { + "bundleName": "net.plv.livescenes.demo", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json new file mode 100644 index 0000000..ad4a2a8 --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "POLYV多场景" + } + ] +} diff --git a/AppScope/resources/base/media/app_icon.png b/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000..805d3e1 Binary files /dev/null and b/AppScope/resources/base/media/app_icon.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0257ff8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## [v1.0.0] - 2024-10-17 + +发布 1.0.0 版本 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a6d9a5 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +polyv-harmony-livescenes-sdk-demo +=== + +[![build passing](https://img.shields.io/badge/build-passing-brightgreen.svg)](#) +[![GitHub release](https://img.shields.io/badge/release-1.0.0-blue.svg)](https://github.com/polyv/polyv-harmony-livescenes-sdk-demo/releases/tag/1.0.0) + + + + +- [1 简介](#1-%E7%AE%80%E4%BB%8B) +- [2 下载安装](#2-%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85) +- [3 文档](#3-%E6%96%87%E6%A1%A3) + - [3.1 集成文档](#31-%E9%9B%86%E6%88%90%E6%96%87%E6%A1%A3) + - [3.2 接口文档](#32-%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3) + - [3.3 版本更新记录](#33-%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95) + + + +### 1 简介 +此项目是保利威鸿蒙多场景 Demo。 + +此项目支持视频播放、在线聊天、打赏、商品、互动等功能(具体见[多场景支持的功能特性](https://github.com/polyv/polyv-harmony-livescenes-sdk-demo/blob/master/docs/public/支持的功能特性.md)) + +多场景项目的文件目录结构如下: + +``` +|-- entry +| |-- ability +| | `-- PLVEntryAbility (SDK初始化) +| |-- pages +| | `-- PLVLoginPage (登录页) +| `-- startup (播放器初始化) +`-- scenes_live (直播带货场景模块) + |-- pages (观看页) + `-- components (功能组件) +``` + +### 2 下载安装 + +```shell +ohpm install @polyvharmony/live-scenes-sdk +``` + +### 3 文档 +#### 3.1 集成文档 +[集成文档](https://github.com/polyv/polyv-harmony-livescenes-sdk-demo/tree/master/publish/docs) +#### 3.2 接口文档 +[v1.0.0 接口文档](https://repo.polyv.net/harmony/documents/livescenes_sdk/1.0.0/index.html) +#### 3.3 版本更新记录 +[全版本更新记录](https://github.com/polyv/polyv-harmony-livescenes-sdk-demo/blob/master/CHANGELOG.md) diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000..9b97094 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,44 @@ +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.0(12)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug" + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "scenes_live", + "srcPath": "./scenes_live" + } + ] +} \ No newline at end of file diff --git a/entry/.gitignore b/entry/.gitignore new file mode 100644 index 0000000..745cc01 --- /dev/null +++ b/entry/.gitignore @@ -0,0 +1,8 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test +/oh-package-lock.json5 +BuildProfile.ets \ No newline at end of file diff --git a/entry/build-profile.json5 b/entry/build-profile.json5 new file mode 100644 index 0000000..808380f --- /dev/null +++ b/entry/build-profile.json5 @@ -0,0 +1,25 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + } + ] +} \ No newline at end of file diff --git a/entry/hvigorfile.ts b/entry/hvigorfile.ts new file mode 100644 index 0000000..c6edcd9 --- /dev/null +++ b/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/entry/obfuscation-rules.txt b/entry/obfuscation-rules.txt new file mode 100644 index 0000000..985b2ae --- /dev/null +++ b/entry/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope \ No newline at end of file diff --git a/entry/oh-package.json5 b/entry/oh-package.json5 new file mode 100644 index 0000000..4502779 --- /dev/null +++ b/entry/oh-package.json5 @@ -0,0 +1,14 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "MIT", + "dependencies": { + "scenes_live": "file:../scenes_live", + "@polyvharmony/media-player-sdk": "2.2.0", + "@polyvharmony/media-player-core-ijk": "2.2.0" + } +} + diff --git a/entry/src/main/ets/ability/PLVAbilityStage.ets b/entry/src/main/ets/ability/PLVAbilityStage.ets new file mode 100644 index 0000000..b9e2581 --- /dev/null +++ b/entry/src/main/ets/ability/PLVAbilityStage.ets @@ -0,0 +1,4 @@ +import AbilityStage from '@ohos.app.ability.AbilityStage'; + +export default class PLVAbilityStage extends AbilityStage { +} \ No newline at end of file diff --git a/entry/src/main/ets/ability/PLVEntryAbility.ets b/entry/src/main/ets/ability/PLVEntryAbility.ets new file mode 100644 index 0000000..0f11fb2 --- /dev/null +++ b/entry/src/main/ets/ability/PLVEntryAbility.ets @@ -0,0 +1,44 @@ +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; +import { PLVLiveSceneSDK } from 'scenes_live'; +import { PLVMediaPlayerStartUp } from '../startup/PLVMediaPlayerStartUp'; + +export default class PLVEntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + PLVLiveSceneSDK.init(this.context, windowStage) + PLVMediaPlayerStartUp.start(this.context.getApplicationContext()) + windowStage.loadContent('pages/PLVLoginPage', (err) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); + } +} diff --git a/entry/src/main/ets/pages/PLVLoginPage.ets b/entry/src/main/ets/pages/PLVLoginPage.ets new file mode 100644 index 0000000..bdb3698 --- /dev/null +++ b/entry/src/main/ets/pages/PLVLoginPage.ets @@ -0,0 +1,193 @@ +import { PLVCommonConstants, PLVLiveChannelType, PLVLiveSceneSDK, PLVLogger, PLVTextUtils, PLVToastUtils, PLVUserConstants, PLVUtils, pushToWatchPage } from 'scenes_live'; + +const TAG = '[PLVLoginPage]' + +@Entry +@Component +struct PLVLoginPage { + // live data + @State @Watch('onDataChanged') liveUserId: string = '' + @State @Watch('onDataChanged') liveChannelId: string = '' + @State @Watch('onDataChanged') liveAppId: string = '' + @State @Watch('onDataChanged') liveAppSecret: string = '' + // playback data + @State @Watch('onDataChanged') playbackUserId: string = '' + @State @Watch('onDataChanged') playbackChannelId: string = '' + @State @Watch('onDataChanged') playbackAppId: string = '' + @State @Watch('onDataChanged') playbackAppSecret: string = '' + @State playbackVid: string = '' + @State isOnVodList: boolean = false + // common data + @State @Watch('onDataChanged') tabSelIndex: number = 0 + @State loginButtonEnabled: boolean = PLVTextUtils.checkValidDataLength(this.liveUserId, this.liveChannelId, this.liveAppId, this.liveAppSecret) + @State loadingVisibility: Visibility = Visibility.None + viewerId?: string + viewerName?: string + viewerAvatar?: string + + override async aboutToAppear(): Promise { + // todo 请务必在这里替换为你的学员(用户)ID,设置学员(用户)ID的意义详细可以查看:https://github.com/polyv/polyv-android-cloudClass-sdk-demo/wiki/6-设置学员唯一标识的意义 + this.viewerId = PLVUtils.getDeviceId() + // todo 请务必在这里替换为你的学员(用户)昵称 + this.viewerName = '观众' + this.viewerId?.substring(0, 6) + // todo 在这里可替换为你的学员(用户)头像地址 + this.viewerAvatar = PLVUserConstants.STUDENT_AVATAR_URL + } + + build() { + Stack() { + Column() { + Text('直播带货') + .fontSize(30) + .fontColor(Color.Gray) + .margin({ top: 30 }) + Tabs({ barPosition: BarPosition.Start }) { + TabContent() { + Column({ space: 36 }) { + TextInput({ placeholder: '用户Id', text: $$this.liveUserId }) + .showUnderline(true) + TextInput({ placeholder: '频道Id', text: $$this.liveChannelId }) + .showUnderline(true) + TextInput({ placeholder: 'AppId', text: $$this.liveAppId }) + .showUnderline(true) + TextInput({ placeholder: 'AppSecret', text: $$this.liveAppSecret }) + .showUnderline(true) + } + } + .tabBar('直播') + + TabContent() { + Column({ space: 15 }) { + TextInput({ placeholder: '用户Id', text: $$this.playbackUserId }) + .showUnderline(true) + TextInput({ placeholder: '频道Id', text: $$this.playbackChannelId }) + .showUnderline(true) + TextInput({ placeholder: 'AppId', text: $$this.playbackAppId }) + .showUnderline(true) + TextInput({ placeholder: 'AppSecret', text: $$this.playbackAppSecret }) + .showUnderline(true) + TextInput({ placeholder: '视频Id', text: $$this.playbackVid }) + .showUnderline(true) + Row() { + Text('点播列表') + .margin({ left: 10, right: 6 }) + Toggle({ type: ToggleType.Switch, isOn: $$this.isOnVodList }) + }.width(PLVCommonConstants.FULL_PERCENT) + } + } + .tabBar('回放') + } + .onChange((index) => { + this.tabSelIndex = index + }) + .margin({ top: 30 }) + .height(424) + + Button('登录') + .width(PLVCommonConstants.FULL_PERCENT) + .height(48) + .enabled(this.loginButtonEnabled) + .onClick(() => { + this.loadingVisibility = Visibility.Visible + if (this.tabSelIndex == 0) { + this.loginLive() + } else { + this.loginPlayback() + } + }) + Text('2013 – 2024 易方信息科技股份有限公司 版权所有') + .fontSize(14) + .fontColor(Color.Gray) + .align(Alignment.Bottom) + .margin({ bottom: 16 }) + .layoutWeight(1) + } + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + .padding(18) + .alignItems(HorizontalAlign.Center) + + Stack() { + Stack() { + } + .backgroundColor(Color.Gray) + .opacity(0.5) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + + Row() { + LoadingProgress() + .width(36) + .height(36) + Text('正在登录中,请稍等...') + .fontSize(16) + } + .padding(12) + .border({ radius: 4 }) + .backgroundColor(Color.White) + } + .visibility(this.loadingVisibility) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + } + + onDataChanged(propName: string): void { + this.loginButtonEnabled = this.tabSelIndex == 0 + ? PLVTextUtils.checkValidDataLength(this.liveUserId, this.liveChannelId, this.liveAppId, this.liveAppSecret) + : PLVTextUtils.checkValidDataLength(this.playbackUserId, this.playbackChannelId, this.playbackAppId, this.playbackAppSecret) + } + + createSDK(): PLVLiveSceneSDK { + const sdk: PLVLiveSceneSDK = PLVLiveSceneSDK.create() + sdk.channelData.viewerId = this.viewerId + sdk.channelData.viewerName = this.viewerName + sdk.channelData.viewerAvatar = this.viewerAvatar + return sdk + } + + loginLive() { + const sdk = this.createSDK() + sdk.loginManager.loginLive(this.liveUserId, this.liveChannelId, this.liveAppId, this.liveAppSecret) + .then((channelData) => { + if (PLVLiveChannelType.ALONE != channelData.channelType) { + PLVLogger.error(TAG, `loginLive error while channelType=${channelData.channelType}`) + PLVToastUtils.shortShow('直播带货场景仅支持纯视频频道类型') + this.loadingVisibility = Visibility.None + sdk.destroy() + return + } + PLVLogger.info(TAG, `loginLive success`) + this.loadingVisibility = Visibility.None + pushToWatchPage(sdk.uniqueId) + }) + .catch((err: Error) => { + this.loadingVisibility = Visibility.None + PLVToastUtils.shortShow(`loginLive error: ${err.message}`) + sdk.destroy() + }) + } + + loginPlayback() { + const sdk = this.createSDK() + sdk.loginManager.loginPlayback(this.playbackUserId, this.playbackChannelId, this.playbackAppId, this.playbackAppSecret, this.playbackVid) + .then((channelData) => { + if (PLVLiveChannelType.ALONE != channelData.channelType) { + PLVLogger.error(TAG, `loginPlayback error while channelType=${channelData.channelType}`) + PLVToastUtils.shortShow('直播带货场景仅支持纯视频频道类型') + this.loadingVisibility = Visibility.None + sdk.destroy() + return + } + PLVLogger.info(TAG, `loginPlayback success`) + this.loadingVisibility = Visibility.None + sdk.channelData.setupPlaybackListType(this.isOnVodList) + pushToWatchPage(sdk.uniqueId) + }) + .catch((err: Error) => { + PLVToastUtils.shortShow(`loginPlayback error: ${err.message}`) + this.loadingVisibility = Visibility.None + sdk.destroy() + }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/startup/PLVMediaPlayerStartUp.ets b/entry/src/main/ets/startup/PLVMediaPlayerStartUp.ets new file mode 100644 index 0000000..2a56abd --- /dev/null +++ b/entry/src/main/ets/startup/PLVMediaPlayerStartUp.ets @@ -0,0 +1,91 @@ +import { + IPLVKVStore, + Logger, + PLVKVStore, + PLVMediaPlayerAppContext, + PLVMediaPlayerFactory, + PLVMediaPlayerLogger, + safe +} from '@polyvharmony/media-player-sdk'; +import { PLVMediaPlayerCoreIjkProvider } from '@polyvharmony/media-player-core-ijk'; +import { Context } from '@ohos.abilityAccessCtrl'; +import distributedKVStore from '@ohos.data.distributedKVStore'; +import { PLVLogger } from 'scenes_live'; + +export class PLVMediaPlayerStartUp { + private static isInit = false + + static start(context: Context) { + if (PLVMediaPlayerStartUp.isInit) { + return + } + PLVMediaPlayerStartUp.isInit = true + + PLVMediaPlayerAppContext.getInstance().setupAppContext(context) + PLVMediaPlayerLogger.loggerImpl = new PLVLoggerImpl() + PLVKVStore.setupImplement(new PLVKVStoreOhosImpl(context)) + PLVMediaPlayerFactory.getInstance().register(PLVMediaPlayerCoreIjkProvider.getInstance()) + } +} + +class PLVLoggerImpl implements Logger { + debug(tag: string, message: string) { + PLVLogger.debug(tag, message) + } + + info(tag: string, message: string) { + PLVLogger.info(tag, message) + } + + warn(tag: string, message: string) { + PLVLogger.warn(tag, message) + } + + error(tag: string, message: string) { + PLVLogger.error(tag, message) + } +} + +class PLVKVStoreOhosImpl implements IPLVKVStore { + private static readonly kvStoreId = "PLVKVStoreOhosImpl" + + constructor(context: Context) { + this.kvStoreManager = distributedKVStore.createKVManager({ + context: context, + bundleName: context.applicationInfo.name + }) + this.ensureKvStore() + } + + private kvStoreManager: distributedKVStore.KVManager | undefined = undefined; + private kvStore: distributedKVStore.DeviceKVStore | undefined = undefined; + + async getValue(key: string): Promise { + if (!this.kvStore) { + await this.ensureKvStore() + } + + const result = await safe(this.kvStore?.get(key)) + if (result.success) { + return result.data as string + } else { + return undefined + } + } + + async setValue(key: string, value: string): Promise { + await this.ensureKvStore() + this.kvStore?.put(key, value) + } + + async delete(key: string): Promise { + await this.ensureKvStore() + this.kvStore?.delete(key) + } + + private async ensureKvStore() { + this.kvStore = await this.kvStoreManager?.getKVStore(PLVKVStoreOhosImpl.kvStoreId, { + securityLevel: distributedKVStore.SecurityLevel.S1 + }) as distributedKVStore.DeviceKVStore + } +} \ No newline at end of file diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 new file mode 100644 index 0000000..59ce135 --- /dev/null +++ b/entry/src/main/module.json5 @@ -0,0 +1,44 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "srcEntry": "./ets/ability/PLVAbilityStage.ets", + "description": "$string:module_desc", + "mainElement": "PLVEntryAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "PLVEntryAbility", + "srcEntry": "./ets/ability/PLVEntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.INTERNET" + } + ] + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000..3c71296 --- /dev/null +++ b/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000..b459da4 --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "POLYV多场景" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/background.png b/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000..f939c9f Binary files /dev/null and b/entry/src/main/resources/base/media/background.png differ diff --git a/entry/src/main/resources/base/media/foreground.png b/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000..bbca4bc Binary files /dev/null and b/entry/src/main/resources/base/media/foreground.png differ diff --git a/entry/src/main/resources/base/media/layered_image.json b/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000..2fc00e9 --- /dev/null +++ b/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:foreground", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/startIcon.png b/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000..805d3e1 Binary files /dev/null and b/entry/src/main/resources/base/media/startIcon.png differ diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000..57fe1cc --- /dev/null +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/PLVLoginPage" + ] +} diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000..b459da4 --- /dev/null +++ b/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "POLYV多场景" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..9293fba --- /dev/null +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "POLYV多场景" + } + ] +} \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000..36ec404 --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,21 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + "stacktrace": true /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 4096 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process */ + } +} \ No newline at end of file diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000..20a8c11 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000..9c81199 --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,16 @@ +{ + "modelVersion": "5.0.0", + "name": "@polyvharmony/harmony-livescenes-sdk", + "version": "1.0.0", + "description": "polyv harmony-livescenes-sdk", + "main": "", + "author": "", + "license": "MIT", + "dependencies": { + }, + "devDependencies": { + }, + "dynamicDependencies": {}, + "overrides": { + } +} \ No newline at end of file diff --git "a/publish/docs/1-\351\241\271\347\233\256\344\273\213\347\273\215.md" "b/publish/docs/1-\351\241\271\347\233\256\344\273\213\347\273\215.md" new file mode 100644 index 0000000..0bd300e --- /dev/null +++ "b/publish/docs/1-\351\241\271\347\233\256\344\273\213\347\273\215.md" @@ -0,0 +1,17 @@ +此项目是保利威鸿蒙多场景 Demo。 + +此项目支持视频播放、在线聊天、打赏、商品、互动等功能(具体见[多场景支持的功能特性](https://github.com/polyv/polyv-harmony-livescenes-sdk-demo/blob/master/docs/public/支持的功能特性.md)) + +多场景项目的文件目录结构如下: + +``` +|-- entry +| |-- ability +| | `-- PLVEntryAbility (SDK初始化) +| |-- pages +| | `-- PLVLoginPage (登录页) +| `-- startup (播放器初始化) +`-- scenes_live (直播带货场景模块) + |-- pages (观看页) + `-- components (功能组件) +``` \ No newline at end of file diff --git "a/publish/docs/2-\345\277\253\351\200\237\351\233\206\346\210\220.md" "b/publish/docs/2-\345\277\253\351\200\237\351\233\206\346\210\220.md" new file mode 100644 index 0000000..585359d --- /dev/null +++ "b/publish/docs/2-\345\277\253\351\200\237\351\233\206\346\210\220.md" @@ -0,0 +1,97 @@ + + + +- [1 账号准备](#1-%E8%B4%A6%E5%8F%B7%E5%87%86%E5%A4%87) +- [2 环境要求](#2-%E7%8E%AF%E5%A2%83%E8%A6%81%E6%B1%82) +- [3 集成多场景项目](#3-%E9%9B%86%E6%88%90%E5%A4%9A%E5%9C%BA%E6%99%AF%E9%A1%B9%E7%9B%AE) + - [3.1 导入直播观看模块](#31-%E5%AF%BC%E5%85%A5%E7%9B%B4%E6%92%AD%E8%A7%82%E7%9C%8B%E6%A8%A1%E5%9D%97) + - [3.2 配置播放器 SDK](#32-%E9%85%8D%E7%BD%AE%E6%92%AD%E6%94%BE%E5%99%A8-SDK) + - [3.3 初始化多场景 SDK](#33-%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%9A%E5%9C%BA%E6%99%AF-SDK) + - [3.4 跳转直播观看页面](#34-%E8%B7%B3%E8%BD%AC%E7%9B%B4%E6%92%AD%E8%A7%82%E7%9C%8B%E9%A1%B5%E9%9D%A2) + - [3.5 参数修改](#35-%E5%8F%82%E6%95%B0%E4%BF%AE%E6%94%B9) + + + +### 1 账号准备 + +在集成多场景 SDK 项目之前,请先在[Polyv 官网](http://www.polyv.net/)注册账号,并开通云直播服务。 + +Demo 中登录直播需要账号直播系统中的`appId`(应用ID),`appSecret`(应用密匙),`userId`(账号ID),`channelId`(频道号),登录回放再额外需要频道号对应回放列表里的`vid`(回放视频id)。您可以登录直播后台,在[开发者信息](https://console.polyv.net/live/#/develop/app-id)中获取参数。 + +### 2 环境要求 + +| 名称 | 要求 | +|----------------------|---------------| +| compatibleSdkVersion | \>= 5.0.0(12) | +| abiFilters | 仅支持 arm64-v8a | + +### 3 集成多场景项目 + +#### 3.1 导入直播观看模块 + +拷贝 demo 项目中的`scenes_live`模块到您项目的根目录下,并修改项目的`build-profile.json5`文件,添加对 scenes_live 模块的引用: + +```json5 +{ + "name": "scenes_live", + "srcPath": "./scenes_live" +} +``` + +#### 3.2 配置播放器 SDK + +多场景 SDK 的视频播放功能依赖于播放器 SDK,因此在初始化多场景 SDK 之前,需要修改模块的`oh-package.json5`文件,添加播放器 SDK 的依赖: + +```json5 +{ + "dependencies": { + // 播放器 SDK 对外接口依赖 + "@polyvharmony/media-player-sdk": "version", + // 播放器内核依赖 + "@polyvharmony/media-player-core-ijk": "version" + } +} +``` + +播放器 SDK 的播放内核、日志打印、数据存储等组件均需要在播放视频前完成配置,您可以参考 demo 项目中的`PLVMediaPlayerStartUp`类,完成播放器 SDK 的初始化配置。 + +```ts +// 配置全局 AppContext +PLVMediaPlayerAppContext.getInstance().setupAppContext(context) +// 配置日志 +PLVMediaPlayerLogger.loggerImpl = new HiLogImpl() +// 配置数据存储 +PLVKVStore.setupImplement(new PLVKVStoreOhosImpl(context)) +// 配置播放内核 +PLVMediaPlayerFactory.getInstance().register(PLVMediaPlayerCoreIjkProvider.getInstance()) +``` + +#### 3.3 初始化多场景 SDK + +多场景 SDK 需在模块 Ability 的 onWindowStageCreate 方法中进行初始化,您可以参考 demo 项目中的`PLVEntryAbility`类: + +```ts +PLVLiveSceneSDK.init(context, windowStage) +``` + +#### 3.4 跳转直播观看页面 + +demo 模块提供了 2 个页面,分别是: +1. `PLVLoginPage`是项目演示的登录页面,演示了如何跳转到直播观看页面 +2. `PLVLIWatchPage`是直播观看页面,支持播放直播和回放视频,同时也支持聊天室、互动等功能 + +您可以根据项目实际需要,在您项目的任意页面跳转到直播观看页面。 + +#### 3.5 参数修改 + +为了在 demo 项目播放您账号下的视频,您需要对项目中登录所需的参数进行配置。 + +demo 项目中,登录参数在 pages 文件夹下的`PLVLoginPage`类中配置: +- liveUserId/playbackUserId:账号Id +- liveChannelId/playbackChannelId:频道Id +- liveAppId/playbackAppId:应用Id +- liveAppSecret/playbackAppSecret:应用密钥 +- playbackVid:回放视频Id +- viewerId:学员(用户)Id +- viewerName:学员(用户)昵称 +- viewerAvatar:学员(用户)头像地址 \ No newline at end of file diff --git "a/publish/docs/3-SDK\345\205\245\345\217\243.md" "b/publish/docs/3-SDK\345\205\245\345\217\243.md" new file mode 100644 index 0000000..9ad0ece --- /dev/null +++ "b/publish/docs/3-SDK\345\205\245\345\217\243.md" @@ -0,0 +1,65 @@ + + + +- [1.PLVLiveScenesSDK](#1PLVLiveScenesSDK) +- [2.SDK 初始化](#SDK-%E5%88%9D%E5%A7%8B%E5%8C%96) +- [3.SDK 对象创建](#3SDK-%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA) +- [4.登录直播页面](#4%E7%99%BB%E5%BD%95%E7%9B%B4%E6%92%AD%E9%A1%B5%E9%9D%A2) +- [5.直播页面数据初始化](#5%E7%9B%B4%E6%92%AD%E9%A1%B5%E9%9D%A2%E6%95%B0%E6%8D%AE%E5%88%9D%E5%A7%8B%E5%8C%96) +- [6.SDK 对象销毁](#6SDK-%E5%AF%B9%E8%B1%A1%E9%94%80%E6%AF%81) + + + +### 1.PLVLiveScenesSDK + +`PLVLiveScenesSDK`类是多场景 SDK 的入口,该类提供了 SDK 初始化、直播页面数据初始化,以及各功能模块的统一入口和生命周期管理。 + +### 2.SDK 初始化 + +您可以使用`PLVLiveScenesSDK`的`init`静态方法对 SDK 进行初始化,详细使用代码可以参考 demo 项目的`PLVEntryAbility`类: + +```ts +PLVLiveSceneSDK.init(context, windowStage) +``` + +### 3.SDK 对象创建 + +在每次进入一个新的直播页面前,需要先创建一个新的 SDK 对象,该对象内部会生成一个 sdkId 与该直播页面相关联,之后也能在该直播页面使用 sdkId 去获取 SDK 对象,详细使用代码可以参考 demo 项目的`PLVLoginPage`类: + +```ts +// 创建 SDK 对象 +const sdk: PLVLiveSceneSDK = PLVLiveSceneSDK.create() +// 获取 SDK 对象 +PLVLiveSceneSDK.get(sdk.uniqueId) +``` + +### 4.登录直播页面 + +在进入直播页面之前,需要先进行登录验证,可以使用 SDK 对象的`loginManager`进行直播或者回放的登录,详细使用代码可以参考 demo 项目的`PLVLoginPage`类: + +```ts +// 登录直播 +sdk.loginManager.loginLive(userId?: string, channelId?: string, appId?: string, appSecret?: string) +// 登录回放 +sdk.loginManager.loginPlayback(userId?: string, channelId?: string, appId?: string, appSecret?: string, videoId?: string) +``` + +因为每次登录意味着要进入一个新的直播页面,因此在登录前需要创建一个新的 SDK 对象,如果登录失败时则需要调用该 SDK 对象`destroy`方法进行销毁。登录成功后,把 SDK 对象的`uniqueId`传递给直播页面,后续就能在直播页面使用`PLVLiveSceneSDK`的`get`方法获取到 SDK 对象了。 + +### 5.直播页面数据初始化 + +在进入直播页面后,需要对直播页面的数据进行初始化,在调用 SDK 对象的`initData`方法后,SDK 内部会进行本地资源的初始化,以及网络资源的请求,详细使用代码可以参考 demo项目的`PLVLIWatchLayout`类: + +```ts +// 初始化sdk数据,如果当前模块(scenes_live模块)为har类型,传getContext();如果为hsp类型,则需要传getContext().createModuleContext('模块名') +// 因sdk内部需要读取rawfile,因此这里需要传入模块的context +sdk.initData(getContext() as common.UIAbilityContext) +``` + +### 6.SDK 对象销毁 + +除了登录失败需要销毁 SDK 对象外,在退出直播页面的时候,也需要对 SDK 对象进行销毁以释放资源,详细使用代码可以参考 demo项目的`PLVLIWatchLayout`类: + +```ts +sdk.destroy() +``` \ No newline at end of file diff --git "a/publish/docs/4-\350\247\206\351\242\221\346\222\255\346\224\276.md" "b/publish/docs/4-\350\247\206\351\242\221\346\222\255\346\224\276.md" new file mode 100644 index 0000000..99a42bc --- /dev/null +++ "b/publish/docs/4-\350\247\206\351\242\221\346\222\255\346\224\276.md" @@ -0,0 +1,92 @@ + + + +- [1.播放器](#1%E6%92%AD%E6%94%BE%E5%99%A8) +- [2.设置渲染画面](#2%E8%AE%BE%E7%BD%AE%E6%B8%B2%E6%9F%93%E7%94%BB%E9%9D%A2) +- [3.播放参数配置](#3%E6%92%AD%E6%94%BE%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) +- [4.播放控制](#4%E6%92%AD%E6%94%BE%E6%8E%A7%E5%88%B6) +- [5.回调](#5%E5%9B%9E%E8%B0%83) + + + +### 1.播放器 + +播放器管理器的对外核心类为`PLVPlayerManager`,可以通过 SDK 对象的`playerManager`属性对其进行访问,`playerManager`内部的`mainMediaPlayer`为视频播放的播放器。 + +### 2.设置渲染画面 + +播放器通过`surface`类型的 XComponent 组件进行渲染,libraryname 为`plvplayer_xcomponent`,您可以通过`startMainMediaPlayerWithComponent`方法将渲染画面设置给播放器: + +```ts +XComponent({ + id: `plvli_video_xcomponent`, + type: "surface", + libraryname: "plvplayer_xcomponent" +}) { +} +.onLoad((component) => { + // 设置渲染画面并开始播放 + this.sdk.playerManager.startMainMediaPlayerWithComponent(component!) +}) +``` + +### 3.播放参数配置 + +通过调用`mainMediaPlayer`的`setPlayerOption()`方法设置播放参数 + +```ts +/** + * 设置播放参数 + */ +setPlayerOption(options: PLVMediaPlayerOption[]) +``` + +`PLVMediaPlayerOptionEnum`类中提供了一些常用的播放参数,您可以直接引用其中的常量,例如: + +```ts +// 开启精准seek的参数 +PLVMediaPlayerOptionEnum.ENABLE_ACCURATE_SEEK.value("1") +``` +对于重复设置的参数,新设置的参数会覆盖旧的参数;如果想要清空参数,可以在 value 字段中传入空字符串 + +### 4.播放控制 + +播放器`mainMediaPlayer`提供了一系列的播放控制接口,例如: + +```ts +/** + * 开始播放 + */ +start(): void; + +/** + * 暂停播放 + */ +pause(): void;` + +/** + * 跳转播放进度到指定位置 + * @param position 指定位置,单位:毫秒 + */ +seek(position: number): void; +``` + +更多控制操作可以参考`IPLVMediaPlayer`以及它的父接口`IPLVMediaPlayerControl` + +### 5.回调 + +播放器的状态、事件回调可以通过回调注册中心进行监听,包括: + +- IPLVMediaPlayerBusinessListenerRegistry:播放器业务回调注册中心 +- IPLVMediaPlayerEventListenerRegistry:播放器事件回调注册中心 +- IPLVMediaPlayerStateListenerRegistry:播放器状态回调注册中心 + +以监听播放/暂停状态为例,可以通过以下方式进行监听: + +```ts +const playingState: MutableState = player.getStateListenerRegistry().playingState; +const observer = playingState.observe((state: PLVMediaPlayerPlayingState) => { + // 处理逻辑 + const isPlaying = state === PLVMediaPlayerPlayingState.PLAYING; +}); +``` \ No newline at end of file diff --git "a/publish/docs/5-\350\201\212\345\244\251\345\256\244.md" "b/publish/docs/5-\350\201\212\345\244\251\345\256\244.md" new file mode 100644 index 0000000..d5c3c5c --- /dev/null +++ "b/publish/docs/5-\350\201\212\345\244\251\345\256\244.md" @@ -0,0 +1,74 @@ + + + +- [1.聊天室](#1%E8%81%8A%E5%A4%A9%E5%AE%A4) +- [2.登录](#2%E7%99%BB%E5%BD%95) +- [3.回调](#3%E5%9B%9E%E8%B0%83) +- [4.发言](#4%E5%8F%91%E8%A8%80) +- [5.提问](#5%E6%8F%90%E9%97%AE) + + + +### 1.聊天室 + +聊天室管理器的对外核心类为`PLVChatroomManager`,可以通过 SDK 对象的`chatroomManager`属性对其进行访问。 + +### 2.登录 + +聊天室的消息收发依赖于 socket,因此需要先进行 socket 的登录,后续才能正常使用聊天室。socket 管理器的对外核心类为`PLVSocketManager`,可以通过 SDK 对象的`socketManager`属性对其进行访问: + +```ts +// 监听socket连接状态 +sdk.socketManager.onStatus() +// socket登录 +sdk.socketManager.login() +``` + +详细使用代码可以参考 demo 项目的`PLVLIWatchLayout`类。 + +### 3.回调 + +聊天室的 socket通道消息、业务事件消息通过回调注册进行监听,包括: + +- onData:socket通道消息回调注册 +- eventNotify.on:业务事件消息回调注册 + +以监听 socket通道的公告/移除公告消息为例,可以通过以下方式进行监听: + +```ts +sdk.chatroomManager.onData(PLVSocketOnEvent.MESSAGE, (data: string, event: string) => { + switch (event) { + // 公告 + case PLVBulletinEvent.EVENT: + break; + // 移除公告 + case PLVRemoveBulletinEvent.EVENT: + break; + default: + break; + } +}, this) +``` + +详细使用代码可以参考 demo 项目的`PLVLIWatchLayout`、`PLVLIChatListLayout`类。 + +### 4.发言 + +可以使用聊天室的`speak`方法进行发言: + +```ts +// messsage:要发言的消息,replay:携带的回复消息,非回复时为undefined +sdk.chatroomManager.speak(message, replay) +``` + +详细使用代码可以参考 demo 项目的`PLVLIChatInputView`类。 + +### 5.提问 + +可以使用聊天室的`quiz`方法进行提问: + +```ts +sdk.chatroomManager.quiz(message) +``` + +详细使用代码可以参考 demo 项目的`PLVLIChatInputView`类。 \ No newline at end of file diff --git "a/publish/docs/6-\344\272\222\345\212\250.md" "b/publish/docs/6-\344\272\222\345\212\250.md" new file mode 100644 index 0000000..12b129e --- /dev/null +++ "b/publish/docs/6-\344\272\222\345\212\250.md" @@ -0,0 +1,46 @@ + + + +- [1.互动](#1%E4%BA%92%E5%8A%A8) +- [2.互动页](#2%E4%BA%92%E5%8A%A8%E9%A1%B5) +- [3.回调](#3%E5%9B%9E%E8%B0%83) + + + +### 1.互动 + +互动管理器的对外核心类为`PLVInteractManager`,可以通过 SDK 对象的`interactManager`属性对其进行访问。 + +### 2.互动页 + +互动页是一个 Web 组件,其对外核心类为`PLVInteractWeb`,只需要把该组件嵌入到布局中即可完成互动页的集成。 + +```ts +PLVInteractWeb({ + interactManager: this.interactManager, // 互动管理器 + interactCallback: this.interactCallback, // 互动回调 + controller: this.controller, // web组件控制器 + redpackManager: this.redpackManager // 红包管理器 +}) +``` + +详细使用代码可以参考 demo 项目的`PLVLIInteractWebView`类。 + +### 3.回调 + +互动的业务事件消息通过`interactCallback`注册进行监听,包括: + +- processWebViewVisibility:web组件可见性回调注册 +- processOpenLinkEvent:互动内部打开链接回调注册 +- processClickProductEvent:点击商品回调注册 + +以监听 web组件可见性消息为例,可以通过以下方式进行监听: + +```ts +interactCallback.processWebViewVisibility = (show) => { + // 处理逻辑 + this.viewController.isShow = show +} +``` + +详细使用代码可以参考 demo 项目的`PLVLIInteractWebView`类。 \ No newline at end of file diff --git "a/publish/docs/7-\345\225\206\345\223\201.md" "b/publish/docs/7-\345\225\206\345\223\201.md" new file mode 100644 index 0000000..d08d443 --- /dev/null +++ "b/publish/docs/7-\345\225\206\345\223\201.md" @@ -0,0 +1,43 @@ + + + +- [1.商品](#1%E5%95%86%E5%93%81) +- [2.商品页](#2%E5%95%86%E5%93%81%E9%A1%B5) +- [3.回调](#3%E5%9B%9E%E8%B0%83) + + + +### 1.商品 + +商品管理器的对外核心类为`PLVProductManager`,可以通过 SDK 对象的`productManager`属性对其进行访问。 + +### 2.商品页 + +商品页是一个 Web 组件,其对外核心类为`PLVProductWeb`,只需要把该组件嵌入到布局中即可完成商品页的集成。 + +```ts +PLVProductWeb({ + controller: this.productController, // web组件控制器 + interactManager: this.interactManager // 互动管理器 +}) +``` + +详细使用代码可以参考 demo 项目的`PLVLIProductWebView`类。 + +### 3.回调 + +商品的业务事件消息通过回调注册进行监听,包括: + +- productManager.eventNotify.on:商品数据回调注册 +- productController.eventNotify.on:商品点击回调注册 + +以监听商品数据为例,可以通过以下方式进行监听: + +```ts +sdk.productManager.eventNotify.on('product_data', (value: PLVProductDataBean) => { + // 处理逻辑 + this.productDataBean = value +}, this) +``` + +详细使用代码可以参考 demo 项目的`PLVLILiveHomeLayout`类。 \ No newline at end of file diff --git "a/publish/docs/8-\346\211\223\350\265\217.md" "b/publish/docs/8-\346\211\223\350\265\217.md" new file mode 100644 index 0000000..559f60c --- /dev/null +++ "b/publish/docs/8-\346\211\223\350\265\217.md" @@ -0,0 +1,39 @@ + + + +- [1.打赏](#1%E4%BA%92%E5%8A%A8) +- [2.回调](#2%E5%9B%9E%E8%B0%83) +- [3.礼物打赏](#3%E7%A4%BC%E7%89%A9%E6%89%93%E8%B5%8F) + + + +### 1.打赏 + +打赏管理器的对外核心类为`PLVRewardManager`,可以通过 SDK 对象的`rewardManager`属性对其进行访问。 + +### 2.回调 + +打赏的配置在 SDK 进行直播页面数据初始化时会进行请求,这里我们通过注册进行监听,包括: + +- rewardManager.eventNotify.on:打赏配置回调 + +以监听打赏配置为例,可以通过以下方式进行监听: + +```ts +sdk.rewardManager.eventNotify.on('reward_setting', (value: PLVRewardSettingVO) => { + // 处理逻辑 +}, this) +``` + +详细使用代码可以参考 demo 项目的`PLVLILiveHomeLayout`类。 + +### 3.礼物打赏 + +当前 SDK 暂只支持礼物打赏(包含现金支付(仅免费)、积分支付类型),分别对应`makeGiftCashReward`、`makePointReward`方法: + +```ts +rewardManager.makeGiftCashReward(goodId, goodNum) +rewardManager.makePointReward(goodId, goodNum) +``` + +详细使用代码可以参考 demo 项目的`PLVLIRewardView`类。 \ No newline at end of file diff --git "a/publish/docs/\346\224\257\346\214\201\347\232\204\345\212\237\350\203\275\347\211\271\346\200\247.md" "b/publish/docs/\346\224\257\346\214\201\347\232\204\345\212\237\350\203\275\347\211\271\346\200\247.md" new file mode 100644 index 0000000..9ef6bd2 --- /dev/null +++ "b/publish/docs/\346\224\257\346\214\201\347\232\204\345\212\237\350\203\275\347\211\271\346\200\247.md" @@ -0,0 +1,40 @@ +## 功能特性 + +目前多场景项目支持的功能包括: + +- 直播播放器 + - 切换线路 + - 切换码率 + - 切换音视频模式 +- 回放播放器 + - 自动续播 + - 倍速播放 + - 循环播放 +- 聊天室 + - 公聊 + - 提问 + - 点赞 + - 回复 + - 欢迎语 + - 聊天回放 +- 商品 + - 商品卡片 + - 商品列表 + - 商品详情 +- 互动 + - 公告 + - 签到 + - 抽奖 + - 答题卡 + - 问卷 + - 口令红包 + - 卡片推送 +- 打赏 + - 免费打赏 + - 积分打赏 +- 直播间 + - 直播介绍 + - 在线人数 +- 多语言 + - 简体中文 + - 英文 diff --git a/scenes_live/.gitignore b/scenes_live/.gitignore new file mode 100644 index 0000000..745cc01 --- /dev/null +++ b/scenes_live/.gitignore @@ -0,0 +1,8 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test +/oh-package-lock.json5 +BuildProfile.ets \ No newline at end of file diff --git a/scenes_live/Index.ets b/scenes_live/Index.ets new file mode 100644 index 0000000..c38a23b --- /dev/null +++ b/scenes_live/Index.ets @@ -0,0 +1,5 @@ +// pages +export { pushToWatchPage } from './src/main/ets/pages/PLVLIWatchPage' + +// sdk +export * from '@polyvharmony/live-scenes-sdk' \ No newline at end of file diff --git a/scenes_live/build-profile.json5 b/scenes_live/build-profile.json5 new file mode 100644 index 0000000..e8a3c1d --- /dev/null +++ b/scenes_live/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./obfuscation-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + } + ] +} \ No newline at end of file diff --git a/scenes_live/hvigorfile.ts b/scenes_live/hvigorfile.ts new file mode 100644 index 0000000..4218707 --- /dev/null +++ b/scenes_live/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/scenes_live/obfuscation-rules.txt b/scenes_live/obfuscation-rules.txt new file mode 100644 index 0000000..e978fdb --- /dev/null +++ b/scenes_live/obfuscation-rules.txt @@ -0,0 +1,21 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-keep-global-name +* \ No newline at end of file diff --git a/scenes_live/oh-package.json5 b/scenes_live/oh-package.json5 new file mode 100644 index 0000000..dafeadf --- /dev/null +++ b/scenes_live/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "name": "scenes_live", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "MIT", + "packageType": "InterfaceHar", + "dependencies": { + "@polyvharmony/live-scenes-sdk": "1.0.0", + "@polyvharmony/media-player-sdk": "2.2.0" + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/common/PLVLIEventHub.ets b/scenes_live/src/main/ets/common/PLVLIEventHub.ets new file mode 100644 index 0000000..0284966 --- /dev/null +++ b/scenes_live/src/main/ets/common/PLVLIEventHub.ets @@ -0,0 +1,15 @@ +export type EventType = 'plvli_chat_reply' + +export class PLVLIEventHub { + static on(event: EventType, callback: Function) { + getContext().eventHub.on(event, callback) + } + + static off(event: EventType, callback?: Function) { + getContext().eventHub.off(event, callback) + } + + static emit(event: EventType, ...args: Object[]) { + getContext().eventHub.emit(event, ...args) + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/common/PLVLIFaceManager.ets b/scenes_live/src/main/ets/common/PLVLIFaceManager.ets new file mode 100644 index 0000000..bea2cc9 --- /dev/null +++ b/scenes_live/src/main/ets/common/PLVLIFaceManager.ets @@ -0,0 +1,59 @@ +import { HashMap } from '@kit.ArkTS'; + +class PLVLIFaceManager { + private faceMap: HashMap = new HashMap() + + constructor() { + this.initFaceMap() + } + + private initFaceMap() { + this.faceMap.set('[呲牙]', $r('app.media.plvli_face_101')); + this.faceMap.set('[大笑]', $r('app.media.plvli_face_102')); + this.faceMap.set('[可爱]', $r('app.media.plvli_face_103')); + this.faceMap.set('[害羞]', $r('app.media.plvli_face_104')); + this.faceMap.set('[偷笑]', $r('app.media.plvli_face_105')); + this.faceMap.set('[再见]', $r('app.media.plvli_face_106')); + this.faceMap.set('[惊讶]', $r('app.media.plvli_face_107')); + this.faceMap.set('[哭笑]', $r('app.media.plvli_face_108')); + this.faceMap.set('[酷]', $r('app.media.plvli_face_109')); + this.faceMap.set('[奸笑]', $r('app.media.plvli_face_110')); + this.faceMap.set('[鼓掌]', $r('app.media.plvli_face_111')); + this.faceMap.set('[大哭]', $r('app.media.plvli_face_112')); + this.faceMap.set('[敲打]', $r('app.media.plvli_face_113')); + this.faceMap.set('[吃瓜]', $r('app.media.plvli_face_114')); + this.faceMap.set('[让我看看]', $r('app.media.plvli_face_115')); + this.faceMap.set('[按脸哭]', $r('app.media.plvli_face_116')); + this.faceMap.set('[打哈欠]', $r('app.media.plvli_face_117')); + this.faceMap.set('[愤怒]', $r('app.media.plvli_face_118')); + this.faceMap.set('[难过]', $r('app.media.plvli_face_119')); + this.faceMap.set('[ok]', $r('app.media.plvli_face_120')); + this.faceMap.set('[爱心]', $r('app.media.plvli_face_121')); + this.faceMap.set('[加1]', $r('app.media.plvli_face_122')); + this.faceMap.set('[心碎]', $r('app.media.plvli_face_123')); + this.faceMap.set('[正确]', $r('app.media.plvli_face_124')); + this.faceMap.set('[错误]', $r('app.media.plvli_face_125')); + this.faceMap.set('[满分]', $r('app.media.plvli_face_126')); + this.faceMap.set('[笔记]', $r('app.media.plvli_face_127')); + this.faceMap.set('[胜利]', $r('app.media.plvli_face_128')); + this.faceMap.set('[比心]', $r('app.media.plvli_face_129')); + this.faceMap.set('[赞]', $r('app.media.plvli_face_130')); + this.faceMap.set('[蛋糕]', $r('app.media.plvli_face_131')); + this.faceMap.set('[礼物]', $r('app.media.plvli_face_132')); + this.faceMap.set('[红包]', $r('app.media.plvli_face_133')); + this.faceMap.set('[奶茶]', $r('app.media.plvli_face_134')); + this.faceMap.set('[时钟]', $r('app.media.plvli_face_135')); + this.faceMap.set('[晚安]', $r('app.media.plvli_face_136')); + this.faceMap.set('[拍手]', $r('app.media.plvli_face_137')); + this.faceMap.set('[鲜花]', $r('app.media.plvli_face_138')); + } + + getFace(faceKey: string): Resource | string { + if (this.faceMap.hasKey(faceKey)) { + return this.faceMap.get(faceKey) + } + return faceKey + } +} + +export default new PLVLIFaceManager() \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/PLVLIDetailLayout.ets b/scenes_live/src/main/ets/components/PLVLIDetailLayout.ets new file mode 100644 index 0000000..9ace8d5 --- /dev/null +++ b/scenes_live/src/main/ets/components/PLVLIDetailLayout.ets @@ -0,0 +1,133 @@ +import { IPLVBackwardInterface, PLVCallback, PLVChannelData, PLVCommonConstants, PLVDeviceUtils, PLVSimpleWeb, PLVWebController, PLVWebUtils } from '@polyvharmony/live-scenes-sdk' + +const TAG = '[PLVLIDetailLayout]' + +@Preview +@Component +export struct PLVLIDetailLayout { + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @Link layoutController: PLVDetailLayoutController + @Link @Watch('onBulletinSrc') bulletinSrc: string | undefined + @State introSrc: string | undefined = this.channelData?.getMenuDescContent() + + aboutToAppear(): void { + this.initData() + } + + build() { + Column() { + Scroll() { + Column() { + Blank(60) + if (this.bulletinSrc) { + Column() { + Text() { + ImageSpan($r('app.media.plvli_detail_gonggao')) + .width(16) + .height(16) + .margin({ right: 8 }) + .verticalAlign(ImageSpanAlignment.CENTER) + Span($r('app.string.plvli_live_bulletin')) + .fontSize(16) + } + .width(PLVCommonConstants.FULL_PERCENT) + .textAlign(TextAlign.Start) + + PLVSimpleWeb({ controller: this.layoutController.bulletinWebController, src: '' }) + .height(216) + .margin({ top: 16 }) + } + .backgroundColor('#CCF3F3F3') + .borderRadius(10) + .padding(16) + } + + Column() { + Text() { + ImageSpan($r('app.media.plvli_intro')) + .width(16) + .height(16) + .margin({ right: 8 }) + .verticalAlign(ImageSpanAlignment.CENTER) + Span($r('app.string.plvli_live_intro')) + .fontSize(16) + } + .width(PLVCommonConstants.FULL_PERCENT) + .textAlign(TextAlign.Start) + + if (this.introSrc) { + PLVSimpleWeb({ controller: this.layoutController.introWebController, src: '' }) + .margin({ top: 16 }) + } else { + Text($r('app.string.plvli_live_no_intro')) + .width(PLVCommonConstants.FULL_PERCENT) + .layoutWeight(1) + .offset({ y: -8 }) + .textAlign(TextAlign.Center) + .fontSize(14) + } + } + .constraintSize({ minHeight: 216, maxHeight: 400 }) + .margin({ top: 8 }) + .backgroundColor('#CCF3F3F3') + .borderRadius(10) + .padding(16) + } + .width(PLVCommonConstants.FULL_PERCENT) + } + .scrollable(ScrollDirection.Vertical) + .scrollBar(BarState.Off) + .width(PLVCommonConstants.FULL_PERCENT) + .height(400) + } + .padding({ + left: 16, + right: 16, + top: 6, + bottom: 6 + PLVDeviceUtils.getNavigationIndicatorHeight() + }) + .justifyContent(FlexAlign.End) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + + initData() { + this.layoutController.introSrc = this.introSrc + this.layoutController.bulletinSrc = this.bulletinSrc + } + + onBulletinSrc() { + this.layoutController.bulletinSrc = this.bulletinSrc + } +} + +export class PLVDetailLayoutController implements IPLVBackwardInterface { + bulletinSrc: string | undefined + introSrc: string | undefined + isLayoutShow?: boolean + introWebController = new PLVWebController() + .nestedScrollOptions({ + scrollForward: NestedScrollMode.PARALLEL, + scrollBackward: NestedScrollMode.SELF_FIRST + }) + .onControllerAttached(() => { + this.introWebController.loadHtmlData(PLVWebUtils.toWebViewContent(this.introSrc)) + }) + bulletinWebController = new PLVWebController() + .onControllerAttached(() => { + this.bulletinWebController.loadHtmlData(PLVWebUtils.toWebViewContent(this.bulletinSrc)) + }) + accessBackward: PLVCallback = () => { + if (this.isLayoutShow && (this.bulletinWebController.accessBackward() || this.introWebController.accessBackward())) { + return true + } + return false + } + backward: PLVCallback = () => { + if (this.bulletinWebController.accessBackward()) { + this.bulletinWebController.backward() + } else if (this.introWebController.accessBackward()) { + this.introWebController.backward() + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/PLVLILiveHomeLayout.ets b/scenes_live/src/main/ets/components/PLVLILiveHomeLayout.ets new file mode 100644 index 0000000..ac0db31 --- /dev/null +++ b/scenes_live/src/main/ets/components/PLVLILiveHomeLayout.ets @@ -0,0 +1,327 @@ +import { + ChannelMenusBean, + GiftDetailDataBean, + IPLVBackwardInterface, + PLVBaseIdEvent, + PLVCallback, + PLVChannelData, + PLVChatQuoteDataBean, + PLVCommonConstants, + PLVDeviceUtils, + PLVLazyDataSource, + PLVLiveSceneSDK, + PLVProductDataBean, + PLVRewardSettingVO, + PLVToastUtils +} from '@polyvharmony/live-scenes-sdk' +import { PLVLIWatchInfoView } from './liveroom/PLVLIWatchInfoView' +import { PLVLIChatInputEntryView } from './chatroom/PLVLIChatInputEntryView' +import { PLVLIChatInputView } from './chatroom/PLVLIChatInputView' +import { PLVLIChatListLayout } from './chatroom/PLVLIChatListLayout' +import { PLVLIBulletinView } from './liveroom/PLVLIBulletinView' +import { PLVLIMoreView } from './liveroom/PLVLIMoreView' +import { PLVLILikeIconView } from './chatroom/PLVLILikeIconView' +import { PLVLIGreetingView } from './chatroom/PLVLIGreetingView' +import { PLVLIRewardView } from './reward/PLVLIRewardView' +import { PLVLIProductView } from './product/PLVLIProductView' +import { PLVLIProductWebView, PLVProductWebViewController } from './product/PLVLIProductWebView' +import { PLVInteractWebViewController, PLVLIInteractWebView } from './interact/PLVLIInteractWebView' +import { PLVLIInteractEntranceView } from './interact/entrance/PLVLIInteractEntranceView' +import { PLVLICardPushView } from './interact/cardpush/PLVLICardPushView' +import { PLVLILotteryView } from './interact/lottery/PLVLILotteryView' +import { PLVLIRedpackView } from './chatroom/PLVLIRedpackView' +import { PLVLIEventHub } from '../common/PLVLIEventHub' +import { PLVLIToTopView } from './chatroom/PLVLIToTopView' + +const TAG = '[PLVLILiveHomeLayout]' + +@Preview +@Component +export struct PLVLILiveHomeLayout { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @Link layoutController: PLVLiveHomeLayoutController + @Link bulletinSrc: string | undefined + chatListScroller: ListScroller = new ListScroller() + // 全部消息的数据列表 + @State fullChatListData: PLVLazyDataSource = new PLVLazyDataSource() + quizListScroller: ListScroller = new ListScroller() + @State quizListData: PLVLazyDataSource = new PLVLazyDataSource() + @State quizMenu: ChannelMenusBean | undefined = this.channelData?.getMenuQuizBean() + @State isQuizSelected: boolean = false + @State isShowChatInputLayout: boolean = false + @State isCloseRoom: boolean = false + @State isFocusModeOpen: boolean = false + @State reply?: PLVChatQuoteDataBean = undefined + @State rewardDataBeans?: GiftDetailDataBean[] = undefined + @State rewardType?: string = undefined + @State productDataBean?: PLVProductDataBean = this.sdk?.productManager.getData() + @State productWebViewController: PLVProductWebViewController = new PLVProductWebViewController() + @State interactWebViewController: PLVInteractWebViewController = new PLVInteractWebViewController() + + aboutToAppear(): void { + this.initData() + this.onProductData() + this.onRewardData() + this.onChatData() + this.onReplyData() + this.onDelayRedpackData() + } + + aboutToDisappear(): void { + this.offReplyData() + } + + build() { + RelativeContainer() { + // + PLVLIWatchInfoView() + .alignRules({ + top: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Top }, + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start } + }) + .margin({ left: 16 }) + .id('watchInfoView') + // + // + PLVLIBulletinView({ bulletinSrc: this.bulletinSrc }) + .alignRules({ + top: { anchor: 'watchInfoView', align: VerticalAlign.Bottom }, + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start } + }) + .margin({ left: 16, top: 16, right: 16 }) + .id('bulletinView') + // + // + PLVLIInteractEntranceView() + .alignRules({ + top: { anchor: 'bulletinView', align: VerticalAlign.Bottom }, + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start } + }) + .margin({ left: 16, top: 8 }) + .id('interactEntranceView') + // + // + PLVLIToTopView() + .alignRules({ + top: { anchor: 'interactEntranceView', align: VerticalAlign.Bottom }, + middle: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Center } + }) + .margin({ top: 8 }) + .id('toTopView') + // + // + PLVLIGreetingView() + .alignRules({ + bottom: { anchor: 'chatListLayout', align: VerticalAlign.Top }, + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start } + }) + .id('greetingView') + // + // + PLVLIChatListLayout({ + isQuizSelected: this.isQuizSelected, + isFocusModeOpen: this.isFocusModeOpen, + quizMenu: this.quizMenu, + fullChatListData: this.fullChatListData, + chatListScroller: this.chatListScroller, + quizListData: this.quizListData, + quizListScroller: this.quizListScroller + }) + .alignRules({ + bottom: { anchor: 'chatInputEntryView', align: VerticalAlign.Top }, + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start } + }) + .id('chatListLayout') + // + // + PLVLIChatInputEntryView({ + isQuizSelected: this.isQuizSelected, + isShowChatInputLayout: this.isShowChatInputLayout, + quizMenu: this.quizMenu, + isCloseRoom: this.isCloseRoom, + isFocusModeOpen: this.isFocusModeOpen + }) + .alignRules({ + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start }, + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom } + }) + .margin({ left: 16 }) + .id('chatInputEntryView') + // + // + PLVLILikeIconView() + .alignRules({ + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End }, + bottom: { anchor: 'moreView', align: VerticalAlign.Top } + }) + .margin({ right: 16, bottom: 10 }) + .id('likeIconView') + // + // + Row() { + if (this.productDataBean?.isOpenProduct) { + PLVLIProductView({ + productDataBean: this.productDataBean, + productWebViewController: this.productWebViewController + }) + .margin({ right: 12 }) + .id('productView') + } + if (this.rewardDataBeans) { + PLVLIRewardView({ + rewardDataBeans: this.rewardDataBeans, + rewardType: this.rewardType + }) + .margin({ right: 12 }) + .id('rewardView') + } + } + .alignRules({ + right: { anchor: 'moreView', align: HorizontalAlign.Start }, + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom } + }) + .id('rightBottomRow') + + // + // + PLVLIMoreView() + .alignRules({ + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End }, + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom } + }) + .margin({ right: 16 }) + .id('moreView') + // + // + if (this.isShowChatInputLayout) { + PLVLIChatInputView({ + isQuizSelected: this.isQuizSelected, + isShowChatInputLayout: this.isShowChatInputLayout, + fullChatListData: this.fullChatListData, + chatListScroller: this.chatListScroller, + quizListData: this.quizListData, + quizListScroller: this.quizListScroller, + isCloseRoom: this.isCloseRoom, + isFocusModeOpen: this.isFocusModeOpen, + quizMenu: this.quizMenu, + reply: this.reply + }) + .id('chatInputView') + } + // + // + PLVLIProductWebView({ + viewController: this.productWebViewController + }) + .zIndex(99) + .id('productWebView') + // + // + + Column({ space: 10 }) { + PLVLIRedpackView() + PLVLICardPushView() + PLVLILotteryView() + } + .alignItems(HorizontalAlign.End) + .alignRules({ + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End }, + bottom: { anchor: "moreView", align: VerticalAlign.Top } + }) + .margin({ + bottom: 72, + right: 15 + }) + + // + + // + PLVLIInteractWebView({ + viewController: this.interactWebViewController + }) + .zIndex(100) + .hitTestBehavior(this.interactWebViewController.isShow ? HitTestMode.Default : HitTestMode.Transparent) + .id('interactWebView') + // + + } + .padding({ top: 6, bottom: 6 + PLVDeviceUtils.getNavigationIndicatorHeight() }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + + initData() { + this.layoutController.productWebViewController = this.productWebViewController + this.layoutController.interactWebViewController = this.interactWebViewController + } + + onProductData() { + this.sdk?.productManager.eventNotify.on('product_data', (value: PLVProductDataBean) => { + this.productDataBean = value + }, this) + } + + onRewardData() { + this.sdk?.rewardManager.eventNotify.on('reward_setting', (value: PLVRewardSettingVO) => { + // 暂只支持礼物打赏(包含现金支付(仅免费)、积分支付类型) + if (value.getDonateGiftEnabled()) { + this.rewardType = value.giftDonate?.payWay + if ('POINT' === value.giftDonate?.payWay) { + this.rewardDataBeans = value.giftDonate.enabledPointPays + } else if ('CASH' === value.giftDonate?.payWay) { + this.rewardDataBeans = value.giftDonate.enabledCashPays + } + } + }, this) + } + + onChatData() { + this.sdk?.chatroomManager.eventNotify.on('close_room', (value: boolean) => { + this.isCloseRoom = value + if (this.isCloseRoom && !this.isQuizSelected) { + this.isShowChatInputLayout = false + } + PLVToastUtils.shortShow(value ? $r('app.string.plvli_chat_toast_chatroom_close') : $r('app.string.plvli_chat_toast_chatroom_open')) + }, this) + this.sdk?.chatroomManager.eventNotify.on('focus_mode_open', (value: boolean) => { + this.isFocusModeOpen = value + if (this.isFocusModeOpen && !this.isQuizSelected) { + this.isShowChatInputLayout = false + } + }, this) + } + + onReplyData() { + PLVLIEventHub.on('plvli_chat_reply', (reply: PLVChatQuoteDataBean) => { + this.reply = reply + this.isShowChatInputLayout = true + }) + } + + offReplyData() { + PLVLIEventHub.off('plvli_chat_reply') + } + + onDelayRedpackData() { + this.sdk?.redpackManager.updateDelayRedpackStatus(this.sdk.channelData.loginChannelId) + } +} + +export class PLVLiveHomeLayoutController implements IPLVBackwardInterface { + productWebViewController?: PLVProductWebViewController + interactWebViewController?: PLVInteractWebViewController + accessBackward: PLVCallback = () => { + return this.productWebViewController?.accessBackward() || this.interactWebViewController?.accessBackward() || false + } + backward: PLVCallback = () => { + if (this.productWebViewController?.accessBackward()) { + this.productWebViewController.backward() + } + if (this.interactWebViewController?.accessBackward()) { + this.interactWebViewController.isShow = false + this.interactWebViewController.isShowInnerWeb = false + + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/PLVLIPlaybackHomeLayout.ets b/scenes_live/src/main/ets/components/PLVLIPlaybackHomeLayout.ets new file mode 100644 index 0000000..1c8e5c3 --- /dev/null +++ b/scenes_live/src/main/ets/components/PLVLIPlaybackHomeLayout.ets @@ -0,0 +1,125 @@ +import { IPLVBackwardInterface, PLVCallback, PLVChannelData, PLVCommonConstants, PLVDeviceUtils, PLVLiveSceneSDK, PLVProductDataBean } from '@polyvharmony/live-scenes-sdk' +import { PLVLIChatPlaybackLayout } from './chatroom/PLVLIChatPlaybackLayout' +import { PLVLIToTopView } from './chatroom/PLVLIToTopView' +import { PLVLIBulletinView } from './liveroom/PLVLIBulletinView' +import { PLVLIMoreView } from './liveroom/PLVLIMoreView' +import { PLVLIWatchInfoView } from './liveroom/PLVLIWatchInfoView' +import { PLVLIProductView } from './product/PLVLIProductView' +import { PLVLIProductWebView, PLVProductWebViewController } from './product/PLVLIProductWebView' + +const TAG = '[PLVLIPlaybackHomeLayout]' + +@Preview +@Component +export struct PLVLIPlaybackHomeLayout { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @Link layoutController: PLVPlaybackHomeLayoutController + @Link bulletinSrc: string | undefined + @State productDataBean?: PLVProductDataBean = this.sdk?.productManager.getData() + @State productWebViewController: PLVProductWebViewController = new PLVProductWebViewController() + + aboutToAppear(): void { + this.initData() + this.onProductData() + } + + build() { + RelativeContainer() { + // + PLVLIWatchInfoView() + .alignRules({ + top: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Top }, + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start } + }) + .margin({ left: 16 }) + .id('watchInfoView') + // + // + PLVLIBulletinView({ bulletinSrc: this.bulletinSrc }) + .alignRules({ + top: { anchor: 'watchInfoView', align: VerticalAlign.Bottom }, + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start } + }) + .margin({ left: 16, top: 16, right: 16 }) + .id('bulletinView') + // + // + PLVLIToTopView() + .alignRules({ + top: { anchor: 'bulletinView', align: VerticalAlign.Bottom }, + middle: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Center } + }) + .margin({ top: 8 }) + .id('toTopView') + // + // + PLVLIChatPlaybackLayout() + .alignRules({ + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom }, + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start } + }) + .margin({ bottom: 128 }) + .id('chatListLayout') + // + // + Row() { + if (this.productDataBean?.isOpenProduct) { + PLVLIProductView({ + productDataBean: this.productDataBean, + productWebViewController: this.productWebViewController + }) + .margin({ right: 12 }) + .id('productView') + } + } + .alignRules({ + right: { anchor: 'moreView', align: HorizontalAlign.Start }, + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom } + }) + .id('rightBottomRow') + // + // + PLVLIMoreView() + .alignRules({ + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End }, + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom } + }) + .margin({ right: 16 }) + .id('moreView') + // + // + PLVLIProductWebView({ + viewController: this.productWebViewController + }) + .zIndex(99) + .id('productWebView') + // + } + .padding({ top: 6, bottom: 6 + PLVDeviceUtils.getNavigationIndicatorHeight() }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + + initData() { + this.layoutController.productWebViewController = this.productWebViewController + } + + onProductData() { + this.sdk?.productManager.eventNotify.on('product_data', (value: PLVProductDataBean) => { + this.productDataBean = value + }, this) + } +} + +export class PLVPlaybackHomeLayoutController implements IPLVBackwardInterface { + productWebViewController?: PLVProductWebViewController + accessBackward: PLVCallback = () => { + return this.productWebViewController?.accessBackward() || false + } + backward: PLVCallback = () => { + if (this.productWebViewController?.accessBackward()) { + this.productWebViewController.backward() + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/PLVLIWatchLayout.ets b/scenes_live/src/main/ets/components/PLVLIWatchLayout.ets new file mode 100644 index 0000000..cbaf65b --- /dev/null +++ b/scenes_live/src/main/ets/components/PLVLIWatchLayout.ets @@ -0,0 +1,214 @@ +import { + IPLVBackwardInterface, + PLVBulletinEvent, + PLVCallback, + PLVChannelData, + PLVCommonConstants, + PLVDeviceUtils, + PLVJSONUtils, + PLVLiveSceneSDK, + PLVLoginType, + PLVRemoveBulletinEvent, + PLVSocketOnEvent, + PLVSocketStatus, + PLVSocketStatusSup, + PLVToastUtils, + PLVUtils +} from '@polyvharmony/live-scenes-sdk' +import { router } from '@kit.ArkUI' +import { PLVDetailLayoutController, PLVLIDetailLayout } from '../components/PLVLIDetailLayout' +import { PLVLILiveHomeLayout, PLVLiveHomeLayoutController } from '../components/PLVLILiveHomeLayout' +import { PLVLIPlaybackHomeLayout, PLVPlaybackHomeLayoutController } from '../components/PLVLIPlaybackHomeLayout' +import { PLVLIPlayerLayout } from './player/PLVLIPlayerLayout' +import { common } from '@kit.AbilityKit' + +const TAG = '[PLVLIWatchLayout]' + +@Preview +@Component +export struct PLVLIWatchLayout { + @Link layoutController: PLVWatchLayoutController + @State bulletinSrc: string | undefined = '' + @State liveHomeLayoutController: PLVLiveHomeLayoutController = new PLVLiveHomeLayoutController() + @State playbackHomeLayoutController: PLVPlaybackHomeLayoutController = new PLVPlaybackHomeLayoutController() + @State detailLayoutController: PLVDetailLayoutController = new PLVDetailLayoutController() + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + + override aboutToAppear(): void { + this.initData() + this.onChatData() + this.loginSocket() + } + + override aboutToDisappear(): void { + this.sdk?.destroy() + } + + build() { + Stack() { + // player layout + Stack() { + PLVLIPlayerLayout() + } + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + .backgroundColor(Color.Gray) + + // tabs layout + Tabs({ index: 1 }) { + TabContent() { + PLVLIDetailLayout({ layoutController: this.detailLayoutController, bulletinSrc: this.bulletinSrc }) + } + + TabContent() { + if (PLVLoginType.LIVE == this.channelData?.loginType) { + PLVLILiveHomeLayout({ layoutController: this.liveHomeLayoutController, bulletinSrc: this.bulletinSrc}) + } else { + PLVLIPlaybackHomeLayout({ layoutController: this.playbackHomeLayoutController, bulletinSrc: this.bulletinSrc }) + } + } + + TabContent() { + } + } + .onChange((index) => { + this.detailLayoutController.isLayoutShow = index == 0 + }) + .barHeight(0) + .padding({ top: PLVDeviceUtils.getStatusBarHeight() }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + .hitTestBehavior(HitTestMode.Transparent) + .gesture(SwipeGesture()) + + // close widget + Stack({ alignContent: Alignment.TopEnd }) { + Image($r('app.media.plvli_close')) + .width(48) + .height(48) + .padding(8) + .margin({ right: 8 }) + .draggable(false) + .onClick(() => { + router.back() + }) + } + .padding({ top: PLVDeviceUtils.getStatusBarHeight() }) + .hitTestBehavior(HitTestMode.None) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + + initData() { + this.layoutController.detailLayoutController = this.detailLayoutController + this.layoutController.liveHomeLayoutController = this.liveHomeLayoutController + this.layoutController.playbackHomeLayoutController = this.playbackHomeLayoutController + // 初始化sdk数据,如果当前模块为har类型,传getContext();如果为hsp类型,则需要传getContext().createModuleContext('模块名') + // 因sdk内部需要读取rawfile,因此这里需要传入模块的context + this.sdk?.initData(getContext() as common.UIAbilityContext) + } + + onChatData() { + this.sdk?.chatroomManager.onData(PLVSocketOnEvent.MESSAGE, (data: string, event: string) => { + switch (event) { + // 公告 + case PLVBulletinEvent.EVENT: + const bulletinEvent = PLVJSONUtils.json2Bean(PLVBulletinEvent, data) + this.bulletinSrc = PLVUtils.undefinedToValue(bulletinEvent?.content, (value) => { + this.bulletinSrc = value + }) + break; + // 移除公告 + case PLVRemoveBulletinEvent.EVENT: + this.bulletinSrc = undefined + break; + default: + break; + } + }, this) + this.sdk?.chatroomManager.eventNotify.on('restrict_max_viewer', () => { + PLVToastUtils.longShow($r('app.string.plvli_chat_restrict_max_viewer_hint')) + router.back() + }, this) + } + + loginSocket() { + this.sdk?.socketManager.onStatus((status: PLVSocketStatusSup) => { + switch (status.socketStatus) { + case PLVSocketStatus.LOGGING: + PLVToastUtils.shortShow($r('app.string.plvli_chat_toast_logging')) + break; + case PLVSocketStatus.LOGIN_SUCCESS: + PLVToastUtils.shortShow($r('app.string.plvli_chat_toast_login_success')) + break; + case PLVSocketStatus.RECONNECTING: + PLVToastUtils.shortShow($r('app.string.plvli_chat_toast_reconnecting')) + break; + case PLVSocketStatus.RECONNECT_SUCCESS: + PLVToastUtils.shortShow($r('app.string.plvli_chat_toast_reconnect_success')) + break; + case PLVSocketStatus.LOGIN_FAIL: + getContext().resourceManager.getStringValue($r('app.string.plvli_chat_toast_login_failed')) + .then((value) => { + PLVToastUtils.shortShow(`${value}: message=${status.error?.message}`) + }) + break; + case PLVSocketStatus.BE_KICKED_OUT: + PLVToastUtils.longShow($r('app.string.plvli_chat_toast_been_kicked')) + router.back() + break + case PLVSocketStatus.LOGIN_REFUSE: + this.showExitDialog($r('app.string.plvli_chat_toast_been_kicked')) + break + case PLVSocketStatus.LOGIN_ELSEWHERE: + PLVToastUtils.longShow($r('app.string.plvli_chat_toast_account_login_elsewhere')) + setTimeout(() => { + router.back() + }, 3000) + break + default: + break; + } + }, this) + this.sdk?.socketManager.login() + } + + showExitDialog(message: Resource) { + AlertDialog.show({ + title: $r('app.string.plvli_common_dialog_tip_warm'), + message: message, + autoCancel: false, + alignment: DialogAlignment.Center, + onWillDismiss: () => { + }, + confirm: { + value: $r('app.string.plvli_common_dialog_confirm'), + action: () => { + router.back() + } + } + }) + } +} + +export class PLVWatchLayoutController implements IPLVBackwardInterface { + detailLayoutController?: PLVDetailLayoutController + liveHomeLayoutController?: PLVLiveHomeLayoutController + playbackHomeLayoutController?: PLVPlaybackHomeLayoutController + accessBackward: PLVCallback = () => { + return this.detailLayoutController?.accessBackward() || this.liveHomeLayoutController?.accessBackward() || this.playbackHomeLayoutController?.accessBackward() || false + } + backward: PLVCallback = () => { + if (this.detailLayoutController?.accessBackward()) { + this.detailLayoutController.backward() + } else if (this.liveHomeLayoutController?.accessBackward()) { + this.liveHomeLayoutController.backward() + } else if (this.playbackHomeLayoutController?.accessBackward()) { + this.playbackHomeLayoutController.backward() + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/chatroom/PLVLIChatInputEntryView.ets b/scenes_live/src/main/ets/components/chatroom/PLVLIChatInputEntryView.ets new file mode 100644 index 0000000..fee0705 --- /dev/null +++ b/scenes_live/src/main/ets/components/chatroom/PLVLIChatInputEntryView.ets @@ -0,0 +1,60 @@ +import { ChannelMenusBean, PLVCommonConstants } from '@polyvharmony/live-scenes-sdk' + +@Preview +@Component +export struct PLVLIChatInputEntryView { + @Link isQuizSelected: boolean + @Link isShowChatInputLayout: boolean + @Link quizMenu: ChannelMenusBean | undefined + @Link isCloseRoom: boolean + @Link isFocusModeOpen: boolean + + build() { + Row() { + Text() { + ImageSpan($r('app.media.plvli_chat_msg')) + .width(14) + .height(14) + .margin({ right: 6, left: 8 }) + .verticalAlign(ImageSpanAlignment.CENTER) + Span(this.inputEntryText()) + .fontSize(14) + .fontColor('#99ffffff') + .width(PLVCommonConstants.FULL_PERCENT) + } + .layoutWeight(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(1) + + if (this.quizMenu) { + Image(this.isQuizSelected ? $r('app.media.plvli_chat_quiz_selected') : $r('app.media.plvli_chat_quiz_default')) + .width(26) + .height(26) + .padding(4) + .margin({ right: 4 }) + .draggable(false) + .onClick(() => { + this.isQuizSelected = !this.isQuizSelected + }) + } + } + .onClick(() => { + if ((!this.isCloseRoom && !this.isFocusModeOpen) || this.isQuizSelected) { + this.isShowChatInputLayout = true + } + }) + .backgroundColor('#73000000') + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.End) + .borderRadius(100) + .height(32) + .constraintSize({ minWidth: 150, maxWidth: 180 }) + } + + inputEntryText() { + return this.isQuizSelected ? $r('app.string.plvli_chat_input_tips_quiz') : + this.isCloseRoom ? $r('app.string.plvli_chat_input_tips_chatroom_close') : + this.isFocusModeOpen ? $r('app.string.plvli_chat_input_tips_focus') : + $r('app.string.plvli_chat_input_tips_chat') + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/chatroom/PLVLIChatInputView.ets b/scenes_live/src/main/ets/components/chatroom/PLVLIChatInputView.ets new file mode 100644 index 0000000..622bafe --- /dev/null +++ b/scenes_live/src/main/ets/components/chatroom/PLVLIChatInputView.ets @@ -0,0 +1,217 @@ +import { + ChannelMenusBean, + PLVBaseIdEvent, + PLVChatQuoteDataBean, + PLVChatroomError, + PLVChatroomErrorCode, + PLVCommonConstants, + PLVDeviceUtils, + PLVLazyDataSource, + PLVLiveSceneSDK, + PLVLocalQuizEvent, + PLVLocalSpeakEvent, + PLVLogger, + PLVToastUtils +} from '@polyvharmony/live-scenes-sdk' + +const TAG = '[PLVLIChatInputView]' + +@Preview +@Component +export struct PLVLIChatInputView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + chatListScroller: ListScroller = new ListScroller() + @Link fullChatListData: PLVLazyDataSource + quizListScroller: ListScroller = new ListScroller() + @Link quizListData: PLVLazyDataSource + @Link @Watch('onInputStatusChanged') isQuizSelected: boolean + @Link isShowChatInputLayout: boolean + @Link @Watch('onInputStatusChanged') isCloseRoom: boolean + @Link @Watch('onInputStatusChanged') isFocusModeOpen: boolean + @Link quizMenu: ChannelMenusBean | undefined + @Link reply: PLVChatQuoteDataBean | undefined + @LocalStorageLink('chatInputText') @Watch('onChatInputTextChanged') chatInputText: string = '' + tempChatInputText: string = '' + @State offsetY: number = 6 + PLVDeviceUtils.getNavigationIndicatorHeight() + + build() { + Column() { + if (this.reply && !this.isQuizSelected) { + Row() { + Text(this.replyText()) + .fontSize(12) + .fontColor('#F0F1F5') + .wordBreak(WordBreak.BREAK_ALL) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(1) + .constraintSize({ maxWidth: '85%' }) + .padding({ left: 16, right: 10 }) + Blank() + Image($r('app.media.plvli_chat_quote_message_close_icon')) + .width(32) + .height(32) + .padding(6) + .margin({ right: 4 }) + .draggable(false) + .onClick(() => { + this.reply = undefined + }) + } + .onTouch((event) => { + event.stopPropagation() + }) + .translate({ y: this.offsetY }) + .width(PLVCommonConstants.FULL_PERCENT) + .alignItems(VerticalAlign.Center) + .height(48) + .backgroundColor('#66000000') + .backdropBlur(8) + } + + Row() { + if (this.quizMenu) { + Image(this.isQuizSelected ? $r('app.media.plvli_chat_quiz_selected') : $r('app.media.plvli_chat_quiz_default')) + .width(36) + .height(36) + .padding(4) + .margin({ right: 4 }) + .draggable(false) + .onClick(() => { + this.isQuizSelected = !this.isQuizSelected + }) + } + TextInput({ placeholder: this.inputPlaceText(), text: $$this.chatInputText }) + .height(38) + .layoutWeight(1) + .enterKeyType(EnterKeyType.Send) + .onSubmit((key, event) => { + event.keepEditableState() + if (this.isQuizSelected) { + this.quiz() + } else { + this.speak() + } + }) + .onAppear(() => { + focusControl.requestFocus('chatTextInput') + }) + .fontSize(14) + .enabled((!this.isCloseRoom && !this.isFocusModeOpen) || this.isQuizSelected) + .padding({ left: 16, right: 16 }) + .placeholderColor('#33ffffff') + .fontColor('#ffffff') + .maxLength(200) + .maxLines(1) + .backgroundColor('#373635') + .borderRadius(18) + .id('chatTextInput') + Text($r('app.string.plvli_chat_send')) + .padding(8) + .margin({ left: 6 }) + .fontSize(14) + .fontColor(this.chatInputText ? '#FFA611' : '#66F0F1F5') + .enabled(this.chatInputText ? true : false) + .onClick(() => { + if (this.isQuizSelected) { + this.quiz() + } else { + this.speak() + } + }) + } + .onTouch((event) => { + event.stopPropagation() + }) + .translate({ y: this.offsetY }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(68) + .alignItems(VerticalAlign.Center) + .padding({ left: 8, right: 8 }) + .backgroundColor('#262523') + } + .onTouch(() => { + this.isShowChatInputLayout = false + }) + .justifyContent(FlexAlign.End) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + + private speak() { + if (this.chatInputText.trim().length == 0) { + PLVToastUtils.shortShow($r('app.string.plvli_chat_toast_send_text_empty')) + return + } + const handleSendSuccess = (event: PLVLocalSpeakEvent) => { + this.fullChatListData.pushData(event) + this.chatListScroller.scrollEdge(Edge.Bottom) + this.isShowChatInputLayout = false + this.chatInputText = '' + this.reply = undefined + } + this.sdk?.chatroomManager.speak(this.chatInputText, this.reply) + .then((speakEvent) => { + handleSendSuccess(speakEvent) + }) + .catch((err: PLVChatroomError) => { + PLVLogger.printError(TAG, `speak fail`, err as Error) + if (PLVChatroomErrorCode.USER_IS_BANNED == err.code) { + // 被禁言也认为发言成功,并添加到聊天列表中,但不会广播给其他用户 + handleSendSuccess(err.data as PLVLocalSpeakEvent) + } else { + PLVToastUtils.shortShow(`speak fail: ${err.message}`) + } + }) + } + + private quiz() { + if (this.chatInputText.trim().length == 0) { + PLVToastUtils.shortShow($r('app.string.plvli_chat_toast_send_text_empty')) + return + } + const handleSendSuccess = (event: PLVLocalQuizEvent) => { + this.quizListData.pushData(event) + this.quizListScroller.scrollEdge(Edge.Bottom) + this.isShowChatInputLayout = false + this.chatInputText = '' + } + this.sdk?.chatroomManager.quiz(this.chatInputText) + .then((event) => { + handleSendSuccess(event) + }) + .catch((err: PLVChatroomError) => { + PLVLogger.printError(TAG, `quiz fail`, err as Error) + PLVToastUtils.shortShow(`quiz fail: ${err.message}`) + }) + } + + replyText() { + let content = this.reply?.content + if (this.reply?.isChatImgEvent()) { + content = '[图片]' + } else if (this.reply?.isFileShareEvent()) { + content = this.reply?.fileData?.name + } + return `${this.reply?.nick}: ${content}` + } + + inputPlaceText() { + return this.isQuizSelected ? $r('app.string.plvli_chat_input_tips_quiz') : + this.isCloseRoom ? $r('app.string.plvli_chat_input_tips_chatroom_close') : + this.isFocusModeOpen ? $r('app.string.plvli_chat_input_tips_focus') : + $r('app.string.plvli_chat_input_tips_chat') + } + + onInputStatusChanged(propName: string) { + if ((this.isCloseRoom || this.isFocusModeOpen) && !this.isQuizSelected) { + this.tempChatInputText = this.chatInputText + } + this.chatInputText = ((!this.isCloseRoom && !this.isFocusModeOpen) || this.isQuizSelected) ? this.tempChatInputText : '' + } + + onChatInputTextChanged() { + if ((!this.isCloseRoom && !this.isFocusModeOpen) || this.isQuizSelected) { + this.tempChatInputText = this.chatInputText + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/chatroom/PLVLIChatListItem.ets b/scenes_live/src/main/ets/components/chatroom/PLVLIChatListItem.ets new file mode 100644 index 0000000..025ee50 --- /dev/null +++ b/scenes_live/src/main/ets/components/chatroom/PLVLIChatListItem.ets @@ -0,0 +1,789 @@ +import { + IPLVCanOverLenEvent, + PLVBaseIdEvent, + PLVCallback, + PLVChannelData, + PLVChatImgEvent, + PLVChatPlaybackDataVO, + PLVChatQuoteDataBean, + PLVCommonConstants, + PLVEmotionEvent, + PLVHistoryChatImgEvent, + PLVHistoryFileShareEvent, + PLVHistoryRedPaperEvent, + PLVHistorySpeakEvent, + PLVImageBean, + PLVLiveSceneSDK, + PLVLocalQuizEvent, + PLVLocalQuizPromptEvent, + PLVLocalSpeakEvent, + PLVLogger, + PLVPPTShareFileDataBean, + PLVRedPaperEvent, + PLVRedPaperReceiveType, + PLVRewardEvent, + PLVSocketUserBean, + PLVSpeakEvent, + PLVTAnswerEvent, + PLVToastUtils, + PLVUserType, + PLVUtils, + PLVWebUtils, + RedPaperReceiveTypeEvent +} from '@polyvharmony/live-scenes-sdk' +import { PLVLIEventHub } from '../../common/PLVLIEventHub' +import PLVLIFaceManager from '../../common/PLVLIFaceManager' +import { PLVLIChatOverLengthMessageDialog } from './PLVLIChatOverLengthMessageDialog' + +const TAG = '[PLVLIChatListItem]' + +@Preview +@Component +export struct PLVLIChatListItem { + @ObjectLink itemData: PLVBaseIdEvent + + build() { + if (this.itemData instanceof PLVSpeakEvent) { // 在线发言/在线文件分享 + PLVChatSpeakView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVChatImgEvent) { // 在线图片 + PLVChatImgView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVLocalSpeakEvent) { // 本地发言 + PLVChatLocalSpeakView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVLocalQuizEvent) { // 本地提问 + PLVChatLocalQuizView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVTAnswerEvent) { // 在线回答/历史提问和回答 + PLVChatTAnswerView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVLocalQuizPromptEvent) { // 本地提问提示语 + PLVChatLocalQuizPromptView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVHistorySpeakEvent) { // 历史发言 + PLVChatHistorySpeakView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVHistoryChatImgEvent) { // 历史图片/历史个性表情 + PLVChatHistoryChatImgView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVRewardEvent) { // 在线打赏/历史打赏 + PLVChatRewardView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVEmotionEvent) { // 在线个性表情 + PLVChatEmotionView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVHistoryFileShareEvent) { // 历史文件分享 + PLVChatHistoryFileShareView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVRedPaperEvent + || this.itemData instanceof PLVHistoryRedPaperEvent) { // 在线口令红包/历史口令红包 + PLVChatRedPaperView({ itemData: this.itemData }) + } else if (this.itemData instanceof PLVChatPlaybackDataVO) { // 聊天回放消息 + PLVChatPlaybackView({ itemData: this.itemData }) + } + } +} + +@Component +export struct PLVChatSpeakView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @ObjectLink itemData: PLVSpeakEvent + @State nick: string | Resource | undefined = undefined + @State actor: string | undefined = undefined + @State userType: string | undefined = undefined + @State fullMessage: string | undefined = undefined + moreDialogController?: CustomDialogController = new CustomDialogController({ + builder: PLVLIChatOverLengthMessageDialog({ + nick: this.nick, + actor: this.actor, + userType: this.userType, + fullMessage: this.fullMessage + }), + alignment: DialogAlignment.Bottom, + customStyle: true + }) + + aboutToAppear(): void { + this.nick = generateNickByUser(this.itemData.user) + this.actor = this.itemData.user?.getActor() + this.userType = this.itemData.user?.userType + } + + build() { + messageView( + this.itemData.isFileShareEvent() ? this.itemData.fileData?.name : this.itemData.values?.[0], + this.actor, + this.userType, + this.nick, + this.itemData.isFileShareEvent() ? toImageBeanForFileShare(this.itemData.fileData) : undefined, + this.itemData.quote, + this.sdk?.chatroomManager.quoteReplyEnabled ? this.itemData._replyQuoteBean.get() : undefined, + this.itemData.parsedData, + this.itemData.isFileShareEvent() ? this.itemData.fileData?.url : undefined, + this.itemData._isOverLengthFoldingMessage.get(), + overLengthCopyClick(this.itemData.values?.[0], this.itemData, this.sdk), + overLengthMoreClick(this.itemData.values?.[0], this.itemData, this.sdk, (fullMessage: string) => { + this.fullMessage = fullMessage + this.moreDialogController?.open() + }) + ) + } +} + +@Component +export struct PLVChatImgView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @ObjectLink itemData: PLVChatImgEvent + + build() { + messageView( + undefined, + this.itemData.user?.getActor(), + this.itemData.user?.userType, + generateNickByUser(this.itemData.user), + PLVImageBean.toImageBeanForChatImg(this.itemData.values?.[0]), + undefined, + this.sdk?.chatroomManager.quoteReplyEnabled ? this.itemData._replyQuoteBean.get() : undefined + ) + } +} + +@Component +export struct PLVChatLocalSpeakView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @ObjectLink itemData: PLVLocalSpeakEvent + @State nick: string | Resource | undefined = undefined + @State actor: string | undefined = undefined + @State userType: string | undefined = undefined + @State fullMessage: string | undefined = undefined + moreDialogController?: CustomDialogController = new CustomDialogController({ + builder: PLVLIChatOverLengthMessageDialog({ + nick: this.nick, + actor: this.actor, + userType: this.userType, + fullMessage: this.fullMessage + }), + alignment: DialogAlignment.Bottom, + customStyle: true + }) + + aboutToAppear(): void { + this.nick = generateNickByChannelData(this.channelData) + this.actor = this.channelData?.viewerActor + this.userType = this.channelData?.viewerType + } + + build() { + messageView( + this.itemData.speakMessage, + this.actor, + this.userType, + this.nick, + undefined, + this.itemData.quote, + this.sdk?.chatroomManager.quoteReplyEnabled ? this.itemData._replyQuoteBean.get() : undefined, + this.itemData.parsedData, + undefined, + this.itemData._isOverLengthFoldingMessage.get(), + overLengthCopyClick(this.itemData.speakMessage, undefined, this.sdk), + overLengthMoreClick(this.itemData.speakMessage, undefined, this.sdk, (fullMessage: string) => { + this.fullMessage = fullMessage + this.moreDialogController?.open() + }) + ) + } +} + +@Component +export struct PLVChatLocalQuizView { + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @ObjectLink itemData: PLVLocalQuizEvent + + build() { + messageView( + this.itemData.quizMessage, + this.channelData?.viewerActor, + this.channelData?.viewerType, + generateNickByChannelData(this.channelData), + undefined, + undefined, + undefined, + this.itemData.parsedData + ) + } +} + +@Component +export struct PLVChatTAnswerView { + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @ObjectLink itemData: PLVTAnswerEvent + + build() { + messageView( + this.itemData.isImgType() ? undefined : this.itemData.content, + this.itemData.user?.getActor(), + this.itemData.user?.userType, + generateNick(this.itemData.user, this.channelData), + this.itemData.isImgType() ? this.itemData.image : undefined, + undefined, + undefined, + this.itemData.parsedData + ) + } +} + +@Component +export struct PLVChatLocalQuizPromptView { + @ObjectLink itemData: PLVLocalQuizPromptEvent + + build() { + promptView(this.itemData.prompt) + } +} + +@Component +export struct PLVChatHistorySpeakView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @ObjectLink itemData: PLVHistorySpeakEvent + @State nick: string | Resource | undefined = undefined + @State actor: string | undefined = undefined + @State userType: string | undefined = undefined + @State fullMessage: string | undefined = undefined + moreDialogController?: CustomDialogController = new CustomDialogController({ + builder: PLVLIChatOverLengthMessageDialog({ + nick: this.nick, + actor: this.actor, + userType: this.userType, + fullMessage: this.fullMessage + }), + alignment: DialogAlignment.Bottom, + customStyle: true + }) + + aboutToAppear(): void { + this.nick = generateNick(this.itemData.user, this.channelData) + this.actor = this.itemData.user?.getActor() + this.userType = this.itemData.user?.userType + } + + build() { + messageView( + this.itemData.content, + this.actor, + this.userType, + this.nick, + undefined, + this.itemData.quote, + this.sdk?.chatroomManager.quoteReplyEnabled ? this.itemData._replyQuoteBean.get() : undefined, + this.itemData.parsedData, + undefined, + this.itemData._isOverLengthFoldingMessage.get(), + overLengthCopyClick(this.itemData.content, this.itemData, this.sdk), + overLengthMoreClick(this.itemData.content, this.itemData, this.sdk, (fullMessage: string) => { + this.fullMessage = fullMessage + this.moreDialogController?.open() + }) + ) + } +} + +@Component +export struct PLVChatHistoryChatImgView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @ObjectLink itemData: PLVHistoryChatImgEvent + + build() { + messageView( + undefined, + this.itemData.user?.getActor(), + this.itemData.user?.userType, + generateNick(this.itemData.user, this.channelData), + PLVImageBean.toImageBeanForChatImg(this.itemData.content), + undefined, + this.sdk?.chatroomManager.quoteReplyEnabled ? this.itemData._replyQuoteBean.get() : undefined + ) + } +} + +@Component +export struct PLVChatRewardView { + @ObjectLink itemData: PLVRewardEvent + + build() { + rewardView(this.itemData) + } +} + +@Component +export struct PLVChatEmotionView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @ObjectLink itemData: PLVEmotionEvent + + build() { + messageView( + undefined, + this.itemData.user?.getActor(), + this.itemData.user?.userType, + generateNickByUser(this.itemData.user), + PLVImageBean.toImageBeanForEmotion(this.itemData, this.sdk?.chatroomManager.emotions), + undefined, + this.sdk?.chatroomManager.quoteReplyEnabled ? this.itemData._replyQuoteBean.get(this.sdk?.chatroomManager.emotions) : undefined + ) + } +} + +@Component +export struct PLVChatHistoryFileShareView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @ObjectLink itemData: PLVHistoryFileShareEvent + + build() { + messageView( + this.itemData.fileData?.name, + this.itemData.user?.getActor(), + this.itemData.user?.userType, + generateNickByUser(this.itemData.user), + toImageBeanForFileShare(this.itemData.fileData), + undefined, + this.sdk?.chatroomManager.quoteReplyEnabled ? this.itemData._replyQuoteBean.get() : undefined, + undefined, + this.itemData.fileData?.url + ) + } +} + +@Component +export struct PLVChatRedPaperView { + @ObjectLink itemData: PLVRedPaperEvent + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + + aboutToAppear(): void { + this.sdk?.redpackManager.geLocalCachedReceiveStatus(this.itemData.redpackId) + .then((value) => { + this.itemData.receiveType = PLVRedPaperReceiveType.getValue(value as RedPaperReceiveTypeEvent) + this.sdk?.redpackManager.cacheRedPaper(this.itemData) + }) + } + + build() { + redPaperView( + this.itemData, + () => { + this.sdk?.interactManager?.receiveRedPaper(this.itemData) + } + ) + } +} + +@Component +export struct PLVChatPlaybackView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @Link itemData: PLVChatPlaybackDataVO + + build() { + messageView( + this.itemData.isImgMsgType() ? undefined : this.itemData.content, + this.itemData.actor, + this.itemData.userType, + generateNick(this.itemData.user, this.channelData), + this.itemData.isImgMsgType() ? PLVImageBean.toImageBeanForChatImg(this.itemData.chatImgContent) : undefined, + this.itemData.chatQuote, + undefined, + this.itemData.parsedData + ) + } +} + +/** + * 聊天消息View + * @param speakMsg 发言内容/提问内容/回答内容/文件分享的文件名 + * @param actor 用户头衔 + * @param userType 用户类型 + * @param nick 用户昵称 + * @param imgBean 图片/个性表情/文件分享的图片 + * @param quote 消息是否包含回复内容 + * @param reply 消息是否可以被回复的内容 + * @param parsedData 将speakMsg按表情文本解析后的数据 + * @param fileUrl 文件分享的url + * @param isOverLengthFoldingMessage 是否超长消息 + * @param overLengthCopyClick 超长消息复制点击回调 + * @param overLengthMoreClick 超长消息更多点击回调 + */ +@Builder +function messageView( + speakMsg?: string, + actor?: string, + userType?: string, + nick?: string | Resource, + imgBean?: PLVImageBean, + quote?: PLVChatQuoteDataBean, + reply?: PLVChatQuoteDataBean, + parsedData?: string[], + fileUrl?: string, + isOverLengthFoldingMessage?: boolean, + overLengthCopyClick?: PLVCallback, + overLengthMoreClick?: PLVCallback +) { + Column() { + ColumnSplit() { + Row() { + Text() { + if (actor && PLVSocketUserBean.isSpecialType(userType)) { + Span(` ${actor} `) + .fontColor('#ffffff') + .textBackgroundStyle({ color: getActorBgByUserType(userType), radius: 6 }) + .fontSize(10) + } + Span(nick) + .fontColor('#FFD16B') + .fontSize(12) + if (parsedData) { + ForEach(parsedData, (data: string) => { + if (typeof PLVLIFaceManager.getFace(data) === 'string') { + Span(PLVLIFaceManager.getFace(data)) + .fontColor('#ffffff') + .fontSize(12) + } else { + ImageSpan(PLVLIFaceManager.getFace(data)) + .width(18) + .height(18) + .verticalAlign(ImageSpanAlignment.CENTER) + } + }) + } else if (speakMsg) { + Span(speakMsg) + .fontColor('#ffffff') + .fontSize(12) + } + } + .maxLines(speakMsg && isOverLengthFoldingMessage ? 5 : Infinity) + .constraintSize({ maxWidth: imgBean ? 166 : Infinity }) + + imageView(imgBean, fileUrl ? 8 : 0) + } + .alignItems(VerticalAlign.Top) + .justifyContent(FlexAlign.Start) + + if (speakMsg && isOverLengthFoldingMessage) { + Row() { + Text($r('app.string.plvli_chat_copy')) + .height(PLVCommonConstants.FULL_PERCENT) + .textAlign(TextAlign.Center) + .fontColor('#CCFFFEFC') + .margin({ left: 8, right: 8 }) + .fontSize(14) + .layoutWeight(1) + .onClick(() => { + overLengthCopyClick?.() + }) + Divider() + .color('#4DD8D8D8') + .height(15) + .strokeWidth(1) + .vertical(true) + Text($r('app.string.plvli_chat_more')) + .height(PLVCommonConstants.FULL_PERCENT) + .textAlign(TextAlign.Center) + .fontColor('#CCFFFEFC') + .margin({ left: 8, right: 8 }) + .fontSize(14) + .layoutWeight(1) + .onClick(() => { + overLengthMoreClick?.() + }) + } + .height(28) + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Start) + } + + if (quote) { + Row() { + Text() { + Span(quote.nick + ': ') + .fontColor('#CCCCCCCC') + .fontSize(12) + if (quote.isTextEvent()) { + if (quote.parsedData) { + ForEach(quote.parsedData, (data: string) => { + if (typeof PLVLIFaceManager.getFace(data) === 'string') { + Span(PLVLIFaceManager.getFace(data)) + .fontColor('#CCCCCCCC') + .fontSize(12) + } else { + ImageSpan(PLVLIFaceManager.getFace(data)) + .width(18) + .height(18) + .verticalAlign(ImageSpanAlignment.CENTER) + } + }) + } else { + Span(quote.content) + .fontColor('#CCCCCCCC') + .fontSize(12) + } + } else if (quote.isFileShareEvent()) { + Span(quote.fileData?.name) + .fontColor('#CCCCCCCC') + .fontSize(12) + } + } + .maxLines(2) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .constraintSize({ maxWidth: !quote.isTextEvent() ? 166 : Infinity }) + + if (quote.isChatImgEvent()) { + imageView(quote.image) + } else if (quote.isFileShareEvent()) { + imageView(toImageBeanForFileShare(quote.fileData), 8) + } + } + .alignItems(VerticalAlign.Top) + .justifyContent(FlexAlign.Start) + } + } + .hitTestBehavior(HitTestMode.None) + .divider({ startMargin: 4, endMargin: 4 }) + } + .padding({ + left: 8, + right: 8, + top: 4, + bottom: 4 + }) + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Start) + .constraintSize({ minHeight: 20 }) + .backgroundColor('#A8333333') + .borderRadius(6) + .bindContextMenu(((speakMsg && !isOverLengthFoldingMessage) || reply) ? menuView(speakMsg, reply, isOverLengthFoldingMessage) : undefined, ResponseType.LongPress, { + backgroundBlurStyle: BlurStyle.NONE, + backgroundColor: '#BF1B202D' + }) + .onClick(() => { + if (fileUrl) { + PLVWebUtils.openWebLink(fileUrl) + } + }) +} + +@Builder +function menuView(speakMsg?: string, reply?: PLVChatQuoteDataBean, isOverLengthFoldingMessage?: boolean) { + Column() { + ColumnSplit() { + if (speakMsg && !isOverLengthFoldingMessage) { + Button($r('app.string.plvli_chat_copy')) + .backgroundColor(Color.Transparent) + .fontColor('#F0F1F5') + .fontSize(12) + .onClick(() => { + PLVUtils.copyText(speakMsg).then(() => { + PLVToastUtils.shortShow($r('app.string.plvli_chat_copy_success')) + }) + }) + } + if (reply) { + Button($r('app.string.plvli_chat_answer')) + .backgroundColor(Color.Transparent) + .fontColor('#F0F1F5') + .fontSize(12) + .onClick(() => { + PLVLIEventHub.emit(`plvli_chat_reply`, reply) + }) + } + } + .divider({ startMargin: 2, endMargin: 2 }) + } + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Start) +} + +@Builder +function imageView(imgBean?: PLVImageBean, marginLeft?: number) { + if (imgBean) { + Image(imgBean.url) + .alt($r('app.media.plvli_img_site')) + .width(adjustChatImgWH(imgBean.width, imgBean.height, 64, 36)[0]) + .height(adjustChatImgWH(imgBean.width, imgBean.height, 64, 36)[1]) + .margin({ left: marginLeft ? marginLeft : 0 }) + .objectFit(ImageFit.Auto) + .autoResize(true) + .borderRadius(6) + .draggable(false) + .onError((err) => { + PLVLogger.printError(TAG, `chat image url=${imgBean.url} load error`, new Error(err.message)) + }) + } +} + +@Builder +function promptView(prompt?: string | Resource) { + Text(prompt) + .fontColor('#FFD16B') + .fontSize(12) + .constraintSize({ minHeight: 20 }) + .padding({ + left: 8, + right: 8, + top: 4, + bottom: 4 + }) + .backgroundColor('#A8333333') + .borderRadius(6) +} + +@Builder +function rewardView(rewardEvent: PLVRewardEvent) { + Text() { + Span(rewardEvent.content?.unick + ' ') + .fontSize(12) + .fontColor('#ffffff') + Span($r('app.string.plvli_reward_give', ' ')) + .fontSize(12) + .fontColor('#ffffff') + ImageSpan(rewardEvent.content?.getGimg()) + .width(20) + .height(20) + .verticalAlign(ImageSpanAlignment.CENTER) + if (rewardEvent.content?.goodNum && rewardEvent.content.goodNum > 1) { + Span(' x' + rewardEvent.content.goodNum) + .fontSize(12) + .fontColor('#ffffff') + } + } + .constraintSize({ minHeight: 20 }) + .padding({ + left: 8, + right: 8, + top: 4, + bottom: 4 + }) + .backgroundColor('#A8333333') + .borderRadius(6) +} + +@Builder +function redPaperView(redPaperEvent: PLVRedPaperEvent, getRedPaperClick?: PLVCallback) { + Text() { + ImageSpan($r('app.media.plvli_chatroom_red_pack_icon')) + .width(20) + .height(20) + .verticalAlign(ImageSpanAlignment.CENTER) + Span($r('app.string.plvli_red_paper_send_msg', redPaperEvent.user?.nick)) + .fontSize(12) + .fontColor('#FFFFFFFF') + Span(redPaperEvent.getTypeName()) + .fontSize(12) + .fontColor('#FFFFFFFF') + Span($r('app.string.plvli_red_paper_dot')) + .fontSize(12) + .fontColor('#FFFFFFFF') + Span($r('app.string.plv_red_paper_get')) + .fontSize(12) + .fontColor('#FF5459') + .onClick((event) => { + getRedPaperClick?.() + }) + } + .constraintSize({ minHeight: 20 }) + .padding({ + left: 8, + right: 8, + top: 4, + bottom: 4 + }) + .backgroundColor('#A8333333') + .borderRadius(6) +} + +function generateNick(user?: PLVSocketUserBean, channelData?: PLVChannelData) { + return channelData?.viewerId == user?.userId ? generateNickByChannelData(channelData) : generateNickByUser(user) +} + +function generateNickByUser(user?: PLVSocketUserBean) { + return (user?.getActor() && PLVSocketUserBean.isSpecialType(user?.userType) ? ' ' : '') + `${user?.nick}: ` +} + +function generateNickByChannelData(channelData?: PLVChannelData) { + return $r('app.string.plvli_chat_me', (channelData?.viewerActor && PLVSocketUserBean.isSpecialType(channelData?.viewerType) ? ' ' : '') + channelData?.viewerName, ': ') +} + +export function getActorBgByUserType(userType?: string) { + if (PLVUserType.USERTYPE_TEACHER == userType) { + return '#F09343' + } else if (PLVUserType.USERTYPE_ASSISTANT == userType) { + return '#598FE5' + } else if (PLVUserType.USERTYPE_GUEST == userType) { + return '#EB6165' + } else if (PLVUserType.USERTYPE_MANAGER == userType) { + return '#33BBC5' + } + return '' +} + +function adjustChatImgWH(widthVp: number, heightVp: number, maxVp: number, minVp: number) { + const percentage = widthVp / heightVp + if (percentage == 1) { // 方图 + if (widthVp < minVp) { + widthVp = heightVp = minVp + } else if (widthVp > maxVp) { + widthVp = heightVp = maxVp + } + } else if (percentage < 1) { // 竖图 + heightVp = maxVp + widthVp = Math.max(minVp, heightVp * percentage) + } else { // 横图 + widthVp = maxVp + heightVp = Math.max(minVp, widthVp / percentage) + } + return [widthVp, heightVp] +} + +function toImageBeanForFileShare(fileShareBean?: PLVPPTShareFileDataBean): PLVImageBean { + const imageBean = PLVImageBean.toImageBeanForFileShare(fileShareBean) + imageBean.width = 120 + imageBean.height = 144 + switch (fileShareBean?.suffix) { + case "ppt": + case "pptx": + imageBean.url = $r('app.media.plvli_chatroom_file_share_ppt_icon') + break + case "doc": + case "docx": + imageBean.url = $r('app.media.plvli_chatroom_file_share_doc_icon') + break + case "xls": + case "xlsx": + imageBean.url = $r('app.media.plvli_chatroom_file_share_xls_icon') + break + case "pdf": + imageBean.url = $r('app.media.plvli_chatroom_file_share_pdf_icon') + break + default: + break + } + return imageBean +} + +function overLengthCopyClick(message?: string, overLenEvent?: IPLVCanOverLenEvent, sdk?: PLVLiveSceneSDK) { + return () => { + if (overLenEvent?.overLen) { + overLenEvent._overLengthFullMessage.get((fullMessage) => { + PLVUtils.copyText(fullMessage).then(() => { + PLVToastUtils.shortShow($r('app.string.plvli_chat_copy_success')) + }) + }, sdk?.chatroomManager) + } else { + PLVUtils.copyText(message || '').then(() => { + PLVToastUtils.shortShow($r('app.string.plvli_chat_copy_success')) + }) + } + } +} + +function overLengthMoreClick(message?: string, overLenEvent?: IPLVCanOverLenEvent, sdk?: PLVLiveSceneSDK, fullMessageCallback?: PLVCallback) { + return () => { + if (overLenEvent?.overLen) { + overLenEvent._overLengthFullMessage.get((fullMessage) => { + fullMessageCallback?.(fullMessage) + }, sdk?.chatroomManager) + } else { + fullMessageCallback?.(message || '') + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/chatroom/PLVLIChatListLayout.ets b/scenes_live/src/main/ets/components/chatroom/PLVLIChatListLayout.ets new file mode 100644 index 0000000..adf411c --- /dev/null +++ b/scenes_live/src/main/ets/components/chatroom/PLVLIChatListLayout.ets @@ -0,0 +1,473 @@ +import { + ChannelMenusBean, + PLVBaseIdEvent, + PLVBaseIdUserEvent, + PLVChannelData, + PLVChatImgEvent, + PLVCommonConstants, + PLVEmotionEvent, + PLVHistoryRedPaperEvent, + PLVJSONUtils, + PLVLazyDataSource, + PLVLiveSceneSDK, + PLVLocalQuizPromptEvent, + PLVRedPaperEvent, + PLVRedPaperForDelayEvent, + PLVRedPaperReceiveType, + PLVRedPaperResultEvent, + PLVRemoveContentEvent, + PLVRemoveHistoryEvent, + PLVRewardEvent, + PLVSimpleBuffer, + PLVSocketEvent, + PLVSocketOnEvent, + PLVSocketUserBean, + PLVSpeakEvent, + PLVTAnswerEvent, + PLVToastUtils, + RedPaperReceiveTypeEvent +} from '@polyvharmony/live-scenes-sdk' +import { PLVLIChatListItem } from './PLVLIChatListItem' + +const TAG = '[PLVLIChatListLayout]' + +@Preview +@Component +export struct PLVLIChatListLayout { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @Link isQuizSelected: boolean + @Link @Watch('onFocusModeChanged') isFocusModeOpen: boolean + // 聊天 + chatListScroller: ListScroller = new ListScroller() + // 当前显示的数据列表 + @State chatListData: PLVLazyDataSource = new PLVLazyDataSource() + // 全部消息的数据列表 + @Link fullChatListData: PLVLazyDataSource + // 专注模式的数据列表(仅包括讲师类型、嘉宾类型、助教类型、管理员类型的信息) + @State focusModeChatListData: PLVLazyDataSource = new PLVLazyDataSource() + @State isChatListRefreshing: boolean = true + @State isChatListCanRefreshing: boolean = true + @State isShowChatNewMessageTips: boolean = false + getChatHistorySize: number = 10 + oldestChatHistoryTimestamp?: number + oldestChatHistoryTimestampCount?: number + socketMessageBuffer: PLVSimpleBuffer> = new PLVSimpleBuffer() + // 提问 + quizListScroller: ListScroller = new ListScroller() + @Link quizListData: PLVLazyDataSource + @Link quizMenu: ChannelMenusBean | undefined + @State isQuizListRefreshing: boolean = true + @State isQuizListCanRefreshing: boolean = true + @State isShowQuizNewMessageTips: boolean = false + getQuizHistoryPage: number = 1 + getQuizHistorySize: number = 10 + + aboutToAppear(): void { + this.initData() + this.onChatData() + } + + aboutToDisappear(): void { + this.socketMessageBuffer.release() + } + + build() { + Swiper() { + Stack({ alignContent: Alignment.BottomStart }) { + Refresh({ refreshing: $$this.isChatListRefreshing }) { + List({ space: 4, scroller: this.chatListScroller }) { + LazyForEach(this.chatListData, (item: PLVBaseIdEvent) => { + ListItem() { + PLVLIChatListItem({ itemData: item }) + } + }, (item: PLVBaseIdEvent) => item.getUniqueId()) + } + .onScrollIndex((start, end) => { + if (end == this.chatListData.totalCount() - 1) { + this.isShowChatNewMessageTips = false + } + }) + .scrollBar(BarState.Off) + .edgeEffect(EdgeEffect.Fade) + .cachedCount(5) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + .onRefreshing(() => { + if (!this.isChatListCanRefreshing) { + this.isChatListRefreshing = false + return + } + this.getChatHistoryList() + }) + + if (this.isShowChatNewMessageTips) { + this.newMessageTips(() => { + this.isShowChatNewMessageTips = false + this.chatListScroller.scrollEdge(Edge.Bottom) + }) + } + } + .margin({ bottom: 20, left: 24 }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + + if (this.quizMenu) { + Stack({ alignContent: Alignment.BottomStart }) { + Column() { + Text($r('app.string.plvli_chat_quiz_channel')) + .height(28) + .fontSize(14) + .fontColor('#ffffff') + .padding({ left: 10, right: 10 }) + .backgroundColor('#a6333333') + .borderRadius(14) + .margin({ bottom: 6 }) + + Refresh({ refreshing: $$this.isQuizListRefreshing }) { + List({ space: 4, scroller: this.quizListScroller }) { + LazyForEach(this.quizListData, (item: PLVBaseIdEvent) => { + ListItem() { + PLVLIChatListItem({ itemData: item }) + } + }, (item: PLVBaseIdEvent) => item.getUniqueId()) + } + .onScrollIndex((start, end) => { + if (end == this.quizListData.totalCount() - 1) { + this.isShowQuizNewMessageTips = false + } + }) + .scrollBar(BarState.Off) + .edgeEffect(EdgeEffect.Fade) + .cachedCount(5) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + .onRefreshing(() => { + if (!this.isQuizListCanRefreshing) { + this.isQuizListRefreshing = false + return + } + this.getQuizHistoryList() + }) + .height(0) + .layoutWeight(1) + } + .alignItems(HorizontalAlign.Start) + + if (this.isShowQuizNewMessageTips) { + this.newMessageTips(() => { + this.isShowQuizNewMessageTips = false + this.quizListScroller.scrollEdge(Edge.Bottom) + }) + } + } + .margin({ bottom: 20, left: 24 }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + } + .width(268) + .height(172) + .loop(false) + .autoPlay(false) + .nestedScroll(SwiperNestedScrollMode.SELF_FIRST) + .translate({ x: -8, y: 8 }) + .index(this.isQuizSelected ? 1 : 0) + .onChange((index) => { + this.isQuizSelected = index == 1 + }) + .indicator( + this.quizMenu ? Indicator.dot() + .left(0) + .bottom(0) + .itemWidth(38) + .itemHeight(3) + .selectedItemWidth(38) + .selectedItemHeight(3) + .color('#80ffffff') + .selectedColor(Color.White) : false + ) + } + + @Builder + newMessageTips(onClick?: () => void) { + Text($r("app.string.plvli_chat_view_new_msg")) + .backgroundColor('#ffffff') + .borderRadius(10) + .padding({ + left: 8, + right: 8, + top: 4, + bottom: 4 + }) + .fontColor('#FFA611') + .fontSize(14) + .onClick(onClick) + } + + initData() { + this.chatListData.bindSource(this.fullChatListData) + } + + onChatData() { + this.socketMessageBuffer.observe((dataArr) => { + let pushChatListDataArr: PLVBaseIdEvent[] = [] + dataArr.forEach((value) => { + const data = value.message! + const event = value.eventName! + + let pushChatListData: PLVBaseIdEvent | undefined = undefined + let pushQuizListData: PLVBaseIdEvent | undefined = undefined + switch (event) { + // 发言 + case PLVSpeakEvent.EVENT: { + const speakEvent = PLVJSONUtils.toFillData(PLVSpeakEvent, data) + if (speakEvent) { + // 过滤聊天室广播的自己的发言消息 + if (this.channelData?.viewerId != speakEvent.user?.userId) { + pushChatListData = speakEvent + } + } + break + } + // 图片 + case PLVChatImgEvent.EVENT: { + const chatImgEvent = PLVJSONUtils.toFillData(PLVChatImgEvent, data) + if (chatImgEvent) { + // 过滤聊天室广播的自己的图片消息 + if (this.channelData?.viewerId != chatImgEvent.user?.userId) { + pushChatListData = chatImgEvent + } + } + break + } + // 打赏 + case PLVRewardEvent.EVENT: { + const rewardEvent = PLVJSONUtils.toFillData(PLVRewardEvent, data) + if (rewardEvent) { + pushChatListData = rewardEvent + } + break + } + // 口令红包 + case PLVRedPaperEvent.EVENT: { + const redPaperEvent = PLVJSONUtils.toFillData(PLVRedPaperEvent, data) + if (redPaperEvent && redPaperEvent.isSupportType()) { + pushChatListData = redPaperEvent + } + break + } + case PLVRedPaperResultEvent.EVENT: { + const redPaperResult = PLVJSONUtils.json2Bean(PLVRedPaperResultEvent, data) + if (redPaperResult) { + this.sdk?.redpackManager.onRedPaperResultEvent(redPaperResult) + } + break + } + // 延迟红包 + case PLVRedPaperForDelayEvent.EVENT: { + const redPaperForDelayEvent = PLVJSONUtils.json2Bean(PLVRedPaperForDelayEvent, data) + if (redPaperForDelayEvent) { + this.sdk?.redpackManager.notifyPostValue(redPaperForDelayEvent) + } + break + } + // 回答 + case PLVTAnswerEvent.EVENT: { + const tAnswerEvent = PLVJSONUtils.toFillData(PLVTAnswerEvent, data) + if (tAnswerEvent) { + // 只取回答自己的消息 + if (this.channelData?.viewerId == tAnswerEvent.s_userId) { + pushQuizListData = tAnswerEvent + } + } + break + } + // 删除某条消息 + case PLVRemoveContentEvent.EVENT: { + const removeContentEvent = PLVJSONUtils.json2Bean(PLVRemoveContentEvent, data) + if (removeContentEvent?.id) { + this.deleteDataToChatList(removeContentEvent.id) + } + break + } + // 清空所有消息 + case PLVRemoveHistoryEvent.EVENT: { + this.clearDataToChatList() + break + } + default: + break; + } + if (pushChatListData) { + pushChatListDataArr.push(pushChatListData) + } + if (pushQuizListData) { + this.pushDataToQuizList(pushQuizListData) + } + }) + if (pushChatListDataArr.length > 0) { + this.pushDataToChatList(...pushChatListDataArr) + } + }) + this.sdk?.chatroomManager.onData(PLVSocketOnEvent.MESSAGE, (data: string, event: string) => { + // 缓冲500ms再更新一次数据,避免聊天信息刷得太频繁导致UI卡顿 + this.socketMessageBuffer.push({ message: data, eventName: event } as PLVSocketEvent) + }, this) + this.sdk?.chatroomManager.onData(PLVSocketOnEvent.EMOTION, (data: string) => { + // 个性表情 + const emotionEvent = PLVJSONUtils.toFillData(PLVEmotionEvent, data) + if (emotionEvent) { + this.pushDataToChatList(emotionEvent) + } + }, this) + } + + getQuizHistoryList() { + this.sdk?.chatroomManager.getQuizHistoryList(this.getQuizHistoryPage, this.getQuizHistorySize) + .then((value) => { + if (this.getQuizHistoryPage == 1 && value.list?.length == 0) { + // 没有提问历史记录时,添加一条提问提示语 + const quizPromptEvent = new PLVLocalQuizPromptEvent() + quizPromptEvent.prompt = this.quizMenu?.content || $r('app.string.plvli_chat_quiz_default_tips') + this.addDataToQuizList(quizPromptEvent) + } + if (value.list) { + value.list.forEach((data, index) => { + this.addDataToQuizList(data) + }) + if (this.getQuizHistoryPage == 1) { + this.quizListScroller.scrollEdge(Edge.Bottom) + } + this.getQuizHistoryPage++ + this.isQuizListRefreshing = false + if (value.list.length < this.getQuizHistorySize) { + this.isQuizListCanRefreshing = false + } + } + }) + .catch((err: Error) => { + this.isQuizListRefreshing = false + PLVToastUtils.shortShow($r('app.string.plvli_chat_toast_history_load_failed', `: ${err.message}`)) + }) + } + + getChatHistoryList() { + this.sdk?.chatroomManager.getChatHistoryList(this.getChatHistorySize, this.oldestChatHistoryTimestamp, this.oldestChatHistoryTimestampCount) + .then((values) => { + const oldChatHistoryTimestamp = this.oldestChatHistoryTimestamp + values.forEach((value, index) => { + this.sdk?.chatroomManager.parseChatHistoryData(value, (data) => { + const messageTimestamp = data['time'] as number + if (!this.oldestChatHistoryTimestamp || messageTimestamp < this.oldestChatHistoryTimestamp) { + this.oldestChatHistoryTimestamp = messageTimestamp + this.oldestChatHistoryTimestampCount = 1 + } else if (this.oldestChatHistoryTimestamp == messageTimestamp && this.oldestChatHistoryTimestampCount) { + this.oldestChatHistoryTimestampCount++ + } + const baseIdEvent = data['data'] as PLVBaseIdEvent + if (baseIdEvent) { + this.addDataToChatList(baseIdEvent) + } + if (baseIdEvent instanceof PLVHistoryRedPaperEvent) { + const historyRedPagerEvent = baseIdEvent as PLVHistoryRedPaperEvent + const type = this.sdk?.redpackManager.geLocalCachedReceiveStatus(historyRedPagerEvent.redpackId) + .then((value) => { + historyRedPagerEvent.receiveType = PLVRedPaperReceiveType.getValue(value as RedPaperReceiveTypeEvent) + }) + + this.sdk?.redpackManager.cacheRedPaper((baseIdEvent as PLVHistoryRedPaperEvent)) + } + }) + }) + if (!oldChatHistoryTimestamp) { + this.chatListScroller.scrollEdge(Edge.Bottom) + } + this.isChatListRefreshing = false + if (values.length < this.getChatHistorySize) { + this.isChatListCanRefreshing = false + } + }) + .catch((err: Error) => { + this.isChatListRefreshing = false + PLVToastUtils.shortShow($r('app.string.plvli_chat_toast_history_load_failed', `: ${err.message}`)) + }) + } + + clearDataToChatList() { + this.fullChatListData.clearData() + this.focusModeChatListData.clearData() + } + + deleteDataToChatList(id: string) { + this.fullChatListData.dataArray.some((value, index) => { + if (id === value.getUniqueId()) { + this.fullChatListData.deleteData(index) + return true + } + return false + }) + this.focusModeChatListData.dataArray.some((value, index) => { + if (id === value.getUniqueId()) { + this.focusModeChatListData.deleteData(index) + return true + } + return false + }) + } + + addDataToChatList(data: PLVBaseIdEvent) { + this.fullChatListData.addData(0, data) + if (data instanceof PLVBaseIdUserEvent) { + const isSpecialType = PLVSocketUserBean.isSpecialType((data as PLVBaseIdUserEvent).user?.userType) + if (isSpecialType) { + this.focusModeChatListData.addData(0, data) + } + } + } + + pushDataToChatList(...data: PLVBaseIdEvent[]) { + this.fullChatListData.pushData(...data) + const specialTypeData: PLVBaseIdEvent[] = [] + data.forEach((value) => { + if (value instanceof PLVBaseIdUserEvent) { + const isSpecialType = PLVSocketUserBean.isSpecialType((value as PLVBaseIdUserEvent).user?.userType) + if (isSpecialType) { + specialTypeData.push(value) + } + } + }) + this.focusModeChatListData.pushData(...specialTypeData) + if (this.chatListScroller.isAtEnd()) { + this.chatListScroller.scrollEdge(Edge.Bottom) + } else { + this.isShowChatNewMessageTips = true + } + } + + addDataToQuizList(data: PLVBaseIdEvent) { + this.quizListData.addData(0, data) + } + + pushDataToQuizList(data: PLVBaseIdEvent) { + this.quizListData.pushData(data) + if (this.quizListScroller.isAtEnd()) { + this.quizListScroller.scrollEdge(Edge.Bottom) + } else { + this.isShowQuizNewMessageTips = true + } + } + + onFocusModeChanged() { + this.chatListData.unbindSource() + if (this.isFocusModeOpen) { + this.chatListData.bindSource(this.focusModeChatListData) + this.focusModeChatListData.reloadData() + } else { + this.chatListData.bindSource(this.fullChatListData) + this.fullChatListData.reloadData() + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/chatroom/PLVLIChatOverLengthMessageDialog.ets b/scenes_live/src/main/ets/components/chatroom/PLVLIChatOverLengthMessageDialog.ets new file mode 100644 index 0000000..9942c63 --- /dev/null +++ b/scenes_live/src/main/ets/components/chatroom/PLVLIChatOverLengthMessageDialog.ets @@ -0,0 +1,85 @@ +import { PLVCommonConstants, PLVDeviceUtils, PLVSocketUserBean, PLVTextUtils, PLVToastUtils, PLVUtils } from '@polyvharmony/live-scenes-sdk' +import PLVLIFaceManager from '../../common/PLVLIFaceManager' +import { getActorBgByUserType } from './PLVLIChatListItem' + +@Preview +@CustomDialog +export struct PLVLIChatOverLengthMessageDialog { + controller?: CustomDialogController + scroller: Scroller = new Scroller() + @Link nick: string | Resource | undefined + @Link actor: string | undefined + @Link userType: string | undefined + @Link fullMessage: string + + build() { + Stack() { + Column() { + Text($r('app.string.plvli_chat_full_text')) + .width(PLVCommonConstants.FULL_PERCENT) + .textAlign(TextAlign.Start) + .fontSize(18) + .fontColor('#F0F1F5') + Scroll(this.scroller) { + Text() { + if (this.actor && PLVSocketUserBean.isSpecialType(this.userType)) { + Span(` ${this.actor} `) + .fontColor('#ffffff') + .textBackgroundStyle({ color: getActorBgByUserType(this.userType), radius: 6 }) + .fontSize(12) + } + Span(this.nick) + .fontColor('#FFD16B') + .fontSize(14) + ForEach(PLVTextUtils.parseString(this.fullMessage), (data: string) => { + if (typeof PLVLIFaceManager.getFace(data) === 'string') { + Span(PLVLIFaceManager.getFace(data)) + .fontColor('#ffffff') + .fontSize(14) + } else { + ImageSpan(PLVLIFaceManager.getFace(data)) + .width(21) + .height(21) + .verticalAlign(ImageSpanAlignment.CENTER) + } + }) + } + } + .align(Alignment.TopStart) + .scrollBar(BarState.Off) + .margin({ top: 24, bottom: 24 }) + .height(0) + .layoutWeight(1) + + Button($r('app.string.plvli_chat_copy')) + .width(88) + .height(28) + .fontSize(12) + .fontColor('#FFFFFF') + .onClick(() => { + PLVUtils.copyText(this.fullMessage).then(() => { + PLVToastUtils.shortShow($r('app.string.plvli_chat_copy_success')) + }) + }) + } + .padding({ + left: 24, + right: 24, + top: 24, + bottom: PLVDeviceUtils.getNavigationIndicatorHeight() + 24 + }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + .borderRadius({ + topLeft: 12, + topRight: 12, + bottomLeft: 0, + bottomRight: 0 + }) + .width(PLVCommonConstants.FULL_PERCENT) + .height('65%') + .backgroundColor('#464646') + .backdropBlur(8) + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/chatroom/PLVLIChatPlaybackLayout.ets b/scenes_live/src/main/ets/components/chatroom/PLVLIChatPlaybackLayout.ets new file mode 100644 index 0000000..12b818a --- /dev/null +++ b/scenes_live/src/main/ets/components/chatroom/PLVLIChatPlaybackLayout.ets @@ -0,0 +1,141 @@ +import { IPLVIdEvent, PLVChannelData, PLVChatPlaybackDataVO, PLVCommonConstants, PLVLazyDataSource, PLVLiveSceneSDK, PLVLogger } from '@polyvharmony/live-scenes-sdk' +import { MutableObserver, PLVMediaPlayerState } from '@polyvharmony/media-player-sdk' +import { PLVLIChatListItem } from './PLVLIChatListItem' + +const TAG = '[PLVLIChatPlaybackLayout]' + +@Preview +@Component +export struct PLVLIChatPlaybackLayout { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @State isChatPlaybackEnabled: boolean = true + @State chatPlaybackTipsVisible: boolean = true + // 聊天 + chatListScroller: ListScroller = new ListScroller() + // 当前显示的数据列表 + @State chatListData: PLVLazyDataSource = new PLVLazyDataSource() + @State isShowChatNewMessageTips: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.initData() + this.onPlayerData() + this.onChatPlaybackData() + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + + build() { + Column() { + Text($r('app.string.plvli_chat_playback_tips')) + .fontSize(12) + .backgroundColor('#E6FFFFFF') + .borderRadius(4) + .padding(8) + .textAlign(TextAlign.Center) + .margin({ bottom: 12 }) + .fontColor('#FF333333') + .constraintSize({ minWidth: 258 }) + .visibility(this.chatPlaybackTipsVisible ? Visibility.Visible : Visibility.Hidden) + .onAppear(() => { + setTimeout(() => { + this.chatPlaybackTipsVisible = false + }, 5000) + }) + Stack({ alignContent: Alignment.BottomStart }) { + List({ space: 4, scroller: this.chatListScroller }) { + LazyForEach(this.chatListData, (item: IPLVIdEvent) => { + ListItem() { + PLVLIChatListItem({ itemData: item }) + } + }, (item: IPLVIdEvent) => item.getUniqueId()) + } + .onScrollIndex((start, end) => { + if (end == this.chatListData.totalCount() - 1) { + this.isShowChatNewMessageTips = false + } + }) + .scrollBar(BarState.Off) + .edgeEffect(EdgeEffect.Fade) + .cachedCount(5) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + + if (this.isShowChatNewMessageTips) { + this.newMessageTips(() => { + this.isShowChatNewMessageTips = false + this.chatListScroller.scrollEdge(Edge.Bottom) + }) + } + } + .margin({ left: 16 }) + .width(244) + .height(152) + } + .visibility(this.isChatPlaybackEnabled ? Visibility.Visible : Visibility.None) + } + + @Builder + newMessageTips(onClick?: () => void) { + Text($r("app.string.plvli_chat_view_new_msg")) + .backgroundColor('#ffffff') + .borderRadius(10) + .padding({ + left: 8, + right: 8, + top: 4, + bottom: 4 + }) + .fontColor('#FFA611') + .fontSize(14) + .onClick(onClick) + } + + initData() { + this.isChatPlaybackEnabled = this.channelData?.liveDetail?.isChatPlaybackEnabled() ?? false + } + + onPlayerData() { + this.sdk?.playerManager.mainMediaPlayer.getStateListenerRegistry() + .playerState + .observe((value) => { + if (value === PLVMediaPlayerState.STATE_PREPARED) { + const playbackVideoData = this.sdk?.playerManager.mainMediaPlayer.getBusinessListenerRegistry().playbackVideoData.value + const sessionId = playbackVideoData?.channelSessionId ?? '' + const fileId = playbackVideoData?.fileId ?? '' + this.sdk?.chatPlaybackManager.start(sessionId, fileId) + } + PLVLogger.info(TAG, `onPlayerData, playerState=${value}`) + }) + .pushTo(this.observers) + } + + onChatPlaybackData() { + this.sdk?.chatPlaybackManager.setupPlayTimeCallback(() => { + return this.sdk?.playerManager.mainMediaPlayer.getCurrentPosition() ?? 0 + }) + this.sdk?.chatPlaybackManager.eventNotify.on('insert_data', (...dataList: PLVChatPlaybackDataVO[]) => { + this.pushDataToChatList(...dataList) + }, this) + this.sdk?.chatPlaybackManager.eventNotify.on('clear_data', () => { + this.clearDataToChatList() + }, this) + } + + pushDataToChatList(...data: IPLVIdEvent[]) { + this.chatListData.pushData(...data) + if (this.chatListScroller.isAtEnd()) { + this.chatListScroller.scrollEdge(Edge.Bottom) + } else { + this.isShowChatNewMessageTips = true + } + } + + clearDataToChatList() { + this.chatListData.clearData() + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/chatroom/PLVLIGreetingView.ets b/scenes_live/src/main/ets/components/chatroom/PLVLIGreetingView.ets new file mode 100644 index 0000000..e095be8 --- /dev/null +++ b/scenes_live/src/main/ets/components/chatroom/PLVLIGreetingView.ets @@ -0,0 +1,93 @@ +import { PLVCommonConstants, PLVJSONUtils, PLVLiveSceneSDK, PLVLoginEvent, PLVSocketOnEvent } from '@polyvharmony/live-scenes-sdk' + +const TAG = '[PLVLIGreetingView]' + +@Preview +@Component +export struct PLVLIGreetingView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @State welcomeTips?: string | Resource = undefined + @State layoutWidthTranslateX: number = 0 + appearTimerId?: number + loginListSize: number = 0 + loginNickList: string[] = [] + + aboutToAppear(): void { + this.onChatData() + } + + build() { + if (this.welcomeTips) { + Text(this.welcomeTips) + .translate({ x: 268 * this.layoutWidthTranslateX - 268 }) + .width(268) + .height(22) + .onAppear(() => { + animateTo({ duration: 500, curve: Curve.Linear }, () => { + this.layoutWidthTranslateX = 1 + }) + this.appearTimerId = setTimeout(() => { + animateTo({ + duration: 250, curve: Curve.Linear, onFinish: () => { + this.welcomeTips = undefined + } + }, () => { + this.layoutWidthTranslateX = 0 + }) + }, 2500) + }) + .onDisAppear(() => { + this.layoutWidthTranslateX = 0 + clearTimeout(this.appearTimerId) + setTimeout(() => { + this.buildNextWelcomeTips() + }, PLVCommonConstants.DELAY_TIME_S) + }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(1) + .fontSize(14) + .padding({ left: 16, right: 3 }) + .fontColor('#ffffff') + .backgroundImage($r('app.media.plvli_greet_bg')) + .backgroundImageSize(ImageSize.Cover) + } + } + + onChatData() { + this.sdk?.chatroomManager.onData(PLVSocketOnEvent.MESSAGE, (data: string, event: string) => { + switch (event) { + // 登录 + case PLVLoginEvent.EVENT: + const loginEvent = PLVJSONUtils.toFillData(PLVLoginEvent, data) + if (loginEvent) { + this.buildWelcomeTips(loginEvent) + } + break; + default: + break; + } + }, this) + } + + buildWelcomeTips(loginEvent: PLVLoginEvent) { + this.loginListSize++ + if (this.loginNickList.length < 100) { + this.loginNickList.push(loginEvent.user?.nick || '') + } + this.buildNextWelcomeTips() + } + + buildNextWelcomeTips() { + if (this.welcomeTips === undefined) { + if (this.loginListSize >= 10) { + this.loginNickList.length = 2 + this.welcomeTips = $r('app.string.plvli_chat_welcome_join_multi', this.loginNickList.join('、'), this.loginListSize + '') + this.loginNickList.length = 0 + this.loginListSize = 0 + } else if (this.loginNickList.length >= 1) { + this.welcomeTips = $r('app.string.plvli_chat_welcome_join', this.loginNickList.shift()) + this.loginListSize-- + } + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/chatroom/PLVLILikeIconView.ets b/scenes_live/src/main/ets/components/chatroom/PLVLILikeIconView.ets new file mode 100644 index 0000000..f6d3994 --- /dev/null +++ b/scenes_live/src/main/ets/components/chatroom/PLVLILikeIconView.ets @@ -0,0 +1,136 @@ +import { curves } from '@kit.ArkUI' +import { util } from '@kit.ArkTS' +import { PLVChatroomManager, PLVLiveSceneSDK, PLVTextUtils, PLVUtils } from '@polyvharmony/live-scenes-sdk' + +const TAG = '[PLVLILikeIconView]' + +@Preview +@Component +export struct PLVLILikeIconView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @State chatroomManager: PLVChatroomManager | undefined = this.sdk?.chatroomManager + @State iconScale: number = 1 + @State likeIconAnimationParams: LikeIconAnimationParam[] = [] + + aboutToAppear(): void { + this.onChatData() + } + + build() { + Stack() { + Column() { + Image($r('app.media.plvli_chatroom_btn_like')) + .width(34) + .height(34) + .scale({ x: this.iconScale, y: this.iconScale }) + .draggable(false) + .onClick(() => { + this.iconScale = 1.2 + this.chatroomManager?.likes(1) + }) + .animation({ + duration: 60, + curve: Curve.Sharp, + iterations: 2, + playMode: PlayMode.Alternate, + onFinish: () => { + this.iconScale = 1 + } + }) + + Text(PLVTextUtils.toWString(this.chatroomManager?.likesCount)) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(1) + .width(68) + .fontSize(12) + .margin({ top: 2 }) + .textAlign(TextAlign.Center) + .fontColor('#ffffff') + } + .alignItems(HorizontalAlign.Center) + .zIndex(99) + + ForEach(this.likeIconAnimationParams, (data: LikeIconAnimationParam, index) => { + LikeIconAnimationView({ data: data, likeIconAnimationParams: this.likeIconAnimationParams }) + .zIndex(-99) + }, (data: LikeIconAnimationParam, index) => data.id) + } + .width(34) + } + + onChatData() { + this.chatroomManager?.eventNotify.on('likes_count', async (arg: number) => { + const addedCount = arg - this.chatroomManager!.likesCount + this.chatroomManager!.likesCount = arg + // 每次最多添加5个点赞动画 + for (let i = 0; i < Math.min(5, addedCount); i++) { + this.likeIconAnimationParams.push(new LikeIconAnimationParam()) + if (i < (Math.min(5, addedCount) - 1)) { + await PLVUtils.delay(200) + } + } + }, this) + } +} + +@Preview +@Component +export struct LikeIconAnimationView { + @ObjectLink data: LikeIconAnimationParam + @Link likeIconAnimationParams: LikeIconAnimationParam[] + + build() { + Image(this.data.icon) + .width(this.data.width) + .height(this.data.height) + .opacity(this.data.opacity) + .rotate({ z: 1, angle: this.data.rotateZ }) + .scale({ x: this.data.scale, y: this.data.scale }) + .translate({ x: this.data.translateX, y: this.data.translateY }) + .draggable(false) + .onAppear(() => { + this.data.start() + }) + .animation({ + duration: 1200, + curve: curves.cubicBezierCurve(0.0, 0.0, 1.0, 1.0), + onFinish: () => { + this.likeIconAnimationParams.shift() + } + }) + } +} + +@Observed +export class LikeIconAnimationParam { + static readonly LIKE_ICON: Resource[] = [ + $r('app.media.plvli_chatroom_btn_like_1'), + $r('app.media.plvli_chatroom_btn_like_2'), + $r('app.media.plvli_chatroom_btn_like_3'), + $r('app.media.plvli_chatroom_btn_like_4'), + $r('app.media.plvli_chatroom_btn_like_5'), + $r('app.media.plvli_chatroom_btn_like_6'), + $r('app.media.plvli_chatroom_btn_like_7'), + $r('app.media.plvli_chatroom_btn_like_8'), + $r('app.media.plvli_chatroom_btn_like_9'), + $r('app.media.plvli_chatroom_btn_like_10'), + ] + id: string = util.generateRandomUUID(true) + icon: Resource = LikeIconAnimationParam.LIKE_ICON[Math.floor(Math.random() * LikeIconAnimationParam.LIKE_ICON.length)] + width: number = 34 + height: number = 34 + scale: number = 0.3 + translateX: number = 0 + translateY: number = 0 + rotateZ: number = 0 + opacity: number = 1 + + start() { + this.scale = 1.2 + this.translateX = -(40 + Math.floor(Math.random() * 21)) + this.translateY = -160 + this.rotateZ = Math.floor(Math.random() * 61) + this.opacity = 0.3 + return this + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/chatroom/PLVLIRedpackView.ets b/scenes_live/src/main/ets/components/chatroom/PLVLIRedpackView.ets new file mode 100644 index 0000000..63b80be --- /dev/null +++ b/scenes_live/src/main/ets/components/chatroom/PLVLIRedpackView.ets @@ -0,0 +1,162 @@ +import { PLVCommonConstants, PLVDelayRedpackVO, PLVLiveSceneSDK, PLVTimeUtils } from '@polyvharmony/live-scenes-sdk' + +@Component +@Preview +export struct PLVLIRedpackView { + + // + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + + @State isShow: boolean = false + @State redpackTipsShow: boolean = false + @State colors: Array<[ResourceColor, number]> = [["#FF9D4D", 0.0], ["#F65F49", 0.5]] + @State tipsWidth: number = 0 + @State redpackWidth: number = 0 + @State redpackHeight: number = 0 + + @State redpackCdHeight: number = 0 + @State cdText: string | Resource = "" + + tipsCountDownTaskId?: number + cdCountDownTaskId?: number + // + + // + + aboutToAppear(): void { + this.observer() + } + + build() { + if (this.isShow) { + RelativeContainer() { + Column() { + Image($r('app.media.plvli_red_pack_password_icon')) + .onAreaChange((oldValue: Area, newValue: Area) => { + this.redpackWidth = newValue.width as number + this.redpackHeight = newValue.height as number + }) + .width(48) + .height(48) + + Column() { + Text(this.cdText) + .fontSize(12) + .fontColor("#FFFFFF") + } + .onAreaChange((oldValue: Area, newValue: Area) => { + this.redpackCdHeight = newValue.height as number + }) + .padding({ + left: 3, + right: 3, + top: 1, + bottom: 1 + }) + .borderRadius(8) + .justifyContent(FlexAlign.Center) + .backgroundColor("#CC202127") + } + .alignRules({ + top: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Top }, + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End } + }) + .onClick((event) => { + this.showRedpackTips() + }) + .id("redpack_img") + + if (this.redpackTipsShow) { + Row() { + Text($r('app.string.plv_red_paper_red_pack_hint')) + .fontSize(13) + .fontColor("#FFFFFF") + } + .borderRadius(4) + .padding({ + left: 4, + right: 4, + top: 3, + bottom: 3 + }) + .linearGradient({ + direction: GradientDirection.Right, + colors: this.colors + }) + .alignRules({ + right: { anchor: "redpack_img", align: HorizontalAlign.Start }, + top: { anchor: "redpack_img", align: VerticalAlign.Center } + }) + .onAreaChange((oldValue: Area, newValue: Area) => { + this.tipsWidth = newValue.width as number + }) + .offset({ x: -10, y: -10 }) + .id("redpack_tips_tv") + + Polygon({ width: 10, height: 10 }) + .points([[0, 0], [0, 8], [4, 4]]) + .fill("#F65F49") + .alignRules({ + left: { anchor: "redpack_tips_tv", align: HorizontalAlign.End }, + top: { anchor: "redpack_tips_tv", align: VerticalAlign.Center } + }) + .offset({ y: -15, x: -10 }) + } + + } + .height(this.redpackHeight + this.redpackCdHeight) + .width(this.redpackWidth + this.tipsWidth) + + } + } + // + + // + observer() { + this.sdk?.redpackManager.eventDataNotify.on('redpack_data', (delayRedpack: PLVDelayRedpackVO) => { + if (delayRedpack.redpackSendTime === 0) { + return + } + // 倒数时间处理 + const cdTime = delayRedpack.redpackSendTime ?? 0 + if (cdTime <= Date.now()) { + this.stop() + return + } + + this.start(cdTime - Date.now()) + }) + } + + // + + // + + private showRedpackTips() { + clearTimeout(this.tipsCountDownTaskId) + this.redpackTipsShow = true + this.tipsCountDownTaskId = setTimeout(() => { + this.redpackTipsShow = false + }, 3000) + } + + private start(delay: number) { + this.stop() + this.cdCountDownTaskId = setInterval(() => { + delay -= 1000 + this.isShow = true + this.cdText = PLVTimeUtils.generateTime(delay, false) + if (delay <= 0) { + this.isShow = false + clearInterval(this.cdCountDownTaskId) + } + }, 1000) + } + + private stop() { + clearInterval(this.cdCountDownTaskId) + this.isShow = false + } + // + +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/chatroom/PLVLIToTopView.ets b/scenes_live/src/main/ets/components/chatroom/PLVLIToTopView.ets new file mode 100644 index 0000000..043af3a --- /dev/null +++ b/scenes_live/src/main/ets/components/chatroom/PLVLIToTopView.ets @@ -0,0 +1,103 @@ +import { PLVChannelData, PLVCommonConstants, PLVLiveSceneSDK, PLVLogger, PLVLoginType, PLVStateData, PLVTextUtils, PLVToTopEvent } from '@polyvharmony/live-scenes-sdk' +import { MutableObserver, PLVLiveStatusEnum } from '@polyvharmony/media-player-sdk' +import PLVLIFaceManager from '../../common/PLVLIFaceManager' + +@Preview +@Component +export struct PLVLIToTopView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @State @Watch('onNoLiveChanged') isNoLive: boolean = true + @State toTopData?: PLVStateData = undefined + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + if (this.channelData?.loginType == PLVLoginType.LIVE) { + this.onPlayerData() + this.onChatData() + } else { + this.isNoLive = false + this.onChatPlaybackData() + } + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + + build() { + if (this.toTopData?.data && !this.isNoLive) { + Row() { + Text() { + Span(this.toTopData?.data.nick + ': ') + .fontColor('#FFD16B') + ForEach(PLVTextUtils.parseString(this.toTopData?.data.content), (data: string) => { + if (typeof PLVLIFaceManager.getFace(data) === 'string') { + Span(PLVLIFaceManager.getFace(data)) + } else { + ImageSpan(PLVLIFaceManager.getFace(data)) + .width(21) + .height(21) + .verticalAlign(ImageSpanAlignment.CENTER) + } + }) + } + .width(0) + .layoutWeight(1) + .padding({ right: 6 }) + .height(PLVCommonConstants.FULL_PERCENT) + .fontColor('#FFFFFFFF') + .fontSize(14) + .maxLines(2) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + + Image($r('app.media.plvli_popup_close')) + .draggable(false) + .padding(10) + .width(32) + .height(32) + .onClick(() => { + this.toTopData = undefined + }) + } + .alignItems(VerticalAlign.Center) + .padding({ + left: 12, + right: 12 + }) + .width(320) + .height(60) + .borderRadius(8) + .backgroundColor('#A8333333') + .backdropBlur(8) + } + } + + onPlayerData() { + this.sdk?.playerManager.mainMediaPlayer.getBusinessListenerRegistry() + .liveStatus + .observe((liveStatus) => { + this.isNoLive = liveStatus === PLVLiveStatusEnum.NO_LIVE + }) + .pushTo(this.observers) + } + + onChatData() { + this.sdk?.chatroomManager.eventNotify.on('to_top', (data: PLVStateData) => { + this.toTopData = data + }, this) + } + + onChatPlaybackData() { + this.sdk?.chatPlaybackManager.eventNotify.on('to_top', (data: PLVStateData) => { + this.toTopData = data + }, this) + } + + onNoLiveChanged() { + if (this.isNoLive) { + this.toTopData = undefined + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/interact/PLVLIInteractWebView.ets b/scenes_live/src/main/ets/components/interact/PLVLIInteractWebView.ets new file mode 100644 index 0000000..7035698 --- /dev/null +++ b/scenes_live/src/main/ets/components/interact/PLVLIInteractWebView.ets @@ -0,0 +1,136 @@ +import { + IPLVBackwardInterface, + PLVCallback, + PLVCommonConstants, + PLVInteractCallback, + PLVInteractManager, + PLVInteractWeb, + PLVLiveSceneSDK, + PLVRedpackManager, + PLVSimpleWeb, + PLVSocketWebController, + PLVToastUtils, + PLVWebController +} from '@polyvharmony/live-scenes-sdk' +import { pushToCardPushDetailPage } from './cardpush/PLVLICardPushDetailPage'; +import { pushToProductDetailPage } from '../product/PLVLIProductDetailPage'; + + +const TAG = "[PLVInteractWeb]" + +@Component +export struct PLVLIInteractWebView { + // + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @State interactManager?: PLVInteractManager = this.sdk?.interactManager + @State interactCallback: PLVInteractCallback = new PLVInteractCallback() + redpackManager?: PLVRedpackManager = this.sdk?.redpackManager + @ObjectLink viewController: PLVInteractWebViewController + @State controller?: PLVSocketWebController = this.sdk?.webControllerManager.create() + @State innerController?:PLVWebController = new PLVWebController() + @State isShowInnerWeb: boolean = false + // 内部webview点击时拦截 + touchInnerWeb: boolean = false + // 内部webview事件处理逻辑回调 + // + + // + aboutToAppear(): void { + this.config() + } + + build() { + Column(){ + Stack({ alignContent: Alignment.Bottom}){ + PLVInteractWeb({ + interactManager: this.interactManager, + interactCallback: this.interactCallback, + controller: this.controller, + redpackManager: this.redpackManager + }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + .visibility(this.viewController.isShow ? Visibility.Visible : Visibility.Hidden) + + if (this.viewController.isShowInnerWeb) { + PLVSimpleWeb({ + controller: this.innerController, + src: "" + }) + .width(PLVCommonConstants.FULL_PERCENT) + .height('70%') + .align(Alignment.BottomEnd) + .onClick((event) => { + this.touchInnerWeb = true + }) + .hitTestBehavior(HitTestMode.Transparent) + } + } + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + .onClick((event) => { + if (this.touchInnerWeb) { + this.touchInnerWeb = false + return + } + if (this.viewController.isShowInnerWeb && !this.touchInnerWeb) { + this.viewController.isShowInnerWeb = false + } + }) + .visibility(this.viewController.isShow || this.viewController.isShowInnerWeb ? Visibility.Visible : Visibility.Hidden) + } + } + + // + + // + + config() { + this.interactCallback.processWebViewVisibility = (show) => { + this.viewController.isShow = show + } + + this.interactCallback.processOpenLinkEvent = (isInsideOpen, isOutsideOpen, url) => { + if (isInsideOpen) { + this.viewController.isShowInnerWeb = true + + this.innerController?.addOnControllerAttached(() => { + this.innerController?.loadUrlCatch(url) + }) + } + if (isOutsideOpen) { + pushToCardPushDetailPage(url) + } + } + + this.interactCallback.processClickProductEvent = (productLink) => { + if (!productLink) { + PLVToastUtils.longShow($r('app.string.plvli_commodity_toast_empty_link')) + return + } + pushToProductDetailPage(productLink) + } + } + + // +} + + +// +@Observed +export class PLVInteractWebViewController implements IPLVBackwardInterface { + isShow: boolean = false + isShowInnerWeb: boolean = false + accessBackward: PLVCallback = () => { + return this.isShow || this.isShowInnerWeb + } + backward: PLVCallback = () => { + if (this.isShowInnerWeb) { + this.isShowInnerWeb = false + } + if (this.isShow) { + this.isShow = false + } + } +} +// diff --git a/scenes_live/src/main/ets/components/interact/cardpush/PLVCardLookTimeLocalRepository.ets b/scenes_live/src/main/ets/components/interact/cardpush/PLVCardLookTimeLocalRepository.ets new file mode 100644 index 0000000..cdc9a65 --- /dev/null +++ b/scenes_live/src/main/ets/components/interact/cardpush/PLVCardLookTimeLocalRepository.ets @@ -0,0 +1,20 @@ +import { PLVBaseModule, PLVPreferencesUtils } from '@polyvharmony/live-scenes-sdk' + +const PREFERENCE_NAME = "plv_card_look_time_local_cache" + +export class PLVCardLookTimeLocalRepository { + // + static saveCache(channelId: string, id: string, lookTime: number) { + PLVPreferencesUtils.put(`${channelId}_${id}`, lookTime, PREFERENCE_NAME) + } + + static getCache(channelId: string, id: string): Promise { + return PLVPreferencesUtils.getNumber(`${channelId}_${id}`, PREFERENCE_NAME) + } + + static hasSaveCache(channelId: string, id: string) { + PLVPreferencesUtils.has(`${channelId}_${id}`, PREFERENCE_NAME) + } + // + +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/interact/cardpush/PLVLICardPushDetailPage.ets b/scenes_live/src/main/ets/components/interact/cardpush/PLVLICardPushDetailPage.ets new file mode 100644 index 0000000..d934a1b --- /dev/null +++ b/scenes_live/src/main/ets/components/interact/cardpush/PLVLICardPushDetailPage.ets @@ -0,0 +1,44 @@ +import { router } from '@kit.ArkUI' +import { PLVDeviceUtils, PLVJSONObject, PLVSimpleWeb, PLVWebController } from '@polyvharmony/live-scenes-sdk' + +const ROUTE_NAME = 'PLVLICardPushDetailPage' + +export function pushToCardPushDetailPage(url: string) { + router.pushNamedRoute({ + name: ROUTE_NAME, + params: { + url: url + } + }) +} + +@Preview +@Component +@Entry({ + routeName: ROUTE_NAME +}) +struct PLVLICardPushDetailPage { + cardpushDetailUrl?: string = (router.getParams() as PLVJSONObject)['url'] as string + controller: PLVWebController = new PLVWebController() + + aboutToAppear(): void { + } + + onBackPress(): boolean | void { + if (this.controller.accessBackward()) { + this.controller.backward() + return true + } + } + + build() { + Stack() { + PLVSimpleWeb({ + controller: this.controller, + src: this.cardpushDetailUrl, + showLoading: true + }) + } + .margin({ top: PLVDeviceUtils.getStatusBarHeight(), bottom: PLVDeviceUtils.getNavigationIndicatorHeight() }) + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/interact/cardpush/PLVLICardPushView.ets b/scenes_live/src/main/ets/components/interact/cardpush/PLVLICardPushView.ets new file mode 100644 index 0000000..b327ed6 --- /dev/null +++ b/scenes_live/src/main/ets/components/interact/cardpush/PLVLICardPushView.ets @@ -0,0 +1,234 @@ +import { + PLVCardPushVO, + PLVCommonConstants, + PLVLiveSceneSDK, + PLVLogger, + PLVNewsPushEvent, + PLVShowPushCardEvent, + PLVSocketPushCardEvent, + PLVTextUtils, + PLVTimeUtils +} from '@polyvharmony/live-scenes-sdk' +import { PLVCardLookTimeLocalRepository } from './PLVCardLookTimeLocalRepository' + +@Component +@Preview +export struct PLVLICardPushView { + // + + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @State cardPushInfo: PLVCardPushVO = new PLVCardPushVO() + @State cardImage: Resource | string = "" + @State cardShow: boolean = false + @State cardCdShow: boolean = false + @State cardTipsShow: boolean = false + @State colors: Array<[ResourceColor, number]> = [["#FF9D4D", 0.0], ["#F65F49", 0.5]] + @State tipsText: string | Resource = "" + @State cdText: string = "" + + @State cardWidth: number = 0 + @State cardHeight: number = 0 + @State cdTextHeight: number = 0 + @State tipsWidth: number = 0 + + private canSendCardPushEvent = false + private countDownTaskId?: number + private showPushCardEvent?: PLVShowPushCardEvent + // + + // + + aboutToAppear(): void { + this.observer() + } + // + + // + build() { + RelativeContainer() { + Column() { + if (this.cardShow) { + Image(this.cardImage) + .width(34) + .height(34) + .onAreaChange((oldValue: Area, newValue: Area) => { + this.cardWidth = newValue.width as number + this.cardHeight = newValue.height as number + }) + .onClick((event) => { + if (this.showPushCardEvent != undefined && this.canSendCardPushEvent) { + this.sdk?.interactManager.showCardPush(this.showPushCardEvent) + } + if (this.cardCdShow) { + this.cardTipsShow = true + setTimeout(() => { + this.cardTipsShow = false + }, 3000) + } + }) + } + } + .alignRules({ + top: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Top }, + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End } + }) + .width(34) + .height(34) + .id("cardpush_img") + + if (this.cardCdShow) { + Row() { + Text(this.cdText) + .fontColor("#ADADC0") + .fontSize(10) + } + .onAreaChange((oldValue: Area, newValue: Area) => { + this.cdTextHeight = newValue.height as number + }) + .alignRules({ + top: {anchor: "cardpush_img", align: VerticalAlign.Bottom}, + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End } + }) + .id("cardpush_cd_tv") + } + + if (this.cardTipsShow) { + Row() { + Text(this.tipsText) + .fontSize(13) + .fontColor("#FFFFFF") + } + .borderRadius(4) + .padding({ + left: 4, + right: 4, + top: 3, + bottom: 3 + }) + .linearGradient({ + direction: GradientDirection.Right, + colors: this.colors + }) + .alignRules({ + right: { anchor: "cardpush_img", align: HorizontalAlign.Start }, + top: { anchor: "cardpush_img", align: VerticalAlign.Center } + }) + .onAreaChange((oldValue: Area, newValue: Area) => { + this.tipsWidth = newValue.width as number + }) + .offset({ x: -10, y: -10 }) + .id("cardpush_tips_tv") + + Polygon({ width: 10, height: 10 }) + .points([[0, 0], [0, 8], [4, 4]]) + .fill("#F65F49") + .alignRules({ + left: { anchor: "cardpush_tips_tv", align: HorizontalAlign.End }, + top: { anchor: "cardpush_tips_tv", align: VerticalAlign.Center } + }) + .offset({ y: -15, x: -10 }) + } + } + .height(this.cardShow ? this.cardHeight + this.cdTextHeight : 0) + .width(this.cardShow ? this.tipsWidth + this.cardWidth : 0) + } + // + + // + observer() { + this.sdk?.pushcardManager.eventNotify.on('pushcard_data', (pushEvent: PLVNewsPushEvent) => { + if (pushEvent.EVENT === PLVSocketPushCardEvent.NEWS_PUSH_START) { + this.sdk?.pushcardManager.getCardPushInfo(pushEvent.id) + .then((value) => { + this.cardPushInfo = value + this.acceptCardPushVO(value, pushEvent) + }) + } + + if (pushEvent.EVENT === PLVSocketPushCardEvent.NEWS_PUSH_CANCEL) { + this.disposeCardPushAllTask() + this.cardShow = false + this.cardCdShow = false + this.cardTipsShow = false + } + }) + } + // + + // + + async acceptCardPushVO(cardPushVO: PLVCardPushVO, pushEvent: PLVNewsPushEvent) { + this.disposeCardPushAllTask() + + const id = pushEvent.id + const lookTime = pushEvent.lookTime ?? 0 + const alreadyLookTime = await PLVCardLookTimeLocalRepository.getCache(this.sdk?.channelData.loginChannelId!, id!) + const needLookTime = lookTime - alreadyLookTime + const isEntrance = pushEvent.isEntrance() + const isEntranceOrNeedLook = isEntrance || needLookTime > 0 + this.canSendCardPushEvent = needLookTime <= 0 + + this.cardShow = isEntranceOrNeedLook + this.cardCdShow = isEntranceOrNeedLook && needLookTime > 0 + this.cardTipsShow = isEntranceOrNeedLook && lookTime > 0 + + if (cardPushVO.isGiftboxType()) { + this.cardImage = $r('app.media.plvli_interact_giftbox_gain') + this.colors = [["#F6A125", 0.0], ["#FD8121", 0.0]] + } else if (cardPushVO.isRedpackType()) { + this.cardImage = $r('app.media.plvli_interact_redpack_gain') + this.colors = [["#FF9D4D", 0.0], ["#F65F49", 0.5]] + } else if (cardPushVO.isCustomType()) { + const imgUrl = PLVTextUtils.fixPic(cardPushVO.cardImage) + this.cardImage = imgUrl === "" ? $r('app.media.plvli_interact_redpack_gain') : imgUrl + } + + this.tipsText = cardPushVO.countdownMsg ?? $r("app.string.plvli_card_push_reward_hint") + if (needLookTime > 0) { + setTimeout(() => { + this.cardTipsShow = false + }, 3000) + } + + this.showPushCardEvent = new PLVShowPushCardEvent().setData(pushEvent) + + // 卡片观看计时 + if (needLookTime > 0) { + this.startCardLookCountdownTask(id!, needLookTime, alreadyLookTime, isEntrance) + } + + } + + startCardLookCountdownTask(id: string, needLookTime: number, alreadyLookTime: number, isEntrance: boolean) { + clearInterval(this.countDownTaskId) + this.countDownTaskId = setInterval(() => { + needLookTime -= 1000 + PLVCardLookTimeLocalRepository.saveCache(this.sdk?.channelData.loginChannelId!, id, alreadyLookTime += 1000) + this.cdText = PLVTimeUtils.generateTime(needLookTime, true) + if (needLookTime <= 0) { + this.canSendCardPushEvent = true + // 点击事件 + if (this.showPushCardEvent != undefined) { + this.sdk?.interactManager.showCardPush(this.showPushCardEvent) + } + + this.cardCdShow = false + this.cardTipsShow = false + if (!isEntrance) { + this.cardShow = false + } + clearInterval(this.countDownTaskId) + } + }, 1000) + } + + disposeCardPushAllTask() { + clearInterval(this.countDownTaskId) + this.sdk?.pushcardManager.disposable() + } + + // + + + +} diff --git a/scenes_live/src/main/ets/components/interact/entrance/PLVLIInteractEntranceView.ets b/scenes_live/src/main/ets/components/interact/entrance/PLVLIInteractEntranceView.ets new file mode 100644 index 0000000..2a96804 --- /dev/null +++ b/scenes_live/src/main/ets/components/interact/entrance/PLVLIInteractEntranceView.ets @@ -0,0 +1,58 @@ +import { PLVInteractDataBean, PLVLiveSceneSDK } from '@polyvharmony/live-scenes-sdk' + +@Preview +@Component +export struct PLVLIInteractEntranceView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @State isShowQuestionEntrance?: boolean = false + @State questionText: string | Resource = $r("app.string.plvli_interact_question") + + aboutToAppear(): void { + this.onInteractData() + } + + build() { + if (this.isShowQuestionEntrance) { + Row() { + Row() { + Image($r('app.media.plvli_questionnaire')) + .width(16) + .height(16) + Text(this.questionText) + .fontColor("#ffffff") + .fontSize(14) + .margin({ + left: 4 + }) + } + .padding({ + left: 10, top: 5, right: 10, bottom: 5 + }) + } + .backgroundColor("#333439") + .borderRadius(16) + .onClick((event) => { + this.sdk?.interactManager.showQuestionnaire() + }) + } + } + + onInteractData() { + this.sdk?.interactManager.eventDataNotify.on('interact_data', (data: PLVInteractDataBean) => { + let dataArray = data.callAppEvent?.getDataArray() + if (!dataArray || dataArray.length == 0) { + this.isShowQuestionEntrance = false + } + dataArray?.forEach((value, index)=> { + if (value.isShowQuestionnaireEvent()) { + this.isShowQuestionEntrance = value.isShow ?? false + if (value.title) { + this.questionText = value.title + } + } + }) + }, this) + } + + +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/interact/lottery/PLVLILotteryView.ets b/scenes_live/src/main/ets/components/interact/lottery/PLVLILotteryView.ets new file mode 100644 index 0000000..8dc7688 --- /dev/null +++ b/scenes_live/src/main/ets/components/interact/lottery/PLVLILotteryView.ets @@ -0,0 +1,262 @@ +import { FunctionBean, PLVCommonConstants, PLVLiveSceneSDK, PLVTimeUtils, PLVWebviewUpdateAppStatusVO } from '@polyvharmony/live-scenes-sdk' + +const EVENT_CLICK_LOTTERY_PENDANT = "CLICK_LOTTERY_PENDANT" +const EVENT_UPDATE_IAR_PENDANT = "UPDATE_IAR_PENDANT" +const STATUS_OVER = "over" +const STATUS_RUNNING = "running" +const STATUS_DELAYTIME = "delayTime" + +@Component +export struct PLVLILotteryView { + // + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + + @State colors: Array<[ResourceColor, number]> = [["#FF9D4D", 0.0], ["#F65F49", 0.5]] + @State tipsText: string | Resource = "" + @State cdText: string | Resource = "" + + @State lotteryShow: boolean = false + @State lotteryCdShow: boolean = false + @State lotteryTipsShow: boolean = false + @State lotteryImage: string | Resource = $r('app.media.plvli_interact_lottery') + + @State lotteryWidth: number = 0 + @State lotteryHeight: number = 0 + @State cdTextHeight: number = 0 + @State cdTextWidth: number = 0 + @State tipsWidth: number = 0 + + private lotteryVo: FunctionBean | undefined + private remainTime: number = -1 + private lotteryStatus: string = "" + private showTipsTaskId?: number + private lotteryLookCountdownTaskId?: number + // + + // + + aboutToAppear(): void { + this.observer() + } + + build() { + RelativeContainer() { + if (this.lotteryShow) { + Column() { + Image(this.lotteryImage) + .width(34) + .height(34) + .onAreaChange((oldValue: Area, newValue: Area) => { + this.lotteryWidth = newValue.width as number + this.lotteryHeight = newValue.height as number + }) + + if (this.lotteryCdShow) { + Text(this.cdText) + .fontSize(8) + .fontColor("#FFFFFF") + .onAreaChange((oldValue: Area, newValue: Area) => { + this.cdTextWidth = newValue.width as number + this.cdTextHeight = newValue.height as number + }) + } + } + .onClick((event) => { + this.onClickLottery() + }) + .alignItems(HorizontalAlign.Center) + .alignRules({ + top: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Top }, + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End } + }) + .id('lottery_img') + } + + if (this.lotteryTipsShow) { + Row() { + Text(this.tipsText) + .fontSize(13) + .fontColor("#FFFFFF") + } + .borderRadius(4) + .padding({ + left: 4, + right: 4, + top: 3, + bottom: 3 + }) + .linearGradient({ + direction: GradientDirection.Right, + colors: this.colors + }) + .alignRules({ + right: { anchor: "lottery_img", align: HorizontalAlign.Start }, + top: { anchor: "lottery_img", align: VerticalAlign.Center } + }) + .onAreaChange((oldValue: Area, newValue: Area) => { + this.tipsWidth = newValue.width as number + }) + .offset({ x: -10, y: -10 }) + .id("lottery_tips_tv") + + Polygon({ width: 10, height: 10 }) + .points([[0, 0], [0, 8], [4, 4]]) + .fill("#F65F49") + .alignRules({ + left: { anchor: "lottery_tips_tv", align: HorizontalAlign.End }, + top: { anchor: "lottery_tips_tv", align: VerticalAlign.Center } + }) + .offset({ y: -15, x: -10 }) + } + + } + .height(this.lotteryShow ? this.lotteryHeight + this.cdTextHeight : 0) + .width(this.lotteryShow ? this.tipsWidth + this.lotteryWidth : 0) + } + // + + // + observer() { + this.sdk?.interactManager.interactStatusNotify.on("interact_status", (data: PLVWebviewUpdateAppStatusVO) => { + if (data.event === EVENT_UPDATE_IAR_PENDANT) { + if (!data.value || data.value.dataArray?.length == 0) { + this.hide() + } + data.value?.dataArray?.forEach((functionBean) => { + if (functionBean.event === EVENT_CLICK_LOTTERY_PENDANT) { + this.lotteryVo = functionBean + this.parseLotteryMessage(this.lotteryVo) + } + }) + } + }) + } + // + + + // + parseLotteryMessage(lotteryVo: FunctionBean | undefined) { + if (lotteryVo?.delayTime) { + this.remainTime = lotteryVo.delayTime != 0 ? lotteryVo?.delayTime : -1 + } else { + this.remainTime = -1 + } + this.lotteryStatus = lotteryVo?.status ?? "" + if (!lotteryVo?.isShow) { + this.hide() + return + } + //加载自定义的URL图片 + this.lotteryImage = lotteryVo.icon ?? $r('app.media.plvli_interact_lottery') + if (lotteryVo.status === STATUS_OVER) { + this.disposeLotteryTask() + this.disposeShowTipsTask() + this.lotteryCdShow = true + this.lotteryShow = true + this.lotteryTipsShow = false + return + } + + if (this.remainTime != -1) { + this.startLotteryLookCountDownTask(this.remainTime) + } else { + this.startLotteryNoCountDown() + } + this.show() + } + + show() { + this.lotteryShow = true + this.lotteryCdShow = true + } + + hide() { + this.lotteryShow = false + this.lotteryCdShow = false + this.lotteryTipsShow = false + } + + startLotteryLookCountDownTask(needLookTime: number) { + if (this.lotteryStatus !== STATUS_DELAYTIME) { + return + } + this.disposeLotteryTask() + this.lotteryLookCountdownTaskId = setInterval(() => { + needLookTime -= 1 + this.remainTime = needLookTime + + //设置倒数时间 + this.cdText = PLVTimeUtils.generateTime(this.remainTime * 1000, true) + + //设置tips文案 大于3s的显示 '抽奖暂未开始' 小于3s显示'抽奖即将开始' + if (this.remainTime > 3) { + this.tipsText = $r("app.string.plvli_lottery_no_start") + } + + if (this.remainTime <= 3 && this.remainTime > 0) { + this.tipsText = $r("app.string.plvli_lottery_will_start") + this.showTipsTask() + } + + if (needLookTime <= 0) { + this.cdText = $r("app.string.plvli_lottery_running") + this.lotteryTipsShow = false + clearInterval(this.lotteryLookCountdownTaskId) + } + + }, 1000) + } + + startLotteryNoCountDown() { + this.disposeLotteryTask() + this.disposeShowTipsTask() + switch (this.lotteryStatus) { + case STATUS_RUNNING: + this.lotteryShow = true + this.tipsText = $r("app.string.plvli_lottery_will_start") + this.cdText = $r("app.string.plvli_lottery_running") + break + case STATUS_OVER: + this.cdText = $r("app.string.plvli_lottery_over") + this.lotteryCdShow = true + break + default : + this.lotteryTipsShow = false + } + } + + showTipsTask() { + this.disposeShowTipsTask() + this.lotteryTipsShow = true + this.showTipsTaskId = setTimeout(() => { + this.lotteryTipsShow = false + }, 3000) + } + + disposeLotteryTask() { + clearInterval(this.lotteryLookCountdownTaskId) + } + + disposeShowTipsTask() { + clearTimeout(this.showTipsTaskId) + } + + onClickLottery() { + if (this.lotteryStatus === "") { + return + } + + if (this.lotteryStatus === STATUS_OVER || this.lotteryStatus === STATUS_RUNNING) { + //这里有两种抽奖情况 + // 一种是即时抽奖,即时抽奖的状态为running,时间为0 + //一种是定时抽奖 定时抽奖有 状态有 delayTime、running、over 时间可以为0也可以不为0 + this.sdk?.interactManager.showLottery() + } else { + this.showTipsTask() + } + } + + // + + +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/liveroom/PLVLIBulletinView.ets b/scenes_live/src/main/ets/components/liveroom/PLVLIBulletinView.ets new file mode 100644 index 0000000..39ec36e --- /dev/null +++ b/scenes_live/src/main/ets/components/liveroom/PLVLIBulletinView.ets @@ -0,0 +1,61 @@ +import { PLVWebUtils } from '@polyvharmony/live-scenes-sdk' + +@Preview +@Component +export struct PLVLIBulletinView { + @Link @Watch('onBulletinSrc') bulletinSrc: string | undefined + @State propBulletinSrc: string | undefined = this.bulletinSrc + @State layoutHeightScaleY: number = 0 + appearTimerId?: number + + build() { + if (this.propBulletinSrc) { + Row() { + Text() { + ImageSpan($r('app.media.plvli_gonggao')) + .width(12) + .height(12) + .margin({ right: 6 }) + .verticalAlign(ImageSpanAlignment.CENTER) + Span($r('app.string.plvli_live_bulletin_tip')) + .fontSize(12) + .fontColor('#ffffff') + } + + Text(this.propBulletinSrc) + .fontSize(12) + .fontColor('#ffffff') + .constraintSize({ maxWidth: '75%' }) + .textOverflow({ overflow: TextOverflow.MARQUEE }) + } + .onAppear(() => { + animateTo({ duration: 200, curve: Curve.Linear }, () => { + this.layoutHeightScaleY = 1 + }) + this.appearTimerId = setTimeout(() => { + animateTo({ + duration: 200, curve: Curve.Linear, onFinish: () => { + this.propBulletinSrc = undefined + } + }, () => { + this.layoutHeightScaleY = 0 + }) + }, 2000 + (this.propBulletinSrc?.length || 0) * 80) + }) + .onDisAppear(() => { + this.layoutHeightScaleY = 0 + clearTimeout(this.appearTimerId) + }) + .padding({ left: 10, right: 10 }) + .backgroundColor('#B30181FF') + .alignItems(VerticalAlign.Center) + .height(24) + .scale({ y: this.layoutHeightScaleY }) + .borderRadius(12) + } + } + + onBulletinSrc() { + this.propBulletinSrc = PLVWebUtils.cleanHTML(this.bulletinSrc || '') + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/liveroom/PLVLILanguageSwitchDialog.ets b/scenes_live/src/main/ets/components/liveroom/PLVLILanguageSwitchDialog.ets new file mode 100644 index 0000000..04634f7 --- /dev/null +++ b/scenes_live/src/main/ets/components/liveroom/PLVLILanguageSwitchDialog.ets @@ -0,0 +1,90 @@ +import { PLVCommonConstants, PLVDeviceUtils, PLVLiveSceneSDK } from '@polyvharmony/live-scenes-sdk' + +@Preview +@CustomDialog +export struct PLVLILanguageSwitchDialog { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + controller?: CustomDialogController + + build() { + Stack() { + Column() { + Button($r('app.string.plvli_live_language_switch_zh'), { type: ButtonType.Normal }) + .padding({ top: 16, bottom: 16 }) + .backgroundColor(Color.Transparent) + .width(PLVCommonConstants.FULL_PERCENT) + .fontSize(16) + .fontColor('#000000') + .onClick(() => { + if (!this.sdk?.languageManager.isZH()) { + this.confirmSwitchLanguage(true) + return + } + this.controller?.close() + }) + Divider() + .strokeWidth(1) + .color('#f2f2f2') + Button($r('app.string.plvli_live_language_switch_en'), { type: ButtonType.Normal }) + .padding({ top: 16, bottom: 16 }) + .backgroundColor(Color.Transparent) + .width(PLVCommonConstants.FULL_PERCENT) + .fontSize(16) + .fontColor('#000000') + .onClick(() => { + if (!this.sdk?.languageManager.isEN()) { + this.confirmSwitchLanguage(false) + return + } + this.controller?.close() + }) + Divider() + .strokeWidth(8) + .color('#f2f2f2') + Button($r('app.string.plvli_common_dialog_cancel'), { type: ButtonType.Normal }) + .padding({ top: 16, bottom: 16 }) + .backgroundColor(Color.Transparent) + .width(PLVCommonConstants.FULL_PERCENT) + .fontSize(16) + .fontColor('#000000') + .onClick(() => { + this.controller?.close() + }) + } + .padding({ bottom: PLVDeviceUtils.getNavigationIndicatorHeight() - 6 }) + .width(PLVCommonConstants.FULL_PERCENT) + .justifyContent(FlexAlign.End) + } + .borderRadius({ + topLeft: 12, + topRight: 12, + bottomLeft: 0, + bottomRight: 0 + }) + .width(PLVCommonConstants.FULL_PERCENT) + .backgroundColor('#ffffff') + } + + confirmSwitchLanguage(changToZH: boolean) { + changToZH ? this.sdk?.languageManager.changeToZH() : this.sdk?.languageManager.changeToEN() + this.controller?.close() + // reserve temporarily + // AlertDialog.show({ + // message: $r('app.string.plvli_live_language_switch_hint'), + // autoCancel: true, + // alignment: DialogAlignment.Center, + // primaryButton: { + // value: $r('app.string.plvli_common_dialog_cancel'), + // action: () => { + // } + // }, + // secondaryButton: { + // value: $r('app.string.plvli_common_dialog_confirm'), + // action: () => { + // changToZH ? this.sdk?.languageManager.changeToZH() : this.sdk?.languageManager.changeToEN() + // this.controller?.close() + // } + // } + // }) + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/liveroom/PLVLIMoreView.ets b/scenes_live/src/main/ets/components/liveroom/PLVLIMoreView.ets new file mode 100644 index 0000000..b1d7bb1 --- /dev/null +++ b/scenes_live/src/main/ets/components/liveroom/PLVLIMoreView.ets @@ -0,0 +1,120 @@ +import { PLVCommonConstants, PLVDeviceUtils, PLVFunctionSwitchBean } from '@polyvharmony/live-scenes-sdk' +import { PLVLILanguageSwitchDialog } from './PLVLILanguageSwitchDialog' + +@Preview +@Component +export struct PLVLIMoreView { + moreDialogController = new CustomDialogController({ + builder: PLVLIMoreDialog(), + alignment: DialogAlignment.Bottom, + customStyle: true + }) + + build() { + Image($r('app.media.plvli_more')) + .width(32) + .height(32) + .draggable(false) + .onClick(() => { + this.moreDialogController.open() + }) + } +} + +@Preview +@CustomDialog +export struct PLVLIMoreDialog { + // 语言切换 + static readonly MORE_FUNCTION_TYPE_LANGUAGE_SWITCH = 'MORE_FUNCTION_TYPE_LANGUAGE_SWITCH' + static readonly FUNCTION_SWITCH_LIST: PLVFunctionSwitchBean[] = [ + new PLVFunctionSwitchBean(PLVLIMoreDialog.MORE_FUNCTION_TYPE_LANGUAGE_SWITCH, $r('app.media.plvli_live_more_language_switch'), $r('app.string.plvli_live_language_switch'), true), + ] + functionSwitchList = PLVLIMoreDialog.FUNCTION_SWITCH_LIST + controller?: CustomDialogController + languageSwitchDialogController = new CustomDialogController({ + builder: PLVLILanguageSwitchDialog(), + alignment: DialogAlignment.Bottom, + customStyle: true + }) + + build() { + Stack() { + Column() { + Row() { + Blank() + Image($r('app.media.plvli_popup_close')) + .width(34) + .height(34) + .objectFit(ImageFit.ScaleDown) + .draggable(false) + .onClick(() => { + this.controller?.close() + }) + } + .width(PLVCommonConstants.FULL_PERCENT) + + Grid() { + ForEach(this.functionSwitchList, (data: PLVFunctionSwitchBean, index) => { + GridItem() { + Column() { + Image(data.imageResource ? data.imageResource : data.icon) + .width(32) + .height(32) + .draggable(false) + Text(data.name) + .margin({ top: 10 }) + .maxLines(2) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontSize(12) + .textAlign(TextAlign.Center) + .fontColor('#CDCDCD') + } + .onClick(() => { + this.handleGridItemClick(data) + this.controller?.close() + }) + } + .height(PLVCommonConstants.FULL_PERCENT) + }) + } + .rowsGap(4) + .columnsGap(4) + .columnsTemplate('1fr 1fr 1fr 1fr 1fr') + .height(0) + .layoutWeight(1) + } + .padding({ + left: 6, + right: 6, + top: 6, + bottom: PLVDeviceUtils.getNavigationIndicatorHeight() - 6 + }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + .borderRadius({ + topLeft: 12, + topRight: 12, + bottomLeft: 0, + bottomRight: 0 + }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(130) + .backgroundColor('#cc000000') + .backdropBlur(8) + } + + handleGridItemClick(data: PLVFunctionSwitchBean) { + switch (data.type) { + case PLVLIMoreDialog.MORE_FUNCTION_TYPE_LANGUAGE_SWITCH: + this.handleLanguageClick() + break + default: + break + } + } + + handleLanguageClick() { + this.languageSwitchDialogController.open() + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/liveroom/PLVLIWatchInfoView.ets b/scenes_live/src/main/ets/components/liveroom/PLVLIWatchInfoView.ets new file mode 100644 index 0000000..41acf83 --- /dev/null +++ b/scenes_live/src/main/ets/components/liveroom/PLVLIWatchInfoView.ets @@ -0,0 +1,78 @@ +import { PLVChannelData, PLVChatroomManager, PLVCommonConstants, PLVLiveSceneSDK } from '@polyvharmony/live-scenes-sdk' + +const TAG = '[PLVLIWatchInfoView]' + +@Preview +@Component +export struct PLVLIWatchInfoView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @LocalStorageLink('channelData') channelData?: PLVChannelData = undefined + @State chatroomManager: PLVChatroomManager | undefined = this.sdk?.chatroomManager + // 为true时显示在线人数,为false时显示观看热度 + showOnlineUserNumber: boolean = true + + aboutToAppear(): void { + this.onChannelData() + this.onChatData() + } + + build() { + RelativeContainer() { + Image(this.channelData?.liveDetail?.coverImage) + .width(28) + .height(28) + .borderRadius(28) + .margin({ left: 4 }) + .draggable(false) + .alignRules({ + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start }, + center: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Center } + }) + .id('avatarImage') + Text(this.channelData?.liveDetail?.getPublisher()) + .fontSize(14) + .fontColor('#ffffff') + .margin({ left: 4, top: 2 }) + .alignRules({ + top: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Top }, + left: { anchor: 'avatarImage', align: HorizontalAlign.End } + }) + .id('nickNameText') + Text() { + ImageSpan($r('app.media.plvli_watch')) + .width(12) + .height(12) + .margin({ right: 3 }) + .verticalAlign(ImageSpanAlignment.CENTER) + Span(this.showOnlineUserNumber ? this.chatroomManager?.onlineUserNumber + '' : this.channelData?.pageViewer + '') + .fontColor('#ffffff') + .fontSize(12) + } + .alignRules({ + left: { anchor: 'nickNameText', align: HorizontalAlign.Start }, + top: { anchor: 'nickNameText', align: VerticalAlign.Bottom }, + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End } + }) + .margin({ right: 8 }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(1) + .id('watchCountText') + } + .backgroundColor('#73000000') + .borderRadius(36) + .width(118) + .height(36) + } + + onChannelData() { + this.channelData?.eventNotify.on('page_viewer', (arg: number) => { + this.channelData!.pageViewer = arg + }, this) + } + + onChatData() { + this.chatroomManager?.eventNotify.on('online_user_number', (arg: number) => { + this.chatroomManager!.onlineUserNumber = arg + }, this) + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/player/PLVLIPlayerLayout.ets b/scenes_live/src/main/ets/components/player/PLVLIPlayerLayout.ets new file mode 100644 index 0000000..6abfd88 --- /dev/null +++ b/scenes_live/src/main/ets/components/player/PLVLIPlayerLayout.ets @@ -0,0 +1,157 @@ +import { PLVCommonConstants, PLVLiveSceneSDK } from '@polyvharmony/live-scenes-sdk' +import { + DerivedState, + lateInit, + MutableObserver, + PLVLiveStatusEnum, + PLVMediaPlayerPlayingState, + PLVMediaPlayerState +} from '@polyvharmony/media-player-sdk' +import { display, window } from '@kit.ArkUI' +import { common } from '@kit.AbilityKit' + +@Component +export struct PLVLIPlayerLayout { + @LocalStorageLink('sdk') sdk: PLVLiveSceneSDK = lateInit() + private context = getContext(this) as common.UIAbilityContext + @State videoWidth: number = 0 + @State videoHeight: number = 0 + @State isNoLive: boolean = true + @State isToPlayButtonVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.observeMediaPlayerState() + } + + build() { + RelativeContainer() { + // 播放器渲染视图 + XComponent({ + id: `plvli_video_xcomponent`, + type: "surface", + libraryname: "plvplayer_xcomponent" + }) { + } + .onLoad((component) => { + this.sdk.playerManager.startMainMediaPlayerWithComponent(component!) + }) + .id('plvli_player_xcomponent') + .width(this.videoWidth) + .height(this.videoHeight) + .alignRules({ + center: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Center }, + middle: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Center } + }) + + // 双击暂停播放手势 & 暂停播放按钮 + Stack() { + Image($r("app.media.plvli_player_to_play_icon")) + .width(72) + .height(72) + .visibility(this.isToPlayButtonVisible ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.switchMainPlayerPlayingState() + }) + } + .id('plvli_player_play_pause_gesture_layout') + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + .gesture( + TapGesture({ count: 2 }) + .onAction(() => { + this.switchMainPlayerPlayingState() + }) + ) + + // 暂无直播提示 + Column({ space: 12 }) { + Image($r("app.media.plvli_player_no_live_placeholder")) + .width(150) + .height(116) + + Text($r("app.string.plvli_player_no_live_hint_text")) + .fontSize(12) + .fontColor('#e4e4e4') + } + .id('plvli_player_no_live_placeholder_layout') + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .backgroundColor('#00021A') + .visibility(this.isNoLive ? Visibility.Visible : Visibility.None) + .hitTestBehavior(HitTestMode.None) + } + } + + private observeMediaPlayerState() { + this.sdk.playerManager.mainMediaPlayer.getStateListenerRegistry() + .videoSize + .observe((videoSize) => { + const containerWidth = px2vp(display.getDefaultDisplaySync().width) + const containerHeight = px2vp(display.getDefaultDisplaySync().height) + const containerRatio = containerWidth / containerHeight + let videoWidth = containerWidth + let videoHeight = containerHeight + const videoRatio = videoSize.width() / videoSize.height() + // fit center + if (containerRatio > videoRatio) { + videoWidth = containerHeight / videoSize.height() * videoSize.width() + } else if (containerRatio < videoRatio) { + videoHeight = containerWidth / videoSize.width() * videoSize.height() + } + this.videoWidth = videoWidth + this.videoHeight = videoHeight + }) + .pushTo(this.observers) + + this.sdk.playerManager.mainMediaPlayer.getBusinessListenerRegistry() + .liveStatus + .observe((liveStatus) => { + this.isNoLive = liveStatus === PLVLiveStatusEnum.NO_LIVE + }) + .pushTo(this.observers) + + this.sdk.playerManager.mainMediaPlayer.getStateListenerRegistry() + .playingState + .observe((playingState) => { + this.setKeepScreenOn(playingState === PLVMediaPlayerPlayingState.PLAYING) + }) + .pushTo(this.observers) + + const toPlayButtonVisibleState = new DerivedState(() => { + const isLive = this.sdk.playerManager.mainMediaPlayer.getBusinessListenerRegistry() + .liveStatus + .value === PLVLiveStatusEnum.LIVE + const isPaused = this.sdk.playerManager.mainMediaPlayer.getStateListenerRegistry() + .playerState + .value === PLVMediaPlayerState.STATE_PAUSED + return isLive && isPaused + }); + toPlayButtonVisibleState.observe((isToPlayButtonVisible) => { + this.isToPlayButtonVisible = isToPlayButtonVisible + }).pushTo(this.observers) + } + + private switchMainPlayerPlayingState() { + const isPlaying = this.sdk.playerManager.mainMediaPlayer.getStateListenerRegistry() + .playingState + .value === PLVMediaPlayerPlayingState.PLAYING + if (isPlaying) { + this.sdk.playerManager.mainMediaPlayer.pause() + } else { + this.sdk.playerManager.mainMediaPlayer.restart() + } + } + + private async setKeepScreenOn(keepScreenOn: boolean) { + const lastWindow = await window.getLastWindow(this.context); + lastWindow.setWindowKeepScreenOn(keepScreenOn); + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/product/PLVLIProductDetailPage.ets b/scenes_live/src/main/ets/components/product/PLVLIProductDetailPage.ets new file mode 100644 index 0000000..bb04d05 --- /dev/null +++ b/scenes_live/src/main/ets/components/product/PLVLIProductDetailPage.ets @@ -0,0 +1,44 @@ +import { router } from '@kit.ArkUI' +import { PLVDeviceUtils, PLVJSONObject, PLVSimpleWeb, PLVWebController } from '@polyvharmony/live-scenes-sdk' + +const ROUTE_NAME = 'PLVLIProductDetailPage' + +export function pushToProductDetailPage(url: string) { + router.pushNamedRoute({ + name: ROUTE_NAME, + params: { + url: url + } + }) +} + +@Preview +@Component +@Entry({ + routeName: ROUTE_NAME +}) +struct PLVLIProductDetailPage { + productDetailUrl?: string = (router.getParams() as PLVJSONObject)['url'] as string + controller: PLVWebController = new PLVWebController() + + aboutToAppear(): void { + } + + onBackPress(): boolean | void { + if (this.controller.accessBackward()) { + this.controller.backward() + return true + } + } + + build() { + Stack() { + PLVSimpleWeb({ + controller: this.controller, + src: this.productDetailUrl, + showLoading: true + }) + } + .margin({ top: PLVDeviceUtils.getStatusBarHeight(), bottom: PLVDeviceUtils.getNavigationIndicatorHeight() }) + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/product/PLVLIProductView.ets b/scenes_live/src/main/ets/components/product/PLVLIProductView.ets new file mode 100644 index 0000000..66aa345 --- /dev/null +++ b/scenes_live/src/main/ets/components/product/PLVLIProductView.ets @@ -0,0 +1,251 @@ +import { PLVCommonConstants, PLVLiveSceneSDK, PLVProductDataBean, PLVTextUtils, PLVToastUtils } from '@polyvharmony/live-scenes-sdk' +import { pushToProductDetailPage } from './PLVLIProductDetailPage' +import { PLVProductWebViewController } from './PLVLIProductWebView' + +const TAG = '[PLVLIProductView]' + +@Preview +@Component +export struct PLVLIProductView { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @Link productDataBean: PLVProductDataBean + @State clickTimesScale: number = 1 + @Link productWebViewController: PLVProductWebViewController + + build() { + RelativeContainer() { + this.pushCardView() + Image($r('app.media.plvli_shopping_trolley')) + .width(32) + .height(32) + .draggable(false) + .alignRules({ + top: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Top }, + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start }, + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom }, + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End } + }) + .onClick(() => { + this.productWebViewController.isShow = true + }) + .id('entryImageView') + } + .width(32) + .height(32) + } + + @Builder + pushCardView() { + if (this.productDataBean.pushContentBean && + !this.productDataBean.pushContentBean.isBigProduct()) { + Column() { + RelativeContainer() { + if (this.productDataBean.pushContentBean.getCover()) { + Image(this.productDataBean.pushContentBean.getCover()) + .draggable(false) + .width(96) + .alignRules({ + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start }, + top: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Top }, + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom } + }) + .borderRadius(4) + .id('coverView') + } else { + Text(PLVTextUtils.toString(this.productDataBean.pushContentBean.showId)) + .width(96) + .fontColor('#FFFFFFFF') + .backgroundColor('#59000000') + .fontSize(24) + .alignRules({ + left: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.Start }, + top: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Top }, + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom } + }) + .borderRadius(4) + .fontWeight(FontWeight.Bold) + .textAlign(TextAlign.Center) + .id('coverView') + } + if (this.productDataBean.hotEffectEnable) { + Text() { + ImageSpan($r('app.media.plvli_product_hot_effect_icon')) + .width(10) + .height(10) + .margin({ right: 4 }) + .verticalAlign(ImageSpanAlignment.CENTER) + Span(this.productDataBean.hotEffectTips) + .fontColor('#FFFFFFFF') + .fontSize(10) + if (this.productDataBean.getHotEffectClickTimes()) { + Span(` x${this.productDataBean.getHotEffectClickTimes()}`) + .fontColor('#FFFFFFFF') + .fontSize(10) + } + } + .borderRadius({ topLeft: 4, topRight: 4 }) + .alignRules({ + top: { anchor: 'coverView', align: VerticalAlign.Top }, + left: { anchor: 'coverView', align: HorizontalAlign.Start }, + right: { anchor: 'coverView', align: HorizontalAlign.End } + }) + .padding({ left: 4 }) + .height(20) + .backgroundImage($r('app.media.plvli_greet_bg')) + .backgroundImageSize(ImageSize.Cover) + } + Text(this.productDataBean.pushContentBean.name) + .margin({ left: 8 }) + .alignRules({ + left: { anchor: 'coverView', align: HorizontalAlign.End }, + top: { anchor: 'coverView', align: VerticalAlign.Top }, + bottom: { anchor: 'featuresRow', align: VerticalAlign.Top }, + right: { anchor: 'closeImageView', align: HorizontalAlign.Start } + }) + .fontSize(14) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontColor(Color.Gray) + .chainMode(Axis.Vertical, ChainStyle.SPREAD_INSIDE) + .id('nameTextView') + if (this.productDataBean.pushContentBean.getParsedFeatures()) { + Row() { + ForEach(this.productDataBean.pushContentBean.getParsedFeatures(), (item: string, index) => { + Text(item) + .fontSize(10) + .padding({ + left: 6, + right: 6, + top: 2, + bottom: 2 + }) + .margin({ right: 6 }) + .borderRadius(4) + .fontColor('#FF8F11') + .backgroundColor('#14FF8F11') + }) + } + .alignRules({ + top: { anchor: 'nameTextView', align: VerticalAlign.Bottom }, + left: { anchor: 'nameTextView', align: HorizontalAlign.Start }, + bottom: { anchor: 'descTextView', align: VerticalAlign.Top } + }) + .id('featuresRow') + } + Text(this.productDataBean.pushContentBean.productDesc) + .alignRules({ + left: { anchor: 'nameTextView', align: HorizontalAlign.Start }, + top: { anchor: 'featuresRow', align: VerticalAlign.Bottom }, + bottom: { anchor: 'priceTextView', align: VerticalAlign.Top }, + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End } + }) + .fontSize(12) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontColor('#99333333') + .id('descTextView') + Text() { + if (this.productDataBean.pushContentBean.isNormalProduct()) { + Span(this.productDataBean.pushContentBean.isFreeForPay() ? $r('app.string.plvli_commodity_free') : `¥${this.productDataBean.pushContentBean.realPrice}`) + .fontColor('#FF473A') + .fontSize(18) + .fontWeight(FontWeight.Bold) + if (!(this.productDataBean.pushContentBean.isRealPriceEqualsPrice() || this.productDataBean.pushContentBean.isSrcPriceZero() || + this.productDataBean.pushContentBean.isFinanceProduct())) { + Span(` `) + Span(`¥${this.productDataBean.pushContentBean.price}`) + .fontColor('#ADADC0') + .decoration({ type: TextDecorationType.LineThrough, color: '#ADADC0', style: TextDecorationStyle.SOLID }) + .fontSize(12) + } + } else if (this.productDataBean.pushContentBean.isFinanceProduct()) { + Span(this.productDataBean.pushContentBean.yield) + .fontColor('#FF473A') + .fontSize(18) + .fontWeight(FontWeight.Bold) + } else if (this.productDataBean.pushContentBean.isPositionProduct()) { + Span(this.productDataBean.pushContentBean.getTreatment()) + .fontColor('#FF473A') + .fontSize(18) + .fontWeight(FontWeight.Bold) + } + } + .alignRules({ + left: { anchor: 'nameTextView', align: HorizontalAlign.Start }, + top: { anchor: 'descTextView', align: VerticalAlign.Bottom }, + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom }, + right: { anchor: 'linkEntryImageView', align: HorizontalAlign.Start } + }) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .id('priceTextView') + + Image(this.getLink() ? $r('app.media.plvli_product_enter') : $r('app.media.plvli_product_enter_disabled')) + .width(24) + .height(24) + .draggable(false) + .objectFit(ImageFit.Contain) + .margin({ right: 10 }) + .alignRules({ + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End }, + bottom: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Bottom } + }) + .id('linkEntryImageView') + Image($r('app.media.plvli_product_push_close')) + .draggable(false) + .alignRules({ + right: { anchor: PLVCommonConstants.CONTAINER, align: HorizontalAlign.End }, + top: { anchor: PLVCommonConstants.CONTAINER, align: VerticalAlign.Top } + }) + .onClick(() => { + this.sdk?.productManager.notifyPush(undefined) + }) + .objectFit(ImageFit.Contain) + .padding(8) + .translate({ x: 8, y: -8 }) + .width(24) + .height(24) + .id('closeImageView') + } + .onClick(() => { + this.handlePushCardClick() + }) + .padding(10) + .borderRadius(8) + .backgroundColor(Color.White) + .translate({ x: 32 }) + .height(0) + .layoutWeight(1) + + Polygon({ width: 16, height: 14 }) + .points([[0, 0], [8, 8], [16, 0]]) + .translate({ x: -8 }) + .fill(Color.White) + } + .width(300) + .height(132) + .translate({ y: -32 }) + .alignItems(HorizontalAlign.End) + .justifyContent(FlexAlign.End) + .alignRules({ + bottom: { anchor: 'entryImageView', align: VerticalAlign.Bottom }, + right: { anchor: 'entryImageView', align: HorizontalAlign.End } + }) + } + } + + handlePushCardClick() { + const link = this.getLink() + if (!link) { + PLVToastUtils.longShow($r('app.string.plvli_commodity_toast_empty_link')) + return + } + this.sdk?.productManager.notifyClick() + this.sdk?.productManager.notifyPush(undefined) + pushToProductDetailPage(link) + } + + getLink(): string | undefined { + return this.productDataBean.pushContentBean?.getLinkByType() + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/product/PLVLIProductWebView.ets b/scenes_live/src/main/ets/components/product/PLVLIProductWebView.ets new file mode 100644 index 0000000..2d477dc --- /dev/null +++ b/scenes_live/src/main/ets/components/product/PLVLIProductWebView.ets @@ -0,0 +1,106 @@ +import { + EVENT_TYPE_CLICK_PRODUCT_BUTTON, + IPLVBackwardInterface, + PLVCallback, + PLVCommonConstants, + PLVDeviceUtils, + PLVEventNotify, + PLVInteractManager, + PLVLiveSceneSDK, + PLVProductContentBean, + PLVProductWeb, + PLVSocketWebController, + PLVToastUtils +} from '@polyvharmony/live-scenes-sdk' +import { pushToProductDetailPage } from './PLVLIProductDetailPage' + +const TAG = '[PLVLIProductWebView]' + +@Preview +@Component +export struct PLVLIProductWebView { + @Link viewController: PLVProductWebViewController + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + @State productController?: PLVSocketWebController = this.sdk?.webControllerManager.create() + @State interactManager?: PLVInteractManager = this.sdk?.interactManager + @State offsetY: number = 6 + PLVDeviceUtils.getNavigationIndicatorHeight() + + aboutToAppear(): void { + this.onProductData() + this.onViewData() + } + + build() { + Column() { + Stack() { + Column() { + PLVProductWeb({ + controller: this.productController, + interactManager: this.interactManager + }) + } + .padding({ + left: 6, + right: 6, + top: 6, + bottom: PLVDeviceUtils.getNavigationIndicatorHeight() - 6 + }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + .onTouch((event) => { + event.stopPropagation() + }) + .borderRadius({ + topLeft: 12, + topRight: 12, + bottomLeft: 0, + bottomRight: 0 + }) + .width(PLVCommonConstants.FULL_PERCENT) + .height(420) + .backgroundColor('#CC000000') + .backdropBlur(8) + } + .onTouch(() => { + this.viewController.isShow = false + }) + .offset({ y: this.offsetY }) + .justifyContent(FlexAlign.End) + .visibility(this.viewController.isShow ? Visibility.Visible : Visibility.None) + .width(PLVCommonConstants.FULL_PERCENT) + .height(PLVCommonConstants.FULL_PERCENT) + } + + onProductData() { + this.productController?.eventNotify.on(EVENT_TYPE_CLICK_PRODUCT_BUTTON, (data: PLVProductContentBean) => { + const productLink = data.getLinkByType() + if (!productLink) { + PLVToastUtils.longShow($r('app.string.plvli_commodity_toast_empty_link')) + return + } + pushToProductDetailPage(productLink) + this.viewController.isShow = false + }, this) + } + + onViewData() { + this.viewController.eventNotify.on('isShow', (arg: boolean) => { + this.viewController.isShow = arg + }, this) + } +} + +export class PLVProductWebViewController implements IPLVBackwardInterface { + isShow: boolean = false + eventNotify: PLVEventNotify<'isShow'> = new PLVEventNotify(this) + accessBackward: PLVCallback = () => { + return this.isShow + } + backward: PLVCallback = () => { + if (this.isShow) { + this.eventNotify.emit('isShow', false) + this.isShow = false + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/components/reward/PLVLIRewardView.ets b/scenes_live/src/main/ets/components/reward/PLVLIRewardView.ets new file mode 100644 index 0000000..d58e517 --- /dev/null +++ b/scenes_live/src/main/ets/components/reward/PLVLIRewardView.ets @@ -0,0 +1,249 @@ +import { GiftDetailDataBean, PLVCommonConstants, PLVDeviceUtils, PLVHttpError, PLVI18NDataMapper, PLVJSONObject, PLVJSONUtils, PLVLiveSceneSDK, PLVToastUtils } from '@polyvharmony/live-scenes-sdk' + +@Preview +@Component +export struct PLVLIRewardView { + @Link rewardDataBeans: GiftDetailDataBean[] + @Link rewardType: string + rewardDialogController = new CustomDialogController({ + builder: PLVLIRewardDialog({ + rewardDataBeans: this.rewardDataBeans, + rewardType: this.rewardType + }), + alignment: DialogAlignment.Bottom, + customStyle: true + }) + + build() { + Image($r('app.media.plvli_reward')) + .width(32) + .height(32) + .draggable(false) + .onClick(() => { + this.rewardDialogController.open() + }) + } +} + +@Preview +@CustomDialog +export struct PLVLIRewardDialog { + @Link rewardDataBeans: GiftDetailDataBean[] + @Link rewardType: string + @LocalStorageLink('remainingRewardPoint') remainingRewardPoint: number = 0 + controller?: CustomDialogController + swiperRewardDataBeans: Array = [] + @LocalStorageLink('rewardCountSelectIndex') rewardCountSelectIndex: number = 0 + @LocalStorageLink('rewardItemSelectIndex') rewardItemSelectIndex: number = -1 + @LocalStorageLink('rewardSwiperIndex') rewardSwiperIndex: number = 0 + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = undefined + goodNumArr: number[] = [1, 5, 10, 66, 88, 666] + + aboutToAppear(): void { + const rewardDataBeanSection: GiftDetailDataBean[] = [] + for (let i = 0; i < this.rewardDataBeans.length; i++) { + if (rewardDataBeanSection.length < 10) { + rewardDataBeanSection.push(this.rewardDataBeans[i]) + } + if (rewardDataBeanSection.length == 10 || i == this.rewardDataBeans.length - 1) { + this.swiperRewardDataBeans.push([...rewardDataBeanSection]) + rewardDataBeanSection.length = 0 + } + } + if (this.isPointReward()) { + this.sdk?.rewardManager.getRemainingRewardPoint() + .then((value) => { + this.remainingRewardPoint = value + }) + } + } + + build() { + Stack() { + Column() { + Row() { + Text(this.isPointReward() ? $r('app.string.plvli_reward_type_point') : $r('app.string.plvli_reward_type_cash')) + .fontSize(16) + .fontColor('#FFFFFF') + .margin({ left: 10 }) + if (this.isPointReward()) { + Text($r('app.string.plvli_reward_remain_point', this.remainingRewardPoint + '')) + .fontSize(12) + .fontColor('#ADADC0') + .margin({ left: 8 }) + } + Blank() + Image($r('app.media.plvli_popup_close')) + .width(34) + .height(34) + .objectFit(ImageFit.ScaleDown) + .draggable(false) + .onClick(() => { + this.controller?.close() + }) + } + .justifyContent(FlexAlign.Center) + .width(PLVCommonConstants.FULL_PERCENT) + + Swiper() { + ForEach(this.swiperRewardDataBeans, (item: GiftDetailDataBean[], index1) => { + Grid() { + ForEach(item, (data: GiftDetailDataBean, index2) => { + GridItem() { + Column() { + Image(data.getImg()) + .width(48) + .height(48) + .objectFit(ImageFit.Contain) + .draggable(false) + Text(data.getName()) + .margin({ top: 2 }) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontSize(12) + .textAlign(TextAlign.Center) + .fontColor('#D0D0D0') + Text() { + Span(this.isPointReward() ? data.price + '' : $r('app.string.plvli_commodity_free')) + if (this.isPointReward()) { + Span(data.pointUnit) + } + } + .margin({ top: 4 }) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontSize(10) + .textAlign(TextAlign.Center) + .fontColor('#ADADC0') + } + .padding({ top: 8, bottom: 8 }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + } + .backgroundColor(this.rewardItemSelectIndex == index1 * 10 + index2 ? '#ff3e3e4e' : '#00000000') + .borderRadius(8) + .onClick(() => { + this.rewardItemSelectIndex = index1 * 10 + index2 + }) + }) + } + .margin({ bottom: 30 }) + .rowsTemplate('1fr 1fr') + .columnsTemplate('1fr 1fr 1fr 1fr 1fr') + }) + } + .index(this.rewardSwiperIndex) + .onChange((index) => { + this.rewardSwiperIndex = index + }) + .height(240) + .translate({ y: 8 }) + .loop(false) + .autoPlay(false) + .width(PLVCommonConstants.FULL_PERCENT) + .indicator( + Indicator.dot() + .itemWidth(5) + .itemHeight(5) + .selectedItemWidth(5) + .selectedItemHeight(5) + .color('#80ffffff') + .selectedColor(Color.White) + ) + + Row() { + ForEach(this.goodNumArr, (value: number, index) => { + this.rewardCountText(value + '', index) + }) + Blank() + Button($r('app.string.plvli_reward_text')) + .height(32) + .backgroundColor('#FFA611') + .fontColor('#FFFFFF') + .fontSize(12) + .onClick(() => { + if (this.rewardItemSelectIndex == -1) { + PLVToastUtils.shortShow($r('app.string.plvli_reward_select_cash_hint')) + return + } + this.handleReward(this.rewardDataBeans[this.rewardItemSelectIndex]) + this.controller?.close() + }) + } + .margin({ top: 6 }) + .padding({ left: 10, right: 10 }) + .width(PLVCommonConstants.FULL_PERCENT) + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Start) + } + .padding({ + left: 6, + right: 6, + top: 6, + bottom: PLVDeviceUtils.getNavigationIndicatorHeight() - 6 + }) + .justifyContent(FlexAlign.End) + .width(PLVCommonConstants.FULL_PERCENT) + } + .borderRadius({ + topLeft: 12, + topRight: 12, + bottomLeft: 0, + bottomRight: 0 + }) + .width(PLVCommonConstants.FULL_PERCENT) + .backgroundColor('#cc000000') + .backdropBlur(8) + } + + @Builder + rewardCountText(content: string, index: number) { + Text(content) + .height(24) + .fontColor(this.rewardCountSelectIndex == index ? '#FFFFFF' : '#ADADC0') + .fontSize(12) + .textAlign(TextAlign.Center) + .constraintSize({ minWidth: 36 }) + .backgroundColor(this.rewardCountSelectIndex == index ? '#ff3e3e4e' : '#ff1a1b1f') + .borderWidth(this.rewardCountSelectIndex == index ? 1 : 0) + .borderColor('#ffadadc0') + .borderRadius(12) + .margin({ right: 4 }) + .onClick(() => { + this.rewardCountSelectIndex = index + }) + } + + isPointReward() { + return 'POINT' === this.rewardType + } + + isCashReward() { + return 'CASH' === this.rewardType + } + + handleReward(data: GiftDetailDataBean) { + if (this.isPointReward()) { + this.sdk?.rewardManager.makePointReward(data.goodId, this.goodNumArr[this.rewardCountSelectIndex]) + .then((value) => { + this.remainingRewardPoint = value + }) + .catch((err: PLVHttpError) => { + this.toastError(err) + }) + } else { + this.sdk?.rewardManager.makeGiftCashReward(data.goodId, this.goodNumArr[this.rewardCountSelectIndex]) + .catch((err: PLVHttpError) => { + this.toastError(err) + }) + } + } + + toastError(err: PLVHttpError) { + const errData = PLVJSONUtils.safeParse(err.message) as PLVJSONObject + if (errData) { + const errMessage = errData['message'] as string + PLVToastUtils.shortShow(PLVI18NDataMapper.getException(errMessage) || '') + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/ets/pages/PLVLIWatchPage.ets b/scenes_live/src/main/ets/pages/PLVLIWatchPage.ets new file mode 100644 index 0000000..5e1a60b --- /dev/null +++ b/scenes_live/src/main/ets/pages/PLVLIWatchPage.ets @@ -0,0 +1,51 @@ +import { PLVCallback, PLVChannelData, PLVJSONObject, PLVLiveSceneSDK, PLVUtils } from '@polyvharmony/live-scenes-sdk'; +import { PLVLIWatchLayout, PLVWatchLayoutController } from '../components/PLVLIWatchLayout'; +import { router } from '@kit.ArkUI'; +import { webview } from '@kit.ArkWeb'; + +const TAG = '[PLVLIWatchPage]' +const ROUTE_NAME = 'PLVLIWatchPage' +let storage: LocalStorage = new LocalStorage() + +export function pushToWatchPage(sdkId: number, paramsCallback?: PLVCallback) { + const params: PLVJSONObject = {} + params['sdkId'] = sdkId + paramsCallback?.(params) + storage = new LocalStorage() + router.pushNamedRoute({ + name: ROUTE_NAME, + params: params + }) +} + +@Entry({ + routeName: ROUTE_NAME, + storage: storage +}) +@Component +struct PLVLIWatchPage { + @LocalStorageLink('sdk') sdk?: PLVLiveSceneSDK = PLVLiveSceneSDK.get((router.getParams() as PLVJSONObject)['sdkId'] as number) + @LocalStorageLink('channelData') channelData?: PLVChannelData = this.sdk?.channelData + @State watchLayoutController: PLVWatchLayoutController = new PLVWatchLayoutController() + + aboutToAppear(): void { + PLVUtils.setWindowFullScreen(true) + } + + aboutToDisappear(): void { + PLVUtils.setWindowFullScreen(false) + } + + override onBackPress(): boolean | void { + if (this.watchLayoutController.accessBackward()) { + this.watchLayoutController.backward() + return true + } + } + + build() { + Stack() { + PLVLIWatchLayout({ layoutController: this.watchLayoutController }, storage) + } + } +} \ No newline at end of file diff --git a/scenes_live/src/main/module.json5 b/scenes_live/src/main/module.json5 new file mode 100644 index 0000000..f5f273b --- /dev/null +++ b/scenes_live/src/main/module.json5 @@ -0,0 +1,14 @@ +{ + "module": { + "name": "scenes_live", + "type": "har", + "description": "$string:shared_desc", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ]/*, + "deliveryWithInstall": true, + "pages": "$profile:main_pages"*/ + } +} \ No newline at end of file diff --git a/scenes_live/src/main/resources/base/element/color.json b/scenes_live/src/main/resources/base/element/color.json new file mode 100644 index 0000000..1bbc9aa --- /dev/null +++ b/scenes_live/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "white", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/scenes_live/src/main/resources/base/element/string.json b/scenes_live/src/main/resources/base/element/string.json new file mode 100644 index 0000000..479f407 --- /dev/null +++ b/scenes_live/src/main/resources/base/element/string.json @@ -0,0 +1,248 @@ +{ + "string": [ + { + "name": "shared_desc", + "value": "description" + }, + { + "name": "plvli_live_bulletin", + "value": "Bulletin" + }, + { + "name": "plvli_live_intro", + "value": "Intro" + }, + { + "name": "plvli_live_no_intro", + "value": "No live broadcast introduction~" + }, + { + "name": "plvli_live_bulletin_tip", + "value": "Bulletin: " + }, + { + "name": "plvli_live_language_switch", + "value": "Language" + }, + { + "name": "plvli_live_language_switch_zh", + "value": "简体中文-ZH" + }, + { + "name": "plvli_live_language_switch_en", + "value": "English-EN" + }, + { + "name": "plvli_live_language_switch_hint", + "value": "Change language to re-enter live stream, confirm to switch" + }, + { + "name": "plvli_chat_toast_logging", + "value": "Chatroom logging" + }, + { + "name": "plvli_chat_toast_login_success", + "value": "Chatroom login successful" + }, + { + "name": "plvli_chat_toast_reconnecting", + "value": "Chatroom reconnecting" + }, + { + "name": "plvli_chat_toast_reconnect_success", + "value": "Chatroom reconnected successfully" + }, + { + "name": "plvli_chat_toast_login_failed", + "value": "Chatroom login failed" + }, + { + "name": "plvli_chat_restrict_max_viewer_hint", + "value": "The live room is too popular, please come back later" + }, + { + "name": "plvli_chat_input_tips_chat", + "value": "Join the chat" + }, + { + "name": "plvli_chat_input_tips_quiz", + "value": "Initiate a question" + }, + { + "name": "plvli_chat_send", + "value": "Send" + }, + { + "name": "plvli_chat_toast_send_text_empty", + "value": "The sending content cannot be empty!" + }, + { + "name": "plvli_chat_me", + "value": "%s(me)%s" + }, + { + "name": "plvli_chat_quiz_channel", + "value": "Question Channel" + }, + { + "name": "plvli_chat_quiz_default_tips", + "value": "You have entered the exclusive question channel, and the content of the question will not be made public" + }, + { + "name": "plvli_chat_input_tips_chatroom_close", + "value": "Chatroom is closed" + }, + { + "name": "plvli_chat_view_new_msg", + "value": "News" + }, + { + "name": "plvli_chat_welcome_join", + "value": "welcome %s join" + }, + { + "name": "plvli_chat_welcome_join_multi", + "value": "welcome %s and other %s people join" + }, + { + "name": "plvli_chat_copy", + "value": "Copy" + }, + { + "name": "plvli_chat_more", + "value": "More" + }, + { + "name": "plvli_chat_full_text", + "value": "Full text" + }, + { + "name": "plvli_chat_answer", + "value": "Reply" + }, + { + "name": "plvli_chat_copy_success", + "value": "Copy success" + }, + { + "name": "plvli_chat_input_tips_focus", + "value": "In focus mode" + }, + { + "name": "plvli_chat_toast_been_kicked", + "value": "You have been kicked out of the chatroom by the administrator!" + }, + { + "name": "plvli_chat_toast_account_login_elsewhere", + "value": "The current account has been logged in elsewhere, you will be logged out of this live room" + }, + { + "name": "plvli_chat_toast_chatroom_close", + "value": "Chatroom has been closed" + }, + { + "name": "plvli_chat_toast_chatroom_open", + "value": "Chatroom has been opened" + }, + { + "name": "plvli_chat_toast_history_load_failed", + "value": "History loading failed%s" + }, + { + "name": "plvli_chat_playback_tips", + "value": "Chat replay is on, showing historical messages" + }, + { + "name": "plvli_reward_give", + "value": "given%s" + }, + { + "name": "plvli_reward_type_point", + "value": "Points Reward" + }, + { + "name": "plvli_reward_type_cash", + "value": "Gifts Reward" + }, + { + "name": "plvli_reward_remain_point", + "value": "My points: %s" + }, + { + "name": "plvli_reward_text", + "value": "Reward" + }, + { + "name": "plvli_reward_select_cash_hint", + "value": "Please select a reward gift" + }, + { + "name": "plvli_commodity_free", + "value": "Free" + }, + { + "name": "plvli_commodity_toast_empty_link", + "value": "In-app purchases are not supported" + }, + { + "name": "plvli_red_paper_send_msg", + "value": "%s sent a " + }, + { + "name": "plvli_red_paper_dot", + "value": ", " + }, + { + "name": "plvli_red_paper_get", + "value": "Click to receive" + }, + { + "name": "plvli_common_dialog_tip_warm", + "value": "Warm Tips" + }, + { + "name": "plvli_common_dialog_confirm", + "value": "Confirm" + }, + { + "name": "plvli_common_dialog_cancel", + "value": "Cancel" + }, + { + "name": "plvli_player_no_live_hint_text", + "value": "Not Live" + }, + { + "name": "plvli_interact_question", + "value": "Question" + }, + { + "name": "plvli_card_push_reward_hint", + "value": "Continuous viewing rewards" + }, + { + "name": "plvli_lottery_over", + "value": "Lottery over" + }, + { + "name": "plvli_lottery_will_start", + "value": "Lottery is about to begin" + }, + { + "name": "plvli_lottery_no_start", + "value": "Lottery has not yet started" + }, + { + "name": "plvli_lottery_running", + "value": "In lottery" + }, + { + "name": "plv_red_paper_get", + "value": "Click to receive" + }, + { + "name": "plv_red_paper_red_pack_hint", + "value": "Password red envelope coming soon" + } + ] +} diff --git a/scenes_live/src/main/resources/base/media/plvli_chat_msg.png b/scenes_live/src/main/resources/base/media/plvli_chat_msg.png new file mode 100644 index 0000000..fcad34a Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chat_msg.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chat_quiz_default.png b/scenes_live/src/main/resources/base/media/plvli_chat_quiz_default.png new file mode 100644 index 0000000..c771c9f Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chat_quiz_default.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chat_quiz_selected.png b/scenes_live/src/main/resources/base/media/plvli_chat_quiz_selected.png new file mode 100644 index 0000000..8bc72a0 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chat_quiz_selected.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chat_quote_message_close_icon.png b/scenes_live/src/main/resources/base/media/plvli_chat_quote_message_close_icon.png new file mode 100644 index 0000000..497dc13 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chat_quote_message_close_icon.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like.png new file mode 100644 index 0000000..14b99c8 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_1.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_1.png new file mode 100644 index 0000000..5c89372 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_1.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_10.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_10.png new file mode 100644 index 0000000..ed516d2 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_10.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_2.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_2.png new file mode 100644 index 0000000..1b43efe Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_2.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_3.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_3.png new file mode 100644 index 0000000..df50684 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_3.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_4.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_4.png new file mode 100644 index 0000000..9f683c0 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_4.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_5.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_5.png new file mode 100644 index 0000000..3523385 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_5.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_6.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_6.png new file mode 100644 index 0000000..b2a6ed8 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_6.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_7.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_7.png new file mode 100644 index 0000000..56be74a Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_7.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_8.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_8.png new file mode 100644 index 0000000..e9a981d Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_8.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_9.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_9.png new file mode 100644 index 0000000..1c6f18b Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_btn_like_9.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_doc_icon.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_doc_icon.png new file mode 100644 index 0000000..dedc79e Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_doc_icon.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_pdf_icon.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_pdf_icon.png new file mode 100644 index 0000000..4e76b45 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_pdf_icon.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_ppt_icon.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_ppt_icon.png new file mode 100644 index 0000000..5c7682c Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_ppt_icon.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_xls_icon.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_xls_icon.png new file mode 100644 index 0000000..7e54e80 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_file_share_xls_icon.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_chatroom_red_pack_icon.png b/scenes_live/src/main/resources/base/media/plvli_chatroom_red_pack_icon.png new file mode 100644 index 0000000..f38a220 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_chatroom_red_pack_icon.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_close.png b/scenes_live/src/main/resources/base/media/plvli_close.png new file mode 100644 index 0000000..b5c5729 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_close.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_detail_gonggao.png b/scenes_live/src/main/resources/base/media/plvli_detail_gonggao.png new file mode 100644 index 0000000..91294dd Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_detail_gonggao.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_101.png b/scenes_live/src/main/resources/base/media/plvli_face_101.png new file mode 100644 index 0000000..a015945 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_101.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_102.png b/scenes_live/src/main/resources/base/media/plvli_face_102.png new file mode 100644 index 0000000..b85168b Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_102.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_103.png b/scenes_live/src/main/resources/base/media/plvli_face_103.png new file mode 100644 index 0000000..9f4feb3 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_103.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_104.png b/scenes_live/src/main/resources/base/media/plvli_face_104.png new file mode 100644 index 0000000..07689bf Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_104.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_105.png b/scenes_live/src/main/resources/base/media/plvli_face_105.png new file mode 100644 index 0000000..6d95e25 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_105.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_106.png b/scenes_live/src/main/resources/base/media/plvli_face_106.png new file mode 100644 index 0000000..0b64f9a Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_106.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_107.png b/scenes_live/src/main/resources/base/media/plvli_face_107.png new file mode 100644 index 0000000..9a0bb3f Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_107.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_108.png b/scenes_live/src/main/resources/base/media/plvli_face_108.png new file mode 100644 index 0000000..c841dda Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_108.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_109.png b/scenes_live/src/main/resources/base/media/plvli_face_109.png new file mode 100644 index 0000000..2e66dd2 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_109.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_110.png b/scenes_live/src/main/resources/base/media/plvli_face_110.png new file mode 100644 index 0000000..2821260 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_110.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_111.png b/scenes_live/src/main/resources/base/media/plvli_face_111.png new file mode 100644 index 0000000..5c613fa Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_111.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_112.png b/scenes_live/src/main/resources/base/media/plvli_face_112.png new file mode 100644 index 0000000..a49e991 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_112.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_113.png b/scenes_live/src/main/resources/base/media/plvli_face_113.png new file mode 100644 index 0000000..611964a Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_113.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_114.png b/scenes_live/src/main/resources/base/media/plvli_face_114.png new file mode 100644 index 0000000..16aff77 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_114.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_115.png b/scenes_live/src/main/resources/base/media/plvli_face_115.png new file mode 100644 index 0000000..f219dc7 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_115.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_116.png b/scenes_live/src/main/resources/base/media/plvli_face_116.png new file mode 100644 index 0000000..e969f5d Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_116.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_117.png b/scenes_live/src/main/resources/base/media/plvli_face_117.png new file mode 100644 index 0000000..dd363f5 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_117.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_118.png b/scenes_live/src/main/resources/base/media/plvli_face_118.png new file mode 100644 index 0000000..f14a0e3 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_118.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_119.png b/scenes_live/src/main/resources/base/media/plvli_face_119.png new file mode 100644 index 0000000..8265dd7 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_119.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_120.png b/scenes_live/src/main/resources/base/media/plvli_face_120.png new file mode 100644 index 0000000..6541791 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_120.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_121.png b/scenes_live/src/main/resources/base/media/plvli_face_121.png new file mode 100644 index 0000000..742833c Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_121.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_122.png b/scenes_live/src/main/resources/base/media/plvli_face_122.png new file mode 100644 index 0000000..362c59a Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_122.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_123.png b/scenes_live/src/main/resources/base/media/plvli_face_123.png new file mode 100644 index 0000000..0221684 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_123.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_124.png b/scenes_live/src/main/resources/base/media/plvli_face_124.png new file mode 100644 index 0000000..7dd4009 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_124.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_125.png b/scenes_live/src/main/resources/base/media/plvli_face_125.png new file mode 100644 index 0000000..3cdc9a6 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_125.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_126.png b/scenes_live/src/main/resources/base/media/plvli_face_126.png new file mode 100644 index 0000000..bc32f14 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_126.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_127.png b/scenes_live/src/main/resources/base/media/plvli_face_127.png new file mode 100644 index 0000000..fea5147 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_127.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_128.png b/scenes_live/src/main/resources/base/media/plvli_face_128.png new file mode 100644 index 0000000..d11fb55 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_128.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_129.png b/scenes_live/src/main/resources/base/media/plvli_face_129.png new file mode 100644 index 0000000..398ee4c Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_129.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_130.png b/scenes_live/src/main/resources/base/media/plvli_face_130.png new file mode 100644 index 0000000..e88a92d Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_130.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_131.png b/scenes_live/src/main/resources/base/media/plvli_face_131.png new file mode 100644 index 0000000..39b32cd Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_131.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_132.png b/scenes_live/src/main/resources/base/media/plvli_face_132.png new file mode 100644 index 0000000..18b1f3d Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_132.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_133.png b/scenes_live/src/main/resources/base/media/plvli_face_133.png new file mode 100644 index 0000000..4e04ee8 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_133.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_134.png b/scenes_live/src/main/resources/base/media/plvli_face_134.png new file mode 100644 index 0000000..9b6a575 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_134.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_135.png b/scenes_live/src/main/resources/base/media/plvli_face_135.png new file mode 100644 index 0000000..0489b94 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_135.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_136.png b/scenes_live/src/main/resources/base/media/plvli_face_136.png new file mode 100644 index 0000000..53aaf2b Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_136.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_137.png b/scenes_live/src/main/resources/base/media/plvli_face_137.png new file mode 100644 index 0000000..7e5651a Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_137.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_face_138.png b/scenes_live/src/main/resources/base/media/plvli_face_138.png new file mode 100644 index 0000000..4afe4e8 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_face_138.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_gonggao.png b/scenes_live/src/main/resources/base/media/plvli_gonggao.png new file mode 100644 index 0000000..0a0b402 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_gonggao.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_greet_bg.png b/scenes_live/src/main/resources/base/media/plvli_greet_bg.png new file mode 100644 index 0000000..c08a52b Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_greet_bg.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_img_site.png b/scenes_live/src/main/resources/base/media/plvli_img_site.png new file mode 100644 index 0000000..530ff35 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_img_site.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_intro.png b/scenes_live/src/main/resources/base/media/plvli_intro.png new file mode 100644 index 0000000..535b57c Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_intro.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_live_more_language_switch.png b/scenes_live/src/main/resources/base/media/plvli_live_more_language_switch.png new file mode 100644 index 0000000..2eacccc Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_live_more_language_switch.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_more.png b/scenes_live/src/main/resources/base/media/plvli_more.png new file mode 100644 index 0000000..8645237 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_more.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_player_no_live_placeholder.png b/scenes_live/src/main/resources/base/media/plvli_player_no_live_placeholder.png new file mode 100644 index 0000000..4d3be4b Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_player_no_live_placeholder.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_player_to_play_icon.png b/scenes_live/src/main/resources/base/media/plvli_player_to_play_icon.png new file mode 100644 index 0000000..b29685c Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_player_to_play_icon.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_popup_close.png b/scenes_live/src/main/resources/base/media/plvli_popup_close.png new file mode 100644 index 0000000..8bd7b85 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_popup_close.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_product_enter.png b/scenes_live/src/main/resources/base/media/plvli_product_enter.png new file mode 100644 index 0000000..59eaad6 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_product_enter.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_product_enter_disabled.png b/scenes_live/src/main/resources/base/media/plvli_product_enter_disabled.png new file mode 100644 index 0000000..b4b0a3e Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_product_enter_disabled.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_product_hot_effect_icon.png b/scenes_live/src/main/resources/base/media/plvli_product_hot_effect_icon.png new file mode 100644 index 0000000..14c7d2c Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_product_hot_effect_icon.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_product_push_close.png b/scenes_live/src/main/resources/base/media/plvli_product_push_close.png new file mode 100644 index 0000000..222fad5 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_product_push_close.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_questionnaire.png b/scenes_live/src/main/resources/base/media/plvli_questionnaire.png new file mode 100644 index 0000000..816d325 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_questionnaire.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_reward.png b/scenes_live/src/main/resources/base/media/plvli_reward.png new file mode 100644 index 0000000..f01a5d6 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_reward.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_shopping_trolley.png b/scenes_live/src/main/resources/base/media/plvli_shopping_trolley.png new file mode 100644 index 0000000..cbd8989 Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_shopping_trolley.png differ diff --git a/scenes_live/src/main/resources/base/media/plvli_watch.png b/scenes_live/src/main/resources/base/media/plvli_watch.png new file mode 100644 index 0000000..35dd28e Binary files /dev/null and b/scenes_live/src/main/resources/base/media/plvli_watch.png differ diff --git a/scenes_live/src/main/resources/base/profile/main_pages.json b/scenes_live/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000..76bafd9 --- /dev/null +++ b/scenes_live/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,6 @@ +{ + "src": [ + "pages/PLVLIWatchPage", + "components/product/PLVLIProductDetailPage" + ] +} diff --git a/scenes_live/src/main/resources/en_US/element/string.json b/scenes_live/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000..479f407 --- /dev/null +++ b/scenes_live/src/main/resources/en_US/element/string.json @@ -0,0 +1,248 @@ +{ + "string": [ + { + "name": "shared_desc", + "value": "description" + }, + { + "name": "plvli_live_bulletin", + "value": "Bulletin" + }, + { + "name": "plvli_live_intro", + "value": "Intro" + }, + { + "name": "plvli_live_no_intro", + "value": "No live broadcast introduction~" + }, + { + "name": "plvli_live_bulletin_tip", + "value": "Bulletin: " + }, + { + "name": "plvli_live_language_switch", + "value": "Language" + }, + { + "name": "plvli_live_language_switch_zh", + "value": "简体中文-ZH" + }, + { + "name": "plvli_live_language_switch_en", + "value": "English-EN" + }, + { + "name": "plvli_live_language_switch_hint", + "value": "Change language to re-enter live stream, confirm to switch" + }, + { + "name": "plvli_chat_toast_logging", + "value": "Chatroom logging" + }, + { + "name": "plvli_chat_toast_login_success", + "value": "Chatroom login successful" + }, + { + "name": "plvli_chat_toast_reconnecting", + "value": "Chatroom reconnecting" + }, + { + "name": "plvli_chat_toast_reconnect_success", + "value": "Chatroom reconnected successfully" + }, + { + "name": "plvli_chat_toast_login_failed", + "value": "Chatroom login failed" + }, + { + "name": "plvli_chat_restrict_max_viewer_hint", + "value": "The live room is too popular, please come back later" + }, + { + "name": "plvli_chat_input_tips_chat", + "value": "Join the chat" + }, + { + "name": "plvli_chat_input_tips_quiz", + "value": "Initiate a question" + }, + { + "name": "plvli_chat_send", + "value": "Send" + }, + { + "name": "plvli_chat_toast_send_text_empty", + "value": "The sending content cannot be empty!" + }, + { + "name": "plvli_chat_me", + "value": "%s(me)%s" + }, + { + "name": "plvli_chat_quiz_channel", + "value": "Question Channel" + }, + { + "name": "plvli_chat_quiz_default_tips", + "value": "You have entered the exclusive question channel, and the content of the question will not be made public" + }, + { + "name": "plvli_chat_input_tips_chatroom_close", + "value": "Chatroom is closed" + }, + { + "name": "plvli_chat_view_new_msg", + "value": "News" + }, + { + "name": "plvli_chat_welcome_join", + "value": "welcome %s join" + }, + { + "name": "plvli_chat_welcome_join_multi", + "value": "welcome %s and other %s people join" + }, + { + "name": "plvli_chat_copy", + "value": "Copy" + }, + { + "name": "plvli_chat_more", + "value": "More" + }, + { + "name": "plvli_chat_full_text", + "value": "Full text" + }, + { + "name": "plvli_chat_answer", + "value": "Reply" + }, + { + "name": "plvli_chat_copy_success", + "value": "Copy success" + }, + { + "name": "plvli_chat_input_tips_focus", + "value": "In focus mode" + }, + { + "name": "plvli_chat_toast_been_kicked", + "value": "You have been kicked out of the chatroom by the administrator!" + }, + { + "name": "plvli_chat_toast_account_login_elsewhere", + "value": "The current account has been logged in elsewhere, you will be logged out of this live room" + }, + { + "name": "plvli_chat_toast_chatroom_close", + "value": "Chatroom has been closed" + }, + { + "name": "plvli_chat_toast_chatroom_open", + "value": "Chatroom has been opened" + }, + { + "name": "plvli_chat_toast_history_load_failed", + "value": "History loading failed%s" + }, + { + "name": "plvli_chat_playback_tips", + "value": "Chat replay is on, showing historical messages" + }, + { + "name": "plvli_reward_give", + "value": "given%s" + }, + { + "name": "plvli_reward_type_point", + "value": "Points Reward" + }, + { + "name": "plvli_reward_type_cash", + "value": "Gifts Reward" + }, + { + "name": "plvli_reward_remain_point", + "value": "My points: %s" + }, + { + "name": "plvli_reward_text", + "value": "Reward" + }, + { + "name": "plvli_reward_select_cash_hint", + "value": "Please select a reward gift" + }, + { + "name": "plvli_commodity_free", + "value": "Free" + }, + { + "name": "plvli_commodity_toast_empty_link", + "value": "In-app purchases are not supported" + }, + { + "name": "plvli_red_paper_send_msg", + "value": "%s sent a " + }, + { + "name": "plvli_red_paper_dot", + "value": ", " + }, + { + "name": "plvli_red_paper_get", + "value": "Click to receive" + }, + { + "name": "plvli_common_dialog_tip_warm", + "value": "Warm Tips" + }, + { + "name": "plvli_common_dialog_confirm", + "value": "Confirm" + }, + { + "name": "plvli_common_dialog_cancel", + "value": "Cancel" + }, + { + "name": "plvli_player_no_live_hint_text", + "value": "Not Live" + }, + { + "name": "plvli_interact_question", + "value": "Question" + }, + { + "name": "plvli_card_push_reward_hint", + "value": "Continuous viewing rewards" + }, + { + "name": "plvli_lottery_over", + "value": "Lottery over" + }, + { + "name": "plvli_lottery_will_start", + "value": "Lottery is about to begin" + }, + { + "name": "plvli_lottery_no_start", + "value": "Lottery has not yet started" + }, + { + "name": "plvli_lottery_running", + "value": "In lottery" + }, + { + "name": "plv_red_paper_get", + "value": "Click to receive" + }, + { + "name": "plv_red_paper_red_pack_hint", + "value": "Password red envelope coming soon" + } + ] +} diff --git a/scenes_live/src/main/resources/en_US/media/plvli_interact_giftbox_gain.png b/scenes_live/src/main/resources/en_US/media/plvli_interact_giftbox_gain.png new file mode 100644 index 0000000..0852f3c Binary files /dev/null and b/scenes_live/src/main/resources/en_US/media/plvli_interact_giftbox_gain.png differ diff --git a/scenes_live/src/main/resources/en_US/media/plvli_interact_lottery.png b/scenes_live/src/main/resources/en_US/media/plvli_interact_lottery.png new file mode 100644 index 0000000..53660a2 Binary files /dev/null and b/scenes_live/src/main/resources/en_US/media/plvli_interact_lottery.png differ diff --git a/scenes_live/src/main/resources/en_US/media/plvli_interact_redpack_gain.png b/scenes_live/src/main/resources/en_US/media/plvli_interact_redpack_gain.png new file mode 100644 index 0000000..0726c81 Binary files /dev/null and b/scenes_live/src/main/resources/en_US/media/plvli_interact_redpack_gain.png differ diff --git a/scenes_live/src/main/resources/en_US/media/plvli_red_pack_password_icon.png b/scenes_live/src/main/resources/en_US/media/plvli_red_pack_password_icon.png new file mode 100644 index 0000000..0f1c875 Binary files /dev/null and b/scenes_live/src/main/resources/en_US/media/plvli_red_pack_password_icon.png differ diff --git a/scenes_live/src/main/resources/zh_CN/element/string.json b/scenes_live/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..7d4006b --- /dev/null +++ b/scenes_live/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,248 @@ +{ + "string": [ + { + "name": "shared_desc", + "value": "description" + }, + { + "name": "plvli_live_bulletin", + "value": "公告" + }, + { + "name": "plvli_live_intro", + "value": "直播介绍" + }, + { + "name": "plvli_live_no_intro", + "value": "暂无直播介绍~" + }, + { + "name": "plvli_live_bulletin_tip", + "value": "公告: " + }, + { + "name": "plvli_live_language_switch", + "value": "语言切换" + }, + { + "name": "plvli_live_language_switch_zh", + "value": "简体中文-ZH" + }, + { + "name": "plvli_live_language_switch_en", + "value": "English-EN" + }, + { + "name": "plvli_live_language_switch_hint", + "value": "切换语言将为您重新进入直播间,请点击\"确定\"后切换" + }, + { + "name": "plvli_chat_toast_logging", + "value": "聊天室登录中" + }, + { + "name": "plvli_chat_toast_login_success", + "value": "聊天室登录成功" + }, + { + "name": "plvli_chat_toast_reconnecting", + "value": "聊天室重连中" + }, + { + "name": "plvli_chat_toast_reconnect_success", + "value": "聊天室重连成功" + }, + { + "name": "plvli_chat_toast_login_failed", + "value": "聊天室连接失败" + }, + { + "name": "plvli_chat_restrict_max_viewer_hint", + "value": "直播间太过火爆了,请稍后再来" + }, + { + "name": "plvli_chat_input_tips_chat", + "value": "聊点什么吧~" + }, + { + "name": "plvli_chat_input_tips_quiz", + "value": "发起提问" + }, + { + "name": "plvli_chat_send", + "value": "发送" + }, + { + "name": "plvli_chat_toast_send_text_empty", + "value": "发送内容不能为空!" + }, + { + "name": "plvli_chat_me", + "value": "%s(我)%s" + }, + { + "name": "plvli_chat_quiz_channel", + "value": "提问频道" + }, + { + "name": "plvli_chat_quiz_default_tips", + "value": "你已进入专属的提问频道,提问内容不会公开" + }, + { + "name": "plvli_chat_input_tips_chatroom_close", + "value": "聊天室已关闭" + }, + { + "name": "plvli_chat_view_new_msg", + "value": "查看新信息" + }, + { + "name": "plvli_chat_welcome_join", + "value": "欢迎 %s 进入直播间" + }, + { + "name": "plvli_chat_welcome_join_multi", + "value": "欢迎 %s 等%s人进入直播间" + }, + { + "name": "plvli_chat_copy", + "value": "复制" + }, + { + "name": "plvli_chat_more", + "value": "更多" + }, + { + "name": "plvli_chat_full_text", + "value": "全文" + }, + { + "name": "plvli_chat_answer", + "value": "回复" + }, + { + "name": "plvli_chat_copy_success", + "value": "复制成功" + }, + { + "name": "plvli_chat_input_tips_focus", + "value": "当前为专注模式.." + }, + { + "name": "plvli_chat_toast_been_kicked", + "value": "您已被管理员踢出聊天室!" + }, + { + "name": "plvli_chat_toast_account_login_elsewhere", + "value": "当前账号已在其他地方登录,您将被退出本直播间" + }, + { + "name": "plvli_chat_toast_chatroom_close", + "value": "聊天室已经关闭" + }, + { + "name": "plvli_chat_toast_chatroom_open", + "value": "聊天室已经打开" + }, + { + "name": "plvli_chat_toast_history_load_failed", + "value": "历史记录加载失败%s" + }, + { + "name": "plvli_chat_playback_tips", + "value": "聊天重放功能已开启,将会显示历史消息" + }, + { + "name": "plvli_reward_give", + "value": "赠送%s" + }, + { + "name": "plvli_reward_type_point", + "value": "积分打赏" + }, + { + "name": "plvli_reward_type_cash", + "value": "道具打赏" + }, + { + "name": "plvli_reward_remain_point", + "value": "我的积分: %s" + }, + { + "name": "plvli_reward_text", + "value": "打赏" + }, + { + "name": "plvli_reward_select_cash_hint", + "value": "请选择打赏道具" + }, + { + "name": "plvli_commodity_free", + "value": "免费" + }, + { + "name": "plvli_commodity_toast_empty_link", + "value": "APP内不支持购买" + }, + { + "name": "plvli_red_paper_send_msg", + "value": "%s 发了一个" + }, + { + "name": "plvli_red_paper_dot", + "value": "," + }, + { + "name": "plvli_red_paper_get", + "value": "点击领取" + }, + { + "name": "plvli_common_dialog_tip_warm", + "value": "温馨提示" + }, + { + "name": "plvli_common_dialog_confirm", + "value": "确定" + }, + { + "name": "plvli_common_dialog_cancel", + "value": "取消" + }, + { + "name": "plvli_player_no_live_hint_text", + "value": "暂无直播" + }, + { + "name": "plvli_interact_question", + "value": "问卷" + }, + { + "name": "plvli_card_push_reward_hint", + "value": "连续观看有奖励哦" + }, + { + "name": "plvli_lottery_over", + "value": "已开奖" + }, + { + "name": "plvli_lottery_will_start", + "value": "抽奖即将开始" + }, + { + "name": "plvli_lottery_no_start", + "value": "抽奖暂未开始" + }, + { + "name": "plvli_lottery_running", + "value": "开奖中" + }, + { + "name": "plv_red_paper_get", + "value": "点击领取" + }, + { + "name": "plv_red_paper_red_pack_hint", + "value": "口令红包即将来袭" + } + ] +} diff --git a/scenes_live/src/main/resources/zh_CN/media/plvli_chat_quiz_default.png b/scenes_live/src/main/resources/zh_CN/media/plvli_chat_quiz_default.png new file mode 100644 index 0000000..33fbd35 Binary files /dev/null and b/scenes_live/src/main/resources/zh_CN/media/plvli_chat_quiz_default.png differ diff --git a/scenes_live/src/main/resources/zh_CN/media/plvli_chat_quiz_selected.png b/scenes_live/src/main/resources/zh_CN/media/plvli_chat_quiz_selected.png new file mode 100644 index 0000000..1f91722 Binary files /dev/null and b/scenes_live/src/main/resources/zh_CN/media/plvli_chat_quiz_selected.png differ diff --git a/scenes_live/src/main/resources/zh_CN/media/plvli_interact_giftbox_gain.png b/scenes_live/src/main/resources/zh_CN/media/plvli_interact_giftbox_gain.png new file mode 100644 index 0000000..9942399 Binary files /dev/null and b/scenes_live/src/main/resources/zh_CN/media/plvli_interact_giftbox_gain.png differ diff --git a/scenes_live/src/main/resources/zh_CN/media/plvli_interact_lottery.png b/scenes_live/src/main/resources/zh_CN/media/plvli_interact_lottery.png new file mode 100644 index 0000000..a4cd584 Binary files /dev/null and b/scenes_live/src/main/resources/zh_CN/media/plvli_interact_lottery.png differ diff --git a/scenes_live/src/main/resources/zh_CN/media/plvli_interact_redpack_gain.png b/scenes_live/src/main/resources/zh_CN/media/plvli_interact_redpack_gain.png new file mode 100644 index 0000000..c921f03 Binary files /dev/null and b/scenes_live/src/main/resources/zh_CN/media/plvli_interact_redpack_gain.png differ diff --git a/scenes_live/src/main/resources/zh_CN/media/plvli_red_pack_password_icon.png b/scenes_live/src/main/resources/zh_CN/media/plvli_red_pack_password_icon.png new file mode 100644 index 0000000..9dc9fe0 Binary files /dev/null and b/scenes_live/src/main/resources/zh_CN/media/plvli_red_pack_password_icon.png differ