1/* 2 * Copyright (c) 2023-2024 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 InputMethodSubtype from '@ohos.InputMethodSubtype'; 17import inputMethod from '@ohos.inputMethod'; 18import settings from '@ohos.settings'; 19import common from '@ohos.app.ability.common'; 20import deviceInfo from '@ohos.deviceInfo'; 21import bundleManager from '@ohos.bundle.bundleManager'; 22import inputMethodEngine from '@ohos.inputMethodEngine'; 23 24export interface PatternOptions { 25 defaultSelected: number; 26 patterns: Array<Pattern> 27 action: (index: number) => void; 28} 29 30export interface Pattern { 31 icon: Resource; 32 selectedIcon: Resource; 33} 34 35interface SubType { 36 name: string; 37 id: string; 38 isCurrent: boolean; 39} 40 41@Extend(Divider) 42function divider() { 43 .height('1px') 44 .color('#10000000') 45 .margin({ left: 12, right: 12 }) 46} 47 48@Extend(Text) 49function textStyle() { 50 .width('100%') 51 .fontWeight(400) 52 .maxLines(1) 53} 54 55const TAG: string = 'InputMethodListDialog'; 56const BIG_IMAGE_SIZE: number = 30; 57const NORMAL_IMAGE_SIZE: number = 24; 58const BIG_DIALOG_WIDTH: number = 196; 59const NORMAL_DIALOG_WIDTH: number = 156; 60const BIG_FONT_SIZE: number = 20; 61const NORMAL_FONT_SIZE: number = 16; 62const BIG_ITEM_HEIGHT: number = 60; 63const NORMAL_ITEM_HEIGHT: number = 48; 64const NORMAL_IMAGE_BUTTON_WIDTH: number = 40; 65const NORMAL_IMAGE_BUTTON_HEIGHT: number = 32; 66const BIG_IMAGE_BUTTON_WIDTH: number = 50; 67const BIG_IMAGE_BUTTON_HEIGHT: number = 40; 68const NORMAL_COLUMN_PADDING: number = 4; 69const BIG_COLUMN_PADDING: number = 5; 70const NORMAL_IMAGE_RADIUS: number = 8; 71const BIG_IMAGE_RADIUS: number = 10; 72const NORMAL_FONT_PADDING: number = 12; 73const BIG_FONT_PADDING: number = 20; 74const NORMAL_ITEM_RADIUS: number = 16; 75const BIG_ITEM_RADIUS: number = 12; 76 77 78@CustomDialog 79export struct InputMethodListDialog { 80 private listBgColor: ResourceColor = '#ffffff'; 81 private pressedColor: ResourceColor = '#1A000000' 82 private selectedColor: ResourceColor = '#220A59F7'; 83 private fontColor: ResourceColor = '#E6000000'; 84 private selectedFontColor: ResourceColor = '#0A59F7'; 85 @State listItemHeight: number = NORMAL_ITEM_HEIGHT; 86 @State listItemRadius: number = NORMAL_IMAGE_RADIUS; 87 @State inputMethods: Array<inputMethod.InputMethodProperty> = []; 88 @State fontSize: number = NORMAL_FONT_SIZE; 89 @State fontPadding: number = NORMAL_FONT_PADDING; 90 @State dialogWidth: number = NORMAL_DIALOG_WIDTH; 91 @State imageSize: number = NORMAL_IMAGE_SIZE; 92 @State imageBtnWidth: number = NORMAL_IMAGE_BUTTON_WIDTH; 93 @State imageBtnHeight: number = NORMAL_IMAGE_BUTTON_HEIGHT; 94 @State columnPadding: number = NORMAL_COLUMN_PADDING; 95 @State imageRadius: number = NORMAL_IMAGE_RADIUS; 96 @State subTypes: Array<InputMethodSubtype> = []; 97 @State showHand: boolean = false; 98 @State inputMethodConfig: bundleManager.ElementName | undefined = undefined; 99 @State defaultInputMethod: inputMethod.InputMethodProperty | undefined = undefined; 100 @State currentInputMethod: inputMethod.InputMethodProperty | undefined = undefined; 101 @State currentSub: InputMethodSubtype | undefined = undefined; 102 @StorageLink('patternMode') patternMode: number | undefined = 0; 103 @StorageLink('maxListNum') maxListNum: number = 0; 104 private activeSubtypes: Array<SubType> = []; 105 controller: CustomDialogController = new CustomDialogController({ builder: undefined }); 106 patternOptions?: PatternOptions; 107 108 async getDefaultInputMethodSubType(): Promise<void> { 109 console.info(`${TAG} getDefaultInputMethodSubType`); 110 this.inputMethodConfig = inputMethod.getSystemInputMethodConfigAbility(); 111 if (this.inputMethodConfig) { 112 console.info(`${TAG} inputMethodConfig: ${JSON.stringify(this.inputMethodConfig)}`); 113 } 114 this.inputMethods = await inputMethod.getSetting().getInputMethods(true); 115 this.defaultInputMethod = inputMethod.getDefaultInputMethod(); 116 this.currentInputMethod = inputMethod.getCurrentInputMethod(); 117 let index = 0; 118 for (let k = 0; k < this.inputMethods.length; k++) { 119 if (this.inputMethods[k].name === this.defaultInputMethod.name) { 120 index = k; 121 break; 122 } 123 } 124 this.inputMethods.splice(index, 1); 125 this.inputMethods.unshift(this.defaultInputMethod); 126 this.currentSub = inputMethod.getCurrentInputMethodSubtype(); 127 console.info(`${TAG} defaultInput: ${JSON.stringify(this.defaultInputMethod)}`); 128 if (this.defaultInputMethod.name === this.currentInputMethod.name) { 129 if (this.patternOptions) { 130 if (AppStorage.get<number>('patternMode') === undefined) { 131 if (this.patternOptions.defaultSelected) { 132 this.patternMode = this.patternOptions.defaultSelected; 133 } else { 134 this.patternMode = 0; 135 } 136 AppStorage.setOrCreate('patternMode', this.patternMode); 137 } else { 138 this.patternMode = AppStorage.get<number>('patternMode'); 139 } 140 this.showHand = true; 141 } 142 } 143 let context = getContext(this) as common.UIAbilityContext; 144 try { 145 let activeSubTypeStr = await settings.getValue(context, settings.input.ACTIVATED_INPUT_METHOD_SUB_MODE); 146 let activeSubType: SubType[] = JSON.parse(activeSubTypeStr); 147 if (activeSubType) { 148 console.info(`${TAG} activeSubType: ${JSON.stringify(activeSubType)}`); 149 for (let i = 0; i < this.inputMethods.length; i++) { 150 if (this.inputMethods[i].name === this.defaultInputMethod.name) { 151 this.defaultInputMethod = this.inputMethods[i]; 152 let defaultSubTypes = await inputMethod.getSetting().listInputMethodSubtype(this.inputMethods[i]); 153 console.info(`${TAG} defaultSubTypes: ${JSON.stringify(defaultSubTypes)}`) 154 for (let k = 0; k < defaultSubTypes.length; k++) { 155 for (let j = 0; j < activeSubType.length; j++) { 156 if (activeSubType[j].id === defaultSubTypes[k].id) { 157 this.subTypes.push(defaultSubTypes[k]); 158 this.activeSubtypes.push(activeSubType[j]); 159 } 160 } 161 } 162 } 163 } 164 console.info(`${TAG} this.subTypes: ${JSON.stringify(this.subTypes)}`) 165 console.info(`${TAG} this.activeSubtypes: ${JSON.stringify(this.activeSubtypes)}`) 166 } 167 } catch (err) { 168 this.subTypes = []; 169 console.info(`${TAG} subTypes is empty, err = ${JSON.stringify(err)}`); 170 } 171 } 172 173 aboutToAppear(): void { 174 console.info(`${TAG} aboutToAppear`); 175 this.dialogWidth = NORMAL_DIALOG_WIDTH; 176 this.fontSize = NORMAL_FONT_SIZE; 177 this.imageSize = NORMAL_IMAGE_SIZE; 178 this.listItemHeight = NORMAL_ITEM_HEIGHT; 179 this.imageBtnWidth = NORMAL_IMAGE_BUTTON_WIDTH; 180 this.imageBtnHeight = NORMAL_IMAGE_BUTTON_HEIGHT; 181 this.columnPadding = NORMAL_COLUMN_PADDING; 182 this.fontPadding = NORMAL_FONT_PADDING; 183 this.listItemRadius = NORMAL_ITEM_RADIUS; 184 this.imageRadius = BIG_IMAGE_RADIUS; 185 this.getDefaultInputMethodSubType(); 186 let inputMethodAbility = inputMethodEngine.getInputMethodAbility(); 187 inputMethodAbility.on('keyboardHide', () => { 188 this.controller.close(); 189 }); 190 } 191 192 isDefaultInputMethodCurrentSubType(subTypeId: string): boolean { 193 return this.defaultInputMethod?.name === this.currentInputMethod?.name && this.currentSub?.id === subTypeId; 194 } 195 196 @Styles 197 listItemStyle() { 198 .padding({ left: this.fontPadding, right: this.fontPadding }) 199 .height(this.listItemHeight) 200 .borderRadius(this.listItemRadius) 201 } 202 203 @Builder 204 InputMethodItem(name: string | undefined, fontColor: ResourceColor, normalColor: ResourceColor, 205 pressedColor: ResourceColor, isDivider: boolean, handleClick: Function) { 206 Column() { 207 Row() { 208 Text(name) 209 .fontSize(this.fontSize) 210 .textStyle() 211 .listItemStyle() 212 .fontColor(fontColor) 213 .stateStyles({ 214 pressed: { 215 .backgroundColor(pressedColor) 216 }, 217 normal: { 218 .backgroundColor(normalColor) 219 } 220 }) 221 Blank() 222 if (this.isDefaultInputMethodCurrentSubType(id, isSubType)) { 223 Image($r('app.media.ohos_ic_public_ok')) 224 .size({width: this.dialogStyle.listImageSize, height: this.dialogStyle.listImageSize}) 225 .fillColor(this.dialogStyle.listFontColor) 226 } 227 } 228 if (isDivider) { 229 Divider() 230 .divider() 231 } 232 } 233 .width('100%') 234 .onClick(() => { 235 handleClick(); 236 }) 237 } 238 239 build() { 240 Column() { 241 if (this.inputMethodConfig && this.inputMethodConfig.bundleName.length > 0) { 242 Text($r('sys.string.ohos_id_input_method_settings')) 243 .textStyle() 244 .listItemStyle() 245 .fontSize(this.fontSize) 246 .fontColor(this.fontColor) 247 .stateStyles({ 248 pressed: { 249 .backgroundColor(this.pressedColor) 250 }, 251 normal: { 252 .backgroundColor(this.listBgColor) 253 } 254 }) 255 .onClick(() => { 256 if (this.inputMethodConfig) { 257 let context = getContext(this) as common.UIAbilityContext; 258 context.startAbility({ 259 bundleName: this.inputMethodConfig.bundleName, 260 moduleName: this.inputMethodConfig.moduleName, 261 abilityName: this.inputMethodConfig.abilityName, 262 uri: 'set_input' 263 }); 264 } 265 }) 266 Divider() 267 .divider() 268 } 269 Scroll() { 270 Column() { 271 ForEach(this.subTypes, (item: InputMethodSubtype, index: number) => { 272 this.InputMethodItem(this.activeSubtypes[index].name, 273 this.isDefaultInputMethodCurrentSubType(item.id) ? this.selectedFontColor : this.fontColor, 274 this.isDefaultInputMethodCurrentSubType(item.id) ? this.selectedColor : this.listBgColor, 275 this.pressedColor, this.inputMethods.length > 1 || index < this.subTypes.length, 276 () => { 277 this.switchMethodSub(item); 278 }) 279 }, (item: inputMethod.InputMethodProperty) => JSON.stringify(item)); 280 281 ForEach(this.inputMethods, (item: inputMethod.InputMethodProperty, index: number) => { 282 if (this.subTypes.length === 0 || (this.defaultInputMethod && item.name !== this.defaultInputMethod.name)) { 283 this.InputMethodItem(this.inputMethods[index].label, 284 this.currentInputMethod?.name === item.name ? this.selectedFontColor : this.fontColor, 285 this.currentInputMethod?.name === item.name ? this.selectedColor : this.listBgColor, 286 this.pressedColor, 287 index < this.inputMethods.length - 1, 288 () => { 289 this.switchMethod(item); 290 }) 291 } 292 }, (item: inputMethod.InputMethodProperty) => JSON.stringify(item)); 293 } 294 .width('100%') 295 } 296 .width('100%') 297 .constraintSize({ maxHeight: this.maxListNum > 0 ? this.maxListNum * this.listItemHeight : '100%' }) 298 .scrollBar(BarState.Off) 299 300 if (this.patternOptions && this.showHand) { 301 Divider() 302 .divider() 303 Row() { 304 ForEach(this.patternOptions.patterns, (item: Pattern, index: number) => { 305 Row() { 306 Image(index === this.patternMode ? item.selectedIcon : item.icon) 307 .size({ width: this.imageSize, height: this.imageSize }) 308 .objectFit(ImageFit.Contain) 309 } 310 .justifyContent(FlexAlign.Center) 311 .size({ width: this.imageBtnWidth, height: this.imageBtnHeight }) 312 .borderRadius(this.imageRadius) 313 .stateStyles({ 314 pressed: { 315 .backgroundColor(this.pressedColor) 316 }, 317 normal: { 318 .backgroundColor(this.listBgColor) 319 } 320 }) 321 .onClick(() => { 322 this.switchPositionPattern(index); 323 }) 324 }, (item: Resource) => JSON.stringify(item)); 325 } 326 .width('100%') 327 .height(this.listItemHeight) 328 .justifyContent(FlexAlign.SpaceEvenly) 329 } 330 } 331 .width(this.dialogWidth) 332 .margin({ top: this.columnPadding }) 333 .borderRadius('16vp') 334 .backgroundColor(this.listBgColor) 335 .padding(this.columnPadding) 336 .shadow(ShadowStyle.OUTER_DEFAULT_SM) 337 } 338 339 switchPositionPattern(mode: number): void { 340 if (this.patternOptions) { 341 this.patternMode = mode; 342 AppStorage.set('patternMode', this.patternMode); 343 console.info(`${TAG} this.handMode = ${this.patternMode}`); 344 this.patternOptions.action(this.patternMode); 345 this.controller.close(); 346 } 347 } 348 349 async switchMethod(inputProperty: inputMethod.InputMethodProperty): Promise<void> { 350 if (this.currentInputMethod && this.currentInputMethod.name !== inputProperty.name) { 351 let subTypes = await inputMethod.getSetting().listInputMethodSubtype(inputProperty); 352 inputMethod.switchCurrentInputMethodAndSubtype(inputProperty, subTypes[0], (err: Error, result: boolean) => { 353 if (result) { 354 this.currentInputMethod = inputProperty; 355 } 356 this.controller.close(); 357 }) 358 } 359 } 360 361 switchMethodSub(inputSub: InputMethodSubtype): void { 362 if (this.currentInputMethod && this.defaultInputMethod) { 363 if (this.currentInputMethod.name !== this.defaultInputMethod.name) { 364 inputMethod.switchCurrentInputMethodAndSubtype(this.defaultInputMethod, inputSub, () => { 365 this.currentInputMethod = this.defaultInputMethod; 366 this.currentSub = inputSub; 367 this.controller.close(); 368 }) 369 } else { 370 inputMethod.switchCurrentInputMethodSubtype(inputSub, () => { 371 this.currentSub = inputSub; 372 this.controller.close(); 373 }) 374 } 375 } 376 } 377}