1/* 2 * Copyright (c) 2023-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { Theme } from '@ohos.arkui.theme'; 17import { LengthMetrics, LengthUnit } from '@kit.ArkUI'; 18 19export interface ProgressButtonColorOptions { 20 progressColor?: ResourceColor; 21 borderColor?: ResourceColor; 22 textColor?: ResourceColor; 23 backgroundColor?: ResourceColor; 24} 25 26const EMPTY_STRING: string = ''; 27const MAX_PROGRESS: number = 100; 28const MAX_PERCENTAGE: string = '100%'; 29const MIN_PERCENTAGE: string = '0%'; 30const TEXT_OPACITY: number = 0.4; 31const BUTTON_NORMARL_WIDTH: number = 44; 32const BUTTON_NORMARL_HEIGHT: number = 28; 33const BUTTON_BORDER_RADIUS: number = 14; 34const TEXT_ENABLE: number = 1.0; 35 36 37// Set the key value for the basic component of skin changing corresponding to progressButton 38const PROGRESS_BUTTON_PROGRESS_KEY = 'progress_button_progress_key'; 39const PROGRESS_BUTTON_PRIMARY_FONT_KEY = 'progress_button_primary_font_key'; 40const PROGRESS_BUTTON_CONTAINER_BACKGROUND_COLOR_KEY = 'progress_button_container_background_color_key'; 41const PROGRESS_BUTTON_EMPHASIZE_SECONDARY_BUTTON_KEY = 'progress_button_emphasize_secondary_button_key'; 42@Component 43export struct ProgressButton { 44 @Prop @Watch('getProgressContext') progress: number; 45 @State textProgress: string = EMPTY_STRING; 46 @Prop content: string = EMPTY_STRING; 47 @State @Watch('getLoadingProgress')isLoading: boolean = false; 48 progressButtonWidth?: Length = BUTTON_NORMARL_WIDTH; 49 clickCallback: () => void = () => {}; 50 @Prop enable: boolean = true; 51 @Prop colorOptions: ProgressButtonColorOptions | undefined = undefined; 52 @Prop progressButtonRadius?: LengthMetrics | undefined = undefined; 53 54 @State progressColor: ResourceColor = '#330A59F7' 55 @State containerBorderColor: ResourceColor = '#330A59F7' 56 @State containerBackgroundColor: ResourceColor = $r('sys.color.ohos_id_color_foreground_contrary') 57 @State textHeight?: Length = BUTTON_NORMARL_HEIGHT; 58 @State buttonBorderRadius?: number = BUTTON_BORDER_RADIUS; 59 60 onWillApplyTheme(theme: Theme) { 61 this.progressColor = theme.colors.compEmphasizeSecondary; 62 this.containerBorderColor = theme.colors.compEmphasizeSecondary; 63 this.containerBackgroundColor = theme.colors.iconOnFourth; 64 } 65 66 private getButtonProgress(): number { 67 if (this.progress < 0) { 68 return 0 69 } else if (this.progress > MAX_PROGRESS) { 70 return MAX_PROGRESS 71 } 72 return this.progress 73 } 74 75 private getProgressContext() { 76 if (this.progress < 0) { 77 this.isLoading = false 78 this.textProgress = MIN_PERCENTAGE 79 } else if (this.progress >= MAX_PROGRESS) { 80 this.isLoading = false 81 this.textProgress = MAX_PERCENTAGE 82 } else { 83 this.isLoading = true 84 this.textProgress = Math.floor(this.progress / MAX_PROGRESS * MAX_PROGRESS).toString() + '%' 85 } 86 } 87 88 private getProgressButtonRadius(): LengthMetrics { 89 if (!this.progressButtonRadius || this.progressButtonRadius.unit === LengthUnit.PERCENT) { 90 return LengthMetrics.vp(this.buttonBorderRadius); 91 } else if (this.progressButtonRadius.value < 0) { 92 return LengthMetrics.vp(0); 93 } else { 94 return this.progressButtonRadius; 95 } 96 } 97 98 private getLoadingProgress() { 99 if (this.isLoading) { 100 if(this.progress < 0) { 101 this.textProgress = MIN_PERCENTAGE 102 } else if (this.progress >= MAX_PROGRESS) { 103 this.textProgress = MAX_PERCENTAGE 104 } else { 105 this.textProgress = Math.floor(this.progress / MAX_PROGRESS * MAX_PROGRESS).toString() + '%' 106 } 107 } 108 } 109 110 private toLengthString(value: LengthMetrics | undefined): string { 111 if (value === void (0)) { 112 return ''; 113 } 114 const length: number = value.value; 115 let lengthString: string = ''; 116 switch (value.unit) { 117 case LengthUnit.PX: 118 lengthString = `${length}px`; 119 break; 120 case LengthUnit.FP: 121 lengthString = `${length}fp`; 122 break; 123 case LengthUnit.LPX: 124 lengthString = `${length}lpx`; 125 break; 126 case LengthUnit.PERCENT: 127 lengthString = `${length * 100}%`; 128 break; 129 case LengthUnit.VP: 130 lengthString = `${length}vp`; 131 break; 132 default: 133 lengthString = `${length}vp`; 134 break; 135 } 136 return lengthString; 137 } 138 139 build() { 140 Button() { 141 Stack(){ 142 Progress({ value: this.getButtonProgress(), total: MAX_PROGRESS, 143 style: ProgressStyle.Capsule }) 144 .height(this.textHeight) 145 .constraintSize({ minHeight: BUTTON_NORMARL_HEIGHT }) 146 .borderRadius(this.buttonBorderRadius) 147 .width('100%') 148 .hoverEffect(HoverEffect.None) 149 .style({ 150 borderColor: this.colorOptions?.borderColor ? this.colorOptions?.borderColor : this.containerBorderColor, 151 borderRadius: this.getProgressButtonRadius() 152 }) 153 .clip(false) 154 .key(PROGRESS_BUTTON_PROGRESS_KEY) 155 .color(this.colorOptions?.progressColor ? this.colorOptions?.progressColor : this.progressColor) 156 Row() { 157 Text(this.isLoading ? this.textProgress: this.content) 158 .fontSize($r('sys.float.ohos_id_text_size_button3')) 159 .fontWeight(FontWeight.Medium) 160 .key(PROGRESS_BUTTON_PRIMARY_FONT_KEY) 161 .fontColor(this.colorOptions?.textColor) 162 .maxLines(1) 163 .textOverflow({ overflow: TextOverflow.Ellipsis }) 164 .padding({ top: 4, left: 8, right: 8, bottom: 4}) 165 .opacity(this.enable ? TEXT_ENABLE : TEXT_OPACITY) 166 .onAreaChange((_, newValue)=>{ 167 if (!newValue.height || newValue.height === this.textHeight) { 168 return; 169 } 170 this.textHeight = newValue.height > BUTTON_NORMARL_HEIGHT ? newValue.height : BUTTON_NORMARL_HEIGHT; 171 this.buttonBorderRadius = Number(this.textHeight) / 2; 172 }) 173 } 174 .constraintSize({ minHeight: BUTTON_NORMARL_HEIGHT }) 175 Row() 176 .key(PROGRESS_BUTTON_CONTAINER_BACKGROUND_COLOR_KEY) 177 .backgroundColor(Color.Transparent) 178 .border({ 179 width: 1, 180 color: this.colorOptions?.borderColor ? this.colorOptions?.borderColor : this.containerBorderColor 181 }) 182 .height(this.textHeight) 183 .constraintSize({ minHeight: BUTTON_NORMARL_HEIGHT }) 184 .borderRadius(this.progressButtonRadius ? this.toLengthString(this.getProgressButtonRadius()) : 185 this.buttonBorderRadius) 186 .width('100%') 187 } 188 } 189 .borderRadius(this.progressButtonRadius ? this.toLengthString(this.getProgressButtonRadius()) : 190 this.buttonBorderRadius) 191 .clip(false) 192 .hoverEffect(HoverEffect.None) 193 .key(PROGRESS_BUTTON_EMPHASIZE_SECONDARY_BUTTON_KEY) 194 .backgroundColor( 195 this.colorOptions?.backgroundColor 196 ? this.colorOptions?.backgroundColor 197 : this.containerBackgroundColor) 198 .constraintSize({minWidth: 44}) 199 .padding({ top: 0, bottom: 0}) 200 .width((!this.progressButtonWidth || this.progressButtonWidth < BUTTON_NORMARL_WIDTH) ? 201 BUTTON_NORMARL_WIDTH : this.progressButtonWidth) 202 .stateEffect(this.enable) 203 .onClick(() => { 204 if (!this.enable) { 205 return 206 } 207 if (this.progress < MAX_PROGRESS) { 208 this.isLoading = !this.isLoading 209 } 210 this.clickCallback && this.clickCallback() 211 }) 212 } 213}