├── .gitignore ├── AppScope ├── app.json5 └── resources │ └── base │ ├── element │ └── string.json │ └── media │ └── app_icon.png ├── CHANGELOG.md ├── README.md ├── build-profile.json5 ├── dependencies ├── hvigor-4.1.2.tgz └── hvigor-ohos-plugin-4.1.2.tgz ├── example ├── .gitignore ├── build-profile.json5 ├── hvigorfile.ts ├── obfuscation-rules.txt ├── oh-package-lock.json5 ├── oh-package.json5 ├── patch.json └── src │ ├── main │ ├── ets │ │ ├── bean │ │ │ └── CommonItemTitle.ets │ │ ├── components │ │ │ ├── AlbumComponent.ets │ │ │ ├── BaseFragmentComp.ets │ │ │ ├── CameraComponent.ets │ │ │ ├── CameraConfig.ets │ │ │ ├── CameraConstants.ets │ │ │ ├── CameraCreateCore.ets │ │ │ ├── CameraManager.ets │ │ │ └── ThrottleDebounce.ets │ │ ├── const │ │ │ ├── CommonConst.ets │ │ │ └── StateConst.ets │ │ ├── entryability │ │ │ └── EntryAbility.ets │ │ └── pages │ │ │ ├── AppUtilsFragment.ets │ │ │ ├── ClipboardFragment.ets │ │ │ ├── DeviceFragment.ets │ │ │ ├── DialogFragment.ets │ │ │ ├── ImageFragment.ets │ │ │ ├── Index.ets │ │ │ ├── LifecycleFragment.ets │ │ │ ├── ListSortFragment.ets │ │ │ ├── LiveDataBusFragment.ets │ │ │ └── PermissionFragment.ets │ ├── module.json5 │ └── resources │ │ ├── base │ │ ├── element │ │ │ ├── color.json │ │ │ └── string.json │ │ ├── media │ │ │ ├── background.png │ │ │ ├── foreground.png │ │ │ ├── layered_image.json │ │ │ └── startIcon.png │ │ └── profile │ │ │ ├── main_pages.json │ │ │ └── route_map.json │ │ └── rawfile │ │ └── image_compression_before.jpeg │ ├── mock │ └── mock-config.json5 │ ├── ohosTest │ ├── ets │ │ ├── test │ │ │ ├── Ability.test.ets │ │ │ └── List.test.ets │ │ ├── testability │ │ │ ├── TestAbility.ets │ │ │ └── pages │ │ │ │ └── Index.ets │ │ └── testrunner │ │ │ └── OpenHarmonyTestRunner.ets │ ├── module.json5 │ └── resources │ │ └── base │ │ ├── element │ │ ├── color.json │ │ └── string.json │ │ ├── media │ │ └── icon.png │ │ └── profile │ │ └── test_pages.json │ └── test │ ├── List.test.ets │ ├── LocalUnit.test.ets │ └── ThrottleDebounce.ets ├── hvigor └── hvigor-config.json5 ├── hvigorfile.ts ├── obfuscation-namecache.json ├── oh-package-lock.json5 ├── oh-package.json5 └── utilcode ├── .gitignore ├── BuildProfile.ets ├── CHANGELOG.md ├── Index.ets ├── LICENSE ├── README.md ├── build-profile.json5 ├── consumer-rules.txt ├── hvigorfile.ts ├── obfuscation-rules.txt ├── oh-package-lock.json5 ├── oh-package.json5 └── src ├── main ├── ets │ ├── AESUtils.ts │ ├── AppUtils.ets │ ├── ArrayUtils.ets │ ├── AssetStore.ets │ ├── BarUtils.ets │ ├── Base64Util.ets │ ├── BrightnessUtils.ets │ ├── BusUtils.ets │ ├── CharUtils.ts │ ├── CleanUtils.ets │ ├── ClipboardUtil.ets │ ├── ClipboardUtils.ets │ ├── CloneUtils.ets │ ├── CollectionUtils.ets │ ├── ColorUtils.ets │ ├── ConvertUtils.ets │ ├── CrashUtils.ets │ ├── CryptoUtils.ets │ ├── DESUtils.ts │ ├── DeviceUtils.ets │ ├── EncodeUtils.ets │ ├── EncryptUtils.ets │ ├── FileUtils.ets │ ├── FlashlightUtils.ets │ ├── IntentUtils.ets │ ├── JsonUtils.ts │ ├── KeyboardUtils.ets │ ├── LanguageUtils.ets │ ├── LiveDataBus.ets │ ├── LogUtils.ets │ ├── MD5Utils.ets │ ├── MapUtils.ets │ ├── NetworkUtils.ets │ ├── NotificationUtils.ets │ ├── NumberUtils.ts │ ├── ObjectUtils.ts │ ├── PathUtils.ets │ ├── PhoneUtils.ets │ ├── PromptActionUtils.ets │ ├── RandomUtils.ets │ ├── ReflectUtils.ets │ ├── RegexUtils.ets │ ├── ResourceUtils.ets │ ├── SPUtils.ets │ ├── ScreenUtils.ets │ ├── SnowflakeUtil.ets │ ├── SpanUtils.ets │ ├── StopWatch.ets │ ├── StringUtils.ts │ ├── ThreadUtils.ets │ ├── TimeUtils.ets │ ├── ToastUtils.ets │ ├── UIAdapt.ets │ ├── Utils.ets │ ├── VibrateUtils.ets │ ├── VolumeUtils.ets │ ├── ZipUtils.ets │ ├── const │ │ └── CommonConst.ts │ ├── ext │ │ └── StringExt.ts │ ├── helper │ │ └── PickerHelper.ets │ ├── image │ │ ├── ImageCompressOptions.ets │ │ ├── ImageInfo.ets │ │ ├── ImageResult.ets │ │ ├── ImageTask.ets │ │ └── ImageUtils.ets │ ├── lifecycle │ │ ├── Lifecycle.ts │ │ └── LifecycleEvent.ts │ ├── permissions │ │ ├── Permission.ets │ │ ├── PermissionOptions.ets │ │ ├── PermissionResult.ets │ │ ├── PermissionUtils.ets │ │ └── PermissionsHelper.ets │ └── throttle_debounce │ │ ├── debounce.ts │ │ └── throttle.ts ├── module.json5 └── resources │ ├── base │ └── element │ │ └── string.json │ ├── en_US │ └── element │ │ └── string.json │ └── zh_CN │ └── element │ └── string.json ├── mock └── mock-config.json5 ├── ohosTest ├── ets │ ├── test │ │ ├── Ability.test.ets │ │ └── List.test.ets │ ├── testability │ │ ├── TestAbility.ets │ │ └── pages │ │ │ └── Index.ets │ └── testrunner │ │ └── OpenHarmonyTestRunner.ets ├── module.json5 └── resources │ └── base │ ├── element │ ├── color.json │ └── string.json │ ├── media │ └── icon.png │ └── profile │ └── test_pages.json └── test ├── List.test.ets └── LocalUnit.test.ets /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /local.properties 4 | /.idea 5 | **/build 6 | /.hvigor 7 | .cxx 8 | /.clangd 9 | /.clang-format 10 | /.clang-tidy 11 | **/.test 12 | /.appanalyzer 13 | /build 14 | /configs/** 15 | 16 | -------------------------------------------------------------------------------- /AppScope/app.json5: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "bundleName": "com.harmonyos.utilcode", 4 | "vendor": "example", 5 | "versionCode": 1000000, 6 | "versionName": "1.0.0", 7 | "icon": "$media:app_icon", 8 | "label": "$string:app_name" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /AppScope/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "app_name", 5 | "value": "HarmonyUtilCode" 6 | }, 7 | { 8 | "name": "descriptionCamera", 9 | "value": "为了拍摄照片和视频" 10 | }, 11 | { 12 | "name": "descriptionLocation", 13 | "value": "为了申请定位权限" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /AppScope/resources/base/media/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanranran/HarmonyUtilCode/9751d1af45ed7c847e636f760f9c38168f1e479a/AppScope/resources/base/media/app_icon.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.0.9] 2024-11-25 4 | 修复LiveDataBus 广播注销不生效的问题 5 | 新增KeyBoardUtils 工具类 6 | 7 | ## [v0.0.8] 2024-09-19 8 | 升级ohos/crypto-js 9 | 10 | ## [v0.0.7] 2024-08-03 11 | 修复因为@Sendable 导致的FileUtils 奔溃问题 12 | 13 | ## [v0.0.6] 2024-07-26 14 | 新增相册选择类:PickerHelper 15 | 性能统计类:StopWatch 16 | 文件操作类:FileUtils 17 | APP信息类:AppUtils 18 | 图片压缩类:ImageUtils 19 | 吐司工具类:ToastUtils 20 | 21 | ## [v0.0.5] 2024-07-12 22 | 23 | 新增LiveDataBus、ImageUtils 类 24 | 25 | ## [v0.0.4] 2024-06-06 26 | 27 | 新增Lifecycle装饰器类 28 | 29 | ## [v0.0.3] 2024-05-17 30 | 修复DeviceUtils BUG 31 | 新增Utils、ConvertUtils、UIAdapt 32 | 新增ResourceUtils 类 33 | 新增RandomUtils 类 34 | 35 | ## [v0.0.2] 2024-05-15 36 | 37 | 新增CharUtils类 38 | 新增ObjectUtils类 39 | 新增StringUtils类 40 | 新增AssetStore类 41 | 42 | ## [v0.0.1] 2024-05-13 43 | 44 | 初版 45 | 新增DeviceUtils类 46 | 47 | ### 🐣新特性 48 | 49 | * 发布测试版 50 | 51 | ### 🐞Bug修复 52 | 53 | * 暂无 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 📚简介 2 | 3 | `HarmonyUtilCode`是一个功能丰富且易用的**OpenHarmony/HarmonyOS工具库**,通过诸多实用工具类的使用,旨在帮助开发者快速、便捷地完成各类开发任务。 4 | 这些封装的工具涵盖了字符串、数字、集合、JSON等一系列操作, 5 | 可以满足各种不同的开发需求。本人为Android开发,故封装思路借鉴Android的工具类Blankj/AndroidUtilCode ,同时扩展了HarmonyOS的UI组件。 6 | 7 | ## 🛠️包含组件 8 | 9 | 一个OpenHarmony/HarmonyOS基础工具类,组成各种Util工具类 10 | 11 | ### 基础类组件 12 | 13 | * #### 设备相关 DeviceUtils 14 | 15 | ``` 16 | getDeviceId : 获取设备唯一识别码【卸载APP后依旧有效】 17 | ``` 18 | 19 | ## 📦安装 20 | 21 | ### 🍊ohpm 22 | 23 | 执行安装命令 24 | 25 | ``` 26 | ohpm install @android/utilCode 27 | ``` 28 | 29 | ## 📦使用 30 | 31 | ### 1.在项目中引入插件 32 | 33 | ``` 34 | import { DeviceUtils } from '@android_x/utilCode' 35 | ``` 36 | 37 | 类按需引入,项目需要使用那个就引入 38 | 39 | #### 1.1 DeviceUtils的方法 40 | 41 | ``` typescript 42 | import { DeviceUtils } from '@android_x/utilCode' 43 | ``` 44 | 45 | * getDeviceId 获取设备id>32为随机码[卸载APP后依旧不变] 46 | 47 | ``` typescript 48 | console.log(await DeviceUtils.getDeviceId()); 49 | ``` 50 | 51 | #### 1.2 CharUtils的方法 52 | 53 | ``` typescript 54 | import { CharUtils } from '@android_x/utilCode' 55 | ``` 56 | 57 | * isBlankChar 是否空白符 空白符包括空格、制表符、全角空格和不间断空格 58 | * isAscii 检查字符是否位于ASCII范围内(0~127) 59 | * isEmoji 判断是否为emoji表情符 60 | 61 | #### 1.3 StringUtils的方法 62 | 63 | 后期会增加扩展方法,使用会更简单 64 | 65 | ``` typescript 66 | import { StringUtils } from '@android_x/utilCode' 67 | ``` 68 | 69 | * isBlank 判断字符串是否为空白符(空白符包括空格、制表符、全角空格和不间断空格)true为空,否则false 70 | * isNotBlank 判断字符串是否为非空白符(空白符包括空格、制表符、全角空格和不间断空格)true为非空,否则false 71 | * isEmpty 判断字符串是否为空 72 | * toString 字符串转string,主要用于保证空安全 73 | * replaceAll 字符串全部替换为指定字符串 74 | 75 | #### 1.4 ObjectUtils的方法 76 | 77 | 后期会增加扩展方法,使用会更简单 78 | 79 | ``` typescript 80 | import { ObjectUtils } from '@android_x/utilCode' 81 | ``` 82 | 83 | * isString 判断属性是否是string类型类型 84 | * isNull 判断属性是否为空 85 | * isEmpty 判断属性内容是否为空【Object | String | Number | Boolean | null | undefined | Array | Map...】 86 | * equal 判断两个传入的数值或者是字符串是否相等 87 | * notEqual 判断两个传入的数值或者是字符串是否不相等 88 | * deepCopy 深拷贝对象 89 | 90 | #### 1.5 AssetStore的方法 91 | 92 | 基于 @ohos.security.asset 的封装。可以保证『重装/删除应用而不丢失数据』。 93 | 94 | ``` typescript 95 | import { AssetStore } from '@android_x/utilCode' 96 | ``` 97 | 98 | * set 增 99 | * remove 删 100 | * update 改 101 | * get 查 102 | 103 | #### 1.6 ResourceUtils 104 | 105 | 资源相关工具类 106 | 107 | ``` typescript 108 | import { ResourceUtils } from '@android_x/utilCode' 109 | ``` 110 | 111 | * getNumber 返回Resource对应的数值,单位vp 112 | 113 | #### 1.7 RandomUtils 114 | 115 | 随机工具类 116 | 117 | ``` typescript 118 | import { RandomUtils } from '@android_x/utilCode' 119 | ``` 120 | 121 | * randomUUID 随机生成32位uuid f4fed14a-7fab-4219-9440-80aec4735700 122 | 123 | #### 1.8 Lifecycle 124 | 125 | 自定义组件生命周期绑定装饰器,可通过以下方式自动绑定自定义组件的生命周期,使用方法和Android中的Lifecycle类似 126 | 无需关注lifecycle的释放,自定义组件aboutToDisappear时,lifecycle会自动释放 127 | 128 | 使用场景:比如页面关闭后,当前页面上的未请求完毕网络请求自动取消 129 | 130 | 注:目前仅支持aboutToAppear【Component】、onPageShow【Entry】、onPageHide【Entry】、aboutToDisappear【Component】,navigation 131 | 比较特殊,目前暂未找到合适的时机 132 | 133 | ``` typescript 134 | import {Lifecycle, LifecycleEvent } from '@android_x/utilcode'; 135 | @Component 136 | @Preview 137 | export struct TestFragment { 138 | @LifecycleEvent lifecycle: Lifecycle = new Lifecycle() 139 | aboutToAppear(): void { 140 | this.lifecycle.addObserver((state: LifecycleState) => { 141 | //此处即可 142 | console.log("状态" + state) 143 | }) 144 | } 145 | } 146 | ``` 147 | -------------------------------------------------------------------------------- /build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "signingConfigs": [ 4 | { 5 | "name": "default", 6 | "type": "HarmonyOS", 7 | "material": { 8 | "certpath": "/Users/smzdm/.ohos/config/default_HarmonyUtilCode_kfbyGhz8kRKI6ULN1pEMEPR6bNl7dLAXiDpcU7r4qcM=.cer", 9 | "storePassword": "0000001AFAE85E5F5717D138A06D8815F09945CC61B5A5DC8B303C29B40FE52C33C19D37F69472132D2B", 10 | "keyAlias": "debugKey", 11 | "keyPassword": "0000001A26B6AF9447FF0A1DB8667DF7A7F99A3FF6AF49A560D5E6E186A4A160CC9D62AF56EA2461D3A3", 12 | "profile": "/Users/smzdm/.ohos/config/default_HarmonyUtilCode_kfbyGhz8kRKI6ULN1pEMEPR6bNl7dLAXiDpcU7r4qcM=.p7b", 13 | "signAlg": "SHA256withECDSA", 14 | "storeFile": "/Users/smzdm/.ohos/config/default_HarmonyUtilCode_kfbyGhz8kRKI6ULN1pEMEPR6bNl7dLAXiDpcU7r4qcM=.p12" 15 | } 16 | } 17 | ], 18 | "products": [ 19 | { 20 | "name": "default", 21 | "signingConfig": "default", 22 | "compileSdkVersion": "5.0.0(12)", 23 | "compatibleSdkVersion": "5.0.0(12)", 24 | "runtimeOS": "HarmonyOS", 25 | "buildOption": { 26 | "strictMode": { 27 | "useNormalizedOHMUrl": true 28 | } 29 | } 30 | } 31 | ], 32 | "buildModeSet": [ 33 | { 34 | "name": "debug", 35 | }, 36 | { 37 | "name": "release" 38 | } 39 | ] 40 | }, 41 | "modules": [ 42 | { 43 | "name": "example", 44 | "srcPath": "./example", 45 | "targets": [ 46 | { 47 | "name": "default", 48 | "applyToProducts": [ 49 | "default" 50 | ] 51 | } 52 | ] 53 | }, 54 | { 55 | "name": "utilcode", 56 | "srcPath": "./utilcode", 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /dependencies/hvigor-4.1.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanranran/HarmonyUtilCode/9751d1af45ed7c847e636f760f9c38168f1e479a/dependencies/hvigor-4.1.2.tgz -------------------------------------------------------------------------------- /dependencies/hvigor-ohos-plugin-4.1.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanranran/HarmonyUtilCode/9751d1af45ed7c847e636f760f9c38168f1e479a/dependencies/hvigor-ohos-plugin-4.1.2.tgz -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /example/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | }, 5 | "buildOptionSet": [ 6 | { 7 | "name": "release", 8 | "arkOptions": { 9 | "obfuscation": { 10 | "ruleOptions": { 11 | "enable": true, 12 | "files": [ 13 | "./obfuscation-rules.txt" 14 | ] 15 | } 16 | } 17 | } 18 | }, 19 | ], 20 | "targets": [ 21 | { 22 | "name": "default" 23 | }, 24 | { 25 | "name": "ohosTest", 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /example/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { hapTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /example/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # 配置混淆教程 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 2 | 3 | # 开启属性混淆。 如果你使用这个选项,那么所有的属性名都会被混淆,被import/export直接导入或导出的类或对象的属性名不会被混淆 4 | -enable-property-obfuscation 5 | 6 | # 开启顶层作用域名称混淆 7 | -enable-toplevel-obfuscation 8 | 9 | # 开启文件/文件夹名称混淆 10 | -enable-filename-obfuscation 11 | 12 | # 向外导入或导出的名称混淆 13 | -enable-export-obfuscation 14 | 15 | # 去除不必要的空格符和所有的换行符 16 | -compact 17 | 18 | # 删除对 console.* 语句的调用,要求console.*语句返回值未被调用。 19 | -remove-log 20 | 21 | # 删除文件中的所有注释,包括单行、多行,及JsDoc注释 22 | -remove-comments 23 | 24 | #将名称缓存保存到指定的文件路径。名称缓存包含名称混淆前后的映射。 25 | -print-namecache obfuscation-namecache.json 26 | 27 | 28 | #保留指定路径中的所有文件的属性名称 29 | -keep 30 | ../oh_modules/* 31 | -------------------------------------------------------------------------------- /example/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "@android_x/utilcode@../utilcode": "@android_x/utilcode@../utilcode", 9 | "@ohos/crypto-js@^2.0.4": "@ohos/crypto-js@2.0.4", 10 | "@tencent/mmkv@^1.3.9": "@tencent/mmkv@1.3.9", 11 | "class-transformer@^0.5.1": "class-transformer@0.5.1" 12 | }, 13 | "packages": { 14 | "@android_x/utilcode@../utilcode": { 15 | "name": "@android_x/utilcode", 16 | "version": "0.0.8", 17 | "resolved": "../utilcode", 18 | "registryType": "local", 19 | "dependencies": { 20 | "@ohos/crypto-js": "^2.0.4", 21 | "class-transformer": "^0.5.1" 22 | } 23 | }, 24 | "@ohos/crypto-js@2.0.4": { 25 | "name": "@ohos/crypto-js", 26 | "version": "2.0.4", 27 | "integrity": "sha512-589ur6oqU1UNibqefMly2cwEeEhkSoCAA3uc+oNUwRnYYtevn/kQnO+Coi36N+VJSeeg/uFzZk1K/wUMdovpOA==", 28 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/crypto-js/-/crypto-js-2.0.4.har", 29 | "registryType": "ohpm" 30 | }, 31 | "@tencent/mmkv@1.3.9": { 32 | "name": "@tencent/mmkv", 33 | "version": "1.3.9", 34 | "integrity": "sha512-SkkF/KoBih5xMfxwOHAUduSksAy2L3ngcWcyAOZEks4dY4Tbd0xMAsGZRMnHDbE+7cUbaz/1IuNGcejtBAcOXw==", 35 | "resolved": "https://ohpm.openharmony.cn/ohpm/@tencent/mmkv/-/mmkv-1.3.9.har", 36 | "registryType": "ohpm", 37 | "dependencies": { 38 | "libmmkv.so": "file:./src/main/cpp/types/libmmkv" 39 | }, 40 | "packageType": "InterfaceHar" 41 | }, 42 | "class-transformer@0.5.1": { 43 | "name": "class-transformer", 44 | "version": "0.5.1", 45 | "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", 46 | "resolved": "https://ohpm.openharmony.cn/ohpm/class-transformer/-/class-transformer-0.5.1.tgz", 47 | "shasum": "24147d5dffd2a6cea930a3250a677addf96ab336", 48 | "registryType": "ohpm" 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /example/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "Please describe the basic information.", 5 | "main": "", 6 | "author": "tanranran", 7 | "license": "", 8 | "dependencies": { 9 | "@android_x/utilcode": "file:../utilcode", 10 | "@tencent/mmkv": "^1.3.9" 11 | }, 12 | "devDependencies": {}, 13 | "dynamicDependencies": {} 14 | } -------------------------------------------------------------------------------- /example/patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "bundleName": "com.harmonyos.utilcode", 4 | "patchVersionCode": 2000294, 5 | "versionCode": 1000000 6 | }, 7 | "module": { 8 | "name": "example", 9 | "type": "hotreload" 10 | } 11 | } -------------------------------------------------------------------------------- /example/src/main/ets/bean/CommonItemTitle.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/13 22:52 4 | * @description 5 | */ 6 | @Observed 7 | export default class CommonItemTitle { 8 | pageName?: string 9 | title: string 10 | content?: string 11 | callback?: () => void 12 | visible: boolean = true; // 是否可见 13 | 14 | constructor(title: string, content: string, pageName?: string, callback?: () => void) { 15 | this.title = title 16 | this.content = content 17 | this.pageName = pageName 18 | this.callback = callback 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/src/main/ets/components/AlbumComponent.ets: -------------------------------------------------------------------------------- 1 | import { 2 | ClickType, 3 | DataType, 4 | ItemInfo, 5 | ItemType, 6 | PhotoBrowserInfo, 7 | PhotoPickerComponent, 8 | PickerColorMode, 9 | PickerController, 10 | PickerOptions, 11 | ReminderMode 12 | } from '@ohos.file.PhotoPickerComponent'; 13 | import { photoAccessHelper } from '@kit.MediaLibraryKit'; 14 | import { ToastUtils } from '@android_x/utilcode'; 15 | 16 | /** 17 | * 自定义相册组件 18 | * @author Tanranran 19 | * @date 2024/7/15 18:27 20 | * @description 21 | */ 22 | @Component 23 | export struct AlbumComponent { 24 | pickerOptions: PickerOptions = new PickerOptions(); 25 | @State pickerController: PickerController = new PickerController(); 26 | @State selectUris: Array = new Array(); 27 | 28 | aboutToAppear(): void { 29 | this.pickerOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE; 30 | this.pickerOptions.checkBoxColor = '#FFFFFF' 31 | this.pickerOptions.checkboxTextColor='#FF0000' 32 | this.pickerOptions.backgroundColor = '#b7d332' 33 | this.pickerOptions.photoBrowserBackgroundColorMode = PickerColorMode.AUTO 34 | this.pickerOptions.maxSelectedReminderMode = ReminderMode.NONE 35 | this.pickerOptions.isRepeatSelectSupported = false //是否支持单张图片重复选择。true表示支持。 36 | this.pickerOptions.isSearchSupported = false; //是否支持搜索 37 | this.pickerOptions.isPhotoTakingSupported = true; //是否支持拍照 38 | 39 | } 40 | 41 | build() { 42 | Column() { 43 | PhotoPickerComponent({ 44 | pickerOptions: this.pickerOptions, //picker参数信息。 45 | // onSelect: (uri: string): void => this.onSelect(uri), //用户在Picker组件中勾选图片时产生的回调事件,将图片uri报给应用。 46 | // onDeselect: (uri: string): void => this.onDeselect(uri), //用户在Picker组件中取消勾选图片时产生的回调事件,同时也会将图片uri报给应用。 47 | onItemClicked: (itemInfo: ItemInfo, clickType: ClickType): boolean => this.onItemClicked(itemInfo, 48 | clickType), // 该接口可替代上面两个接口[用户在picker组件中点击item产生的回调事件。点击图片(缩略图item)时,返回值为true则勾选此图片,否则不勾选;点击相机item,返回值为true则拉起系统相机,否则应用自行处理。] 49 | // onEnterPhotoBrowser: (photoBrowserInfo: PhotoBrowserInfo): boolean => this.onEnterPhotoBrowser(photoBrowserInfo), //点击进入大图时产生的回调事件,将大图相关信息报给应用。 50 | // onExitPhotoBrowser: (photoBrowserInfo: PhotoBrowserInfo): boolean => this.onExitPhotoBrowser(photoBrowserInfo), //退出大图时产生的回调事件,将大图相关信息报给应用。 51 | // onPickerControllerReady: (): void => this.onPickerControllerReady(), //当pickerController可用时产生的回调事件。 52 | pickerController: this.pickerController, 53 | }).layoutWeight(1).width('100%') 54 | 55 | // 这里模拟应用侧底部的选择栏 56 | Row() { 57 | Repeat(this.selectUris).each((urlItem: RepeatItem) => { 58 | Image(urlItem.item) 59 | .height('100%') 60 | .autoResize(true) 61 | .aspectRatio(1) 62 | .objectFit(ImageFit.Cover) 63 | .onClick(() => { 64 | // 点击事件模拟删除操作,通过pickerController向picker组件发送已选择的数据列表,触发picker组件勾选框刷新 65 | this.selectUris = this.selectUris.filter((item: string) => { 66 | return item != urlItem.item; 67 | }) 68 | this.pickerController.setData(DataType.SET_SELECTED_URIS, this.selectUris); 69 | }) 70 | }).key((item: string) => item) 71 | }.width('100%').height(100).backgroundColor(Color.Red) 72 | }.width('100%') 73 | .height('100%') 74 | } 75 | 76 | private onItemClicked(itemInfo: ItemInfo, clickType: ClickType): boolean { 77 | if (!itemInfo) { 78 | return false; 79 | } 80 | let type: ItemType | undefined = itemInfo.itemType; 81 | if (type === ItemType.CAMERA) { 82 | // 点击相机item 83 | return true; // 返回true则拉起系统相机,若应用需要自行处理则返回false。 84 | } else { 85 | let uri = itemInfo.uri 86 | if (clickType === ClickType.SELECTED) { 87 | if (this.selectUris.length >= 2) { 88 | ToastUtils.show("已经达到最大数量了") 89 | return false 90 | } 91 | if (uri) { 92 | this.selectUris.push(uri); 93 | } 94 | } else if (clickType === ClickType.DESELECTED) { 95 | if (uri) { 96 | this.selectUris = this.selectUris.filter((item: string) => { 97 | return item != uri; 98 | }) 99 | } 100 | } 101 | console.log('最新数量' + this.selectUris.length) 102 | } 103 | return true; // 返回true则勾选,否则则不响应勾选。 104 | } 105 | } -------------------------------------------------------------------------------- /example/src/main/ets/components/BaseFragmentComp.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 23:32 4 | * @description 5 | */ 6 | import CommonItemTitle from '../bean/CommonItemTitle' 7 | import observer from '@ohos.arkui.observer'; 8 | import { JsonUtils, ObjectUtils } from '@android_x/utilcode'; 9 | 10 | @Component 11 | export struct BaseFragmentComp { 12 | @Prop title: string = '' 13 | @Prop listData: Array = [] 14 | @BuilderParam contentComponent?: () => void 15 | build() { 16 | NavDestination() { 17 | if (this.listData.length > 0) { 18 | List() { 19 | ForEach(this.listData, (item: CommonItemTitle) => { 20 | ListItem() { 21 | Column() { 22 | Text(item.title).fontWeight(FontWeight.Bold) 23 | Text(item.content).margin({ top: item.content && item.title ? 12 : 0 }).onClick(() => { 24 | console.log('get navDestinationInfo: ' + JSON.stringify(this.queryNavDestinationInfo())) 25 | }) 26 | } 27 | .onClick(() => { 28 | if (item.callback) { 29 | item.callback() 30 | } 31 | }) 32 | .padding(12) 33 | .constraintSize({ 34 | minHeight: 60 35 | }) 36 | .alignItems(HorizontalAlign.Start) 37 | .justifyContent(FlexAlign.Center) 38 | .borderRadius(3) 39 | .backgroundColor(Color.White) 40 | .width('100%') 41 | } 42 | }, (item: CommonItemTitle) => item.title) 43 | } 44 | .divider({ strokeWidth: '1px', color: $r('sys.color.ohos_id_divider_color') }) 45 | .height('100%') 46 | .width("100%") 47 | .listDirection(Axis.Vertical) 48 | } else { 49 | this.contentComponent?.() 50 | } 51 | } 52 | .title(this.title) 53 | .onReady((context: NavDestinationContext) => { 54 | let params = context.pathInfo.param as object 55 | if (params) { 56 | if (params instanceof CommonItemTitle) { 57 | this.title = params.title 58 | } 59 | } 60 | }) 61 | .backgroundColor('#F5F5F5') 62 | } 63 | 64 | onRead() { 65 | return this 66 | } 67 | } -------------------------------------------------------------------------------- /example/src/main/ets/components/CameraComponent.ets: -------------------------------------------------------------------------------- 1 | import { CameraManager, CameraViewModel } from './CameraManager'; 2 | import { ImageUtils, LogUtils, ScreenUtils, StopWatch } from '@android_x/utilcode'; 3 | import { StateConst } from '../const/StateConst'; 4 | import { CameraConstants } from './CameraConstants'; 5 | 6 | /** 7 | * @author Tanranran 8 | * @date 2024/7/18 15:55 9 | * @description 10 | */ 11 | @Component 12 | export struct CameraComponent { 13 | @StorageProp(StateConst.IS_ON_FOREGROUND) @Watch('onForegroundChange') isForeground: boolean = true 14 | @State cameraViewModel: CameraViewModel = CameraManager.getInstance().cameraViewModel 15 | xComponentController: XComponentController = new XComponentController() 16 | @State pixelMap: PixelMap = ImageUtils.createPixelMap() 17 | 18 | async aboutToAppear(): Promise { 19 | CameraManager.getInstance().init(this.xComponentController) 20 | CameraManager.getInstance().setPictureCallback((image) => { 21 | if (image) { 22 | this.pixelMap = image 23 | } 24 | }) 25 | 26 | } 27 | 28 | aboutToDisappear(): void { 29 | CameraManager.getInstance().releaseCamera(); 30 | } 31 | 32 | //前后台变化回调 33 | onForegroundChange() { 34 | if (this.isForeground) { //前台 35 | CameraManager.getInstance().restart() 36 | } else { //后台 37 | 38 | } 39 | } 40 | 41 | build() { 42 | Column() { 43 | Stack() { 44 | RelativeContainer() { 45 | XComponent({ 46 | id: 'camera_xcomponent', 47 | type: XComponentType.SURFACE, 48 | controller: this.xComponentController 49 | }) 50 | .id('camera_xcomponent') 51 | .height('100%') 52 | .width('100%') 53 | .backgroundColor(Color.Black) 54 | .onLoad(() => { 55 | let surfaceId = this.xComponentController.getXComponentSurfaceId() 56 | if (CameraManager.getInstance().isInitSuccess()) { 57 | CameraManager.getInstance().openPhoto(surfaceId) 58 | } else { 59 | LogUtils.error(CameraComponent.name, '初始化相机失败') 60 | } 61 | }) 62 | 63 | Text(this.cameraViewModel.blankTopHeight + '') 64 | .id('blank_top') 65 | .width('100%') 66 | .fontColor(Color.White) 67 | .height(this.cameraViewModel.blankTopHeight) 68 | .backgroundColor(Color.Black) 69 | .alignRules({ 70 | top: { 'anchor': 'camera_xcomponent', 'align': VerticalAlign.Top } 71 | }) 72 | 73 | Text(this.cameraViewModel.blankBottomHeight + '') 74 | .id('blank_bottom') 75 | .width('100%') 76 | .fontColor(Color.White) 77 | .height(this.cameraViewModel.blankBottomHeight) 78 | .backgroundColor(Color.Black) 79 | .alignRules({ 80 | bottom: { 'anchor': 'camera_xcomponent', 'align': VerticalAlign.Bottom } 81 | }) 82 | } 83 | .backgroundColor(Color.Red) 84 | .width('100%') 85 | .height('100%') 86 | .height(this.cameraViewModel.previewHeight) 87 | }.width('100%') 88 | .clip(true) 89 | .layoutWeight(1) 90 | .backgroundColor(Color.Green) 91 | 92 | Row() { 93 | // Button('前后摄像头').onClick(() => { 94 | // CameraManager.getInstance().switchCamera() 95 | // }) 96 | Button('拍照').onClick(() => { 97 | CameraManager.getInstance().takePicture() 98 | }) 99 | Button('比例').onClick(() => { 100 | CameraManager.getInstance().switchAspectRatio() 101 | }) 102 | // Button('停止').onClick(() => { 103 | // CameraManager.getInstance().stop() 104 | // }) 105 | // Button('重启').onClick(() => { 106 | // CameraManager.getInstance().restart() 107 | // }) 108 | Image(this.pixelMap) 109 | .width(100) 110 | .height(100) 111 | .objectFit(ImageFit.Contain) 112 | }.height(100) 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /example/src/main/ets/components/CameraConfig.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/7/18 16:57 4 | * @description 5 | */ 6 | import { camera } from '@kit.CameraKit' 7 | import { CameraConstants } from './CameraConstants' 8 | 9 | 10 | export class CameraConfig { 11 | frontCameraDevice?: camera.CameraDevice //前置摄像头 12 | backCameraDevice?: camera.CameraDevice // 后置摄像头 13 | cameraDeviceList: Array = [] //设备列表 14 | aspectRatio: number = CameraConstants.AspectRatios[0] //拍摄比例 15 | sceneMode: camera.SceneMode = camera.SceneMode.NORMAL_PHOTO 16 | isFrontCamera = false //是否是前置摄像头 17 | 18 | default() { 19 | this.isFrontCamera = false 20 | } 21 | 22 | /** 23 | * 设置拍摄场景【拍照/录像】 24 | * @param sceneMode 25 | */ 26 | setSceneMode(sceneMode: camera.SceneMode) { 27 | this.sceneMode = sceneMode 28 | } 29 | 30 | /** 31 | * 切换比例 32 | * @param sceneMode 33 | */ 34 | switchAspectRatio() { 35 | let aspectRatios = CameraConstants.AspectRatios 36 | let nextIndex = 0 37 | for (let i = 0; i < aspectRatios.length; i++) { 38 | if (aspectRatios[i] == this.aspectRatio) { 39 | nextIndex = i + 1 40 | if (nextIndex > (aspectRatios.length - 1)) { 41 | nextIndex = 0 42 | } 43 | break 44 | } 45 | } 46 | this.aspectRatio = aspectRatios[nextIndex] 47 | return this.aspectRatio 48 | } 49 | 50 | /** 51 | * 设置前后摄像头 52 | * @param sceneMode 53 | */ 54 | switchCamera() { 55 | this.isFrontCamera = !this.isFrontCamera 56 | } 57 | 58 | 59 | /** 60 | * 设置支持的摄像头列表 61 | * @param cameraDeviceList 62 | */ 63 | setCameraDeviceList(cameraDeviceList: Array) { 64 | this.cameraDeviceList = cameraDeviceList 65 | if (this.cameraDeviceList.length == 0) { 66 | return 67 | } 68 | for (const cameraDevice of cameraDeviceList) { 69 | if (cameraDevice.cameraPosition == camera.CameraPosition.CAMERA_POSITION_BACK) { //默认后置摄像头 70 | this.backCameraDevice = cameraDevice 71 | } else if (cameraDevice.cameraPosition == camera.CameraPosition.CAMERA_POSITION_FRONT) { //前置摄像头 72 | this.frontCameraDevice = cameraDevice 73 | } 74 | } 75 | if (!this.backCameraDevice) { 76 | this.backCameraDevice = cameraDeviceList[0] 77 | } 78 | } 79 | 80 | /** 81 | * 获取当前摄像头 82 | * @returns 83 | */ 84 | getCameraDevice(): camera.CameraDevice | undefined { 85 | if (this.backCameraDevice && this.frontCameraDevice) { 86 | if (this.isFrontCamera) { 87 | return this.frontCameraDevice 88 | } else { 89 | return this.backCameraDevice 90 | } 91 | } else { 92 | if (this.backCameraDevice) { 93 | return this.backCameraDevice 94 | } else { 95 | return this.frontCameraDevice 96 | } 97 | } 98 | return undefined 99 | } 100 | 101 | /** 102 | * 是否支持前置摄像头 103 | * @returns 104 | */ 105 | isSupportFront() { 106 | return this.backCameraDevice && this.frontCameraDevice 107 | } 108 | 109 | 110 | /** 111 | * 是否支持拍照 112 | */ 113 | isSupportTakePhoto(cameraManager?: camera.CameraManager) { 114 | const cameraDevice = this.getCameraDevice() 115 | if (!cameraDevice || !cameraManager) { 116 | return false 117 | } 118 | return cameraManager.getSupportedSceneModes(cameraDevice).indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0; 119 | } 120 | 121 | /** 122 | * 是否支持录像 123 | */ 124 | isSupportTakeVideo(cameraManager?: camera.CameraManager) { 125 | const cameraDevice = this.getCameraDevice() 126 | if (!cameraDevice || !cameraManager) { 127 | return false 128 | } 129 | return cameraManager.getSupportedSceneModes(cameraDevice).indexOf(camera.SceneMode.NORMAL_VIDEO) >= 0; 130 | } 131 | } -------------------------------------------------------------------------------- /example/src/main/ets/components/CameraConstants.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/7/22 17:25 4 | * @description 5 | */ 6 | import { camera } from '@kit.CameraKit'; 7 | 8 | export type CameraProfile = camera.Profile | camera.VideoProfile 9 | 10 | export class CameraConstants { 11 | // 推荐拍照分辨率之一 12 | static photoProfile: camera.Profile = { 13 | format: camera.CameraFormat.CAMERA_FORMAT_JPEG, 14 | size: { 15 | width: 1280, 16 | height: 720 17 | } 18 | }; 19 | // 推荐预览分辨率之一 20 | static previewProfile: camera.Profile = { 21 | format: camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP, 22 | size: { 23 | width: 1280, 24 | height: 720 25 | } 26 | }; 27 | //预览宽高比 28 | static previewRatio = CameraConstants.previewProfile.size.width / CameraConstants.previewProfile.size.height 29 | // 推荐录像分辨率之一 30 | static videoProfile: camera.VideoProfile = { 31 | format: camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP, 32 | size: { 33 | width: 1280, 34 | height: 720 35 | }, 36 | frameRateRange: { 37 | min: 30, 38 | max: 60 39 | } 40 | }; 41 | //拍摄比例 42 | static readonly AspectRatios = [0.75, 1, 1.33] //3/4,1/1,4/3 43 | /** 44 | * VIDEO_FRAME. 45 | */ 46 | static readonly MAX_VIDEO_FRAME: number = 60; 47 | /** 48 | * AUDIO_BITRATE. 49 | */ 50 | static readonly AUDIO_BITRATE: number = 48000; 51 | /** 52 | * AUDIO_CHANNELS. 53 | */ 54 | static readonly AUDIO_CHANNELS: number = 2; 55 | /** 56 | * AUDIO_SAMPLE_RATE. 57 | */ 58 | static readonly AUDIO_SAMPLE_RATE: number = 48000; 59 | /** 60 | * VIDEO_BITRATE. 61 | */ 62 | static readonly VIDEO_BITRATE: number = 512000; 63 | } -------------------------------------------------------------------------------- /example/src/main/ets/components/ThrottleDebounce.ets: -------------------------------------------------------------------------------- 1 | import { debounce, Debounce, throttle, Throttle } from '@android_x/utilcode'; 2 | 3 | 4 | /** 5 | * 限流和防抖函数使用例子 6 | * @author Tanranran 7 | * @date 2024/3/20 15:55 8 | * @description 9 | * 限流 (Throttling) 点击防抖用这个 10 | * 适用场景: 11 | 滚动事件处理:在滚动时按固定时间间隔处理事件,比如无限滚动加载内容。 12 | 监控浏览器的滚动位置:定期检查滚动位置来决定是否显示或隐藏页面上的元素。 13 | 游戏控制:限制用户操作的频率,如每秒只能发射一次子弹。 14 | 15 | * 防抖 (Debouncing) 16 | * 适用场景: 17 | 搜索框实时搜索:用户在搜索框输入时,只有在用户停止输入一定时间后才发起搜索请求,避免对每个键盘输入都进行处理。 18 | 窗口大小调整(resize):只在用户完成窗口大小调整一段时间后,才进行相关的布局计算和更新,避免频繁调整造成的性能问题。 19 | 表单验证:在用户停止输入后延迟执行验证逻辑,减少验证频率。 20 | */ 21 | @Entry 22 | @Component 23 | struct ThrottleDebounce { 24 | @State message: string = 'Hello World'; 25 | 26 | @Debounce(3000) 27 | debounceClick1(msg: string) { 28 | console.log("debounceClick1 " + msg) 29 | } 30 | 31 | debounceClick2(msg: string) { 32 | console.log("handleClick2 " + msg) 33 | } 34 | 35 | private debounceClick22 = debounce(2000, this.debounceClick2) 36 | 37 | @Throttle(3000) 38 | throttleClick1(msg: string) { 39 | console.log("throttleClick1 " + msg) 40 | } 41 | 42 | throttleClick2(msg: string) { 43 | console.log("throttleClick2 " + msg) 44 | } 45 | 46 | private throttleClick22 = throttle(2000, this.throttleClick2) 47 | 48 | build() { 49 | Row() { 50 | Column() { 51 | Text(this.message) 52 | .fontSize(50) 53 | .fontWeight(FontWeight.Bold) 54 | } 55 | .width('100%') 56 | .onClick(() => { 57 | this.debounceClick1('Debounce Decorator') 58 | this.debounceClick22('debounce Function') 59 | 60 | this.throttleClick1('Throttle Decorator') 61 | this.throttleClick22('throttle Function') 62 | }) 63 | } 64 | .height('100%') 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/src/main/ets/const/CommonConst.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/13 22:30 4 | * @description 5 | */ 6 | export default class CommonConst { 7 | static readonly NavPathStack = "pageInfos" 8 | } 9 | 10 | -------------------------------------------------------------------------------- /example/src/main/ets/const/StateConst.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/7/22 19:40 4 | * @description 5 | */ 6 | export enum StateConst { 7 | IS_ON_FOREGROUND = 'IS_ON_FOREGROUND', //是否在前台 8 | } -------------------------------------------------------------------------------- /example/src/main/ets/entryability/EntryAbility.ets: -------------------------------------------------------------------------------- 1 | import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 2 | import { hilog } from '@kit.PerformanceAnalysisKit'; 3 | import { window } from '@kit.ArkUI'; 4 | import { StateConst } from '../const/StateConst'; 5 | import { MMKV } from '@tencent/mmkv'; 6 | 7 | export default class EntryAbility extends UIAbility { 8 | onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 9 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 10 | let appCtx = this.context.getApplicationContext(); 11 | let mmkvRootDir = MMKV.initialize(appCtx); 12 | console.info('mmkv rootDir: ', mmkvRootDir); 13 | } 14 | 15 | onDestroy(): void { 16 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); 17 | } 18 | 19 | onWindowStageCreate(windowStage: window.WindowStage): void { 20 | // Main window is created, set main page for this ability 21 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); 22 | 23 | windowStage.loadContent('pages/Index', (err) => { 24 | if (err.code) { 25 | hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 26 | return; 27 | } 28 | //设置主window 的背景色 29 | windowStage.getMainWindow().then(value => { 30 | value.setWindowBackgroundColor('#00000000') 31 | }) 32 | hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); 33 | }); 34 | } 35 | 36 | onWindowStageDestroy(): void { 37 | // Main window is destroyed, release UI related resources 38 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); 39 | } 40 | 41 | onForeground(): void { 42 | // Ability has brought to foreground 43 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); 44 | AppStorage.setOrCreate(StateConst.IS_ON_FOREGROUND, true) 45 | } 46 | 47 | onBackground(): void { 48 | // Ability has back to background 49 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); 50 | AppStorage.setOrCreate(StateConst.IS_ON_FOREGROUND, false) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/src/main/ets/pages/AppUtilsFragment.ets: -------------------------------------------------------------------------------- 1 | import { DeviceUtils, Lifecycle, LifecycleEvent, LifecycleState, LogUtils } from '@android_x/utilcode'; 2 | import CommonItemTitle from '../bean/CommonItemTitle'; 3 | import CommonConst from '../const/CommonConst'; 4 | import { AppUtils } from '@android_x/utilcode/src/main/ets/AppUtils'; 5 | import { BaseFragmentComp } from '../components/BaseFragmentComp'; 6 | import { SourceType } from '@kit.InputKit'; 7 | import { taskpool } from '@kit.ArkTS'; 8 | import { BusinessError } from '@kit.BasicServicesKit'; 9 | import { LiveDataBus } from '@android_x/utilcode/src/main/ets/LiveDataBus'; 10 | 11 | /** 12 | * @author Tanranran 13 | * @date 2024/5/15 23:25 14 | * @description 15 | */ 16 | @Component 17 | @Preview 18 | export struct AppUtilsFragment { 19 | @Consume(CommonConst.NavPathStack) pageInfos: NavPathStack 20 | @State listData: Array = [] 21 | @State title: string = "" 22 | @LifecycleEvent lifecycle: Lifecycle = new Lifecycle() 23 | 24 | async aboutToAppear(): Promise { 25 | this.listData.push(new CommonItemTitle('isAppDebug', AppUtils.isAppDebug() ? 'true' : 'false')) 26 | this.listData.push(new CommonItemTitle('packageName', AppUtils.getPackageName())) 27 | this.listData.push(new CommonItemTitle('AppName', AppUtils.getAppName())) 28 | this.listData.push(new CommonItemTitle('AppVersionName', AppUtils.getAppVersionName())) 29 | this.listData.push(new CommonItemTitle('AppVersionCode', `${AppUtils.getAppVersionCode()}`)) 30 | this.lifecycle.addObserver((state: LifecycleState) => { 31 | console.log("状态" + state) 32 | }) 33 | } 34 | 35 | build() { 36 | BaseFragmentComp({ title: this.title, listData: this.listData }) 37 | } 38 | 39 | aboutToDisappear(): void { 40 | LiveDataBus.removeObserve("test") 41 | } 42 | } 43 | 44 | 45 | @Builder 46 | export function PageBuilder() { 47 | AppUtilsFragment() 48 | } 49 | -------------------------------------------------------------------------------- /example/src/main/ets/pages/ClipboardFragment.ets: -------------------------------------------------------------------------------- 1 | import { ClipboardUtil } from '@android_x/utilcode'; 2 | import CommonItemTitle from '../bean/CommonItemTitle'; 3 | import { BaseFragmentComp } from '../components/BaseFragmentComp'; 4 | import CommonConst from '../const/CommonConst'; 5 | 6 | /** 7 | * @author Tanranran 8 | * @date 2024/6/6 17:17 9 | * @description 10 | */ 11 | @Component 12 | @Preview 13 | export struct ClipboardFragment { 14 | @Consume(CommonConst.NavPathStack) pageInfos: NavPathStack 15 | @State listData: Array = [] 16 | @State title: string = "" 17 | 18 | async aboutToAppear(): Promise { 19 | this.listData.push(new CommonItemTitle('剪切板中是否有数据', '', '', () => { 20 | console.log(ClipboardUtil.hasData() + "") 21 | })) 22 | this.listData.push(new CommonItemTitle('获取剪切板数据', '', '', () => { 23 | console.log(ClipboardUtil.getText()?.getPrimaryText()) 24 | })) 25 | this.listData.push(new CommonItemTitle('剪切板写入数据', '', '', () => { 26 | ClipboardUtil.setText(Date.now() + "") 27 | })) 28 | } 29 | 30 | build() { 31 | BaseFragmentComp({ title: this.title, listData: this.listData }) 32 | } 33 | } 34 | 35 | @Builder 36 | export function PageBuilder() { 37 | ClipboardFragment() 38 | } -------------------------------------------------------------------------------- /example/src/main/ets/pages/DeviceFragment.ets: -------------------------------------------------------------------------------- 1 | import { DeviceUtils, JsonUtils } from '@android_x/utilcode'; 2 | import CommonItemTitle from '../bean/CommonItemTitle'; 3 | import CommonConst from '../const/CommonConst'; 4 | import { BaseFragmentComp } from '../components/BaseFragmentComp'; 5 | import wifiManager from '@ohos.wifiManager'; 6 | import { distributedDeviceManager } from '@kit.DistributedServiceKit'; 7 | /** 8 | * @author Tanranran 9 | * @date 2024/5/13 22:25 10 | * @description 11 | */ 12 | @Component 13 | export struct DeviceFragment { 14 | @Consume(CommonConst.NavPathStack) pageInfos: NavPathStack 15 | @State listData: Array = [] 16 | @State title: string = "" 17 | msgHistory: string = "" 18 | 19 | async aboutToAppear(): Promise { 20 | try { 21 | let dmInstance = distributedDeviceManager.createDeviceManager('ohos.samples.jsHelloWorld'); 22 | let deviceId: string = dmInstance.getLocalDeviceId(); 23 | console.log('local device id: ' + JSON.stringify(deviceId)); 24 | } catch (e) { 25 | console.error(e) 26 | } 27 | this.listData.push(new CommonItemTitle('设备ID-getDeviceId', await DeviceUtils.getDeviceId())) 28 | this.listData.push(new CommonItemTitle('设备AAID-getAAID', await DeviceUtils.getAAID())) 29 | this.listData.push(new CommonItemTitle('设备类型-getDeviceType', `${DeviceUtils.getDeviceType()}`)) 30 | this.listData.push(new CommonItemTitle('是否是平板-isTabletDevice', 31 | DeviceUtils.isTabletDevice() ? '是平板' : '不是平板')) 32 | this.listData.push(new CommonItemTitle('手机型号-getDeviceModel', DeviceUtils.getDeviceModel())) 33 | this.listData.push(new CommonItemTitle('设备外部产品系列-getDeviceMarketName', DeviceUtils.getDeviceMarketName())) 34 | this.listData.push(new CommonItemTitle('设备品牌名称-getDeviceBrand', DeviceUtils.getDeviceBrand())) 35 | this.listData.push(new CommonItemTitle('设备设备厂家名称-getManufacturer', DeviceUtils.getManufacturer())) 36 | this.listData.push(new CommonItemTitle('设备系统版本号-getDeviceVersionCode', DeviceUtils.getDeviceVersionCode())) 37 | this.listData.push(new CommonItemTitle('设备系统版本-getSdkApiVersion', DeviceUtils.getSdkApiVersion())) 38 | this.listData.push(new CommonItemTitle('设备型号-getDeviceModelName', DeviceUtils.getDeviceModelName())) 39 | this.listData.push(new CommonItemTitle('系统区域-getLanguage', DeviceUtils.getLanguage())) 40 | 41 | let ipInfo = wifiManager.getIpInfo(); 42 | 43 | let ipAddr = this.getIpAddrFromNum(ipInfo.ipAddress) 44 | this.msgHistory += `IP地址: ${ipAddr}\r\n`; 45 | 46 | let gateAddr = this.getIpAddrFromNum(ipInfo.gateway) 47 | this.msgHistory += `网关地址: ${gateAddr}\r\n`; 48 | 49 | let maskAddr = this.getIpAddrFromNum(ipInfo.netmask) 50 | this.msgHistory += `子网掩码: ${maskAddr}\r\n`; 51 | 52 | let dnsAddr = this.getIpAddrFromNum(ipInfo.primaryDns) 53 | this.msgHistory += `DNS服务器: ${dnsAddr}\r\n`; 54 | 55 | let dhcpServer = this.getIpAddrFromNum(ipInfo.serverIp) 56 | this.msgHistory += `DHCP服务器: ${dhcpServer}\r\n`; 57 | 58 | this.msgHistory += `租用时长: ${ipInfo.leaseDuration}\r\n`; 59 | 60 | wifiManager.getLinkedInfo() 61 | .then((linkedInfo) => { 62 | console.log("WIFI信息"+JsonUtils.bean2Json(linkedInfo)) 63 | let len = linkedInfo.ssid.length 64 | let ssid = linkedInfo.ssid.substring(1, len - 1) 65 | this.msgHistory += `SSID: ${ssid}\r\n`; 66 | this.msgHistory += `信号强度: ${linkedInfo.rssi}\r\n`; 67 | this.msgHistory += `网络频段: ${linkedInfo.band}\r\n`; 68 | this.msgHistory += `链接速度: ${linkedInfo.linkSpeed}\r\n`; 69 | this.msgHistory += `网络频率: ${linkedInfo.frequency}\r\n`; 70 | this.msgHistory += `MAC地址: ${linkedInfo.macAddress}\r\n`; 71 | console.log(this.msgHistory) 72 | } 73 | ) 74 | } 75 | 76 | //根据数字形式的IP地址获取字符串形式的IP地址 77 | getIpAddrFromNum(ipNum: number): string { 78 | return (ipNum >>> 24) + '.' + (ipNum >> 16 & 0xFF) + '.' + (ipNum >> 8 & 0xFF) + '.' + (ipNum & 0xFF); 79 | } 80 | 81 | build() { 82 | BaseFragmentComp({ title: this.title, listData: this.listData }) 83 | } 84 | } 85 | @Builder 86 | export function PageBuilder() { 87 | DeviceFragment() 88 | } -------------------------------------------------------------------------------- /example/src/main/ets/pages/DialogFragment.ets: -------------------------------------------------------------------------------- 1 | import { BaseCustomDialog, PromptActionUtils } from '@android_x/utilcode' 2 | /** 3 | * @author Tanranran 4 | * @date 2024/8/5 18:49 5 | * @description 6 | */ 7 | import CommonItemTitle from '../bean/CommonItemTitle' 8 | import { BaseFragmentComp } from '../components/BaseFragmentComp' 9 | 10 | @Component 11 | @Preview 12 | export struct DialogFragment { 13 | @State listData: Array = [] 14 | customerDialog?: CustomerDialog 15 | 16 | aboutToAppear() { 17 | this.listData.push(new CommonItemTitle('弹起自定义弹框', '', '', () => { 18 | this.customerDialog = new CustomerDialog() 19 | this.customerDialog?.show() 20 | })) 21 | 22 | this.listData.push(new CommonItemTitle('弹起自定义弹框', '', '', () => { 23 | this.customerDialog?.close() 24 | })) 25 | } 26 | 27 | build() { 28 | BaseFragmentComp({ listData: this.listData }) 29 | } 30 | } 31 | 32 | export class CustomerDialog extends BaseCustomDialog { 33 | progress: number = 0 34 | 35 | show() { 36 | super.show(wrapBuilder(CustomerDialogBuilder), this) 37 | let intervalId = setInterval(() => { 38 | this.progress += 5; 39 | if (this.progress >= 100) { 40 | this.progress = 100; 41 | clearInterval(intervalId) 42 | } 43 | this.updateProgress(this.progress) 44 | }, 1000); 45 | } 46 | 47 | updateProgress(progress: number) { 48 | this.progress = progress 49 | this.update(this) 50 | } 51 | } 52 | 53 | @Component 54 | export struct CustomerDialogComponent { 55 | @Prop options: CustomerDialog 56 | 57 | build() { 58 | Column({ space: '10vp' }) { 59 | Stack() { 60 | Progress({ value: this.options.progress, total: 100, type: ProgressType.Ring }) 61 | .width('100%') 62 | .height('100%') 63 | .color(Color.White) 64 | .backgroundColor('#33ffffff') 65 | .style({ 66 | strokeWidth: 3, 67 | enableSmoothEffect: true, 68 | }) 69 | 70 | Text(`${this.options.progress}%`) 71 | .width('100%') 72 | .height('100%') 73 | .fontSize(13) 74 | .fontColor(Color.White) 75 | .textAlign(TextAlign.Center) 76 | } 77 | .width(50) 78 | .height(50) 79 | .margin({ 80 | top: 20 81 | }) 82 | 83 | Text('正在处理中') 84 | .fontColor(Color.White) 85 | .textAlign(TextAlign.Center) 86 | .margin({ 87 | top: 15 88 | }) 89 | .fontSize(12) 90 | 91 | Text('请不要退出哦') 92 | .fontColor('#80FFFFFF') 93 | .textAlign(TextAlign.Center) 94 | .margin({ 95 | top: 10, 96 | bottom: 20 97 | }) 98 | .fontSize(12) 99 | } 100 | .width(150) 101 | .justifyContent(FlexAlign.Center) 102 | .alignItems(HorizontalAlign.Center) 103 | .backgroundColor('#CC000000') 104 | .borderRadius(10) 105 | } 106 | } 107 | 108 | @Builder 109 | function CustomerDialogBuilder(options: object) { 110 | CustomerDialogComponent({ options: options as CustomerDialog }) 111 | } 112 | 113 | @Builder 114 | export function PageBuilder() { 115 | DialogFragment() 116 | } -------------------------------------------------------------------------------- /example/src/main/ets/pages/ImageFragment.ets: -------------------------------------------------------------------------------- 1 | import { 2 | ImageCompressOptions, 3 | ImageMimeType, 4 | ImageUtils, 5 | JsonUtils, 6 | Permission, 7 | PermissionUtils, 8 | PickerHelper, 9 | PickerOptions, 10 | ResourceUtils, 11 | ToastUtils 12 | } from '@android_x/utilcode' 13 | import { BaseFragmentComp } from '../components/BaseFragmentComp' 14 | import { AlbumComponent } from '../components/AlbumComponent'; 15 | import { CameraComponent } from '../components/CameraComponent'; 16 | 17 | @Component 18 | @Preview 19 | export struct ImageFragment { 20 | @State compressedImageSrc: string | Resource | PixelMap = ''; // 压缩后的图片路径 21 | @State beforeCompressionSize: string = ''; // 压缩前的图片大小,单位kb 22 | @State afterCompressionSize: string = ''; // 压缩后的图片大小,单位kb 23 | @State strMaxCompressedImageSize: string = ''; // 指定图片压缩目标大小,string类型 24 | private maxCompressedImageSize: number = 0; // 指定图片压缩目标大小,单位kb 25 | private imageUint8Array?: ArrayBuffer 26 | 27 | aboutToAppear(): void { 28 | ResourceUtils.getRawFileContent('image_compression_before.jpeg').then(fileData => { 29 | if (fileData) { 30 | this.imageUint8Array = fileData.buffer.slice(0) 31 | let imageByteLength = fileData.byteLength; 32 | this.beforeCompressionSize = (imageByteLength / 1000).toFixed(1); 33 | } 34 | }) 35 | 36 | PermissionUtils.with().request(Permission.CAMERA).then(res => { 37 | if (res) { 38 | ToastUtils.show("申请成功") 39 | } 40 | }) 41 | } 42 | 43 | build() { 44 | BaseFragmentComp() { 45 | Tabs({ barPosition: BarPosition.End }) { 46 | this.compressContent() 47 | 48 | TabContent() { 49 | Column() { 50 | AlbumComponent() 51 | } 52 | .width('100%').height('100%') 53 | }.tabBar('自定义相册') 54 | 55 | TabContent() { 56 | Column() { 57 | CameraComponent() 58 | } 59 | .width('100%').height('100%') 60 | }.tabBar('自定义相机') 61 | } 62 | } 63 | } 64 | 65 | @Builder 66 | compressContent() { 67 | TabContent() { 68 | Scroll() { 69 | Column() { 70 | Row() { 71 | Text('压缩前图片大小(kb):') 72 | .fontSize(20) 73 | Text(this.beforeCompressionSize) 74 | .fontSize(20) 75 | } 76 | 77 | Image($rawfile('image_compression_before.jpeg')) 78 | .width('100%') 79 | .height('30%') 80 | 81 | Row() { 82 | Text('压缩后图片大小(kb):') 83 | .fontSize(20) 84 | Text(this.afterCompressionSize) 85 | .fontSize(20) 86 | } 87 | 88 | Image(this.compressedImageSrc) 89 | .objectFit(ImageFit.ScaleDown) 90 | .width('100%') 91 | .height('30%') 92 | 93 | Row() { 94 | Button('压缩图片').onClick(() => { 95 | let json = 96 | '[{"tab_id":"193","show_name":"户外服饰","relation_id":"191,193"},{"tab_id":"197","show_name":"体育项目","relation_id":"191,197"},{"tab_id":"1521","show_name":"户外鞋袜","relation_id":"191,1521"}]' 97 | console.log('测试数据'+JsonUtils.json2Array(Relation,json)) 98 | // return 99 | // if (this.imageUint8Array) { 100 | // let options = new ImageCompressOptions() 101 | // options.maxWidth = 400 102 | // options.maxHeight = 400 103 | // options.format = ImageMimeType.JPG 104 | // options.quality = 100 105 | // ImageUtils.compressedImageAsync(this.imageUint8Array, options).then(res => { 106 | // if (res && res.pixelMap) { 107 | // this.compressedImageSrc = res.pixelMap 108 | // this.afterCompressionSize = (((res.compressImageInfo?.size ?? 0) / 1000).toFixed(1)) + 'kb'; 109 | // } 110 | // }) 111 | // } 112 | }) 113 | SaveButton({ 114 | icon: SaveIconStyle.FULL_FILLED, 115 | text: SaveDescription.SAVE_IMAGE, 116 | buttonType: ButtonType.Capsule 117 | }) 118 | .onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => { 119 | if (result == SaveButtonOnClickResult.SUCCESS) { 120 | let saveResult = await ImageUtils.saveImageToPhotoAlbum(this.imageUint8Array) 121 | if (saveResult > 0) { 122 | ToastUtils.show("保存成功") 123 | } 124 | } else { 125 | ToastUtils.show('未获取到临时写入权限') 126 | } 127 | }) 128 | Button('唤起相册').onClick(() => { 129 | let options = new PickerOptions() 130 | options.compressOptions = new ImageCompressOptions() 131 | options.compressOptions.maxSize = 0.01 * 1000 * 1000 132 | PickerHelper.selectPhoto(options).then(async resList => { 133 | if (resList && resList.length > 0) { 134 | let res = resList[0] 135 | if (res.pixelMap) { 136 | this.compressedImageSrc = res.pixelMap 137 | let imageBase64 = await ImageUtils.pixelMapToBase64Str(res.pixelMap) 138 | console.log('图片base64:' + imageBase64) 139 | this.afterCompressionSize = (((res.compressImageInfo?.size ?? 0) / 1000).toFixed(1)) + 'kb'; 140 | } 141 | } 142 | }) 143 | }) 144 | Button('唤起相机').onClick(() => { 145 | let pickerOptions = new PickerOptions() 146 | pickerOptions.compressOptions = new ImageCompressOptions() 147 | pickerOptions.compressOptions.maxWidth = 1024 148 | pickerOptions.compressOptions.maxHeight = 1024 149 | pickerOptions.compressOptions.maxSize = 1 * 1000 * 1000 150 | PickerHelper.takePhoto(pickerOptions).then(res => { 151 | if (res && res.pixelMap) { 152 | this.compressedImageSrc = res.pixelMap 153 | this.afterCompressionSize = (((res.compressImageInfo?.size ?? 0) / 1000).toFixed(1)) + 'kb'; 154 | } 155 | }) 156 | }) 157 | } 158 | } 159 | .width('100%') 160 | } 161 | .width('100%').height('100%') 162 | }.tabBar('压缩图片') 163 | } 164 | } 165 | 166 | class Relation{ 167 | tab_id?:string 168 | show_name?:number 169 | relation_id?:number 170 | } 171 | 172 | @Builder 173 | export function PageBuilder() { 174 | ImageFragment() 175 | } -------------------------------------------------------------------------------- /example/src/main/ets/pages/Index.ets: -------------------------------------------------------------------------------- 1 | import { DeviceFragment } from './DeviceFragment'; 2 | import CommonConst from '../const/CommonConst'; 3 | import CommonItemTitle from '../bean/CommonItemTitle'; 4 | import { AppUtilsFragment } from './AppUtilsFragment'; 5 | import { ClipboardFragment } from './ClipboardFragment'; 6 | import { LiveDataBusFragment } from './LiveDataBusFragment'; 7 | import { ImageFragment } from './ImageFragment'; 8 | import { PermissionFragment } from './PermissionFragment'; 9 | import { DialogFragment } from './DialogFragment'; 10 | import { ListSortFragment } from './ListSortFragment'; 11 | import { webview } from '@kit.ArkWeb'; 12 | 13 | @Entry 14 | @Component 15 | struct Index { 16 | @Provide(CommonConst.NavPathStack) pathStack: NavPathStack = new NavPathStack(); 17 | @State listData: Array = [] 18 | 19 | aboutToAppear(): void { 20 | this.listData = [] 21 | this.listData.push(new CommonItemTitle('DeviceUtils Demo','', DeviceFragment.name)) 22 | this.listData.push(new CommonItemTitle('AppUtils Demo','', AppUtilsFragment.name)) 23 | this.listData.push(new CommonItemTitle('Clipboard Demo','', ClipboardFragment.name)) 24 | this.listData.push(new CommonItemTitle('EventBus Demo','', LiveDataBusFragment.name)) 25 | this.listData.push(new CommonItemTitle('Image Demo','', ImageFragment.name)) 26 | this.listData.push(new CommonItemTitle('Permission Demo','', PermissionFragment.name)) 27 | this.listData.push(new CommonItemTitle('Dialog Demo','', DialogFragment.name)) 28 | this.listData.push(new CommonItemTitle('ListSort Demo','', ListSortFragment.name)) 29 | } 30 | 31 | 32 | build() { 33 | Navigation(this.pathStack) { 34 | Stack(){ 35 | List({ space: 10, initialIndex: 0 }) { 36 | ForEach(this.listData, (item: CommonItemTitle) => { 37 | ListItem() { 38 | Button(item.title).width('100%') 39 | }.padding({ left: 12, right: 12 }) 40 | .onClick(() => { 41 | this.pathStack.pushDestinationByName(item.pageName,item) 42 | }) 43 | }, (item: CommonItemTitle) => item.title) 44 | } 45 | .height('100%') 46 | .width("100%") 47 | .listDirection(Axis.Vertical) 48 | } 49 | } 50 | .title("主页", { 51 | backgroundColor: Color.Transparent, 52 | backgroundBlurStyle: BlurStyle.BACKGROUND_THIN 53 | }) 54 | .mode(NavigationMode.Stack) 55 | .titleMode(NavigationTitleMode.Mini) 56 | .hideBackButton(true) 57 | .width('100%') 58 | .height('100%') 59 | } 60 | } -------------------------------------------------------------------------------- /example/src/main/ets/pages/LifecycleFragment.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/6/7 10:21 4 | * @description 5 | */ 6 | class LifecycleFragment { 7 | } 8 | -------------------------------------------------------------------------------- /example/src/main/ets/pages/ListSortFragment.ets: -------------------------------------------------------------------------------- 1 | import { PermissionUtils } from '@android_x/utilcode' 2 | /** 3 | * @author Tanranran 4 | * @date 2024/7/31 16:25 5 | * @description 6 | */ 7 | import CommonItemTitle from '../bean/CommonItemTitle' 8 | import { BaseFragmentComp } from '../components/BaseFragmentComp' 9 | import CommonConst from '../const/CommonConst' 10 | import { IntentUtils } from '@android_x/utilcode/src/main/ets/IntentUtils' 11 | import { componentSnapshot, ImageModifier } from '@kit.ArkUI' 12 | import { image } from '@kit.ImageKit' 13 | 14 | @Component 15 | @Preview 16 | export struct ListSortFragment { 17 | @Consume(CommonConst.NavPathStack) pageInfos: NavPathStack 18 | @State listData: Array = [] 19 | @State title: string = "" 20 | @State dragIndex: number = 0; 21 | @State myModifier: ImageAttribute = new ImageModifier().scale({x:1.5,y:1.5}) 22 | 23 | aboutToAppear() { 24 | for (let i = 0; i < 100; i++) { 25 | this.listData.push(new CommonItemTitle(i + '', i + '')) 26 | } 27 | } 28 | 29 | build() { 30 | BaseFragmentComp({ title: this.title }) { 31 | List() { 32 | ForEach(this.listData, (item: CommonItemTitle, index) => { 33 | ListItem() { 34 | ListSortItem({ item: item }) 35 | } 36 | // .onPreDrag((status: PreDragStatus) => { 37 | // if (status == PreDragStatus.PREVIEW_LIFT_STARTED) { 38 | // item.visible = false; 39 | // }else if(status == PreDragStatus.PREVIEW_LANDING_FINISHED){ 40 | // item.visible = true; 41 | // } 42 | // }) 43 | .onDragStart(() => { 44 | item.visible = false; // 拖拽时,设置子组件原位置图标不可见 45 | }) 46 | // .dragPreviewOptions({ 47 | // mode: [DragPreviewMode.ENABLE_DEFAULT_SHADOW], 48 | // }, { defaultAnimationBeforeLifting: true }) 49 | .dragPreview(this.dragPreviewBuilder) 50 | .onDragEnd(() => { 51 | item.visible = true; 52 | }) 53 | .onTouch((event: TouchEvent) => { // 拖拽释放时,记录目标位置子组件index值 54 | if (event.type === TouchType.Down) { 55 | this.dragIndex = index; 56 | } 57 | }) 58 | }, (item: CommonItemTitle) => item.title) 59 | } 60 | .divider({ strokeWidth: 10, color: Color.Green}) 61 | .height('100%') 62 | .width("100%") 63 | .listDirection(Axis.Horizontal) 64 | .onDrop((event: DragEvent, extraParams: string) => { // TODO:知识点:在List层,通过onDrop实现拖拽结束后的回调行为 65 | console.log('测试数据' + extraParams) 66 | // let jsonString: JsonObjType = JSON.parse(extraParams) as JsonObjType; // 通过参数extraParams获取原位置子组件index值 67 | // this.changeIndex(this.dragIndex, jsonString.insertIndex); // 互换子组件index值 68 | }) 69 | } 70 | } 71 | 72 | @Builder 73 | dragPreviewBuilder() { 74 | Column() { 75 | Text("dragPreview") 76 | .width(80) 77 | .height(80) 78 | .fontSize(20) 79 | .borderRadius(10) 80 | .textAlign(TextAlign.Center) 81 | .fontColor(Color.Black) 82 | .backgroundColor(Color.Pink) 83 | } 84 | } 85 | } 86 | 87 | @Component 88 | struct ListSortItem { 89 | @ObjectLink item: CommonItemTitle 90 | 91 | build() { 92 | Column() { 93 | Text(this.item.title).fontWeight(FontWeight.Bold) 94 | Text(this.item.content).margin(12) 95 | } 96 | .visibility(this.item.visible ? Visibility.Visible : Visibility.Hidden) 97 | .onClick(() => { 98 | if (this.item.callback) { 99 | this.item.callback() 100 | } 101 | }) 102 | .padding(12) 103 | .constraintSize({ 104 | minHeight: 60 105 | }) 106 | .alignItems(HorizontalAlign.Start) 107 | .justifyContent(FlexAlign.Center) 108 | .borderRadius(3) 109 | .aspectRatio(1) 110 | .height(50) 111 | .backgroundColor(Color.Red) 112 | } 113 | } 114 | 115 | @Builder 116 | export function PageBuilder() { 117 | ListSortFragment() 118 | } -------------------------------------------------------------------------------- /example/src/main/ets/pages/LiveDataBusFragment.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/7/10 11:07 4 | * @description 5 | */ 6 | import CommonItemTitle from '../bean/CommonItemTitle' 7 | import { BaseFragmentComp } from '../components/BaseFragmentComp' 8 | import { emitter } from '@kit.BasicServicesKit' 9 | import { LiveDataBus, Lifecycle, LifecycleEvent } from '@android_x/utilcode' 10 | 11 | @Component 12 | @Preview 13 | export struct LiveDataBusFragment { 14 | @State listData: Array = [] 15 | @LifecycleEvent lifecycle: Lifecycle = new Lifecycle() 16 | private eventCallback: (data: string) => void = (data: string) => { 17 | console.log("收到了事件A" + data) 18 | } 19 | 20 | aboutToAppear() { 21 | this.listData.push(new CommonItemTitle('发送Event事件', '', '', () => { 22 | LiveDataBus.post("test", "我是事件消息") 23 | })) 24 | LiveDataBus.observe("test", this.eventCallback, this.lifecycle) 25 | LiveDataBus.observe("test", (data: string) => { 26 | console.log("收到了推送B" + data) 27 | }, this.lifecycle) 28 | } 29 | 30 | build() { 31 | BaseFragmentComp({ listData: this.listData }) 32 | } 33 | } 34 | 35 | @Builder 36 | export function PageBuilder() { 37 | LiveDataBusFragment() 38 | } -------------------------------------------------------------------------------- /example/src/main/ets/pages/PermissionFragment.ets: -------------------------------------------------------------------------------- 1 | import { PermissionUtils, ToastUtils } from '@android_x/utilcode' 2 | /** 3 | * @author Tanranran 4 | * @date 2024/7/31 16:25 5 | * @description 6 | */ 7 | import CommonItemTitle from '../bean/CommonItemTitle' 8 | import { BaseFragmentComp } from '../components/BaseFragmentComp' 9 | import CommonConst from '../const/CommonConst' 10 | import { IntentUtils } from '@android_x/utilcode/src/main/ets/IntentUtils' 11 | import { abilityAccessCtrl, Context, common, Permissions } from '@kit.AbilityKit'; 12 | import { BusinessError } from '@kit.BasicServicesKit'; 13 | 14 | @Component 15 | @Preview 16 | export struct PermissionFragment { 17 | @Consume(CommonConst.NavPathStack) pageInfos: NavPathStack 18 | @State listData: Array = [] 19 | @State title: string = "" 20 | @State havePermissions: boolean = false 21 | 22 | async aboutToAppear(): Promise { 23 | this.havePermissions = 24 | await PermissionUtils.hasPermissions('ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION') 25 | this.listData.push(new CommonItemTitle(`是否有定位权限${this.havePermissions}`, '', '')) 26 | 27 | this.listData.push(new CommonItemTitle('申请定位权限', '', '', () => { 28 | PermissionUtils.with().request('ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION').then(res => { 29 | if (res.allGranted) { 30 | ToastUtils.show('已授权A') 31 | this.havePermissions = true 32 | } else { 33 | // IntentUtils.startAppSettings() 34 | res.requestPermissionOnSetting?.().then(resSetting => { 35 | if (resSetting.allGranted) { 36 | ToastUtils.show('已再次授权A') 37 | this.havePermissions = true 38 | } else { 39 | ToastUtils.show('已拒绝B') 40 | } 41 | }) 42 | } 43 | }) 44 | })) 45 | } 46 | 47 | build() { 48 | BaseFragmentComp({ title: this.title, listData: this.listData }) 49 | } 50 | } 51 | 52 | 53 | @Builder 54 | export function PageBuilder() { 55 | PermissionFragment() 56 | } -------------------------------------------------------------------------------- /example/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "example", 4 | "type": "entry", 5 | "description": "$string:module_desc", 6 | "mainElement": "EntryAbility", 7 | "deviceTypes": [ 8 | "phone", 9 | "tablet", 10 | "2in1" 11 | ], 12 | "deliveryWithInstall": true, 13 | "installationFree": false, 14 | "pages": "$profile:main_pages", 15 | "routerMap": "$profile:route_map", 16 | "abilities": [ 17 | { 18 | "name": "EntryAbility", 19 | "srcEntry": "./ets/entryability/EntryAbility.ets", 20 | "description": "$string:EntryAbility_desc", 21 | "icon": "$media:layered_image", 22 | "label": "$string:EntryAbility_label", 23 | "startWindowIcon": "$media:foreground", 24 | "startWindowBackground": "$color:start_window_background", 25 | "exported": true, 26 | "orientation": "follow_desktop", 27 | "skills": [ 28 | { 29 | "entities": [ 30 | "entity.system.home" 31 | ], 32 | "actions": [ 33 | "action.system.home" 34 | ] 35 | } 36 | ] 37 | } 38 | ], 39 | "requestPermissions": [ 40 | { 41 | "name": "ohos.permission.STORE_PERSISTENT_DATA" 42 | }, 43 | { 44 | "name": "ohos.permission.GET_WIFI_INFO" 45 | }, 46 | { 47 | "name": "ohos.permission.DISTRIBUTED_DATASYNC", 48 | "reason": "$string:app_name", 49 | "usedScene": { 50 | "abilities": [ 51 | "EntryAbility" 52 | ], 53 | "when": "inuse" 54 | } 55 | }, 56 | { 57 | "name" : "ohos.permission.CAMERA", 58 | "reason": "$string:descriptionCamera", 59 | "usedScene": { 60 | "abilities": [ 61 | "EntryAbility" 62 | ], 63 | "when":"always" 64 | } 65 | }, 66 | { 67 | "name" : "ohos.permission.MICROPHONE", 68 | "reason": "$string:descriptionCamera", 69 | "usedScene": { 70 | "abilities": [ 71 | "EntryAbility" 72 | ], 73 | "when":"always" 74 | } 75 | }, 76 | { 77 | "name" : "ohos.permission.LOCATION", 78 | "reason": "$string:descriptionLocation", 79 | "usedScene": { 80 | "abilities": [ 81 | "EntryAbility" 82 | ], 83 | "when":"always" 84 | } 85 | }, 86 | { 87 | "name" : "ohos.permission.APPROXIMATELY_LOCATION", 88 | "reason": "$string:descriptionLocation", 89 | "usedScene": { 90 | "abilities": [ 91 | "EntryAbility" 92 | ], 93 | "when":"always" 94 | } 95 | } 96 | ] 97 | } 98 | } -------------------------------------------------------------------------------- /example/src/main/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#FFFFFF" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /example/src/main/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_desc", 5 | "value": "module description" 6 | }, 7 | { 8 | "name": "EntryAbility_desc", 9 | "value": "description" 10 | }, 11 | { 12 | "name": "EntryAbility_label", 13 | "value": "$string:app_name" 14 | },{ 15 | "name": "reason_app_read_pasteboard", 16 | "value": "为了获取剪切板数据" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /example/src/main/resources/base/media/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanranran/HarmonyUtilCode/9751d1af45ed7c847e636f760f9c38168f1e479a/example/src/main/resources/base/media/background.png -------------------------------------------------------------------------------- /example/src/main/resources/base/media/foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanranran/HarmonyUtilCode/9751d1af45ed7c847e636f760f9c38168f1e479a/example/src/main/resources/base/media/foreground.png -------------------------------------------------------------------------------- /example/src/main/resources/base/media/layered_image.json: -------------------------------------------------------------------------------- 1 | { 2 | "layered-image": 3 | { 4 | "background" : "$media:background", 5 | "foreground" : "$media:foreground" 6 | } 7 | } -------------------------------------------------------------------------------- /example/src/main/resources/base/media/startIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanranran/HarmonyUtilCode/9751d1af45ed7c847e636f760f9c38168f1e479a/example/src/main/resources/base/media/startIcon.png -------------------------------------------------------------------------------- /example/src/main/resources/base/profile/main_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "pages/Index" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /example/src/main/resources/base/profile/route_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "routerMap": [ 3 | { 4 | "name": "AppUtilsFragment", 5 | "pageSourceFile": "src/main/ets/pages/AppUtilsFragment.ets", 6 | "buildFunction": "PageBuilder", 7 | "data": { 8 | "description": "this is AppUtilsFragment" 9 | } 10 | }, 11 | { 12 | "name": "DeviceFragment", 13 | "pageSourceFile": "src/main/ets/pages/DeviceFragment.ets", 14 | "buildFunction": "PageBuilder", 15 | "data": { 16 | "description": "this is DeviceFragment" 17 | } 18 | }, 19 | { 20 | "name": "ClipboardFragment", 21 | "pageSourceFile": "src/main/ets/pages/ClipboardFragment.ets", 22 | "buildFunction": "PageBuilder", 23 | "data": { 24 | "description": "this is ClipboardFragment" 25 | } 26 | }, 27 | { 28 | "name": "EventBusFragment", 29 | "pageSourceFile": "src/main/ets/pages/LiveDataBusFragment.ets", 30 | "buildFunction": "PageBuilder", 31 | "data": { 32 | "description": "this is EventBusFragment" 33 | } 34 | }, 35 | { 36 | "name": "ImageFragment", 37 | "pageSourceFile": "src/main/ets/pages/ImageFragment.ets", 38 | "buildFunction": "PageBuilder", 39 | "data": { 40 | "description": "this is ImageFragment" 41 | } 42 | }, 43 | { 44 | "name": "PermissionFragment", 45 | "pageSourceFile": "src/main/ets/pages/PermissionFragment.ets", 46 | "buildFunction": "PageBuilder", 47 | "data": { 48 | "description": "this is PermissionFragment" 49 | } 50 | }, 51 | { 52 | "name": "DialogFragment", 53 | "pageSourceFile": "src/main/ets/pages/DialogFragment.ets", 54 | "buildFunction": "PageBuilder", 55 | "data": { 56 | "description": "this is DialogFragment" 57 | } 58 | }, 59 | { 60 | "name": "ListSortFragment", 61 | "pageSourceFile": "src/main/ets/pages/ListSortFragment.ets", 62 | "buildFunction": "PageBuilder", 63 | "data": { 64 | "description": "this is ListSortFragment" 65 | } 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /example/src/main/resources/rawfile/image_compression_before.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanranran/HarmonyUtilCode/9751d1af45ed7c847e636f760f9c38168f1e479a/example/src/main/resources/rawfile/image_compression_before.jpeg -------------------------------------------------------------------------------- /example/src/mock/mock-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /example/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }) 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }) 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }) 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }) 25 | it('assertContain', 0, () => { 26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); 28 | let a = 'abc'; 29 | let b = 'b'; 30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 31 | expect(a).assertContain(b); 32 | expect(a).assertEqual(a); 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /example/src/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test'; 2 | 3 | export default function testsuite() { 4 | abilityTest(); 5 | } -------------------------------------------------------------------------------- /example/src/ohosTest/ets/testability/TestAbility.ets: -------------------------------------------------------------------------------- 1 | import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 2 | import { abilityDelegatorRegistry } from '@kit.TestKit'; 3 | import { hilog } from '@kit.PerformanceAnalysisKit'; 4 | import { window } from '@kit.ArkUI'; 5 | import { Hypium } from '@ohos/hypium'; 6 | import testsuite from '../test/List.test'; 7 | 8 | export default class TestAbility extends UIAbility { 9 | onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 10 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); 11 | hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); 12 | hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? ''); 13 | let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; 14 | abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); 15 | let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; 16 | abilityDelegatorArguments = abilityDelegatorRegistry.getArguments(); 17 | hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); 18 | Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite); 19 | } 20 | 21 | onDestroy() { 22 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); 23 | } 24 | 25 | onWindowStageCreate(windowStage: window.WindowStage) { 26 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); 27 | windowStage.loadContent('testability/pages/Index', (err) => { 28 | if (err.code) { 29 | hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 30 | return; 31 | } 32 | hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); 33 | }); 34 | } 35 | 36 | onWindowStageDestroy() { 37 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); 38 | } 39 | 40 | onForeground() { 41 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); 42 | } 43 | 44 | onBackground() { 45 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); 46 | } 47 | } -------------------------------------------------------------------------------- /example/src/ohosTest/ets/testability/pages/Index.ets: -------------------------------------------------------------------------------- 1 | @Entry 2 | @Component 3 | struct Index { 4 | @State message: string = 'Hello World'; 5 | 6 | build() { 7 | Row() { 8 | Column() { 9 | Text(this.message) 10 | .fontSize(50) 11 | .fontWeight(FontWeight.Bold) 12 | } 13 | .width('100%') 14 | } 15 | .height('100%') 16 | } 17 | } -------------------------------------------------------------------------------- /example/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets: -------------------------------------------------------------------------------- 1 | import { abilityDelegatorRegistry, TestRunner } from '@kit.TestKit'; 2 | import { UIAbility, Want } from '@kit.AbilityKit'; 3 | import { BusinessError } from '@kit.BasicServicesKit'; 4 | import { hilog } from '@kit.PerformanceAnalysisKit'; 5 | import { resourceManager } from '@kit.LocalizationKit'; 6 | import { util } from '@kit.ArkTS'; 7 | 8 | let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; 9 | let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; 10 | let jsonPath: string = 'mock/mock-config.json'; 11 | let tag: string = 'testTag'; 12 | 13 | async function onAbilityCreateCallback(data: UIAbility) { 14 | hilog.info(0x0000, 'testTag', 'onAbilityCreateCallback, data: ${}', JSON.stringify(data)); 15 | } 16 | 17 | async function addAbilityMonitorCallback(err: BusinessError) { 18 | hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); 19 | } 20 | 21 | export default class OpenHarmonyTestRunner implements TestRunner { 22 | constructor() { 23 | } 24 | 25 | onPrepare() { 26 | hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare'); 27 | } 28 | 29 | async onRun() { 30 | let tag = 'testTag'; 31 | hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun run'); 32 | abilityDelegatorArguments = abilityDelegatorRegistry.getArguments() 33 | abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator() 34 | let moduleName = abilityDelegatorArguments.parameters['-m']; 35 | let context = abilityDelegator.getAppContext().getApplicationContext().createModuleContext(moduleName); 36 | let mResourceManager = context.resourceManager; 37 | await checkMock(abilityDelegator, mResourceManager); 38 | const bundleName = abilityDelegatorArguments.bundleName; 39 | const testAbilityName: string = 'TestAbility'; 40 | let lMonitor: abilityDelegatorRegistry.AbilityMonitor = { 41 | abilityName: testAbilityName, 42 | onAbilityCreate: onAbilityCreateCallback, 43 | moduleName: moduleName 44 | }; 45 | abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) 46 | const want: Want = { 47 | bundleName: bundleName, 48 | abilityName: testAbilityName, 49 | moduleName: moduleName 50 | }; 51 | abilityDelegator.startAbility(want, (err: BusinessError, data: void) => { 52 | hilog.info(0x0000, tag, 'startAbility : err : %{public}s', JSON.stringify(err) ?? ''); 53 | hilog.info(0x0000, tag, 'startAbility : data : %{public}s', JSON.stringify(data) ?? ''); 54 | }) 55 | hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun end'); 56 | } 57 | } 58 | 59 | async function checkMock(abilityDelegator: abilityDelegatorRegistry.AbilityDelegator, resourceManager: resourceManager.ResourceManager) { 60 | let rawFile: Uint8Array; 61 | try { 62 | rawFile = resourceManager.getRawFileContentSync(jsonPath); 63 | hilog.info(0x0000, tag, 'MockList file exists'); 64 | let mockStr: string = util.TextDecoder.create('utf-8', { ignoreBOM: true }).decodeWithStream(rawFile); 65 | let mockMap: Record = getMockList(mockStr); 66 | try { 67 | abilityDelegator.setMockList(mockMap) 68 | } catch (error) { 69 | let code = (error as BusinessError).code; 70 | let message = (error as BusinessError).message; 71 | hilog.error(0x0000, tag, `abilityDelegator.setMockList failed, error code: ${code}, message: ${message}.`); 72 | } 73 | } catch (error) { 74 | let code = (error as BusinessError).code; 75 | let message = (error as BusinessError).message; 76 | hilog.error(0x0000, tag, `ResourceManager:callback getRawFileContent failed, error code: ${code}, message: ${message}.`); 77 | } 78 | } 79 | 80 | function getMockList(jsonStr: string) { 81 | let jsonObj: Record = JSON.parse(jsonStr); 82 | let map: Map = new Map(Object.entries(jsonObj)); 83 | let mockList: Record = {}; 84 | map.forEach((value: object, key: string) => { 85 | let realValue: string = value['source'].toString(); 86 | mockList[key] = realValue; 87 | }); 88 | hilog.info(0x0000, tag, '%{public}s', 'mock-json value:' + JSON.stringify(mockList) ?? ''); 89 | return mockList; 90 | } -------------------------------------------------------------------------------- /example/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "example_test", 4 | "type": "feature", 5 | "description": "$string:module_test_desc", 6 | "mainElement": "TestAbility", 7 | "deviceTypes": [ 8 | "phone", 9 | "tablet", 10 | "2in1" 11 | ], 12 | "deliveryWithInstall": true, 13 | "installationFree": false, 14 | "pages": "$profile:test_pages", 15 | "abilities": [ 16 | { 17 | "name": "TestAbility", 18 | "srcEntry": "./ets/testability/TestAbility.ets", 19 | "description": "$string:TestAbility_desc", 20 | "icon": "$media:icon", 21 | "label": "$string:TestAbility_label", 22 | "exported": true, 23 | "startWindowIcon": "$media:icon", 24 | "startWindowBackground": "$color:start_window_background", 25 | "skills": [ 26 | { 27 | "actions": [ 28 | "action.system.home" 29 | ], 30 | "entities": [ 31 | "entity.system.home" 32 | ] 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /example/src/ohosTest/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#FFFFFF" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /example/src/ohosTest/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_test_desc", 5 | "value": "test ability description" 6 | }, 7 | { 8 | "name": "TestAbility_desc", 9 | "value": "the test ability" 10 | }, 11 | { 12 | "name": "TestAbility_label", 13 | "value": "test label" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /example/src/ohosTest/resources/base/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanranran/HarmonyUtilCode/9751d1af45ed7c847e636f760f9c38168f1e479a/example/src/ohosTest/resources/base/media/icon.png -------------------------------------------------------------------------------- /example/src/ohosTest/resources/base/profile/test_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "testability/pages/Index" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /example/src/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import localUnitTest from './LocalUnit.test'; 2 | 3 | export default function testsuite() { 4 | localUnitTest(); 5 | } -------------------------------------------------------------------------------- /example/src/test/LocalUnit.test.ets: -------------------------------------------------------------------------------- 1 | import { ObjectUtils } from '@android_x/utilcode'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 3 | 4 | export default function localUnitTest() { 5 | describe('localUnitTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }); 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }); 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }); 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }); 25 | it('assertContain', 0, () => { 26 | let values = ObjectUtils.values(ImageMimeType) 27 | console.log(JSON.stringify(values)) 28 | console.log(values.includes('image/jpeg') ? '是':'不是') 29 | console.log(values.includes('image/tiff') ? '是':'不是') 30 | }); 31 | }); 32 | } 33 | export enum ImageMimeType { 34 | JPG = 'image/jpeg', 35 | JPEG = 'image/jpeg', 36 | PNG = 'image/png', 37 | GIF = 'image/gif', 38 | WEBP = 'image/webp' 39 | } -------------------------------------------------------------------------------- /example/src/test/ThrottleDebounce.ets: -------------------------------------------------------------------------------- 1 | import { debounce, Debounce, throttle, Throttle } from '@android_x/utilcode'; 2 | 3 | 4 | /** 5 | * 限流和防抖函数使用例子 6 | * @author Tanranran 7 | * @date 2024/3/20 15:55 8 | * @description 9 | * 限流 (Throttling) 点击防抖用这个 10 | * 适用场景: 11 | 滚动事件处理:在滚动时按固定时间间隔处理事件,比如无限滚动加载内容。 12 | 监控浏览器的滚动位置:定期检查滚动位置来决定是否显示或隐藏页面上的元素。 13 | 游戏控制:限制用户操作的频率,如每秒只能发射一次子弹。 14 | 15 | * 防抖 (Debouncing) 16 | * 适用场景: 17 | 搜索框实时搜索:用户在搜索框输入时,只有在用户停止输入一定时间后才发起搜索请求,避免对每个键盘输入都进行处理。 18 | 窗口大小调整(resize):只在用户完成窗口大小调整一段时间后,才进行相关的布局计算和更新,避免频繁调整造成的性能问题。 19 | 表单验证:在用户停止输入后延迟执行验证逻辑,减少验证频率。 20 | */ 21 | @Entry 22 | @Component 23 | struct ThrottleDebounce { 24 | @State message: string = 'Hello World'; 25 | 26 | @Debounce(3000) 27 | debounceClick1(msg: string) { 28 | console.log("debounceClick1 " + msg) 29 | } 30 | 31 | debounceClick2(msg: string) { 32 | console.log("handleClick2 " + msg) 33 | } 34 | 35 | private debounceClick22 = debounce(2000, this.debounceClick2) 36 | 37 | @Throttle(3000) 38 | throttleClick1(msg: string) { 39 | console.log("throttleClick1 " + msg) 40 | } 41 | 42 | throttleClick2(msg: string) { 43 | console.log("throttleClick2 " + msg) 44 | } 45 | 46 | private throttleClick22 = throttle(2000, this.throttleClick2) 47 | 48 | build() { 49 | Row() { 50 | Column() { 51 | Text(this.message) 52 | .fontSize(50) 53 | .fontWeight(FontWeight.Bold) 54 | } 55 | .width('100%') 56 | .onClick(() => { 57 | this.debounceClick1('Debounce Decorator') 58 | this.debounceClick22('debounce Function') 59 | 60 | this.throttleClick1('Throttle Decorator') 61 | this.throttleClick22('throttle Function') 62 | }) 63 | } 64 | .height('100%') 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /hvigor/hvigor-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.0", 3 | "dependencies": { 4 | }, 5 | "execution": { 6 | // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ 7 | // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ 8 | // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ 9 | // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ 10 | // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ 11 | }, 12 | "logging": { 13 | // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ 14 | }, 15 | "debugging": { 16 | // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ 17 | }, 18 | "nodeOptions": { 19 | // "maxOldSpaceSize": 4096 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process */ 20 | }, 21 | "properties": { 22 | "ohos.sign.har": true 23 | } 24 | } -------------------------------------------------------------------------------- /hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { appTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "@aliyun/edid@^1.0.0": "@aliyun/edid@1.0.0", 9 | "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", 10 | "@ohos/hypium@1.0.17": "@ohos/hypium@1.0.17" 11 | }, 12 | "packages": { 13 | "@aliyun/edid@1.0.0": { 14 | "name": "@aliyun/edid", 15 | "version": "1.0.0", 16 | "integrity": "sha512-jw0MkBlHQHjQ5j3JA2ProYTSD6Jxd3X5qdEWDxgnDXhJtzM/rLp7SFtrw9kZupRIX+wUJNJbejghMX+A7xTqTQ==", 17 | "resolved": "https://ohpm.openharmony.cn/ohpm/@aliyun/edid/-/edid-1.0.0.har", 18 | "registryType": "ohpm" 19 | }, 20 | "@ohos/hamock@1.0.0": { 21 | "name": "@ohos/hamock", 22 | "version": "1.0.0", 23 | "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", 24 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", 25 | "registryType": "ohpm" 26 | }, 27 | "@ohos/hypium@1.0.17": { 28 | "name": "@ohos/hypium", 29 | "version": "1.0.17", 30 | "integrity": "sha512-niGSBi8S8izx+TVSe9qKjry1HloeFVdOsBii24W5ApiAohqQIu0K/BVMavDL8YXFrqqEFsHYKcBLSDvLqmtbtQ==", 31 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.17.har", 32 | "registryType": "ohpm" 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.0", 3 | "name": "harmonyutilcode", 4 | "version": "1.0.0", 5 | "description": "Please describe the basic information.", 6 | "main": "", 7 | "author": "", 8 | "license": "", 9 | "dependencies": { 10 | "@aliyun/edid": "^1.0.0" 11 | }, 12 | "devDependencies": { 13 | "@ohos/hypium": "1.0.17", 14 | "@ohos/hamock": "1.0.0" 15 | }, 16 | "dynamicDependencies": {} 17 | } -------------------------------------------------------------------------------- /utilcode/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /utilcode/BuildProfile.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * Use these variables when you tailor your ArkTS code. They must be of the const type. 3 | */ 4 | export const HAR_VERSION = '0.0.8'; 5 | export const BUILD_MODE_NAME = 'debug'; 6 | export const DEBUG = true; 7 | export const TARGET_NAME = 'default'; 8 | 9 | /** 10 | * BuildProfile Class is used only for compatibility purposes. 11 | */ 12 | export default class BuildProfile { 13 | static readonly HAR_VERSION = HAR_VERSION; 14 | static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; 15 | static readonly DEBUG = DEBUG; 16 | static readonly TARGET_NAME = TARGET_NAME; 17 | } -------------------------------------------------------------------------------- /utilcode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.0.9] 2024-11-25 4 | 修复LiveDataBus 广播注销不生效的问题 5 | 新增KeyBoardUtils 工具类 6 | 7 | ## [v0.0.8] 2024-09-19 8 | 升级ohos/crypto-js 9 | 10 | ## [v0.0.7] 2024-08-03 11 | 修复因为@Sendable 导致的FileUtils 奔溃问题 12 | 13 | ## [v0.0.6] 2024-07-26 14 | 新增相册选择类:PickerHelper 15 | 性能统计类:StopWatch 16 | 文件操作类:FileUtils 17 | APP信息类:AppUtils 18 | 图片压缩类:ImageUtils 19 | 吐司工具类:ToastUtils 20 | 21 | ## [v0.0.5] 2024-07-12 22 | 23 | 新增LiveDataBus、ImageUtils 类 24 | 25 | ## [v0.0.4] 2024-06-06 26 | 27 | 新增Lifecycle装饰器类 28 | 29 | ## [v0.0.3] 2024-05-17 30 | 修复DeviceUtils BUG 31 | 新增Utils、ConvertUtils、UIAdapt 32 | 新增ResourceUtils 类 33 | 新增RandomUtils 类 34 | 35 | ## [v0.0.2] 2024-05-15 36 | 37 | 新增CharUtils类 38 | 新增ObjectUtils类 39 | 新增StringUtils类 40 | 新增AssetStore类 41 | 42 | ## [v0.0.1] 2024-05-13 43 | 44 | 初版 45 | 新增DeviceUtils类 46 | 47 | ### 🐣新特性 48 | 49 | * 发布测试版 50 | 51 | ### 🐞Bug修复 52 | 53 | * 暂无 -------------------------------------------------------------------------------- /utilcode/Index.ets: -------------------------------------------------------------------------------- 1 | export { Utils } from './src/main/ets/Utils' 2 | 3 | export { DeviceUtils } from './src/main/ets/DeviceUtils' 4 | 5 | export { CharUtils } from './src/main/ets/CharUtils' 6 | 7 | export { ObjectUtils } from './src/main/ets/ObjectUtils' 8 | 9 | export { StringUtils } from './src/main/ets/StringUtils' 10 | 11 | export { AssetStore } from './src/main/ets/AssetStore' 12 | 13 | export { ConvertUtils } from './src/main/ets/ConvertUtils' 14 | 15 | export { UIAdapt } from './src/main/ets/UIAdapt' 16 | 17 | export { ResourceUtils } from './src/main/ets/ResourceUtils' 18 | 19 | export { RandomUtils } from './src/main/ets/RandomUtils' 20 | 21 | export { LogUtils } from './src/main/ets/LogUtils' 22 | 23 | export { JsonUtils } from './src/main/ets/JsonUtils' 24 | 25 | export { NetworkUtils, NetworkType } from './src/main/ets/NetworkUtils' 26 | 27 | export { Lifecycle, LifecycleState } from './src/main/ets/lifecycle/Lifecycle' 28 | 29 | export { LifecycleEvent } from './src/main/ets/lifecycle/LifecycleEvent' 30 | 31 | export { AESUtils } from './src/main/ets/AESUtils' 32 | 33 | export { DESUtils } from './src/main/ets/DESUtils' 34 | 35 | export { ClipboardUtil } from './src/main/ets/ClipboardUtil' 36 | 37 | export { LiveDataBus } from './src/main/ets/LiveDataBus' 38 | 39 | export { ImageCompressOptions } from './src/main/ets/image/ImageCompressOptions' 40 | 41 | export { ImageUtils, ImageMimeType } from './src/main/ets/image/ImageUtils' 42 | 43 | export { ToastUtils } from './src/main/ets/ToastUtils' 44 | 45 | export { PickerHelper, PickerOptions } from './src/main/ets/helper/PickerHelper' 46 | 47 | export { ScreenUtils } from './src/main/ets/ScreenUtils' 48 | 49 | export { PermissionUtils, Permission } from './src/main/ets/permissions/PermissionUtils' 50 | 51 | export { StopWatch } from './src/main/ets/StopWatch' 52 | 53 | export { FileUtils } from './src/main/ets/FileUtils' 54 | 55 | export { AppUtils } from './src/main/ets/AppUtils' 56 | 57 | export { KeyBoardUtils } from './src/main/ets/KeyBoardUtils' 58 | 59 | export { Debounce, debounce } from './src/main/ets/throttle_debounce/debounce' 60 | 61 | export { Throttle, throttle } from './src/main/ets/throttle_debounce/throttle' 62 | 63 | 64 | export { PromptActionUtils,BaseCustomDialog,PromptActionInfo } from './src/main/ets/PromptActionUtils' 65 | -------------------------------------------------------------------------------- /utilcode/README.md: -------------------------------------------------------------------------------- 1 | ## 📚简介 2 | 3 | `HarmonyUtilCode`是一个功能丰富且易用的**OpenHarmony/HarmonyOS工具库**,通过诸多实用工具类的使用,旨在帮助开发者快速、便捷地完成各类开发任务。 4 | 这些封装的工具涵盖了字符串、数字、集合、JSON等一系列操作, 5 | 可以满足各种不同的开发需求。本人为Android开发,故封装思路借鉴Android的工具类Blankj/AndroidUtilCode ,同时扩展了HarmonyOS的UI组件。 6 | 7 | ## 🛠️包含组件 8 | 9 | 一个OpenHarmony/HarmonyOS基础工具类,组成各种Util工具类 10 | 11 | ### 基础类组件 12 | 13 | * #### 设备相关 DeviceUtils 14 | 15 | ``` 16 | getDeviceId : 获取设备唯一识别码【卸载APP后依旧有效】 17 | ``` 18 | 19 | ## 📦安装 20 | 21 | ### 🍊ohpm 22 | 23 | 执行安装命令 24 | 25 | ``` 26 | ohpm i @android_x/utilcode 27 | ``` 28 | 29 | ## 📦使用 30 | 31 | ### 1.在项目中引入插件 32 | 33 | ``` 34 | import { DeviceUtils } from '@android_x/utilCode' 35 | ``` 36 | 37 | 类按需引入,项目需要使用那个就引入 38 | 39 | #### 1.1 DeviceUtils的方法 40 | 41 | ``` typescript 42 | import { DeviceUtils } from '@android_x/utilCode' 43 | ``` 44 | 45 | * getDeviceId 获取设备id>32为随机码[卸载APP后依旧不变] 46 | 47 | ``` typescript 48 | console.log(await DeviceUtils.getDeviceId()); 49 | ``` 50 | 51 | #### 1.2 CharUtils的方法 52 | 53 | ``` typescript 54 | import { CharUtils } from '@android_x/utilCode' 55 | ``` 56 | 57 | * isBlankChar 是否空白符 空白符包括空格、制表符、全角空格和不间断空格 58 | * isAscii 检查字符是否位于ASCII范围内(0~127) 59 | * isEmoji 判断是否为emoji表情符 60 | 61 | #### 1.3 StringUtils的方法 62 | 63 | 后期会增加扩展方法,使用会更简单 64 | 65 | ``` typescript 66 | import { StringUtils } from '@android_x/utilCode' 67 | ``` 68 | 69 | * isBlank 判断字符串是否为空白符(空白符包括空格、制表符、全角空格和不间断空格)true为空,否则false 70 | * isNotBlank 判断字符串是否为非空白符(空白符包括空格、制表符、全角空格和不间断空格)true为非空,否则false 71 | * isEmpty 判断字符串是否为空 72 | * toString 字符串转string,主要用于保证空安全 73 | * replaceAll 字符串全部替换为指定字符串 74 | 75 | #### 1.4 ObjectUtils的方法 76 | 77 | 后期会增加扩展方法,使用会更简单 78 | 79 | ``` typescript 80 | import { ObjectUtils } from '@android_x/utilCode' 81 | ``` 82 | 83 | * isString 判断属性是否是string类型类型 84 | * isNull 判断属性是否为空 85 | * isEmpty 判断属性内容是否为空【Object | String | Number | Boolean | null | undefined | Array | Map...】 86 | * equal 判断两个传入的数值或者是字符串是否相等 87 | * notEqual 判断两个传入的数值或者是字符串是否不相等 88 | * deepCopy 深拷贝对象 89 | 90 | #### 1.5 AssetStore的方法 91 | 92 | 基于 @ohos.security.asset 的封装。可以保证『重装/删除应用而不丢失数据』。 93 | 94 | ``` typescript 95 | import { AssetStore } from '@android_x/utilCode' 96 | ``` 97 | 98 | * set 增 99 | * remove 删 100 | * update 改 101 | * get 查 102 | 103 | #### 1.6 ResourceUtils 104 | 105 | 资源相关工具类 106 | 107 | ``` typescript 108 | import { ResourceUtils } from '@android_x/utilCode' 109 | ``` 110 | 111 | * getNumber 返回Resource对应的数值,单位vp 112 | 113 | #### 1.7 RandomUtils 114 | 115 | 随机工具类 116 | 117 | ``` typescript 118 | import { RandomUtils } from '@android_x/utilCode' 119 | ``` 120 | 121 | * randomUUID 随机生成32位uuid f4fed14a-7fab-4219-9440-80aec4735700 122 | 123 | #### 1.8 Lifecycle 124 | 125 | 自定义组件生命周期绑定装饰器,可通过以下方式自动绑定自定义组件的生命周期,使用方法和Android中的Lifecycle类似 126 | 无需关注lifecycle的释放,自定义组件aboutToDisappear时,lifecycle会自动释放 127 | 128 | 使用场景:比如页面关闭后,当前页面上的未请求完毕网络请求自动取消 129 | 130 | 注:目前仅支持aboutToAppear【Component】、onPageShow【Entry】、onPageHide【Entry】、aboutToDisappear【Component】,navigation 131 | 比较特殊,目前暂未找到合适的时机 132 | 133 | ``` typescript 134 | import {Lifecycle, LifecycleEvent } from '@android_x/utilcode'; 135 | @Component 136 | @Preview 137 | export struct TestFragment { 138 | @LifecycleEvent lifecycle: Lifecycle = new Lifecycle() 139 | aboutToAppear(): void { 140 | this.lifecycle.addObserver((state: LifecycleState) => { 141 | //此处即可 142 | console.log("状态" + state) 143 | }) 144 | } 145 | } 146 | ``` 147 | 148 | 149 | #### 1.9 LiveDataBus 150 | 151 | 具有生命周期感知自动释放的事件总线 152 | 153 | ```typescript 154 | import { LiveDataBus, Lifecycle, LifecycleEvent } from '@android_x/utilcode' 155 | @Component 156 | @Preview 157 | export struct EventBusFragment { 158 | @LifecycleEvent lifecycle: Lifecycle = new Lifecycle() 159 | private eventCallback: (string: string) => void = (string) => { 160 | console.log("收到了推送" + string) 161 | } 162 | aboutToAppear() { 163 | LiveDataBus.observe("test", this.eventCallback, this.lifecycle) 164 | LiveDataBus.post("test", "我是事件消息") 165 | } 166 | } 167 | ``` 168 | -------------------------------------------------------------------------------- /utilcode/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | "arkOptions": { 5 | "byteCodeHar": false 6 | } 7 | }, 8 | "buildOptionSet": [ 9 | { 10 | "name": "release", 11 | "arkOptions": { 12 | "obfuscation": { 13 | "ruleOptions": { 14 | "enable": false, 15 | "files": [ 16 | "./obfuscation-rules.txt" 17 | ] 18 | }, 19 | "consumerFiles": [ 20 | "./consumer-rules.txt" 21 | ] 22 | } 23 | }, 24 | }, 25 | ], 26 | "targets": [ 27 | { 28 | "name": "default" 29 | }, 30 | { 31 | "name": "ohosTest" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /utilcode/consumer-rules.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanranran/HarmonyUtilCode/9751d1af45ed7c847e636f760f9c38168f1e479a/utilcode/consumer-rules.txt -------------------------------------------------------------------------------- /utilcode/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { harTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /utilcode/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # 配置混淆教程 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 2 | 3 | # 开启属性混淆。 如果你使用这个选项,那么所有的属性名都会被混淆,被import/export直接导入或导出的类或对象的属性名不会被混淆 4 | -enable-property-obfuscation 5 | 6 | # 开启顶层作用域名称混淆 7 | -enable-toplevel-obfuscation 8 | 9 | # 开启文件/文件夹名称混淆 10 | -enable-filename-obfuscation 11 | 12 | # 向外导入或导出的名称混淆 13 | -enable-export-obfuscation 14 | 15 | # 去除不必要的空格符和所有的换行符 16 | -compact 17 | 18 | # 删除对 console.* 语句的调用,要求console.*语句返回值未被调用。 19 | -remove-log 20 | 21 | # 删除文件中的所有注释,包括单行、多行,及JsDoc注释 22 | -remove-comments 23 | 24 | #将名称缓存保存到指定的文件路径。名称缓存包含名称混淆前后的映射。 25 | -print-namecache obfuscation-namecache.json 26 | 27 | 28 | #保留指定路径中的所有文件的属性名称 29 | -keep 30 | ../oh_modules/* 31 | -------------------------------------------------------------------------------- /utilcode/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "@ohos/crypto-js@^2.0.4": "@ohos/crypto-js@2.0.4", 9 | "class-transformer@^0.5.1": "class-transformer@0.5.1" 10 | }, 11 | "packages": { 12 | "@ohos/crypto-js@2.0.4": { 13 | "name": "@ohos/crypto-js", 14 | "version": "2.0.4", 15 | "integrity": "sha512-589ur6oqU1UNibqefMly2cwEeEhkSoCAA3uc+oNUwRnYYtevn/kQnO+Coi36N+VJSeeg/uFzZk1K/wUMdovpOA==", 16 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/crypto-js/-/crypto-js-2.0.4.har", 17 | "registryType": "ohpm" 18 | }, 19 | "class-transformer@0.5.1": { 20 | "name": "class-transformer", 21 | "version": "0.5.1", 22 | "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", 23 | "resolved": "https://ohpm.openharmony.cn/ohpm/class-transformer/-/class-transformer-0.5.1.tgz", 24 | "shasum": "24147d5dffd2a6cea930a3250a677addf96ab336", 25 | "registryType": "ohpm" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /utilcode/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@android_x/utilcode", 3 | "version": "0.0.9", 4 | "description": "一款高效的OpenHarmony/HarmonyOS工具包从Blankj/AndroidUtilCode 移植而来.封装了常用工具类,提供一系列快捷操作方法。", 5 | "keywords": [ 6 | "工具类", 7 | "生命周期", 8 | "JSON", 9 | "Blankj", 10 | "AndroidUtilCode" 11 | ], 12 | "main": "Index.ets", 13 | "author": "tanranran", 14 | "license": "Apache-2.0", 15 | "repository": "https://github.com/tanranran/HarmonyUtilCode.git", 16 | "homepage": "https://github.com/tanranran", 17 | "dependencies": { 18 | "@ohos/crypto-js": "^2.0.4", 19 | "class-transformer": "^0.5.1" 20 | }, 21 | "devDependencies": {}, 22 | "dynamicDependencies": {} 23 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/AESUtils.ts: -------------------------------------------------------------------------------- 1 | import { CryptoJS } from '@ohos/crypto-js' 2 | 3 | /** 4 | * @author Tanranran 5 | * @date 2024/6/6 22:05 6 | * @description 7 | */ 8 | export class AESUtils { 9 | static encode(_key, _data) { 10 | try { 11 | let key = CryptoJS.enc.Utf8.parse(_key); 12 | let data = CryptoJS.enc.Utf8.parse(_data); 13 | let encrypted = CryptoJS.AES.encrypt(data, key, { 14 | iv: key, 15 | mode: CryptoJS.mode.CFB, 16 | padding: CryptoJS.pad.Pkcs7 17 | }); 18 | return encrypted.toString() 19 | } catch (e) { 20 | console.error(e) 21 | } 22 | return "" 23 | } 24 | 25 | static decode(_key, data) { 26 | try { 27 | var key = CryptoJS.enc.Utf8.parse(_key); 28 | let decrypted = CryptoJS.AES.decrypt(data, key, { 29 | iv: key, 30 | mode: CryptoJS.mode.CFB, 31 | padding: CryptoJS.pad.Pkcs7 32 | }) 33 | return decrypted.toString(CryptoJS.enc.Utf8) 34 | } catch (e) { 35 | console.error(e) 36 | } 37 | return "" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/AppUtils.ets: -------------------------------------------------------------------------------- 1 | import { bundleManager } from '@kit.AbilityKit'; 2 | import { JsonUtils } from './JsonUtils'; 3 | 4 | /** 5 | * @author Tanranran 6 | * @date 2024/5/15 22:10 7 | * @description 8 | */ 9 | export class AppUtils { 10 | private static mBundleInfo?: bundleManager.BundleInfo 11 | private static mAppName: string = "" 12 | 13 | private static getAppBundleInfo() { 14 | if (AppUtils.mBundleInfo != null) { 15 | return AppUtils.mBundleInfo 16 | } 17 | let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION; 18 | AppUtils.mBundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags) 19 | return AppUtils.mBundleInfo 20 | } 21 | 22 | static isAppDebug(): Boolean { 23 | //https://developer.huawei.com/consumer/cn/forum/topic/0201147109742425145?fid=0109140870620153026 24 | return AppUtils.getAppBundleInfo().appInfo.debug 25 | } 26 | 27 | static isAppSystem() { 28 | return AppUtils.getAppBundleInfo().appInfo.systemApp 29 | } 30 | 31 | // 32 | // public static boolean isAppForeground() { 33 | // return UtilsBridge.isAppForeground(); 34 | // } 35 | // 36 | // public static void launchApp(final String packageName) { 37 | // 38 | // } 39 | // 40 | // public static void relaunchApp(final boolean isKillProcess) { 41 | // 42 | // } 43 | // 44 | // launchAppDetailsSettings(){ 45 | // 46 | // } 47 | // 48 | // exitApp(){ 49 | // 50 | // } 51 | // 52 | // getAppIcon(){ 53 | // 54 | // } 55 | // 56 | // getAppIconId() 57 | // } 58 | // 59 | // isFirstTimeInstall() 60 | // } 61 | // 62 | // isAppUpgraded() 63 | 64 | static getPackageName(): string { 65 | return AppUtils.getAppBundleInfo().name 66 | } 67 | 68 | static getAppName(): string { 69 | if (AppUtils.mAppName.length == 0) { 70 | AppUtils.mAppName = getContext().resourceManager.getStringSync(AppUtils.getAppBundleInfo().appInfo.labelId) 71 | } 72 | return AppUtils.mAppName 73 | } 74 | 75 | // getAppPath() 76 | 77 | static getAppVersionName(): string { 78 | return AppUtils.getAppBundleInfo().versionName 79 | } 80 | 81 | static getAppVersionCode(): number { 82 | return AppUtils.getAppBundleInfo().versionCode 83 | } 84 | 85 | /** 86 | * 运行应用包所需要最低的SDK版本号。 87 | * @returns 1000000 88 | */ 89 | static getAppMinSdkVersion(): number { 90 | return AppUtils.getAppBundleInfo().minCompatibleVersionCode 91 | } 92 | 93 | /** 94 | * 运行应用包所需要最高SDK版本号。 95 | * @returns 40100011 96 | */ 97 | static getAppTargetSdkVersion(): number { 98 | return AppUtils.getAppBundleInfo().targetVersion 99 | } 100 | 101 | 102 | static getAppUid(): number { 103 | return AppUtils.getAppBundleInfo().appInfo.uid 104 | } 105 | 106 | static getAppInfo() { 107 | return AppUtils.getAppBundleInfo().appInfo 108 | } 109 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/ArrayUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:16 4 | * @description 5 | */ 6 | class ArrayUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/BarUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:16 4 | * @description 5 | */ 6 | class BarUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/Base64Util.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * Base64Util base64工具类 3 | * @author Tanranran 4 | * @date 2024/6/27 20:00 5 | * @description 6 | */ 7 | import { util } from '@kit.ArkTS'; 8 | 9 | export class Base64Util { 10 | 11 | /** 12 | * 将Uint8Array转化为字符串-异步 13 | * @param array Uint8Array数组 14 | * @returns 转码后的字符串 15 | */ 16 | static encodeToStr(array: Uint8Array, options?: util.Type): Promise { 17 | let base64 = new util.Base64Helper(); 18 | return base64.encodeToString(array, options); 19 | } 20 | 21 | /** 22 | * 将字符串转换为Uint8Array数组-异步 23 | * @param array 待转换的字符串 24 | * @returns 转码后的Uint8Array数组 25 | */ 26 | static decode(str: string, options?: util.Type): Promise { 27 | let base64 = new util.Base64Helper(); 28 | return base64.decode(str, options); 29 | } 30 | 31 | /** 32 | * 将Uint8Array转化为字符串-同步 33 | * @param array Uint8Array数组 34 | * @returns 转码后的字符串 35 | */ 36 | static encodeToStrSync(array: Uint8Array, options?: util.Type): string { 37 | let base64 = new util.Base64Helper(); 38 | let result = base64.encodeToStringSync(array, options); 39 | return result; 40 | } 41 | /** 42 | * @param str 字符串 43 | * @returns 转码后的字符串 44 | */ 45 | static encodeStrToStrSync(str: string, options?: util.Type): string { 46 | let array = new Uint8Array(str.length); 47 | for (let i = 0, j = str.length; i < j; i++) { 48 | array[i] = str.charCodeAt(i); 49 | } 50 | let base64 = new util.Base64Helper(); 51 | let result = base64.encodeToStringSync(array, options); 52 | return result; 53 | } 54 | /** 55 | * 将字符串转换为Uint8Array数组-同步 56 | * @param string 待转换的字符串 57 | * @returns 转码后的Uint8Array数组 58 | */ 59 | static decodeSync(str: string, options?: util.Type): Uint8Array { 60 | let base64 = new util.Base64Helper(); 61 | let result = base64.decodeSync(str, options); 62 | return result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/BrightnessUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:16 4 | * @description 5 | */ 6 | class BrightnessUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/BusUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:16 4 | * @description 5 | */ 6 | class BusUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/CharUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 字符工具类 3 | * @author Tanranran 4 | * @date 2024/3/19 22:54 5 | * @description 6 | */ 7 | export class CharUtils { 8 | /** 9 | * 是否空白符 空白符包括空格、制表符、全角空格和不间断空格 10 | * @param c 11 | * @returns 12 | */ 13 | static isBlankChar(c: number): boolean { 14 | return CharUtils.isWhitespace(c) 15 | || CharUtils.isSpaceChar(c) 16 | || c == 0xFEFF 17 | || c == 0x202A 18 | || c == 0x0000; 19 | } 20 | 21 | /** 22 | * 检查字符是否位于ASCII范围内(0~127) 23 | * @param ch 被检查的字符 24 | * @returns `true`表示为ASCII字符,否则为`false` 25 | */ 26 | static isAscii(ch: string): boolean { 27 | // 确保输入的是单个字符 28 | if (ch.length !== 1) throw new Error("Input must be a single character"); 29 | return ch.charCodeAt(0) < 128; 30 | } 31 | 32 | /** 33 | * 判断是否为emoji表情符 34 | * 35 | * @param c 字符 36 | * @returns 是否为emoji 37 | */ 38 | static isEmoji(c: number): boolean { 39 | // 使用 TypeScript 类型断言来告诉编译器我们已知这个条件不会是null或undefined 40 | const isNotEmoji = (c === 0x0) || 41 | (c === 0x9) || 42 | (c === 0xA) || 43 | (c === 0xD) || 44 | ((c >= 0x20 && c == 0xD7FF)) || 45 | ((c >= 0xE000 && c == 0xFFFD)) || 46 | ((c >= 0x100000 && c == 0x10FFFF)); 47 | 48 | return !isNotEmoji; 49 | } 50 | 51 | private static isWhitespace(codePoint: number): boolean { 52 | const whitespaceRegex = /^\s$/; 53 | const character = String.fromCodePoint(codePoint); 54 | return whitespaceRegex.test(character); 55 | } 56 | 57 | private static isSpaceChar(codePoint: number): boolean { 58 | const spaceCategories = [ 59 | "Zs", // Space separator 60 | "Zl", // Line separator 61 | "Zp" // Paragraph separator 62 | ]; 63 | 64 | const character = String.fromCodePoint(codePoint); 65 | const category = character.charCodeAt(0).toString(16); 66 | return spaceCategories.includes(category); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/CleanUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:17 4 | * @description 5 | */ 6 | class CleanUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ClipboardUtil.ets: -------------------------------------------------------------------------------- 1 | import { pasteboard } from '@kit.BasicServicesKit' 2 | 3 | /** 4 | * 剪切板工具类 5 | * @author Tanranran 6 | * @date 2024/6/6 17:05 7 | * @description 8 | */ 9 | export class ClipboardUtil { 10 | /** 11 | * 将文本内容写入剪贴板 12 | */ 13 | static async setText(dataText: string): Promise { 14 | let pasteData: pasteboard.PasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, dataText); 15 | let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard(); 16 | try { 17 | await systemPasteboard.setData(pasteData) 18 | return true 19 | } catch (e) { 20 | console.error(e) 21 | return false 22 | } 23 | } 24 | 25 | /** 26 | * 读取剪贴板中文本 27 | */ 28 | static getText(): pasteboard.PasteData | null { 29 | let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard(); 30 | try { 31 | return systemPasteboard.getDataSync() 32 | } catch (e) { 33 | console.error(e) 34 | return null 35 | } 36 | } 37 | 38 | /** 39 | * 剪贴板中是否有内容 40 | */ 41 | static hasData(): boolean { 42 | try { 43 | return pasteboard.getSystemPasteboard().hasDataType(pasteboard.MIMETYPE_TEXT_PLAIN) 44 | } catch (e) { 45 | console.error(e) 46 | return false 47 | } 48 | } 49 | 50 | /** 51 | * 清空剪贴板 52 | */ 53 | static clear() { 54 | try { 55 | pasteboard.getSystemPasteboard().clearDataSync() 56 | return true 57 | } catch (e) { 58 | console.error(e) 59 | return false 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ClipboardUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:17 4 | * @description 5 | */ 6 | class ClipboardUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/CloneUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:17 4 | * @description 5 | */ 6 | class CloneUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/CollectionUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:18 4 | * @description 5 | */ 6 | class CollectionUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ColorUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:18 4 | * @description 5 | */ 6 | class ColorUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ConvertUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:18 4 | * @description 5 | */ 6 | import { display } from '@kit.ArkUI'; 7 | 8 | export class ConvertUtils { 9 | /** 10 | * 遇到px2vp不生效时,自己计算 11 | * @author 鸿洋 12 | */ 13 | px2vp(val: number) { 14 | let vpVal = px2vp(val) 15 | if (vpVal == val) { 16 | let displayObject = display.getDefaultDisplaySync(); 17 | let screenDensityDPI = displayObject.densityDPI 18 | return val * (160 / screenDensityDPI) 19 | } 20 | return vpVal 21 | } 22 | 23 | /** 24 | * 遇到vp2px不生效时,自己计算 25 | * @author 鸿洋 26 | */ 27 | vp2px(val: number) { 28 | let vpVal = vp2px(val) 29 | if (vpVal == val) { 30 | let displayObject = display.getDefaultDisplaySync(); 31 | let screenDensityDPI = displayObject.densityDPI 32 | return val * (screenDensityDPI / 160) 33 | } 34 | return vpVal 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/CrashUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:18 4 | * @description 5 | */ 6 | class CrashUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/CryptoUtils.ets: -------------------------------------------------------------------------------- 1 | import { cryptoFramework } from '@kit.CryptoArchitectureKit'; 2 | import { buffer } from '@kit.ArkTS'; 3 | import { LogUtils } from './LogUtils'; 4 | 5 | /** 6 | * 系统加解密工具类 7 | * @author Tanranran 8 | * @date 2024/7/9 21:59 9 | * @description 10 | */ 11 | type HashType = 'SHA1' | 'SHA224' | 'SHA256' | 'SHA384' | 'SHA512' | 'MD5' | 'SM3' 12 | 13 | export class CryptoUtils { 14 | /** 15 | * 消息摘要HASH 算法 16 | * https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/crypto-generate-message-digest-V5#%E6%94%AF%E6%8C%81%E7%9A%84%E7%AE%97%E6%B3%95%E4%B8%8E%E8%A7%84%E6%A0%BC 17 | * @param message 18 | * @param hashType 19 | * @returns 20 | */ 21 | static hash(message: string | ArrayBuffer, hashType: HashType): string { 22 | try { 23 | let md = cryptoFramework.createMd(hashType); 24 | let messageData: Uint8Array = new Uint8Array(); 25 | if (message instanceof ArrayBuffer) { 26 | messageData = new Uint8Array(message) 27 | } else { 28 | messageData = new Uint8Array(buffer.from(message, 'utf-8').buffer) 29 | } 30 | let dataBlob: cryptoFramework.DataBlob = { 31 | data: messageData 32 | } 33 | md.updateSync(dataBlob) 34 | let mdResult = md.digestSync(); 35 | let bufferStr = buffer.from(mdResult.data).toString('hex'); 36 | return bufferStr 37 | } catch (e) { 38 | LogUtils.error(`${CryptoUtils.name}_hash`, e) 39 | return '' 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/DESUtils.ts: -------------------------------------------------------------------------------- 1 | import { CryptoJS } from '@ohos/crypto-js' 2 | 3 | /** 4 | * DES加解密工具 5 | * @author Tanranran 6 | * @date 2024/6/06 22:18 7 | * @description 8 | */ 9 | export class DESUtils { 10 | static encode(key, data): string { 11 | try { 12 | let keyHex = CryptoJS.enc.Utf8.parse(key); 13 | var encrypted = CryptoJS.DES.encrypt(data, keyHex, { 14 | mode: CryptoJS.mode.ECB, 15 | padding: CryptoJS.pad.Pkcs7 16 | }); 17 | return encrypted.toString() 18 | } catch (e) { 19 | console.error(e) 20 | } 21 | return "" 22 | } 23 | 24 | static decode(key, data): string { 25 | try { 26 | var keyHex = CryptoJS.enc.Utf8.parse(key); 27 | let ciphertext: CryptoJS.lib.CipherParams = { 28 | ciphertext: CryptoJS.enc.Base64.parse(data), 29 | key: undefined, 30 | iv: undefined, 31 | salt: undefined, 32 | algorithm: undefined, 33 | mode: undefined, 34 | padding: undefined, 35 | blockSize: 0, 36 | formatter: undefined 37 | } 38 | var decrypted = CryptoJS.DES.decrypt(ciphertext, keyHex, { 39 | mode: CryptoJS.mode.ECB, 40 | padding: CryptoJS.pad.Pkcs7 41 | }); 42 | return decrypted.toString(CryptoJS.enc.Utf8); 43 | } catch (e) { 44 | console.error(e) 45 | } 46 | return "" 47 | } 48 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/DeviceUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/14 22:20 4 | * @description 5 | */ 6 | import { AssetStore } from './AssetStore' 7 | import { util } from '@kit.ArkTS' 8 | import { AAID } from '@kit.PushKit'; 9 | import { StringUtils } from './StringUtils' 10 | import { deviceInfo } from '@kit.BasicServicesKit' 11 | import { i18n, resourceManager } from '@kit.LocalizationKit' 12 | import { Utils } from './Utils' 13 | 14 | export class DeviceUtils { 15 | private static deviceIdCacheKey = "device_id_cache_key" 16 | private static deviceId = "" 17 | 18 | /** 19 | * 判断字符串是否为空 20 | * @param property 被检测的字符串 21 | */ 22 | static isEmpty(property?: string | null): Boolean { 23 | if (property == '' || property == null || property == undefined || property == 'undefined' || 24 | property.length == 0) { 25 | return true 26 | } 27 | return false 28 | } 29 | 30 | /** 31 | * 获取设备id>32为随机码[卸载APP后依旧不变] 32 | * @param isMD5 33 | * @returns 34 | */ 35 | static async getDeviceId() { 36 | let deviceId = DeviceUtils.deviceId 37 | //如果内存缓存为空,则从AssetStore中读取 38 | if (DeviceUtils.isEmpty(deviceId)) { 39 | deviceId = `${(await AssetStore.get(DeviceUtils.deviceIdCacheKey)).data}` 40 | } 41 | //如果AssetStore中未读取到,则随机生成32位随机码,然后缓存到AssetStore中 42 | if (DeviceUtils.isEmpty(deviceId)) { 43 | deviceId = util.generateRandomUUID(true).replace(new RegExp('-', "gm"), '') 44 | AssetStore.set(DeviceUtils.deviceIdCacheKey, deviceId) 45 | } 46 | DeviceUtils.deviceId = deviceId 47 | return deviceId 48 | } 49 | 50 | static async getAAID() { 51 | try { 52 | return await AAID.getAAID(); 53 | } catch (e) { 54 | console.error("getAAID error is " + e); 55 | } 56 | return '' 57 | } 58 | 59 | /** 60 | * 获取设备类型 61 | * DEVICE_TYPE_PHONE 0x00 手机。 62 | * DEVICE_TYPE_TABLET 0x01 平板。 63 | * DEVICE_TYPE_CAR 0x02 汽车。 64 | * DEVICE_TYPE_PC 0x03 电脑。 65 | * DEVICE_TYPE_TV 0x04 电视。 66 | * DEVICE_TYPE_WEARABLE 0x06 穿戴。 67 | * DEVICE_TYPE_2IN111+ 0x07 2IN1。 68 | * @returns 69 | */ 70 | static getDeviceType(): resourceManager.DeviceType { 71 | try { 72 | let value = Utils.getAbilityContext().resourceManager.getDeviceCapabilitySync(); 73 | let deviceType = value.deviceType; 74 | return deviceType 75 | } catch (error) { 76 | console.error("getDeviceCapabilitySync error is " + error); 77 | } 78 | return resourceManager.DeviceType.DEVICE_TYPE_PHONE 79 | } 80 | 81 | /** 82 | * 获取 是否是平板设备 83 | * 84 | * @param context 85 | * @return true:平板,false:手机 86 | */ 87 | static isTabletDevice() { 88 | return DeviceUtils.getDeviceType() == resourceManager.DeviceType.DEVICE_TYPE_TABLET 89 | } 90 | 91 | /** 92 | * 获取手机型号 93 | * 94 | * @return 例如 HUAWEI ALN-AL00 95 | */ 96 | static getDeviceModel() { 97 | return util.format("%s %s", DeviceUtils.getDeviceBrand(), DeviceUtils.getDeviceModelName()); 98 | } 99 | 100 | /** 101 | * 获取设备外部产品系列 102 | * 103 | * @return HUAWEI Mate 60 Pro 104 | */ 105 | static getDeviceMarketName() { 106 | return deviceInfo.marketName 107 | } 108 | 109 | /** 110 | * 获取设备品牌名称 111 | * 112 | * @return HUAWEI 113 | */ 114 | static getDeviceBrand() { 115 | return deviceInfo.brand; 116 | } 117 | 118 | /** 119 | * 获取设备设备厂家名称 120 | * 121 | * @return HUAWEI 122 | */ 123 | static getManufacturer() { 124 | return deviceInfo.manufacture; 125 | } 126 | 127 | /** 128 | * 获取设备型号 129 | * 130 | * @return ALN-AL00 131 | */ 132 | static getDeviceModelName() { 133 | return deviceInfo.productModel; 134 | } 135 | 136 | /** 137 | * 获取设备系统版本号 138 | * 139 | * @return HarmonyOS 2.1.7.1 140 | */ 141 | static getDeviceVersionCode() { 142 | return "HarmonyOS" + deviceInfo.osFullName; 143 | } 144 | 145 | /** 146 | * 获取设备系统版本号 147 | * 148 | * @return 10/11/12... 149 | */ 150 | static getSdkApiVersion() { 151 | return `${deviceInfo.sdkApiVersion}` 152 | } 153 | 154 | /** 155 | * 获取系统区域。 156 | * @returns zh-Hans-CN 157 | */ 158 | static getLanguage() { 159 | return i18n.System.getSystemLocale() 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/EncodeUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:19 4 | * @description 5 | */ 6 | class EncodeUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/EncryptUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:19 4 | * @description 5 | */ 6 | class EncryptUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/FlashlightUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:19 4 | * @description 5 | */ 6 | class FlashlightUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/IntentUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 意图工具类 3 | * @author Tanranran 4 | * @date 2024/5/15 22:20 5 | * @description 6 | */ 7 | import { productViewManager } from '@kit.StoreKit'; 8 | import { LogUtils } from './LogUtils'; 9 | import { BusinessError } from '@kit.BasicServicesKit'; 10 | import { AppUtils } from './AppUtils'; 11 | import { Utils } from './Utils'; 12 | import { Want } from '@kit.AbilityKit'; 13 | 14 | export class IntentUtils { 15 | 16 | /** 17 | * 打开设置页面 18 | * @param uri 更多参数可参考 https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-ability-kit-0000001769732194#section858910165268 19 | * @returns 20 | */ 21 | private static getSettingWant(uri?: string, parameters?: Record): Want { 22 | return { 23 | bundleName: 'com.huawei.hmos.settings', 24 | abilityName: 'com.huawei.hmos.settings.MainAbility', 25 | uri: uri, 26 | parameters: parameters 27 | } 28 | } 29 | 30 | /** 31 | * 打开应用详情 32 | */ 33 | static startAppSettings(): Promise { 34 | return Utils.getAbilityContext().startAbility(IntentUtils.getSettingWant("application_info_entry", { 35 | "pushParams": AppUtils.getPackageName() 36 | })); 37 | } 38 | 39 | /** 40 | * 打开系统浏览器 41 | * @param uri 42 | * @returns 43 | */ 44 | static startBrowser(uri?: string): Promise { 45 | return new Promise((resolve) => { 46 | let want: Want = { 47 | action: 'ohos.want.action.viewData', 48 | entities: ['entity.system.browsable'], 49 | uri: uri 50 | }; 51 | Utils.getAbilityContext().startAbility(want).then(() => { 52 | resolve(true) 53 | }).catch((error: object) => { 54 | LogUtils.error(`startBrowser`, error) 55 | resolve(false) 56 | }) 57 | }) 58 | } 59 | 60 | /** 61 | * 打开应用商店详情页 62 | */ 63 | static startAppStoreDetail() { 64 | try { 65 | const request: Want = { 66 | parameters: { 67 | bundleName: AppUtils.getPackageName() 68 | } 69 | }; 70 | productViewManager.loadProduct(Utils.getAbilityContext(), request, { 71 | onError: (error: BusinessError) => { 72 | LogUtils.error('startAppStoreDetail', `loadProduct onError.code is ${error.code}, message is ${error.message}`) 73 | } 74 | }); 75 | } catch (err) { 76 | LogUtils.error('startAppStoreDetail', `loadProduct failed.code is ${err.code}, message is ${err.message}`) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/JsonUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * JSON相关工具类 3 | * @author Tanranran 4 | * @date 2024/5/15 22:20 5 | * @description 6 | */ 7 | import { plainToInstance, ClassConstructor, instanceToPlain, Transform } from "class-transformer"; 8 | import { CommonAllType } from './const/CommonConst' 9 | import { ObjectUtils } from './ObjectUtils' 10 | import { StringUtils } from './StringUtils' 11 | import { JSON } from '@kit.ArkTS' 12 | 13 | export class JsonUtils { 14 | /** 15 | * JSON字符串转Class对象 16 | * @param cls 类名 17 | * @param jsonStr json 字符串 18 | * @returns class对象 19 | */ 20 | static json2Bean(cls: ClassConstructor, jsonStr: string | null | undefined): T | null { 21 | try { 22 | if (StringUtils.isEmpty(jsonStr)) { 23 | return null 24 | } 25 | return plainToInstance(cls, JSON.parse(jsonStr!), { 26 | enableImplicitConversion: false, exposeDefaultValues: true 27 | }) as T 28 | } catch (e) { 29 | return null 30 | } 31 | } 32 | 33 | 34 | /** 35 | * 对象转字符串 36 | * @param data 37 | * @returns 字符串 38 | */ 39 | static bean2Json(data: CommonAllType): string { 40 | try { 41 | if (ObjectUtils.isNull(data)) { 42 | return "" 43 | } else if (data instanceof Map) { 44 | let jsonObject: Record = {}; 45 | data.forEach((val: string, key: Object) => { 46 | if (key !== undefined && val !== undefined) { 47 | jsonObject[key as string] = JsonUtils.bean2Json(val); 48 | } 49 | }); 50 | return JSON.stringify(jsonObject); 51 | } else if (Array.isArray(data)) { 52 | return JSON.stringify(instanceToPlain(data)) 53 | } else if (ObjectUtils.isString(data)) { 54 | return StringUtils.toString(data, '') 55 | } else if (typeof data === 'number') { 56 | return new String(data).toString(); 57 | } else if (typeof data === 'boolean') { 58 | return new String(data).toString(); 59 | } 60 | return JSON.stringify(instanceToPlain(data)) 61 | } catch (e) { 62 | return "" 63 | } 64 | } 65 | 66 | /** 67 | * JSON转Map 68 | * @param jsonStr 69 | * @returns 70 | */ 71 | static json2Map(jsonStr: string): Map { 72 | return new Map(Object.entries(JSON.parse(jsonStr))); 73 | } 74 | 75 | /** 76 | * JSON 字符串转JSON Object 77 | * @param text 78 | * @returns 79 | */ 80 | static parse(text: string): Object | null { 81 | try { 82 | return JSON.parse(text) 83 | } catch (e) { 84 | console.log(`${JsonUtils}_parse`, e) 85 | } 86 | return null 87 | } 88 | 89 | /** 90 | * Object 转json string 91 | * @param value 92 | * @returns 93 | */ 94 | static stringify(value?: Object): string { 95 | try { 96 | if (value == undefined) { 97 | return '' 98 | } 99 | if (value instanceof Map) { 100 | value = Object.fromEntries(value) 101 | } 102 | return JSON.stringify(value) 103 | } catch (e) { 104 | console.log(`JSONUtils_parse`, e) 105 | } 106 | return '' 107 | } 108 | 109 | /** 110 | * JSON字符串转Array对象 111 | * @param cls 类名 112 | * @param jsonStr json 字符串 113 | * @returns class对象 114 | */ 115 | static json2Array(cls: ClassConstructor, jsonStr: string | null | undefined): Array | null { 116 | try { 117 | return (JSON.parse(jsonStr) as ESObject).map(value => JsonUtils.json2Bean(cls,JsonUtils.stringify(value))) 118 | } catch (e) { 119 | return null 120 | } 121 | } 122 | 123 | 124 | /** 125 | * 检查ArkTS对象是否包含某种属性,可用于JSON.parse解析JSON字符串之后的相关操作。 126 | * @param value 127 | * @returns 128 | */ 129 | static has(obj: object, property: string): boolean { 130 | try { 131 | return JSON.has(obj, property) 132 | } catch (e) { 133 | console.log(`${JsonUtils.name}_parse`, e) 134 | } 135 | return false 136 | } 137 | 138 | /** 139 | * 获取JsonObject 中指定属性的值 140 | * @param obj 141 | * @param key 142 | * @param defaultValue 143 | * @returns 144 | */ 145 | static getValue(obj: object | undefined | null, key: string, defaultValue: T): T { 146 | try { 147 | if (obj == undefined || obj == null) { 148 | return defaultValue 149 | } 150 | const value = obj[key]; 151 | if (value === undefined || value === null) { 152 | return defaultValue; 153 | } 154 | return value; 155 | } catch (e) { 156 | console.error(`${JsonUtils.name}_getValue`, e) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/KeyboardUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/6/10 17:26 4 | * @description 5 | */ 6 | 7 | import { ArrayList } from '@kit.ArkTS'; 8 | import { Lifecycle, LifecycleState } from './lifecycle/Lifecycle'; 9 | import { inputMethod, inputMethodEngine } from '@kit.IMEKit'; 10 | import { BusinessError } from '@kit.BasicServicesKit'; 11 | import { KeyboardAvoidMode } from '@kit.ArkUI'; 12 | import { Utils } from './Utils'; 13 | import { LogUtils } from './LogUtils'; 14 | 15 | export class KeyBoardUtils { 16 | 17 | private static mObserverList: ArrayList<(height: number) => void> | null = new ArrayList(); 18 | private static onKeyboardHeightChange = (height: number) => { 19 | KeyBoardUtils.dispatchEvent(px2vp(height)) 20 | } 21 | 22 | static init() { 23 | //设置键盘的避让模式 24 | Utils.getUIContext()?.setKeyboardAvoidMode(KeyboardAvoidMode.OFFSET); 25 | Utils.getMainWindow()?.on('keyboardHeightChange', KeyBoardUtils.onKeyboardHeightChange) 26 | } 27 | 28 | /** 29 | * 关闭键盘,但是不取消控件焦点 30 | */ 31 | static hideKeyBoard() { 32 | inputMethod.getController().hideTextInput() 33 | } 34 | 35 | /** 36 | * 显示键盘,配合hideKeyBoard 使用 37 | */ 38 | static showKeyBoard() { 39 | inputMethod.getController().showTextInput() 40 | } 41 | 42 | /** 43 | * 向所有观察者分发状态 44 | * @param state 45 | */ 46 | private static dispatchEvent(height: number) { 47 | LogUtils.logWrite(`${KeyBoardUtils.name}_dispatchEvent`, '键盘高度' + height) 48 | KeyBoardUtils.mObserverList?.forEach((callback: (height: number) => void) => { 49 | callback(height) 50 | }); 51 | } 52 | 53 | /** 54 | * 添加观察者 55 | * @param callback 56 | */ 57 | static addObserver(callback: (height: number) => void, lifecycle?: Lifecycle) { 58 | KeyBoardUtils.mObserverList?.add(callback) 59 | lifecycle?.addObserver((state: LifecycleState) => { 60 | if (state == LifecycleState.ToDisappear) { 61 | KeyBoardUtils.removeObserver(callback) 62 | } 63 | }) 64 | } 65 | 66 | /** 67 | * 移除观察者 68 | * @param callback 69 | */ 70 | static removeObserver(callback: (height: number) => void) { 71 | KeyBoardUtils.mObserverList?.remove(callback) 72 | } 73 | 74 | /** 75 | * 订阅文本内容变化 76 | * @param callback 回调函数,返回订阅的文本内容。 77 | */ 78 | static onInputTextChanged(callback: (text: string) => void) { 79 | try { 80 | inputMethodEngine.getKeyboardDelegate().on('textChange', callback); 81 | } catch (err) { 82 | let error = err as BusinessError; 83 | console.error(`KeyboardUtil-onInputTextChanged-异常 ~ code: ${error.code} -·- message: ${error.message}`); 84 | } 85 | } 86 | 87 | 88 | /** 89 | * 取消订阅文本内容变化 90 | */ 91 | static removeInputTextChanged() { 92 | try { 93 | inputMethodEngine.getKeyboardDelegate().off('textChange'); 94 | } catch (err) { 95 | let error = err as BusinessError; 96 | console.error(`KeyboardUtil-removeInputTextChanged-异常 ~ code: ${error.code} -·- message: ${error.message}`); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/LanguageUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:20 4 | * @description 5 | */ 6 | class LanguageUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/LiveDataBus.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 基于emitter封装的事件消息总线 3 | * @author Tanranran 4 | * @date 2024/7/9 14:57 5 | * @description 6 | */ 7 | import { HashMap } from '@kit.ArkTS' 8 | import { Callback, emitter } from '@kit.BasicServicesKit' 9 | import { Lifecycle, LifecycleState } from './lifecycle/Lifecycle' 10 | import { LogUtils } from './LogUtils' 11 | 12 | export class LiveDataBus { 13 | private dispatch: Dispatch = new Dispatch() 14 | 15 | private static getInstance() { 16 | const storageKey = 'HUC_EVENT_BUS' 17 | let liveDataBus: LiveDataBus 18 | if (!AppStorage.has(storageKey)) { 19 | AppStorage.setOrCreate(storageKey, new LiveDataBus()) 20 | } 21 | liveDataBus = AppStorage.get(storageKey)! 22 | return liveDataBus 23 | } 24 | 25 | public static observe(eventName: string, callback: Callback, lifecycle?: Lifecycle) { 26 | LiveDataBus.getInstance().dispatch.observe(eventName, callback) 27 | lifecycle?.addObserver((state: LifecycleState) => { 28 | if (state == LifecycleState.ToDisappear) { 29 | LiveDataBus.getInstance().dispatch.removeObserve(eventName, callback) 30 | } 31 | }) 32 | return callback 33 | } 34 | 35 | public static removeObserve(eventName: string, callback?: Callback) { 36 | if(!callback){ 37 | return 38 | } 39 | LiveDataBus.getInstance().dispatch.removeObserve(eventName, callback) 40 | } 41 | 42 | public static post(eventName: string, value: T) { 43 | LiveDataBus.getInstance().dispatch.post(eventName, value) 44 | } 45 | } 46 | 47 | class Dispatch { 48 | private callbackMap = new HashMap, Callback>>() 49 | 50 | observe(eventName: string, callback: Callback) { 51 | if (!this.callbackMap.hasKey(eventName)) { 52 | this.callbackMap.set(eventName, new HashMap()) 53 | } 54 | let realCallback: Callback = (eventData) => { 55 | callback(eventData.data as T) 56 | } 57 | this.callbackMap.get(eventName).set(callback as Callback, realCallback) 58 | emitter.on(eventName, realCallback); 59 | } 60 | 61 | removeObserve(eventName: string, callback: Callback) { 62 | let realCallback: Callback | null = null 63 | if (this.callbackMap.hasKey(eventName)) { 64 | realCallback = this.callbackMap.get(eventName).get(callback as Callback) 65 | } 66 | if (realCallback) { 67 | emitter.off(eventName, realCallback) 68 | let callbackList=this.callbackMap.get(eventName) 69 | LogUtils.debug(LiveDataBus.name,'removeObserve:'+eventName+'移除前大小'+callbackList.length) 70 | callbackList.remove(callback as Callback) 71 | LogUtils.debug(LiveDataBus.name,'removeObserve:'+eventName+'移除后大小'+callbackList.length) 72 | } else if (realCallback) { 73 | emitter.off(eventName) 74 | } 75 | } 76 | 77 | post(eventName: string, data: T) { 78 | if (emitter.getListenerCount(eventName) > 0) { 79 | let eventData: emitter.EventData = { 80 | data: data, 81 | }; 82 | emitter.emit(eventName, eventData); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/MD5Utils.ets: -------------------------------------------------------------------------------- 1 | import { CryptoUtils } from './CryptoUtils' 2 | 3 | export class MD5Utils { 4 | static md5(data?: string | ArrayBuffer): string { 5 | let message: string | ArrayBuffer = data ?? '' 6 | return CryptoUtils.hash(message, 'MD5') 7 | } 8 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/MapUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:21 4 | * @description 5 | */ 6 | class MapUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/NetworkUtils.ets: -------------------------------------------------------------------------------- 1 | import { connection, statistics } from '@kit.NetworkKit'; 2 | import { radio } from '@kit.TelephonyKit' 3 | 4 | 5 | /** 6 | * @author Tanranran 7 | * @date 2024/5/15 22:21 8 | * @description 9 | */ 10 | export enum NetworkType { 11 | NETWORK_TYPE_UNKNOWN = 0, 12 | NETWORK_TYPE_WIFI = 1, 13 | NETWORK_TYPE_2G = 2, 14 | NETWORK_TYPE_3G = 3, 15 | NETWORK_TYPE_4G = 4, 16 | NETWORK_TYPE_5G = 5 17 | } 18 | 19 | export class NetworkUtils { 20 | /** 21 | * 是否有网络 22 | */ 23 | static isConnected() { 24 | return connection.hasDefaultNetSync() 25 | } 26 | 27 | /** 28 | * 是否是移动网络 29 | * @return true->移动网络 false->Wifi等其他网络 30 | */ 31 | static isMobile(): boolean { 32 | return NetworkUtils.hasNetBearType(connection.NetBearType.BEARER_CELLULAR) 33 | } 34 | 35 | /** 36 | * 判断当前网络是否是wifi网络 37 | * 38 | * @return boolean 39 | */ 40 | static isWifi(): boolean { 41 | return NetworkUtils.hasNetBearType(connection.NetBearType.BEARER_WIFI) 42 | } 43 | 44 | /** 45 | * 判断当前网络是否是以太网网络 46 | * 47 | * @return boolean 48 | */ 49 | static isEthernet(): boolean { 50 | return NetworkUtils.hasNetBearType(connection.NetBearType.BEARER_ETHERNET) 51 | } 52 | 53 | /** 54 | * 获取网络类型 55 | * 56 | * @return C中的NETWORK_TYPE常量 57 | */ 58 | static async getNetworkType(): Promise { 59 | try { 60 | if (NetworkUtils.isWifi()) { 61 | return NetworkType.NETWORK_TYPE_WIFI 62 | } 63 | if (NetworkUtils.isMobile()) { 64 | let slotId: number = await radio.getPrimarySlotId(); //获取主卡所在卡槽的索引号 65 | let signalInfo: Array = radio.getSignalInformationSync(slotId); 66 | for (let item of signalInfo) { 67 | if (item.signalType == radio.NetworkType.NETWORK_TYPE_UNKNOWN) { 68 | return NetworkType.NETWORK_TYPE_UNKNOWN 69 | } else if (item.signalType == radio.NetworkType.NETWORK_TYPE_GSM || 70 | item.signalType == radio.NetworkType.NETWORK_TYPE_CDMA) { 71 | return NetworkType.NETWORK_TYPE_2G 72 | } else if (item.signalType == radio.NetworkType.NETWORK_TYPE_WCDMA || 73 | item.signalType == radio.NetworkType.NETWORK_TYPE_TDSCDMA) { 74 | return NetworkType.NETWORK_TYPE_3G 75 | } else if (item.signalType == radio.NetworkType.NETWORK_TYPE_LTE) { 76 | return NetworkType.NETWORK_TYPE_4G; 77 | } else if (item.signalType == radio.NetworkType.NETWORK_TYPE_NR) { 78 | return NetworkType.NETWORK_TYPE_5G; 79 | } 80 | } 81 | } 82 | } catch (e) { 83 | console.error(e) 84 | } 85 | return NetworkType.NETWORK_TYPE_UNKNOWN 86 | } 87 | 88 | /** 89 | * 获取网络类型字符 90 | * 91 | * @return 92 | */ 93 | static async getNetStyleString() { 94 | try { 95 | switch (await NetworkUtils.getNetworkType()) { 96 | case NetworkType.NETWORK_TYPE_WIFI: 97 | return "wifi"; 98 | case NetworkType.NETWORK_TYPE_2G: 99 | return "2G"; 100 | case NetworkType.NETWORK_TYPE_3G: 101 | return "3G"; 102 | case NetworkType.NETWORK_TYPE_4G: 103 | return "4G"; 104 | case NetworkType.NETWORK_TYPE_5G: 105 | return "5G"; 106 | } 107 | } catch (e) { 108 | console.error(e) 109 | } 110 | return "noNet_type"; 111 | } 112 | 113 | static async getNetworkState() { 114 | let slotId = await radio.getPrimarySlotId(); //获取主卡所在卡槽的索引号 115 | let networkState = await radio.getNetworkState(slotId) 116 | return networkState 117 | } 118 | 119 | /** 120 | * 获取设备的网络运营商 121 | * 关于PLMN可参考这里 https://zh.wikipedia.org/wiki/%E7%A7%BB%E5%8A%A8%E8%AE%BE%E5%A4%87%E7%BD%91%E7%BB%9C%E4%BB%A3%E7%A0%81#%E4%B8%AD%E5%8D%8E%E4%BA%BA%E6%B0%91%E5%85%B1%E5%92%8C%E5%9B%BD_-_CN 122 | * @return 123 | */ 124 | static async getSimOperatorType(_networkState?: radio.NetworkState) { 125 | try { 126 | let networkState = _networkState ?? await NetworkUtils.getNetworkState() 127 | switch (networkState.plmnNumeric) { 128 | case "46000": 129 | case "46002": 130 | case "46004": 131 | case "46007": 132 | case "46008": 133 | return 1; //中国移动 134 | case "46001": 135 | case "46006": 136 | case "46009": 137 | return 2; //中国联通 138 | case "46003": 139 | case "46005": 140 | case "46011": 141 | return 3; //中国电信 142 | case "46020": 143 | return 4; //中国铁通 144 | case "46015": 145 | return 5; //中国广电 146 | } 147 | } catch (e) { 148 | console.error(e) 149 | } 150 | return 0; 151 | } 152 | 153 | static async getSimOperatorName() { 154 | let networkState = await NetworkUtils.getNetworkState() 155 | switch (await NetworkUtils.getSimOperatorType(networkState)) { 156 | case 1: 157 | return "中国移动"; 158 | case 2: 159 | return "中国联通"; 160 | case 3: 161 | return "中国电信"; 162 | case 4: 163 | return "中国铁通"; 164 | case 5: 165 | return "中国广电"; 166 | } 167 | return networkState.longOperatorName; 168 | } 169 | 170 | /** 171 | * 获取通过Mobile连接收到的字节总数,不包含WiFi 172 | * 173 | * @return 数据流量MB 174 | */ 175 | static async getMobileRxMB() { 176 | let mb = await statistics.getCellularRxBytes() 177 | if (mb <= 0) { 178 | mb = 0 179 | } else { 180 | mb = mb / 1024 / 1024 181 | } 182 | return mb + "MB"; 183 | } 184 | 185 | /** 186 | * 获取通过Mobile连接发送的字节总数,不包含WiFi 187 | * 188 | * @return 数据流量MB 189 | */ 190 | static async getMobileSdMB() { 191 | let mb = await statistics.getCellularTxBytes() 192 | if (mb <= 0) { 193 | mb = 0 194 | } else { 195 | mb = mb / 1024 / 1024 196 | } 197 | return mb + "MB"; 198 | } 199 | 200 | /** 201 | * 获取默认激活的网络【可能是移动数据,也可能是WIFI】 202 | * @returns 203 | */ 204 | private static getDefaultNet(): connection.NetHandle { 205 | return connection.getDefaultNetSync() 206 | } 207 | 208 | /** 209 | * 是否存在指定的网络 210 | * @param netType 211 | * @returns 212 | */ 213 | private static hasNetBearType(netType: connection.NetBearType) { 214 | let netCapabilities = connection.getNetCapabilitiesSync(NetworkUtils.getDefaultNet()); 215 | for (let item of netCapabilities.bearerTypes.values()) { 216 | if (item == netType) { 217 | return true 218 | } 219 | } 220 | return false 221 | } 222 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/NotificationUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:21 4 | * @description 5 | */ 6 | class NotificationUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/NumberUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:22 4 | * @description 5 | */ 6 | export class NumberUtils { 7 | /** 8 | * 判断是否是数值 9 | * @param value 需要判断的参数 10 | * @returns 11 | * @version Egret 2.4 12 | * @platform Web,Native 13 | * @language zh_CN 14 | */ 15 | public static isNumber(value: any): boolean { 16 | return typeof (value) === "number" && !isNaN(value); 17 | } 18 | 19 | 20 | public static toInt(value: any, defaultValue: number = 0): number { 21 | try { 22 | const parsedValue = parseInt(value); 23 | if (isNaN(parsedValue)) { 24 | return defaultValue; 25 | } 26 | return parsedValue; 27 | } catch (e) { 28 | return defaultValue 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ObjectUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/13 22:58 4 | * @description 5 | */ 6 | import { CommonAllType } from './const/CommonConst' 7 | import { ArrayList, List, HashMap } from '@kit.ArkTS'; 8 | import { instanceToInstance } from 'class-transformer'; 9 | 10 | export class ObjectUtils { 11 | /** 12 | * 判断属性是否是string类型类型 13 | * @param property 14 | * @returns 15 | */ 16 | static isString(property: string | Object | ArrayBuffer | undefined | null): Boolean { 17 | return typeof property === 'string' || property instanceof String 18 | } 19 | 20 | /** 21 | * 判断属性是否为空 22 | * @param property 23 | * @returns 24 | */ 25 | static isNull(property: CommonAllType): Boolean { 26 | return property === null || property === undefined 27 | } 28 | 29 | /** 30 | * 判断属性内容是否为空 31 | * @param property 32 | * @returns 33 | */ 34 | static isEmpty(property:CommonAllType): Boolean { 35 | if (ObjectUtils.isNull(property)) { 36 | return true 37 | } else if (Array.isArray(property) || property instanceof Array) { 38 | return property.length == 0 39 | } else if (property instanceof List) { 40 | return property.isEmpty() 41 | } else if (property instanceof ArrayList) { 42 | return property.isEmpty() 43 | } else if (property instanceof HashMap) { 44 | return property.isEmpty() 45 | } 46 | return property == ''; 47 | } 48 | 49 | /** 50 | * 判断两个传入的数值或者是字符串是否相等 51 | * @param source 52 | * @param target 53 | * @returns 54 | */ 55 | static equal(source: string | number, target: string | number): boolean { 56 | return source === target; 57 | } 58 | 59 | /** 60 | * 判断两个传入的数值或者是字符串是否不相等 61 | * @param source 62 | * @param target 63 | * @returns 64 | */ 65 | static notEqual(source: string | number, target: string | number): boolean { 66 | return false == ObjectUtils.equal(source, target); 67 | } 68 | 69 | /** 70 | * 深拷贝对象 71 | * @param obj 72 | * @returns 73 | */ 74 | static deepCopy(obj: object): T { 75 | return instanceToInstance(obj) as T 76 | } 77 | 78 | /** 79 | * 设置Object 中指定属性的值 80 | * @param obj 81 | * @param key 82 | * @param defaultValue 83 | * @returns 84 | */ 85 | static setValue(obj: object, key: string, value: CommonAllType) { 86 | try { 87 | if (obj) { 88 | obj[key] = value; 89 | } 90 | } catch (e) { 91 | console.error(`${ObjectUtils.name}_setValue`, e) 92 | } 93 | } 94 | 95 | /** 96 | * 获取Object 中指定属性的值 97 | * @param obj 98 | * @param key 99 | * @param defaultValue 100 | * @returns 101 | */ 102 | static getValue(obj: object | undefined | null, key: string, defaultValue: T): T { 103 | try { 104 | const value = obj[key]; 105 | if (value === undefined || value === null) { 106 | return defaultValue; 107 | } 108 | return value; 109 | } catch (e) { 110 | console.error(`${ObjectUtils.name}_getValue`, e) 111 | } 112 | } 113 | 114 | /** 115 | * obj转class ,解决obj as class 后丢失方法的问题 116 | * 例子:ObjectUtils.objToClass(SendCommentParam,new Object()) 117 | * @param clazz 118 | * @param obj 119 | * @returns 120 | */ 121 | static objToClass(clazz: new (...args: any[]) => T, obj: any): T { 122 | const instance = new clazz(); 123 | Object.assign(instance, obj); 124 | return instance; 125 | } 126 | 127 | static values(o: { [s: string]: T } | ArrayLike): T[] { 128 | return Object.values(o) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/PathUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:22 4 | * @description 5 | */ 6 | class PathUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/PhoneUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:22 4 | * @description 5 | */ 6 | class PhoneUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/PromptActionUtils.ets: -------------------------------------------------------------------------------- 1 | import { ComponentContent, promptAction, window } from '@kit.ArkUI' 2 | import { util } from '@kit.ArkTS' 3 | import { BusinessError } from '@kit.BasicServicesKit' 4 | 5 | /** 6 | * @author Tanranran 7 | * @date 2024/8/5 18:46 8 | * @description 9 | */ 10 | export class PromptActionUtils { 11 | private static instance: PromptActionUtils 12 | private promptActionInfoList: PromptActionInfo[] = new Array() 13 | 14 | private constructor() { 15 | } 16 | 17 | 18 | static getInstance(): PromptActionUtils { 19 | if (!PromptActionUtils.instance) { 20 | PromptActionUtils.instance = new PromptActionUtils() 21 | } 22 | return PromptActionUtils.instance 23 | } 24 | 25 | openCustomDialog(contentView: WrappedBuilder<[T]>, args: T, 26 | options?: promptAction.BaseDialogOptions) { 27 | window.getLastWindow(getContext()).then((result: window.Window) => { 28 | const uiContext: UIContext = result.getUIContext() 29 | const promptAction = uiContext.getPromptAction() 30 | let componentContent = new ComponentContent(uiContext, contentView, args) 31 | 32 | let dialogOptions: promptAction.BaseDialogOptions = { 33 | alignment: options?.alignment || DialogAlignment.Bottom, 34 | autoCancel: options?.autoCancel || true, 35 | maskColor: options?.maskColor, 36 | onWillDismiss: (action: DismissDialogAction) => { 37 | if (options?.onWillDismiss) { 38 | options.onWillDismiss(action) 39 | } else { 40 | if (action.reason == DismissReason.TOUCH_OUTSIDE || action.reason == DismissReason.PRESS_BACK) { 41 | action.dismiss() 42 | } 43 | } 44 | }, 45 | onDidAppear: () => { 46 | if (options?.onDidAppear) { 47 | options.onDidAppear() 48 | } 49 | 50 | }, 51 | onDidDisappear: () => { 52 | PromptActionUtils.removePromptActionInfo() 53 | if (options?.onDidDisappear) { 54 | options.onDidDisappear() 55 | } 56 | }, 57 | onWillDisappear: () => { 58 | if (options?.onWillDisappear) { 59 | options.onWillDisappear() 60 | } 61 | } 62 | } 63 | promptAction.openCustomDialog(componentContent, dialogOptions) 64 | PromptActionUtils.addPromptActionInfo(componentContent, args) 65 | 66 | }).catch((error: BusinessError) => { 67 | console.error('openCustomDialog', `error=${error.message}`) 68 | }) 69 | } 70 | 71 | updateCustomDialog(args: T) { 72 | let promptActionInfo: PromptActionInfo | undefined = 73 | PromptActionUtils.getInstance().getPromptActionInfoByDialogId(args.dialogId) 74 | if (promptActionInfo) { 75 | promptActionInfo.dialogContent.update(args) 76 | } 77 | } 78 | 79 | 80 | closeCustomDialog(dialogId?: string) { 81 | window.getLastWindow(getContext()).then((result: window.Window) => { 82 | const uiContext: UIContext = result.getUIContext() 83 | const promptAction = uiContext.getPromptAction() 84 | let info: PromptActionInfo | undefined = undefined 85 | if (dialogId) { 86 | info = PromptActionUtils.getInstance().getPromptActionInfoByDialogId(dialogId) 87 | } else { 88 | info = PromptActionUtils.getInstance().getCurrentPromptActionInfo() 89 | } 90 | if (info) { 91 | promptAction.closeCustomDialog(info.dialogContent) 92 | } else { 93 | console.error('closeCustomDialog', 'info is null') 94 | } 95 | }) 96 | } 97 | 98 | getCurrentPromptActionInfo(): PromptActionInfo { 99 | return PromptActionUtils.getInstance().promptActionInfoList[0] 100 | } 101 | 102 | getPromptActionInfoByDialogId(dialogId: string): PromptActionInfo | undefined { 103 | let infoList: PromptActionInfo[] = PromptActionUtils.getInstance().promptActionInfoList 104 | let promptActionInfo = infoList.find((item) => 105 | dialogId == item.dialogId) 106 | return promptActionInfo 107 | } 108 | 109 | 110 | private static addPromptActionInfo(dialogContent: ComponentContent, 111 | args: object) { 112 | let infoList = PromptActionUtils.getInstance().promptActionInfoList 113 | let info: PromptActionInfo = { 114 | dialogId: (args as BaseCustomDialog).dialogId, 115 | dialogContent: dialogContent, 116 | args: args 117 | } 118 | infoList.unshift(info) 119 | } 120 | 121 | private static removePromptActionInfo() { 122 | let infoList = PromptActionUtils.getInstance().promptActionInfoList 123 | infoList.shift() 124 | } 125 | } 126 | 127 | 128 | @Observed 129 | export class BaseCustomDialog { 130 | dialogId: string = '' 131 | dialogOptions?: promptAction.BaseDialogOptions 132 | 133 | constructor() { 134 | this.dialogId = util.generateRandomUUID() 135 | } 136 | 137 | show(builder: WrappedBuilder, args: T) { 138 | PromptActionUtils.getInstance().openCustomDialog(builder, args, 139 | args?.dialogOptions ? this?.dialogOptions : { 140 | alignment: DialogAlignment.Center, 141 | onWillDismiss: (dismissDialogAction: DismissDialogAction) => { 142 | dismissDialogAction.dismiss() 143 | } 144 | }) 145 | } 146 | 147 | update(options: T) { 148 | PromptActionUtils.getInstance().updateCustomDialog(options) 149 | } 150 | 151 | close() { 152 | PromptActionUtils.getInstance().closeCustomDialog(this.dialogId) 153 | } 154 | } 155 | 156 | 157 | export interface PromptActionInfo { 158 | dialogId: string 159 | dialogContent: ComponentContent 160 | args: object 161 | } 162 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/RandomUtils.ets: -------------------------------------------------------------------------------- 1 | import { util } from '@kit.ArkTS' 2 | import { LogUtils } from './LogUtils' 3 | 4 | /** 5 | * @author Tanranran 6 | * @date 2024/5/17 11:02 7 | * @description 8 | */ 9 | export class RandomUtils { 10 | /** 11 | * 随机生成32位UUID xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx 12 | * @param trimmedUuid 是否去除- 13 | * @returns 14 | */ 15 | static randomUUID(trimmedUuid: boolean = true) { 16 | try { 17 | if (canIUse('SystemCapability.Utils.Lang')) { 18 | let random = util.generateRandomUUID(true) 19 | if (trimmedUuid) { 20 | return random.replace(new RegExp('-', "gm"), '') 21 | } 22 | } 23 | } catch (e) { 24 | LogUtils.error(RandomUtils.name, e) 25 | } 26 | return RandomUtils.generateUUID(trimmedUuid) 27 | } 28 | 29 | private static generateUUID(trimmedUuid: boolean = true): string { 30 | let randomStr = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { 31 | const r = (Math.random() * 16) | 0; 32 | const v = c === 'x' ? r : (r & 0x3) | 0x8; 33 | return v.toString(16); 34 | }); 35 | if(trimmedUuid){ 36 | return randomStr.replace(new RegExp('-', "gm"), '') 37 | } 38 | return randomStr 39 | } 40 | 41 | /** 42 | * 生成十六进制的随机颜色 #ffffff 格式 43 | * @returns 44 | */ 45 | static generateRandomColor(): string { 46 | const color = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0'); 47 | return color; 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ReflectUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:23 4 | * @description 5 | */ 6 | class ReflectUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/RegexUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:23 4 | * @description 5 | */ 6 | class RegexUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ResourceUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 资源文件工具类 3 | * @author Tanranran 4 | * @date 2024/5/15 22:23 5 | * @description 6 | */ 7 | import { Utils } from './Utils' 8 | import { BusinessError } from '@kit.BasicServicesKit' 9 | import { LogUtils } from './LogUtils' 10 | 11 | export class ResourceUtils { 12 | /** 13 | * 资源名称对应的数值。Integer[app.integer.integer_test]对应的是原数值,float不带单位对应的是原数值[$r('app.float.float_test')];带"vp","fp"单位时对应的是px值。 14 | * @param resource 15 | * @returns 返回单位vp 16 | */ 17 | static getNumber(resource: Resource): number { 18 | try { 19 | return px2vp(Utils.getAbilityContext().resourceManager.getNumber(resource)) 20 | } catch (e) { 21 | console.error(e) 22 | return 0 23 | } 24 | } 25 | 26 | /** 27 | * 获取raw文件的字节流 28 | * @param path 29 | * @returns 30 | */ 31 | static getRawFileContent(path: string): Promise { 32 | return new Promise((resolve) => { 33 | Utils.getAbilityContext() 34 | .resourceManager 35 | .getRawFileContent(path) 36 | .then(res => { 37 | resolve(res) 38 | }) 39 | .catch((err: BusinessError) => { 40 | resolve(null) 41 | LogUtils.error(ResourceUtils.name, 42 | `Failed to get RawFileContent with error message: ${err.message}, error code: ${err.code}`); 43 | }); 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/SPUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:24 4 | * @description 5 | */ 6 | class SPUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ScreenUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:23 4 | * @description 5 | */ 6 | import { display, window } from '@kit.ArkUI'; 7 | import { LogUtils } from './LogUtils'; 8 | import { Utils } from './Utils'; 9 | 10 | export class ScreenUtils { 11 | 12 | /** 13 | * 获取当前默认的屏幕对象【有些设备有多个屏幕】 14 | * @returns 15 | */ 16 | static getDefaultDisplay() { 17 | return display.getDefaultDisplaySync() 18 | } 19 | 20 | /** 21 | * 获取当前主窗口的窗口属性 22 | * @returns 23 | */ 24 | static getWindowProperties(): window.WindowProperties | null { 25 | return Utils.getMainWindow()?.getWindowProperties() ?? null 26 | } 27 | 28 | /** 29 | * 获取当前App窗口的宽度 30 | * @returns 31 | */ 32 | static getAppScreenWidth(): number { 33 | try { 34 | return ScreenUtils.getWindowProperties()?.windowRect.width ?? 0 35 | } catch (e) { 36 | LogUtils.error(`${ScreenUtils.name}_getAppScreenWidth`, e) 37 | return 0 38 | } 39 | } 40 | 41 | /** 42 | * 显示设备的屏幕宽度,单位为px,该参数应为整数。 43 | * @returns 44 | */ 45 | static getScreenWidth(): number { 46 | try { 47 | return ScreenUtils.getDefaultDisplay().width 48 | } catch (e) { 49 | LogUtils.error(`${ScreenUtils.name}_getScreenWidth`, e) 50 | return 0 51 | } 52 | } 53 | 54 | /** 55 | * 显示设备的屏幕高度,单位为px,该参数应为整数。 56 | * @returns 57 | */ 58 | static getScreenHeight(): number { 59 | try { 60 | return ScreenUtils.getDefaultDisplay().height 61 | } catch (e) { 62 | LogUtils.error(`${ScreenUtils.name}_getScreenHeight`, e) 63 | return 0 64 | } 65 | } 66 | 67 | /** 68 | * 获取当前App窗口的高度 69 | * @returns 70 | */ 71 | static getAppScreenHeight(): number { 72 | try { 73 | return ScreenUtils.getWindowProperties()?.windowRect.height ?? 0 74 | } catch (e) { 75 | LogUtils.error(`${ScreenUtils.name}_getAppScreenHeight`, e) 76 | return 0 77 | } 78 | } 79 | 80 | static getDpi(){ 81 | let displayObject = display.getDefaultDisplaySync(); 82 | let screenDensityDPI = displayObject.densityDPI 83 | return screenDensityDPI 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/SnowflakeUtil.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/31 13:22 4 | * @description 5 | * // 实例化Snowflake,假设我们使用workerId为1,datacenterId为2 6 | const snowflake = new MySnowflakeUtil(1, 2); 7 | // 生成下一个ID 8 | console.log(snowflake.nextId().toString()); // 输出类似于:1784466839682822144的字符串形式ID 9 | 10 | // 可以连续生成多个ID来观察序列号的变化 11 | for (let i = 0; i < 5; i++) { 12 | console.log(snowflake.nextId().toString()); 13 | } 14 | */ 15 | export class SnowflakeUtil { 16 | private readonly epoch: bigint = BigInt('1288834974657'); 17 | private readonly workerIdBits: number = 5; 18 | private readonly datacenterIdBits: number = 5; 19 | private readonly maxWorkerId: bigint = (BigInt(1) << BigInt(this.workerIdBits)) - BigInt(1); 20 | private readonly maxDatacenterId: bigint = (BigInt(1) << BigInt(this.datacenterIdBits)) - BigInt(1); 21 | private readonly sequenceBits: number = 12; 22 | private readonly workerIdShift: number = this.sequenceBits; 23 | private readonly datacenterIdShift: number = this.sequenceBits + this.workerIdBits; 24 | private readonly timestampLeftShift: number = this.sequenceBits + this.workerIdBits + this.datacenterIdBits; 25 | private readonly sequenceMask: bigint = (BigInt(1) << BigInt(this.sequenceBits)) - BigInt(1); 26 | private workerId: bigint; 27 | private datacenterId: bigint; 28 | private sequence: bigint = BigInt(0); 29 | private lastTimestamp: bigint = -BigInt(1); 30 | 31 | constructor(workerId: number, datacenterId: number) { 32 | if (workerId > Number(this.maxWorkerId) || workerId < 0) { 33 | throw new Error(`worker Id can't be greater than ${this.maxWorkerId} or less than 0`); 34 | } 35 | if (datacenterId > Number(this.maxDatacenterId) || datacenterId < 0) { 36 | throw new Error(`datacenter Id can't be greater than ${this.maxDatacenterId} or less than 0`); 37 | } 38 | this.workerId = BigInt(workerId); 39 | this.datacenterId = BigInt(datacenterId); 40 | } 41 | 42 | nextId(): bigint { 43 | let timestamp = this.timeGen(); 44 | 45 | if (timestamp < this.lastTimestamp) { 46 | throw new Error(`Clock moved backwards. Refusing to generate id for ${this.lastTimestamp - timestamp} milliseconds`); 47 | } 48 | 49 | if (this.lastTimestamp === timestamp) { 50 | this.sequence = (this.sequence + BigInt(1)) & this.sequenceMask; 51 | if (this.sequence === BigInt(0)) { 52 | timestamp = this.tilNextMillis(this.lastTimestamp); 53 | } 54 | } else { 55 | this.sequence = BigInt(0); 56 | } 57 | 58 | this.lastTimestamp = timestamp; 59 | 60 | return ((BigInt(timestamp) - this.epoch) << BigInt(this.timestampLeftShift)) 61 | | (BigInt(this.datacenterId) << BigInt(this.datacenterIdShift)) 62 | | (BigInt(this.workerId) << BigInt(this.workerIdShift)) 63 | | BigInt(this.sequence); 64 | } 65 | 66 | tilNextMillis(lastTimestamp: bigint): bigint { 67 | let timestamp = this.timeGen(); 68 | while (timestamp <= lastTimestamp) { 69 | timestamp = this.timeGen(); 70 | } 71 | return timestamp; 72 | } 73 | 74 | timeGen(): bigint { 75 | return BigInt(Date.now()); 76 | } 77 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/SpanUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:23 4 | * @description 5 | */ 6 | class SpanUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/StopWatch.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 方法执行业务统计类 3 | * @author Tanranran 4 | * @date 2024/7/15 14:50 5 | * @description 6 | */ 7 | import { faceDetector } from '@kit.CoreVisionKit'; 8 | 9 | export class StopWatch { 10 | private startTime: number | null = null; 11 | private initTime: number | null = null; 12 | 13 | constructor() { 14 | this.initTime = Date.now(); 15 | this.reset() 16 | } 17 | 18 | reset(): void { 19 | this.startTime = Date.now(); 20 | } 21 | 22 | printElapsedTime(message: string, isReset: boolean = false) { 23 | const now = Date.now(); 24 | let elapsed = now - (this.startTime ?? now) 25 | let allTime = now - (this.initTime ?? now) 26 | console.log(`${message}_耗时_${elapsed}_总耗时${allTime}`) 27 | if (isReset) { 28 | this.reset() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/StringUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/13 17:57 4 | * @description 5 | */ 6 | import { CharUtils } from './CharUtils'; 7 | import { CommonAllType } from './const/CommonConst' 8 | import { ObjectUtils } from './ObjectUtils' 9 | import { buffer, util } from '@kit.ArkTS'; 10 | 11 | export class StringUtils { 12 | /** 13 | *判断字符串是否为空白符(空白符包括空格、制表符、全角空格和不间断空格)true为空,否则false 14 | * @param str 15 | * @returns 16 | */ 17 | static isBlank(str?: string | String | null | undefined): Boolean { 18 | let length: number; 19 | if ((str == null) || ((length = str.length) == 0)) { 20 | return true; 21 | } 22 | for (let i = 0; i < length; i++) { 23 | // 只要有一个非空字符即为非空字符串 24 | if (false == CharUtils.isBlankChar(str.charCodeAt(i))) { 25 | return false; 26 | } 27 | } 28 | 29 | return true; 30 | } 31 | 32 | /** 33 | *判断字符串是否为非空白符(空白符包括空格、制表符、全角空格和不间断空格)true为非空,否则false 34 | * @param str 35 | * @returns 36 | */ 37 | static isNotBlank(str: string | String | null | undefined): Boolean { 38 | return false == StringUtils.isBlank(str); 39 | } 40 | 41 | static startsWith(string: string = '', target: string, position: number = 0): boolean { 42 | return string.startsWith(target, position); 43 | } 44 | 45 | /** 46 | * 判断字符串是否为空 47 | * @param str 被检测的字符串 48 | * @return 是否为空 49 | */ 50 | static isEmpty(property?: CommonAllType,isCheckUndefinedStr?:boolean): Boolean { 51 | if (ObjectUtils.isNull(property) || property == '') { 52 | return true 53 | } 54 | if (isCheckUndefinedStr&&property=='undefined') { 55 | return true 56 | } 57 | return ObjectUtils.isEmpty(property) 58 | } 59 | 60 | /** 61 | * 判断字符串是否为非空。true为非空空,否则false 62 | * @param str 63 | * @returns 64 | */ 65 | static isNotEmpty(str: string | undefined | null) { 66 | return false == StringUtils.isEmpty(str); 67 | } 68 | 69 | /** 70 | * 字符串转string,主要用于保证空安全 71 | * @param any 72 | * @param defaultValue 73 | * @returns 74 | */ 75 | static toString(any: any | null | undefined, defaultValue: string = ""): string { 76 | if (any == null || any == undefined) { 77 | return defaultValue 78 | } 79 | return String(any); 80 | } 81 | 82 | /** 83 | * 字符串全部替换为指定字符串 84 | * @param source 要替换的字符串 85 | * @param s1 被替换文本 86 | * @param s2 替换文本 87 | * @returns 88 | */ 89 | static replaceAll(source: string, s1, s2) { 90 | return source.replace(new RegExp(s1, "gm"), s2); 91 | } 92 | 93 | /** 94 | * Uint8Array转字符串 95 | * @param src Uint8Array 96 | * @returns 字符串 97 | */ 98 | static unit8ArrayToStr(src: Uint8Array, encoding: buffer.BufferEncoding = 'utf-8'): string { 99 | let textDecoder = util.TextDecoder.create(encoding, { ignoreBOM: true }) 100 | let result = textDecoder.decodeWithStream(src, { stream: true }); 101 | return result; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ThreadUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:24 4 | * @description 5 | */ 6 | class ThreadUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/TimeUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:24 4 | * @description 5 | */ 6 | class TimeUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ToastUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:24 4 | * @description 5 | */ 6 | import { promptAction } from '@kit.ArkUI'; 7 | import { Utils } from './Utils'; 8 | 9 | export class ToastUtils { 10 | private static readonly LENGTH_SHORT = 2000; 11 | private static readonly LENGTH_LONG = 3500; 12 | 13 | static show(message: string | Resource) { 14 | ToastUtils.finalShow(message, ToastUtils.LENGTH_SHORT) 15 | } 16 | 17 | static showLong(message: string | Resource) { 18 | ToastUtils.finalShow(message, ToastUtils.LENGTH_LONG) 19 | } 20 | 21 | private static finalShow(message: string | Resource, duration: number) { 22 | let toastOptions: promptAction.ShowToastOptions = { 23 | message: message, //显示的文本信息。 24 | duration: duration, //默认值1500ms,取值区间:1500ms-10000ms。若小于1500ms则取默认值,若大于10000ms则取上限值10000ms。 25 | alignment: Alignment.Center, 26 | //bottom: "80vp", //设置弹窗边框距离屏幕底部的位置。 27 | showMode: promptAction.ToastShowMode.DEFAULT//设置弹窗是否显示在应用之上。DEFAULT:应用内,TOP_MOST应用之上 28 | } 29 | let mPromptAction = Utils.getUIContext()?.getPromptAction() 30 | if (mPromptAction) { 31 | mPromptAction.showToast(toastOptions) 32 | } else { 33 | promptAction.showToast(toastOptions) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/UIAdapt.ets: -------------------------------------------------------------------------------- 1 | import { window } from '@kit.ArkUI' 2 | import { BusinessError } from '@ohos.base' 3 | 4 | /** 5 | * @Author: ZhongRui ༄༅哟嚯྅ᯤ⁶ᴳ 6 | * @Date: 2024/5/16 22:41 7 | * @Description: ui适配工具类 8 | **/ 9 | export class UIAdapt { 10 | private static instance: UIAdapt 11 | private static readonly UI_DESIGN_WIDTH = 375 12 | private static readonly UI_DESIGN_HEIGHT = 1373.5 13 | private windowWidth: number = 0 14 | private windowHeight: number = 0 15 | private static readonly aspectRatioList = [0.73, 1.5] 16 | private static readonly scaleList = [2, 3] 17 | 18 | static get(): UIAdapt { 19 | if (!UIAdapt.instance) { 20 | UIAdapt.instance = new UIAdapt() 21 | } 22 | return UIAdapt.instance 23 | } 24 | 25 | 26 | private checkWidth() { 27 | if (this.windowWidth <= 0) { 28 | this.windowWidth = LocalStorage.getShared().get("windowWidth") ?? 0 29 | } 30 | } 31 | 32 | private checkHeight() { 33 | if (this.windowHeight <= 0) { 34 | this.windowHeight = LocalStorage.getShared().get("windowHeight") ?? 0 35 | } 36 | } 37 | 38 | 39 | toPx(uiSize: number, adaptFoldingScreen: boolean = true, uiDesignWidth: number = UIAdapt.UI_DESIGN_WIDTH, 40 | aspectRatio?: number, scale?: number): string { 41 | return this.toPxNumber(uiSize, adaptFoldingScreen, uiDesignWidth, aspectRatio, scale) + "px" 42 | } 43 | 44 | toPxNumber(uiSize: number, adaptFoldingScreen: boolean = true, uiDesignWidth: number = UIAdapt.UI_DESIGN_WIDTH, 45 | aspectRatio?: number, scale?: number): number { 46 | this.checkWidth() 47 | if (this.windowWidth <= 0) { 48 | return vp2px(uiSize) 49 | } 50 | let size = this.windowWidth * uiSize / uiDesignWidth 51 | if (adaptFoldingScreen) { 52 | this.checkHeight() 53 | if (this.windowHeight <= 0) { 54 | return vp2px(uiSize) 55 | } 56 | let widthHeightRatio = this.windowWidth / this.windowHeight 57 | 58 | if (aspectRatio != null && aspectRatio != undefined && aspectRatio > 0 && scale != null && scale != undefined && 59 | scale > 0) { 60 | if (widthHeightRatio >= aspectRatio) { 61 | return (size / scale) 62 | } 63 | return size 64 | } 65 | for (let index = 0; index < UIAdapt.aspectRatioList.length; index++) { 66 | const element = UIAdapt.aspectRatioList[index]; 67 | if (widthHeightRatio < element) { 68 | if (index == 0) { 69 | return size 70 | } else { 71 | return (size / UIAdapt.scaleList[index-1]) 72 | } 73 | } 74 | } 75 | } 76 | return size 77 | } 78 | 79 | toPxForHeight(uiSize: number, uiDesignHeight: number = UIAdapt.UI_DESIGN_HEIGHT): string { 80 | this.checkHeight() 81 | if (this.windowHeight <= 0) { 82 | return uiSize + "vp" 83 | } 84 | let size = this.windowHeight * uiSize / uiDesignHeight 85 | return size + "px" 86 | } 87 | 88 | init() { 89 | window.getLastWindow(getContext(this), (err: BusinessError, data) => { 90 | if (err) { 91 | return 92 | } 93 | let properties = data.getWindowProperties() 94 | this.windowWidth = properties.windowRect.width 95 | this.windowHeight = properties.windowRect.height 96 | }) 97 | } 98 | 99 | setWindowSize(width: number, height: number) { 100 | this.windowWidth = width; 101 | this.windowHeight = height; 102 | } 103 | 104 | getWindowWidth(): number { 105 | this.checkWidth() 106 | if (this.windowWidth <= 0) { 107 | return 0 108 | } 109 | return this.windowWidth 110 | } 111 | 112 | getWindowHeight(): number { 113 | this.checkHeight() 114 | if (this.windowHeight <= 0) { 115 | return 0 116 | } 117 | return this.windowHeight 118 | } 119 | } 120 | 121 | let UI = UIAdapt.get() 122 | 123 | export { UI } -------------------------------------------------------------------------------- /utilcode/src/main/ets/Utils.ets: -------------------------------------------------------------------------------- 1 | import { common } from '@kit.AbilityKit'; 2 | import { window } from '@kit.ArkUI' 3 | import { AppUtils } from './AppUtils'; 4 | import { BusinessError } from '@kit.BasicServicesKit'; 5 | import { LogUtils } from './LogUtils'; 6 | 7 | /** 8 | * @author Tanranran 9 | * @date 2024/5/15 22:25 10 | * @description 11 | * //初始化通用工具类,建议再UIAbility onCreate 的时候初始化 12 | * Utils.init(this.context) 13 | */ 14 | export class Utils { 15 | private constructor() { 16 | } 17 | 18 | private static instance: Utils; 19 | //每个UIAbility中都包含了一个Context属性,提供操作应用组件、获取应用组件的配置信息等能力。 20 | private static sAbilityContext: common.UIAbilityContext; 21 | private static sUiContext?: UIContext | null 22 | private static mainWindow?: window.Window | null 23 | 24 | public static getInstance(): Utils { 25 | if (!Utils.instance) { 26 | Utils.instance = new Utils(); 27 | } 28 | return Utils.instance; 29 | } 30 | 31 | /** 32 | * 初始化工具类, 33 | * 1、涉及到需要context的位置都可以用Utils来获取 34 | * 2、涉及到需要初始化的工具类,可以放到此处初始化 35 | * @param entryContext 36 | */ 37 | public static init(entryContext: common.UIAbilityContext) { 38 | Utils.sAbilityContext = entryContext 39 | } 40 | 41 | 42 | /** 43 | * 设置主窗口window 44 | * @returns 45 | */ 46 | public static setMainWindow(mainWindow?: window.Window) { 47 | try { 48 | Utils.mainWindow = mainWindow 49 | if (!mainWindow) { 50 | window.getLastWindow(getContext(), (err: BusinessError, data) => { 51 | const errCode: number = err.code; 52 | if (errCode) { 53 | LogUtils.error(Utils.constructor.name,'Failed to obtain the top window. Cause: ' + JSON.stringify(err)) 54 | return; 55 | } 56 | Utils.mainWindow = data; 57 | Utils.sUiContext = data.getUIContext() 58 | }) 59 | } else { 60 | Utils.sUiContext = mainWindow.getUIContext() 61 | } 62 | } catch (e) { 63 | console.error(e) 64 | } 65 | } 66 | 67 | 68 | /** 69 | * 获取应用级别的Context 70 | * @returns 71 | */ 72 | public static getApplicationContext() { 73 | return Utils.getAbilityContext().getApplicationContext() 74 | } 75 | 76 | /** 77 | * 获取UIAbility级别的Context 78 | * @returns 79 | */ 80 | public static getAbilityContext() { 81 | return Utils.sAbilityContext ?? getContext() as common.UIAbilityContext; 82 | } 83 | 84 | /** 85 | * 获取MainWindow 86 | * @returns 87 | */ 88 | public static getMainWindow() { 89 | return Utils.mainWindow 90 | } 91 | 92 | /** 93 | * 获取MainWindow 对应的uiContext 94 | * @returns 95 | */ 96 | public static getUIContext() { 97 | return Utils.sUiContext ?? Utils.getMainWindow()?.getUIContext() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/VibrateUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:26 4 | * @description 5 | */ 6 | class VibrateUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/VolumeUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:27 4 | * @description 5 | */ 6 | class VolumeUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ZipUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 22:27 4 | * @description 5 | */ 6 | class ZipUtils { 7 | } 8 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/const/CommonConst.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 通用常量 3 | * @author Tanranran 4 | * @date 2024/4/3 16:55 5 | * @description 6 | */ 7 | import { ArrayList, HashMap } from '@kit.ArkTS'; 8 | 9 | export type CommonSingleType = Object | String | Number | Boolean | null | undefined; //通用单个联合类型 10 | 11 | export type CommonAllType = CommonSingleType | Array | ArrayList | HashMap; //通用所有联合类型 12 | 13 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/ext/StringExt.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/5/15 12:29 4 | * @description 5 | */ 6 | 7 | declare global { 8 | interface String { 9 | replaceAll(s1, s2): string; 10 | } 11 | } 12 | 13 | String.prototype.replaceAll = function(s1, s2) { 14 | return this.replace(new RegExp(s1, "gm"), s2); 15 | } 16 | 17 | export {}; -------------------------------------------------------------------------------- /utilcode/src/main/ets/helper/PickerHelper.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/7/16 14:28 4 | * @description 5 | */ 6 | import { photoAccessHelper } from '@kit.MediaLibraryKit'; 7 | import { BusinessError } from '@kit.BasicServicesKit'; 8 | import picker from '@ohos.multimedia.cameraPicker'; 9 | import camera from '@ohos.multimedia.camera'; 10 | import { Utils } from '../Utils'; 11 | import { LogUtils } from '../LogUtils'; 12 | import { FileUtils } from '../FileUtils'; 13 | import { ImageUtils } from '../image/ImageUtils'; 14 | import { ImageResult } from '../image/ImageResult'; 15 | import { StopWatch } from '../StopWatch'; 16 | import { ImageCompressOptions } from '../image/ImageCompressOptions'; 17 | 18 | export class PickerOptions { 19 | MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE 20 | maxSelectNumber = 9; // 选择媒体文件的最大数目 21 | isPhotoTakingSupported = true //是否支持拍照,true表示支持,false表示不支持,默认为true。 22 | isSearchSupported = true //是否支持搜索,true表示支持,false表示不支持,默认为true。 23 | isEditSupported = true //是否支持编辑照片,true表示支持,false表示不支持,默认为true。 24 | // recommendationOptions?: photoAccessHelper.RecommendationOptions;//图片推荐选项(基于图片数据分析结果,依赖设备适配)。 25 | returnPixelMap: boolean = true 26 | isCompress: boolean = true 27 | compressOptions: ImageCompressOptions = new ImageCompressOptions() 28 | } 29 | 30 | export class PickerHelper { 31 | static selectPhoto(options: PickerOptions = new PickerOptions()) { 32 | return new Promise>((resolve) => { 33 | let imageResults = new Array() 34 | const photoViewPicker = new photoAccessHelper.PhotoViewPicker(); 35 | photoViewPicker.select(options).then(async (photoSelectResult: photoAccessHelper.PhotoSelectResult) => { 36 | for (let i = 0; i < photoSelectResult.photoUris.length; i++) { 37 | const uri = photoSelectResult.photoUris[i] 38 | let imageResult = new ImageResult() 39 | imageResult = await PickerHelper.getImageResult(options, uri, imageResult) 40 | imageResults.push(imageResult) 41 | } 42 | resolve(imageResults) 43 | }).catch((err: BusinessError) => { 44 | LogUtils.error(PickerHelper.name, `selectPhoto failed, code is ${err.code}, message is ${err.message}`); 45 | resolve(imageResults) 46 | }) 47 | }) 48 | } 49 | 50 | /** 51 | * 拍摄图片 52 | * @param returnPixelMap 是否返回returnPixelMap 53 | * @param isCompress 是否压缩图片 54 | * @returns 55 | */ 56 | static takePhoto(options: PickerOptions = new PickerOptions()): Promise { 57 | return new Promise((resolve) => { 58 | let takePhotoPath = FileUtils.getCacheDirPath('takePhoto', 'takePhoto.jpg') 59 | let file = FileUtils.mkFile(takePhotoPath) 60 | let takePhotoUri = FileUtils.getUriFromPath(file.path) 61 | let pickerProfile: picker.PickerProfile = { 62 | cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK, 63 | saveUri: takePhotoUri, 64 | }; 65 | let mediaTypes = [picker.PickerMediaType.PHOTO] 66 | let imageResult = new ImageResult() 67 | picker.pick(Utils.getAbilityContext(), mediaTypes, pickerProfile).then(async result => { 68 | if (result && result.resultCode == 0) { 69 | const stopWatch = new StopWatch() 70 | imageResult = await PickerHelper.getImageResult(options, result.resultUri, imageResult) 71 | stopWatch.printElapsedTime("takePhoto 处理完毕") 72 | } 73 | FileUtils.removeFile(result.resultUri) 74 | LogUtils.debug(PickerHelper.name, JSON.stringify(result)) 75 | resolve(imageResult) 76 | }).catch((err: BusinessError) => { 77 | LogUtils.error(PickerHelper.name, `takePhoto failed, code is ${err.code}, message is ${err.message}`); 78 | }).finally(() => { 79 | FileUtils.close(file) 80 | }) 81 | }) 82 | } 83 | 84 | private static async getImageResult(options: PickerOptions, uri: string, imageResult: ImageResult) { 85 | if (options.returnPixelMap) { 86 | imageResult.pixelMap = await ImageUtils.getImagePixelMap(uri) 87 | if (options.isCompress && imageResult.pixelMap) { 88 | imageResult = await ImageUtils.compressedImage(imageResult.pixelMap, options.compressOptions) 89 | } 90 | } else { 91 | imageResult.arrayBuffer = await ImageUtils.getImageBufferFromPath(uri) 92 | if (options.isCompress && imageResult.arrayBuffer) { 93 | imageResult = await ImageUtils.compressedImage(imageResult.arrayBuffer, options.compressOptions) 94 | } 95 | } 96 | return imageResult 97 | } 98 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/image/ImageCompressOptions.ets: -------------------------------------------------------------------------------- 1 | import { ImageMimeType } from './ImageUtils'; 2 | 3 | 4 | export class ImageCompressOptions { 5 | /** 压缩最大大小,单位为byte,默认为10MB */ 6 | maxSize: number = 10*1000*1000; 7 | /** 压缩最大宽度,单位是px,默认是4000 */ 8 | maxWidth: number = 4000; 9 | /** 压缩最大高度,单位是px,默认是4000 */ 10 | maxHeight: number = 4000; 11 | /** 压缩图片格式,默认JPEG */ 12 | format: ImageMimeType = ImageMimeType.JPG; 13 | /** 压缩质量,0-100,100为最佳,默认为80 */ 14 | quality: number = 90; 15 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/image/ImageInfo.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/7/18 15:15 4 | * @description 5 | */ 6 | export class ImageInfo { 7 | title: string = "" 8 | width: number = 0 9 | height: number = 0 10 | size: number = 0 11 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/image/ImageResult.ets: -------------------------------------------------------------------------------- 1 | /** 图片结果 2 | * @author Tanranran 3 | * @date 2024/7/15 16:25 4 | * @description 5 | */ 6 | import { image } from '@kit.ImageKit' 7 | import { ImageInfo } from './ImageInfo' 8 | 9 | export class ImageResult { 10 | arrayBuffer?: ArrayBuffer | null 11 | pixelMap?: image.PixelMap | null 12 | imageInfo?: ImageInfo = new ImageInfo() 13 | compressImageInfo?: ImageInfo = new ImageInfo() 14 | isOriginalPhoto?: boolean = false 15 | 16 | constructor() { 17 | this.imageInfo=new ImageInfo() 18 | this.compressImageInfo=new ImageInfo() 19 | } 20 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/image/ImageTask.ets: -------------------------------------------------------------------------------- 1 | import { ImageCompressOptions } from './ImageCompressOptions'; 2 | import { ImageUtils } from './ImageUtils'; 3 | import { image } from '@kit.ImageKit'; 4 | import { ImageResult } from './ImageResult'; 5 | 6 | 7 | @Concurrent 8 | export async function compressedImageAsync(source: image.PixelMap|ArrayBuffer, opt: ImageCompressOptions): Promise { 9 | return await ImageUtils.compressedImage(source,opt); 10 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/lifecycle/Lifecycle.ts: -------------------------------------------------------------------------------- 1 | import { ArrayList } from '@kit.ArkTS'; 2 | 3 | 4 | /** 5 | * 生命周期状态 6 | * @author Tanranran 7 | * @date 2024/6/5 23:45 8 | * @description 9 | */ 10 | export enum LifecycleState { 11 | ToAppear, 12 | PageShow, 13 | PageHide, 14 | ToDisappear, 15 | } 16 | 17 | /** 18 | * 生命周期包装类 19 | * @author Tanranran 20 | * @date 2024/6/5 23:45 21 | * @description 22 | */ 23 | export class Lifecycle { 24 | private mObserverList: ArrayList<(state: LifecycleState) => void> | null = new ArrayList(); 25 | 26 | /** 27 | * @Component 28 | * 组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。 29 | */ 30 | aboutToAppear() { 31 | this.dispatchEvent(LifecycleState.ToAppear) 32 | } 33 | 34 | /** 35 | * @Entry 36 | * 页面每次显示时触发一次,包括路由过程、应用进入前台等场景。 37 | */ 38 | onPageShow() { 39 | this.dispatchEvent(LifecycleState.PageShow) 40 | } 41 | 42 | /** 43 | * @Entry 44 | * 页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。 45 | */ 46 | onPageHide() { 47 | this.dispatchEvent(LifecycleState.PageHide) 48 | } 49 | 50 | /** 51 | * @Component 52 | * aboutToDisappear函数在自定义组件析构销毁之前执行 53 | */ 54 | aboutToDisappear() { 55 | this.dispatchEvent(LifecycleState.ToDisappear) 56 | } 57 | 58 | /** 59 | * 向所有观察者分发状态 60 | * @param state 61 | */ 62 | private dispatchEvent(state: LifecycleState) { 63 | this.mObserverList?.forEach((callback: (state: LifecycleState) => void) => { 64 | callback(state) 65 | }); 66 | } 67 | 68 | /** 69 | * 添加观察者 70 | * @param callback 71 | */ 72 | addObserver(callback: (state: LifecycleState) => void) { 73 | this.mObserverList?.add(callback) 74 | } 75 | 76 | /** 77 | * 移除观察者 78 | * @param callback 79 | */ 80 | removeObserver(callback: (state: LifecycleState) => void) { 81 | this.mObserverList?.remove(callback) 82 | } 83 | 84 | /** 85 | * 释放所有观察者 86 | */ 87 | release() { 88 | this.mObserverList?.clear() 89 | this.mObserverList = null 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/lifecycle/LifecycleEvent.ts: -------------------------------------------------------------------------------- 1 | export function LifecycleEvent(target: any, propertyKey: string | any) { 2 | let value: any; 3 | const getter = () => value; 4 | const setter = function (newValue: any) { 5 | value = newValue; 6 | }; 7 | Object.defineProperty(target, propertyKey, { 8 | get: getter, 9 | set: setter, 10 | enumerable: true, 11 | configurable: true, 12 | }); 13 | 14 | if (target.rerender) { 15 | if (target.aboutToAppear) { 16 | let oldFunction = target.aboutToAppear 17 | function appear() { 18 | oldFunction.call(this) 19 | target[propertyKey].aboutToAppear() 20 | } 21 | target.aboutToAppear = appear 22 | } else { 23 | target.aboutToAppear = () => { 24 | target[propertyKey].aboutToAppear() 25 | } 26 | } 27 | 28 | if (target.onPageShow) { 29 | let oldFunction = target.onPageShow 30 | function pageShow() { 31 | target[propertyKey].onPageShow() 32 | oldFunction.call(this) 33 | } 34 | 35 | target.onPageShow = pageShow 36 | } else { 37 | target.onPageShow = () => { 38 | target[propertyKey].onPageShow() 39 | } 40 | } 41 | 42 | if (target.onPageHide) { 43 | let oldFunction = target.onPageHide 44 | 45 | function pageHide() { 46 | target[propertyKey].onPageHide() 47 | oldFunction.call(this) 48 | } 49 | 50 | target.onPageHide = pageHide 51 | } else { 52 | target.onPageHide = () => { 53 | target[propertyKey].onPageHide() 54 | } 55 | } 56 | 57 | if (target.aboutToDisappear) { 58 | let oldFunction = target.aboutToDisappear 59 | function disappear() { 60 | target[propertyKey].aboutToDisappear() 61 | target[propertyKey].release() 62 | oldFunction.call(disappear.prototype.caller) 63 | } 64 | target.aboutToDisappear = disappear 65 | } else { 66 | target.aboutToDisappear = () => { 67 | target[propertyKey].aboutToDisappear() 68 | target[propertyKey].release() 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/permissions/Permission.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/4/19 16:52 4 | * @description 5 | */ 6 | export default class Permission { 7 | //https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-permission-group-list-0000001871741413 8 | static readonly CAMERA = "ohos.permission.CAMERA" 9 | static readonly READ_PASTEBOARD = "ohos.permission.READ_PASTEBOARD" 10 | //https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/oaid-service-0000001774279734#ZH-CN_TOPIC_0000001857915477__%E8%8E%B7%E5%8F%96oaid%E4%BF%A1%E6%81%AF 11 | static readonly APP_TRACKING_CONSENT = "ohos.permission.APP_TRACKING_CONSENT" 12 | } 13 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/permissions/PermissionOptions.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/4/12 15:21 4 | * @description 5 | */ 6 | import { Permissions } from '@kit.AbilityKit' 7 | import PermissionResult from './PermissionResult' 8 | import PermissionsHelper from './PermissionsHelper' 9 | 10 | export class PermissionOptions { 11 | private mPermissions = new Array() 12 | 13 | // permission(...permissions: Permissions[]): ZDMPermissionOptions { 14 | // for (const permission of permissions) { 15 | // if (this.mPermissions.indexOf(permission) != -1) { 16 | // continue 17 | // } 18 | // this.mPermissions.push(permission) 19 | // } 20 | // return this 21 | // } 22 | 23 | request(...permissions: Permissions[]): Promise { 24 | //检查权限列表 25 | if (permissions.length == 0) { 26 | return Promise.resolve(new PermissionResult()) 27 | } 28 | return PermissionsHelper.getInstance().requestPermissions(permissions) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/permissions/PermissionResult.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/4/19 16:25 4 | * @description 5 | */ 6 | import { BusinessError } from '@kit.BasicServicesKit' 7 | 8 | export default class PermissionResult { 9 | //同意了的授权 10 | grantedPermissions: string[] = [] 11 | //拒绝了的授权 12 | deniedPermissions: string[] = [] 13 | //是否全部授予 14 | allGranted: boolean = false 15 | //error 异常 16 | error?: BusinessError 17 | //二次向用户申请授权 18 | requestPermissionOnSetting?: () => Promise 19 | } 20 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/permissions/PermissionUtils.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tanranran 3 | * @date 2024/4/12 15:18 4 | * @description 5 | */ 6 | import { PermissionOptions } from './PermissionOptions'; 7 | import PermissionsHelper from './PermissionsHelper'; 8 | import { Permissions } from '@kit.AbilityKit'; 9 | import Permission from './Permission' 10 | import { IntentUtils } from '../IntentUtils'; 11 | 12 | /** 13 | * 权限工具类 14 | * 用法: 15 | * let result = await PermissionUtils.with().request(Permission.CAMERA,Permission.APP_TRACKING_CONSENT) 16 | */ 17 | export class PermissionUtils { 18 | static with(): PermissionOptions { 19 | return new PermissionOptions() 20 | } 21 | 22 | /** 23 | * 是否有权限 24 | * @param permissions 25 | * @returns 26 | */ 27 | static async hasPermissions(...permissions: Permissions[]) { 28 | return await PermissionsHelper.getInstance().checkPermission(...permissions) 29 | } 30 | 31 | /** 32 | * 打开应用详情 33 | */ 34 | static startAppSettings() { 35 | IntentUtils.startAppSettings() 36 | } 37 | } 38 | 39 | export { 40 | Permission 41 | } 42 | 43 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/permissions/PermissionsHelper.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 权限工具类 3 | * @author Tanranran 4 | * @date 2024/4/2 11:34 5 | * @description 6 | */ 7 | import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit'; 8 | import { BusinessError } from '@kit.BasicServicesKit'; 9 | import PermissionResult from './PermissionResult'; 10 | 11 | export default class PermissionsHelper { 12 | /** 13 | * 单例模型私有化构造函数,使用getInstance静态方法获得单例 14 | */ 15 | private constructor() { 16 | } 17 | 18 | /** 19 | * PermissionModel 单例 20 | */ 21 | private static instance?: PermissionsHelper; 22 | 23 | /** 24 | * 获取PermissionsHelper单例实例 25 | * @returns {PermissionsHelper} PermissionsHelper 26 | */ 27 | static getInstance(): PermissionsHelper { 28 | if (!PermissionsHelper.instance) { 29 | PermissionsHelper.instance = new PermissionsHelper(); 30 | } 31 | 32 | return PermissionsHelper.instance; 33 | } 34 | 35 | /** 36 | * 检测是否已授权 37 | * @param {Permissions} permissionName 检测授权的权限名 38 | * @returns {boolean} 检测结果 39 | */ 40 | async checkPermission(...permissions: Permissions[]): Promise { 41 | if (permissions == null || permissions.length == 0) { 42 | return false; 43 | } 44 | for (const permission of permissions) { 45 | const grantStatus: abilityAccessCtrl.GrantStatus = await this.reqCurGrantStatus(permission); 46 | if (grantStatus !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { 47 | return false; 48 | } 49 | } 50 | return true; 51 | } 52 | 53 | /** 54 | * 检查单个权限是否授权 55 | * 校验当前权限是否已经授权 56 | * @param permission 57 | * @returns 58 | */ 59 | private async reqCurGrantStatus(permission: Permissions): Promise { 60 | const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); 61 | let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED; 62 | // 获取应用程序的accessTokenID 63 | let tokenID: number = 0; 64 | try { 65 | let bundleInfo: bundleManager.BundleInfo = 66 | await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); 67 | let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo; 68 | tokenID = appInfo.accessTokenId; 69 | } catch (err) { 70 | console.error(`getBundleInfoForSelf failed, error: ${err}`); 71 | } 72 | 73 | // 校验应用是否被授予权限 74 | try { 75 | grantStatus = await atManager.checkAccessToken(tokenID, permission); 76 | } catch (err) { 77 | console.error(`checkAccessToken failed, error: ${err}`); 78 | } 79 | return grantStatus; 80 | } 81 | 82 | /** 83 | * 批量请求权限 84 | * @param permissions 85 | * @param context 86 | */ 87 | async requestPermissions(permissions: Permissions[]): Promise { 88 | // 向用户申请授权 89 | let context = getContext() as common.UIAbilityContext; 90 | let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); 91 | let permissionResult = new PermissionResult() 92 | //二次向用户申请授权 93 | permissionResult.requestPermissionOnSetting = () => { 94 | return new Promise((resolve) => { 95 | let permissionSettingResult = new PermissionResult() 96 | atManager.requestPermissionOnSetting(context, permissions) 97 | .then((authResults: Array) => { 98 | resolve(this.handleGrantStatus(permissionSettingResult, permissions, authResults)) 99 | }) 100 | .catch((err: BusinessError) => { 101 | permissionSettingResult.error = err 102 | console.error(`Failed to requestPermissionOnSetting. Code is ${err.code}, message is ${err.message}`); 103 | resolve(permissionSettingResult) 104 | }); 105 | }) 106 | } 107 | return new Promise(async (resolve) => { 108 | if (await PermissionsHelper.getInstance().checkPermission(...permissions)) { 109 | permissionResult.grantedPermissions = permissions 110 | permissionResult.allGranted = true 111 | resolve(permissionResult) 112 | return 113 | } 114 | // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗 115 | atManager.requestPermissionsFromUser(context, permissions).then((data) => { 116 | resolve(this.handleGrantStatus(permissionResult, data.permissions, data.authResults)) 117 | // 授权成功 118 | }).catch((err: BusinessError) => { 119 | permissionResult.error = err 120 | console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`); 121 | resolve(permissionResult) 122 | }) 123 | }) 124 | } 125 | 126 | handleGrantStatus(permissionResult: PermissionResult, permissions: Array, grantStatus: Array) { 127 | let length: number = grantStatus.length; 128 | for (let i = 0; i < length; i++) { 129 | let permission = permissions[i] 130 | if (grantStatus[i] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { 131 | // 用户授权,可以继续访问目标操作 132 | permissionResult.grantedPermissions.push(permission) 133 | } else { 134 | // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限 135 | permissionResult.deniedPermissions.push(permission) 136 | } 137 | } 138 | permissionResult.allGranted = permissionResult.grantedPermissions.length == permissions.length 139 | return permissionResult 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /utilcode/src/main/ets/throttle_debounce/debounce.ts: -------------------------------------------------------------------------------- 1 | // 防抖使用参考:https://github.com/niksy/throttle-debounce 2 | type DebounceOptions = { 3 | atBegin?: boolean; 4 | } 5 | 6 | /* eslint-disable no-undefined */ 7 | 8 | import {throttle, AnyRetFunc,ThrottleFuncInterface} from './throttle'; 9 | 10 | /** 11 | * Debounce execution of a function. Debouncing, unlike throttling, 12 | * guarantees that a function is only executed a single time, either at the 13 | * very beginning of a series of calls, or at the very end. 14 | * 15 | * @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful. 16 | * @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is, 17 | * to `callback` when the debounced-function is executed. 18 | * @param {object} [options] - An object to configure options. 19 | * @param {boolean} [options.atBegin] - Optional, defaults to false. If atBegin is false or unspecified, callback will only be executed `delay` milliseconds 20 | * after the last debounced-function call. If atBegin is true, callback will be executed only at the first debounced-function call. 21 | * (After the throttled-function has not been called for `delay` milliseconds, the internal counter is reset). 22 | * 23 | * @returns {Function} A new, debounced function. 24 | */ 25 | export function debounce(delay:number, callback:T, options?:DebounceOptions):ThrottleFuncInterface { 26 | const { atBegin = false } = options || {}; 27 | return throttle(delay, callback, { debounceMode: atBegin !== false }); 28 | } 29 | 30 | /** 31 | * 防抖 (Debouncing) 32 | * 适用场景: 33 | 搜索框实时搜索:用户在搜索框输入时,只有在用户停止输入一定时间后才发起搜索请求,避免对每个键盘输入都进行处理。 34 | 窗口大小调整(resize):只在用户完成窗口大小调整一段时间后,才进行相关的布局计算和更新,避免频繁调整造成的性能问题。 35 | 表单验证:在用户停止输入后延迟执行验证逻辑,减少验证频率。 36 | * @param wait 37 | * @param options 38 | * @returns 39 | */ 40 | export function Debounce(wait: number, options?: DebounceOptions): MethodDecorator { 41 | return function (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) { 42 | const originalMethod = descriptor.value; 43 | const debouncedMethod = debounce(wait, originalMethod, options); 44 | descriptor.value = debouncedMethod; 45 | return descriptor; 46 | }; 47 | } -------------------------------------------------------------------------------- /utilcode/src/main/ets/throttle_debounce/throttle.ts: -------------------------------------------------------------------------------- 1 | // 限流使用参考:https://github.com/niksy/throttle-debounce 2 | 3 | type ThrottleOptions = { 4 | noTrailing?: boolean; 5 | noLeading?: boolean; 6 | debounceMode?: boolean; 7 | } 8 | 9 | export type AnyRetFunc = (...args: any) => void 10 | 11 | export interface ThrottleFuncInterface { 12 | (...param: Parameters): void, 13 | 14 | cancel(p: { upcomingOnly: boolean }): void 15 | } 16 | 17 | /* eslint-disable no-undefined,no-param-reassign,no-shadow */ 18 | 19 | /** 20 | * Throttle execution of a function. Especially useful for rate limiting 21 | * execution of handlers on events like resize and scroll. 22 | * 23 | * @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) 24 | * are most useful. 25 | * @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, 26 | * as-is, to `callback` when the throttled-function is executed. 27 | * @param {object} [options] - An object to configure options. 28 | * @param {boolean} [options.noTrailing] - Optional, defaults to false. If noTrailing is true, callback will only execute every `delay` milliseconds 29 | * while the throttled-function is being called. If noTrailing is false or unspecified, callback will be executed 30 | * one final time after the last throttled-function call. (After the throttled-function has not been called for 31 | * `delay` milliseconds, the internal counter is reset). 32 | * @param {boolean} [options.noLeading] - Optional, defaults to false. If noLeading is false, the first throttled-function call will execute callback 33 | * immediately. If noLeading is true, the first the callback execution will be skipped. It should be noted that 34 | * callback will never executed if both noLeading = true and noTrailing = true. 35 | * @param {boolean} [options.debounceMode] - If `debounceMode` is true (at begin), schedule `clear` to execute after `delay` ms. If `debounceMode` is 36 | * false (at end), schedule `callback` to execute after `delay` ms. 37 | * 38 | * @returns {Function} A new, throttled, function. 39 | */ 40 | export function throttle(delay: number, callback: T, options?: ThrottleOptions): ThrottleFuncInterface { 41 | const { 42 | noTrailing = false, 43 | noLeading = false, 44 | debounceMode = undefined 45 | } = options || {}; 46 | /* 47 | * After wrapper has stopped being called, this timeout ensures that 48 | * `callback` is executed at the proper times in `throttle` and `end` 49 | * debounce modes. 50 | */ 51 | let timeoutID; 52 | let cancelled = false; 53 | 54 | // Keep track of the last time `callback` was executed. 55 | let lastExec = 0; 56 | 57 | // Function to clear existing timeout 58 | function clearExistingTimeout() { 59 | if (timeoutID) { 60 | clearTimeout(timeoutID); 61 | } 62 | } 63 | 64 | // Function to cancel next exec 65 | function cancel(options) { 66 | const { upcomingOnly = false } = options || {}; 67 | clearExistingTimeout(); 68 | cancelled = !upcomingOnly; 69 | } 70 | 71 | /* 72 | * The `wrapper` function encapsulates all of the throttling / debouncing 73 | * functionality and when executed will limit the rate at which `callback` 74 | * is executed. 75 | */ 76 | function wrapper(...arguments_) { 77 | let self = this; 78 | let elapsed = Date.now() - lastExec; 79 | 80 | if (cancelled) { 81 | return; 82 | } 83 | 84 | // Execute `callback` and update the `lastExec` timestamp. 85 | function exec() { 86 | lastExec = Date.now(); 87 | callback.apply(self, arguments_); 88 | } 89 | 90 | /* 91 | * If `debounceMode` is true (at begin) this is used to clear the flag 92 | * to allow future `callback` executions. 93 | */ 94 | function clear() { 95 | timeoutID = undefined; 96 | } 97 | 98 | if (!noLeading && debounceMode && !timeoutID) { 99 | /* 100 | * Since `wrapper` is being called for the first time and 101 | * `debounceMode` is true (at begin), execute `callback` 102 | * and noLeading != true. 103 | */ 104 | exec(); 105 | } 106 | 107 | clearExistingTimeout(); 108 | 109 | if (debounceMode === undefined && elapsed > delay) { 110 | if (noLeading) { 111 | /* 112 | * In throttle mode with noLeading, if `delay` time has 113 | * been exceeded, update `lastExec` and schedule `callback` 114 | * to execute after `delay` ms. 115 | */ 116 | lastExec = Date.now(); 117 | if (!noTrailing) { 118 | timeoutID = setTimeout(debounceMode ? clear : exec, delay); 119 | } 120 | } else { 121 | /* 122 | * In throttle mode without noLeading, if `delay` time has been exceeded, execute 123 | * `callback`. 124 | */ 125 | exec(); 126 | } 127 | } else if (noTrailing !== true) { 128 | /* 129 | * In trailing throttle mode, since `delay` time has not been 130 | * exceeded, schedule `callback` to execute `delay` ms after most 131 | * recent execution. 132 | * 133 | * If `debounceMode` is true (at begin), schedule `clear` to execute 134 | * after `delay` ms. 135 | * 136 | * If `debounceMode` is false (at end), schedule `callback` to 137 | * execute after `delay` ms. 138 | */ 139 | timeoutID = setTimeout( 140 | debounceMode ? clear : exec, 141 | debounceMode === undefined ? delay - elapsed : delay 142 | ); 143 | } 144 | } 145 | 146 | wrapper.cancel = cancel; 147 | 148 | // Return the wrapper function. 149 | return wrapper; 150 | } 151 | 152 | /** 153 | * 限流 (Throttling) 点击防抖用这个 154 | * 适用场景: 155 | 滚动事件处理:在滚动时按固定时间间隔处理事件,比如无限滚动加载内容。 156 | 监控浏览器的滚动位置:定期检查滚动位置来决定是否显示或隐藏页面上的元素。 157 | 游戏控制:限制用户操作的频率,如每秒只能发射一次子弹。 158 | * @param wait 159 | * @param options 160 | * @returns 161 | */ 162 | export function Throttle(wait: number, options?: ThrottleOptions): MethodDecorator { 163 | return function (_target: Object, _propertyKey: string | symbol, descriptor: PropertyDescriptor) { 164 | const originalMethod = descriptor.value; 165 | const throttledMethod = throttle(wait, originalMethod, options); 166 | descriptor.value = throttledMethod; 167 | return descriptor; 168 | }; 169 | } -------------------------------------------------------------------------------- /utilcode/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "utilcode", 4 | "type": "har", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet", 8 | "2in1" 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /utilcode/src/main/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "page_show", 5 | "value": "page from package" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /utilcode/src/main/resources/en_US/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "page_show", 5 | "value": "page from package" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /utilcode/src/main/resources/zh_CN/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "page_show", 5 | "value": "page from package" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /utilcode/src/mock/mock-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /utilcode/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }) 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }) 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }) 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }) 25 | it('assertContain', 0, () => { 26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); 28 | let a = 'abc'; 29 | let b = 'b'; 30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 31 | expect(a).assertContain(b); 32 | expect(a).assertEqual(a); 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /utilcode/src/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test'; 2 | 3 | export default function testsuite() { 4 | abilityTest(); 5 | } -------------------------------------------------------------------------------- /utilcode/src/ohosTest/ets/testability/TestAbility.ets: -------------------------------------------------------------------------------- 1 | import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 2 | import { abilityDelegatorRegistry } from '@kit.TestKit'; 3 | import { hilog } from '@kit.PerformanceAnalysisKit'; 4 | import { window } from '@kit.ArkUI'; 5 | import { Hypium } from '@ohos/hypium'; 6 | import testsuite from '../test/List.test'; 7 | 8 | export default class TestAbility extends UIAbility { 9 | onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 10 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); 11 | hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); 12 | hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? ''); 13 | let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; 14 | abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); 15 | let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; 16 | abilityDelegatorArguments = abilityDelegatorRegistry.getArguments(); 17 | hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); 18 | Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite); 19 | } 20 | 21 | onDestroy() { 22 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); 23 | } 24 | 25 | onWindowStageCreate(windowStage: window.WindowStage) { 26 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); 27 | windowStage.loadContent('testability/pages/Index', (err) => { 28 | if (err.code) { 29 | hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 30 | return; 31 | } 32 | hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); 33 | }); 34 | } 35 | 36 | onWindowStageDestroy() { 37 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); 38 | } 39 | 40 | onForeground() { 41 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); 42 | } 43 | 44 | onBackground() { 45 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); 46 | } 47 | } -------------------------------------------------------------------------------- /utilcode/src/ohosTest/ets/testability/pages/Index.ets: -------------------------------------------------------------------------------- 1 | @Entry 2 | @Component 3 | struct Index { 4 | @State message: string = 'Hello World'; 5 | 6 | build() { 7 | Row() { 8 | Column() { 9 | Text(this.message) 10 | .fontSize(50) 11 | .fontWeight(FontWeight.Bold) 12 | } 13 | .width('100%') 14 | } 15 | .height('100%') 16 | } 17 | } -------------------------------------------------------------------------------- /utilcode/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets: -------------------------------------------------------------------------------- 1 | import { abilityDelegatorRegistry, TestRunner } from '@kit.TestKit'; 2 | import { UIAbility, Want } from '@kit.AbilityKit'; 3 | import { BusinessError } from '@kit.BasicServicesKit'; 4 | import { hilog } from '@kit.PerformanceAnalysisKit'; 5 | import { resourceManager } from '@kit.LocalizationKit'; 6 | import { util } from '@kit.ArkTS'; 7 | 8 | let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; 9 | let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; 10 | let jsonPath: string = 'mock/mock-config.json'; 11 | let tag: string = 'testTag'; 12 | 13 | async function onAbilityCreateCallback(data: UIAbility) { 14 | hilog.info(0x0000, 'testTag', 'onAbilityCreateCallback, data: ${}', JSON.stringify(data)); 15 | } 16 | 17 | async function addAbilityMonitorCallback(err: BusinessError) { 18 | hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); 19 | } 20 | 21 | export default class OpenHarmonyTestRunner implements TestRunner { 22 | constructor() { 23 | } 24 | 25 | onPrepare() { 26 | hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare'); 27 | } 28 | 29 | async onRun() { 30 | let tag = 'testTag'; 31 | hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun run'); 32 | abilityDelegatorArguments = abilityDelegatorRegistry.getArguments() 33 | abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator() 34 | let moduleName = abilityDelegatorArguments.parameters['-m']; 35 | let context = abilityDelegator.getAppContext().getApplicationContext().createModuleContext(moduleName); 36 | let mResourceManager = context.resourceManager; 37 | await checkMock(abilityDelegator, mResourceManager); 38 | const bundleName = abilityDelegatorArguments.bundleName; 39 | const testAbilityName: string = 'TestAbility'; 40 | let lMonitor: abilityDelegatorRegistry.AbilityMonitor = { 41 | abilityName: testAbilityName, 42 | onAbilityCreate: onAbilityCreateCallback, 43 | moduleName: moduleName 44 | }; 45 | abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) 46 | const want: Want = { 47 | bundleName: bundleName, 48 | abilityName: testAbilityName, 49 | moduleName: moduleName 50 | }; 51 | abilityDelegator.startAbility(want, (err: BusinessError, data: void) => { 52 | hilog.info(0x0000, tag, 'startAbility : err : %{public}s', JSON.stringify(err) ?? ''); 53 | hilog.info(0x0000, tag, 'startAbility : data : %{public}s', JSON.stringify(data) ?? ''); 54 | }) 55 | hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun end'); 56 | } 57 | } 58 | 59 | async function checkMock(abilityDelegator: abilityDelegatorRegistry.AbilityDelegator, resourceManager: resourceManager.ResourceManager) { 60 | let rawFile: Uint8Array; 61 | try { 62 | rawFile = resourceManager.getRawFileContentSync(jsonPath); 63 | hilog.info(0x0000, tag, 'MockList file exists'); 64 | let mockStr: string = util.TextDecoder.create('utf-8', { ignoreBOM: true }).decodeWithStream(rawFile); 65 | let mockMap: Record = getMockList(mockStr); 66 | try { 67 | abilityDelegator.setMockList(mockMap) 68 | } catch (error) { 69 | let code = (error as BusinessError).code; 70 | let message = (error as BusinessError).message; 71 | hilog.error(0x0000, tag, `abilityDelegator.setMockList failed, error code: ${code}, message: ${message}.`); 72 | } 73 | } catch (error) { 74 | let code = (error as BusinessError).code; 75 | let message = (error as BusinessError).message; 76 | hilog.error(0x0000, tag, `ResourceManager:callback getRawFileContent failed, error code: ${code}, message: ${message}.`); 77 | } 78 | } 79 | 80 | function getMockList(jsonStr: string) { 81 | let jsonObj: Record = JSON.parse(jsonStr); 82 | let map: Map = new Map(Object.entries(jsonObj)); 83 | let mockList: Record = {}; 84 | map.forEach((value: object, key: string) => { 85 | let realValue: string = value['source'].toString(); 86 | mockList[key] = realValue; 87 | }); 88 | hilog.info(0x0000, tag, '%{public}s', 'mock-json value:' + JSON.stringify(mockList) ?? ''); 89 | return mockList; 90 | } -------------------------------------------------------------------------------- /utilcode/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "utilcode_test", 4 | "type": "feature", 5 | "description": "$string:module_test_desc", 6 | "mainElement": "TestAbility", 7 | "deviceTypes": [ 8 | "default", 9 | "tablet", 10 | "2in1" 11 | ], 12 | "deliveryWithInstall": true, 13 | "installationFree": false, 14 | "pages": "$profile:test_pages", 15 | "abilities": [ 16 | { 17 | "name": "TestAbility", 18 | "srcEntry": "./ets/testability/TestAbility.ets", 19 | "description": "$string:TestAbility_desc", 20 | "icon": "$media:icon", 21 | "label": "$string:TestAbility_label", 22 | "exported": true, 23 | "startWindowIcon": "$media:icon", 24 | "startWindowBackground": "$color:start_window_background", 25 | "skills": [ 26 | { 27 | "actions": [ 28 | "action.system.home" 29 | ], 30 | "entities": [ 31 | "entity.system.home" 32 | ] 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /utilcode/src/ohosTest/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#FFFFFF" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /utilcode/src/ohosTest/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_test_desc", 5 | "value": "test ability description" 6 | }, 7 | { 8 | "name": "TestAbility_desc", 9 | "value": "the test ability" 10 | }, 11 | { 12 | "name": "TestAbility_label", 13 | "value": "test label" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /utilcode/src/ohosTest/resources/base/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanranran/HarmonyUtilCode/9751d1af45ed7c847e636f760f9c38168f1e479a/utilcode/src/ohosTest/resources/base/media/icon.png -------------------------------------------------------------------------------- /utilcode/src/ohosTest/resources/base/profile/test_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "testability/pages/Index" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /utilcode/src/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import localUnitTest from './LocalUnit.test'; 2 | 3 | export default function testsuite() { 4 | localUnitTest(); 5 | } -------------------------------------------------------------------------------- /utilcode/src/test/LocalUnit.test.ets: -------------------------------------------------------------------------------- 1 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 2 | import { JsonUtils } from '../../Index'; 3 | 4 | export default function localUnitTest() { 5 | describe('localUnitTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }); 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }); 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }); 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }); 25 | it('assertContain', 0, () => { 26 | 27 | }); 28 | }); 29 | } --------------------------------------------------------------------------------