/*
* Copyright (c) Huawei Device Co., Ltd. 2024-2025. All rights reserved.
* 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 { LogDomain, Logger } from '@ohos/basicutils';
import { DeviceHelper, obtainStartAbilityWithWant, TargetPanel } from '@ohos/frameworkwrapper';
import { EventManagerAdapter, NotificationSysEventReporter } from '@ohos/systemuicommon';
import { SystemUICommonUtil } from '@ohos/systemuicommon';
import {
CellularDataVm,
DeviceInfoVm,
DropdownVm,
INotificationDataPlanVm,
INotificationHeaderVm,
NotificationBaseVm,
} from '@ohos/systemuicommon/newIndex';
import { DataInfo, NtfDisplayDataInfo } from '@ohos/systemuicommon/src/main/ets/vm/CellularDataVm';
import { NotificationDataPlanAnimation } from '../animation/NotificationDataPlanAnimation';
import { ResUtils } from '@ohos/windowscene';
import { MeasureText, Size } from '@kit.ArkUI';
import { ArkUIAdapter, ViewName } from '@ohos/systemuicommon/src/main/ets/utils/ArkUIAdapter';
import { i18n } from '@kit.LocalizationKit';
const TAG = 'NotificationDataPlanVm';
const log = Logger.getLogHelper(LogDomain.NC);
@ObservedV2
export class NotificationDataPlanVm extends NotificationBaseVm implements INotificationDataPlanVm {
/**
* 流量文本
*/
@Trace public todayUseDataText: string = ResUtils.getInnerStringNumS($r('app.string.data_use_today'), '-- KB');
@Trace public totalDataText: string = ResUtils.getInnerStringNumS($r('app.string.data_total'), '-- KB');
@Trace public remainDataText: string = ResUtils.getInnerStringNumS($r('app.string.data_remain'), '-- KB');
/**
* 是否接入天水计划
*/
@Trace public isTianShuiConnected: boolean = false;
/**
* 水平对齐规格
*/
@Trace public flexAlign: FlexAlign = this.isTianShuiConnected ? FlexAlign.End : FlexAlign.Start;
/**
* 下拉跟手动效
*/
@Trace public followAnim?: AnimateParam;
/**
* 流量文本缩放
*/
@Trace public dataTextScale = 1
/**
* 流量文本透明度
*/
@Trace public dataTextOpacity = 0
/**
* 流量文本位移
*/
@Trace public dataTextTranslateY = 0
/**
* 流量文本字体大小
*/
@Trace public dataTextFontSize: Resource = this.getDataTextFontSize();
/**
* 流量文本字重
*/
@Trace public dataTextFontWeight: FontWeight = FontWeight.Normal;
/**
* 流量文本行高
*/
@Trace public dataTextLineHeight: Resource | undefined = undefined;
/**
* 数据加载完成标识
*/
@Trace private loadSuccess: boolean = false;
@Trace private todayData: number = 0;
@Trace private totalData: number = 0;
@Trace private remainData: number = 0;
/**
* 无障碍播报文本
*/
@Trace public accessibilityText: string = ResUtils.getInnerString($r('app.string.text_loading'));
/**
* 延迟加载
*/
@Trace public delayLoad: boolean = false;
/**
* 剩余流量文本宽度
*/
@Trace public remainTextWidth?: Length;
private preDataTransY: number = 0;
/**
* 前一次下拉面板距离
*/
public preDropDownMoveY: number = 0;
@Computed
get headerVm(): INotificationHeaderVm {
return this.vmInjector.getHeaderVm()!;
}
init(): void {
CellularDataVm.instance.registerDataPlanRefreshCallback(this.refreshDisplayDataInfo);
}
destroy(): void {
CellularDataVm.instance.unRegisterDataPlanRefreshCallback(this.refreshDisplayDataInfo);
}
@Trace public canDisplayAll: boolean = true;
getId(text: string): string {
return this.vmInjector.getId(TAG + '_' + text);
}
public refreshDisplayDataInfo = (dataPlan: NtfDisplayDataInfo): void => {
try {
log.showInfo(TAG, `refreshDisplayDataInfo`);
this.loadSuccess = true;
const today = dataPlan.todayUseData;
if (today) {
this.todayData = today.data;
this.todayUseDataText = ResUtils.getInnerStringNumS($r('app.string.data_use_today'), this.getI18nUnit(today));
}
const total = dataPlan.totalData;
if (total) {
this.totalData = total.data;
this.totalDataText = ResUtils.getInnerStringNumS($r('app.string.data_total'), this.getI18nUnit(total));
}
const month = dataPlan.monthlyRemainData;
if (month) {
this.remainData = month.data;
this.remainDataText =
ResUtils.getInnerStringNumS(month.data < 0 ? $r('app.string.data_exceed') : $r('app.string.data_remain'),
this.getI18nUnit(month));
}
if (DropdownVm.instance.isShow) {
this.delayLoad = true;
this.vmInjector.getListScrollerVm()?.scroller.scrollEdge(Edge.Top, { velocity: 100 });
const height = ResUtils.getNumber($r('app.float.ntf_data_plan_margin_top')) +
ResUtils.getNumber(this.getDataTextFontSize());
setTimeout(() => {
this.delayLoad = false;
}, height / 100 * 1000 + 50);
}
} catch (e) {
log.showWarn(TAG, `refreshDataPlanInfo error. ${e}`);
}
}
private getI18nUnit(dataInfo: DataInfo): string {
const local = i18n.System.getSystemLocale();
const unitMap: Map<string, string> = new Map([
['KB', 'kilobyte'],
['MB', 'megabyte'],
['GB', 'gigabyte'],
['TB', 'terabyte']
])
const ntf = new Intl.NumberFormat(local,
{ style: 'unit', unit: `${unitMap.get(dataInfo.unit) ?? 'kilobyte'}`, unitDisplay: 'short' });
return ntf.format(Math.abs(dataInfo.data)).toUpperCase();
}
@Monitor('todayUseDataText', 'totalDataText', 'remainDataText')
onDataTextChange(): void {
const todayWidth = this.getTextWidth(this.todayUseDataText);
const totalWidth = this.getTextWidth(this.totalDataText);
const symbolWidth = this.remainData < 0 ? 19 : 0;
const remainWidth = this.getTextWidth(this.remainDataText);
let spaceWidth = 2 * this.getTextWidth(' | ');
const dataViewWidth = this.vmInjector.getPanelVm().cardWidth -
2 * ResUtils.getNumber($r('app.float.ntf_item_padding_start_end'));
this.canDisplayAll = todayWidth + totalWidth + symbolWidth + remainWidth + spaceWidth <= dataViewWidth;
if (this.canDisplayAll) {
this.accessibilityText = this.todayUseDataText + ' | ' + this.totalDataText + ' | ' + this.remainDataText;
} else {
this.accessibilityText = this.todayUseDataText + ' | ' + this.remainDataText;
spaceWidth = spaceWidth / 2;
const availableWidth = dataViewWidth - todayWidth - symbolWidth - spaceWidth ;
log.showInfo(TAG, `availableWidth: ${availableWidth}`)
if (availableWidth < remainWidth) {
this.remainTextWidth = availableWidth;
}
}
log.showInfo(TAG,
`dataViewWidth: ${dataViewWidth}, todayWidth: ${todayWidth}, totalWidth: ${totalWidth}, symbolWidth: ${symbolWidth}, remainWidth: ${remainWidth}, canDisplayAll: ${this.canDisplayAll}`)
}
private getTextWidth(text: string): number {
const size = MeasureText.measureTextSize({
textContent: text,
maxLines: 1,
overflow: TextOverflow.Ellipsis,
fontWeight: this.dataTextFontWeight,
fontSize: this.dataTextFontSize,
lineHeight: this.dataTextLineHeight,
});
return ArkUIAdapter.px2vp(size.width as number);
}
public getTextHeight(): number {
const text = this.todayUseDataText
const size = MeasureText.measureTextSize({
textContent: text,
maxLines: 1,
overflow: TextOverflow.Ellipsis,
fontWeight: this.dataTextFontWeight,
fontSize: this.dataTextFontSize,
lineHeight: this.dataTextLineHeight,
});
return ArkUIAdapter.px2vp(size.height as number);
}
@Computed
get isShowDataPlan(): boolean {
return CellularDataVm.instance.isShowData;
}
dataExceed(): boolean {
log.showInfo(TAG, `loadSuccess: ${this.loadSuccess}, remainData: ${this.remainData}`)
return this.loadSuccess && this.remainData < 0;
}
reset(): void {
this.loadSuccess = false;
this.accessibilityText = ResUtils.getInnerString($r('app.string.text_loading'));
this.todayUseDataText = ResUtils.getInnerStringNumS($r('app.string.data_use_today'), '-- KB');
this.totalDataText = ResUtils.getInnerStringNumS($r('app.string.data_total'), '-- KB');
this.remainDataText = ResUtils.getInnerStringNumS($r('app.string.data_remain'), '-- KB');
this.remainTextWidth = undefined;
}
getDataTextFontSize(): Resource {
return DeviceHelper.isPad() ? $r('app.float.ntf_title_pad_data_font_size') :
$r('app.float.ntf_title_data_font_size');
}
/**
* 跳转到流量数据
*/
jumpToDataPage(): void {
EventManagerAdapter.startAbility(obtainStartAbilityWithWant({
bundleName: 'com.ohos.settings',
abilityName: 'com.ohos.settings.MainAbility',
uri: 'mobile_data_manage_entry',
moduleName: 'phone_settings'
}));
NotificationSysEventReporter.notificationPanelDataPlan({
TIME_STAMP: `${DropdownVm.instance.lastDropdownTime}`
});
}
/**
* 横屏动效跟随
*/
dataPlanDoFollowHandAnim(firstCardTransY: number): void {
// 执行缩放动效的时候,组件不跟随移动,动效设为undefined
this.followAnim = undefined;
const dataTranY =
ResUtils.getNumber($r('app.float.ntf_title_padding_bottom')) + ResUtils.getNumber(this.dataTextFontSize) / 2;
if (firstCardTransY < 0) {
this.dataTextScale = Math.max(0.4, 1 + (0.8 / dataTranY * firstCardTransY));
this.dataTextOpacity = Math.max(0, 1 + (1 / dataTranY * firstCardTransY));
} else {
this.dataTextScale = 1;
this.dataTextOpacity = 1;
}
}
/**
* 横屏动效回弹
*/
dataPlanDoFollowUpdateTransY(transY: number, dropDownMoveY: number): void {
if (this.preDataTransY === transY) {
return;
}
if ((dropDownMoveY === 0 && this.preDropDownMoveY === 0) || this.preDropDownMoveY < 0) {
this.followAnim = undefined
} else {
this.followAnim = { duration: 500, curve: Curve.Friction, }
}
this.dataTextTranslateY = transY;
this.preDataTransY = transY;
this.preDropDownMoveY = dropDownMoveY;
}
showDataText(): void {
this.dataTextOpacity = 1;
this.dataTextScale = NotificationDataPlanAnimation.dataShowScale;
this.dataTextTranslateY = 0;
}
doDataAnim(isShow: boolean): void {
// 动效管理
const dataPlanAnimation: NotificationDataPlanAnimation = new NotificationDataPlanAnimation();
if (isShow) {
dataPlanAnimation.doShowAnim(this);
} else if (DropdownVm.instance.dropdownEvent.target === TargetPanel.NOTIFICATION_PANEL) {
dataPlanAnimation.doHideAnim(this);
}
}
}