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 if (this.activeSubtypes.length === this.subTypes.length) { 272 ForEach(this.subTypes, (item: InputMethodSubtype, index: number) => { 273 this.InputMethodItem(this.activeSubtypes[index].name, 274 this.isDefaultInputMethodCurrentSubType(item.id) ? this.selectedFontColor : this.fontColor, 275 this.isDefaultInputMethodCurrentSubType(item.id) ? this.selectedColor : this.listBgColor, 276 this.pressedColor, this.inputMethods.length > 1 || index < this.subTypes.length, 277 () => { 278 this.switchMethodSub(item); 279 }) 280 }, (item: inputMethod.InputMethodProperty) => JSON.stringify(item)); 281 } 282 283 ForEach(this.inputMethods, (item: inputMethod.InputMethodProperty, index: number) => { 284 if (this.subTypes.length === 0 || (this.defaultInputMethod && item.name !== this.defaultInputMethod.name)) { 285 this.InputMethodItem(this.inputMethods[index].label, 286 this.currentInputMethod?.name === item.name ? this.selectedFontColor : this.fontColor, 287 this.currentInputMethod?.name === item.name ? this.selectedColor : this.listBgColor, 288 this.pressedColor, 289 index < this.inputMethods.length - 1, 290 () => { 291 this.switchMethod(item); 292 }) 293 } 294 }, (item: inputMethod.InputMethodProperty) => JSON.stringify(item)); 295 } 296 .width('100%') 297 } 298 .width('100%') 299 .constraintSize({ maxHeight: this.maxListNum > 0 ? this.maxListNum * this.listItemHeight : '100%' }) 300 .scrollBar(BarState.Off) 301 302 if (this.patternOptions && this.showHand) { 303 Divider() 304 .divider() 305 Row() { 306 ForEach(this.patternOptions.patterns, (item: Pattern, index: number) => { 307 Row() { 308 Image(index === this.patternMode ? item.selectedIcon : item.icon) 309 .size({ width: this.imageSize, height: this.imageSize }) 310 .objectFit(ImageFit.Contain) 311 } 312 .justifyContent(FlexAlign.Center) 313 .size({ width: this.imageBtnWidth, height: this.imageBtnHeight }) 314 .borderRadius(this.imageRadius) 315 .stateStyles({ 316 pressed: { 317 .backgroundColor(this.pressedColor) 318 }, 319 normal: { 320 .backgroundColor(this.listBgColor) 321 } 322 }) 323 .onClick(() => { 324 this.switchPositionPattern(index); 325 }) 326 }, (item: Resource) => JSON.stringify(item)); 327 } 328 .width('100%') 329 .height(this.listItemHeight) 330 .justifyContent(FlexAlign.SpaceEvenly) 331 } 332 } 333 .width(this.dialogWidth) 334 .margin({ top: this.columnPadding }) 335 .borderRadius('16vp') 336 .backgroundColor(this.listBgColor) 337 .padding(this.columnPadding) 338 .shadow(ShadowStyle.OUTER_DEFAULT_SM) 339 } 340 341 switchPositionPattern(mode: number): void { 342 if (this.patternOptions) { 343 this.patternMode = mode; 344 AppStorage.set('patternMode', this.patternMode); 345 console.info(`${TAG} this.handMode = ${this.patternMode}`); 346 this.patternOptions.action(this.patternMode); 347 this.controller.close(); 348 } 349 } 350 351 async switchMethod(inputProperty: inputMethod.InputMethodProperty): Promise<void> { 352 if (this.currentInputMethod && this.currentInputMethod.name !== inputProperty.name) { 353 let subTypes = await inputMethod.getSetting().listInputMethodSubtype(inputProperty); 354 inputMethod.switchCurrentInputMethodAndSubtype(inputProperty, subTypes[0], (err: Error, result: boolean) => { 355 if (result) { 356 this.currentInputMethod = inputProperty; 357 } 358 this.controller.close(); 359 }) 360 } 361 } 362 363 switchMethodSub(inputSub: InputMethodSubtype): void { 364 if (this.currentInputMethod && this.defaultInputMethod) { 365 if (this.currentInputMethod.name !== this.defaultInputMethod.name) { 366 inputMethod.switchCurrentInputMethodAndSubtype(this.defaultInputMethod, inputSub, () => { 367 this.currentInputMethod = this.defaultInputMethod; 368 this.currentSub = inputSub; 369 this.controller.close(); 370 }) 371 } else { 372 inputMethod.switchCurrentInputMethodSubtype(inputSub, () => { 373 this.currentSub = inputSub; 374 this.controller.close(); 375 }) 376 } 377 } 378 } 379}