!78 audioPicker界面及功能

Merge pull request !78 from niuyuanyuan/master
This commit is contained in:
openharmony_ci
2024-06-21 08:33:31 +00:00
committed by Gitee
39 changed files with 3297 additions and 1 deletions
+1
View File
@@ -36,6 +36,7 @@
<filteritem type="filepath" name="figures/.*.png" desc="self developed image"/>
<filteritem type="filepath" name="doc/images/.*.png" desc="self developed image"/>
<filteritem type="filepath" name="AppScope/resources/base/media/.*.png" desc="self developed image"/>
<filteritem type="filepath" name="audiopicker/src/main/resources/base/media/.*.webp" desc="self developed image"/>
</filefilter>
+6
View File
@@ -0,0 +1,6 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test
+31
View File
@@ -0,0 +1,31 @@
{
"apiType": "stageMode",
"buildOption": {
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"entryModules": [
"entry"
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2021-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { 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. */
}
+18
View File
@@ -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
+11
View File
@@ -0,0 +1,11 @@
{
"name": "audiopicker",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
}
}
@@ -0,0 +1,51 @@
import UIExtensionAbility from '@ohos.app.ability.UIExtensionAbility';
import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession';
import Want from '@ohos.app.ability.Want';
import common from '@ohos.app.ability.common';
import { Logger } from '../common/util/HiLogger'
import { MusicApp } from '../common/global/globalmodel/GlobalModel';
import { createOrGet, globalKeys } from '../common/global/GlobalThisHelper'
const logger: Logger = new Logger('AudioPickerExtAbility')
const app: MusicApp = createOrGet(MusicApp, globalKeys.app, ['phone']);
/**
* AudioPicker模态弹框
*/
export default class AudioPickerUIExtensionAbility extends UIExtensionAbility {
onCreate() {
Logger.domain = 0xD004722
logger.info(`UIExtAbility onCreate`)
app.UIExtensionContext = this.context;
app.abilityContext = getContext(this) as common.UIAbilityContext
}
onSessionCreate(want: Want, session: UIExtensionContentSession) {
logger.info(`UIExtAbility onSessionCreate, want: ${JSON.stringify(want)}`)
let param: Record<string, UIExtensionContentSession | Want> = {
'session': session,
'want': want
}
let storage: LocalStorage = new LocalStorage(param);
try {
session.loadContent('pages/card/AudioPickerExtension', storage); // 提供方加载自己界面
} catch (e) {
logger.error(` error ${JSON.stringify(e)}`)
}
}
onSessionDestroy(session: UIExtensionContentSession) {
logger.info(`UIExtAbility onSessionDestroy`)
}
onForeground(): void {
}
onBackground(): void {
}
onDestroy(): void | Promise<void> {
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
import { BusinessError } from '@ohos.base'
import { Logger } from '../common/util/HiLogger'
import { MusicApp } from '../common/global/globalmodel/GlobalModel';
import { createOrGet, globalKeys } from '../common/global/GlobalThisHelper'
import { PreferenceManager } from '../common/preference/PreferenceManager'
const keyPermissionTipLog = 'Key_Permission_TipLog'
const logger: Logger = new Logger('AudioPickerPreference')
const app: MusicApp = createOrGet(MusicApp, globalKeys.app, ['phone']);
export class AudioPickerPreference {
private preferenceManager: PreferenceManager = createOrGet(PreferenceManager,
globalKeys.preferenceManager, [app.abilityContext])
/**
* 保存安全提示状态
* @param isFirstStartUp
*/
public saveSafetyTipStatus(isFirstStartUp: boolean): void {
this.preferenceManager.put(keyPermissionTipLog, isFirstStartUp, true,
this.preferenceManager.preferKeys.audioPicker).then((res) => {
logger.info(`saveSafetyTipStatus Success ? : ${res}`)
}).catch((err: BusinessError) => {
logger.error(`saveSafetyTipStatus Fail, err name:${err.name} , err message:${err.message}`)
})
}
/**
* 获取权限状态
*
* @returns boolean
*/
public getSafetyTipStatus(): Promise<boolean> {
return new Promise((resolve) => {
this.preferenceManager.get(keyPermissionTipLog, false,
this.preferenceManager.preferKeys.audioPicker).then((res) => {
resolve(res as boolean)
}).catch((err: BusinessError) => {
logger.error(`getPrivacyStatus error, err name:${err.name}, err message:${err.message}`)
resolve(false)
})
})
}
}
@@ -0,0 +1,187 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
*/
import { ObservedArray } from '../data/ObservedArray';
import { BaseState, ViewState } from './ViewState';
/**
* [铃声业务页面ViewData]<BR>
*
* @Author: lWX927629
* @Date: 2024/3/7
* @Version: V1.0.71
* @param <VS> 页面状态
* @param <VD> 页面数据
*/
export abstract class AbsBaseViewData<VS extends ViewState, VD> implements IDataSource {
private listeners: DataChangeListener[] = []
/**
* view state
*/
private state: VS;
/**
* view live date list
*/
private liveData: ObservedArray<VD> = new ObservedArray<VD>();
/**
* 构造函数
*/
protected constructor() {
this.state = this.initViewState();
}
totalCount(): number {
return this.liveData.length
}
registerDataChangeListener(listener: DataChangeListener) {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
this.listeners.splice(pos, 1)
}
}
public getData(index: number): VD {
return this.liveData[index]
}
private initViewState(): VS {
return this.createViewState();
}
protected abstract createViewState(): VS;
public getState(): VS {
return this.state;
}
/**
* start loading
*/
public loading(): void {
if (this.isEmpty()) {
this.state?.setViewState(BaseState.LOADING);
}
}
public normal(newData: ObservedArray<VD>): void {
this.clear()
this.liveData.push(...newData);
this.notifyDataReload()
this.state.setViewState(BaseState.NORMAL);
}
/**
* finish loading without data
*/
public normalState(): void {
this.state?.setViewState(BaseState.NORMAL);
}
/**
* clear livedata
*/
public clear(): void {
this.liveData.splice(0, this.liveData?.length)
}
/**
* 获取列表数据,不可变
*
* @return 不可变列表数据,运行时放通,问题在开发阶段发现问题
*/
public getDataList(): ObservedArray<VD> {
return this.liveData;
}
/**
* 针对分页的数据,不要使用normal,直接使用append
*
* @param appendData 需要追加的数据
*/
public append(appendData: VD[]): void {
this.liveData.push(...appendData);
this.notifyDataReload()
this.state.setViewState(BaseState.NORMAL);
}
/**
* 针对分页的数据,不要使用normal,直接使用append
* @param index index
* @param appendData 需要追加的数据
*/
public appendByIndex(index: number, appendData: VD[]): void {
this.liveData.splice(index, 0, ...appendData);
this.notifyDataAdd(index);
this.notifyDataReload()
this.state.setViewState(BaseState.NORMAL);
}
/**
* finish loading without data (only one data)
*
* @param newData newData
*/
public appendSingle(newData: VD): void {
this.liveData.push(newData);
this.state.setViewState(BaseState.NORMAL);
}
/**
* 移除
*
* @param index 位置
*/
public remove(index: number): void {
this.liveData.splice(index, 1)
this.notifyDataDelete(index)
this.notifyDataReload()
}
/**
* 设置数据是否为空,涉及nodata页面展示
*
* @return 数据是否为空
*/
public isEmpty(): boolean {
return this.liveData.length === 0;
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index)
})
}
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index)
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index)
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to)
})
}
}
@@ -0,0 +1,18 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
*/
import { ObservedArray } from '../data/ObservedArray';
import { ViewState } from './ViewState';
/**
* [AbsBaseViewModel]<BR>
*
* @Author: lWX927629
* @Date: 2024/3/7
* @Version: V1.0.71
*/
export abstract class AbsBaseViewModel<VD> {
protected abstract getState() : ViewState
protected abstract getData() : ObservedArray<VD>;
}
@@ -0,0 +1,58 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
*/
/**
* [页面状态]<BR>
*
* @Author: lWX927629
* @Date: 2024/3/7
* @Version: V1.0.71
*/
export enum BaseState {
// 初始状态
DEFAULT,
// 渲染中
LOADING,
// 渲染失败
ERROR,
// 渲染完成
NORMAL
}
@Observed
export class ViewState {
public baseState: BaseState = BaseState.DEFAULT
constructor(baseState: BaseState = BaseState.DEFAULT) {
this.baseState = baseState
}
setViewState(baseState: BaseState) {
this.baseState = baseState
}
getViewState() {
return this.baseState;
}
isLoading(): boolean {
return this.baseState === BaseState.LOADING ? true : false
}
isDefault(): boolean {
return this.baseState === BaseState.DEFAULT ? true : false
}
isNormal(): boolean {
return this.baseState === BaseState.NORMAL ? true : false
}
isError(): boolean {
return this.baseState === BaseState.ERROR ? true : false
}
}
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2021-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Logger } from '../util/HiLogger'
const logger: Logger = new Logger("AppStorageHelper")
/**
* [系统全局storage管理类,支持界面绑定]
*
* @author
* @version [V1.0.0.0, 2022/7/20]
* @since V1.0.0.0
*/
export class AppStorageHelper {
active: boolean = true
unhandledMap: Map<string, any> = new Map()
disable: () => void = () => {
this.active = false
}
enable: () => void = () => {
this.active = true
if (!this.unhandledMap.size) {
return
}
this.unhandledMap.forEach((value, key) => {
setOrCreateAppStorage(key, value)
})
this.unhandledMap.clear()
}
}
// 定义了AppStorage下支持挂载的对象
export const appStorageKeys = {
// 屏幕宽度 px
screenWidth: 'screenWidth',
// 屏幕高度 px
screenHeight: 'screenHeight',
// 窗口宽度 px
windowWidth: 'windowWidth',
// 窗口高度 px
windowHeight: 'windowHeight',
// 顶部状态栏高度 px
statusBarHeight: 'statusBarHeight',
// 底部导航栏高度 px
navigatorBarHeight: 'navigatorBarHeight',
// 样式单位管理对象
styleConfig: 'styleConfig',
// 主题管理对象
themeConfig: 'themeConfig',
// 窗口密度模式
densityType: 'densityType',
// 设置
setting: 'setting',
// 是否可以访问网络
isInternetConnected: 'isInternetConnected',
// 当前系统语言
lang: "lang",
// 是否是深色主题
isDarkTheme: "isDarkTheme",
// 横竖屏切换宽高对象
windowArea: "windowArea",
// 全屏模式比例
appSplitRatio: "appSplitRatio",
serverEnvironment: "serverEnvironment",
// 铃声播放状态
ringTonePlayStatus: "ringTonePlayStatus"
}
/**
* 创建或者重新设置appstorage
*
* @param storageKey key
* @param object 塞入的对象
*/
export function setOrCreateAppStorage<T>(storageKey: string, object: T): void {
let globalStore = globalThis.appStorageHelper as AppStorageHelper
if (globalStore && !globalStore.active) {
globalStore.unhandledMap.set(storageKey, object)
return
}
if (!appStorageKeys[storageKey]) {
logger.error(`Cannot Create storage key of ${storageKey}`)
return
}
AppStorage.SetOrCreate<T>(storageKey, object)
}
/**
* 获取设置过的appstorage
*
* @param storageKey key
* @return storageValue
*/
export function getAppStorage<T>(storageKey: string): T {
logger.info('getAppStorage' + storageKey)
if (!appStorageKeys[storageKey]) {
logger.error(`Cannot getAppStorage storage key of ${storageKey}`)
return undefined
}
return AppStorage.Get<T>(storageKey)
}
@@ -0,0 +1,126 @@
/*
* Copyright (c) 2021-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Logger } from "../util/HiLogger"
/**
* [系统全局globalThis管理类,挂载全局变量]
*
* @author
* @version [V1.0.0.0, 2022/7/20]
* @since V1.0.0.0
*/
const logger: Logger = new Logger("GlobalHelper")
// 支持注册的全局对象名称
export const globalKeys = {
// 全局设备属性对象
deviceInfo: 'deviceInfo',
// ability对象
app: 'app',
// sp管理对象
preferenceManager: 'preferenceManager',
// 安全提示
audioPickerPreference: 'audioPickerPreference'
}
/**
* 设置或者已经创建就返回全局的global对象
*
* @param objectClass 类型
* @param storageKey key
* @param params 参数数组
* @return storageObject
*/
export function createOrGet<T>(objectClass: { new(...params: any[]): T }, storageKey: string, params?: any[]): T {
if (!globalKeys[storageKey]) {
logger.error(`Cannot create key of ${storageKey}`)
return undefined
}
if (!globalThis[storageKey]) {
if (params) {
globalThis[storageKey] = new objectClass(...params)
} else {
globalThis[storageKey] = new objectClass()
}
logger.info(`Create key of ${storageKey}${JSON.stringify(params)}`)
}
return globalThis[storageKey]
}
/**
* 设置并返回全局的global对象
*
* @param objectClass 类型
* @param storageKey key
* @param params 参数数组
* @return storageObject
*/
export function create<T>(objectClass: { new(...params: any[]): T }, storageKey: string, params?: any[]): T {
if (!globalKeys[storageKey]) {
logger.error(`Cannot create key of ${storageKey}`)
return undefined
}
if (params) {
globalThis[storageKey] = new objectClass(...params)
} else {
globalThis[storageKey] = new objectClass()
}
logger.info(`Create key of ${storageKey}${JSON.stringify(params)}`)
return globalThis[storageKey]
}
/**
* 设置全局的global对象
*
* @param object 类型
* @param storageKey key
* @return storageObject
*/
export function setGlobalObj<T>(object: T, storageKey: string): T {
globalThis[storageKey] = object;
logger.info(`Set key of ${storageKey}`)
return globalThis[storageKey]
}
/**
* 删除全局的global对象
*
* @param storageKey key
*/
export function delGlobalObj(storageKey: string): void {
if (!globalKeys[storageKey]) {
logger.error(`Cannot set key of ${storageKey}`)
return
}
logger.info(`delGlobalObj key of ${storageKey}`)
delete globalThis[storageKey]
}
/**
* 获取设置过的global对象
*
* @param object 类型
* @param storageKey key
* @return storageObject
*/
export function getGlobalObj<T>(storageKey: string): T {
if (!globalKeys[storageKey]) {
logger.error(`Cannot get key of ${storageKey}`)
return undefined
}
return globalThis[storageKey]
}
@@ -0,0 +1,980 @@
/*
* Copyright (c) 2021-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* [系统全局globalThis挂载的对象类型定义]
*
* @author
* @version [V1.0.0.0, 2022/7/20]
* @since V1.0.0.0
*/
import window from '@ohos.window'
import display from '@ohos.display'
import deviceInfo from '@ohos.deviceInfo'
import type context from '@ohos.app.ability.common'
import i18n from '@ohos.i18n'
import pointer from '@ohos.multimodalInput.pointer';
import type { BusinessError } from '@ohos.base'
import { Logger } from '../../util/HiLogger'
import { appStorageKeys, setOrCreateAppStorage } from '../AppStorageHelper'
import { createOrGet, globalKeys } from '../GlobalThisHelper'
import { Util } from '../../util/Util'
const logger: Logger = new Logger("GlobalModel")
// PC的头部间距,16 / 160
const PC_TOP_PADDING: number = 0.1
// 分屏比例
const SPLIT_RATIO_MINI = 5 / 12
const SPLIT_RATIO_MAX = 7 / 12
// 白色字符串
const WHITE: string = "#ffffffff"
// 黑色字符串
const BLACK: string = "#ff000000"
// 透明
const TRANS: string = "#00ffffff"
/**
* 窗口对象
*/
export class AppScreen {
// abilityContext
context: context.UIAbilityContext
// stage模型的窗口对象
windowStage: window.WindowStage
// 屏幕宽度
width: number = 0
// 屏幕高度
height: number = 0
// 窗口x坐标
windowPosX: number = 0
// 窗口y坐标
windowPosY: number = 0
// 窗口宽度
windowWidth: number = 0
// 窗口高度
windowHeight: number = 0
// 状态栏高度
topHeight: number = 0
// 导航栏高度
bottomHeight: number = 0
// 屏幕密度类型,用来计算lpx真实物理大小
densityType: DensityTypes = DensityTypes.SM
// 窗口配置变化监听
configChangeListener: Array<(densityType: DensityTypes) => void> = []
// 接收窗口变化后一系列配置完成后的promise
lastWindowSizeResult: Promise<boolean> = Promise.resolve(true)
// 横竖屏模式设定
orientationSetting: window.Orientation = window.Orientation?.AUTO_ROTATION_LANDSCAPE_RESTRICTED
// 当前是横屏1还是竖屏0
orientation: number = 0
// 是否已经设置过沉浸式
isImmersiveSet: boolean = false
// 应用启动后是否设置过densityType
isDensityTypeSet: boolean = false
// DPI mate40pro 560
densityDPI: number = 0
// 是否全屏
isFullScreen: boolean = false
// 沉浸式,可以显示状态栏文字
isLayoutFullScreen: boolean = false
// 沉浸式,不显示状态栏文字
isFullScreenWindow: boolean = false
// 天枢wgr是否分屏, 高度为设备高度,且宽度小于设备宽度
isPCMultiWindow: boolean = false
// 状态栏背景是否是深色
isTopBgDark: boolean = false
// 状态栏背景颜色
statusBarBgColor: string = ""
// 导航栏背景是否是深色
isBottomBgDark: boolean = false
// 屏幕是否常亮
isKeepScreenOn: boolean = false
// 判断当前设备是否可折叠
isFoldScreen: boolean = false
// 获取当前屏幕折叠状态 展开态: 1 折叠态: 2 半折态: 3
displayStatus: number = 1
// 获取当前屏幕显示模式0:折叠状态未知1:折叠状态为完全展开2:折叠状态为折叠3:折叠状态为半折叠。半折叠指完全展开和折叠之间的状态。
displayMode: number = 0
// 分屏比例
appSplitRatio: AppSplitRatios = AppSplitRatios.NO
// 当前窗口模式
windowStatusType: window.WindowStatusType = window.WindowStatusType.UNDEFINED
// 应用信息
app: MusicApp
// 构造函数
constructor(context: context.UIAbilityContext, windowStage: window.WindowStage) {
logger.info('new Screen')
this.context = context
this.windowStage = windowStage
this.app = createOrGet(MusicApp, globalKeys.app)
this.init()
}
/**
* 初始化
*/
async init(): Promise<void> {
this.enterImmersion()
this.getDisplayStatus()
this.bindWindowSizeListener()
this.bindFoldStatusChange()
this.bindFoldDisplayModeChange()
this.setOrientationSetting()
this.lastWindowSizeResult = this.windowResize()
this.bindWindowEventListener()
}
/**
* 监听应用窗口焦点变化
*/
bindWindowEventListener(): void {
window.getLastWindow(this.context).then((windowClass) => {
logger.info('Succeeded in obtaining the top window. Data: ' + windowClass);
if (!windowClass) {
return
}
try {
windowClass.on('windowEvent', (value) => {
if (value === window.WindowEventType.WINDOW_ACTIVE) {
logger.info('Window event happened. Event:' + value);
this.app?.setActiveNo()
}
});
} catch (err) {
logger.error('Failed to register callback. Cause: ' + JSON.stringify(err));
}
}).catch((err: BusinessError) => {
logger.error('Failed to obtain the top window. Cause: ' + JSON.stringify(err));
});
}
getDisplayStatus(): void {
this.isFoldScreen = display.isFoldable();
this.displayStatus = display.getFoldStatus();
this.displayMode = display.getFoldDisplayMode();
}
setOrientationSetting(): void {
let orientation: window.Orientation = window.Orientation.AUTO_ROTATION_RESTRICTED
let globalDeviceInfo: DeviceInfo = createOrGet(DeviceInfo, globalKeys.deviceInfo)
let isPhone: boolean = globalDeviceInfo.deviceType === DeviceTypes.PHONE
logger.info('this.isFoldScreen' + this.isFoldScreen + 'this.displayMode' + this.displayMode)
if (this.isFoldScreen) {
if (this.displayMode === 1) {
logger.info('this.isFoldScreen' + this.isFoldScreen + 'this.displayMode' + this.displayMode)
orientation = window.Orientation.AUTO_ROTATION_RESTRICTED
} else if (this.displayMode === 2) {
logger.info('this.isFoldScreen' + this.isFoldScreen + 'this.displayMode' + this.displayMode)
orientation = window.Orientation.PORTRAIT
} else if (this.displayMode === 3 || this.displayMode === 0) {
return
}
} else if (isPhone) {
// 手机横屏MD修改判断设备类型
orientation = window.Orientation.PORTRAIT
} else {
orientation = window.Orientation.AUTO_ROTATION_RESTRICTED
}
this.setOrientation(orientation)
}
/**
* 销毁
*/
destroy(): void {
this.unbindWindowSizeListener()
this.unbindFoldDisplayModeChange()
this.unbindFoldStatusChange()
this.unbindWindowEventListener()
}
/**
* 取消监听应用窗口焦点变化
*/
unbindWindowEventListener(): void {
// todo: onDestroy的时候窗口已经销毁了,目前没有合适的元能力生命周期用于解注册监听,窗口已知的一个现象,后续解决再放开
// try {
// windowClass.off('windowEvent');
// } catch (exception) {
// logger.error('Failed to unregister callback. Cause: ' + JSON.stringify(exception));
// }
}
/**
* 绑定窗口变化监听
*/
async bindWindowSizeListener(): Promise<void> {
let w = await this.windowStage.getMainWindow()
if (!w) {
return
}
// 绑定窗口变化监听
w.on('windowSizeChange', (data: window.Size) => {
logger.info('windowSizeChange' + JSON.stringify(data))
this.windowResizeHapenned(data)
})
w.on('avoidAreaChange', (data) => {
if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {
logger.info('avoidAreaChange' + JSON.stringify(data.area))
let isPC = isWideService()
this.topHeight = data.area.topRect.height
this.bottomHeight = data.area.bottomRect.height
if (isPC && this.topHeight === 0) {
this.topHeight = PC_TOP_PADDING * this.densityDPI
}
setOrCreateAppStorage<number>(appStorageKeys.statusBarHeight, this.topHeight)
setOrCreateAppStorage<number>(appStorageKeys.navigatorBarHeight, this.bottomHeight)
}
})
// 绑定窗口模式变化监听
w.on('windowStatusChange', (windowStatusChange: window.WindowStatusType) => {
logger.info('windowStatusChange: ' + JSON.stringify(windowStatusChange))
this.windowStatusType = windowStatusChange
if (windowStatusChange === window.WindowStatusType.MINIMIZE) {
return
}
if (windowStatusChange === window.WindowStatusType.SPLIT_SCREEN) {
this.setSplitRatio()
} else {
this.setAppSplitRatio(AppSplitRatios.NO)
}
})
}
/**
* 注销窗口变化监听
*/
async unbindWindowSizeListener(): Promise<void> {
let w = await this.windowStage.getMainWindow()
if (!w) {
return
}
// 和zhengjiangliang OFF不传callback整体注销
await w.off('windowSizeChange')
await w.off('avoidAreaChange')
await w.off('windowStatusChange')
}
/**
* 绑定折叠屏折叠状态变化监听
*/
bindFoldStatusChange(): void {
try {
display.on('foldStatusChange', (foldStatus: display.FoldStatus) => {
this.displayStatus = foldStatus;
logger.info('displayMode' + foldStatus)
});
} catch (exception) {
logger.error('Failed code' + JSON.stringify(exception));
}
}
/**
* 注销绑定折叠屏折叠状态变化监听
*/
unbindFoldStatusChange(): void {
display.off('foldStatusChange')
}
/**
* 绑定折叠屏折叠模式变化监听
*/
bindFoldDisplayModeChange(): void {
try {
display.on('foldDisplayModeChange', (foldDisplayMode: display.FoldDisplayMode) => {
logger.info('displayMode' + foldDisplayMode + this.displayMode + this.orientationSetting)
this.displayMode = foldDisplayMode;
this.setOrientationSetting()
});
} catch (exception) {
logger.error('Failed code' + JSON.stringify(exception));
}
}
unbindFoldDisplayModeChange(): void {
display.off('foldDisplayModeChange')
}
/**
* 设置全局ability上下文
*/
setContext(context: context.UIAbilityContext): void {
this.context = context
}
/**
* 获取屏幕大小
*/
async getScreenSize(): Promise<boolean> {
logger.info('windows getScreenSize')
let p1 = display.getDefaultDisplay().then((disp) => {
logger.info('getDefaultDisplay:' + JSON.stringify(disp));
this.width = disp.width
this.height = disp.height
this.densityDPI = disp.densityDPI
})
let w = await this.windowStage.getMainWindow()
if (!w) {
return new Promise((resolve) => {
p1.then(() => {
resolve(true)
})
})
}
let p2 = w.getProperties().then((prop: window.WindowProperties) => {
logger.info('getProperties:' + JSON.stringify(prop));
this.windowWidth = prop.windowRect.width
this.windowHeight = prop.windowRect.height
this.windowPosX = prop.windowRect.left
this.windowPosY = prop.windowRect.top
this.isLayoutFullScreen = prop.isLayoutFullScreen || false
this.isFullScreenWindow = prop.isFullScreen
})
let isPC = isWideService()
return isPC ? new Promise((resolve) => {
Promise.all([p1, p2]).then(() => {
// PC模式下,有可能设置为手机全屏,此时模式如下判断,需要设置窗口高度减去topHeight
if (!this.isFullScreen && this.windowWidth === this.width && this.windowHeight === this.height && this.isLayoutFullScreen && !this.isFullScreenWindow) {
logger.info('pc topHeight:' + this.topHeight);
this.windowHeight -= this.topHeight
logger.info('pc windowHeight:' + this.windowHeight);
}
this.isPCMultiWindow = this.windowHeight === this.height && this.windowWidth !== this.width
this.topHeight = PC_TOP_PADDING * this.densityDPI
setOrCreateAppStorage<number>(appStorageKeys.statusBarHeight, this.topHeight)
if (!this.isLayoutFullScreen && !this.isPCMultiWindow) {
// 天枢窗口非全屏模式非分屏模式,顶部38vp,底部和左右各5vp
this.windowWidth = this.windowWidth - 10 * this.densityDPI / 160
this.windowHeight = this.windowHeight - 43 * this.densityDPI / 160
}
this.setSplitRatio()
resolve(true)
})
}) : Promise.all([p1, p2]).then(() => {
this.setSplitRatio()
return true
}).catch(() => {
return false
})
}
/**
* 设置全屏和状态栏颜色
*/
async enterImmersion(): Promise<void> {
logger.info('windows enterImmersion')
// 手机默认设置全屏,平板PC默认设置不全屏。 如果全屏模式发生切换,则置设置沉浸模式标记位为否,重新进入设置
// 除PC之外,其他默认设置全屏
let isFullScreen = !isWideService()
this.isImmersiveSet && (this.isImmersiveSet = isFullScreen === this.isFullScreen)
this.isFullScreen = isFullScreen
if (this.isImmersiveSet) {
return
}
this.isImmersiveSet = true
let w = await this.windowStage.getMainWindow()
if (!w) {
return
}
if (this.isFullScreen) {
logger.info('this.isFullScreen' + this.isFullScreen)
// await w.setFullScreen(this.isFullScreen)
await w.setWindowLayoutFullScreen(this.isFullScreen)
}
// await w.setSystemBarEnable(["status", "navigation"])
this.setSystemBarColor(this.isTopBgDark, this.isBottomBgDark, w)
logger.info('windows enterImmersion finish')
}
/**
* 设置横竖屏模式
*
* @param isTopBgDark 顶部背景是否深色
* @param isBottomBgDark 底部背景是否深色
* @param w topWindow
*/
setSystemBarColor(isTopBgDark: boolean = false, isBottomBgDark: boolean = false, w?: window.Window,): void {
this.isTopBgDark = isTopBgDark
this.setSystemBarColorWithColor(isTopBgDark ? WHITE : BLACK, isBottomBgDark, w)
}
/**
* 设置系统bar颜色
*
* @param isTopBgDark 顶部背景是否深色
* @param isBottomBgDark 底部背景是否深色
* @param w topWindow
*/
setSystemBarColorWithColor(statusBarBgColor: string, isBottomBgDark: boolean = false, w?: window.Window): void {
if (Util.isEmpty(statusBarBgColor)) {
logger.error("setSystemBarColorWithColor :: statusBarBgColor is Empty")
return
}
if (statusBarBgColor === this.statusBarBgColor && isBottomBgDark === this.isBottomBgDark) {
return
}
if (!w) {
try {
w = this.windowStage.getMainWindowSync()
} catch (error) {
logger.info(`setSystemStatusBarColor, getMainWindowSync error: ${JSON.stringify(error, [`code`, `message`])}`);
}
if (!w) {
return
}
}
try {
this.statusBarBgColor = statusBarBgColor
this.isBottomBgDark = isBottomBgDark
w.setWindowSystemBarProperties({
navigationBarColor: TRANS,
statusBarColor: TRANS,
navigationBarContentColor: isBottomBgDark ? WHITE : BLACK,
// todo - 如果是深色模式,只需此处逻辑改为 statusBarContentColor: isDark ? ColorUtil.WHITE : statusBarBgColor
statusBarContentColor: statusBarBgColor
}, null)
} catch (error) {
logger.error(`setSystemBarColor, setWindowSystemBarProperties error: ${JSON.stringify(error, [`code`, `message`])}`);
}
}
/**
* 设置横竖屏模式
*
* @param orientation 横竖屏模式
*/
async setOrientation(orientation: window.Orientation = window.Orientation.AUTO_ROTATION_RESTRICTED): Promise<void> {
let w = await this.windowStage.getMainWindow()
if (!w) {
return
}
logger.info('this.densityType' + this.densityType + 'this.isFoldScreen' + this.isFoldScreen + 'this.displayMode' + this.displayMode)
logger.info('windowResize densityType' + this.orientationSetting + 'orientation' + orientation)
if (this.orientationSetting === orientation) {
return
}
this.orientationSetting = orientation
logger.info('windowResize densityType350' + this.orientationSetting + 'orientation' + orientation)
await w.setPreferredOrientation(orientation)
}
/**
* 获取状态栏和导航栏高度
*/
async getAvoidArea(): Promise<void> {
logger.info('windows getAvoidArea')
let w = await this.windowStage.getMainWindow()
if (!w) {
return
}
let area = await w.getAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)
logger.info('windows getAvoidArea' + JSON.stringify(area))
let isPC = isWideService()
// PC模式下,如果头部没有空隙,留一定空隙
if (isPC && area.topRect.height === 0) {
this.topHeight = PC_TOP_PADDING * this.densityDPI
// fix systemUI bug 每次下拉系统通知栏,状态栏高度会为0 需要屏蔽掉
} else if (area.topRect.height !== 0) {
this.topHeight = area.topRect.height
}
this.bottomHeight = area?.bottomRect?.height || 0
logger.info('windows getAvoidArea finish')
}
/**
* 窗口变化发生
*
* @param data 窗口数据
*/
windowResizeHapenned(data: window.Size): void {
logger.info('Succeeded in enabling the listener for window size changes. Data: ' + JSON.stringify(data));
this.lastWindowSizeResult = this.windowResize()
}
/**
* 处理窗口变化修改成员变量属性值
*
* @return Promise<boolean> 窗口变化结果,成功失败
*/
windowResize(): Promise<boolean> {
return new Promise(async (resolve, reject) => {
// todo 当前设置全屏后会触发resize,然后又走到这个方法,所以设置全屏和获取屏幕属性方法的时序不需要控制。反正resize之后还有重新获取屏幕属性
let p2 = this.getAvoidArea()
let p3 = this.getScreenSize()
Promise.all([p2, p3]).then(() => {
this.windowResizeDetail()
resolve(true)
}).catch(() => {
resolve(false)
})
})
}
/**
* 处理窗口变化修改成员变量属性值具体方法
*/
windowResizeDetail: () => void = () => {
if (this.densityDPI === 0) {
logger.info('densityDPI zero')
return
}
// 说明计算公式pixels = dips * (density / 160)
let vpWidth = this.windowWidth / (this.densityDPI / 160)
let densityType: DensityTypes = DensityTypes.SM
if (vpWidth < 320) {
densityType = DensityTypes.XS
} else if (vpWidth < 600) {
densityType = DensityTypes.SM
} else if (vpWidth < 840) {
densityType = DensityTypes.MD
} else {
densityType = DensityTypes.LG
}
logger.info('windowResize densityType' + densityType + 'vpWidth' + vpWidth)
this.setDensityType(densityType)
this.setOrientationSetting()
}
/**
* 计算分屏模式下比例
*
* @param listener 窗口变化监听器
*/
setSplitRatio: () => void = (): void => {
let appSplitRatio: AppSplitRatios
if (this.windowStatusType !== window.WindowStatusType.SPLIT_SCREEN) {
// 应用宽高与窗口宽高未分屏
logger.info('windowResize appSplitRatio none')
appSplitRatio = AppSplitRatios.NO
return
} else if (this.windowWidth === this.width) {
// 应用宽度 = 窗口宽度:上下分屏,通过计算 应用高度与窗口高度比值 判断 音乐分屏比例
if (this.windowHeight <= this.height * SPLIT_RATIO_MINI) {
appSplitRatio = AppSplitRatios.PORTRAIT_S
} else if (this.windowHeight > this.height * SPLIT_RATIO_MAX) {
appSplitRatio = AppSplitRatios.PORTRAIT_L
} else {
appSplitRatio = AppSplitRatios.PORTRAIT_M
}
} else if (this.windowHeight === this.height) {
// 应用高度 = 窗口高度:左右分屏,通过计算 应用宽度与窗口宽度比值 判断 音乐分屏比例
if (this.windowWidth <= this.width * SPLIT_RATIO_MINI) {
appSplitRatio = AppSplitRatios.LANDSCAPE_S
} else if (this.windowWidth > this.width * SPLIT_RATIO_MAX) {
appSplitRatio = AppSplitRatios.LANDSCAPE_L
} else {
appSplitRatio = AppSplitRatios.LANDSCAPE_M
}
} else {
appSplitRatio = AppSplitRatios.NO
}
logger.info(`windowResize appSplitRatio ${appSplitRatio} ${this.windowWidth} ${this.width} ${this.windowHeight} ${this.height}`)
this.setAppSplitRatio(appSplitRatio)
}
/**
* 设置屏幕密度类型
*
* @param type 屏幕密度类型
*/
setDensityType: (type: DensityTypes) => void = (type: DensityTypes): void => {
this.densityType = type
let windowArea: WindowArea = { windowWidth: this.windowWidth, windowHeight: this.windowHeight }
setOrCreateAppStorage<DensityTypes>(appStorageKeys.densityType, type)
setOrCreateAppStorage<number>(appStorageKeys.screenWidth, this.width)
setOrCreateAppStorage<number>(appStorageKeys.screenHeight, this.height)
setOrCreateAppStorage<number>(appStorageKeys.windowWidth, this.windowWidth)
setOrCreateAppStorage<number>(appStorageKeys.windowHeight, this.windowHeight)
// 保证横竖屏切换两个同时更新
setOrCreateAppStorage<WindowArea>(appStorageKeys.windowArea, windowArea)
setOrCreateAppStorage<number>(appStorageKeys.statusBarHeight, this.topHeight)
setOrCreateAppStorage<number>(appStorageKeys.navigatorBarHeight, this.bottomHeight)
if (this.configChangeListener.length > 0) {
this.configChangeListener.forEach((listener) => {
try {
listener(type)
} catch (e) {
logger.info('setDensityType forEach error = ' + e);
}
})
}
}
/**
* 设置分屏比例
*
* @param ratio 分屏比例
*/
setAppSplitRatio: (ratio: AppSplitRatios) => void = (ratio: AppSplitRatios): void => {
this.appSplitRatio = ratio
setOrCreateAppStorage<AppSplitRatios>(appStorageKeys.appSplitRatio, ratio)
}
/**
* 设置鼠标形状
* @param pointerStyle 鼠标形状
*/
async setPointerStyle(pointerTypes: pointer.PointerStyle): Promise<void> {
let w = await this.windowStage.getMainWindow()
w.getProperties().then((prop: window.WindowProperties) => {
let windowId = prop.id;
if (windowId < 0) {
logger.info(`Invalid windowId`);
return;
}
try {
pointer.setPointerStyle(windowId, pointerTypes).then(() => {
logger.info(`Set pointer style success`);
});
} catch (error) {
logger.info(`Set pointer style failed, error: ${JSON.stringify(error, [`code`, `message`])}`);
}
})
}
/**
* 设置/取消屏幕常亮
* @param screenOn
*/
async setIsKeepScreenOn(screenOn: boolean): Promise<void> {
logger.info('setIsKeepScreenOn ' + screenOn + ' this.isKeepScreenOn ' + this.isKeepScreenOn)
if (screenOn === this.isKeepScreenOn) {
return
}
let w = await this.windowStage.getMainWindow()
if (!w) {
logger.info('setIsKeepScreenOn return')
return
}
try {
w.setWindowKeepScreenOn(screenOn, (err) => {
if (err.code) {
logger.error('Failed to set the screen to be always on. Cause: ' + JSON.stringify(err));
return;
}
this.isKeepScreenOn = screenOn
logger.info('Succeeded in setting the screen to be always on.' + screenOn);
});
} catch (exception) {
logger.error('Error to set the screen to be always on. Cause: ' + JSON.stringify(exception));
}
}
}
export function isWideService(): boolean {
const globalDeviceInfo: DeviceInfo = createOrGet(DeviceInfo, globalKeys.deviceInfo)
// return globalDeviceInfo.deviceType === DeviceTypes.TABLET || globalDeviceInfo.deviceType === DeviceTypes.PC
return globalDeviceInfo.deviceType === DeviceTypes.PC
}
/**
* 设备类型
*/
export enum DeviceTypes {
PHONE = 0,
TABLET = 1,
PC = 2
}
/**
* 渠道类型
*/
export enum EnvironmentType {
// 联调环境
DEV = 0,
// 镜像环境
MIRROR = 1,
// 现网环境
SECURITY = 3
}
/**
* 屏幕密度类型
*/
export enum DensityTypes {
// 手表
XS = 0,
// 手机竖屏和折叠屏不展开
SM = 1,
// 折叠屏展开和pad竖屏
MD = 2,
// pad横屏
LG = 3
}
/**
* 分屏模式下音乐与其他应用所占屏幕比例
*/
export enum AppSplitRatios {
// 不分屏
NO = 0,
// 上下分屏 音乐与其他应用屏幕占比 <= 1:2
PORTRAIT_S = 1,
// 上下分屏 音乐与其他应用屏幕占比 1:2 ~ 2:1
PORTRAIT_M = 2,
// 上下分屏 音乐与其他应用屏幕占比 >= 2:1
PORTRAIT_L = 3,
// 左右分屏 音乐与其他应用屏幕占比 <= 1:2
LANDSCAPE_S = 4,
// 左右分屏 音乐与其他应用屏幕占比 1:2 ~ 2:1
LANDSCAPE_M = 5,
// 左右分屏 音乐与其他应用屏幕占比 >= 2:1
LANDSCAPE_L = 6,
}
/**
* 设备信息
*/
export class DeviceInfo {
// 设备类型
deviceType: number = DeviceTypes.PHONE
/**
* Obtains the product model represented by a string.
*
* @syscap SystemCapability.Startup.SystemInfo
* @since 6
*/
productModel: string
/**
* Obtains the OS version represented by a string.
*
* @syscap SystemCapability.Startup.SystemInfo
* @since 6
*/
osFullName: string
/**
* Obtains the SDK API version number.
*
* @syscap SystemCapability.Startup.SystemInfo
* @since 6
*/
sdkApiVersion: number
/**
* Obtains the device udid.
*
* @syscap SystemCapability.Startup.SystemInfo
* @since 7
*/
udid: string
/**
* Obtains the device manufacturer represented by a string.
*
* @syscap SystemCapability.Startup.SystemInfo
* @since 6
*/
manufacture: string
/**
* Obtains the device brand represented by a string.
*
* @syscap SystemCapability.Startup.SystemInfo
* @since 6
*/
brand: string
/**
* User-Agent
*
*/
ua: string = ''
/**
* Obtains the major (M) version number, which increases with any updates to the overall architecture.
* <p>The M version number monotonically increases from 1 to 99.
*
* @syscap SystemCapability.Startup.SystemInfo
* @since 6
*/
majorVersion: number
// 系统语言
lang: string = MusicApp.DEFAULT_LANG
/**
* 获取CPU的核数 todo
*
* @return CPU的核数,异常情况下CPU核数为-1
*/
cpucores: string = '8'
memory: string = '8.0G'
constructor() {
// * which can be {@code phone} (or {@code default} for phones), {@code wearable}, {@code liteWearable},
// * {@code tablet}, {@code tv}, {@code car}, or {@code smartVision}.
switch (deviceInfo.deviceType) {
case 'phone':
this.deviceType = DeviceTypes.PHONE
break;
case 'default':
this.deviceType = DeviceTypes.PHONE
break;
case 'tablet':
this.deviceType = DeviceTypes.TABLET
break;
case '2in1':
this.deviceType = DeviceTypes.PC
break;
case 'pc':
this.deviceType = DeviceTypes.PC
break;
default:
this.deviceType = DeviceTypes.PHONE
break;
}
this.osFullName = deviceInfo.osFullName
this.sdkApiVersion = deviceInfo.sdkApiVersion
// this.udid = deviceInfo.udid 权限不满足获取不到
this.udid = ''
this.manufacture = deviceInfo.manufacture
this.majorVersion = deviceInfo.majorVersion
this.brand = (deviceInfo.brand).toUpperCase()
this.productModel = deviceInfo.productModel
display.getDefaultDisplay().then((disp) => {
logger.info('getDefaultDisplay in device:' + JSON.stringify(disp));
this.ua = `model=${this.productModel},brand=${this.brand},rom=${this.osFullName},emui=${this.osFullName},os=${this.majorVersion},apilevel=${this.sdkApiVersion},manufacturer=${this.brand},useBrandCust=0,extChannel=,cpucore=8,memory=8.0G,srceenHeight=${disp.width},screenWidth=${disp.height},harmonyApiLevel=${this.sdkApiVersion},huaweiOsBrand=harmony`
logger.debug('deviceType ua:' + this.ua)
})
// todo 需要确认udid是否可以在同意协议之前获取。
logger.debug('deviceType: ' + deviceInfo.deviceType + ';osFullName: ' + deviceInfo.osFullName + ';sdkApiVersion: ' + deviceInfo.sdkApiVersion + ';udid: ' + (deviceInfo.udid !== undefined) + ';manufacture: ' + deviceInfo.manufacture + ';brand: ' + deviceInfo.brand + ';hardwareProfile:' + deviceInfo.hardwareProfile)
this.getLang()
}
/**
* 获取系统语言
*
* @return 语言是否发生切换
*/
getLang(): boolean {
let lang = i18n.getSystemLocale()
let langRes = ''
// en-Latn-US 去除Latn
if (lang) {
let langArray = lang.split('-')
if (langArray.length === 3) {
langRes = langArray[0] + '-' + langArray[2]
} else {
langRes = lang
}
} else {
langRes = MusicApp.DEFAULT_LANG
}
let res = langRes !== this.lang
this.lang = langRes
// 语言发生变化时更新全局变量
if (res) {
setOrCreateAppStorage<string>(appStorageKeys.lang, this.lang)
}
logger.info("i18n getSystemLanguage: " + this.lang)
return res
}
}
/**
* BEARER_CELLULAR 0 蜂窝网络。
* BEARER_WIFI 1 Wi-Fi网络。
* BEARER_ETHERNET 3 以太网网络。
*/
enum NetworkType {
// 蜂窝网络
BEARER_CELLULAR = 0,
// Wi-Fi网络
BEARER_WIFI = 1,
// 以太网网络。
BEARER_ETHERNET = 3
}
/**
* 应用信息
*/
export class MusicApp {
// 客户端traceId
xClientTraceId: string
static readonly DEFAULT_LANG: string = 'zh_CN'
// abilityStage上下文
abilityStageContext: context.AbilityStageContext | undefined = undefined
// ability上下文
abilityContext: context.UIAbilityContext | null = null
// UIExtensionContext上下文
UIExtensionContext: context.UIExtensionContext | null = null
// 产品类型名称 phone、watch
productName: string
// 應用是否在前台
active: boolean = true
activeNo: number = 0
activeChangeListeners: Array<(active: boolean) => void> = []
constructor(productName: string = 'phone') {
this.productName = productName
this.xClientTraceId = Util.systemUUid()
logger.info("Music app init productName:" + this.productName)
}
/**
* 注册應用是否在前台
* @param listener listener
*/
addActiveChangeListener(listener: (active: boolean) => void): void {
this.activeChangeListeners.push(listener)
}
/**
* 取消注册應用是否在前台
* @param listener listener
*/
removeActiveChangeListener(listener: (active: boolean) => void): void {
let idx = this.activeChangeListeners.findIndex((item) => {
return item === listener
})
if (idx > -1) {
this.activeChangeListeners.splice(idx, 1)
}
}
/**
* 修改應用是否在前台
* @param active active
*/
setActive(active: boolean): void {
this.active = active
if (this.activeChangeListeners.length) {
this.activeChangeListeners.forEach((listener: (active: boolean) => void) => {
listener(active)
})
}
}
/**
* 修改重复退出回到应用次数
* @param active active
*/
setActiveNo(): void {
this.activeNo++
logger.info("activeNo " + this.activeNo)
}
}
/**
* 宽高同时更新
*/
export class WindowArea {
windowWidth: number = 0
windowHeight: number = 0
}
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2021-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import data_preferences from '@ohos.data.preferences';
import { Logger } from '../util/HiLogger'
const logger: Logger = new Logger("PreferenceManager")
// store:通用,setting:应用全局配置,music_key_sp:工作秘钥缓存,music_info_sp:用户信息缓存
const preferKeys = ['store', 'setting', 'server_config', 'music_key_sp', 'user_info_sp']
/**
* [系统全局SP管理类]
*
* @version [V1.0.0.0, 2022/7/20]
* @since V1.0.0.0
*/
export class PreferenceManager {
preferenceMap: Map<string, Promise<data_preferences.Preferences>> = new Map()
preferKeys: any = {
store: 'store',
setting: 'setting',
serverConfig: 'server_config',
encrypt: 'music_key_sp',
userInfo: 'user_info_sp'
}
context: any
constructor(context: any) {
this.context = context
this.init()
}
init(): void {
logger.info('init')
preferKeys.forEach((item: string) => {
let promise: Promise<data_preferences.Preferences> = data_preferences.getPreferences(this.context, item)
this.preferenceMap.set(item, promise)
})
}
/**
* 获取storeName中的键值对
*
* @param keyName keyName
* @param def def
* @param storeName storeName
* @return Promise<any>
*/
get(keyName: string, def: number | string | boolean, storeName?: string): Promise<number | string | boolean> {
if (!storeName) {
storeName = preferKeys[0]
}
let promise: Promise<data_preferences.Preferences> = this.preferenceMap.get(storeName)
if (!promise) {
logger.error(`Can not get value of ${keyName} from ${storeName}`)
return new Promise((resolve, reject) => {
resolve(false)
})
}
return new Promise((resolve, reject) => {
promise.then((preferences: data_preferences.Preferences) => {
let keyPromise = preferences.get(keyName, def)
keyPromise.then((value: number | string | boolean) => {
logger.info(`Get value of ${keyName} from ${storeName} is ${value}`)
resolve(value)
}).catch((err) => {
logger.info(`Get value of ${keyName} from ${storeName} startup failed, err: ` + err)
resolve(false)
})
}).catch((err) => {
logger.info(`Get value of ${keyName} from ${storeName} startup failed, err: ` + err)
resolve(false)
})
})
}
/**
* 存储storeName中的键值对
*
* @param keyName keyName
* @param def def
* @param storeName storeName
* @return Promise<boolean> 成功失败
*/
put(keyName: string, def: any, autoFlush: boolean = true, storeName?: string): Promise<boolean> {
if (!storeName) {
storeName = preferKeys[0]
}
let promise: Promise<data_preferences.Preferences> = this.preferenceMap.get(storeName)
if (!promise) {
logger.error(`Can not put value of ${keyName} from ${storeName}`)
return new Promise((resolve, reject) => {
resolve(false)
})
}
return new Promise((resolve, reject) => {
promise.then((preferences: data_preferences.Preferences) => {
let keyPromise = preferences.put(keyName, def)
keyPromise.then(() => {
if (autoFlush) {
let flushPromise = preferences.flush()
flushPromise.then(() => {
resolve(true)
}).catch((err) => {
logger.info("Flush to file failed, err: " + err)
resolve(false)
})
} else {
resolve(true)
}
}).catch((err) => {
logger.info(`Get value of ${keyName} from ${storeName} startup failed, err: ` + err)
resolve(false)
})
}).catch((err) => {
logger.info(`Get value of ${keyName} from ${storeName} startup failed, err: ` + err)
resolve(false)
})
})
}
}
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2021-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* [日志工具类]<BR>
*
* @author
* @version [V1.0.0.0, 2021/12/28]
* @since V1.0.0.0
*/
// app tag
import hilog from '@ohos.hilog'
// import { LOG, HiLogNode, Logger as Hlogger } from '@hw-hmf/logger'
const APP_TAG = "HwMsc_"
const DOMAIN = 0x0001
// Hlogger.config(new HiLogNode(DOMAIN))
export class Logger {
static domain: number = DOMAIN
prefix: string;
constructor(module: string | number) {
this.prefix = APP_TAG + module;
}
debug(message: string, ...args: any[]): void {
hilog.debug(Logger.domain, this.prefix, message, args)
}
log(message: string, ...args: any[]): void {
hilog.debug(Logger.domain, this.prefix, message, args)
}
info(message: string, ...args: any[]): void {
hilog.info(Logger.domain, this.prefix, message, args)
}
warn(message: string, ...args: any[]): void {
hilog.warn(Logger.domain, this.prefix, message, args)
}
error(message: string, ...args: any[]): void {
hilog.error(Logger.domain, this.prefix, message, args)
}
}
@@ -0,0 +1,114 @@
/*
* Copyright (c) 2021-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import util from '@ohos.util'
/**
* [处理工具类]
*/
export class Util {
/**
* 判断是否为空值
* @param value 数值
* @returns
*/
static isEmpty(value: string | null | undefined): boolean {
return value === null || value === undefined || value.length === 0
}
static systemUUid(): string {
return util.generateRandomUUID(true).toUpperCase();
}
static uuid(len?: number, radix?: number): string {
let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
let uuids: Array<string> = [];
let i: number;
radix = radix || chars.length;
if (len) {
// Compact form
for (i = 0; i < len; i++) {
uuids[i] = chars[0 | Math.random() * radix];
}
} else {
// rfc4122, version 4 form
let r: number;
// rfc4122 requires these characters
uuids[8] = uuids[13] = uuids[18] = uuids[23] = '-';
uuids[14] = '4';
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (i = 0; i < 36; i++) {
if (!uuids[i]) {
r = 0 | Math.random() * 16;
uuids[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuids.join('');
}
/**
* 只能拷贝简单格式,正则之类的拷贝不了
*
* @param obj 原始对象
* @return cloned obj
*/
static deepClone(obj): any {
let copy
if (null === obj || "object" !== typeof obj) {
return obj;
}
if (obj instanceof Array) {
copy = []
for (let i = 0, len = obj.length; i < len; i++) {
copy[i] = Util.deepClone(obj[i])
}
return copy
}
if (obj instanceof Object) {
copy = {}
for (let attr in obj) {
if (Object.prototype.hasOwnProperty.call(obj, attr)) {
copy[attr] = Util.deepClone(obj[attr])
}
}
}
return copy
}
/**
* 立即执行函数,delay时间段内不再执行
*
* @param fn 函数
* @param delay 时间毫秒
* @returns
*/
static debounceImmediate(fn, delay): () => void {
let debounceTimer = null
return function (...args): void {
if (debounceTimer !== null) {
return
}
fn.apply(this, args)
debounceTimer = setTimeout(() => {
debounceTimer = null
}, delay)
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
*/
/**
* [常量]<BR>
*
* @author
* @version [V1.0.0.0, 2021/12/28]
* @since V1.0.0.0
*/
export class Constants {
/**
* Bundle name of audioPicker
*/
public static readonly AUDIO_PICKER_BUNDLE = 'com.ohos.filepicker';
/**
* AudioPicker每页请求数量
*/
public static readonly PICKER_PAGE_SIZE: number = 100
/**
* 每次请求参数
*/
public static readonly PAGE_COUNT: number = 1
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
*/
/**
* [ObservedArray可观察的数组] <BR>
*
* @Author: lWX927629
* @Date: 2024/3/7
* @Version: V1.0.71
*/
@Observed
export class ObservedArray<T> extends Array<T> {
constructor(args?: T[]) {
if (args instanceof Array) {
super(...args);
} else {
super();
}
}
}
@@ -0,0 +1,50 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
import wantConstant from '@ohos.app.ability.wantConstant';
import fileShare from '@ohos.fileshare';
import { BusinessError } from '@ohos.base';
import { Logger } from '../common/util/HiLogger'
import { LocalAudioFile } from './localaudio/LocalAudioFile'
import { LocalAudioManager } from './localaudio/LocalAudioManager'
import { Constants } from '../constant/Constants';
const logger: Logger = new Logger('LocalResourceManager')
export class LocalResourceManager {
/**
* 本地歌曲资源管理类 todo 改全局注册
*/
public localAudioManager: LocalAudioManager = new LocalAudioManager()
public getLocalAudioResource(offset: number, limit: number, context: Context): Promise<Array<LocalAudioFile>> {
return this.localAudioManager.getLocalAudio(offset, limit, context)
}
/**
* 用户选择文件后,返回结果URI列表并赋予临时权限
*/
public terminateSelfWithResult(localAudioFile: Array<LocalAudioFile>): Promise<Array<string>> {
//赋予权限就放在audiopicker模块,直接调系统接口
return new Promise(async (resolve) => {
let uriList: string[] = []
for(let item of localAudioFile) {
try {
await fileShare.grantUriPermission(item.uri, Constants.AUDIO_PICKER_BUNDLE, wantConstant
.Flags.FLAG_AUTH_READ_URI_PERMISSION | wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION)
uriList.push(item.uri)
logger.info('push uriList length: ' + uriList.length)
} catch (err) {
let error: BusinessError = err as BusinessError;
logger.error('grantUriPermission failed with error:' + JSON.stringify(error));
resolve([])
return
}
}
logger.info('Permission uriList length: ' + uriList.length)
resolve(uriList)
})
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
import image from '@ohos.multimedia.image';
export class LocalAudioFile {
// 文件uri
public uri: string = ''
// 名称
public name: string = ''
// 歌手
public artist: string = ''
// 专辑
public album: string = ''
// 显示名字
public displayName: string = ''
// 缩略图
public getThumbnail?: image.PixelMap | Resource
constructor(uri: string, name: string, artist: string, album: string,
displayName: string, getThumbnail?: image.PixelMap | Resource) {
this.uri = uri
this.name = name
this.artist = artist
this.album = album
this.displayName = displayName
this.getThumbnail = getThumbnail
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
import dataSharePredicates from '@ohos.data.dataSharePredicates';
import userFileManager from '@ohos.filemanagement.userFileManager';
import image from '@ohos.multimedia.image';
import { Logger } from '../../common/util/HiLogger'
import { LocalAudioFile } from './LocalAudioFile'
const logger: Logger = new Logger('LocalAudioManager')
/**
* 对接媒体库/文件系统,提供本地音频查询能力,本地音频分页查询能力
*/
export class LocalAudioManager {
private fileAssets: Array<userFileManager.FileAsset> = []
/**
* 获取FetchResult<FileAsset>
*/
getLocalAudio(offset: number, limit: number, context: Context): Promise<Array<LocalAudioFile>> {
logger.info('getAudioAssets');
let mgr = userFileManager.getUserFileMgr(context);
let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
let fetchOptions: userFileManager.FetchOptions = {
fetchColumns: [
userFileManager.AudioKey.TITLE.toString(),
userFileManager.AudioKey.ARTIST.toString(),
userFileManager.AudioKey.AUDIOALBUM.toString()
],
predicates: predicates.limit(limit, offset)
};
return new Promise(async (resolve) => {
try {
let fetchResult: userFileManager.FetchResult<userFileManager.FileAsset> = await mgr
.getAudioAssets(fetchOptions);
if (fetchResult) {
logger.info('fetchFileResult success' + JSON.stringify(fetchResult));
this.fileAssets = await fetchResult.getAllObject();
if (this.fileAssets && this.fileAssets.length > 0) {
let localAudioFile = await this.transferFileAssetsToLocalAudioFiles()
logger.info('localAudioFile length: ' + (await localAudioFile).length)
resolve(localAudioFile)
} else {
logger.error('fileAssets is null')
resolve([])
}
}
} catch (err) {
logger.error('getAudioAssets failed, message = ' + err);
resolve([])
}
})
}
/**
* 将FileAsset转换为LocalAudioFile
*/
transferFileAssetsToLocalAudioFiles(): Promise<Array<LocalAudioFile>> {
let localAudioFile: LocalAudioFile[] = []
return new Promise(async (resolve) => {
if (this.fileAssets) {
logger.info('fileAsset.length :' + this.fileAssets.length);
for (let fileAsset of this.fileAssets) {
let uri = fileAsset.uri || ''
let name = fileAsset.get(userFileManager.AudioKey.TITLE.toString()).toString() || fileAsset.displayName
let artist = fileAsset.get(userFileManager.AudioKey.ARTIST.toString()).toString() || ''
let album = fileAsset.get(userFileManager.AudioKey.AUDIOALBUM.toString()).toString() || ''
let displayName = fileAsset.displayName
let getThumbnail: image.PixelMap | Resource
try {
getThumbnail = await fileAsset.getThumbnail()
} catch (err) {
getThumbnail = $r('app.media.default')
logger.error('transferGetThumbnail failed: ' + err)
}
if (uri) {
let localAudioFileItem: LocalAudioFile = new LocalAudioFile(uri, name, artist, album,
displayName, getThumbnail)
localAudioFile.push(localAudioFileItem)
logger.info(`localAudioFileItem: uri: ${uri}, name: ${name}, artist: ${artist}, album: ${album}, getThumbnail: ${JSON.stringify(getThumbnail)}}`)
} else {
logger.error('transferFileAssetsToLocalAudioFiles failed!')
}
}
}
resolve(localAudioFile)
})
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
*/
import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession';
import common from '@ohos.app.ability.common';
import Want from '@ohos.app.ability.Want';
import { Logger } from '../../common/util/HiLogger'
import { MusicApp } from '../../common/global/globalmodel/GlobalModel';
import { createOrGet, globalKeys } from '../../common/global/GlobalThisHelper'
import { AudioPickerView } from './AudioPickerView';
let storage = LocalStorage.getShared()
const app: MusicApp = createOrGet(MusicApp, globalKeys.app);
const logger: Logger = new Logger('AudioPickerExtension')
/**
* [扩展Ability入口页面文件extension]
*
* @version [V1.0.0.0, 2024/4/9]
* @since V1.0.0.0
*/
@Entry(storage)
@Component
struct AudioPickerExtension {
@State isShow: boolean = false
private session: UIExtensionContentSession | undefined = storage.get<UIExtensionContentSession>('session');
private want: Want | undefined = storage.get<Want>('want');
private context = getContext(this) as common.UIAbilityContext;
aboutToAppear(): void {
logger.info('aboutToAppear')
app.abilityContext = this.context
this.isShow = true
}
aboutToDisappear(): void {
storage.clear()
}
/**
* 半模态框标题
*/
@Builder
titleStyle() {
Text($r('app.string.select_audio'))
.height(56)
.fontSize(20)
.fontColor($r('sys.color.ohos_id_color_text_primary'))
.fontWeight(FontWeight.Bold)
}
@Builder
//装饰器
myBuilder() {
Column() {
AudioPickerView({
isShow: $isShow,
session: this.session,
want: this.want
});
}
}
build() {
Row() {
}
.width('100%')
.height('100%')
.bindSheet(
this.isShow, //是否显示半模态页面
this.myBuilder(), //builder:配置半模态页面内容
{
title: this.titleStyle,
height: SheetSize.FIT_CONTENT,
dragBar: false, //是否显示控制条,默认显示
preferType: SheetType.CENTER,
onAppear: () => {
logger.info('BindSheet onAppear.') //半模态页面显示回调函数
},
onDisappear: () => {
if (this.session !== undefined) {
this.session.terminateSelf()
}
logger.info('BindSheet onDisappear.') //半模态页面回退回调函数
}
})
}
}
@@ -0,0 +1,347 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession'
import Want from '@ohos.app.ability.Want';
import ability from '@ohos.ability.ability';
import { Logger } from '../../common/util/HiLogger'
import { createOrGet, globalKeys } from '../../common/global/GlobalThisHelper'
import { DeviceInfo, DeviceTypes } from '../../common/global/globalmodel/GlobalModel'
import { LocalAudioFile } from '../../localresource/localaudio/LocalAudioFile'
import { LocalResourceManager } from '../../localresource/LocalResourceManager';
import { AudioPickerViewData } from '../model/AudioPickerViewData';
import { AudioPickerViewModel } from '../viewmodel/AudioPickerViewModel';
import { AudioPickerPreference } from '../../audiopreference/AudioPickerPreference';
import { Constants } from '../../constant/Constants';
import { SafetyTipDialog } from '../dialog/SafetyTipDialog';
const logger: Logger = new Logger('AudioPickerView')
@Component
export struct AudioPickerView {
@StorageLink('navigatorBarHeight') navigatorBarHeight: number = 200
@Link isShow: boolean;
@State audioPickerData: AudioPickerViewData = new AudioPickerViewData()
@State audioPickerViewModel: AudioPickerViewModel = new AudioPickerViewModel()
// 选中列表信息
@State isSelectedAudioPickerList: Array<LocalAudioFile> = []
@State isSafetyTip: boolean = false
@State requestCounts: number = 0
@State isSelect: boolean = true
@State indexArr: Array<number> = []
// load more 提前的条目数量
private loadMoreAdvance: number = 3
private pageNo: number = 0;
// 设备信息
private globalDeviceInfo: DeviceInfo = createOrGet(DeviceInfo, globalKeys.deviceInfo)
private audioPickerPreference: AudioPickerPreference =
createOrGet(AudioPickerPreference, globalKeys.audioPickerPreference)
private localResourceManager: LocalResourceManager = new LocalResourceManager()
session: UIExtensionContentSession | undefined
want: Want | undefined
scroller: Scroller = new Scroller()
context: Context = getContext(this)
// 弹窗知道了按钮回调
dialogKnow: () => void = () => {
// 保存安全提示状态
this.audioPickerPreference.saveSafetyTipStatus(true)
logger.info('isSafetyTip ' + this.isSafetyTip)
this.dialogController.close()
}
dialogController: CustomDialogController = new CustomDialogController({
builder: SafetyTipDialog({
know: this.dialogKnow
}),
alignment: DialogAlignment.Center
})
async aboutToAppear() {
this.audioPickerData = this.audioPickerViewModel.getDataSource()
logger.info('this.audioPickerData.getDataList(): ' + this.audioPickerData.getDataList())
this.isSafetyTip = await this.audioPickerPreference.getSafetyTipStatus()
this.audioPickerViewModel.queryAudioPickerList(this.pageNo, this.context)
if (!this.isSafetyTip) {
this.dialogController.open()
}
}
routerBack: () => void = () => {
if (this.session !== undefined) {
this.session?.sendData({ 'isShowUIExtension': false })
this.session?.terminateSelf()
}
}
onBackPress() {
this.routerBack()
}
/**
* 是否是列表最后一个音频
*/
isLast(index: number): boolean {
this.requestCounts = Constants.PICKER_PAGE_SIZE * (this.pageNo + 1)
let musicTotal: number = 0
if (this.audioPickerData.totalCount() < this.requestCounts) {
musicTotal = this.audioPickerViewModel.getAudioTotal()
}
if (musicTotal) {
return index >= musicTotal - 1
} else {
return false
}
}
/**
* 返回赋予临时权限的uri列表回调
*/
async settingTerminateSelfWithResult() {
if (this.session) {
let uriArr = await this.localResourceManager.terminateSelfWithResult(this.isSelectedAudioPickerList)
logger.info('uriArr length: ' + uriArr.length)
let abilityResult: ability.AbilityResult = {
resultCode: (uriArr === undefined) ? -1 : 0,
want: {
parameters: {
'ability.params.stream': uriArr,
'uriArr': uriArr
}
}
}
this.session.terminateSelfWithResult(abilityResult, (err) => {
logger.error('terminateSelfWithResult is called: ' + err)
})
} else {
logger.error(`oncancel session: ${this.session}`)
}
this.routerBack()
}
build() {
if (this.audioPickerViewModel.audioTotal > 0) {
Stack({ alignContent: Alignment.TopStart }) {
Scroll(this.scroller) {
Grid() {
LazyForEach(this.audioPickerData, (item: LocalAudioFile, index: number) => {
GridItem() {
Column() {
Row() {
Image(item.getThumbnail)
.width(48)
.height(48)
.margin({ right: 16 })
.borderRadius(8)
.draggable(false)
.id('audiopicker_thumbnail')
Column() {
Text(item.name)
.fontSize(16)
.fontColor($r('sys.color.ohos_id_color_text_primary'))
.fontWeight(FontWeight.Medium)
.lineHeight(21)
.width('100%')
.height(21)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ bottom: 2 })
.id('audiopicker_name')
Text() {
if (item.artist && item.album) {
Span(item.artist)
Span('-')
Span(item.album)
} else if (item.artist || item.album) {
Span(item.artist || item.album)
} else {
Span('')
}
}
.fontSize(12)
.fontWeight(FontWeight.Regular)
.fontColor($r('sys.color.ohos_id_color_text_tertiary'))
.lineHeight(16)
.maxLines(1)
.width('100%')
.height(16)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.textAlign(TextAlign.Start)
.id('audiopicker_artist_album')
}
.width('calc(100% - 128vp)')
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Start)
.margin({ right: 40 })
Column() {
Checkbox()
.selectedColor($r('sys.color.ohos_id_color_component_activated'))
.shape(CheckBoxShape.CIRCLE)
.onChange((value) => {
if (value) {
this.isSelectedAudioPickerList.push(item)
this.indexArr.push(index)
} else {
this.isSelectedAudioPickerList = this.isSelectedAudioPickerList.filter(val => val != item)
let selectIndex = this.indexArr.indexOf(index)
if(selectIndex != -1){
this.indexArr.splice(selectIndex, 1)
}
}
let keyPickNum = this.want?.parameters?.key_pick_num
logger.info('key_pick_num: ' + JSON.stringify(keyPickNum))
if (keyPickNum) {
if (this.isSelectedAudioPickerList.length < keyPickNum) {
this.isSelect = true
} else {
this.isSelect = false
}
}
logger.info('indexArr: ' + JSON.stringify(this.indexArr))
logger.info('isSelect: ' + this.isSelect)
})
.enabled(this.indexArr.indexOf(index) != -1 ? true : this.isSelect)
.unselectedColor($r('sys.color.ohos_id_color_switch_outline_off'))
.width(20)
.height(20)
}
.width(24)
.height(24)
}
.width('100%')
.height(72)
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.SpaceBetween)
if (!this.isLast(index)) {
Divider()
.strokeWidth('1px')
.margin({ left: 64 })
.backgroundColor($r('sys.color.ohos_id_color_list_separator'))
}
}
.padding({ left: 16, right: 16 })
}
}, (item: LocalAudioFile) => {
return item.uri
})
}
.onScrollIndex((start, end) => {
// 判断是否还有更多数据
if (this.audioPickerData.totalCount() < this.requestCounts) {
logger.warn(`audioPickerData less than ,size: ${this.audioPickerData.totalCount()}`)
this.audioPickerData.hasMoreData = false
return
} else {
this.audioPickerData.hasMoreData = true
}
// 判断首页数据是否满足一页
if (this.audioPickerData) {
if (this.audioPickerData.totalCount() < Constants.PICKER_PAGE_SIZE &&
this.pageNo === Constants.PAGE_COUNT) {
logger.warn('audioPickerData less than 100,size: ' + this.audioPickerData.totalCount())
return
}
}
// 判断是否是在加载中
if (this.audioPickerData.isLoadMore || !this.audioPickerData.hasMoreData) {
logger.warn('audioPickerData is showing more view : ' + this.audioPickerData.isLoadMore +
' no more data: ' + this.audioPickerData.hasMoreData)
return
}
// 查询下一页数据
let lastIndex: number = this.audioPickerData.totalCount() - 1
if (end >= lastIndex - this.loadMoreAdvance) {
this.audioPickerData.hasMoreData = true
this.audioPickerViewModel.loadMore(this.pageNo, this.context).then((res) => {
if (res) {
// 加载成功后,页面+1
this.pageNo++
logger.info(`get success pageNo:${this.pageNo}`)
}
})
}
})
}
.scrollBar(BarState.Off)
// 手机端列表高度要 - 标题 - 已选完成栏 - 提示语栏目 - 底部避让,其他设备不涉及底部避让
.margin({ bottom: this.globalDeviceInfo.deviceType === DeviceTypes.PHONE ? 108 : 96 })
Column() {
Row() {
Text() {
Span($r('app.string.is_elected'))
Span(`${this.isSelectedAudioPickerList.length}`)
}
.fontSize(16)
.fontColor($r('sys.color.ohos_id_color_text_primary'))
.fontWeight(FontWeight.Medium)
.height(22)
.id('audiopicker_selected')
Button($r('app.string.complete'), { type: ButtonType.Capsule, stateEffect: false })
.fontColor($r('sys.color.ohos_id_color_text_primary_contrary'))
.backgroundColor($r('sys.color.ohos_id_color_component_activated'))
.opacity(this.isSelectedAudioPickerList.length ? 1 : 0.4)
.width(72)
.height(28)
.onClick(() => {
if (this.isSelectedAudioPickerList.length > 0) {
this.settingTerminateSelfWithResult()
this.isShow = false
} else {
this.isShow = true
}
})
.id('audiopicker_button')
}
.width('100%')
.height(52)
.padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.SpaceBetween)
Row() {
Image($r('app.media.ic_public_privacy'))
.width(14)
.height(14)
Text($r('app.string.only_selected_items_can_be_accessed'))
.fontSize(12)
.fontColor($r('sys.color.ohos_id_color_tertiary'))
.width(96)
.height(16)
}
.width(114)
.height(28)
.justifyContent(FlexAlign.SpaceBetween)
}
.position({ x: 0, y: 576 })
.height(this.globalDeviceInfo.deviceType === DeviceTypes.PHONE ? 108 : 96)
.padding(this.globalDeviceInfo.deviceType === DeviceTypes.PHONE ?
{ bottom: `${this.navigatorBarHeight}px` } : { bottom: 0 })
}
.width(this.globalDeviceInfo.deviceType === DeviceTypes.PHONE ? '100%' : 480)
.height(this.globalDeviceInfo.deviceType === DeviceTypes.PHONE ? 684 : 560)
} else {
Column() {
Image($r('app.media.emptypage'))
.width(96)
.height(96)
.margin({ bottom: 8 })
Text($r('app.string.no_matching_content'))
.fontSize(14)
.fontColor($r('sys.color.ohos_id_color_text_tertiary'))
.fontFamily('HarmonyHeiTi')
.fontWeight(FontWeight.Regular)
.lineHeight(19)
}
.width(this.globalDeviceInfo.deviceType === DeviceTypes.PHONE ? '100%' : 480)
.height(this.globalDeviceInfo.deviceType === DeviceTypes.PHONE ? 411 : 560)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
}
@@ -0,0 +1,93 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
import { DeviceInfo, DeviceTypes } from '../../common/global/globalmodel/GlobalModel';
import { createOrGet, globalKeys } from '../../common/global/GlobalThisHelper';
import { Logger } from '../../common/util/HiLogger'
const logger: Logger = new Logger('SafetyTipDialog')
/*
* 安全提示弹窗
* */
@CustomDialog
export struct SafetyTipDialog {
// 设备信息
private globalDeviceInfo: DeviceInfo = createOrGet(DeviceInfo, globalKeys.deviceInfo)
controller?: CustomDialogController
know?: () => void
build() {
Column() {
Stack() {
Image($r('app.media.ic_public_privacy_big'))
.height(28)
.fillColor($r('sys.color.ohos_id_color_component_activated'))
Image($r('app.media.img'))
.width(24)
.height(24)
.zIndex(99)
.borderRadius(5)
.position({
x: 24,
y: 24
})
}
.width(48)
.height(48)
.borderRadius(8)
.margin({ top: 24, bottom: 16 })
Text($r('app.string.secure_access_to_audio_library'))
.width(this.globalDeviceInfo.deviceType === DeviceTypes.PHONE ? 280 : 352)
.height(26)
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontFamily('HarmonyHeiTi')
.lineHeight(27)
.textAlign(TextAlign.Center)
Text($r('app.string.your_audio_will_be_displayed_here'))
.width(this.globalDeviceInfo.deviceType === DeviceTypes.PHONE ? 280 : 352)
.height(38)
.fontSize(14)
.fontWeight(FontWeight.Regular)
.fontFamily('HarmonyHeiTi')
.lineHeight(19)
.margin({
left: 24,
right: 24,
top: 8,
bottom: 16
})
.textAlign(TextAlign.Center)
Text($r('app.string.got_it'))
.width(this.globalDeviceInfo.deviceType === DeviceTypes.PHONE ? 280 : 352)
.height(40)
.margin({ left: 16, right: 16 })
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontFamily('HarmonyHeiTi')
.lineHeight(21)
.fontColor($r('sys.color.ohos_id_text_color_active'))
.textAlign(TextAlign.Center)
.onClick(() => {
if (this.know) {
this.know()
}
})
}
.width('100%')
.height(232)
.borderRadius(32)
.margin({ left: 16, right: 16 })
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
import { LocalAudioFile } from '../../localresource/localaudio/LocalAudioFile'
import { AbsBaseViewData } from '../../basemvvm/AbsBaseViewData'
import { BaseState, ViewState } from '../../basemvvm/ViewState'
/**
* [audioPicker页面 viewData]
*/
@Observed
export class AudioPickerViewData extends AbsBaseViewData<ViewState, LocalAudioFile> {
// 页面加载状态
public viewState: ViewState = new ViewState(BaseState.DEFAULT)
// 页面错误码
public errCode: number = -2
// 是否加载更多
public hasMoreData: boolean = false
// 是否正在加载更多
public isLoadMore: boolean = false
constructor() {
super()
}
/**
* 初始化页面状态为DEFAULT
* @returns
*/
protected createViewState(): ViewState {
return new ViewState(BaseState.DEFAULT)
}
getViewState() {
return this.viewState
}
setViewState(state: BaseState) {
if (this.viewState.baseState === state) {
return
}
this.viewState.setViewState(state)
}
}
@@ -0,0 +1,105 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
import { Logger } from '../../common/util/HiLogger'
import { ObservedArray } from '../../data/ObservedArray'
import { AbsBaseViewModel } from '../../basemvvm/AbsBaseViewModel'
import { BaseState, ViewState } from '../../basemvvm/ViewState'
import { AudioPickerViewData } from '../model/AudioPickerViewData'
import { LocalResourceManager } from '../../localresource/LocalResourceManager'
import { Constants } from '../../constant/Constants'
import { LocalAudioFile } from '../../localresource/localaudio/LocalAudioFile'
const logger: Logger = new Logger('AudioPickerViewModel')
/**
* [audioPicker页面 viewmodel]
*/
export class AudioPickerViewModel extends AbsBaseViewModel<LocalAudioFile> {
// 页面待观察的数据
private audioPickerViewData: AudioPickerViewData = new AudioPickerViewData()
private localResourceManager: LocalResourceManager = new LocalResourceManager();
public audioTotal: number = 0
constructor() {
super()
}
/**
* 获取当前页面的可见性
* @returns
*/
public getState(): ViewState {
return this.audioPickerViewData.getState()
}
/**
* 获取当前页面的音频列表
* @returns
*/
protected getData(): ObservedArray<LocalAudioFile> {
return this.audioPickerViewData.getDataList()
}
/**
* 获取列表中音频总数
* @returns
*/
public getAudioTotal(): number {
return this.audioPickerViewData.totalCount()
}
/**
* 查询音频详情基类,子类需要重载
* @returns
*/
public queryAudioPickerList(pageNo: number, context: Context): Promise<boolean> {
return new Promise<boolean>((resolve) => {
try {
this.localResourceManager.getLocalAudioResource(pageNo * Constants.PICKER_PAGE_SIZE,
Constants.PICKER_PAGE_SIZE, context).then((value) => {
this.audioPickerViewData.append(value)
this.audioTotal = value.length
logger.info(`queryAudioPickerList: ${JSON.stringify(value.length)}`)
resolve(true)
})
} catch (err) {
logger.error(`localAudioFile error: ${JSON.stringify(err)}`)
this.getState().setViewState(BaseState.ERROR)
resolve(false)
}
})
}
/**
* 加载更多
*/
public loadMore(pageNo: number, context: Context): Promise<boolean> {
return new Promise((resolve) => {
logger.info('loadMoreAudioPicker')
if (!this.audioPickerViewData.hasMoreData) {
resolve(false)
return
}
let pageNum = pageNo + 1
this.queryAudioPickerList(pageNum, context).then((isSuccess: boolean) => {
logger.info(`loadMoreMusic: ${isSuccess}`)
if (isSuccess) {
resolve(true)
} else {
resolve(false)
}
})
})
}
/**
* 获取lazyforeach的数据源
* @returns
*/
public getDataSource(): AudioPickerViewData {
return this.audioPickerViewData
}
}
+59
View File
@@ -0,0 +1,59 @@
{
"module": {
"name": "audiopicker",
"type": "feature",
"description": "$string:module_desc",
"mainElement": "AudiopickerAbility",
"deviceTypes": [
"default",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.GET_NETWORK_INFO"
},
{
"name": "ohos.permission.GET_WIFI_INFO"
},
{
"name": "ohos.permission.ACCESS_NOTIFICATION_POLICY"
},
{
"name": "ohos.permission.WRITE_AUDIO",
"reason": "$string:media_permission",
"usedScene": {
"abilities": [
"audioPickerUIExtensionAbility"
],
"when": "always"
}
},
{
"name": "ohos.permission.READ_AUDIO",
"reason": "$string:media_permission",
"usedScene": {
"abilities": [
"audioPickerUIExtensionAbility"
],
"when": "always"
}
}
],
"extensionAbilities": [
{
"name": "audiopicker",
"srcEntry": "./ets/audiopickerability/AudioPickerUIExtensionAbility.ets",
"description": "$string:audiopicker_desc",
"label": "$string:audiopicker_label",
"type": "sysPicker/audioPicker",
"exported": true
}
]
}
}
@@ -0,0 +1,84 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "AudiopickerAbility_desc",
"value": "description"
},
{
"name": "AudiopickerAbility_label",
"value": "label"
},
{
"name": "AccessibilityExtAbility_desc",
"value": "description"
},
{
"name": "AccessibilityExtAbility_label",
"value": "label"
},
{
"name": "AudioPickerExtAbility_desc",
"value": "description"
},
{
"name": "AudioPickerExtAbility_label",
"value": "label"
},
{
"name": "Audio_pickerAbility_desc",
"value": "description"
},
{
"name": "Audio_pickerAbility_label",
"value": "label"
},
{
"name": "media_permission",
"value": "访问您设备上的图片,媒体内容或文件。用于为您读写储卡,以便查看、扫描、播放本地音频。"
},
{
"name": "audiopicker_desc",
"value": "description"
},
{
"name": "audiopicker_label",
"value": "label"
},
{
"name": "got_it",
"value": "Got it."
},
{
"name": "secure_access_to_audio_library",
"value": "Secure access to audio library"
},
{
"name": "your_audio_will_be_displayed_here",
"value": "Your audio will be displayed here and only the audio you select will be accessible."
},
{
"name": "is_elected",
"value": "Is selected"
},
{
"name": "complete",
"value": "Complete"
},
{
"name": "select_audio",
"value": "Select audio"
},
{
"name": "only_selected_items_can_be_accessed",
"value": "Only selected items can be accessed"
},
{
"name": "no_matching_content",
"value": "No matching content."
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="120px" height="120px" viewBox="0 0 120 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>EmptyPage/05 SearchNoResult</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="#illustration_资源备注" transform="translate(-492, -60)">
<g id="EmptyPage/05-SearchNoResult" transform="translate(492, 60)">
<rect id="矩形" stroke="#979797" fill="#D8D8D8" opacity="0" x="0.5" y="0.5" width="119" height="119"></rect>
<g id="编组" transform="translate(5.603, 15.4449)" fill="#A1A7B3">
<path d="M66.7143344,81.0702073 C68.2649473,81.0866452 72.6681798,81.6808811 74.6068973,75.3444782 C81.0661765,76.7308358 85.146966,78.7726474 85.146966,81.0491274 C85.146966,85.2304649 71.379722,88.6201088 54.396966,88.6201088 C37.4142099,88.6201088 23.646966,85.2304649 23.646966,81.0491274 L23.647966,81.0694782 Z" id="shadow" opacity="0.15"></path>
<g id="名单">
<path d="M84.0241071,0 C89.0149241,0 92.5285714,4.30386278 92.5285714,9.09542149 C92.5285714,9.19565908 92.5269051,9.29549964 92.5235992,9.39491591 L92.5285714,18.3696429 L75.5196429,18.3696429 L75.5196429,9.39458992 C75.5213092,9.29549964 75.5196429,9.19565908 75.5196429,9.09542149 C75.5196429,4.30386278 79.0332902,0 84.0241071,0 Z" id="形状结合" opacity="0.600000024"></path>
<path d="M84.1509614,0.151662166 C80.7845064,0.151662166 78.5787877,1.9129334 77.4639056,3.45748198 C76.0543393,5.41028368 75.5660069,7.2121116 75.5428249,8.86753381 C75.5273702,9.97114862 75.5273702,11.0616699 75.5428249,12.1390977 L75.5427573,35.9986873 C67.270286,37.7865633 61.071966,45.1463034 61.071966,53.9537388 C61.071966,62.6792511 67.155514,69.9838746 75.312458,71.8574572 C74.1066102,81.8796665 68.5060753,81.0892014 66.7143344,81.0702073 L19.7303571,81.0698756 L19.73,81.0486622 L7.68041323,81.0491274 C6.68969057,81.0491274 3.5324209,78.7968545 1.73212301,75.2280618 C0.531924409,72.8488666 -0.0445554911,69.3182271 0.00268330572,64.6361432 L19.73,64.6356622 L19.7303571,10.0491483 C19.7303571,5.01467171 22.1325245,1.74728949 26.9368592,0.247001691 L27.2535911,0.151662166 L84.1509614,0.151662166 Z" id="形状结合" opacity="0.300000012"></path>
<path d="M0.00268330572,64.6361432 L57.126472,64.6361432 C56.7495391,70.341418 57.4782663,74.3892292 59.3126537,76.7795768 C61.147041,79.1699244 63.8630927,80.5931079 67.4608087,81.0491274 C28.2676935,81.0491274 8.340895,81.0491274 7.68041323,81.0491274 C6.68969057,81.0491274 3.5324209,78.7968545 1.73212301,75.2280618 C0.531924409,72.8488666 -0.0445554911,69.3182271 0.00268330572,64.6361432 Z" id="矩形" opacity="0.400000006"></path>
</g>
<g id="放大镜" transform="translate(61.072, 35.5819)">
<path d="M31.8552859,30.8429902 L35.4226083,34.4104671 C36.3682462,34.0276822 37.4921636,34.2196849 38.2589537,34.986475 L47.5001621,44.2276834 C48.5230917,45.250613 48.5230917,46.9091095 47.5001621,47.932039 L47.247423,48.1847781 C46.2244935,49.2077077 44.5659969,49.2077077 43.5430674,48.1847781 L34.301859,38.9435698 C33.5352083,38.176919 33.343136,37.053271 33.7256423,36.1077402 L30.1144064,32.4948519 C30.7300227,31.9824198 31.3116118,31.4305033 31.8552859,30.8429902 Z" id="形状结合" opacity="0.600000024"></path>
<path d="M18.3696429,0 C28.5149165,0 36.7392857,8.22436925 36.7392857,18.3696429 C36.7392857,28.5149165 28.5149165,36.7392857 18.3696429,36.7392857 C8.22436925,36.7392857 0,28.5149165 0,18.3696429 C0,8.22436925 8.22436925,0 18.3696429,0 Z M18.3696429,4.08214286 C10.4788745,4.08214286 4.08214286,10.4788745 4.08214286,18.3696429 C4.08214286,26.2604112 10.4788745,32.6571429 18.3696429,32.6571429 C26.2604112,32.6571429 32.6571429,26.2604112 32.6571429,18.3696429 C32.6571429,10.4788745 26.2604112,4.08214286 18.3696429,4.08214286 Z" id="形状结合" opacity="0.5"></path>
<circle id="椭圆形" opacity="0.200000003" cx="18.3696429" cy="18.3696429" r="14.2875"></circle>
<rect id="矩形" opacity="0.200000003" transform="translate(27.039, 19.1565) rotate(-255) translate(-27.039, -19.1565)" x="22.9568713" y="18.4761844" width="8.16428571" height="1.36071429" rx="0.680357143"></rect>
<rect id="矩形备份" opacity="0.200000003" transform="translate(24.9979, 14.394) rotate(-255) translate(-24.9979, -14.394)" x="22.9568713" y="13.7136844" width="4.08214286" height="1.36071429" rx="0.680357143"></rect>
</g>
</g>
<path d="M52.7160161,47.187668 C53.441911,46.9931651 54.188041,47.4239434 54.3825439,48.1498383 L56.2216997,55.0136613 C56.4162027,55.7395562 55.9854244,56.4856861 55.2595294,56.6801891 C54.5336345,56.8746921 53.7875046,56.4439138 53.5930016,55.7180188 L51.7538458,48.8541959 C51.5593428,48.1283009 51.9901211,47.382171 52.7160161,47.187668 Z M63.0592013,46.2642857 C63.8107031,46.2642857 64.4199156,46.8734983 64.4199156,47.625 L64.4199156,54.7309524 C64.4199156,55.4824541 63.8107031,56.0916667 63.0592013,56.0916667 C62.3076996,56.0916667 61.6984871,55.4824541 61.6984871,54.7309524 L61.6984871,47.625 C61.6984871,46.8734983 62.3076996,46.2642857 63.0592013,46.2642857 Z M72.6464342,47.187668 C73.3723292,47.382171 73.8031075,48.1283009 73.6086045,48.8541959 L71.7694487,55.7180188 C71.5749457,56.4439138 70.8288158,56.8746921 70.1029209,56.6801891 C69.3770259,56.4856861 68.9462476,55.7395562 69.1407506,55.0136613 L70.9799064,48.1498383 C71.1744093,47.4239434 71.9205393,46.9931651 72.6464342,47.187668 Z" id="形状结合" fill="#A1A7B3" opacity="0.300000012" transform="translate(62.6812, 51.4956) rotate(-53) translate(-62.6812, -51.4956)"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Public/ic_public_privacy</title>
<defs>
<path d="M6.23652136,0.758959923 C6.71866597,0.52519284 7.28133403,0.52519284 7.76347864,0.758959923 L11.510109,2.57550797 C11.9626336,2.79491384 12.25,3.25360499 12.25,3.75651398 L12.25,6.5625 C12.25,9.63427329 10.2934548,12.3142313 7.43801693,13.2984621 C7.4298632,13.3012726 7.41560033,13.3059279 7.39517086,13.3122471 C7.13591183,13.3945685 6.85748012,13.3943042 6.59837784,13.3114908 C6.58245532,13.3053306 6.57078112,13.3015047 6.56366204,13.2990531 C3.70737046,12.3154314 1.75,9.63504162 1.75,6.5625 L1.75,3.75651398 C1.75,3.25360499 2.03736642,2.79491384 2.48989102,2.57550797 Z M7.38173932,1.54629727 C7.14066701,1.42941373 6.85933299,1.42941373 6.61826068,1.54629727 L2.87163034,3.36284531 C2.72078881,3.4359806 2.625,3.58887765 2.625,3.75651398 L2.625,6.5625 C2.625,7.76240507 2.96527241,8.89406606 3.56406653,9.85659627 C3.59797529,9.66732526 3.64666634,9.50971965 3.70948591,9.38194444 C3.94933946,8.89408153 4.57255679,7.4375 7.00104976,7.4375 C9.42954274,7.4375 10.0633746,8.89408153 10.2926136,9.38194444 C10.3528515,9.51014174 10.3999667,9.66836685 10.4339594,9.85661979 C11.0204621,8.91786684 11.3592899,7.81500653 11.3744672,6.64470304 L11.375,6.5625 L11.375,3.75651398 C11.375,3.58887765 11.2792112,3.4359806 11.1283697,3.36284531 Z M6.92708333,3.57 C7.89519248,3.57 8.68,4.35480752 8.68,5.32291667 C8.68,6.29102581 7.89519248,7.07583333 6.92708333,7.07583333 C5.95897419,7.07583333 5.17416667,6.29102581 5.17416667,5.32291667 C5.17416667,4.35480752 5.95897419,3.57 6.92708333,3.57 Z" id="path-1"></path>
</defs>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="#illustration_资源备注" transform="translate(-49, -108)">
<g id="Public/ic_public_privacy" transform="translate(49, 108)">
<rect id="矩形" fill-opacity="0.4" fill="#000000" opacity="0" x="0" y="0" width="14" height="14"></rect>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="形状结合" fill-opacity="0.4" fill="#000000" fill-rule="nonzero" xlink:href="#path-1"></use>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Public/ic_public_privacy</title>
<defs>
<path d="M12.4730427,1.51791985 C13.4373319,1.05038568 14.5626681,1.05038568 15.5269573,1.51791985 L23.020218,5.15101593 C23.9252672,5.58982767 24.5,6.50720998 24.5,7.51302797 L24.5,13.125 C24.5,19.2685466 20.5869096,24.6284626 14.8760339,26.5969242 C14.8064554,26.6209069 14.5144498,26.7120587 14.0000171,26.8703795 L13.8376283,26.8203879 C13.4263993,26.6936568 13.1896312,26.6195628 13.1273241,26.5981061 L12.8175243,26.4874521 C7.27368083,24.4356661 3.5,19.1583545 3.5,13.125 L3.5,7.51302797 C3.5,6.50720998 4.07473284,5.58982767 4.97978205,5.15101593 Z M14.7634786,3.09259453 C14.281334,2.85882745 13.718666,2.85882745 13.2365214,3.09259453 L5.74326068,6.72569062 C5.44157761,6.8719612 5.25,7.17775531 5.25,7.51302797 L5.25,13.125 C5.25,15.5232678 5.92967034,17.7852228 7.12582464,19.7094804 C7.19620345,19.3338323 7.29349578,19.0191076 7.41897182,18.7638889 C7.89867893,17.7881631 9.14511358,14.875 14.0020995,14.875 C18.8590855,14.875 20.1267492,17.7881631 20.5852272,18.7638889 C20.7061387,19.0212109 20.8006148,19.3390237 20.8686555,19.7173274 C22.0409231,17.8357374 22.7185798,15.630015 22.7489344,13.2894061 L22.75,13.125 L22.75,7.51302797 C22.75,7.17775531 22.5584224,6.8719612 22.2567393,6.72569062 Z M13.8541667,7.14 C15.790385,7.14 17.36,8.70961505 17.36,10.6458333 C17.36,12.5820516 15.790385,14.1516667 13.8541667,14.1516667 C11.9179484,14.1516667 10.3483333,12.5820516 10.3483333,10.6458333 C10.3483333,8.70961505 11.9179484,7.14 13.8541667,7.14 Z" id="path-1"></path>
</defs>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="#illustration_资源备注备份" transform="translate(-371, -51)">
<g id="Public/ic_public_privacy" transform="translate(371, 51)">
<rect id="矩形" fill="#D8D8D8" opacity="0" x="0" y="0" width="28" height="28"></rect>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="形状结合" fill="#000000" fill-rule="nonzero" xlink:href="#path-1"></use>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 49 KiB

@@ -0,0 +1,8 @@
{
"accessibilityCapabilities": [
"retrieve",
"keyEventObserver",
"gesture",
"touchGuide"
]
}
@@ -0,0 +1,5 @@
{
"src": [
"pages/card/AudioPickerExtension"
]
}
@@ -0,0 +1,84 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "AudiopickerAbility_desc",
"value": "description"
},
{
"name": "AudiopickerAbility_label",
"value": "label"
},
{
"name": "AccessibilityExtAbility_desc",
"value": "description"
},
{
"name": "AccessibilityExtAbility_label",
"value": "label"
},
{
"name": "AudioPickerExtAbility_desc",
"value": "description"
},
{
"name": "AudioPickerExtAbility_label",
"value": "label"
},
{
"name": "Audio_pickerAbility_desc",
"value": "description"
},
{
"name": "Audio_pickerAbility_label",
"value": "label"
},
{
"name": "media_permission",
"value": "Grant access to images, media, and files on your device. This is used to read and write to the memory card, so you can view, search for, and play local audio content."
},
{
"name": "audiopicker_desc",
"value": "description"
},
{
"name": "audiopicker_label",
"value": "label"
},
{
"name": "got_it",
"value": "Got it."
},
{
"name": "secure_access_to_audio_library",
"value": "Secure access to audio library"
},
{
"name": "your_audio_will_be_displayed_here",
"value": "Your audio will be displayed here and only the audio you select will be accessible."
},
{
"name": "is_elected",
"value": "Is selected"
},
{
"name": "complete",
"value": "Complete"
},
{
"name": "select_audio",
"value": "Select audio"
},
{
"name": "only_selected_items_can_be_accessed",
"value": "Only selected items can be accessed"
},
{
"name": "no_matching_content",
"value": "No matching content."
}
]
}
@@ -0,0 +1,84 @@
{
"string": [
{
"name": "module_desc",
"value": "模块描述"
},
{
"name": "AudiopickerAbility_desc",
"value": "description"
},
{
"name": "AudiopickerAbility_label",
"value": "label"
},
{
"name": "AccessibilityExtAbility_desc",
"value": "description"
},
{
"name": "AccessibilityExtAbility_label",
"value": "label"
},
{
"name": "AudioPickerExtAbility_desc",
"value": "description"
},
{
"name": "AudioPickerExtAbility_label",
"value": "label"
},
{
"name": "Audio_pickerAbility_desc",
"value": "description"
},
{
"name": "Audio_pickerAbility_label",
"value": "label"
},
{
"name": "media_permission",
"value": "访问您设备上的图片,媒体内容或文件。用于为您读写储卡,以便查看、扫描、播放本地音频。"
},
{
"name": "audiopicker_desc",
"value": "description"
},
{
"name": "audiopicker_label",
"value": "label"
},
{
"name": "got_it",
"value": "知道了"
},
{
"name": "secure_access_to_audio_library",
"value": "安全访问音频库"
},
{
"name": "your_audio_will_be_displayed_here",
"value": "您的音频会在此处显示,仅可访问您选中的音频。"
},
{
"name": "is_elected",
"value": "已选"
},
{
"name": "complete",
"value": "完成"
},
{
"name": "select_audio",
"value": "选择音频"
},
{
"name": "only_selected_items_can_be_accessed",
"value": "仅可访问所选项目"
},
{
"name": "no_matching_content",
"value": "没有符合条件的内容"
}
]
}
+13 -1
View File
@@ -4,7 +4,7 @@
{
"name": "default",
"signingConfig": "default",
"compileSdkVersion": 10,
"compileSdkVersion": 12,
"compatibleSdkVersion": 9
}
]
@@ -21,6 +21,18 @@
]
}
]
},
{
"name": "audiopicker",
"srcPath": "./audiopicker",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
}