commit 3936d39f7ad09431547dfd68fd01475071c66c36 Author: wangyu Date: Thu Mar 5 18:33:30 2026 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2ff201 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000..0dfa6ea --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.ylqh.hm_cube", + "vendor": "devcon", + "versionCode": 210, + "versionName": "2.1.0", + "icon": "$media:layer_logo", + "label": "$string:app_name" + } +} \ No newline at end of file diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json new file mode 100644 index 0000000..3da3fac --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,28 @@ +{ + "string": [ + { + "name": "app_name", + "value": "素材魔方" + }, + { + "name": "oaid_reason", + "value": "为了更好的为您提供服务,请求授权广告跨应用关联访问权限" + }, + { + "name": "read_pasteboard_reason", + "value": "为了方便识别您复制的链接并跳转相关页面,请求授权剪贴板读取权限" + }, + { + "name": "privacy_content", + "value": "请你务必审慎阅读、充分理解服务协议和隐私政策各条款,包括但不限于:为了更好的向你提供服务,我们需要访问你的相册、位置信息等。你可阅读《隐私政策》了解详细信息。如果你同意,请点击下面同意按钮开始接受我们的服务。" + }, + { + "name": "wx_video_course", + "value": "1、点击【添加助手】,自动跳转进入微信创建快存助手;\n2、进入微信视频号,选择视频分享至-客服消息-素材助手;\n3、返回【素材魔方APP】微信视频号页面,下拉刷新即可下载微信号视频;" + }, + { + "name": "wx_playback_course", + "value": "1、点击【添加直播回放助手】;\n2、自动跳转进入微信小程序,扫二维码加入群聊;\n3、成功加入会自动绑定您的ID,若显示绑定失败可回到我们APP,点击复制我的ID后发送至群聊即可重新绑定;\n4、绑定成功就可以去微信直播回放页面,选择直播回放视频分享到群聊里,返回我们APP刷新即可;" + } + ] +} diff --git a/AppScope/resources/base/media/ic_launcher_background.png b/AppScope/resources/base/media/ic_launcher_background.png new file mode 100644 index 0000000..c7ac492 Binary files /dev/null and b/AppScope/resources/base/media/ic_launcher_background.png differ diff --git a/AppScope/resources/base/media/ic_launcher_foreground.png b/AppScope/resources/base/media/ic_launcher_foreground.png new file mode 100644 index 0000000..2eb527b Binary files /dev/null and b/AppScope/resources/base/media/ic_launcher_foreground.png differ diff --git a/AppScope/resources/base/media/layer_logo.json b/AppScope/resources/base/media/layer_logo.json new file mode 100644 index 0000000..e5be61f --- /dev/null +++ b/AppScope/resources/base/media/layer_logo.json @@ -0,0 +1,6 @@ +{ + "layered-image": { + "foreground": "$media:ic_launcher_foreground", + "background": "$media:ic_launcher_background" + } +} \ No newline at end of file diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000..3a98672 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,97 @@ +{ + "app": { + "signingConfigs": [ + { + "name": "release", + "type": "HarmonyOS", + "material": { + "storeFile": "D:/android/签名/hmos/hmos.p12", + "storePassword": "000000188D653F4B8748579F0084CAC913B6D6AB6FF60084FB0F318CC52AC425A874B607DD8261F9", + "keyAlias": "__uni__1be0b2f", + "keyPassword": "00000018779445CEA4B7EA75CB8F722F9DA76CBDBC5CE7C083CE8B5E1973141BF17E654C7237F5EF", + "signAlg": "SHA256withECDSA", + "profile": "D:/android/签名/hmos/素材魔方/cube_releaseRelease.p7b", + "certpath": "D:/android/签名/hmos/素材魔方/cube_release.cer" + } + }, + { + "name": "debug", + "type": "HarmonyOS", + "material": { + "storeFile": "D:/android/签名/hmos/hmos.p12", + "storePassword": "000000188D653F4B8748579F0084CAC913B6D6AB6FF60084FB0F318CC52AC425A874B607DD8261F9", + "keyAlias": "__uni__1be0b2f", + "keyPassword": "00000018779445CEA4B7EA75CB8F722F9DA76CBDBC5CE7C083CE8B5E1973141BF17E654C7237F5EF", + "signAlg": "SHA256withECDSA", + "profile": "D:/android/签名/hmos/素材魔方/cube_debugDebug.p7b", + "certpath": "D:/android/签名/hmos/素材魔方/cube_debug.cer" + } + } + ], + "products": [ + { + "name": "default", + "signingConfig": "release", + "targetSdkVersion": "5.1.0(18)", + "compatibleSdkVersion": "5.0.4(16)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + }, + { + "name": "debug", + "signingConfig": "debug", + "targetSdkVersion": "5.1.0(18)", + "compatibleSdkVersion": "5.0.4(16)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release", + "buildOption": { + "nativeLib": { + "filter": { + //根据正则表达式排除匹配到的.so文件,匹配到的so文件将不会被打包 + "excludes": [ + "**/x86_64/*.so", + "**/armeabi-v7a/*.so" + //排除所有x86_64架构的so文件 + ], + //允许当.so重名冲突时,使用高优先级的.so文件覆盖低优先级的.so文件 + "enableOverride": true + } + } + } + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default", + "debug" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/code-linter.json5 b/code-linter.json5 new file mode 100644 index 0000000..073990f --- /dev/null +++ b/code-linter.json5 @@ -0,0 +1,32 @@ +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/entry/.gitignore b/entry/.gitignore new file mode 100644 index 0000000..e2713a2 --- /dev/null +++ b/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/entry/build-profile.json5 b/entry/build-profile.json5 new file mode 100644 index 0000000..4d61187 --- /dev/null +++ b/entry/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/entry/hvigorfile.ts b/entry/hvigorfile.ts new file mode 100644 index 0000000..c6edcd9 --- /dev/null +++ b/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/entry/obfuscation-rules.txt b/entry/obfuscation-rules.txt new file mode 100644 index 0000000..1781290 --- /dev/null +++ b/entry/obfuscation-rules.txt @@ -0,0 +1,28 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation +-keep-property-name +quickLoginAnonymousPhone + +-keep +./oh_modules/@ohos/imageknifepro \ No newline at end of file diff --git a/entry/oh-package-lock.json5 b/entry/oh-package-lock.json5 new file mode 100644 index 0000000..52698da --- /dev/null +++ b/entry/oh-package-lock.json5 @@ -0,0 +1,72 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@getui/gtc@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/GTC-HM-1.0.5-20241203.har": "@getui/gtc@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/GTC-HM-1.0.5-20241203.har", + "@getui/gysdk@1.0.10": "@getui/gysdk@1.0.10", + "@ohos/axios@^2.2.6": "@ohos/axios@2.2.6", + "cmccssosdk@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har": "cmccssosdk@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har", + "ctaccount@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har": "ctaccount@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har", + "dljson@../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har": "dljson@../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har", + "unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har": "unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har" + }, + "packages": { + "@getui/gtc@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/GTC-HM-1.0.5-20241203.har": { + "name": "@getui/gtc", + "version": "1.0.5", + "resolved": "../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/GTC-HM-1.0.5-20241203.har", + "registryType": "local" + }, + "@getui/gysdk@1.0.10": { + "name": "@getui/gysdk", + "version": "1.0.10", + "integrity": "sha512-wsPJMEtrDgVi3uWizD1Yg96hkoK9uhPUzXkR6daNGsxHaf0bRtjg79aRDkE7o6xTHNyOVkPI/1l/5cDiZqbIow==", + "resolved": "https://repo.harmonyos.com/ohpm/@getui/gysdk/-/gysdk-1.0.10.har", + "registryType": "ohpm", + "dependencies": { + "@getui/gtc": "file:./libs/GTC-HM-1.0.5-20241203.har", + "unicom_login_harmony": "file:./libs/unicom_login_harmony_v1.0.4AR001B0214.har", + "cmccssosdk": "file:./libs/quick_login_hm_1.0.2.har", + "ctaccount": "file:./libs/ctaccount_v1.1.2.har" + } + }, + "@ohos/axios@2.2.6": { + "name": "@ohos/axios", + "version": "2.2.6", + "integrity": "sha512-A1JqGe6XaeqWyjQETitFW4EkubQS7Fv7h0YG5a/ry3/a/vOgVGzwC4y5KAhvMzVv1tYjfY0ntMtV2kJGlmOHcQ==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/axios/-/axios-2.2.6.har", + "registryType": "ohpm" + }, + "cmccssosdk@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har": { + "name": "cmccssosdk", + "version": "1.0.0", + "resolved": "../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har", + "registryType": "local" + }, + "ctaccount@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har": { + "name": "ctaccount", + "version": "1.1.2", + "resolved": "../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har", + "registryType": "local", + "dependencies": { + "dljson": "file:library/dlJson.har" + } + }, + "dljson@../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har": { + "name": "dljson", + "version": "0.0.5", + "resolved": "../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har", + "registryType": "local" + }, + "unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har": { + "name": "unicom_login_harmony", + "version": "1.0.4", + "resolved": "../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/entry/oh-package.json5 b/entry/oh-package.json5 new file mode 100644 index 0000000..3b94d70 --- /dev/null +++ b/entry/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "@ohos/axios": "^2.2.6", + "@getui/gysdk": "1.0.10" + } +} + diff --git a/entry/src/main/ets/MyAbilityStage.ets b/entry/src/main/ets/MyAbilityStage.ets new file mode 100644 index 0000000..341953f --- /dev/null +++ b/entry/src/main/ets/MyAbilityStage.ets @@ -0,0 +1,9 @@ +import { AbilityStage } from '@kit.AbilityKit'; +import { PrefUtils } from './utils/PrefUtils'; + +export default class MyAbilityStage extends AbilityStage { + + onCreate(): void { + PrefUtils.init(this.context) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/callback/DialogCallback.ets b/entry/src/main/ets/callback/DialogCallback.ets new file mode 100644 index 0000000..5d54288 --- /dev/null +++ b/entry/src/main/ets/callback/DialogCallback.ets @@ -0,0 +1,5 @@ +export declare class DialogCallback { + confirm?: () => void; + + cancel?: () => void; +} \ No newline at end of file diff --git a/entry/src/main/ets/common/Constants.ets b/entry/src/main/ets/common/Constants.ets new file mode 100644 index 0000000..5bc2755 --- /dev/null +++ b/entry/src/main/ets/common/Constants.ets @@ -0,0 +1,19 @@ +export class Constants { + static readonly BASE_URL = "https://material.u8t.cn" //正式地址 + static readonly TEST_URL = "https://material.u8t.cn" //测试地址 + + static readonly WEB_URL = "" //网页版地址 + + static readonly APP_ID = "10040" + // + static readonly WX_APP_ID = "wx19e5013ad43754c8" //微信APPID + static readonly MINI_PROGRAM_APP_ID = "gh_b38277bd004a" //小程序 + static readonly UMENG_APP_KEY = "6883311979267e0210c15cb9" //友盟 + // + static readonly USER_AGREEMENT = "https://material.u8t.cn/static/policy/user.html" //用户协议 + static readonly PRIVACY_POLICY = "https://material.u8t.cn/static/policy/provacy.html" //隐私政策 + static readonly RENEW_AGREEMENT = "https://material.u8t.cn/static/policy/renew.html" //自动续费协议 + // + static readonly ENCRYPT = "wE8x4EnIHgyGOyjnoluzI2vk60wz5eNI" + static readonly SIGNATURE = "hfLLOtXRjd0e1Ac7O6sAXrECH2E828S9" +} \ No newline at end of file diff --git a/entry/src/main/ets/common/EventConstants.ets b/entry/src/main/ets/common/EventConstants.ets new file mode 100644 index 0000000..8a28b1f --- /dev/null +++ b/entry/src/main/ets/common/EventConstants.ets @@ -0,0 +1,272 @@ +export class EventConstants { + /*---------------------------------------------APP事件---------------------------------------------*/ + static readonly LoginSuccessEvent = "LoginSuccessEvent" + static readonly LogoutSuccessEvent = "LogoutSuccessEvent" + static readonly HomeRefreshEvent = "HomeRefreshEvent" + static readonly RecordRefreshEvent = "RecordRefreshEvent" + static readonly MineRefreshEvent = "MineRefreshEvent" + static readonly MediaActionEvent = "MediaActionEvent" + static readonly JumpToRecordEvent = "JumpToRecordEvent" + static readonly DownloadHistoryRefreshEvent = "DownloadHistoryRefreshEvent" + + + /*-------------------------------------------客户端上报事件-------------------------------------------*/ + static readonly APP_LAUNCH = "client.launch" //app启动 + + static readonly GUIDE_LAUNCH = "client.guide.launch" //引导页启动 + + static readonly GUIDE_OPPORTUNITY_SCROLL = "client.guide.content.scroll" //滑动切换引导页内容 + + static readonly GUIDE_SKIP = "client.guide.pay.skip" //跳过引导页支付 + + static readonly APP_ACTIVE = "client.home.active" //app激活 + + static readonly HOME_BOTTOM_TAB_CHECK = "client.main.bottom.tab.check" //底部tab切换 + + static readonly HOME_NOTICE_CHECK = "client.main.notice.check" //首页通知点击 + + static readonly GOODS_SELECT = "client.goods.select" //点击切换支付的会员类型 + + static readonly PAY_SELECT = "client.pay.select" //点击切换支付类型 + + static readonly PAY_PAY = "client.pay.pay" //支付按钮点击 + + static readonly CHALLENGE_TASK_PAY_PAY = "client.challenge.task.pay.pay" //0元挑战支付按钮点击 + + static readonly PAY_SUCCESS = "client.pay.success" //支付成功 + + static readonly PAY_CANCEL = "client.pay.cancel" //支付取消 + + static readonly ERROR_CLIENT_WXPAY_ERR = "client.wxpay.err" //微信支付失败 + + static readonly ERROR_CLIENT_ALIPAY_ERR = "client.alipay.err" //支付宝支付失败 + + static readonly ERROR_CLIENT_DOWNLOAD_IMG = "client.download.img.err" //图片下载失败 + + static readonly ERROR_CLIENT_DOWNLOAD_VIDEO = "client.download.video.err" //视频下载失败 + + static readonly ERROR_CLIENT_DOWNLOAD_AUDIO = "client.download.audio.err" //音频下载失败 + + static readonly CANCEL_DOWNLOAD_VIDEO = "client.download.video.cancel" //取消视频下载 + + static readonly PAUSE_DOWNLOAD_VIDEO = "client.download.video.pause" //暂停视频下载 + + static readonly CONTINUE_DOWNLOAD_VIDEO = "client.download.video.continue" //继续视频下载 + + static readonly RESTART_DOWNLOAD_VIDEO = "client.download.video.restart" //重新视频下载 + + static readonly SPEED_UP_DOWNLOAD_VIDEO = "client.download.video.speed.up" //加速视频下载 + + static readonly BACKGROUND_CLIENT_DOWNLOAD = "client.download.background" //后台下载 + + static readonly FLOAT_WINDOW_CLICK = "client.float.window.click" //点击悬浮窗 + + static readonly PKG_UPDATE = "client.pkg.update" //升级弹窗点击更新 + + static readonly PKG_CANCEL = "client.pkg.cancel" //升级弹窗点击取消 + + static readonly GET_MATERIAL = "client.get.material" //获取素材 + + static readonly GET_MATERIAL_CANCEL = "client.get.material.cancel" //取消获取素材 + + static readonly DIALOG_CONFIRM_SAVE_FILE = "client.dialog.confirm.save.file" //保存文件地址弹框确认 + + static readonly DIALOG_GO_TO_VIEW = "client.dialog.go.to.view" //前往保存文件的地址查看 + + static readonly JUMP_TO_ABOUT_US = "client.jump.to.about.us" //界面跳转 + + static readonly JUMP_TO_LINK_EXTRACT = "client.jump.to.link.extract" //跳转链接提取 + + static readonly JUMP_TO_WECHAT_VIDEO = "client.jump.to.wechat.video" //跳转视频号 + + static readonly JUMP_TO_WECHAT_PLAYBACK = "client.jump.to.wechat.video.playback" //跳转直播回放 + + static readonly JUMP_TO_COURSE_WX_VIDEO = "client.course.wechat.video" //视频号视频教程 + + static readonly JUMP_TO_COURSE_PLAYBACK = "client.course.playback" //直播回放视频教程 + + static readonly JUMP_TO_HOME_TOOL = "client.jump.to.home.tool" //跳转首页工具 + + static readonly MAIN_CENTER_ENABLE = "client.main.center.enable" //首页跳转个人中心 + + static readonly JUMP_TO_MEMBER_RECHARGE = "client.jump.to.member.recharge" //跳转到充值页 + + static readonly JUMP_TO_LOGIN = "client.jump.to.login" //跳转到登录页 + + static readonly JUMP_TO_SYSTEM_SETTING = "client.jump.to.system.setting" //跳转到系统设置 + + static readonly JUMP_TO_USER_SETTING = "client.jump.to.user.setting" //跳转到用户设置 + + static readonly JUMP_TO_FEEDBACK = "client.jump.to.feedback" //跳转到意见反馈 + + static readonly JUMP_TO_ACCOUNT_BIND = "client.jump.to.account.bind" //跳转到账号绑定 + + static readonly JUMP_TO_ACCOUNT_MANAGE = "client.jump.to.account.manage" //跳转到账号管理 + + static readonly JUMP_TO_DOWNLOAD_HISTORY = "client.jump.to.download.history" //跳转到下载记录 + + static readonly JUMP_TO_DOWNLOAD_TASK_LIST = "client.jump.to.download.task.list" //跳转到下载任务列表 + + static readonly JUMP_TO_RECHARGE_DIAMOND = "client.jump.to.recharge.diamond" //跳转到钻石充值 + + static readonly JUMP_TO_COUPON_LIST = "client.jump.to.coupon.list" //跳转优惠券列表 + + static readonly JUMP_TO_CHALLENGE_TASK = "client.jump.to.challenge.task" //跳转到0元挑战 + + static readonly JUMP_TO_SHARE_WX_VIDEO = "client.jump.to.wechat.share.video" //跳转到视频号分享 + + static readonly JUMP_TO_SHARE_WX_PLAYBACK = "client.jump.to.wechat.share.playback" //跳转到直播回放分享 + + static readonly JUMP_TO_COURSE = "client.jump.to.course" //跳转到指导教程 + + static readonly DOWNLOAD_FILE = "client.download.file" //下载文件 + + static readonly TRANSPOND_FILE = "client.transpond.file" //转发文件 + + static readonly MATERIAL_COPY_TEXT = "client.material.copy.text" //复制文字 + + static readonly MATERIAL_TYPE_CHECK = "client.material.type.check" //素材切换 + + static readonly MATERIAL_ALL_SELECT = "client.material.all.select" //全部选中素材 + + static readonly MATERIAL_SELECT = "client.material.select" //选择素材 + + static readonly TOOLS_VIDEO_EXTRACT_AUDIO = "client.tools.video.audio" //提取音频 + + static readonly MATERIAL_PLAY_VIDEO = "client.material.play.video" //播发视频 + + static readonly GET_CODE = "client.get.code" //获取验证码 + + static readonly LOGIN = "client.login" //登录 + + static readonly SWITCH_ACCOUNT = "client.switch.account" //切换账户 + + static readonly ACCOUNT_BIND = "client.account.bind" //绑定账号 + + static readonly ACCOUNT_BIND_CANCEL = "client.account.bind.cancel" //取消绑定账号 + + static readonly CHECK_AGREEMENT = "client.check.agreement" //切换协议状态 + + static readonly VIEW_AGREEMENT = "client.view.agreement" //查看用户协议 + + static readonly PRIVACY_POLICY_CLICK_OK = "client.privacy.policy.click.ok" //同意隐私协议 + + static readonly SHARE_APP = "client.share.app" //分享app + + static readonly CLEAR_CACHE = "client.clear.cache" //清除缓存 + + static readonly CONTACT_SERVICE = "client.contact.service" //联系客服 + + static readonly EXIT_LOGIN = "client.exit.login" //退出登录 + + static readonly CANCEL_ACCOUNT = "client.cancel.account" //注销账户 + + static readonly MEMBER_FORCE_LOGIN = "client.member.force.login" //会员强制登录 + + static readonly GET_MATERIAL_TIMES_USE_UP = "client.times.use.up.get.material" //获取素材次数已用完 + + static readonly PICTURE_HANDLE_TIMES_USE_UP = "client.times.use.up.picture.handle" //图片处理次数已用完 + + static readonly CHECK_LOGIN_TYPE = "client.check.login.type" //切换登录方式 + + static readonly OPEN_SCREEN_AD_SHOW = "client.ad.open.screen.show" //开屏广告展示 + + static readonly OPEN_SCREEN_AD_SKIP = "client.ad.open.screen.skip" //开屏广告跳过 + + static readonly OPEN_SCREEN_AD_CLICK = "client.ad.open.screen.click" //开屏广告点击 + + static readonly BANNER_AD_SHOW = "client.ad.banner.show" //banner广告展示 + + static readonly BANNER_AD_CLOSE = "client.ad.banner.close" //banner广告关闭 + + static readonly BANNER_AD_CLICK = "client.ad.banner.click" //banner广告点击 + + static readonly INSERT_SCREEN_AD_SHOW = "client.ad.insert.screen.show" //插屏广告展示 + + static readonly INSERT_SCREEN_AD_CLOSE = "client.ad.insert.screen.close" //插屏广告关闭 + + static readonly INSERT_SCREEN_AD_CLICK = "client.ad.insert.screen.click" //插屏广告点击 + + static readonly INSERT_SCREEN_AD_SKIP_VIDEO = "client.ad.insert.screen.skip.video" //跳过插屏广告 + + static readonly INCENTIVE_AD_SHOW = "client.ad.incentive.show" //激励广告展示 + + static readonly INCENTIVE_AD_CLOSE = "client.ad.incentive.close" //激励广告关闭 + + static readonly INCENTIVE_AD_REWARD = "client.ad.incentive.reward" //激励广告已获取到奖励 + + static readonly INCENTIVE_AD_SKIP_VIDEO = "client.ad.incentive.skip.video" //跳过激励广告 + + static readonly ACCOUNT_UNBIND = "client.account.unbind" //解除绑定账号 + + static readonly HISTORY_RECORD_TYPE_CHECK = "client.history.record.type.check" //历史记录切换 + + static readonly CLOSE_FREE_TIME_USES_UP_DIALOG = + "client.free.time.uses.up.dialog.close" //关闭免费次数用完的提示框 + + static readonly CHECK_FREE_TIME_USES_UP_DIALOG = + "client.free.time.uses.up.dialog.check" //免费次数用完的提示框切换充值类型 + + static readonly CONFIRM_FREE_TIME_USES_UP_DIALOG = + "client.free.time.uses.up.dialog.confirm" //确认免费次数用完的提示框 + + static readonly MULTI_DELETE_FILE = "client.multi.delete.file" //批量删除文件 + + static readonly MULTI_TRANSMIT_FILE = "client.multi.transmit.file" //批量转发文件 + + static readonly PREVIEW_DELETE_FILE = "client.preview.delete.file" //预览时删除文件 + + static readonly PREVIEW_TRANSMIT_FILE = "client.preview.transmit.file" //预览时删除文件 + + static readonly PREVIEW_HANDLE_IMAGE = "client.preview.handle.image" //预览时进行图片处理 + + static readonly TOOLS_HANDLE_IMAGE_START = "client.tools.handle.image.start" //图片处理开始 + + static readonly TOOLS_HANDLE_SAVE_IMAGE = "client.tools.handle.save.image" //保存已处理过的图片至相册 + + static readonly AUTO_SWITCH_DOWNLOAD_URL = "client.auto.switch.download.url" //自动切换下载链接 + + static readonly HAND_SWITCH_DOWNLOAD_URL = "client.hand.switch.download.url" //手动切换下载链接立即加速 + + static readonly HOME_BANNER_CLICK = "client.home.banner.click" + + static readonly COUPON_ANIMATION_PLAY = "client.coupon.animation.play" //播放优惠券动画 + + static readonly COUPON_ANIMATION_CLOSE = "client.coupon.animation.close" //关闭优惠券动画 + + static readonly COUPON_RECEIVE = "client.coupon.receive" //领取优惠券 + + static readonly COUPON_REDEEM_ENABLE = "client.coupon.redeem.enable" //优惠券兑换按钮点击 + + static readonly COUPON_REDEEM_INFO = "client.coupon.redeem.info" //优惠券兑换详情 + + static readonly COUPON_REDEEM = "client.coupon.redeem" //优惠券兑换 + + static readonly COUPON_REDEEM_SUCCESS = "client.coupon.redeem.success" //优惠券兑换成功 + + static readonly COUPON_REDEEM_SUCCESS_CONFIRM = "client.coupon.redeem.success.confirm" //优惠券兑换成功 + + static readonly COUPON_VIEW = "client.coupon.view" //查看优惠券 + + static readonly COUPON_DIALOG_CHECK = "client.coupon.dialog.check" //切换优惠券 + + static readonly COUPON_DIALOG_CLOSE = "client.coupon.dialog.close" //关闭优惠券 + + static readonly COUPON_DIALOG_CONFIRM = "client.coupon.dialog.confirm" //确认优惠券 + + static readonly COPY_USER_ID = "client.copy_user_id" //复制用户id + + static readonly SHOW_PALYBACK_HINT_DIALOG = "client.show.playback.hint.dialog" + + static readonly EXIT_APP = "client.exit.app" //退出APP + + static readonly SHOW_DIALOG = "client.show.dialog" //弹出退出app的弹框 + + static readonly START_COUPON_ANIMATION = "client.start.coupon.animation" + + static readonly CHALLENGE_TASK_SIGN_IN = "client.challenge.tasK.sign.in" //签到 + + static readonly CHALLENGE_TASK_SIGN_IN_SUCCESS = "client.challenge.tasK.sign.in.success" //签到成功 + + static readonly CHALLENGE_TASK_SIGN_IN_FAIL = "client.challenge.tasK.sign.in.fail" //签到失败 +} \ No newline at end of file diff --git a/entry/src/main/ets/common/RouterUrls.ets b/entry/src/main/ets/common/RouterUrls.ets new file mode 100644 index 0000000..d26eca5 --- /dev/null +++ b/entry/src/main/ets/common/RouterUrls.ets @@ -0,0 +1,146 @@ +export class RouterUrls { + /** + * 通用web加载页 + */ + static readonly WEB_PAGE = "pages/web/WebPage" + + /** + * 引导页 + */ + static readonly GUIDE_PAGE = "pages/guide/GuidePage" + + /** + * 登录页 + */ + static readonly LOGIN_PAGE = "pages/login/LoginPage" + + /** + * 扫码登录页 + */ + static readonly QRCODE_LOGIN_PAGE = "pages/login/qrcode/QrcodeLoginPage" + + /** + * 主页 + */ + static readonly MAIN_PAGE = "pages/main/MainPage" + + /** + * 链接提取页 + */ + static readonly TAKE_MATERIAL_PAGE = "pages/main/home/link/TakeMaterialPage" + + /** + * 提取教程页 + */ + static readonly COURSE_PAGE = "pages/main/home/course/CoursePage" + + /** + * 视频号提取页 + */ + static readonly WX_VIDEO_PAGE = "pages/main/home/wx/WxVideoPage" + + /** + * 添加水印页 + */ + static readonly ADD_WATER_MARKER_PAGE = "pages/main/home/tools/AddWaterMarkerPage" + + /** + * MD5去重页 + */ + static readonly MD5_RESET_PAGE = "pages/main/home/tools/MD5ResetPage" + + /** + * 视频倒放页 + */ + static readonly VIDEO_REVERSE_PAGE = "pages/main/home/tools/VideoReversePage" + + /** + * 视频裁剪页 + */ + static readonly VIDEO_MIRROR_PAGE = "pages/main/home/tools/VideoMirrorPage" + + /** + * 视频裁剪页 + */ + static readonly CLIP_VIDEO_PAGE = "pages/main/home/tools/ClipVideoPage" + + /** + * 去除音乐页 + */ + static readonly REMOVE_AUDIO_PAGE = "pages/main/home/tools/RemoveAudioPage" + + /** + * 添加音乐页 + */ + static readonly ADD_AUDIO_PAGE = "pages/main/home/tools/AddAudioPage" + + /** + * 视频转音频页 + */ + static readonly TAKE_AUDIO_PAGE = "pages/main/home/tools/TakeAudioPage" + + /** + * 素材详情页 + */ + static readonly MATERIAL_DETAIL_PAGE = "pages/main/home/material/MaterialDetailPage" + + /** + * 用户设置页 + */ + static readonly USER_SETTINGS_PAGE = "pages/main/mine/user/UserSettingsPage" + + /** + * vip页 + */ + static readonly VIP_PAGE = "pages/main/mine/vip/VipPage" + + /** + * 下载记录页 + */ + static readonly DOWNLOAD_HISTORY_PAGE = "pages/main/mine/history/DownloadHistoryPage" + + /** + * 钻石充值页 + */ + static readonly RECHARGE_DIAMOND_PAGE = "pages/main/mine/diamond/DiamondPage" + + /** + * 设置页 + */ + static readonly SETTING_PAGE = "pages/main/mine/setting/SettingsPage" + + /** + * 意见反馈页 + */ + static readonly FEEDBACK_PAGE = "pages/main/mine/setting/feedback/FeedbackPage" + + /** + * 关于页 + */ + static readonly ABOUT_PAGE = "pages/main/mine/setting/about/AboutPage" + + /** + * 账号绑定页 + */ + static readonly BIND_ACCOUNT_PAGE = "pages/main/mine/setting/account/BindAccountPage" + + /** + * 账号管理页 + */ + static readonly MANAGE_ACCOUNT_PAGE = "pages/main/mine/setting/account/ManageAccountPage" + + /** + * 视频播放页 + */ + static readonly VIDEO_PLAYER_PAGE = "pages/video/VideoPlayerPage" + + /** + * 音频播放页 + */ + static readonly AUDIO_PLAYER_PAGE = "pages/audio/AudioPlayerPage" + + /** + * 图片查看页 + */ + static readonly PHOTO_VIEW_PAGE = "pages/photo/PhotoViewPage" +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/BindPhoneDialog.ets b/entry/src/main/ets/dialog/BindPhoneDialog.ets new file mode 100644 index 0000000..2a2395d --- /dev/null +++ b/entry/src/main/ets/dialog/BindPhoneDialog.ets @@ -0,0 +1,205 @@ +import { StrUtil } from '@pura/harmony-utils'; +import { plainToInstance } from 'class-transformer'; +import { SendCodeEntity } from '../entity/SendCodeEntity'; +import { apiService } from '../net/ApiService'; +import { HttpResult } from '../net/HttpResult'; +import { ToastUtils } from '../utils/ToastUtils'; +import { LoadingDialog } from './LoadingDialog'; + +@CustomDialog +export struct BindPhoneDialog { + controller: CustomDialogController; + + success: () => void = () => {} + + phone: string = ''; + code: string = ''; + timestamp: string = ''; + intervalId: number = -1; + + @State countDownTime: number = 0; + + build() { + RelativeContainer() { + Text('绑定手机号') + .width('auto') + .fontColor($r('app.color.color_90ffffff')) + .fontSize(16) + .fontWeight(FontWeight.Medium) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + left: { anchor: '__container__', align: HorizontalAlign.Start }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + }) + .margin({ top: 18 }) + .id('tv_title') + + Image($r('app.media.ic_close')) + .width(20) + .height(20) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .margin({ top: 12, right: 12 }) + .onClick(() => { + this.controller.close(); + }) + + TextInput({ placeholder: '请输入手机号' }) + .height(48) + .type(InputType.PhoneNumber) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .placeholderColor($r('app.color.color_30ffffff')) + .placeholderFont({ size: 15 }) + .maxLength(11) + .backgroundColor($r('app.color.color_222222')) + .borderRadius(8) + .margin({ top: 20, left: 16, right: 16 }) + .alignRules({ + top: { anchor: 'tv_title', align: VerticalAlign.Bottom } + }) + .id('et_phone') + .onChange((value: string) => { + this.phone = value; + }) + + Row() { + TextInput({ placeholder: '请输入验证码' }) + .height(48) + .type(InputType.Number) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .placeholderColor($r('app.color.color_30ffffff')) + .placeholderFont({ size: 15 }) + .maxLength(6) + .backgroundColor($r('app.color.color_222222')) + .borderRadius(8) + .margin({ left: 16, right: 12 }) + .id('et_code') + .onChange((value: string) => { + this.code = value; + }) + .layoutWeight(1) + + Text(this.countDownTime === 0 && StrUtil.isEmpty(this.timestamp) ? '获取验证码' : this.countDownTime > 0 ? `${this.countDownTime}s` : '重新发送') + .width(110) + .height(48) + .textAlign(TextAlign.Center) + .fontColor($r("app.color.color_466afd")) + .fontSize(15) + .backgroundColor($r('app.color.color_222222')) + .borderRadius(8) + .margin({ right: 16 }) + .onClick(() => { + this.sendCode(this.phone); + }) + } + .alignRules({ + top: { anchor: 'et_phone', align: VerticalAlign.Bottom } + }) + .margin({ top: 14 }) + .id('layout_code') + + Stack() { + Button('确定', { type: ButtonType.Capsule, stateEffect: true }) + .width('100%') + .height(46) + .fontColor(Color.White) + .fontSize(15) + .fontWeight(FontWeight.Medium) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .onClick(() => { + this.bindPhone(); + }) + } + .padding({ left: 16, right: 16 }) + .margin({ top: 55 }) + .alignRules({ + top: { anchor: 'layout_code', align: VerticalAlign.Bottom } + }) + } + .width('100%') + .height(320) + .borderRadius(20) + .backgroundColor($r('app.color.window_background')) + } + + sendCode(phone: string) { + if (StrUtil.isEmpty(this.phone)) { + ToastUtils.show('请输入手机号'); + return; + } + if (this.phone.length != 11) { + ToastUtils.show('请输入正确的手机号'); + return; + } + LoadingDialog.show(this.getUIContext()); + try { + apiService.sendCode(phone) + .then((result: HttpResult) => { + if (result.isSuccess()) { + ToastUtils.show('验证码已发送'); + const codeEntity = plainToInstance(SendCodeEntity, result.data); + this.timestamp = codeEntity.timestamp; + this.countDownTime = 60; + this.intervalId = setInterval(() => { + if (this.countDownTime > 0) { + this.countDownTime-- + } else { + if (this.intervalId !== 0) { + clearInterval(this.intervalId); + } + } + }, 1000) + } else { + ToastUtils.show(result.message, true); + } + }) + .finally(() => { + LoadingDialog.dismiss(); + }) + } catch (e) { + LoadingDialog.dismiss(); + console.log(e); + ToastUtils.show(e); + } + } + + bindPhone() { + if (StrUtil.isEmpty(this.phone)) { + ToastUtils.show('请输入手机号'); + return; + } + if (this.phone.length != 11) { + ToastUtils.show('请输入正确的手机号'); + return; + } + if (StrUtil.isEmpty(this.code)) { + ToastUtils.show('请输入验证码'); + return; + } + LoadingDialog.show(this.getUIContext()); + try { + apiService.loginByPhone(this.phone, this.code, this.timestamp, true) + .then((result: HttpResult) => { + if (result.isSuccess()) { + ToastUtils.show('绑定成功'); + this.controller.close(); + this.success(); + } else { + ToastUtils.show(result.message, true) + } + }) + .finally(() => { + LoadingDialog.dismiss(); + }) + } catch (e) { + LoadingDialog.dismiss(); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/DiamondRuleDialog.ets b/entry/src/main/ets/dialog/DiamondRuleDialog.ets new file mode 100644 index 0000000..fb455bb --- /dev/null +++ b/entry/src/main/ets/dialog/DiamondRuleDialog.ets @@ -0,0 +1,59 @@ +import { DiamondRuleEntity } from "../entity/DiamondRuleEntity"; +import { DiamondRuleItemView } from "../view/DiamondRuleItemView"; + +@CustomDialog +export struct DiamondRuleDialog { + controller: CustomDialogController; + + build() { + RelativeContainer() { + Image($r('app.media.ic_diamond_rule_top_bg')).width('100%').aspectRatio(1.811) + + Image($r('app.media.ic_diamond_rule_indicator')).width(86).height(16) + .alignRules({ + left: {anchor: 'tv_title', align: HorizontalAlign.Start}, + right: {anchor: 'tv_title', align: HorizontalAlign.End}, + bottom: {anchor: 'tv_title', align: VerticalAlign.Bottom} + }) + .margin({left: -8, bottom: -4}) + Text('规则说明').fontColor($r('app.color.color_1a1a1a')).fontSize(20).fontFamily('almmsht').id('tv_title') + .alignRules({ + left: {anchor: '__container__', align: HorizontalAlign.Start}, + right: {anchor: '__container__', align: HorizontalAlign.End} + }) + .width('auto') + .margin({top: 20}) + + Image($r('app.media.ic_close2')).width(24).height(24) + .alignRules({ + top: {anchor: 'tv_title', align: VerticalAlign.Top}, + right: {anchor: '__container__', align: HorizontalAlign.End}, + bottom: {anchor: 'tv_title', align: VerticalAlign.Bottom} + }) + .margin({right: 16}) + .onClick(() => { + this.controller.close() + }) + + List({space: 20}) { + ForEach(DiamondRuleEntity.getRuleList(), (item: DiamondRuleEntity) => { + ListItem() { + DiamondRuleItemView({entity: item}) + } + }) + } + .width('auto') + .height('auto') + .margin({left: 16, top: 24, right: 16, bottom: 30}) + .backgroundColor(Color.White) + .borderRadius(10) + .padding({left: 16, top: 20, right: 16, bottom: 20}) + .alignRules({ + top: {anchor: 'tv_title', align: VerticalAlign.Bottom} + }) + } + .width('100%') + .height('auto') + .backgroundColor('#F6F6F6') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/DownloadDialog.ets b/entry/src/main/ets/dialog/DownloadDialog.ets new file mode 100644 index 0000000..df07083 --- /dev/null +++ b/entry/src/main/ets/dialog/DownloadDialog.ets @@ -0,0 +1,220 @@ +import { ComponentContent } from '@kit.ArkUI'; +import { AppUtil, DisplayUtil, FileUtil, FormatUtil, NumberUtil, StrUtil, WindowUtil } from '@pura/harmony-utils'; +import { DialogCallback } from '../callback/DialogCallback'; + +export enum DownloadStatus { + COMPLETED, + DOWNLOADING, + VIDEO_DOWNLOADING, + AUDIO_DOWNLOADING, + PROCESSING, + PROCESSING_PROGRESS, + TAKING +} + +export declare class DownloadDialogOption { + status: DownloadStatus + totalSize: number + progress: number + totalCount: number + index: number + isAudio?: boolean + callback?: DialogCallback +} + +@Builder +function defaultBuilder(option: DownloadDialogOption) { + Column() { + Text((option.status === DownloadStatus.DOWNLOADING ? '下载中' : + option.status === DownloadStatus.VIDEO_DOWNLOADING ? '视频下载中' : + option.status === DownloadStatus.AUDIO_DOWNLOADING ? '音频下载中' : '处理中') + + (option.totalCount > 1 ? ` ${option.index + 1}/${option.totalCount}` : '')) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(16) + .visibility(option.status === DownloadStatus.COMPLETED ? Visibility.None : Visibility.Visible) + + Image(option.status === DownloadStatus.COMPLETED ? $r('app.media.ic_completed') : $r('app.media.ic_downloading')) + .width(80) + .height(80) + .margin({ top: 20 }) + + Stack() { + Column() { + Stack({ alignContent: Alignment.Start }) { + Progress({ value: option.progress, total: option.totalSize, type: ProgressType.Linear }) + .width('100%') + .style({ strokeWidth: 12, strokeRadius: 6 }) + .color('#FC4F54') + .colorBlend($r('app.color.color_10ffffff')) + .borderRadius(6) + + Text(FormatUtil.getFormatPercentage(option.progress / option.totalSize, 1)) + .width(40) + .height(18) + .textAlign(TextAlign.Center) + .fontColor(Color.White) + .fontSize(10) + .borderRadius(10) + .borderWidth(1) + .borderColor($r('app.color.color_80ffffff')) + .backgroundColor($r("app.color.color_466afd")) + .translate({ x: (AppUtil.getUIContext().px2vp(DisplayUtil.getWidth()) * 0.8 - 80) * option.progress / option.totalSize }) + } + + Text(`${FileUtil.getFormatFileSize(option.progress)}/${option.totalSize !== 0 ? + FileUtil.getFormatFileSize(option.totalSize) : '获取中'}`) + .fontColor($r('app.color.color_999999')) + .fontSize(12) + .margin({ top: 16 }) + .visibility(option.status === DownloadStatus.PROCESSING ? Visibility.Hidden : Visibility.Visible) + + Row() { + Button('取消下载', { type: ButtonType.Capsule, stateEffect: true }) + .width(110) + .height(36) + .backgroundColor($r('app.color.color_333333')) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(15) + .onClick(() => { + if (option.callback?.cancel) { + option.callback.cancel() + } + DownloadDialog.dismiss() + }) + + Button('后台下载', { type: ButtonType.Capsule, stateEffect: true }) + .width(110) + .height(36) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .margin({ left: 10 }) + .onClick(() => { + if (option.callback?.confirm) { + option.callback.confirm() + } + DownloadDialog.dismiss() + }) + .visibility(Visibility.None) + } + .margin({ top: 16 }) + } + .margin({ top: 20 }) + .visibility(option.status === DownloadStatus.COMPLETED ? Visibility.None : Visibility.Visible) + + Column() { + Text(option.isAudio ? '已保存到本地' : '已保存到系统相册中').fontColor($r('app.color.color_90ffffff')).fontSize(16) + + Text(option.isAudio ? '文件管理/我的手机/Download/素材魔方' : '文件管理/我的手机/Download/图库') + .fontColor($r('app.color.color_999999')) + .fontSize(12) + .margin({ top: 10 }) + + Row() { + Button('取消', { type: ButtonType.Capsule, stateEffect: true }) + .width(110) + .height(36) + .backgroundColor($r('app.color.color_333333')) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(15) + .onClick(() => { + if (option.callback?.cancel) { + option.callback.cancel() + } + DownloadDialog.dismiss() + }) + + Blank().width(10) + + Button('前往查看', { type: ButtonType.Capsule, stateEffect: true }) + .width(110) + .height(36) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .onClick(() => { + if (option.callback?.confirm) { + option.callback.confirm() + } + DownloadDialog.dismiss() + }) + } + .margin({ top: 16 }) + } + .margin({ top: 20 }) + .visibility(option.status === DownloadStatus.COMPLETED ? Visibility.Visible : Visibility.None) + } + } + .width('80%') + .borderRadius(10) + .backgroundColor($r('app.color.color_222222')) + .padding(20) +} + +export class DownloadDialog { + context: UIContext | null = null; + contentNode: ComponentContent | null = null; + confirm?: () => void; + cancel?: () => void; + + private static instance: DownloadDialog | null = null; + + static show(context: UIContext, option: DownloadDialogOption) { + if (DownloadDialog.instance === null) { + DownloadDialog.instance = new DownloadDialog(context, option); + } + DownloadDialog.instance?.openDialog(); + } + + static update(option: DownloadDialogOption) { + DownloadDialog.instance?.updateDialog(option); + } + + static dismiss() { + if (DownloadDialog.instance !== null) { + DownloadDialog.instance.closeDialog(); + DownloadDialog.instance = null; + } + } + + constructor(context: UIContext, option: DownloadDialogOption) { + this.context = context; + this.confirm = option.callback?.confirm + this.cancel = option.callback?.cancel + this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder), option); + } + + + openDialog() { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().openCustomDialog(this.contentNode, { + onWillDismiss: () => { + return false + } + }) + .then(() => { + console.info('OpenCustomDialog complete.'); + }) + } + } + + closeDialog() { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().closeCustomDialog(this.contentNode) + .then(() => { + console.info('CloseCustomDialog complete.'); + }) + } + } + + updateDialog(option: DownloadDialogOption) { + option.callback = { confirm: this.confirm, cancel: this.cancel } + this.contentNode?.update(option); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/EditTextDialog.ets b/entry/src/main/ets/dialog/EditTextDialog.ets new file mode 100644 index 0000000..3741070 --- /dev/null +++ b/entry/src/main/ets/dialog/EditTextDialog.ets @@ -0,0 +1,128 @@ +import { ComponentContent } from '@kit.ArkUI'; +import { StrUtil } from '@pura/harmony-utils'; +import { DialogCallback } from '../callback/DialogCallback'; +import { ToastUtils } from '../utils/ToastUtils'; + +export declare class EditTextDialogOption { + title?: string; + content?: string; + hintText?: string; + confirm?: (content: string) => void; +} + +@Builder +function defaultBuilder(option: EditTextDialogOption) { + Column() { + RelativeContainer() { + Text(option.title ?? '编辑') + .width('auto') + .fontColor($r('app.color.color_90ffffff')) + .fontSize(18) + .fontWeight(FontWeight.Medium) + .alignRules( { + left: {anchor: '__container__', align: HorizontalAlign.Start}, + right: {anchor: '__container__', align: HorizontalAlign.End} + }) + + Image($r('app.media.ic_close')).width(20).height(20) + .alignRules( { + top: {anchor: '__container__', align: VerticalAlign.Top}, + right: {anchor: '__container__', align: HorizontalAlign.End} + }) + .margin({ right: 16 }) + .onClick(() => { + EditTextDialog.dismiss() + }) + } + .width('100%') + .height(30) + + TextInput({ placeholder: option.hintText ?? '请输入', text: option.content}) + .width('90%') + .height(48) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .placeholderColor($r('app.color.color_30ffffff')) + .placeholderFont({ size: 15 }) + .maxLength(12) + .backgroundColor($r('app.color.color_222222')) + .borderRadius(8) + .margin({ top: 16 }) + .onChange((content) => { + option.content = content + }) + + Button('确定', { type: ButtonType.Capsule, stateEffect: true }) + .fontColor(Color.White) + .fontSize(16) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .width('90%') + .height(46) + .margin({ top: 30 }) + .onClick(() => { + if (StrUtil.isEmpty(option.content)) { + ToastUtils.show('内容不能为空') + } else { + if (option.confirm) { + option.confirm(option.content!!); + } + EditTextDialog.dismiss(); + } + }) + } + .padding({ top: 22, bottom: 22 }) + .borderRadius(20) + .backgroundColor($r('app.color.window_background')) + .width('100%') +} + +export class EditTextDialog { + context: UIContext | null = null; + contentNode: ComponentContent | null = null; + + private static instance: EditTextDialog | null = null; + + static show(context: UIContext, option: EditTextDialogOption) { + if (EditTextDialog.instance === null) { + EditTextDialog.instance = new EditTextDialog(context, option); + } + EditTextDialog.instance?.openDialog(); + } + + static dismiss() { + if (EditTextDialog.instance !== null) { + EditTextDialog.instance.closeDialog(); + EditTextDialog.instance = null; + } + } + + constructor(context: UIContext, option: EditTextDialogOption) { + this.context = context; + this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder), option); + } + + openDialog() { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().openCustomDialog(this.contentNode, { + maskColor: '#CC000000', + autoCancel: false, + alignment: DialogAlignment.Bottom + }) + .then(() => { + console.info('OpenCustomDialog complete.'); + }) + } + } + + closeDialog() { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().closeCustomDialog(this.contentNode) + .then(() => { + console.info('CloseCustomDialog complete.'); + }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/JoinWxGroupCourseDialog.ets b/entry/src/main/ets/dialog/JoinWxGroupCourseDialog.ets new file mode 100644 index 0000000..76a7398 --- /dev/null +++ b/entry/src/main/ets/dialog/JoinWxGroupCourseDialog.ets @@ -0,0 +1,179 @@ +import { BusinessError, request } from '@kit.BasicServicesKit'; +import { AppUtil, FileUtil, PasteboardUtil } from '@pura/harmony-utils'; +import { ToastUtils } from '../utils/ToastUtils'; +import { SaveUtils } from '../utils/SaveUtils'; +import { LoginManager } from '../manager/LoginGlobalManager'; +import { Want } from '@kit.AbilityKit'; +import { WXApi } from '../utils/wechat/WXApiEventHandlerImpl'; + +@CustomDialog +export struct JoinWxGroupCourseDialog { + controller: CustomDialogController; + + swiperController: SwiperController = new SwiperController() + + isPlayback: boolean = true + + images: Array = [ + $r('app.media.ic_wx_group_tip1'), + $r("app.media.ic_wx_group_tip2"), + $r("app.media.ic_wx_group_tip3"), + $r("app.media.ic_wx_group_tip4"), + $r('app.media.ic_wx_group_tip5') + ]; + + steps: Array = ['第一步', '第二步', '第三步', '第四步', '第五步'] + + qrCodePath = 'https://cdn.batiao8.com/kct/mp/kcsp_qrcode.png' + + @State currentIndex: number = 0 + + downloadImage() { + try { + const cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + 'kcsp_wx_group_qrcode.jpg'; + if (FileUtil.accessSync(cachePath)) { + FileUtil.unlink(cachePath) + } + request.downloadFile(AppUtil.getContext(), { + url: this.qrCodePath, + filePath: cachePath + }).then((downloadTask: request.DownloadTask) => { + downloadTask.on('complete', () => { + console.info('download complete'); + SaveUtils.saveImageVideoToAlbumDialog([cachePath], false) + .then((saved) => { + if (saved) { + PasteboardUtil.setDataTextSync(LoginManager.getUserInfo()!!.user_id) + ToastUtils.show('ID复制成功') + this.jumpToWxScan() + this.controller.close() + } else { + ToastUtils.show('二维码保存失败') + } + }) + .catch(() => { + ToastUtils.show('二维码保存失败') + }) + }) + }).catch((err: BusinessError) => { + ToastUtils.show('二维码下载失败') + }); + } catch (error) { + ToastUtils.show('二维码下载失败') + } + } + + async jumpToWxScan() { + let intent: Want = { + action: 'ohos.want.action.viewData', + uri: 'weixin://dl/scan' // 微信扫一扫的固定协议 + }; + try { + await AppUtil.getContext().startAbility(intent); + } catch (error) { + let err: BusinessError = error as BusinessError; + console.error(err.message) + } + } + + build() { + RelativeContainer() { + Image($r('app.media.ic_wx_group_tip_bg')).width('100%').height(320) + + Text(this.isPlayback ? '添加直播回放助手流程' : '添加视频助手流程') + .width('auto') + .fontColor(Color.White) + .fontSize(16) + .fontWeight(FontWeight.Medium) + .margin({ top: 16 }) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .id('tv_title') + + List({space: 16}) { + ForEach(this.steps, (item: string, index) => { + ListItem() { + Text(item) + .fontColor(index == this.currentIndex ? Color.White : $r('app.color.color_bebebe')) + .fontSize(index == this.currentIndex ? 16 : 14) + .fontWeight(index == this.currentIndex ? FontWeight.Medium : FontWeight.Normal) + } + .onClick(() => { + this.swiperController.changeIndex(index, true) + }) + }) + } + .width('100%') + .height(24) + .listDirection(Axis.Horizontal) + .alignListItem(ListItemAlign.Center) + .alignRules({ + top: {anchor: 'tv_title', align: VerticalAlign.Bottom} + }) + .margin({top: 20}) + .padding({left: 16, right: 16}) + .id('li_step') + + Swiper(this.swiperController) { + ForEach(this.images, (item: Resource) => { + Image(item).width('100%').aspectRatio(2.04).margin({ left: 16, right: 16 }) + .borderRadius(20) + }) + } + .indicator(false) + .loop(true) + .autoPlay(true) + .interval(2000) + .onChange((number) => { + this.currentIndex = number + }) + .alignRules({ + top: {anchor: 'li_step', align: VerticalAlign.Bottom} + }) + .margin({top: 24}) + .id('swiper') + + Row() { + Button('取消', { type: ButtonType.Capsule, stateEffect: false }) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(15) + .backgroundColor($r('app.color.color_333333')) + .width(126) + .height(46) + .onClick(() => { + this.controller.close() + }) + Blank().width(9) + Button('前往微信扫码加群', { type: ButtonType.Capsule, stateEffect: false }) + .fontColor(Color.White) + .fontSize(15) + .backgroundColor(Color.Transparent) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .layoutWeight(1) + .height(46) + .onClick(() => { + if (!WXApi.isWXAppInstalled()) { + ToastUtils.show('未安装微信客户端,请先下载安装微信客户端'); + return; + } + this.downloadImage() + }) + } + .backgroundColor($r('app.color.color_222222')) + .alignRules({ + top: {anchor: 'swiper', align: VerticalAlign.Bottom} + }) + .margin({top: 28}) + .padding({left: 16, top: 9, right: 16, bottom: 30}) + } + .width('100%') + .height('auto') + .borderRadius(20) + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/LoadingDialog.ets b/entry/src/main/ets/dialog/LoadingDialog.ets new file mode 100644 index 0000000..78e57de --- /dev/null +++ b/entry/src/main/ets/dialog/LoadingDialog.ets @@ -0,0 +1,81 @@ +import { ComponentContent } from '@kit.ArkUI'; + +@Builder +function defaultBuilder() { + Column() { + LoadingProgress() + .color(Color.White) + .width(50) + .height(50) + + Text('加载中') + .fontColor(Color.White) + .fontSize(12) + .margin({ top: 5 }) + .width('auto') + .height('auto') + } + .justifyContent(FlexAlign.Center) + .borderRadius(6) + .backgroundColor('#CC000000') + .width(86) + .height(86) +} + +export class LoadingDialog { + context: UIContext | null = null; + contentNode: ComponentContent | null = null; + + private static instance: LoadingDialog | null = null; + + static show(context: UIContext, cancelable: boolean = false) { + if (LoadingDialog.instance === null) { + LoadingDialog.instance = new LoadingDialog(context); + } + LoadingDialog.instance?.openDialog(cancelable); + } + + static dismiss() { + if (LoadingDialog.instance !== null) { + LoadingDialog.instance.closeDialog(); + LoadingDialog.instance = null; + } + } + + constructor(context: UIContext) { + this.context = context; + this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder)); + } + + openDialog(cancelable: boolean) { + if (this.context !== null && this.contentNode !== null) { + if (!cancelable) { + this.context.getPromptAction().openCustomDialog(this.contentNode, { + onWillDismiss: () => { + return false + }, + autoCancel: false + }) + .then(() => { + console.info('OpenCustomDialog complete.'); + }) + } else { + this.context.getPromptAction().openCustomDialog(this.contentNode, { + autoCancel: false + }) + .then(() => { + console.info('OpenCustomDialog complete.'); + }) + } + } + } + + closeDialog() { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().closeCustomDialog(this.contentNode) + .then(() => { + console.info('CloseCustomDialog complete.'); + }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/LoginTipDialog.ets b/entry/src/main/ets/dialog/LoginTipDialog.ets new file mode 100644 index 0000000..df49359 --- /dev/null +++ b/entry/src/main/ets/dialog/LoginTipDialog.ets @@ -0,0 +1,80 @@ +import { Constants } from '../common/Constants'; +import { RouterUrls } from '../common/RouterUrls'; + +@CustomDialog +export struct LoginTipDialog { + controller: CustomDialogController; + cancel: () => void = () => { + } + confirm: () => void = () => { + } + + build() { + RelativeContainer() { + Image($r('app.media.ic_tip_dialog_top_bg')).width('100%').aspectRatio(2.72) + + Column() { + Text('温馨提示') + .fontColor($r('app.color.color_212226')) + .fontSize(24) + .fontFamily('ysbth') + .margin({ top: 20 }) + + Text() { + Span('登录之前需查看并同意') + Span('《用户协议》') + .fontColor($r("app.color.color_466afd")) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({ + url: RouterUrls.WEB_PAGE, params: { + title: '用户协议', + url: Constants.USER_AGREEMENT + } + }) + }) + Span('和') + Span('《隐私政策》') + .fontColor($r("app.color.color_466afd")) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({ + url: RouterUrls.WEB_PAGE, params: { + title: '隐私政策', + url: Constants.PRIVACY_POLICY + } + }) + }) + } + .fontColor($r('app.color.color_727686')) + .fontSize(14) + .textAlign(TextAlign.Center) + .margin({ top: 20, left: 20, right: 20 }) + + Button('同意', { type: ButtonType.Capsule, stateEffect: true }) + .width(110) + .height(40) + .fontColor(Color.White) + .fontSize(16) + .backgroundColor($r("app.color.color_466afd")) + .margin({ top: 20, bottom: 20 }) + .onClick(() => { + this.controller.close(); + if (this.confirm) { + this.confirm(); + } + }) + } + + Image($r('app.media.ic_close_dialog')).width(18).height(18) + .margin({top: 10, right: 10}) + .alignRules({ + right: {anchor: '__container__', align: HorizontalAlign.End} + }) + .onClick(() => { + this.controller.close() + }) + } + .height('auto') + .borderRadius(20) + .backgroundColor(Color.White) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/MaterialLoadingDialog.ets b/entry/src/main/ets/dialog/MaterialLoadingDialog.ets new file mode 100644 index 0000000..faafc9f --- /dev/null +++ b/entry/src/main/ets/dialog/MaterialLoadingDialog.ets @@ -0,0 +1,84 @@ +import { ComponentContent } from '@kit.ArkUI'; + +@Builder +function defaultBuilder(text: string) { + Column() { + LoadingProgress() + .color(Color.White) + .width(50) + .height(50) + + Text(text) + .fontColor(Color.White) + .fontSize(12) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .ellipsisMode(EllipsisMode.END) + .margin({ left:10, top: 5, right: 10 }) + .width('auto') + .height('auto') + } + .width(192) + .height(124) + .justifyContent(FlexAlign.Center) + .borderRadius(6) + .backgroundColor($r('app.color.color_222222')) + .padding({left: 10, right: 10}) +} + +export class MaterialLoadingDialog { + context: UIContext | null = null; + contentNode: ComponentContent | null = null; + + private static instance: MaterialLoadingDialog | null = null; + + static show(context: UIContext, text?: string) { + if (MaterialLoadingDialog.instance === null) { + MaterialLoadingDialog.instance = new MaterialLoadingDialog(context, text ? text : ''); + } + MaterialLoadingDialog.instance?.openDialog(); + } + + static update(text: string) { + MaterialLoadingDialog.instance?.updateDialog(text); + } + + static dismiss() { + if (MaterialLoadingDialog.instance !== null) { + MaterialLoadingDialog.instance.closeDialog(); + MaterialLoadingDialog.instance = null; + } + } + + constructor(context: UIContext, text: string) { + this.context = context; + this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder), text); + } + + + openDialog() { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().openCustomDialog(this.contentNode, { + onWillDismiss: () => { + return false + } + }) + .then(() => { + console.info('OpenCustomDialog complete.'); + }) + } + } + + closeDialog() { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().closeCustomDialog(this.contentNode) + .then(() => { + console.info('CloseCustomDialog complete.'); + }) + } + } + + updateDialog(text: string) { + this.contentNode?.update(text); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/PrivacyPolicyDialog.ets b/entry/src/main/ets/dialog/PrivacyPolicyDialog.ets new file mode 100644 index 0000000..ebaa5bd --- /dev/null +++ b/entry/src/main/ets/dialog/PrivacyPolicyDialog.ets @@ -0,0 +1,100 @@ +import { Constants } from '../common/Constants'; +import { RouterUrls } from '../common/RouterUrls'; +import { LengthMetrics } from '@kit.ArkUI'; + +@CustomDialog +export struct PrivacyPolicyDialog { + controller: CustomDialogController; + cancel: () => void = () => { + } + confirm: () => void = () => { + } + + build() { + Stack({alignContent: Alignment.Top}) { + Image($r('app.media.ic_tip_dialog_top_bg')).width('100%').aspectRatio(2.72) + + Column() { + Text('欢迎使用') + .fontColor($r('app.color.color_212226')) + .fontSize(24) + .fontFamily('ysbth') + .margin({ top: 20 }) + + Text() { + Span('请你务必审慎阅读、充分理解') + Span('《服务协议》') + .fontColor($r("app.color.color_466afd")) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({ + url: RouterUrls.WEB_PAGE, params: { + title: '用户协议', + url: Constants.USER_AGREEMENT + } + }) + }) + Span('和') + Span('《隐私政策》') + .fontColor($r("app.color.color_466afd")) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({ + url: RouterUrls.WEB_PAGE, params: { + title: '隐私政策', + url: Constants.PRIVACY_POLICY + } + }) + }) + Span('各条款,包括但不限于:为了更好的向你提供服务,我们需要访问你的相册、位置信息等。你可阅读') + Span('《隐私政策》') + .fontColor($r("app.color.color_466afd")) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({ + url: RouterUrls.WEB_PAGE, params: { + title: '隐私政策', + url: Constants.PRIVACY_POLICY + } + }) + }) + Span('了解详细信息。如果你同意,请点击下面同意按钮开始接受我们的服务。') + } + .fontColor($r('app.color.color_727686')) + .fontSize(14) + .lineSpacing(LengthMetrics.px(20)) + .margin({ top: 10, left: 20, right: 20 }) + + Row(){ + Button('拒绝', { type: ButtonType.Capsule, stateEffect: true }) + .width(110) + .height(40) + .fontColor($r('app.color.color_80859B')) + .fontSize(16) + .backgroundColor($r("app.color.color_f1f2f6")) + .margin({left: 20}) + .onClick(() => { + this.controller.close(); + if (this.cancel) { + this.cancel(); + } + }) + Blank().layoutWeight(1) + Button('同意', { type: ButtonType.Capsule, stateEffect: true }) + .width(110) + .height(40) + .fontColor(Color.White) + .fontSize(16) + .backgroundColor($r("app.color.color_466afd")) + .margin({right: 20}) + .onClick(() => { + this.controller.close(); + if (this.confirm) { + this.confirm(); + } + }) + } + .margin({ top: 20, bottom: 20 }) + } + } + .borderRadius(20) + .backgroundColor(Color.White) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/SimpleTipDialog.ets b/entry/src/main/ets/dialog/SimpleTipDialog.ets new file mode 100644 index 0000000..02f8917 --- /dev/null +++ b/entry/src/main/ets/dialog/SimpleTipDialog.ets @@ -0,0 +1,111 @@ +import { ComponentContent } from '@kit.ArkUI'; +import { StrUtil } from '@pura/harmony-utils'; +import { DialogCallback } from '../callback/DialogCallback'; + +export declare class TipDialogOption { + title?: string; + content: string; + buttonText?: string; + callback?: DialogCallback; +} + +@Builder +function defaultBuilder(option: TipDialogOption) { + RelativeContainer() { + Image($r('app.media.ic_tip_dialog_top_bg')).width('100%').aspectRatio(2.72) + + Column() { + Text(option.title) + .fontColor($r('app.color.color_212226')) + .fontSize(22) + .fontWeight(FontWeight.Medium) + .visibility(StrUtil.isEmpty(option.title) ? Visibility.Hidden : Visibility.Visible) + Text(option.content) + .textAlign(TextAlign.Center) + .fontColor(StrUtil.isNotEmpty(option.title) ? $r('app.color.color_727686') : $r('app.color.color_212226')) + .fontSize(StrUtil.isNotEmpty(option.title) ? 14 : 22) + .fontWeight(StrUtil.isNotEmpty(option.title) ? FontWeight.Normal : FontWeight.Medium) + .margin({ left: 27, top: 12, right: 27 }) + .maxLines(5) + Button(StrUtil.isNotEmpty(option.buttonText) ? option.buttonText : '确定', { type: ButtonType.Capsule, stateEffect: true }) + .width(110) + .height(40) + .fontColor(Color.White) + .fontSize(16) + .backgroundColor($r("app.color.color_466afd")) + .margin({ top: 20, bottom: 20 }) + .onClick(() => { + SimpleTipDialog.dismiss(); + if (option.callback?.confirm) { + option.callback?.confirm(); + } + }) + } + .padding({ top: 22, bottom: 22 }) + + Image($r('app.media.ic_close_dialog')).width(18).height(18) + .margin({top: 10, right: 10}) + .alignRules({ + right: {anchor: '__container__', align: HorizontalAlign.End} + }) + .onClick(() => { + SimpleTipDialog.dismiss(); + }) + } + .borderRadius(20) + .backgroundColor(Color.White) + .width('80%') +} + +export class SimpleTipDialog { + context: UIContext | null = null; + contentNode: ComponentContent | null = null; + + private static instance: SimpleTipDialog | null = null; + + static show(context: UIContext, option: TipDialogOption, canCancel: boolean = true) { + if (SimpleTipDialog.instance === null) { + SimpleTipDialog.instance = new SimpleTipDialog(context, option); + } + SimpleTipDialog.instance?.openDialog(canCancel); + } + + static dismiss() { + if (SimpleTipDialog.instance !== null) { + SimpleTipDialog.instance.closeDialog(); + SimpleTipDialog.instance = null; + } + } + + constructor(context: UIContext, option: TipDialogOption) { + this.context = context; + this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder), option); + } + + openDialog(canCancel: boolean) { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().openCustomDialog(this.contentNode, { + maskColor: '#CC000000', + onWillDismiss: () => { + return canCancel + } + }) + .then(() => { + console.info('OpenCustomDialog complete.'); + }) + } + } + + closeDialog() { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().closeCustomDialog(this.contentNode) + .then(() => { + console.info('CloseCustomDialog complete.'); + }) + } + } + + updateDialog(options: TipDialogOption) { + this.contentNode?.update(options) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/dialog/TipDialog.ets b/entry/src/main/ets/dialog/TipDialog.ets new file mode 100644 index 0000000..7f37990 --- /dev/null +++ b/entry/src/main/ets/dialog/TipDialog.ets @@ -0,0 +1,121 @@ +import { ComponentContent } from '@kit.ArkUI'; +import { StrUtil } from '@pura/harmony-utils'; +import { DialogCallback } from '../callback/DialogCallback'; + +export declare class TipDialogOption { + title?: string; + content: string; + leftText?: string; + rightText?: string; + callback?: DialogCallback; +} + +@Builder +function defaultBuilder(option: TipDialogOption) { + Column() { + Text(option.title) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(18) + .fontWeight(FontWeight.Medium) + .visibility(StrUtil.isEmpty(option.title) ? Visibility.Hidden : Visibility.Visible) + Text(option.content) + .textAlign(TextAlign.Center) + .fontColor(StrUtil.isNotEmpty(option.title) ? $r('app.color.color_80ffffff') : $r('app.color.color_90ffffff')) + .fontSize(StrUtil.isNotEmpty(option.title) ? 14 : 18) + .fontWeight(StrUtil.isNotEmpty(option.title) ? FontWeight.Normal : FontWeight.Medium) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .ellipsisMode(EllipsisMode.END) + .margin({ left: 27, top: 12, right: 27 }) + .maxLines(5) + Row() { + Button(StrUtil.isNotEmpty(option.leftText) ? option.leftText : '取消', { type: ButtonType.Capsule, stateEffect: false }) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(14) + .backgroundColor($r('app.color.color_333333')) + .layoutWeight(1) + .height(40) + .onClick(() => { + TipDialog.dismiss(); + if (option.callback?.cancel) { + option.callback?.cancel(); + } + }) + Blank().width(12) + Button(StrUtil.isNotEmpty(option.rightText) ? option.rightText : '确定', { type: ButtonType.Capsule, stateEffect: false }) + .fontColor(Color.White) + .fontSize(14) + .backgroundColor(Color.Transparent) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .layoutWeight(1) + .height(40) + .onClick(() => { + TipDialog.dismiss(); + if (option.callback?.confirm) { + option.callback?.confirm(); + } + }) + } + .padding({ left: 14, right: 14 }) + .margin({ top: 20 }) + } + .padding({ top: 22, bottom: 22 }) + .borderRadius(20) + .backgroundColor($r('app.color.color_222222')) + .width('80%') +} + +export class TipDialog { + context: UIContext | null = null; + contentNode: ComponentContent | null = null; + + private static instance: TipDialog | null = null; + + static show(context: UIContext, option: TipDialogOption, canCancel: boolean = true) { + if (TipDialog.instance === null) { + TipDialog.instance = new TipDialog(context, option); + } + TipDialog.instance?.openDialog(canCancel); + } + + static dismiss() { + if (TipDialog.instance !== null) { + TipDialog.instance.closeDialog(); + TipDialog.instance = null; + } + } + + constructor(context: UIContext, option: TipDialogOption) { + this.context = context; + this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder), option); + } + + openDialog(canCancel: boolean) { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().openCustomDialog(this.contentNode, { + maskColor: '#CC000000', + onWillDismiss: () => { + return canCancel; + } + }) + .then(() => { + console.info('OpenCustomDialog complete.'); + }) + } + } + + closeDialog() { + if (this.context !== null && this.contentNode !== null) { + this.context.getPromptAction().closeCustomDialog(this.contentNode) + .then(() => { + console.info('CloseCustomDialog complete.'); + }) + } + } + + updateDialog(options: TipDialogOption) { + this.contentNode?.update(options) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/AccountEntity.ets b/entry/src/main/ets/entity/AccountEntity.ets new file mode 100644 index 0000000..c42d99f --- /dev/null +++ b/entry/src/main/ets/entity/AccountEntity.ets @@ -0,0 +1,13 @@ + +export class AccountEntity { + avater: string = ''; + bind: Array = new Array(); + create_time: string = ''; + name: string = ''; + phone: string = ''; + role: number = 0; + temp: boolean = true; + user_id: string = ''; + vip_name: string = ''; + vip_type: number = 1; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/BannerEntity.ets b/entry/src/main/ets/entity/BannerEntity.ets new file mode 100644 index 0000000..6d86d51 --- /dev/null +++ b/entry/src/main/ets/entity/BannerEntity.ets @@ -0,0 +1,5 @@ +export class BannerEntity { + image: string = ""; + page: string = ""; + type: string = ""; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/ConfigEntity.ets b/entry/src/main/ets/entity/ConfigEntity.ets new file mode 100644 index 0000000..942f7fb --- /dev/null +++ b/entry/src/main/ets/entity/ConfigEntity.ets @@ -0,0 +1,59 @@ +import { Expose, Type } from 'class-transformer'; +import { BannerEntity } from './BannerEntity'; +import "reflect-metadata"; +import { WxVideoConfigEntity } from './WxVideoConfig'; + +export class ConfigEntity { + @Expose({ name: 'client.guide.pay.enable' }) + guidePayEnable: boolean = true; //引导页是否开启支付,默认可以 + + @Expose({ name: 'client.guide.enable' }) + guideEnable: boolean = true; //是否开启引导页 + + @Expose({ name: 'client.start.function.hint' }) + guideHint: string = ""; //引导页提示语 + + @Expose({ name: 'client.nologin.pay.enable' }) + noLoginPayEnable: boolean = false; //是否开启未登录支付 + + @Expose({ name: 'client.pay.agreement' }) + payAgreementEnable: boolean = true; //是否显示支付协议 + + @Expose({ name: 'client.login.type' }) + loginType: Array = new Array(); //支持的登录类型 + + @Expose({ name: 'client.banner.urls' }) + @Type(() => BannerEntity) + homeBanners: Array = new Array(); //首页轮播图 + + @Expose({ name: 'client.wechat.video.share.enable' }) + wxVideoEnable: boolean = true; //视频号开关 + + @Expose({ name: 'client.wechat.video.playback.share.enable' }) + playbackEnable: boolean = true; //直播回放开关 + + @Expose({ name: 'client.course.wechat.video' }) + wxVideoCourse: string = ""; //视频号教程 + + @Expose({ name: 'client.course.playback' }) + wxPlaybackCourse: string = ""; //直播回放教程 + + @Expose({ name: 'client.playback.join.type' }) + wxPlaybackJoinType: string = ""; //直播加群方式 + + @Type(() => WxVideoConfigEntity) + @Expose({ name: 'client.mp.share.config.kcsp' }) + wxVideoConfig: WxVideoConfigEntity = new WxVideoConfigEntity() //直播回放小程序跳转配置 + + @Expose({ name: 'client.hmos.video.service.enable' }) //视频号助手是否可用 + wxVideoServiceEnable: boolean = true + + @Expose({ name: 'client.link.collection' }) + domainMap: Record = {}; //网站主机名 + + @Expose({ name: 'client.copy.contains' }) + copyContainsList: Array = []; //链接识别配置 + + @Expose({ name: 'client.challenge.enable' }) + challengeEnable?: boolean = true; //0元挑战 +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/DiamondDetailEntity.ets b/entry/src/main/ets/entity/DiamondDetailEntity.ets new file mode 100644 index 0000000..26b3a17 --- /dev/null +++ b/entry/src/main/ets/entity/DiamondDetailEntity.ets @@ -0,0 +1,7 @@ +export class DiamondDetailEntity { + buy_total: number = 0 + buy_used: number = 0 + month_total: number = 0 + month_used: number = 0 + remain: number = 0 +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/DiamondRuleEntity.ets b/entry/src/main/ets/entity/DiamondRuleEntity.ets new file mode 100644 index 0000000..5c15197 --- /dev/null +++ b/entry/src/main/ets/entity/DiamondRuleEntity.ets @@ -0,0 +1,18 @@ +export class DiamondRuleEntity { + title: string = '' + desc: string = '' + + constructor(title: string, desc: string) { + this.title = title + this.desc = desc + } + + static getRuleList(): Array { + const list = new Array() + list.push(new DiamondRuleEntity('一、固定钻石领取', '会员用户每月最后一天,系统会固定发放500钻石到用户平台账户。')) + list.push(new DiamondRuleEntity('二、固定钻石刷新', '若会员用户在第一个月有没用完的钻石,那么会在本月最后一天重置不会留存到第二月。')) + list.push(new DiamondRuleEntity('三、兑换钻石', '会员兑换后的钻石统一叫【兑换钻石】,【兑换钻石】不同于【固定钻石】每月刷新钻石数量;用户兑换了多少就可以使用多少,用完截止,没有时间限制,也没有每月最后一天刷新。')) + list.push(new DiamondRuleEntity('四、重复兑换钻石', '兑换钻石可重复购买,用完截止,没有时间限制,也没有每月最后一天刷新。')) + return list + } +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/DownloadHistoryEntity.ets b/entry/src/main/ets/entity/DownloadHistoryEntity.ets new file mode 100644 index 0000000..e766ed2 --- /dev/null +++ b/entry/src/main/ets/entity/DownloadHistoryEntity.ets @@ -0,0 +1,102 @@ +export class DownloadHistoryEntity { + id: string = '' + title: string = '' + status: string = '' + description: string = '' + create_time: string = '' + domain: string = '' + request: string = '' + save_cost: string = '' + save_end_time: string = '' + save_size: string = '' + save_start_time: string = '' + save_status: string = '' + + getTypeBgColor(): string { + switch (this.domain) { + case 'b23.tv': + case 'm.bilibili.com': + case 'www.bilibili.com': + return '#F4306F' + case 'v.douyin.com': + case 'www.iesdouyin.com': + case 'vt.tiktok.com': + return '#80FFFFFF' + case 'm.toutiao.com': + return '#F91716' + case 'video.weibo.com': + case 'weibo.com': + case 'm.weibo.cn': + case 'shop.sc.weibo.com': + return '#DE860C' + case 'v.kuaishou.com': + return '#DE440D' + case 'wxapp.tc.qq.com': + case 'm.v.qq.com': + return '#0D95F3' + case 'twitter.com': + case 'x.com': + return '#139EEA' + case 'xhslink.com': + case 'www.xiaohongshu.com': + return '#EA3046' + case 'instagram.com': + case 'www.instagram.com': + return '#8D32F4' + case 'mr.baidu.com': + case 'm.baidu.com': + case 'mbd.baidu.com': + return '#0D95F3' + case 'youtu.be': + case 'youtube.com': + case 'm.youtube.com': + case 'www.youtube.com': + return '#F92C2C' + case 'mp.weixin.qq.com': + return '#1DDB50' + case 'e.tb.cn': + case 'm.tb.cn': + return '#F96E12' + case 'www.facebook.com': + return '#1B6EF6' + case 'v.ixigua.com': + case 'www.ixigua.com': + return '#F01566' + case 'mobile.yangkeduo.com': + return '#F40F18' + case 'm.youku.com': + return '#0CB3E2' + case '163cn.tv': + return '#F42C34' + case 'qishui.douyin.com': + return '#20C485' + case 'qr.1688.com': + case 'detail.m.1688.com': + return '#F65611' + case 'h5.pipix.com': + return '#EE3958' + case 'app.cctv.com': + return '#80FFFFFF' + case 'vk.com': + return '#1E82F3' + case 'www.finkapp.cn': + return '#F62C2C' + case 'novelquickapp.com': + return '#F18221' + case 'video.weishi.qq.com': + return '#2E5DF0' + case 'm.ctrip.com': + return '#177DF0' + } + return '#33FFFFFF' + } + + getTypeTextColor(): string { + switch (this.domain) { + case 'v.douyin.com': + case 'app.cctv.com': + return '#CC000000' + } + return '#CCFFFFFF' + } +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/HomeMenuEntity.ets b/entry/src/main/ets/entity/HomeMenuEntity.ets new file mode 100644 index 0000000..b55148b --- /dev/null +++ b/entry/src/main/ets/entity/HomeMenuEntity.ets @@ -0,0 +1,23 @@ +import { ArrayList } from "@kit.ArkTS"; + +export class HomeMenuEntity { + icon: Resource | null = null; + title: string = ""; + alias: string = ""; + + constructor(icon: Resource, title: string, alias: string) { + this.icon = icon; + this.title = title; + this.alias = alias; + } +} + +export function homeMenuList(): ArrayList { + let list = new ArrayList() + list.add(new HomeMenuEntity($r('app.media.ic_home_icon6'), "视频转音频", "videoToAudio")) + list.add(new HomeMenuEntity($r('app.media.ic_home_icon8'), "视频加水印", "addWatermark")) + list.add(new HomeMenuEntity($r('app.media.ic_home_icon5'), "视频转文字", "videoToText")) + list.add(new HomeMenuEntity($r('app.media.ic_home_icon9'), "长图拼接", "longImageMerge")) + list.add(new HomeMenuEntity($r('app.media.ic_home_icon10'), "更多功能", "moreTools")) + return list; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/LoginEntity.ets b/entry/src/main/ets/entity/LoginEntity.ets new file mode 100644 index 0000000..5dbc476 --- /dev/null +++ b/entry/src/main/ets/entity/LoginEntity.ets @@ -0,0 +1,7 @@ + +export class LoginEntity { + user_id: string = ""; + name: string = ""; + avater: string = ""; + token: string = ""; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/MaterialEntity.ets b/entry/src/main/ets/entity/MaterialEntity.ets new file mode 100644 index 0000000..2dac547 --- /dev/null +++ b/entry/src/main/ets/entity/MaterialEntity.ets @@ -0,0 +1,13 @@ +import { UploadImgEntity } from './UploadImgEntity'; + +export class MaterialEntity { + id: string = ''; + title: string = ''; + pic?: UploadImgEntity = undefined; + pic_size: string = ''; + tags: Array = []; + user_id: string = ''; + download: string = ''; + update_time: string = ''; + create_time: string = ''; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/MaterialInfoEntity.ets b/entry/src/main/ets/entity/MaterialInfoEntity.ets new file mode 100644 index 0000000..de861fd --- /dev/null +++ b/entry/src/main/ets/entity/MaterialInfoEntity.ets @@ -0,0 +1,87 @@ +import { Type } from 'class-transformer'; +import "reflect-metadata"; +import systemDateTime from '@ohos.systemDateTime'; +import { RandomUtil } from '@pura/harmony-utils'; + +export class MaterialInfoEntity{ + enc_user_id: string = ''; + logid: string = ''; + @Type(() => MaterialInfoEntity) + material?: MaterialDetailEntity; + offline: number = 0; + status: number = 0; + status_name: string = ''; + timeout: number = 0; + user_id: string = ''; +} + +export class MaterialDetailEntity { + @Type(() => AudioMaterial) + audio?: Array; + desc: string = ''; + @Type(() => ImageMaterial) + image?: Array; + merge: boolean = false; + proxyUrlList?: Array; + threading: boolean = false; + title: string = ''; + @Type(() => VideoMaterial) + video?: Array; +} + +@ObservedV2 +export class MediaEntity { + name: string = "" + origin: string = "" + title: string = "" + url: string = "" + origin_url: string = "" + speed_up: number = 0 //1 加速 + isThreading: boolean = false + isMerge: boolean = false + currentLen: number = 0 + totalSize: number = 0 + continueDownload: boolean = false + cacheName: string = "" + logid: string = "" + headers?: Map + isM3u8: boolean = false + flag: number = 0 + @Trace isChecked: boolean = false + + initFileName(): string { + if (!this.name) { + if (this instanceof VideoMaterial) { + this.name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp4` + } else if (this instanceof AudioMaterial) { + this.name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp3` + } else if (this instanceof ImageMaterial) { + this.name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.jpeg` + } + } + return this.name + } +} + +export class VideoMaterial extends MediaEntity { + play: boolean = false + thumb?: string = '' + audio?: AudioMaterial + decodeKey?: Int8Array + sequenceNumber: number = 0 + keyInfo?: Map = new Map() +} + +export class AudioMaterial extends MediaEntity { + +} + +export class ImageMaterial extends MediaEntity { + +} + +export class TextMaterial extends MediaEntity { + title: string = '' + desc: string = '' +} + diff --git a/entry/src/main/ets/entity/MediaRecordEntity.ets b/entry/src/main/ets/entity/MediaRecordEntity.ets new file mode 100644 index 0000000..cd7b8cb --- /dev/null +++ b/entry/src/main/ets/entity/MediaRecordEntity.ets @@ -0,0 +1,14 @@ +import image from '@ohos.multimedia.image' + +export class MediaRecordEntity { + uri?: string = '' + name?: string = '' + thumb?: image.PixelMap = undefined + duration: number = 0 + createTime: number = 0 + isChecked: boolean = false + + constructor(uri: string) { + this.uri = uri + } +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/NoticeEntity.ets b/entry/src/main/ets/entity/NoticeEntity.ets new file mode 100644 index 0000000..94fc731 --- /dev/null +++ b/entry/src/main/ets/entity/NoticeEntity.ets @@ -0,0 +1,6 @@ + +export class NoticeEntity { + loop: boolean = true; + + notice?: Array = []; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/OrderEntity.ets b/entry/src/main/ets/entity/OrderEntity.ets new file mode 100644 index 0000000..82c59fa --- /dev/null +++ b/entry/src/main/ets/entity/OrderEntity.ets @@ -0,0 +1,14 @@ +export class OrderEntity { + id: string = ''; + create_time: string = ''; + goods_name: string = ''; + invoice_major: boolean = false; + invoice_status: string = ''; + invoice_url: string = ''; + out_trade_no: string = ''; + pay_params: string = ''; + pay_time: string = ''; + pay_type: string = ''; + status: string = ''; + total_fee: string = ''; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/OrderPayEntity.ets b/entry/src/main/ets/entity/OrderPayEntity.ets new file mode 100644 index 0000000..02e1543 --- /dev/null +++ b/entry/src/main/ets/entity/OrderPayEntity.ets @@ -0,0 +1,16 @@ +export class PayOrderEntity { + //支付宝 + appId: string = ''; + orderId: string = ''; + outTradeNo: string = ''; + payParam: string = ''; + payType: string = ''; + + //微信 + nonceStr: string = ''; + package: string = ''; + partnerId: string = ''; + prepayId: string = ''; + sign: string = ''; + timeStamp: string = ''; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/SendCodeEntity.ets b/entry/src/main/ets/entity/SendCodeEntity.ets new file mode 100644 index 0000000..cc2e0ec --- /dev/null +++ b/entry/src/main/ets/entity/SendCodeEntity.ets @@ -0,0 +1,3 @@ +export class SendCodeEntity { + timestamp: string = ''; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/UploadImgEntity.ets b/entry/src/main/ets/entity/UploadImgEntity.ets new file mode 100644 index 0000000..e0ac809 --- /dev/null +++ b/entry/src/main/ets/entity/UploadImgEntity.ets @@ -0,0 +1,4 @@ +export class UploadImgEntity { + id: string = '' + url: string = '' +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/UserConfigEntity.ets b/entry/src/main/ets/entity/UserConfigEntity.ets new file mode 100644 index 0000000..d61a9ad --- /dev/null +++ b/entry/src/main/ets/entity/UserConfigEntity.ets @@ -0,0 +1,12 @@ +import { Type } from 'class-transformer'; +import { ConfigEntity } from './ConfigEntity'; +import "reflect-metadata"; + +export class UserConfigEntity { + token: string = ""; + temp: boolean = false; + name: string = ""; + user_id: string = ""; + @Type(() => ConfigEntity) + config: ConfigEntity = new ConfigEntity(); +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/UserEntity.ets b/entry/src/main/ets/entity/UserEntity.ets new file mode 100644 index 0000000..97f56bf --- /dev/null +++ b/entry/src/main/ets/entity/UserEntity.ets @@ -0,0 +1,30 @@ +export class UserEntity { + appleid: string = ''; + avater: string = ''; + balance: string = ''; + city: string = ''; + client_cid: string = ''; + country: string = ''; + coupon_count: number = 0; + imei: string = ''; + month_download_count: string = '0'; + month_download_size: string = '0'; + name: string = ''; + oaid: string = ''; + os_version: string = ''; + phone: string = ''; + province: string = ''; + role: string = ''; + sex: number = 0; + show_contact_menu: boolean = true; + show_masonry_menu: boolean = true; + temp: boolean = true; + unionid: string = ''; + user_id: string = ''; + vip: number = 1; // 1非会员 2会员 3终生会员 + vip_expire: string = ''; + vip_expire_time: string = ''; + vip_name: string = ''; + weixinAppOpenId: string = ''; + ip_area: string = ''; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/VipMealEntity.ets b/entry/src/main/ets/entity/VipMealEntity.ets new file mode 100644 index 0000000..16ea99c --- /dev/null +++ b/entry/src/main/ets/entity/VipMealEntity.ets @@ -0,0 +1,16 @@ +@ObservedV2 +export class VipMealEntity { + @Trace checked: boolean = false; + goods_id: string = ''; + goods_name: string = ''; + origin_price: string = ''; + pay_type: string = ''; + price: string = ''; + single_pay_price: string = ''; + tips: string = ''; + sign_value = ''; + value: string = ''; + image: string = ''; + description: string = ''; + weixinMpOriId: string = ''; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/VipPermissionEntity.ets b/entry/src/main/ets/entity/VipPermissionEntity.ets new file mode 100644 index 0000000..a4e8cf3 --- /dev/null +++ b/entry/src/main/ets/entity/VipPermissionEntity.ets @@ -0,0 +1,13 @@ +export class VipPermissionEntity { + auth: boolean = false + auth_ad: boolean = false + scene: string = '' + user_id: number = 0 + vip: number = 0 + vip_expire: string = '' + vip_expire_time: string = '' + vip_goods_type: string = '' + vip_message: string = '' + vip_name: string = '' + type: number = 0 +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/WxServiceEntity.ets b/entry/src/main/ets/entity/WxServiceEntity.ets new file mode 100644 index 0000000..b07965d --- /dev/null +++ b/entry/src/main/ets/entity/WxServiceEntity.ets @@ -0,0 +1,8 @@ +import { Expose } from 'class-transformer'; +import "reflect-metadata"; + +export class WxServiceEntity { + corpid: string = "" + @Expose({ name: 'kf.address' }) + address: string = "" +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/WxVideoConfig.ets b/entry/src/main/ets/entity/WxVideoConfig.ets new file mode 100644 index 0000000..6e2a627 --- /dev/null +++ b/entry/src/main/ets/entity/WxVideoConfig.ets @@ -0,0 +1,8 @@ +export class WxVideoConfigEntity { + id: string = ''; + title: string = ''; + desc: string = ''; + descTitle: string = ''; + qrcode: string = ''; + tips: string = ''; +} \ No newline at end of file diff --git a/entry/src/main/ets/entity/WxVideoEntity.ets b/entry/src/main/ets/entity/WxVideoEntity.ets new file mode 100644 index 0000000..97657db --- /dev/null +++ b/entry/src/main/ets/entity/WxVideoEntity.ets @@ -0,0 +1,6 @@ +import { MaterialInfoEntity } from "./MaterialInfoEntity" + +export class WxVideoEntity { + items?: Array + playback: boolean = false +} \ No newline at end of file diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000..4d4cedb --- /dev/null +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,46 @@ +import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { window } from '@kit.ArkUI'; +import { AppUtil } from '@pura/harmony-utils'; +import { WXApi, WXEventHandler } from '../utils/wechat/WXApiEventHandlerImpl'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + AppUtil.init(this.context); + this.handleWeChatCallIfNeed(want); + } + + onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { + this.handleWeChatCallIfNeed(want); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + AppStorage.setOrCreate('windowStage', windowStage); + windowStage.loadContent('pages/splash/SplashPage', (err) => { + // this.registerFont(); + if (err.code) { + return; + } + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + } + + onDestroy(): void { + } + + onForeground(): void { + // Ability has brought to foreground + } + + onBackground(): void { + // Ability has back to background + } + + private handleWeChatCallIfNeed(want: Want) { //放在与onCreate同级 + WXApi.handleWant(want, WXEventHandler) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000..8e4de99 --- /dev/null +++ b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,16 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/manager/AVSessionManager.ets b/entry/src/main/ets/manager/AVSessionManager.ets new file mode 100644 index 0000000..b7313eb --- /dev/null +++ b/entry/src/main/ets/manager/AVSessionManager.ets @@ -0,0 +1,61 @@ +import { audio } from "@kit.AudioKit"; +import { BusinessError } from "@kit.BasicServicesKit"; + +class AVSessionManager { + private audioManager = audio.getAudioManager(); + private audioSessionManager: audio.AudioSessionManager = this.audioManager.getSessionManager(); + + private strategy: audio.AudioSessionStrategy = { + concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_PAUSE_OTHERS + }; + + /** + * 激活音频会话 + */ + async activate() { + if (!this.isActive()) { + await this.audioSessionManager.activateAudioSession(this.strategy).then(() => { + console.info('Succeeded in activating audio session.'); + }).catch((err: BusinessError) => { + console.error(`Failed to activate audio session. Code: ${err.code}, message: ${err.message}`); + }); + } + } + + /** + * 停用音频会话 + */ + async deactivate() { + if (this.isActive()) { + await this.audioSessionManager.deactivateAudioSession().then(() => { + console.info('Succeeded in deactivating audio session.'); + }).catch((err: BusinessError) => { + console.error(`Failed to deactivate audio session. Code: ${err.code}, message: ${err.message}`); + }); + } + } + + /** + * 会话是否激活 + * @returns + */ + isActive(): boolean { + return this.audioSessionManager.isAudioSessionActivated(); + } + + /** + * 添加会话停用监听 + */ + setDeactivatedCallback(callback: Callback) { + this.audioSessionManager.on('audioSessionDeactivated', callback); + } + + /** + * 移除会话停用监听 + */ + removeDeactivatedCallback(callback?: Callback) { + this.audioSessionManager.off('audioSessionDeactivated', callback); + } +} + +export const avSessionManager = new AVSessionManager() \ No newline at end of file diff --git a/entry/src/main/ets/manager/EventReportGlobalManager.ets b/entry/src/main/ets/manager/EventReportGlobalManager.ets new file mode 100644 index 0000000..4af6d5d --- /dev/null +++ b/entry/src/main/ets/manager/EventReportGlobalManager.ets @@ -0,0 +1,11 @@ +import { apiService } from "../net/ApiService" + +export class EventReportGlobalManager { + static async eventReport(key: string, value: string = '', extra: string = '') { + try { + apiService.eventReport(key, value, extra) + } catch (e) { + console.error(e) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/manager/LocalMediaManager.ets b/entry/src/main/ets/manager/LocalMediaManager.ets new file mode 100644 index 0000000..b25dfac --- /dev/null +++ b/entry/src/main/ets/manager/LocalMediaManager.ets @@ -0,0 +1,67 @@ +import { PrefUtils } from '../utils/PrefUtils'; +import { JSON } from '@kit.ArkTS'; + +/** + * 保存到相册的媒体列表 + */ +export class LocalMediaManager { + static add(uri: string) { + let array = PrefUtils.getStringArray('local_record') + if (!array.includes(uri)) { + array.push(uri) + } + PrefUtils.put('local_record', JSON.stringify(array)) + } + + static delete(uri: string) { + let array = PrefUtils.getStringArray('local_record') + if (array.includes(uri)) { + array.splice(array.indexOf(uri), 1) + } + PrefUtils.put('local_record', JSON.stringify(array)) + } + + static getAllVideos(): Array { + let array = PrefUtils.getStringArray('local_record') + return array.filter(item => item.endsWith('.mp4')) + } + + static deleteAllVideos() { + let imageArray = LocalMediaManager.getAllImages() + let audioArray = LocalMediaManager.getAllAudios() + let newArray = imageArray.concat(audioArray) + PrefUtils.put('local_record', JSON.stringify(newArray)) + } + + static getAllAudios(): Array { + let array = PrefUtils.getStringArray('local_record') + return array.filter(item => item.endsWith('.mp3')) + } + + static deleteAllAudios() { + let videoArray = LocalMediaManager.getAllVideos() + let audioArray = LocalMediaManager.getAllImages() + let newArray = videoArray.concat(audioArray) + PrefUtils.put('local_record', JSON.stringify(newArray)) + } + + static getAllImages(): Array { + let array = PrefUtils.getStringArray('local_record') + return array.filter(item => item.endsWith('.jpeg')) + } + + static deleteAllImages() { + let videoArray = LocalMediaManager.getAllVideos() + let audioArray = LocalMediaManager.getAllAudios() + let newArray = videoArray.concat(audioArray) + PrefUtils.put('local_record', JSON.stringify(newArray)) + } + + static getAll(): Array { + return PrefUtils.getStringArray('local_record') + } + + static deleteAll() { + PrefUtils.remove('local_record') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/manager/LoginGlobalManager.ets b/entry/src/main/ets/manager/LoginGlobalManager.ets new file mode 100644 index 0000000..de789be --- /dev/null +++ b/entry/src/main/ets/manager/LoginGlobalManager.ets @@ -0,0 +1,92 @@ +import { StrUtil } from '@pura/harmony-utils'; +import { UserEntity } from '../entity/UserEntity'; +import { PrefUtils } from '../utils/PrefUtils'; +import { ConfigManager } from './UserConfigManager'; +import { JSON } from '@kit.ArkTS'; +import { plainToInstance } from 'class-transformer'; + +class LoginGlobalManager { + + private userInfo?: UserEntity; + + /** + * 保存当前用户基本信息 + * @param userEntity + */ + setUserInfo(userEntity: UserEntity) { + this.userInfo = userEntity; + } + + /** + * 获取当前用户基本信息 + * @returns + */ + getUserInfo(): UserEntity | undefined { + return this.userInfo; + } + + /** + * 保存上一个用户基本信息 + * @param userEntity + */ + saveLastUserInfo(userEntity: UserEntity) { + PrefUtils.put('last_userinfo', JSON.stringify(userEntity)); + } + + /** + * 获取上一个用户基本信息 + * @returns + */ + getLastUserInfo(): UserEntity | null { + const str = PrefUtils.getString('last_userinfo'); + if (StrUtil.isNotEmpty(str)) { + const userEntity = plainToInstance(UserEntity, JSON.parse(str)); + return userEntity; + } + return null; + } + + /** + * 保存上次登录方式 + * @param userEntity + */ + saveLastLoginType(type: string) { + PrefUtils.put('last_login_type', type); + } + + /** + * 获取上次登录方式 + * @returns + */ + getLastLoginType(): string { + return PrefUtils.getString('last_login_type'); + } + + /** + * 用户token + * @param token + */ + saveToken(token: string) { + PrefUtils.put('x-token', token); + } + + getToken(): string { + return PrefUtils.getString('x-token'); + } + + /** + * 是否已登录 + * @returns + */ + isLogin(): boolean { + return !ConfigManager.isTemp(); + } + + logout() { + PrefUtils.remove('x-role'); + PrefUtils.remove('x-token'); + PrefUtils.remove('userConfig') + } +} + +export const LoginManager = new LoginGlobalManager(); \ No newline at end of file diff --git a/entry/src/main/ets/manager/MediaManager.ets b/entry/src/main/ets/manager/MediaManager.ets new file mode 100644 index 0000000..6718ece --- /dev/null +++ b/entry/src/main/ets/manager/MediaManager.ets @@ -0,0 +1,119 @@ +import { MediaRecordEntity } from '../entity/MediaRecordEntity'; +import { LocalMediaManager } from './LocalMediaManager'; +import { FileUtil, NumberUtil } from '@pura/harmony-utils'; +import { fileIo as fs } from '@kit.CoreFileKit'; +import { media } from '@kit.MediaKit'; + +export enum MediaAction { + ADD, DELETE, CLEAR +} + +export enum MediaType { + VIDEO, IMAGE, AUDIO +} + +export class MediaManager { + + /** + * 获取相册中保存的视频 + * @returns + */ + static async getVideoList(): Promise> { + try { + let mediaList = new Array() + + let videoUris = LocalMediaManager.getAllVideos() + for (let i = 0; i < videoUris.length; i++) { + try { + let uri = videoUris[i] + let record = new MediaRecordEntity(uri) + record.name = FileUtil.getFileName(uri) + let file = FileUtil.openSync(uri) + + let videoSize: media.PixelMapParams = {} + let avMetaDataExtractor: media.AVMetadataExtractor = await media.createAVMetadataExtractor(); + avMetaDataExtractor.fdSrc = file; + let metadata = await avMetaDataExtractor.fetchMetadata(); + videoSize.width = parseInt(metadata.videoWidth as string); + videoSize.height = parseInt(metadata.videoHeight as string); + + let avImageGenerator = await media.createAVImageGenerator(); + if (avImageGenerator) { + avImageGenerator.fdSrc = file; + record.thumb = + await avImageGenerator.fetchFrameByTime(0, media.AVImageQueryOptions.AV_IMAGE_QUERY_CLOSEST_SYNC, + videoSize); + } else { + console.error('Create AVImageGenerator failed!'); + } + mediaList.push(record) + } catch (e) { + console.error(e) + } + } + return Promise.resolve(mediaList) + } catch (err) { + console.error('video failed with err: ' + err); + return Promise.reject(err) + } + } + + /** + * 获取相册中保存的图片 + * @returns + */ + static async getImageList(): Promise> { + let mediaList = new Array() + + let imageUri = LocalMediaManager.getAllImages() + for (let i = 0; i < imageUri.length; i++) { + try { + let uri = imageUri[i] + let file = FileUtil.openSync(uri) + let record = new MediaRecordEntity(uri) + record.name = FileUtil.getFileName(uri) + mediaList.push(record) + } catch (e) { + console.error(e) + } + } + return Promise.resolve(mediaList) + } + + /** + * 获取保存的音频 + * @returns + */ + static async getAudioList(): Promise> { + try { + let mediaList = new Array() + + let audioUris = LocalMediaManager.getAllAudios() + for (let i = 0; i < audioUris.length; i++) { + let uri = audioUris[i] + let filePath = FileUtil.getFilePath(uri) + if (FileUtil.accessSync(filePath)) { + let record = new MediaRecordEntity(filePath) + record.name = FileUtil.getFileName(filePath) + let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE) + let stat = fs.statSync(file.fd) + record.createTime = stat.ctime + let avMetadataExtractor = await media.createAVMetadataExtractor() + avMetadataExtractor.fdSrc = file + // 获取元数据(promise模式) + let result = await avMetadataExtractor.fetchMetadata() + record.duration = result.duration ? NumberUtil.toNumber(result.duration) : 0 + avMetadataExtractor.release() + fs.close(file) + mediaList.push(record) + } else { + LocalMediaManager.delete(uri) + } + } + return Promise.resolve(mediaList) + } catch (err) { + console.error('audio failed with err: ' + err); + return Promise.reject(err) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/manager/ShareManager.ets b/entry/src/main/ets/manager/ShareManager.ets new file mode 100644 index 0000000..a7d3e4d --- /dev/null +++ b/entry/src/main/ets/manager/ShareManager.ets @@ -0,0 +1,40 @@ +import { systemShare } from '@kit.ShareKit'; +import { uniformTypeDescriptor as utd } from '@kit.ArkData'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { AppUtil, FileUtil } from '@pura/harmony-utils'; + +export class ShareManager { + /** + * 分享文件 + * @param path + */ + static shareFile(path: string) { + // 获取精准的utd类型 + let ext = '' + let type = '' + if (path.endsWith('.mp4')) { + ext = '.mp4' + type = utd.UniformDataType.VIDEO + } else if (path.endsWith('.mp3')) { + ext = '.mp3' + type = utd.UniformDataType.AUDIO + } else if (path.endsWith('.jpeg')) { + ext = '.jpeg' + type = utd.UniformDataType.IMAGE + } + let utdTypeId = utd.getUniformDataTypeByFilenameExtension(ext, type); + let shareData: systemShare.SharedData = new systemShare.SharedData({ + utd: utdTypeId, + uri: FileUtil.getUriFromPath(path) + }); + let controller: systemShare.ShareController = new systemShare.ShareController(shareData); + controller.show(AppUtil.getContext(), { + selectionMode: systemShare.SelectionMode.SINGLE, + previewMode: systemShare.SharePreviewMode.DETAIL, + }).then(() => { + console.info('ShareController show success.'); + }).catch((error: BusinessError) => { + console.error(`ShareController show error. code: ${error.code}, message: ${error.message}`); + }); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/manager/UserConfigManager.ets b/entry/src/main/ets/manager/UserConfigManager.ets new file mode 100644 index 0000000..959f946 --- /dev/null +++ b/entry/src/main/ets/manager/UserConfigManager.ets @@ -0,0 +1,368 @@ +import { ArrayUtil, PermissionUtil, StrUtil } from '@pura/harmony-utils'; +import { apiService } from '../net/ApiService'; +import { PrefUtils } from '../utils/PrefUtils'; +import { JSON } from '@kit.ArkTS'; +import { UserConfigEntity } from '../entity/UserConfigEntity'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import { BannerEntity } from '../entity/BannerEntity'; +import identifier from '@ohos.identifier.oaid'; +import { LoginManager } from './LoginGlobalManager'; +import { WxVideoConfigEntity } from '../entity/WxVideoConfig'; + +class UserConfigManager { + private INVALID_OAID = "00000000-0000-0000-0000-000000000000"; + + async getOaid(): Promise { + try { + const isGranted = await PermissionUtil.checkRequestPermissions('ohos.permission.APP_TRACKING_CONSENT'); + if (isGranted) { + const oaid = await identifier.getOAID(); + if (StrUtil.isNotEmpty(oaid) && oaid !== this.INVALID_OAID) { + PrefUtils.put("oaid", oaid) + return Promise.resolve(oaid); + } + } + } catch (e) { + console.error(e); + } + return Promise.resolve(''); + } + + async getUserConfig(): Promise { + if (StrUtil.isNotEmpty(PrefUtils.getString('userConfig'))) { + const str = PrefUtils.getString('userConfig'); + const userConfig = plainToInstance(UserConfigEntity, JSON.parse(str)); + this.saveUserConfig(userConfig); + return Promise.resolve() + } else { + return this.userConfig(); + } + } + + async userConfig(): Promise { + try { + const oaid = PrefUtils.getString('oaid'); + const result = await apiService.userConfig(oaid); + if (result.isSuccess()) { + const userConfig = plainToInstance(UserConfigEntity, result.data); + PrefUtils.put("userConfig", JSON.stringify(instanceToPlain(userConfig))) + this.saveUserConfig(userConfig); + return Promise.resolve(); + } else { + return Promise.reject(result.message); + } + } catch (e) { + console.error(e); + return Promise.reject(e); + } + } + + private saveUserConfig(config: UserConfigEntity) { + LoginManager.saveToken(config.token); + this.saveIsTemp(config.temp); + if (config.config !== null) { + this.saveGuideEnable(config.config.guideEnable); + this.saveGuideHint(config.config.guideHint); + this.saveGuidePayEnable(config.config.guidePayEnable); + this.saveNoLoginPayEnable(config.config.noLoginPayEnable); + this.savePayAgreementEnable(config.config.payAgreementEnable); + this.saveWxVideoEnable(config.config.wxVideoEnable); + this.savePlaybackEnable(config.config.playbackEnable); + this.saveLoginType(config.config.loginType); + this.saveWxVideoCourse(config.config.wxVideoCourse); + this.savePlaybackCourse(config.config.wxPlaybackCourse); + this.saveWxPlaybackJoinType(config.config.wxPlaybackJoinType); + this.saveWxVideoConfig(config.config.wxVideoConfig); + this.saveWxVideoServiceEnable(config.config.wxVideoServiceEnable); + this.saveDomainMap(config.config.domainMap); + this.saveCopyContainsList(config.config.copyContainsList); + this.saveHomeBanner(config.config.homeBanners); + } + } + + /** + * 是否同意隐私政策 + * @param agree + */ + saveIsAgreePrivacy(agree: boolean) { + PrefUtils.put('agree_privacy', agree); + } + + isAgreePrivacy(): boolean { + return PrefUtils.getBoolean('agree_privacy'); + } + + /** + * 首次使用app + * @param isFirst + */ + saveFirstUse(isFirst: boolean) { + PrefUtils.put('is_first', isFirst); + } + + isFirstUse(): boolean { + return PrefUtils.getBoolean('is_first', true); + } + + /** + * 用户角色 + * @param temp true临时用户 ,false登录用户 + */ + saveIsTemp(temp: boolean) { + PrefUtils.put('x-role', temp); + } + + isTemp(): boolean { + return PrefUtils.getBoolean('x-role', true); + } + + /** + * 是否启用引导页 + * @param enable + */ + saveGuideEnable(enable: boolean) { + PrefUtils.put('guide_enable', enable); + } + + isGuideEnable(): boolean { + return PrefUtils.getBoolean('guide_enable', true); + } + + /** + * 引导页提示语 + * @param enable + */ + saveGuideHint(hint: string) { + PrefUtils.put('guide_hint', hint); + } + + getGuideHint(): string { + return PrefUtils.getString('guide_hint'); + } + + /** + * 是否启用引导页支付 + * @param enable + */ + saveGuidePayEnable(enable: boolean) { + PrefUtils.put('guide_pay_enable', enable); + } + + isGuidePayEnable(): boolean { + return PrefUtils.getBoolean('guide_pay_enable', true); + } + + /** + * 是否启用未登录支付 + * @param enable + */ + saveNoLoginPayEnable(enable: boolean) { + PrefUtils.put('nologin_pay', enable); + } + + isNoLoginPayEnable(): boolean { + return PrefUtils.getBoolean('nologin_pay', true); + } + + /** + * 是否显示支付协议 + * @param enable + */ + savePayAgreementEnable(enable: boolean) { + PrefUtils.put('pay_agreement', enable); + } + + isPayAgreementEnable(): boolean { + return PrefUtils.getBoolean('pay_agreement', true); + } + + /** + * 是否启用视频号功能 + * @param enable + */ + saveWxVideoEnable(enable: boolean) { + PrefUtils.put('wx_video_enable', enable); + } + + isWxVideoEnable(): boolean { + return PrefUtils.getBoolean('wx_video_enable', true); + } + + /** + * 是否启用直播回放功能 + * @param enable + */ + savePlaybackEnable(enable: boolean) { + PrefUtils.put('playback_enable', enable); + } + + isPlaybackEnable(): boolean { + return PrefUtils.getBoolean('playback_enable', true); + } + + /** + * 是否已绑定视频号助手 + * @param isBind + */ + saveBindWxVideoHelper(isBind: boolean) { + PrefUtils.put('bind_wx_video', isBind); + } + + isBindWxVideoHelper(): boolean { + return PrefUtils.getBoolean('bind_wx_video'); + } + + /** + * 是否已绑定直播回放助手 + * @param isBind + */ + saveBindWxPlaybackHelper(isBind: boolean) { + PrefUtils.put('bind_wx_playback', isBind); + } + + isBindWxPlaybackHelper(): boolean { + return PrefUtils.getBoolean('bind_wx_playback'); + } + + /** + * 支持的登录类型 + * @param array + */ + saveLoginType(array?: Array) { + if (ArrayUtil.isNotEmpty(array)) { + PrefUtils.put('login_type', JSON.stringify(array)); + } + } + + getLoginType(): Array { + const str = PrefUtils.getString('login_type'); + if (StrUtil.isNotEmpty(str)) { + return JSON.parse(str) as Array; + } + return new Array('phone'); + } + + /** + * 视频号教程链接 + * @param url + */ + saveWxVideoCourse(url?: string) { + if (StrUtil.isNotEmpty(url)) { + PrefUtils.put('wx_video_course', url!!); + } + } + + getWxVideoCourse(): string { + return PrefUtils.getString('wx_video_course'); + } + + /** + * 直播回放教程链接 + * @param url + */ + savePlaybackCourse(url?: string) { + if (StrUtil.isNotEmpty(url)) { + PrefUtils.put('playback_course', url!!); + } + } + + getPlaybackCourse(): string { + return PrefUtils.getString('playback_course'); + } + + /** + * 直播回放加群方式 + * @param type + */ + saveWxPlaybackJoinType(type?: string) { + if (StrUtil.isNotEmpty(type)) { + PrefUtils.put('playback_join_type', type!!); + } + } + + getPlaybackJoinType(): string { + return PrefUtils.getString('playback_join_type'); + } + + /** + * 直播回放小程序跳转配置 + * @param config + */ + saveWxVideoConfig(config: WxVideoConfigEntity) { + PrefUtils.put('wx_video_config', JSON.stringify(config)) + } + + getWxVideoConfig(): WxVideoConfigEntity | null { + const str = PrefUtils.getString('wx_video_config') + if (StrUtil.isNotEmpty(str)) { + return JSON.parse(str) as WxVideoConfigEntity + } + return null + } + + /** + * 视频号助手是否可用 + * @param enable + */ + saveWxVideoServiceEnable(enable: boolean) { + PrefUtils.put("wx_video_service_enable", enable) + } + + isWxVideoServiceEnable(): boolean { + return PrefUtils.getBoolean('wx_video_service_enable', true) + } + + /** + * 网站主机名 + * @param domains + */ + saveDomainMap(domains: object) { + if (domains) { + PrefUtils.put('domain_map', JSON.stringify(domains)); + } + } + + getDomainMap(): object { + const str = PrefUtils.getString('domain_map'); + if (StrUtil.isNotEmpty(str)) { + return JSON.parse(str)!! + } + return new Object(); + } + + /** + * 链接识别配置 + * @param array + */ + saveCopyContainsList(array: Array) { + PrefUtils.put('copy_contains_list', JSON.stringify(array)) + } + + getCopyContainsList(): Array { + let array = PrefUtils.getStringArray('copy_contains_list') + if (array.length !== 0) { + return array + } + return ['http://', 'https://'] + } + + /** + * 首页banner + * @param array + */ + saveHomeBanner(array?: Array) { + if (ArrayUtil.isNotEmpty(array)) { + PrefUtils.put('home_banner', JSON.stringify(array)); + } + } + + getHomeBanner(): Array { + const str = PrefUtils.getString('home_banner'); + if (StrUtil.isNotEmpty(str)) { + return JSON.parse(str) as Array; + } + return new Array(); + } +} + +export const ConfigManager = new UserConfigManager() \ No newline at end of file diff --git a/entry/src/main/ets/net/Api.ets b/entry/src/main/ets/net/Api.ets new file mode 100644 index 0000000..d37197d --- /dev/null +++ b/entry/src/main/ets/net/Api.ets @@ -0,0 +1,116 @@ +export class Api { + /** + * 获取用户配置信息 + */ + static readonly USER_CONFIG = '/api/user/config'; + + /** + * 用户基本信息 + */ + static readonly USERINFO = '/api/user'; + + /** + * 事件上报 + */ + static readonly EVENT_REPORT = '/api/user/event' + + /** + * 图片上传 + */ + static readonly UPLOAD_IMAGE = '/api/user/upload' + + /** + * 意见反馈 + */ + static readonly FEEDBACK = '/api/user/feedback' + + /** + * 发送验证码 + */ + static readonly SEND_CODE = '/api/user/code'; + + /** + * 用户登录 + */ + static readonly LOGIN = '/api/user/login'; + + /** + * 扫码登录 + */ + static readonly QRCODE_LOGIN = '/api/app/code'; + + /** + * 用户注销 + */ + static readonly USER_DESTROY = '/api/user/destroy'; + + /** + * 用户账号列表 + */ + static readonly ACCOUNT_LIST = '/api/user/account'; + + /** + * 套餐列表 + */ + static readonly GOODS_LIST = '/api/order/goods'; + + /** + * 创建订单 + */ + static readonly CREATE_ORDER = '/api/order'; + + /** + * 微信客服 + */ + static readonly WX_SERVICE = '/api/weixin/service' + + /** + * 钻石信息 + */ + static readonly USER_DIAMOND_INFO = '/api/diamond' + + /** + * 权限验证 + */ + static readonly USER_AUTH = '/api/user/auth'; + + /** + * 首页顶部通知 + */ + static readonly NOTICE_LIST = '/api/user/notice'; + + /** + * 链接提取 + */ + static readonly MATERIAL_INFO = '/api/material'; + + /** + * 视频号和直播回放提取 + */ + static readonly WX_VIDEO = '/api/weixin/video/log'; + + /** + * 跳转至微信发送视频号给客服 + */ + static readonly WX_VIDEO_SERVICE = '/api/weixin/video/service' + + /** + * 绑定微信用户信息 + */ + static readonly BIND_WX_USER_INFO = '/api/weixin/user/info' + + /** + * 下载记录 + */ + static readonly DOWNLOAD_HISTORY_LIST = '/api/material/log' + + /** + * 素材列表 + */ + static readonly MATERIAL_LIST = '/api/user/mat/search' + + /** + * 素材分类列表 + */ + static readonly MATERIAL_CATE_LIST = '/api/mat/cate' +} \ No newline at end of file diff --git a/entry/src/main/ets/net/ApiService.ets b/entry/src/main/ets/net/ApiService.ets new file mode 100644 index 0000000..2605da3 --- /dev/null +++ b/entry/src/main/ets/net/ApiService.ets @@ -0,0 +1,390 @@ +import { Api } from './Api'; +import { AxiosRequest } from './AxiosRequest'; +import { HttpResult } from './HttpResult'; +import { deviceInfo } from '@kit.BasicServicesKit'; +import { DeviceUtil, FileUtil } from '@pura/harmony-utils'; +import { LoginRequest } from './request/LoginRequest'; +import { FormData } from '@ohos/axios'; +import { fileIo } from '@kit.CoreFileKit'; +import { JSON } from '@kit.ArkTS'; + +class ApiService { + /** + * 用户配置 + * @param oaid + * @returns + */ + userConfig(oaid: string): Promise { + const params: Record = { + 'oaid': oaid, + 'os_version': deviceInfo.buildVersion.toString(), + 'imei': DeviceUtil.getDeviceId(), + 'cid': '' + } + return AxiosRequest.get(Api.USER_CONFIG, params) + } + + /** + * 用户信息 + * @returns + */ + userinfo(): Promise { + return AxiosRequest.get(Api.USERINFO) + } + + /** + * 更新用户信息 + * @returns + */ + updateUserinfo(params: Record): Promise { + return AxiosRequest.put(Api.USERINFO, params) + } + + /** + * 事件上报 + * @param key + * @param value + * @param extra + * @returns + */ + eventReport(key: string, value: string, extra: string): Promise { + const params: Record = { + 'source': 'android', + 'type': 'click', + 'key': key, + 'value': value, + 'extra': extra + } + return AxiosRequest.post(Api.EVENT_REPORT, params); + } + + /** + * 图片上传 + * @returns + */ + uploadImage(base64: string, scene: string): Promise { + const data: Record = { + 'file': base64 + } + const params: Record = { + 'type': 'base64', + 'scene': scene + } + return AxiosRequest.request({ + url: Api.UPLOAD_IMAGE, + method: 'post', + data: data, + params: params // query参数 + }) + } + + feedback(type: string, content: string, contact: string, images: Array): Promise { + const params: Record = { + 'type': type, + 'content': content, + 'contact': contact, + 'images': images + } + return AxiosRequest.post(Api.FEEDBACK, params) + } + + /** + * 发送验证码 + * @returns + */ + sendCode(phone: string): Promise { + const params: Record = { 'phone': phone } + return AxiosRequest.post(Api.SEND_CODE, params) + } + + /** + * 手机号登录 + * @returns + */ + loginByPhone(phone: string, code: string, timestamp: string, isBind: boolean = false): Promise { + const params: LoginRequest = { + login_type: 'phone', + phone: { + 'phone': phone, + 'code': code, + 'timestamp': timestamp + }, + bind: isBind + } + return AxiosRequest.post(Api.LOGIN, params) + } + + /** + * 微信登录 + * @returns + */ + loginByWX(code: string, isBind: boolean = false): Promise { + const params: LoginRequest = { + login_type: 'weixin', + weixin: { + 'code': code, + 'code_type': '' + }, + bind: isBind + } + return AxiosRequest.post(Api.LOGIN, params) + } + + /** + * 微信登录 + * @returns + */ + loginByCode(code: string): Promise { + const params: Record = { 'key': code } + return AxiosRequest.put(Api.QRCODE_LOGIN, params) + } + + /** + * 注销账户 + * @returns + */ + userDestroy(): Promise { + return AxiosRequest.post(Api.USER_DESTROY) + } + + /** + * 解绑账号 + * @returns + */ + unbind(loginType: string): Promise { + const params: LoginRequest = { + login_type: loginType, + unbind: true + } + return AxiosRequest.post(Api.LOGIN, params); + } + + /** + * 账号列表 + * @returns + */ + accountList(scene: string): Promise { + const params: Record = { 'scene': scene } + return AxiosRequest.get(Api.ACCOUNT_LIST, params); + } + + /** + * 切换账户 + * @param userId + * @returns + */ + changeAccount(userId: string): Promise { + const params: LoginRequest = { + login_type: 'device', + device: { + 'user_id': userId + } + } + return AxiosRequest.post(Api.LOGIN, params) + } + + /** + * 套餐列表 + * @returns + */ + goodsList(type: string): Promise { + const params: Record = { 'type': type } + return AxiosRequest.get(Api.GOODS_LIST, params) + } + + /** + * 创建订单 + * @returns + */ + createOrder(goodsId: string, payType: string, source: string, coupon: string): Promise { + const params: Record = { + 'goods_id': goodsId, + 'pay_type': payType, + 'source': source, + 'pay_source': 'app', + 'coupon': coupon + } + return AxiosRequest.post(Api.CREATE_ORDER, params) + } + + /** + * 查询订单 + * @param orderId + * @returns + */ + getOrderInfo(orderId: string): Promise { + const params: Record = { 'order_id': orderId} + return AxiosRequest.get(Api.CREATE_ORDER, params) + } + + /** + * 微信客服 + * @returns + */ + wxService(): Promise { + return AxiosRequest.get(Api.WX_SERVICE) + } + + /** + * 钻石信息 + */ + getDiamondInfo(): Promise { + return AxiosRequest.get(Api.USER_DIAMOND_INFO) + } + + /** + * 检查权限 + * @param scene wechat download + * @returns + */ + checkPermission(scene: string): Promise { + const params: Record = { 'scene': scene } + return AxiosRequest.get(Api.USER_AUTH, params) + } + + /** + * 上报权限 + * @param scene + * @param count + * @returns + */ + sendCheckPermission(scene: string, count: number): Promise { + const params: Record = { 'scene': scene, 'count': count } + return AxiosRequest.post(Api.USER_AUTH, params) + } + + /** + * 首页顶部通知 + * @returns + */ + noticeList(): Promise { + return AxiosRequest.get(Api.NOTICE_LIST) + } + + /** + * 发送链接 + * @returns + */ + getMaterialInfo(content: string): Promise { + const params: Record = { 'content': content } + return AxiosRequest.post(Api.MATERIAL_INFO, params) + } + + /** + * 获取提取状态信息 + * @returns + */ + analysisMaterial(logId: string): Promise { + const params: Record = { 'logid': logId } + return AxiosRequest.get(Api.MATERIAL_INFO, params) + } + + /** + * 获取视频号和直播回放 + * @returns + */ + wxVideoList(scene: string): Promise { + const params: Record = { 'v': 'v2', 'scene': scene } + return AxiosRequest.get(Api.WX_VIDEO, params) + } + + /** + * 删除视频号和直播回放 + * @returns + */ + deleteWxVideo(logId: string): Promise { + const params: Record = { 'logId': logId } + return AxiosRequest.delete(Api.WX_VIDEO, params) + } + + /** + * 上报下载状态 + */ + reportDownLoadStatus(logId: string, status: string, size: string, message: string): Promise { + const params: Record = { + 'logid': logId, + 'status': status, + 'size': size, + 'message': message + } + return AxiosRequest.put(Api.MATERIAL_INFO, params) + } + + /** + * 跳转至微信发送视频号给客服 + */ + wxVideoService(): Promise { + return AxiosRequest.get(Api.WX_VIDEO_SERVICE) + } + + /** + * 跳转至微信发送视频号给客服 + */ + bindWxUserInfo(code: string): Promise { + return AxiosRequest.request({ + url: Api.BIND_WX_USER_INFO, + method: 'post', + params: { code: code } // query参数 + }) + } + + /** + * 获取下载记录 + * @returns + */ + getDownloadHistoryList(page: string, startTime: string, endTime: string): Promise { + const params: Record = { + 'status': '2', + 'is_deleted': '0', + 'page': page, + 'size': '20', + 'start_time': startTime, + 'end_time': endTime + } + return AxiosRequest.get(Api.DOWNLOAD_HISTORY_LIST, params) + } + + /** + * 删除下载记录 + * @returns + */ + deleteDownloadHistory(startTime: string, endTime: string): Promise { + const params: Record = { + 'status': '2', + 'is_deleted': '0', + 'page': '1', + 'size': '9999', + 'start_time': startTime, + 'end_time': endTime + } + return AxiosRequest.delete(Api.DOWNLOAD_HISTORY_LIST, params) + } + + /** + * 获取素材列表 + * @returns + */ + getMaterialList(page: string, cateId: string = '', keywords: string = ''): Promise { + const params: Record = { + 'page': page, + 'size': '20', + 'cate_id': cateId, + 'keywords': keywords + } + return AxiosRequest.get(Api.MATERIAL_LIST, params) + } + + /** + * 获取素材分类列表 + * @returns + */ + getMaterialCateList(page: string): Promise { + const params: Record = { + 'page': page, + 'size': '50' + } + return AxiosRequest.get(Api.MATERIAL_CATE_LIST, params) + } +} + +export const apiService = new ApiService(); \ No newline at end of file diff --git a/entry/src/main/ets/net/AxiosRequest.ets b/entry/src/main/ets/net/AxiosRequest.ets new file mode 100644 index 0000000..52511e3 --- /dev/null +++ b/entry/src/main/ets/net/AxiosRequest.ets @@ -0,0 +1,168 @@ +import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, + FormData, + InternalAxiosRequestConfig } from '@ohos/axios'; +import BuildProfile from 'BuildProfile'; +import { Constants } from '../common/Constants'; +import deviceInfo from '@ohos.deviceInfo'; +import { AppUtil, DeviceUtil, JSONUtil, MD5, RandomUtil, StrUtil } from '@pura/harmony-utils'; +import systemDateTime from '@ohos.systemDateTime'; +import { JSON } from '@kit.ArkTS'; +import { AESpkcs7paddingUtil } from '../utils/AESpkcs7paddingUtil'; +import { HttpResult } from './HttpResult'; +import { plainToInstance } from 'class-transformer'; +import { LoginManager } from '../manager/LoginGlobalManager'; +import { router } from '@kit.ArkUI'; +import { RouterUrls } from '../common/RouterUrls'; +import { EventConstants } from '../common/EventConstants'; + +const instance = axios.create({ + baseURL: BuildProfile.DEBUG ? Constants.TEST_URL : Constants.BASE_URL, + timeout: 20000 +}); + +// 请求头拦截器 +instance.interceptors.request.use((config: InternalAxiosRequestConfig) => { + config.headers.set('x-token', LoginManager.getToken()) + config.headers.set('x-app-id', Constants.APP_ID) + config.headers.set('x-device-id', DeviceUtil.getDeviceId()) + config.headers.set('x-version', BuildProfile.VERSION_NAME) + config.headers.set('x-mobile-brand', deviceInfo.brand) + config.headers.set('x-mobile-model', deviceInfo.productModel) + config.headers.set('x-base-version', BuildProfile.VERSION_NAME) + config.headers.set('x-package', 'com.yuan.scmf') + config.headers.set('x-platform', 'android') + config.headers.set('x-channel', 'scmf_hmos') + return config; +}, (error: AxiosError) => { + return Promise.reject(error); +}); + +// 请求内容拦截器 +instance.interceptors.request.use((config: InternalAxiosRequestConfig) => { + let url = config.url; + if (!url?.includes("user/upload")) { + let method = config.method?.toLowerCase(); + if (method === "post" || method === "put") { + config.headers.set("Content-Type", "application/json; charset=utf-8"); + } + } + return config; +}, (error: AxiosError) => { + return Promise.reject(error); +}); + +// 请求内容加密拦截器 +instance.interceptors.request.use((config: InternalAxiosRequestConfig) => { + if (config.baseURL === Constants.BASE_URL) { + config.params = config.params || {}; + config.params.nonce = RandomUtil.generateUUID36() + config.params.timestamp = Math.trunc(systemDateTime.getTime() / 1000) + let paramsMap = JSONUtil.jsonToMap(JSON.stringify(config.params)); + let arrayMap = Array.from(paramsMap); + arrayMap.sort((a, b) => { + return a[0].localeCompare(b[0]) + }) + paramsMap = new Map(arrayMap) + let sortQueryString = ""; + paramsMap.forEach((value, key) => { + sortQueryString += key + "=" + value + "&" + }) + sortQueryString = sortQueryString.substring(0, sortQueryString.length - 1) + let signature = MD5.digestSync(sortQueryString + '&' + MD5.digestSync(Constants.SIGNATURE)); + let method = config.method?.toLowerCase(); + if (method === "post" || method === "put") { + if (config.data) { + let dataStr = JSON.stringify(config.data); + if (StrUtil.isNotEmpty(dataStr)) { + signature = MD5.digestSync(sortQueryString + '&' + dataStr + "&" + MD5.digestSync(Constants.SIGNATURE)); + } + } + } + config.params.signature = signature; + } + return config; +}, (error: AxiosError) => { + return Promise.reject(error); +}); + +// 响应拦截器 +instance.interceptors.response.use((response: AxiosResponse) => { + if (response.config.baseURL === (BuildProfile.DEBUG ? Constants.TEST_URL : Constants.BASE_URL)) { + try { + let contentType = response.headers['content-type'] as string; + let dataString: string = ''; + if (StrUtil.isNotEmpty(contentType) && contentType?.includes("application/json")) { + if (response.data !== null) { + let isEncrypt = response.data['encrypt'] as boolean + dataString = response.data['data'] as string; + if (isEncrypt) { + dataString = AESpkcs7paddingUtil.decryptNormal(dataString, Constants.ENCRYPT); + let decData = JSON.parse(dataString) as Record; + let decCode = decData['code'] as number; + switch (decCode) { + case 11018: { + // 刷新首页 + AppUtil.getContext().eventHub.emit(EventConstants.HomeRefreshEvent); + AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent); + break; + } + case 19000: { + // 跳转Vip页面 + router.push({url: RouterUrls.VIP_PAGE, params: { origin: response.config.url }}) + break; + } + case 1001003: { + } + case 1001004: { + + } + case 1001005: { + // 跳转登录 + LoginManager.logout() + router.push({url: RouterUrls.LOGIN_PAGE}) + break; + } + } + } + } + } + response.data = JSON.parse(dataString); + console.error('AxiosResponse:' + JSON.stringify(response.data)) + return plainToInstance(HttpResult, JSON.parse(dataString)); + } catch (e) { + console.log(e); + return new HttpResult(-1, e); + } + } + return response.data; +}, (error: AxiosError) => { + // 响应失败 + return Promise.reject(error); +}); + +export class AxiosRequest { + // 万能请求 + static request(config: AxiosRequestConfig): Promise { + return instance.request(config) + } + + // get请求 + static get(url: string, params?: object): Promise { + return instance.get(url, { params }); + } + + // post请求 + static post(url: string, data?: object): Promise { + return instance.post(url, data); + } + + // put请求 + static put(url: string, data?: object): Promise { + return instance.put(url, data); + } + + // delete请求 + static delete(url: string, params?: object): Promise { + return instance.delete(url, { params }); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/net/HttpResult.ets b/entry/src/main/ets/net/HttpResult.ets new file mode 100644 index 0000000..f394d6b --- /dev/null +++ b/entry/src/main/ets/net/HttpResult.ets @@ -0,0 +1,15 @@ +export class HttpResult { + code: number = 0; + message: string = "" + data?: object = undefined; + + constructor(code: number, message: string, data?: object) { + this.code = code; + this.message = message; + this.data = data; + } + + isSuccess(): boolean { + return this.code === 0; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/net/MediaDownloader.ets b/entry/src/main/ets/net/MediaDownloader.ets new file mode 100644 index 0000000..f246a03 --- /dev/null +++ b/entry/src/main/ets/net/MediaDownloader.ets @@ -0,0 +1,426 @@ +import { AppUtil, FileUtil, JSONUtil, StrUtil } from '@pura/harmony-utils'; +import { MediaEntity, VideoMaterial } from '../entity/MaterialInfoEntity'; +import url from '@ohos.url'; +import { Constants } from '../common/Constants'; +import axios, { AxiosHeaders, AxiosResponse, AxiosResponseHeaders } from '@ohos/axios'; +import { BusinessError, request } from '@kit.BasicServicesKit'; +import { MP4Parser } from '@ohos/mp4parser'; +import { JSON } from '@kit.ArkTS'; + +export interface DownloadCallback { + onGetTotal?:(total: number) => void; + onProgress?:(progress: number) => void; + onPause?:() => void; + onMerge?:(step: number) => void; + onSuccess?:(filePath: string) => void; + onCancel?:() => void; + onFailed?:(err: string) => void; +} + +export class MediaDownloader { + static getInstance(): MediaDownloader { + return new MediaDownloader(); + } + + /** + * 缓存文件夹 + */ + private cacheDir = FileUtil.getCacheDirPath() + FileUtil.separator; + + /** + * 任务对象 + */ + private task?: request.agent.Task | null + + /** + * 下载回调 + */ + private mCallback?: DownloadCallback; + + /** + * 文件名 + */ + private fileName = ''; + + /** + * 代理池 + */ + private proxyList = new Array(); + + callback(callback: DownloadCallback): MediaDownloader { + this.mCallback = callback; + return this; + } + + createHeaders(map?: Map): Record { + const headers: Record = {}; + map?.forEach((value, key) => { + headers[key] = value.toString(); + }) + return headers; + } + + setProxyList(list?: Array): MediaDownloader { + if (list && list.length > 0) { + this.proxyList = list + } + return this + } + + async down(media: MediaEntity) { + this.fileName = media.name; + if (media.isM3u8) { + // this.doM3u8Down(); + this.doException('暂不支持') + } else if (media.isThreading) { + // this.doThreadingDown(); + this.doMediaDown(media, media.isMerge ? 1 : 0); + } else { + this.doMediaDown(media, media.isMerge ? 1 : 0); + } + } + + async doMediaDown(media: MediaEntity, step: number) { + if (media instanceof VideoMaterial && step === 0) { + await this.decodeWxVideo(media) + } + + let url = media.origin; + if (step === 2 && media instanceof VideoMaterial) { + url = media.speed_up === 1 || media.isThreading ? media.audio!!.url : media.audio!!.origin_url; + } else { + url = media.speed_up === 1 || media.isThreading ? media.url : media.origin_url; + } + + let headers: Record = {} + if (media.headers) { + headers = this.createHeaders(JSONUtil.jsonToMap(JSON.stringify(media.headers))); + } + headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0' + headers['Accept'] = '*/*' + + let isCanContinue = false; + /*if (step === 0 || step === 1) { + isCanContinue = await this.isCanContinue(url, media.currentLen) + media.continueDownload = true; + } else if (step === 2 && media instanceof VideoMaterial && media.audio) { + isCanContinue = await this.isCanContinue(url, media.audio.currentLen) + media.continueDownload = true; + }*/ + + if (step === 0 && FileUtil.accessSync(this.cacheDir + this.fileName) && isCanContinue) { + headers['Range'] = `bytes=${media.currentLen}-`; + } else if (step == 1 && FileUtil.accessSync(this.cacheDir + media.cacheName) && + (isCanContinue || media.totalSize !== 0 && media.currentLen === media.totalSize)) { + if (media instanceof VideoMaterial && media.audio && media.totalSize !== 0 && media.currentLen === media.totalSize) { + this.doMerge(4) // 音频提取中 + this.doMediaDown(media, 2) + } else { + headers['Range'] = `bytes=${media.currentLen}-`; + } + } else if (step === 2 && media instanceof VideoMaterial && media.audio && FileUtil.accessSync(this.cacheDir + media.audio.cacheName) && + (isCanContinue || media.audio.totalSize !== 0 && media.audio.currentLen === media.audio.totalSize)) { + if (media.audio.totalSize !== 0 && media.audio.currentLen == media.audio.totalSize) { + this.doMerge(5) // 音视频合并中 + let videoPath = this.cacheDir + media.cacheName + let audioPath = this.cacheDir + media.audio.cacheName + if (FileUtil.accessSync(videoPath) && FileUtil.accessSync(audioPath)) { + this.mergeVideoAndAudio(videoPath, audioPath, url) + } else { + throw Error('file not exists') + } + } else { + headers['Range'] = `bytes=${media.audio.currentLen}-`; + } + } else { + if (step === 0 || step === 1) { + media.currentLen = 0 + } else if (step === 2 && media instanceof VideoMaterial && media.audio) { + media.audio.currentLen = 0 + } + } + + if (this.canceled) return + try { + let filePath = '' + if (step == 1 && media instanceof VideoMaterial) { + filePath = this.cacheDir + media.cacheName + } else if (step == 2 && media instanceof VideoMaterial) { + filePath = this.cacheDir + media.audio!!.cacheName + } else { + filePath = this.cacheDir + this.fileName + } + let config: request.agent.Config = { + action: request.agent.Action.DOWNLOAD, + url: url, + overwrite: true, + saveas: filePath, + headers: headers, + gauge: true, + priority:0 + } + let total = 0; + request.agent.create(AppUtil.getApplicationContext(), config) + .then((task: request.agent.Task) => { + this.task = task + task.start((err: BusinessError) => { + if (err) { + console.error(err.message); + this.doException(err.message); + return; + } + }); + task.on('progress', (progress) => { + if (total === 0) { + total = progress.sizes[0] as number; + if (step === 0 || step === 1) { + total += media.currentLen + media.totalSize = total + } else if (step === 2 && media instanceof VideoMaterial && media.audio) { + total += media.audio.currentLen + media.audio.totalSize = total + } + this.doGetTotal(total); + console.debug(`total: ${progress.sizes[0].toString()}`) + } + if (step === 0 || step === 1) { + media.currentLen = progress.processed + } else if (step === 2 && media instanceof VideoMaterial && media.audio) { + media.audio.currentLen = progress.processed + } + this.doProgress(progress.processed); + console.debug(`progress: ${progress.processed.toString()}`); + }) + task.on('completed', () => { + console.debug('download completed'); + if (step == 1 && media instanceof VideoMaterial && media.audio) { + this.doMerge(4) // 音频提取中 + this.doMediaDown(media, 2) + } else if (step == 2 && media instanceof VideoMaterial) { + this.doMerge(5) // 音视频合并中 + let videoPath = this.cacheDir + media.cacheName + let audioPath = this.cacheDir + media.audio!!.cacheName + if (FileUtil.accessSync(videoPath) && FileUtil.accessSync(audioPath)) { + this.mergeVideoAndAudio(videoPath, audioPath, url) + } else { + this.doException('文件不存在') + } + } else { + this.doSuccess(this.cacheDir + this.fileName); + } + request.agent.remove(task.tid); + }) + task.on('failed', (progress) => { + console.log('download failed'); + request.agent.show(task.tid).then((taskInfo: request.agent.TaskInfo) => { + request.agent.remove(task.tid); + if (this.proxyList.length > 0 && this.replaceUrlWithProxy(media)) { + this.doMediaDown(media, step) + } else { + this.doException(taskInfo.reason); + } + }).catch((err: BusinessError) => { + this.doException(err.message); + request.agent.remove(task.tid); + }); + }); + }) + .catch((err: BusinessError) => { + console.error(err.message); + this.doException(err.message); + }) + } catch (e) { + console.error(e); + this.doException(e); + } + } + + async mergeVideoAndAudio(videoPath: string, audioPath: string, url: string) { + let outputPath = this.cacheDir + this.fileName + if (FileUtil.accessSync(outputPath)) { + FileUtil.unlinkSync(outputPath) + } + let cmd = `ffmpeg -i ${videoPath} -i ${audioPath} -c copy ${outputPath}` + MP4Parser.ffmpegCmd(cmd, { + callBackResult: (code: number) => { + if (code === 0) { + this.doSuccess(outputPath) + } else { + this.doException('合并失败') + } + } + }) + } + + async fixM4s(filePath: string) { + const file = FileUtil.openSync(filePath, 0o2); + const stat = FileUtil.lstatSync(filePath); + const buffer = new ArrayBuffer(stat.size); + FileUtil.readSync(file.fd, buffer); + FileUtil.fsyncSync(file.fd); + FileUtil.closeSync(file.fd); + + let bytes = new Uint8Array(buffer) + if (bytes.length > 9) { + bytes = bytes.subarray(9) + FileUtil.writeEasy(filePath, bytes.buffer, false) + } + } + + async decodeWxVideo(video: VideoMaterial) { + if (!video.url.includes("wechatDecode=batiao")) { + return + } + try { + const parseUrl = url.URL.parseURL(video.url); + const realUrl = parseUrl.params.get('url') as string; + const decodeKey = parseUrl.params.get("decodeKey"); + const keyUrl = Constants.BASE_URL + "/api/weixin/video/key?decodeKey=" + decodeKey; + const response: AxiosResponse = await axios({ + url: keyUrl, + method: 'get', + responseType: 'array_buffer', + }) + if (response.status === 200) { + video.url = realUrl; + video.origin_url = realUrl; + video.decodeKey = new Int8Array(response.data) + } + } catch (e) { + console.error(e); + } + } + + async isCanContinue(url: string, start: number): Promise { + try { + const headers = new AxiosHeaders(); + headers.set('RANGE', `bytes=${start}-`); + headers.set('Connection', 'close'); + const response: AxiosResponse = await axios({ + url: url, + method: 'get', + headers: headers, + responseType: 'array_buffer' + }) + if (response.status === 206) { //支持 + return Promise.resolve(true); + } else { + const acceptRangesHeaderValue = (response.headers as AxiosResponseHeaders).get('Accept-Ranges') as string + if ("bytes" === acceptRangesHeaderValue?.toLowerCase()) { //支持 + return Promise.resolve(true); + } else { //不支持 + return Promise.resolve(false); + } + } + } catch (e) { + console.log(e); + return Promise.resolve(false); + } + } + + replaceUrlWithProxy(media: MediaEntity): boolean { + try { + let vUrl = url.URL.parseURL(media.url) + + let domainAndPort = vUrl?.host ? vUrl?.host : '' + for (let i = 0; i < this.proxyList.length; i++) { + const proxy = this.proxyList[i]; + if (i < this.proxyList.length - 1) { + if (proxy.includes(domainAndPort)) { + domainAndPort = this.proxyList[i + 1] + break + } + } else return false + } + + // 获取原始 URL 的路径和查询参数 + media.url = domainAndPort + vUrl?.pathname + vUrl?.search + if (media instanceof VideoMaterial && media.audio) { + let aUrl = url.URL.parseURL(media.audio.url) + media.audio!!.url = domainAndPort + aUrl?.pathname + aUrl?.search + } + return true + } catch (e) { + console.error(e) + return false + } + } + + getDomainFromUrl(urlString: string): string { + try { + let parseURL = url.URL.parseURL(urlString) + return parseURL.protocol + "//" + parseURL.host + } catch (e) { + return '' + } + } + + getUrlFileExt(srcUrl: string): string { + try { + let url = srcUrl.substring(0, srcUrl.indexOf('?')) + if (url) { + return url.substring(url.lastIndexOf('.') + 1) + } + } catch (e) { + console.error(e) + } + return '' + } + + private canceled = false + async cancel() { + this.canceled = true + if (this.task) { + this.task.stop() + .then(() => { + this.task = null + this.doCancel() + }) + .catch((e: Error) => { + this.doException(e.message) + }) + } + } + + doGetTotal(total: number) { + if (this.mCallback?.onGetTotal) { + this.mCallback.onGetTotal(total); + } + } + + doProgress(progress: number) { + if (this.mCallback?.onProgress) { + this.mCallback.onProgress(progress); + } + } + + doPause() { + if (this.mCallback?.onPause) { + this.mCallback.onPause(); + } + } + + doMerge(step: number) { + if (this.mCallback?.onMerge) { + this.mCallback.onMerge(step); + } + } + + doSuccess(filePath: string) { + if (this.mCallback?.onSuccess) { + this.mCallback.onSuccess(filePath) + } + } + + doCancel() { + if (this.mCallback?.onCancel) { + this.mCallback.onCancel(); + } + } + + doException(err: string) { + if (this.mCallback?.onFailed) { + this.mCallback.onFailed(err); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/net/request/LoginRequest.ets b/entry/src/main/ets/net/request/LoginRequest.ets new file mode 100644 index 0000000..3c34cb7 --- /dev/null +++ b/entry/src/main/ets/net/request/LoginRequest.ets @@ -0,0 +1,9 @@ + +export interface LoginRequest { + login_type: string; + bind?: boolean; + unbind?: boolean; + phone?: Record; + weixin?: Record; + device?: Record; +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/audio/AudioPlayerPage.ets b/entry/src/main/ets/pages/audio/AudioPlayerPage.ets new file mode 100644 index 0000000..0f502cf --- /dev/null +++ b/entry/src/main/ets/pages/audio/AudioPlayerPage.ets @@ -0,0 +1,237 @@ +import { TipDialog } from '../../dialog/TipDialog' +import { ShareManager } from '../../manager/ShareManager' +import { TitleBar } from '../../view/TitleBar' +import { ToastUtils } from '../../utils/ToastUtils' +import { AppUtil } from '@pura/harmony-utils' +import { EventConstants } from '../../common/EventConstants' +import { MediaAction, MediaType } from '../../manager/MediaManager' +import { media } from '@kit.MediaKit' +import { fileIo } from '@kit.CoreFileKit' +import { LocalMediaManager } from '../../manager/LocalMediaManager' +import { avSessionManager } from '../../manager/AVSessionManager' + +@Entry +@ComponentV2 +struct AudioPlayerPage { + @Local title: string = '' + @Local uri: string = '' + @Local showActions: boolean = false + @Local currentTime: number = 0 + @Local durationTime: number = 0 + @Local isPlaying: boolean = false + + avPlayer?: media.AVPlayer; + + aboutToAppear(): void { + this.initParams() + this.initPlayer() + } + + onPageHide(): void { + if (this.avPlayer) { + this.avPlayer.pause() + } + } + + aboutToDisappear(): void { + if (this.avPlayer) { + this.avPlayer.release() + } + } + + initParams() { + const params = this.getUIContext().getRouter().getParams() as Record; + if (params) { + this.title = params.title as string + this.uri = params.uri as string; + this.showActions = params.showActions as boolean + } + } + + async initPlayer() { + this.avPlayer = await media.createAVPlayer(); + // 创建状态机变化回调函数 + this.setAVPlayerCallback(); + // 打开相应的资源文件地址获取fd + let file = await fileIo.open(this.uri); + this.avPlayer.url = 'fd://' + file.fd; + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } + + // 注册avplayer回调函数 + setAVPlayerCallback() { + this.avPlayer!!.on('error', (err) => { + console.error(`播放器发生错误,错误码:${err.code}, 错误信息:${err.message}`); + // 调用reset重置资源,触发idle状态 + this.isPlaying = false + this.avPlayer!!.reset(); + avSessionManager.deactivate() + }) + // 状态机变化回调函数 + this.avPlayer!!.on('stateChange', async (state, reason) => { + switch (state) { + case 'initialized': + console.info('资源初始化完成'); + // 资源初始化完成,开始准备文件 + this.avPlayer!!.prepare(); + break; + case 'prepared': + console.info('资源准备完成'); + // 资源准备完成,开始准备文件 + this.durationTime = Math.trunc(this.avPlayer!!.duration / 1000) + this.currentTime = this.avPlayer!!.currentTime; + + await avSessionManager.activate() + this.avPlayer!!.play(); + break; + case 'completed': + console.info('播放完成'); + this.isPlaying = false + this.avPlayer!!.off('bufferingUpdate') + AppStorage.setOrCreate('currentTime', this.durationTime); + avSessionManager.deactivate() + break; + case 'playing': + console.info('播放开始'); + this.isPlaying = true + break; + case 'released': + case 'stopped': + case 'error': + case 'paused': + console.info('播放暂停'); + this.isPlaying = false + avSessionManager.deactivate() + break; + } + }) + // 时间上报监听函数 + this.avPlayer!!.on('timeUpdate', (time: number) => { + this.currentTime = Math.trunc(time / 1000); + }); + } + + build() { + Column() { + TitleBar().width('100%') + + RelativeContainer() { + Image($r('app.media.ic_audio_thumb')) + + Row() { + Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play')) + .width(20) + .height(20) + .margin({ right: 20 }) + .onClick(async () => { + if (this.isPlaying) { + this.avPlayer!!.pause() + } else { + await avSessionManager.activate() + this.avPlayer!!.play() + } + }) + Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .blockColor(Color.White) + .trackColor($r('app.color.color_60ffffff')) + .onChange((value: number, mode: SliderChangeMode) => { + this.avPlayer!!.seek(value * 1000, 2); // 设置视频播放的进度跳转到value处 + this.currentTime = value; + }) + .layoutWeight(1) + Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12) + } + .opacity(0.8) + .width("100%") + .padding({ left: 30, right: 30 }) + .margin({ bottom: this.showActions ? 0 : 50}) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + }.layoutWeight(1) + + Row() { + Column() { + Image($r('app.media.ic_action_share')).width(50).height(50) + Text('转发').fontSize(12).fontColor($r('app.color.color_50ffffff')).margin({ top: 13 }) + } + .id('btn_share') + .alignRules({ + right: { anchor: '__container__', align: HorizontalAlign.Center } + }) + .margin({ right: 40 }) + .onClick(() => { + ShareManager.shareFile(this.uri) + }) + + Column() { + Image($r('app.media.ic_action_delete')).width(50).height(50) + Text('删除').fontSize(12).fontColor($r('app.color.color_50ffffff')).margin({ top: 13 }) + } + .id('btn_delete') + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Center }, + }) + .margin({ left: 40 }) + .onClick(() => { + this.avPlayer!!.pause() + TipDialog.show(this.getUIContext(), { + title: '提示', content: '确定删除该音频?', callback: { + confirm: () => { + fileIo.unlink(this.uri) + .then(() => { + ToastUtils.show('删除成功') + LocalMediaManager.delete(this.title) + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.AUDIO, MediaAction.DELETE) + this.getUIContext().getRouter().back() + }) + .catch(() => { + ToastUtils.show('删除失败, 请到文件管理中手动删除') + }) + } + } + }) + }) + } + .height(200) + .margin({ top: 10 }) + .visibility(this.showActions ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/guide/GuidePage.ets b/entry/src/main/ets/pages/guide/GuidePage.ets new file mode 100644 index 0000000..f70b829 --- /dev/null +++ b/entry/src/main/ets/pages/guide/GuidePage.ets @@ -0,0 +1,90 @@ +import { RouterUrls } from '../../common/RouterUrls'; +import { ToastUtils } from '../../utils/ToastUtils'; +import systemDateTime from '@ohos.systemDateTime'; +import { router } from '@kit.ArkUI'; +import { AppUtil } from '@pura/harmony-utils'; +import { ConfigManager } from '../../manager/UserConfigManager'; +import { EventReportGlobalManager } from '../../manager/EventReportGlobalManager'; +import { EventConstants } from '../../common/EventConstants'; + +@Entry +@ComponentV2 +struct GuidePage { + child = [ + $r('app.media.ic_guide_1'), + $r('app.media.ic_guide_2'), + $r('app.media.ic_guide_3'), + $r('app.media.ic_guide_4') + ] + currentIndex: number = 0; + + clickTime: number = 0; + + @Local showHomePage: boolean = true + + aboutToAppear(): void { + EventReportGlobalManager.eventReport(EventConstants.GUIDE_LAUNCH) + } + + build() { + Stack() { + Image($r('app.media.ic_guide_bg')).width('100%').height('100%') + if (this.showHomePage) { + Column() { + Image($r('app.media.ic_guide_cover')).width('100%').aspectRatio(0.75) + Text('视频搬运工具').fontColor('#0B2449').fontSize(32).fontFamily('almmsht') + .margin({top: 27}) + Text(ConfigManager.getGuideHint()).fontColor('#1E385D').fontSize(16).margin({top: 12, left: 54, right: 54}) + Blank().layoutWeight(1) + Button('立即使用') + .width(180) + .height(40) + .fontColor(Color.White) + .fontSize(16) + .borderRadius(20) + .backgroundColor($r("app.color.color_466afd")) + .margin({bottom: 30}) + .onClick(() => { + this.showHomePage = false + }) + } + .width('100%') + .height('100%') + } else { + Swiper() { + ForEach(this.child, (item: Resource) => { + Image(item).width('100%').height('100%') + }) + } + .indicator(false) + .onChange((index: number) => { + this.currentIndex = index; + EventReportGlobalManager.eventReport(EventConstants.GUIDE_OPPORTUNITY_SCROLL, `${index + 1}`) + }) + .onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => { + if (index === this.child.length - 1 && extraInfo.currentOffset < -60) { + // 跳转VIP充值页面 + if (ConfigManager.isGuidePayEnable()) { + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.VIP_PAGE, params: { origin: 'bootpage'} }); + } else { + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.MAIN_PAGE }, router.RouterMode.Single); + } + } + }) + .loop(false) + .width('100%') + .height('100%') + } + } + } + + onBackPress(): boolean | void { + if (systemDateTime.getTime() - this.clickTime < 1500) { + AppUtil.getContext().terminateSelf(); + } else { + this.clickTime = systemDateTime.getTime(); + ToastUtils.show('双击退出应用'); + } + return true; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/login/LoginPage.ets b/entry/src/main/ets/pages/login/LoginPage.ets new file mode 100644 index 0000000..55200ad --- /dev/null +++ b/entry/src/main/ets/pages/login/LoginPage.ets @@ -0,0 +1,355 @@ +import { TitleBar } from '../../view/TitleBar'; +import systemDateTime from '@ohos.systemDateTime'; +import { ToastUtils } from '../../utils/ToastUtils'; +import { common } from '@kit.AbilityKit'; +import { RouterUrls } from '../../common/RouterUrls'; +import { Constants } from '../../common/Constants'; +import LoginViewModel from '../../viewModel/LoginViewModel'; +import { AppUtil, RandomUtil, StrUtil } from '@pura/harmony-utils'; +import { SendCodeEntity } from '../../entity/SendCodeEntity'; +import { LoginEntity } from '../../entity/LoginEntity'; +import { ConfigManager } from '../../manager/UserConfigManager'; +import { LoginTipDialog } from '../../dialog/LoginTipDialog'; +import { LevelMode, router } from '@kit.ArkUI'; +import { LoginManager } from '../../manager/LoginGlobalManager'; +import { OnWXResp, WXApi, WXEventHandler } from '../../utils/wechat/WXApiEventHandlerImpl'; +import * as WxOpenSdk from '@tencent/wechat_open_sdk'; +import { ErrCode, SendAuthResp } from '@tencent/wechat_open_sdk'; +import BuildProfile from 'BuildProfile'; +import { LoadingDialog } from '../../dialog/LoadingDialog'; +import { EventConstants } from '../../common/EventConstants'; +import { EventReportGlobalManager } from '../../manager/EventReportGlobalManager'; + +@Entry +@ComponentV2 +struct LoginPage { + @Local from: number = 0; + @Local isAgree: boolean = false; + @Local countDownTime: number = 0; + + loginTipDialogController?: CustomDialogController | null; + + viewModel: LoginViewModel = new LoginViewModel(this.getUIContext()); + + clickTime: number = 0; + phone: string = ''; + code: string = ''; + timestamp: string = ''; + intervalId: number = -1; + //从微信返回的回调 + onWXResp: OnWXResp = (resp) => { + //微信返回的数据 + if (resp instanceof SendAuthResp && resp.state?.endsWith('phone')) { + const authResult = JSON.stringify(resp ?? {}, null , 2); + const errCode = JSON.parse(authResult).errCode as number; + if (errCode === ErrCode.ERR_OK) { + const authCode = JSON.parse(authResult).code as string; + this.viewModel.wxLogin(authCode); + } else { + ToastUtils.show(JSON.parse(authResult).errStr); + } + } + } + + @Monitor('viewModel.codeEntity') + onCodeChange(monitor: IMonitor) { + const code = monitor.value()?.now as SendCodeEntity; + ToastUtils.show('验证码已发送'); + this.timestamp = code.timestamp; + this.countDownTime = 60; + this.intervalId = setInterval(() => { + if (this.countDownTime > 0) { + this.countDownTime-- + } else { + if (this.intervalId !== 0) { + clearInterval(this.intervalId); + } + } + }, 1000) + } + + @Monitor('viewModel.phoneLoginEntity') + onPhoneLogin(monitor: IMonitor) { + const loginEntity = monitor.value()?.now as LoginEntity; + EventReportGlobalManager.eventReport(EventConstants.LOGIN, 'phone', this.phone) + LoginManager.saveToken(loginEntity.token); + LoginManager.saveLastLoginType('phone') + ConfigManager.userConfig() + .then(() => { + if (this.from === 0) { + this.toMainPage(); + } else { + this.getUIContext().getRouter().back() + } + }) + } + + @Monitor('viewModel.wxLoginEntity') + onWxLogin(monitor: IMonitor) { + const loginEntity = monitor.value()?.now as LoginEntity; + EventReportGlobalManager.eventReport(EventConstants.LOGIN, 'weixin') + LoginManager.saveToken(loginEntity.token); + LoginManager.saveLastLoginType('weixin') + ConfigManager.userConfig() + .then(() => { + if (this.from === 0) { + this.toMainPage(); + } else { + this.getUIContext().getRouter().back() + } + }) + } + + aboutToAppear() { + WXEventHandler.registerOnWXRespCallback(this.onWXResp) + this.initParams(); + } + + aboutToDisappear() { + WXEventHandler.unregisterOnWXRespCallback(this.onWXResp) + this.loginTipDialogController = null + } + + initParams() { + const params = this.getUIContext().getRouter().getParams() as Record; + if (params) { + this.from = params.from as number; + } + } + + sendCode() { + if (StrUtil.isEmpty(this.phone)) { + ToastUtils.show('请输入手机号'); + return; + } + if (this.phone.length != 11) { + ToastUtils.show('请输入正确的手机号'); + return; + } + this.viewModel.sendCode(this.phone); + EventReportGlobalManager.eventReport(EventConstants.GET_CODE, "code_login", this.phone) + } + + toMainPage() { + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.MAIN_PAGE }, router.RouterMode.Single); + AppUtil.getContext().eventHub.emit(EventConstants.LoginSuccessEvent); + } + + async wxAuth() { + if (!WXApi.isWXAppInstalled()) { + ToastUtils.show('未安装微信客户端,请先下载安装微信客户端'); + return; + } + LoadingDialog.show(this.getUIContext()); + let req = new WxOpenSdk.SendAuthReq; + req.isOption1 = false; + req.nonAutomatic = true; + req.scope = 'snsapi_userinfo'; + req.state = BuildProfile.BUNDLE_NAME + RandomUtil.getRandomInt(0, 1000) + '_phone'; + req.transaction =''; + + await WXApi.sendReq(AppUtil.getContext(), req) + LoadingDialog.dismiss(); + } + + showLoginTipDialog(type: number) { + this.loginTipDialogController = new CustomDialogController({ + builder: LoginTipDialog({ + confirm: () => { + this.isAgree = true; + if (type === 0) { + this.viewModel.phoneLogin(this.phone, this.code, this.timestamp); + } else if (type === 1) { + this.wxAuth(); + } + } + }), + cornerRadius: 20, + maskColor: '#CC000000', + levelMode: LevelMode.EMBEDDED, + backgroundBlurStyle: BlurStyle.NONE + }) + this.loginTipDialogController.open(); + } + + onBackPress(): boolean | void { + if (this.from === 0) { + if (systemDateTime.getTime() - this.clickTime < 1500) { + (this.getUIContext().getHostContext() as common.UIAbilityContext).terminateSelf(); + } else { + EventReportGlobalManager.eventReport(EventConstants.EXIT_APP, 'login') + this.clickTime = systemDateTime.getTime(); + ToastUtils.show('双击退出应用'); + } + return true; + } + } + + build() { + Stack({alignContent: Alignment.TopStart}) { + Image($r('app.media.ic_login_top_bg')).width('100%') + + Column() { + TitleBar({ showBack: this.from !== 0 }).width('100%') + Image($r('app.media.ic_login_logo')) + .margin({ top: 10 }) + .width(80) + .height(80) + + Text($r('app.string.app_name')) + .fontColor($r('app.color.color_212226')) + .fontSize(18) + .fontFamily('almmsht') + .margin({ top: 12 }) + .width('auto') + + Row(){ + Image($r('app.media.ic_login_phone')).width(22).height(22) + TextInput({ placeholder: '请输入您的手机号' }) + .type(InputType.PhoneNumber) + .fontColor($r('app.color.color_1a1a1a')) + .fontSize(16) + .placeholderColor($r('app.color.color_bcbcbc')) + .placeholderFont({ size: 16 }) + .maxLength(11) + .backgroundColor(Color.Transparent) + .onChange((value: string) => { + this.phone = value; + }) + } + .height(50) + .margin({ top: 50, left: 38, right: 38 }) + + Divider().strokeWidth(1).color($r('app.color.color_dfdfdf')).margin({ left: 38, right: 38 }) + + RelativeContainer() { + Row() { + Image($r('app.media.ic_login_code')).width(22).height(22) + TextInput({ placeholder: '请输入验证码' }) + .type(InputType.Number) + .fontColor($r('app.color.color_1a1a1a')) + .fontSize(16) + .placeholderColor($r('app.color.color_bcbcbc')) + .placeholderFont({ size: 16 }) + .maxLength(6) + .backgroundColor(Color.Transparent) + .onChange((value: string) => { + this.code = value; + }) + } + .id('row_code') + + Text(this.countDownTime === 0 && StrUtil.isEmpty(this.timestamp) ? '获取验证码' : + this.countDownTime > 0 ? `${this.countDownTime}s` : '重新发送') + .fontColor(this.countDownTime === 0 ? $r("app.color.color_466afd") : $r('app.color.color_999999')) + .fontSize(14) + .alignRules({ + top: { anchor: 'row_code', align: VerticalAlign.Top }, + right: { anchor: 'row_code', align: HorizontalAlign.End }, + bottom: { anchor: 'row_code', align: VerticalAlign.Bottom }, + }) + .margin({ right: 16 }) + .width('auto') + .onClick(() => { + if (this.countDownTime === 0) { + this.sendCode(); + } + }) + }.margin({ top: 26, left: 38, right: 38 }).height(50) + + Divider().strokeWidth(1).color($r('app.color.color_dfdfdf')).margin({ left: 38, right: 38 }) + + Stack() { + Button('登录', { type: ButtonType.Capsule, stateEffect: true }) + .width('100%') + .height(50) + .fontColor(Color.White) + .fontSize(16) + .fontWeight(FontWeight.Medium) + .backgroundColor($r("app.color.color_466afd")) + }.margin({ top: 40 }).padding({ left: 38, right: 38 }) + .onClick(() => { + if (StrUtil.isEmpty(this.phone)) { + ToastUtils.show('请输入手机号'); + return; + } + if (this.phone.length != 11) { + ToastUtils.show('请输入正确的手机号'); + return; + } + if (StrUtil.isEmpty(this.code)) { + ToastUtils.show('请输入验证码'); + return; + } + if (this.isAgree) { + this.viewModel.phoneLogin(this.phone, this.code, this.timestamp); + } else { + this.showLoginTipDialog(0); + } + }) + + Row() { + Image(this.isAgree ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')).width(16).height(16) + Text() { + Span('我已阅读并同意') + Span('《用户协议》').fontColor($r("app.color.color_466afd")) + .onClick(() => { + this.getUIContext() + .getRouter() + .pushUrl({ url: RouterUrls.WEB_PAGE, params: { title: '用户协议', url: Constants.USER_AGREEMENT } }) + }) + Span('和') + Span('《隐私政策》').fontColor($r("app.color.color_466afd")) + .onClick(() => { + this.getUIContext() + .getRouter() + .pushUrl({ url: RouterUrls.WEB_PAGE, params: { title: '隐私政策', url: Constants.PRIVACY_POLICY } }) + }) + } + .fontColor($r('app.color.color_999999')) + .fontSize(12) + .margin({ left: 4, top: 2 }) + } + .alignItems(VerticalAlign.Top) + .margin({ left: 38, top: 16, right: 38 }) + .onClick(() => { + this.isAgree = !this.isAgree; + }) + + Blank().layoutWeight(1) + + Text('其他登录方式') + .fontColor('#AAAAAA') + .fontSize(10) + .width('auto') + .visibility(ConfigManager.getLoginType().length > 1 ? Visibility.Visible : Visibility.None) + Row() { + Image($r('app.media.ic_wx_login')) + .margin({ left: 25, right: 25 }) + .width(38) + .height(38) + .id('iv_wx_login') + .onClick(() => { + if (this.isAgree) { + this.wxAuth() + } else { + this.showLoginTipDialog(1); + } + }) + .visibility(ConfigManager.getLoginType().includes('weixin') ? Visibility.Visible : Visibility.None) + + Image($r('app.media.ic_onekey_login')) + .margin({ left: 25, right: 25 }) + .width(38) + .height(38) + .id('iv_onekey_login') + .visibility(Visibility.None) + } + .margin({ top: 20, bottom: 50 }) + } + .width('100%') + .height('100%') + } + .backgroundColor(Color.White) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/login/qrcode/QrcodeLoginPage.ets b/entry/src/main/ets/pages/login/qrcode/QrcodeLoginPage.ets new file mode 100644 index 0000000..ad19b0f --- /dev/null +++ b/entry/src/main/ets/pages/login/qrcode/QrcodeLoginPage.ets @@ -0,0 +1,81 @@ +import { TitleBar } from '../../../view/TitleBar' +import { QrcodeLoginViewModel } from '../../../viewModel/QrcodeLoginViewModel' + +@Entry +@ComponentV2 +struct QrcodeLoginPage { + viewModel: QrcodeLoginViewModel = new QrcodeLoginViewModel(this.getUIContext()) + + code: string = '' + + @Monitor('viewModel.loginResult') + onLogin() { + this.getUIContext().getRouter().back() + } + + aboutToAppear(): void { + this.initParams() + } + + initParams() { + const params = this.getUIContext().getRouter().getParams() as Record; + if (params) { + this.code = params.code as string + } + } + + onBackPress(): boolean | void { + return true + } + + build() { + Column() { + TitleBar({ title: '登录确认', isDark: false}) + + Column() { + Image($r('app.media.ic_qrcode_login_computer_black')) + .width(110) + .height(110) + .margin({ top: 100 }) + + Text() { + Span('登录 ') + Span($r('app.string.app_name')).fontColor($r("app.color.color_466afd")) + Span(' 网页端') + } + .fontSize(20) + .fontColor($r('app.color.color_1a1a1a')) + .fontWeight(FontWeight.Bold) + .margin({ top: 20 }) + + Button('登录') + .width(184) + .height(48) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .borderRadius(6) + .margin({ top: 90 }) + .onClick(() => { + this.viewModel.login(this.code) + }) + + Text('取消登录').fontColor('#95979E') + .fontSize(14) + .margin({ top: 20 }) + .onClick(() => { + this.getUIContext().getRouter().back() + }) + } + .width('90%') + .layoutWeight(1) + .margin(16) + .borderRadius(16) + .backgroundColor(Color.White) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.color_ededed')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/MainPage.ets b/entry/src/main/ets/pages/main/MainPage.ets new file mode 100644 index 0000000..11b71aa --- /dev/null +++ b/entry/src/main/ets/pages/main/MainPage.ets @@ -0,0 +1,177 @@ +import { router, window } from '@kit.ArkUI'; +import { HomePage } from './home/HomePage'; +import { MinePage } from './mine/MinePage'; +import { RecordPage } from './record/RecordPage'; +import systemDateTime from '@ohos.systemDateTime'; +import { ToastUtils } from '../../utils/ToastUtils'; +import { AppUtil, PasteboardUtil, StrUtil } from '@pura/harmony-utils'; +import { EventConstants } from '../../common/EventConstants'; +import { MainViewModel } from '../../viewModel/MainViewModel'; +import { UserEntity } from '../../entity/UserEntity'; +import { SimpleTipDialog } from '../../dialog/SimpleTipDialog'; +import { RouterUrls } from '../../common/RouterUrls'; +import { TipDialog } from '../../dialog/TipDialog'; +import { ConfigManager } from '../../manager/UserConfigManager'; +import { EventReportGlobalManager } from '../../manager/EventReportGlobalManager'; +import { PasteboardUtils } from '../../utils/PasteboardUtils'; + +@Entry +@ComponentV2 +struct MainPage { + windowStage: window.WindowStage = AppStorage.get("windowStage") as window.WindowStage; + + viewModel: MainViewModel = new MainViewModel(this.getUIContext()); + + tabController: TabsController = new TabsController() + + userinfo?: UserEntity; + + titles = ['首页', '素材库', '工具', '我的']; + + clickTime: number = 0; + + @Local currentIndex: number = 0; + @Provider() recordIndex: number = 0 + + @Monitor('viewModel.userEntity') + onUserinfoChange(monitor: IMonitor) { + this.userinfo = monitor.value()?.now as UserEntity; + this.showLoginTip(); + } + + aboutToAppear(): void { + this.windowStage.getMainWindowSync().setWindowSystemBarProperties({ + statusBarColor: '#00000000', + statusBarContentColor: '#000000' + }); + this.initObserver(); + ConfigManager.userConfig() + } + + aboutToDisappear(): void { + AppUtil.getContext().eventHub.off(EventConstants.LoginSuccessEvent); + AppUtil.getContext().eventHub.off(EventConstants.JumpToRecordEvent); + } + + onPageShow(): void { + this.viewModel.userinfo(); + if (this.currentIndex === 1) { + AppUtil.getContext().eventHub.emit(EventConstants.RecordRefreshEvent); + } else if (this.currentIndex === 2) { + AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent); + } + this.checkPasteboard() + } + + initObserver() { + AppUtil.getContext().eventHub.on(EventConstants.LoginSuccessEvent, () => { + AppUtil.getContext().eventHub.emit(EventConstants.HomeRefreshEvent); + AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent); + }) + AppUtil.getContext().eventHub.on(EventConstants.JumpToRecordEvent, (index: number) => { + this.tabController.changeIndex(1) + this.currentIndex = 1 + }) + } + + checkPasteboard() { + PasteboardUtils.isNeedGetPermissionFromUser() + .then((isNeed) => { + if (isNeed) { + PasteboardUtil.requestPermissions() + .then((isGranted) => { + if (isGranted) { + let content = PasteboardUtil.getDataTextSync() + if (StrUtil.isNotEmpty(content) && content !== PasteboardUtils.clipText && PasteboardUtils.isValidUrl(content)) { + TipDialog.show(this.getUIContext(), {title: '您已复制链接,是否粘贴', content: content, leftText: '取消', rightText: '粘贴', callback: { + confirm: () => { + PasteboardUtils.clipText = content + this.getUIContext().getRouter().pushUrl({url: RouterUrls.TAKE_MATERIAL_PAGE, params:{url: content}}) + }, + cancel: () => { + PasteboardUtils.clipText = content + } + }}) + } + } + }) + .catch(() => {}) + } + }) + } + + build() { + Tabs({ controller: this.tabController, barPosition: BarPosition.End }) { + TabContent() { + HomePage() + } + .tabBar(this.tabBuilder(this.titles[0], 0, $r('app.media.ic_home_select'), $r('app.media.ic_home_default'))) + + TabContent() { + + } + .tabBar(this.tabBuilder(this.titles[1], 1, $r('app.media.ic_material_select'), $r('app.media.ic_material_default'))) + + TabContent() { + + } + .tabBar(this.tabBuilder(this.titles[2], 2, $r('app.media.ic_tool_select'), $r('app.media.ic_tool_default'))) + + TabContent() { + MinePage() + } + .tabBar(this.tabBuilder(this.titles[3], 3, $r('app.media.ic_mine_select'), $r('app.media.ic_mine_default'))) + } + .scrollable(false) + .onSelected((index: number) => { + this.currentIndex = index; + if (index === 3) { + AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent); + } + this.showLoginTip(); + EventReportGlobalManager.eventReport(EventConstants.HOME_BOTTOM_TAB_CHECK, this.titles[index], '') + }) + .padding({ bottom: 20 }) + .backgroundColor(Color.White) + } + + @Builder + tabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) { + Column() { + Image(this.currentIndex === targetIndex ? selectedImg : normalImg) + .size({ width: 24, height: 24 }) + Text(title) + .fontColor(this.currentIndex === targetIndex ? $r("app.color.color_466afd") : '#676E87') + .fontSize(10) + } + .width('100%') + .height(50) + .justifyContent(FlexAlign.Center) + } + + showLoginTip() { + if (this.userinfo && this.userinfo.temp && (this.userinfo.vip === 2 || this.userinfo.vip == 3)) { + SimpleTipDialog.show(this.getUIContext(), { + title: '郑重提示', + content: '系统检测到您目前尚未登录,但您已成为我们尊贵的会员,为了防止您的会员账号丢失,建议您立即前往登录', + buttonText: '立即登录', + callback: { + confirm: () => { + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.LOGIN_PAGE }, router.RouterMode.Single) + } + } + }, false) + } + } + + onBackPress(): boolean | void { + if (systemDateTime.getTime() - this.clickTime < 1500) { + AppUtil.getContext().terminateSelf(); + } else { + EventReportGlobalManager.eventReport(EventConstants.EXIT_APP, 'main') + this.clickTime = systemDateTime.getTime(); + ToastUtils.show('双击退出应用'); + } + return true; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/HomePage.ets b/entry/src/main/ets/pages/main/home/HomePage.ets new file mode 100644 index 0000000..cef80f1 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/HomePage.ets @@ -0,0 +1,457 @@ +import { borderRadiuses } from '@kit.ArkUI'; +import { HomeMenuEntity, homeMenuList } from '../../../entity/HomeMenuEntity'; +import { NoticeEntity } from '../../../entity/NoticeEntity'; +import { HomeViewModel } from '../../../viewModel/HomeViewModel'; +import { AppUtil, WantUtil } from '@pura/harmony-utils'; +import { EventConstants } from '../../../common/EventConstants'; +import { BannerEntity } from '../../../entity/BannerEntity'; +import { ConfigManager } from '../../../manager/UserConfigManager'; +import { LoginManager } from '../../../manager/LoginGlobalManager'; +import { RouterUrls } from '../../../common/RouterUrls'; +import { TipDialog } from '../../../dialog/TipDialog'; +import { EventReportGlobalManager } from '../../../manager/EventReportGlobalManager'; +import { Constants } from '../../../common/Constants'; +import { MaterialEntity } from '../../../entity/MaterialEntity'; +import { DownSamplingStrategy, ImageKnifeComponent, ImageKnifeOption } from '@ohos/imageknifepro'; + +@ComponentV2 +export struct HomePage { + @Local notices?: Array = []; + @Local banners: Array = []; + @Local materialList: Array = [] + @Local isRefreshing: boolean = false + @Local isLoading: boolean = false + @Local canLoadMore: boolean = false + + viewModel: HomeViewModel = new HomeViewModel(this.getUIContext()); + + noticeScroller: Scroller = new Scroller(); + + noticeTaskId: number = 0; + bannerTaskId: number = 0; + + private page: number = 1; + + @Monitor('viewModel.noticeEntity') + onNoticeChange(monitor: IMonitor) { + const noticeEntity = monitor.value()?.now as NoticeEntity; + this.notices = noticeEntity.notice; + if (this.notices && this.notices.length >= 1) { + this.startNoticeTask(); + } else { + this.stopNoticeTask(); + } + } + + @Monitor('viewModel.materialList') + onMaterialListChange(monitor: IMonitor) { + const list = monitor.value()?.now as Array + if (this.page === 1) { + this.materialList = list + this.isRefreshing = false + } else { + this.materialList.push(...list) + this.isLoading = false + } + this.canLoadMore = list.length === 20 + } + + aboutToAppear(): void { + this.initObserver(); + this.initBanner(); + this.viewModel.noticeList(); + this.viewModel.getMaterialList(this.page.toString()) + } + + aboutToDisappear(): void { + this.stopNoticeTask(); + } + + initObserver() { + AppUtil.getContext().eventHub.on(EventConstants.HomeRefreshEvent, () => { + this.viewModel.noticeList(); + }) + } + + initBanner() { + if (this.banners.length > 0) { + this.banners.splice(0, this.banners.length) + } + const list = ConfigManager.getHomeBanner(); + for (let i = 0; i < list.length; i++) { + const item = list[i]; + if (item.page === 'check_Task') { + continue + } else if (item.page === 'recharge' && LoginManager.getUserInfo()?.vip === 3) { + continue + } + this.banners.push(item); + } + } + + startNoticeTask() { + if (this.noticeTaskId === 0 && this.notices && this.notices.length >= 1) { + this.noticeTaskId = setInterval(() => { + this.noticeScroller.scrollBy(3, 0) + if (this.noticeScroller.isAtEnd()) { + this.notices = this.notices?.concat(this.notices); + } + }, 25) + } + } + + stopNoticeTask() { + if (this.noticeTaskId !== 0) { + clearInterval(this.noticeTaskId); + this.noticeTaskId = 0; + } + } + + createImageOption(item: MaterialEntity): ImageKnifeOption { + const option:ImageKnifeOption = { + loadSrc: item.pic ? item.pic.url : '', + placeholderSrc: $r('app.media.ic_placeholder'), + thumbnailSrc: $r('app.media.ic_placeholder'), + errorSrc: $r('app.media.ic_placeholder'), + objectFit: ImageFit.Cover, + border: {radius: 6}, + downSampling: DownSamplingStrategy.FIT_CENTER_QUALITY, + onLoadListener:{ + onLoadSuccess: (imageInfo) => { + item.pic_size = `${imageInfo.imageWidth}:${imageInfo.imageHeight}` + } + } + } + return option + } + + build() { + Refresh({refreshing: this.isRefreshing}) { + Scroll() { + RelativeContainer() { + Image($r('app.media.ic_home_top_bg')).width('100%').aspectRatio(1.973) + + Swiper() { + ForEach(this.banners, (item: BannerEntity) => { + Image(item.image).width('100%').aspectRatio(2.5).margin({left: 12, right: 12}) + .onClick(() => { + switch (item.page) { + case 'vip': { + if (LoginManager.getUserInfo()?.vip !== 3) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIP_PAGE, params: {origin: 'banner'}}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_MEMBER_RECHARGE, 'banner') + } + break; + } + case 'check_Task': { + + } + case 'course': { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.COURSE_PAGE}) + } + case 'web_link': { + WantUtil.toWebBrowser(Constants.WEB_URL) + } + } + }) + .borderRadius(10) + }) + } + .indicator( + Indicator.dot() + .itemWidth(4) + .itemHeight(4) + .selectedItemWidth(12) + .selectedItemHeight(4) + .color('#D6D6D6') + .selectedColor(Color.White) + ) + .loop(this.banners && this.banners.length > 1) + .autoPlay(true) + .interval(2000) + .margin({ top: 70 }) + .visibility(this.banners && this.banners.length > 0 ? Visibility.Visible : Visibility.None) + .id('swiper_banner') + + RelativeContainer() { + List({space: 100, scroller: this.noticeScroller}) { + if (this.notices && this.notices.length >= 1) { + ForEach(this.notices, (item: string) => { + ListItem() { + Text(item).height('100%').textAlign(TextAlign.Center).fontColor('#1876E2').fontSize(13) + } + }) + } + } + .listDirection(Axis.Horizontal) + .backgroundColor('#E4EDFD') + .borderRadius(borderRadiuses(6)) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .scrollBar(BarState.Off) + .enableScrollInteraction(false) + .margin({ left: 12, right: 12 }) + .padding({left: 45}) + .height(36) + .id('li_notice') + + Image($r('app.media.ic_notice')) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .margin({ left: 5, bottom: 2 }) + .width(54) + .height(54) + } + .alignRules({ + top: { anchor: 'swiper_banner', align: VerticalAlign.Bottom } + }) + .margin({top: 11}) + .width('100%') + .height(54) + .visibility(this.notices && this.notices?.length > 0 ? Visibility.Visible : Visibility.None) + .id('layout_notice') + + Row() { + Column() { + Row() { + Text('链接提取').fontColor('#15005D').fontSize(17).fontFamily('almmsht') + Image($r('app.media.ic_home_top_menu_arrow1')).width(12).height(12).margin({left: 2}) + } + Text('涵盖大多数平台').fontColor($r('app.color.color_666666')).fontSize(11).margin({top: 4}) + Blank().layoutWeight(1) + Text('1000+国内外视频平台').fontColor($r('app.color.color_999999')).fontSize(10) + Row({space: 6}){ + Image($r('app.media.ic_home_link_support_icon1')).width(30).width(30) + Image($r('app.media.ic_home_link_support_icon2')).width(30).width(30) + Image($r('app.media.ic_home_link_support_icon3')).width(30).width(30) + Text('更多').fontColor(Color.White).fontSize(10).width(30).height(30).textAlign(TextAlign.Center).backgroundColor('#825EFF').borderRadius(6) + } + .margin({top: 8}) + Row() { + Image($r('app.media.yq_0')).width(14).height(14).borderRadius(7) + Image($r('app.media.yq_1')).width(14).height(14).borderRadius(7).margin({left: -4}) + Image($r('app.media.yq_2')).width(14).height(14).borderRadius(7).margin({left: -4}) + Text('1000+用户体验中').fontColor($r('app.color.color_999999')).fontSize(10).margin({left: 6}) + } + .margin({top: 12}) + } + .width('52.7%') + .aspectRatio(1.12) + .backgroundImage($r('app.media.ic_home_top_menu_bg1')) + .backgroundImageSize({width: '100%', height: '100%'}) + .padding({left: 10, top: 25, right: 10, bottom: 8}) + .alignItems(HorizontalAlign.Start) + .id('layout_link') + .onClick(() => { + // let info = AppUtil.getSignatureInfoSync() + this.getUIContext().getRouter().pushUrl({url: RouterUrls.TAKE_MATERIAL_PAGE}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_LINK_EXTRACT) + }) + + Column() { + Column() { + Row() { + Text('视频号').fontColor('#113100').fontSize(16).fontFamily('almmsht') + Image($r('app.media.ic_home_top_menu_arrow2')).width(12).height(12).margin({left: 2}) + } + Text('支持微信视频号').fontColor($r('app.color.color_666666')).fontSize(11) + Blank().layoutWeight(1) + Row() { + Image($r('app.media.yq_3')).width(14).height(14).borderRadius(7) + Image($r('app.media.yq_4')).width(14).height(14).borderRadius(7).margin({left: -4}) + Image($r('app.media.yq_5')).width(14).height(14).borderRadius(7).margin({left: -4}) + Text('1200+用户体验中').fontColor($r('app.color.color_999999')).fontSize(10).margin({left: 6}) + } + } + .width('48.7%') + .aspectRatio(1.965) + .backgroundImage($r('app.media.ic_home_top_menu_bg2')) + .backgroundImageSize({width: '100%', height: '100%'}) + .padding({left: 10, top: 20, right: 10, bottom: 8}) + .margin({top: -5}) + .alignItems(HorizontalAlign.Start) + .id('layout_wx_video') + .onClick(() => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.WX_VIDEO_PAGE, params: { isPlayback: false }}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_WECHAT_VIDEO) + }) + + Column() { + Row() { + Text('直播回放').fontColor('#4A0006').fontSize(16).fontFamily('almmsht') + Image($r('app.media.ic_home_top_menu_arrow3')).width(12).height(12).margin({left: 2}) + } + Text('支持微信直播回放').fontColor($r('app.color.color_666666')).fontSize(11) + Blank().layoutWeight(1) + Row() { + Image($r('app.media.yq_6')).width(14).height(14).borderRadius(7) + Image($r('app.media.yq_7')).width(14).height(14).borderRadius(7).margin({left: -4}) + Image($r('app.media.yq_8')).width(14).height(14).borderRadius(7).margin({left: -4}) + Text('2000+用户体验中').fontColor($r('app.color.color_999999')).fontSize(10).margin({left: 6}) + } + } + .width('48.7%') + .aspectRatio(2.28) + .backgroundImage($r('app.media.ic_home_top_menu_bg3')) + .backgroundImageSize({width: '100%', height: '100%'}) + .padding({left: 10, top: 9, right: 10, bottom: 8}) + .margin({top: 9}) + .alignItems(HorizontalAlign.Start) + .id('layout_wx_playback') + .onClick(() => { + TipDialog.show(this.getUIContext(), {title: '提示', content: '仅限微信直播回放视频提取,是否前往?', callback: { + confirm: () => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.WX_VIDEO_PAGE, params: { isPlayback: true }}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_WECHAT_PLAYBACK) + } + }}) + }) + } + } + .alignRules({ + top: { anchor: 'layout_notice', align: VerticalAlign.Bottom } + }) + .padding({left: 12, right: 12}) + .margin({ top: 10 }) + .height('auto') + .id('layout_top_menu') + + Grid() { + ForEach(homeMenuList().convertToArray(), (item: HomeMenuEntity) => { + GridItem() { + Column() { + Image(item.icon) + .width(44) + .height(44) + Text(item.title) + .fontColor($r('app.color.color_212226')) + .fontSize(12) + .margin({ top: 6 }) + } + .alignItems(HorizontalAlign.Center) + } + .width('20%') + .onClick(() => { + switch (item.alias) { + case 'videoToAudio': { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.TAKE_AUDIO_PAGE}) + break + } + case 'addWatermark': { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.ADD_WATER_MARKER_PAGE}) + break + } + case 'videoToText': { + break + } + case 'longImageMerge': { + break + } + case 'moreTools': { + break + } + } + }) + }) + } + .maxCount(5) + .layoutDirection(GridDirection.Row) + .alignRules({ + top: { anchor: 'layout_top_menu', align: VerticalAlign.Bottom } + }) + .margin({ + top: 20, + left: 12, + right: 12 + }) + .id('li_menu') + + Column() { + Stack() { + Image($r('app.media.ic_tab_indicator')).width(27).height(11).margin({top: 13}) + Text('高清推荐素材').fontColor($r('app.color.color_212226')).fontSize(16).fontWeight(FontWeight.Medium) + } + .alignSelf(ItemAlign.Start) + .margin({left: 12, top: 27}) + + WaterFlow({footer: this.itemFoot()}) { + ForEach(this.materialList, (item: MaterialEntity, index: number) => { + FlowItem() { + ImageKnifeComponent({ + imageKnifeOption:this.createImageOption(item) + }) + .width('100%') + .height('100%') + .borderRadius(6) + .backgroundColor($r('app.color.color_ededed')) + .onClick(() => { + if (item.pic?.url) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.MATERIAL_DETAIL_PAGE, params: {material: item}}) + } + }) + } + .width('100%') + .height(500 / (index % 2 === 0 ? 2 : 3)) + }) + } + .columnsTemplate('1fr 1fr') + .columnsGap(7) + .rowsGap(7) + .width('100%') + .height('100%') + .padding({left: 12, right: 12}) + .margin({top: 14}) + .onReachEnd(() => { + this.page++ + this.isLoading = true + this.viewModel.getMaterialList(this.page.toString()) + }) + .nestedScroll({ + scrollForward: NestedScrollMode.PARENT_FIRST, + scrollBackward: NestedScrollMode.SELF_FIRST + }) + } + .alignRules({ + top: {anchor: 'li_menu', align: VerticalAlign.Bottom} + }) + .height('100%') + } + .height('auto') + } + .scrollBar(BarState.Off) + } + .backgroundColor(Color.White) + .onRefreshing(() => { + this.page = 1 + this.isRefreshing = true + this.viewModel.getMaterialList(this.page.toString()) + }) + } + + @Builder + itemFoot() { + // 注意:不要直接用IfElse节点作为footer的根节点 + // 必须在外面使用(Column/Row/Stack等)容器包裹,确保布局正确 + Column() { + if (!this.canLoadMore) { + Text('到底啦') + .width('100%') + .height(50) + .textAlign(TextAlign.Center) + } else if (this.isLoading) { + Row() { + LoadingProgress().height(32).width(48) + Text("加载中") + }.width("100%") + .height(50) + .justifyContent(FlexAlign.Center) + } + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/course/CoursePage.ets b/entry/src/main/ets/pages/main/home/course/CoursePage.ets new file mode 100644 index 0000000..32fc070 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/course/CoursePage.ets @@ -0,0 +1,22 @@ +import { TitleBar } from '../../../../view/TitleBar' + +@Entry +@ComponentV2 +struct CoursePage { + build() { + Column() { + TitleBar({title: '指导教程'}).width('100%') + Scroll() { + Column() { + Image($r('app.media.ic_course1')) + Image($r('app.media.ic_course2')) + Image($r('app.media.ic_course3')) + } + .height('auto') + } + .layoutWeight(1) + .scrollBar(BarState.Off) + } + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/link/TakeMaterialPage.ets b/entry/src/main/ets/pages/main/home/link/TakeMaterialPage.ets new file mode 100644 index 0000000..f6b00ce --- /dev/null +++ b/entry/src/main/ets/pages/main/home/link/TakeMaterialPage.ets @@ -0,0 +1,600 @@ +import { AppUtil, FileUtil, KeyboardUtil, ObjectUtil, PasteboardUtil, StrUtil } from '@pura/harmony-utils'; +import { RouterUrls } from '../../../../common/RouterUrls'; +import { MaterialLoadingDialog } from '../../../../dialog/MaterialLoadingDialog'; +import { + AudioMaterial, + ImageMaterial, + MaterialDetailEntity, + MaterialInfoEntity, + MediaEntity, + TextMaterial, + VideoMaterial +} from '../../../../entity/MaterialInfoEntity'; +import { VipPermissionEntity } from '../../../../entity/VipPermissionEntity'; +import { LoginManager } from '../../../../manager/LoginGlobalManager'; +import { ToastUtils } from '../../../../utils/ToastUtils'; +import { LinkRecognizeViewModel } from '../../../../viewModel/LinkRecognizeViewModel'; +import { AudioMaterialPage } from './material/AudioMaterialPage'; +import { ImageMaterialPage } from './material/ImageMaterialPage'; +import { TextMaterialPage } from './material/TextMaterialPage'; +import { VideoMaterialPage } from './material/VideoMaterialPage'; +import { router } from '@kit.ArkUI'; +import { MediaDownloader } from '../../../../net/MediaDownloader'; +import { ShareManager } from '../../../../manager/ShareManager'; +import { systemDateTime } from '@kit.BasicServicesKit'; +import { SaveUtils } from '../../../../utils/SaveUtils'; +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'; +import { EventConstants } from '../../../../common/EventConstants'; +import { SimpleTipDialog } from '../../../../dialog/SimpleTipDialog'; +import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager'; +import { PasteboardUtils } from '../../../../utils/PasteboardUtils'; +import { TipDialog } from '../../../../dialog/TipDialog'; +import { PrefUtils } from '../../../../utils/PrefUtils'; + +@Entry +@ComponentV2 +struct TakeMaterialPage { + @Local inputText: string = ''; + @Local currentIndex: number = 0; + + @Local materialInfo?: MaterialInfoEntity; + + @Local videoList: Array = []; + @Local imageList: Array = []; + @Local audioList: Array = []; + @Local textList: Array = []; + + @Local videoRowCount: number = 1; + @Local imageRowCount: number = 1; + + viewModel: LinkRecognizeViewModel = new LinkRecognizeViewModel(this.getUIContext()); + + tabController: TabsController = new TabsController(); + titles: Array = ['视频', '图片', '音频', '文本']; + logId?: string; + type: number = 0 + originText: string = '' + + mediaDownloader?: MediaDownloader | null + selectedList: Array = [] + cacheFileUris: Array = [] + downloadIndex: number = 0 + totalSize = 0 + downloadStatus = DownloadStatus.DOWNLOADING + + @Monitor('viewModel.materialInfo') + onMaterialInfoChange(monitor: IMonitor) { + const info = monitor.value()?.now as MaterialInfoEntity + this.logId = info.logid; + MaterialLoadingDialog.show(this.getUIContext(), info.status_name) + this.viewModel.analysisMaterial(info.logid, info.timeout); + } + + @Monitor('viewModel.analysisInfo') + onAnalysisInfoInfoChange(monitor: IMonitor) { + const info = monitor.value()?.now as MaterialInfoEntity + if (info.material && info.material !== null) { + this.materialInfo = info; + this.originText = this.inputText + this.materialInfo.logid = this.logId!! + this.createVideoList(info.material); + this.createImageList(info.material); + this.createAudioList(info.material); + this.createTextList(info.material); + if (this.videoList.length > 0) { + this.tabController.changeIndex(0); + } else if (this.imageList.length > 0) { + this.tabController.changeIndex(1); + } + MaterialLoadingDialog.dismiss(); + } else { + if (info.status !== 1) { + ToastUtils.show(info.status_name) + MaterialLoadingDialog.dismiss() + } else { + MaterialLoadingDialog.update(info.status_name) + } + } + } + + @Monitor('viewModel.permissionInfo') + onPermissionInfoChange(monitor: IMonitor) { + const info = monitor.value()?.now as VipPermissionEntity; + if (info.auth) { + if (!LoginManager.isLogin()) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.LOGIN_PAGE, params: {from: 1}}, router.RouterMode.Single) + return; + } + this.shareOrDownload() + } else { + if (!info.auth_ad) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIP_PAGE, params: {origin: 'download_material'}}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_MEMBER_RECHARGE, 'download_material') + return; + } + } + } + + @Monitor('viewModel.errorCode') + onErrorCodeChange(monitor: IMonitor) { + const errorCode = monitor.value()?.now as number; + if (errorCode === 12002 || errorCode === 12003 || errorCode === 12004) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.RECHARGE_DIAMOND_PAGE}); + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_RECHARGE_DIAMOND, 'download_material') + ToastUtils.show('钻石已用完') + } + } + + aboutToAppear(): void { + const params = this.getUIContext().getRouter().getParams() as Record + if (params && params.url) { + this.inputText = params.url as string + this.parseUrl(this.inputText) + } + } + + aboutToDisappear(): void { + this.viewModel.cancelInterval() + } + + onPageShow(): void { + this.checkPasteboard() + } + + checkPasteboard() { + PasteboardUtils.isNeedGetPermissionFromUser() + .then((isNeed) => { + if (isNeed) { + PasteboardUtil.requestPermissions() + .then((isGranted) => { + if (isGranted) { + let content = PasteboardUtil.getDataTextSync() + if (StrUtil.isNotEmpty(content) && content !== PasteboardUtils.clipText && PasteboardUtils.isValidUrl(content)) { + TipDialog.show(this.getUIContext(), {title: '您已复制链接,是否粘贴', content: content, leftText: '取消', rightText: '粘贴', callback: { + confirm: () => { + PasteboardUtils.clipText = content + this.inputText = content + this.parseUrl(content) + }, + cancel: () => { + PasteboardUtils.clipText = content + } + }}) + } + } + }) + .catch(() => {}) + } + }) + } + + showSaveTip(isAudio: boolean = false) { + if (!isAudio) { + if (PrefUtils.getBoolean('show_save_tip', true)) { + SimpleTipDialog.show(this.getUIContext(), {title: '重要提示', content: '下载完成后需要您点击弹窗允许保存之后才能保存文件到相册', callback: { + confirm: () => { + this.viewModel.checkVip(); + PrefUtils.put('show_save_tip', false) + } + }}) + } else { + this.viewModel.checkVip(); + } + } else { + this.viewModel.checkVip(); + } + } + + shareOrDownload() { + this.cacheFileUris.length = 0 + if (this.type == 0) { + if (this.selectedList.length > 0) { + this.shareMedia(this.selectedList[0]) + } + } else { + if (this.selectedList.length > 0) { + this.downloadIndex = 0 + this.downloadMedia(this.selectedList[this.downloadIndex]) + } + } + } + + shareMedia(media: MediaEntity) { + let filePath = FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName() + if (FileUtil.accessSync(filePath) && FileUtil.isFile(filePath) && !(media.totalSize === 0 || media.currentLen !== media.totalSize)) { + this.shareFile(media) + } else { + this.showDownloadDialog(media instanceof VideoMaterial && media.isMerge ? DownloadStatus.VIDEO_DOWNLOADING : DownloadStatus.DOWNLOADING) + if (media.isMerge) { + this.beforeDoMerge(media) + } + this.download(media, true) + } + } + + async downloadMedia(media: MediaEntity) { + let filePath = FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName() + if (FileUtil.accessSync(filePath) && FileUtil.isFile(filePath) && !(media.totalSize === 0 || media.currentLen !== media.totalSize)) { + this.cacheFileUris.push(FileUtil.getUriFromPath(filePath)) + if (this.downloadIndex < this.selectedList.length - 1) { + this.downloadIndex++ + this.downloadMedia(this.selectedList[this.downloadIndex]) + } else { + let saved = await this.saveFile(media) + if (saved) { + if (this.downloadIndex < this.selectedList.length - 1) { + this.downloadIndex++ + this.downloadMedia(this.selectedList[this.downloadIndex]) + } else { + this.updateDownloadDialog(DownloadStatus.COMPLETED, this.totalSize, this.totalSize, media instanceof AudioMaterial) + } + } else { + ToastUtils.show('保存失败') + this.dismissDownloadDialog() + } + } + } else { + this.showDownloadDialog(media instanceof VideoMaterial && media.isMerge ? DownloadStatus.VIDEO_DOWNLOADING : DownloadStatus.DOWNLOADING) + if (media.isMerge) { + this.beforeDoMerge(media) + } + this.download(media) + } + } + + beforeDoMerge(media: MediaEntity) { + let video = media as VideoMaterial + let audio = video.audio + let name = systemDateTime.getTime() + if (StrUtil.isEmpty(video.cacheName)) video.cacheName = `merge_${name}.mp4` + if (audio && StrUtil.isEmpty(audio?.cacheName)) audio.cacheName = `merge_${name}.mp3` + } + + async download(media: MediaEntity, isShare: boolean = false) { + this.viewModel.reportStatus(this.materialInfo!!.logid, '1') + media.initFileName() + media.origin = this.originText + this.mediaDownloader = MediaDownloader.getInstance() + this.mediaDownloader.setProxyList(this.materialInfo?.material?.proxyUrlList) + .callback({ + onSuccess: async (path) => { + if (isShare) { + this.shareFile(media) + } else { + this.cacheFileUris.push(FileUtil.getUriFromPath(path)) + if (this.downloadIndex < this.selectedList.length - 1) { + this.downloadIndex++ + this.downloadMedia(this.selectedList[this.downloadIndex]) + } else { + let saved = await this.saveFile(media) + if (saved) { + if (this.downloadIndex < this.selectedList.length - 1) { + this.downloadIndex++ + this.downloadMedia(this.selectedList[this.downloadIndex]) + } else { + this.updateDownloadDialog(DownloadStatus.COMPLETED, this.totalSize, this.totalSize, media instanceof AudioMaterial) + } + } else { + ToastUtils.show('保存失败') + this.dismissDownloadDialog() + } + } + } + }, + onGetTotal: (total) => { + this.totalSize = total + this.updateDownloadDialog(this.downloadStatus, total) + }, + onProgress: (progress) => { + this.updateDownloadDialog(this.downloadStatus, this.totalSize, progress) + }, + onMerge: (step) => { + if (step === 4) { + this.updateDownloadDialog(DownloadStatus.AUDIO_DOWNLOADING) + } else { + this.updateDownloadDialog(DownloadStatus.PROCESSING) + } + }, + onPause:() => { + this.dismissDownloadDialog() + }, + onCancel: () => { + this.dismissDownloadDialog() + }, + onFailed: (msg) => { + ToastUtils.show('下载失败:' + msg) + this.dismissDownloadDialog() + this.viewModel.reportStatus(this.materialInfo!!.logid, '-1', `${media.totalSize}`, msg) + this.reportErrorEvent(media, msg) + } + }) + .down(media); + } + + shareFile(media: MediaEntity) { + ShareManager.shareFile(FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName()) + this.viewModel.reportStatus(this.materialInfo!!.logid, '2', `${media.totalSize}`) + this.dismissDownloadDialog() + } + + async saveFile(media: MediaEntity): Promise { + let saved = false + if (media instanceof VideoMaterial) { + saved = await SaveUtils.saveImageVideoToAlbumDialog(this.cacheFileUris) + } else if (media instanceof ImageMaterial) { + saved = await SaveUtils.saveImageVideoToAlbumDialog(this.cacheFileUris) + } else if (media instanceof AudioMaterial) { + saved = await SaveUtils.saveAudioToMusic(this.cacheFileUris) + } + if (saved) { + let totalSize = 0 + this.cacheFileUris.forEach((uri) => { + let stat = FileUtil.statSync(FileUtil.getFilePath(uri)) + totalSize += stat.size + }) + this.viewModel.reportStatus(this.materialInfo!!.logid, '2', `${totalSize}`) + } else { + this.viewModel.reportStatus(this.materialInfo!!.logid, '-1', `保存失败`) + } + return Promise.resolve(saved) + } + + showDownloadDialog(status: DownloadStatus = DownloadStatus.DOWNLOADING) { + this.downloadStatus = status + DownloadDialog.show(this.getUIContext(), { status: status, totalSize: 0, progress: 0, totalCount: this.selectedList.length, index: this.downloadIndex, callback: { + confirm: () => { + if (this.downloadStatus === DownloadStatus.COMPLETED) { + EventReportGlobalManager.eventReport(EventConstants.DIALOG_GO_TO_VIEW, this.titles[this.currentIndex]) + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, this.currentIndex) + this.getUIContext().getRouter().back() + } else { + //todo 后台下载 + } + }, + cancel: () => { + if (this.downloadStatus !== DownloadStatus.COMPLETED) { + if (this.mediaDownloader) { + this.mediaDownloader.cancel() + EventReportGlobalManager.eventReport(EventConstants.CANCEL_DOWNLOAD_VIDEO, this.selectedList[this.downloadIndex].url) + } + } else { + EventReportGlobalManager.eventReport(EventConstants.DIALOG_CONFIRM_SAVE_FILE, this.titles[this.currentIndex]) + } + } + } }) + } + + updateDownloadDialog(status: DownloadStatus = DownloadStatus.DOWNLOADING, totalSize: number = 0, progress: number = 0, isAudio: boolean = false) { + this.downloadStatus = status + DownloadDialog.update({ status: status, totalSize: totalSize, progress: progress, totalCount: this.selectedList.length, index: this.downloadIndex, isAudio: isAudio }) + } + + dismissDownloadDialog() { + this.downloadStatus = DownloadStatus.DOWNLOADING + this.totalSize = 0 + this.downloadIndex = 0 + this.mediaDownloader = null + DownloadDialog.dismiss() + } + + parseUrl(url: string) { + if (StrUtil.isNotEmpty(this.inputText)) { + this.viewModel.getMaterialInfo(url); + } + } + + createVideoList(material?: MaterialDetailEntity) { + const list = new Array(); + material?.video?.forEach((item, index) => { + item.isThreading = material?.threading!!; + if (material?.merge && material.audio && material.audio.length > index) { + item.audio = material.audio[index]; + item.isMerge = true; + } + if (material?.image && material.image.length > index) { + item.thumb = material.image[index].url; + } + list.push(item); + }) + this.videoList = list.map(item => ObjectUtil.assign(new VideoMaterial(), item) as VideoMaterial); + if (this.videoList.length > 0) { + this.videoList[0].isChecked = true + } + this.videoRowCount = this.computeRowCount(this.videoList); + } + + createImageList(material: MaterialDetailEntity) { + if (material.image) { + this.imageList = material.image.map(item => ObjectUtil.assign(new ImageMaterial(), item) as ImageMaterial); + if (this.imageList.length > 0) { + this.imageList[0].isChecked = true + } + this.imageRowCount = this.computeRowCount(this.imageList); + } + } + + createAudioList(material: MaterialDetailEntity) { + if (material.audio) { + this.audioList = material.audio.map(item => ObjectUtil.assign(new AudioMaterial(), item) as AudioMaterial); + if (this.audioList.length > 0) { + this.audioList[0].isChecked = true + } + } + } + + createTextList(material: MaterialDetailEntity) { + const list = new Array(); + if (material.desc) { + const text = new TextMaterial() + text.title = material.title; + text.desc = material.desc; + list.push(text); + } + this.textList = list; + if (this.textList.length > 0) { + this.textList[0].isChecked = true + } + } + + computeRowCount(list: Array): number { + if (list.length > 8) { + return 3; + } else if (list.length >= 3 && list.length <= 8) { + return 2; + } else { + return 1; + } + } + + reportErrorEvent(media: MediaEntity, message: string) { + if (media instanceof VideoMaterial) { + EventReportGlobalManager.eventReport(EventConstants.ERROR_CLIENT_DOWNLOAD_VIDEO, media.url, message) + } else if (media instanceof ImageMaterial) { + EventReportGlobalManager.eventReport(EventConstants.ERROR_CLIENT_DOWNLOAD_IMG, media.url, message) + } else if (media instanceof AudioMaterial) { + EventReportGlobalManager.eventReport(EventConstants.ERROR_CLIENT_DOWNLOAD_AUDIO, media.url, message) + } + } + + build() { + Column() { + Row() { + Button({ type: ButtonType.Circle, stateEffect: true }) { + Image($r('app.media.ic_back')).width(24).height(24) + } + .width(40) + .height(40) + .margin({ left: 10 }) + .onClick(() => { + this.getUIContext().getRouter().back() + }) + .backgroundColor(Color.Transparent) + + Row() { + Image($r('app.media.ic_link')).width(18).height(18) + TextInput({ placeholder: '请输入链接地址', text: this.inputText }) + .layoutWeight(1) + .fontColor('#D6D6D6') + .fontSize(14) + .placeholderColor($r('app.color.color_30ffffff')) + .placeholderFont({ size: 14 }) + .onChange((value: string) => { + this.inputText = value; + }) + Image($r('app.media.ic_clear_text')) + .width(18) + .height(18) + .padding(2) + .visibility(StrUtil.isNotEmpty(this.inputText) ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.inputText = ''; + }) + Divider() + .vertical(true) + .strokeWidth(1) + .height(14) + .color($r('app.color.color_10ffffff')) + .margin({ left: 12, right: 12 }) + Text('获取').fontColor($r('app.color.color_80ffffff')).fontSize(14) + .onClick(() => { + if (StrUtil.isNotEmpty(this.inputText)) { + KeyboardUtil.hide() + this.parseUrl(this.inputText) + EventReportGlobalManager.eventReport(EventConstants.GET_MATERIAL, "material-button", this.inputText) + } else { + ToastUtils.show('请输入链接地址') + } + }) + } + .layoutWeight(1) + .height(34) + .borderRadius(20) + .backgroundColor($r('app.color.color_333333')) + .margin({ left: 20, right: 16 }) + .padding({ left: 10, right: 10 }) + }.height(100).padding({ top: 50 }) + + Tabs({ barPosition: BarPosition.Start, controller: this.tabController }) { + TabContent() { + VideoMaterialPage({ + mediaList: this.videoList, rowCount: this.videoRowCount, + onShare:(video) => { + this.type = 0 + this.selectedList.length = 0 + this.selectedList.push(video) + this.viewModel.checkVip(); + }, + onSave: (list) => { + this.type = 1 + this.selectedList = list + this.showSaveTip(); + } }) + } + .tabBar(this.tabBuilder(this.titles[0], 0)) + + TabContent() { + ImageMaterialPage({ + mediaList: this.imageList, rowCount: this.imageRowCount, + onShare:(image) => { + this.type = 0 + this.selectedList.length = 0 + this.selectedList.push(image) + this.viewModel.checkVip(); + }, + onSave: (list) => { + this.type = 1 + this.selectedList = list + this.showSaveTip(); + } }) + } + .tabBar(this.tabBuilder(this.titles[1], 1)) + + TabContent() { + AudioMaterialPage({ + mediaList: this.audioList, + onShare:(audio) => { + this.type = 0 + this.selectedList.length = 0 + this.selectedList.push(audio) + this.viewModel.checkVip(); + }, + onSave: (list) => { + this.type = 1 + this.selectedList = list + this.showSaveTip(true); + } }) + } + .tabBar(this.tabBuilder(this.titles[2], 2)) + + TabContent() { + TextMaterialPage({ mediaList: this.textList }) + } + .tabBar(this.tabBuilder(this.titles[3], 3)) + } + .scrollable(false) + .onTabBarClick((index) => { + this.currentIndex = index; + }) + /*.onSelected((index: number) => { + this.currentIndex = index; + })*/ + .layoutWeight(1) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } + + @Builder + tabBuilder(title: string, targetIndex: number) { + Column() { + Text(title) + .fontColor(this.currentIndex === targetIndex ? $r("app.color.color_466afd") : $r('app.color.color_50ffffff')) + .fontSize(this.currentIndex === targetIndex ? 17 : 14) + .fontWeight(this.currentIndex === targetIndex ? FontWeight.Medium : FontWeight.Regular) + } + .width('100%') + .height(50) + .justifyContent(FlexAlign.Center) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/link/material/AudioMaterialPage.ets b/entry/src/main/ets/pages/main/home/link/material/AudioMaterialPage.ets new file mode 100644 index 0000000..51c9c17 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/link/material/AudioMaterialPage.ets @@ -0,0 +1,114 @@ +import { AudioMaterial } from '../../../../../entity/MaterialInfoEntity'; +import { ToastUtils } from '../../../../../utils/ToastUtils'; +import { EmptyView, PageStatus } from '../../../../../view/EmptyView'; +import { AudioMaterialItemView } from '../../../../../view/MaterialItemView'; + +@ComponentV2 +export struct AudioMaterialPage { + @Param mediaList: Array = []; + @Param onShare?: (audio: AudioMaterial) => void = undefined + @Param onSave?: (list: Array) => void = undefined + + @Local isCheckAll: boolean = false + + selectedItems(): Array { + const list = new Array(); + this.mediaList.forEach((item) => { + if (item.isChecked) { + list.push(item); + } + }) + return list; + } + + build() { + Stack() { + Column() { + List({ space: 10 }) { + ForEach(this.mediaList, (item: AudioMaterial, index) => { + ListItem() { + AudioMaterialItemView({ media: item }) + } + .onClick(() => { + item.isChecked = !item.isChecked; + this.isCheckAll = this.mediaList.every(item => item.isChecked) + }) + }) + } + .width('auto') + .layoutWeight(1) + .scrollBar(BarState.Off) + .margin({ left: 16, right: 16, bottom: 15 }) + + Row() { + Row() { + Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')).width(18).height(18) + Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 }) + } + .onClick(() => { + this.isCheckAll = !this.isCheckAll; + this.mediaList.forEach((item) => { + item.isChecked = this.isCheckAll; + }) + }) + + Blank().layoutWeight(1) + + Button('转发', { type: ButtonType.Capsule, stateEffect: true }) + .width(122) + .height(40) + .backgroundColor($r('app.color.color_333333')) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(15) + .onClick(() => { + if (this.onShare) { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要转发的音频'); + } else if (list.length > 1) { + ToastUtils.show('一次只能转发一个音频'); + } else { + this.onShare(list[0]); + } + } + }) + + Button('保存', { type: ButtonType.Capsule, stateEffect: true }) + .width(122) + .height(40) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .margin({ left: 12 }) + .onClick(() => { + if (this.onSave) { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要保存的音频'); + } else { + this.onSave(list); + } + } + }) + } + .backgroundColor($r('app.color.color_222222')) + .padding({ + left: 16, + top: 10, + right: 16, + bottom: 30 + }) + .visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None) + } + + EmptyView({ + status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA, + noDataImage: $r('app.media.ic_empty_audio'), + noDataText: '暂无音频' + }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/link/material/ImageMaterialPage.ets b/entry/src/main/ets/pages/main/home/link/material/ImageMaterialPage.ets new file mode 100644 index 0000000..1d25221 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/link/material/ImageMaterialPage.ets @@ -0,0 +1,117 @@ +import { ImageMaterial } from '../../../../../entity/MaterialInfoEntity'; +import { ToastUtils } from '../../../../../utils/ToastUtils'; +import { EmptyView, PageStatus } from '../../../../../view/EmptyView'; +import { ImageMaterialItemView } from '../../../../../view/MaterialItemView'; + +@ComponentV2 +export struct ImageMaterialPage { + @Param mediaList: Array = []; + @Param rowCount: number = 1; + @Param onShare?: (image: ImageMaterial) => void = undefined + @Param onSave?: (list: Array) => void = undefined + + @Local isCheckAll: boolean = false + + selectedItems(): Array { + const list = new Array(); + this.mediaList.forEach((item) => { + if (item.isChecked) { + list.push(item); + } + }) + return list; + } + + build() { + Stack() { + Column() { + Grid() { + ForEach(this.mediaList, (item: ImageMaterial, index) => { + GridItem() { + ImageMaterialItemView({ media: item, rowCount: this.rowCount }) + } + .onClick(() => { + item.isChecked = !item.isChecked; + this.isCheckAll = this.mediaList.every(item => item.isChecked) + }) + }) + } + .scrollBar(BarState.Off) + .columnsTemplate(this.rowCount === 1 ? '1fr' : this.rowCount === 2 ? '1fr 1fr' : '1fr 1fr 1fr') + .rowsGap(10) + .columnsGap(10) + .margin({ left: 16, right: 16, bottom: 15 }) + .layoutWeight(1) + + Row() { + Row() { + Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')).width(18).height(18) + Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 }) + } + .onClick(() => { + this.isCheckAll = !this.isCheckAll; + this.mediaList.forEach((item) => { + item.isChecked = this.isCheckAll; + }) + }) + + Blank().layoutWeight(1) + + Button('转发', { type: ButtonType.Capsule, stateEffect: true }) + .width(122) + .height(40) + .backgroundColor($r('app.color.color_333333')) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(15) + .onClick(() => { + if (this.onShare) { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要转发的图片'); + } else if (list.length > 1) { + ToastUtils.show('一次只能转发一张图片'); + } else { + this.onShare(list[0]); + } + } + }) + + Button('保存', { type: ButtonType.Capsule, stateEffect: true }) + .width(122) + .height(40) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .margin({ left: 12 }) + .onClick(() => { + if (this.onSave) { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要保存的图片'); + } else { + this.onSave(list); + } + } + }) + } + .backgroundColor($r('app.color.color_222222')) + .padding({ + left: 16, + top: 10, + right: 16, + bottom: 30 + }) + .visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None) + } + + EmptyView({ + status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA, + noDataImage: $r('app.media.ic_empty_image'), + noDataText: '暂无图片' + }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/link/material/TextMaterialPage.ets b/entry/src/main/ets/pages/main/home/link/material/TextMaterialPage.ets new file mode 100644 index 0000000..d2090cc --- /dev/null +++ b/entry/src/main/ets/pages/main/home/link/material/TextMaterialPage.ets @@ -0,0 +1,93 @@ +import { PasteboardUtil } from '@pura/harmony-utils'; +import { TextMaterial } from '../../../../../entity/MaterialInfoEntity'; +import { ToastUtils } from '../../../../../utils/ToastUtils'; +import { EmptyView, PageStatus } from '../../../../../view/EmptyView'; +import { TextMaterialItemView } from '../../../../../view/MaterialItemView'; + +@ComponentV2 +export struct TextMaterialPage { + @Param mediaList: Array = []; + + @Local isCheckAll: boolean = false + + selectedItems(): Array { + const list = new Array(); + this.mediaList.forEach((item) => { + if (item.isChecked) { + list.push(item); + } + }) + return list; + } + + build() { + Stack() { + Column() { + List({ space: 10 }) { + ForEach(this.mediaList, (item: TextMaterial, index) => { + ListItem() { + TextMaterialItemView({ media: item }) + } + .onClick(() => { + item.isChecked = !item.isChecked; + this.isCheckAll = this.mediaList.every(item => item.isChecked) + }) + }) + } + .width('auto') + .layoutWeight(1) + .scrollBar(BarState.Off) + .margin({ left: 16, right: 16, bottom: 15 }) + + Row() { + Row() { + Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')).width(18).height(18) + Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 }) + } + .onClick(() => { + this.isCheckAll = !this.isCheckAll; + this.mediaList.forEach((item) => { + item.isChecked = this.isCheckAll; + }) + }) + + Button('复制文本', { type: ButtonType.Capsule, stateEffect: true }) + .layoutWeight(1) + .height(40) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .margin({ left: 27 }) + .onClick(() => { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要复制的文本'); + } else { + list.forEach((item) => { + PasteboardUtil.setDataTextSync(item.title); + }) + ToastUtils.show('已复制'); + } + }) + } + .backgroundColor($r('app.color.color_222222')) + .padding({ + left: 16, + top: 10, + right: 16, + bottom: 30 + }) + .visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None) + } + + EmptyView({ + status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA, + noDataImage: $r('app.media.ic_empty_text'), + noDataText: '暂无文本' + }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/link/material/VideoMaterialPage.ets b/entry/src/main/ets/pages/main/home/link/material/VideoMaterialPage.ets new file mode 100644 index 0000000..aef469a --- /dev/null +++ b/entry/src/main/ets/pages/main/home/link/material/VideoMaterialPage.ets @@ -0,0 +1,117 @@ +import { VideoMaterial } from '../../../../../entity/MaterialInfoEntity'; +import { ToastUtils } from '../../../../../utils/ToastUtils'; +import { EmptyView, PageStatus } from '../../../../../view/EmptyView'; +import { VideoMaterialItemView } from '../../../../../view/MaterialItemView'; + +@ComponentV2 +export struct VideoMaterialPage { + @Param mediaList: Array = []; + @Param rowCount: number = 1; + @Param onShare?: (video: VideoMaterial) => void = undefined + @Param onSave?: (list: Array) => void = undefined + + @Local isCheckAll: boolean = false + + selectedItems(): Array { + const list = new Array(); + this.mediaList.forEach((item) => { + if (item.isChecked) { + list.push(item); + } + }) + return list; + } + + build() { + Stack() { + Column() { + Grid() { + ForEach(this.mediaList, (item: VideoMaterial, index) => { + GridItem() { + VideoMaterialItemView({ media: item, rowCount: this.rowCount }) + } + .onClick(() => { + item.isChecked = !item.isChecked; + this.isCheckAll = this.mediaList.every(item => item.isChecked) + }) + }) + } + .scrollBar(BarState.Off) + .columnsTemplate(this.rowCount === 1 ? '1fr' : this.rowCount === 2 ? '1fr 1fr' : '1fr 1fr 1fr') + .rowsGap(10) + .columnsGap(10) + .margin({ left: 16, right: 16, bottom: 15 }) + .layoutWeight(1) + + Row() { + Row() { + Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')).width(18).height(18) + Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 }) + } + .onClick(() => { + this.isCheckAll = !this.isCheckAll; + this.mediaList.forEach((item) => { + item.isChecked = this.isCheckAll; + }) + }) + + Blank().layoutWeight(1) + + Button('转发', { type: ButtonType.Capsule, stateEffect: true }) + .width(122) + .height(40) + .backgroundColor($r('app.color.color_333333')) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(15) + .onClick(() => { + if (this.onShare) { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要转发的视频'); + } else if (list.length > 1) { + ToastUtils.show('一次只能转发一个视频'); + } else { + this.onShare(list[0]); + } + } + }) + + Button('保存', { type: ButtonType.Capsule, stateEffect: true }) + .width(122) + .height(40) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .margin({ left: 12 }) + .onClick(() => { + if (this.onSave) { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要保存的视频'); + } else { + this.onSave(list); + } + } + }) + } + .backgroundColor($r('app.color.color_222222')) + .padding({ + left: 16, + top: 10, + right: 16, + bottom: 30 + }) + .visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None) + } + + EmptyView({ + status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA, + noDataImage: $r('app.media.ic_empty_video'), + noDataText: '暂无视频' + }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/material/MaterialDetailPage.ets b/entry/src/main/ets/pages/main/home/material/MaterialDetailPage.ets new file mode 100644 index 0000000..7ede649 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/material/MaterialDetailPage.ets @@ -0,0 +1,228 @@ +import { MaterialEntity } from '../../../../entity/MaterialEntity'; +import { TitleBar } from '../../../../view/TitleBar'; +import { router, window } from '@kit.ArkUI'; +import { ImagePreview } from '@rv/image-preview'; +import { AppUtil, DisplayUtil, FileUtil, NumberUtil } from '@pura/harmony-utils'; +import { media } from '@kit.MediaKit'; +import { BusinessError, request, systemDateTime } from '@kit.BasicServicesKit'; +import { ToastUtils } from '../../../../utils/ToastUtils'; +import { SaveUtils } from '../../../../utils/SaveUtils'; +import { AuthViewModel } from '../../../../viewModel/AuthViewModel'; +import { VipPermissionEntity } from '../../../../entity/VipPermissionEntity'; +import { LoginManager } from '../../../../manager/LoginGlobalManager'; +import { RouterUrls } from '../../../../common/RouterUrls'; +import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager'; +import { EventConstants } from '../../../../common/EventConstants'; +import { LoadingDialog } from '../../../../dialog/LoadingDialog'; +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'; + +@Entry +@ComponentV2 +struct MaterialDetailPage { + windowStage: window.WindowStage = AppStorage.get("windowStage") as window.WindowStage; + + @Local material?: MaterialEntity; + @Local tagArray: Array = [] + + private viewModel: AuthViewModel = new AuthViewModel(this.getUIContext()) + private imageSize: media.PixelMapParams = {} + + @Monitor('viewModel.permissionInfo') + onPermissionInfoChange(monitor: IMonitor) { + const info = monitor.value()?.now as VipPermissionEntity; + if (info.auth) { + if (!LoginManager.isLogin()) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.LOGIN_PAGE, params: {from: 1}}, router.RouterMode.Single) + return; + } + this.download() + } else { + if (!info.auth_ad) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIP_PAGE, params: {origin: 'download_material'}}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_MEMBER_RECHARGE, 'download_material') + return; + } + } + } + + @Monitor('viewModel.errorCode') + onErrorCodeChange(monitor: IMonitor) { + const errorCode = monitor.value()?.now as number; + if (errorCode === 12002 || errorCode === 12003 || errorCode === 12004) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.RECHARGE_DIAMOND_PAGE}); + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_RECHARGE_DIAMOND, 'download_material') + ToastUtils.show('M币已用完') + } + } + + aboutToAppear(): void { + this.windowStage.getMainWindowSync().setWindowSystemBarProperties({ + statusBarColor: '#00000000', + statusBarContentColor: '#FFFFFF' + }); + this.initParams() + } + + aboutToDisappear(): void { + this.windowStage.getMainWindowSync().setWindowSystemBarProperties({ + statusBarColor: '#00000000', + statusBarContentColor: '#000000' + }); + } + + initParams() { + const params = this.getUIContext().getRouter().getParams() as Record; + if (params) { + this.material = params.material as MaterialEntity + if (this.material.pic_size) { + const size = this.material.pic_size.split(':') + if (size.length === 2) { + this.imageSize.width = NumberUtil.toNumber(size[0]) + this.imageSize.height = NumberUtil.toNumber(size[1]) + } + } + this.tagArray = this.material.tags + } + } + + download() { + LoadingDialog.show(this.getUIContext()) + let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.jpeg` + let config: request.agent.Config = { + action: request.agent.Action.DOWNLOAD, + url: this.material!!.pic!!.url, + overwrite: true, + saveas: outputPath, + gauge: true, + priority:0 + } + request.agent.create(AppUtil.getApplicationContext(), config) + .then((task: request.agent.Task) => { + task.start((err: BusinessError) => { + if (err) { + console.error(`下载失败: ${err.message}`); + ToastUtils.show(err.message); + return; + } + LoadingDialog.dismiss() + }); + task.on('completed',() => { + LoadingDialog.dismiss() + if (FileUtil.accessSync(outputPath)) { + SaveUtils.saveImageVideoToAlbumDialog([outputPath]) + .then((saved) => { + if (saved) { + this.showDownloadDialog() + } else { + ToastUtils.show('保存失败') + } + }) + .catch(() => { + ToastUtils.show('保存失败') + }) + } else { + ToastUtils.show('下载失败: 文件不存在'); + } + request.agent.remove(task.tid); + }) + task.on('failed', () => { + LoadingDialog.dismiss() + ToastUtils.show('下载失败,请检查网络后重试'); + request.agent.remove(task.tid); + }); + }) + .catch((err: BusinessError) => { + if (err) { + console.error(`下载失败: ${err.message}`); + ToastUtils.show(err.message); + } + LoadingDialog.dismiss() + }) + } + + showDownloadDialog() { + DownloadDialog.show(this.getUIContext(), { + status: DownloadStatus.COMPLETED, + totalSize: 0, + progress: 0, + totalCount: 1, + index: 0, + callback: { + confirm: () => { + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0) + this.getUIContext().getRouter().back() + } + } + }) + } + + build() { + Column() { + TitleBar({title: this.material?.title, isDark: true}) + + Stack() { + ImagePreview() { + RelativeContainer() { + Image(this.material?.pic?.url) + .width("100%") + .height(px2vp(Math.round(this.imageSize.height!! * DisplayUtil.getWidth() / this.imageSize.width!!))) + .sourceSize({ + width: px2vp(DisplayUtil.getWidth()), + height: px2vp(Math.round(this.imageSize.height!! * DisplayUtil.getWidth() / this.imageSize.width!!)) + }) + .alignRules({ + top: {anchor: '__container__', align: VerticalAlign.Top}, + bottom: {anchor: '__container__', align: VerticalAlign.Bottom} + }) + .draggable(false) + .id('image') + + List({space: 4}){ + ForEach(this.tagArray, (item: string) => { + ListItem() { + Text(item).fontColor(Color.White).fontSize(10) + .borderRadius(2) + .backgroundColor('#80000000') + .padding({left: 4, top: 1, right: 4, bottom: 1}) + } + }) + } + .listDirection(Axis.Horizontal) + .alignRules({ + top: {anchor: 'image', align: VerticalAlign.Top}, + right: {anchor: 'image', align: HorizontalAlign.End} + }) + .margin({top: 12, right: 12}) + } + .height('auto') + } + } + .width('100%') + .layoutWeight(1) + + Stack() { + Button({ type: ButtonType.Capsule, stateEffect: true }) { + Row() { + Image($r('app.media.ic_download1')).width(22).height(22) + Text('下载').fontColor(Color.White).fontSize(15).fontWeight(FontWeight.Medium) + } + } + .width('100%') + .height(46) + .backgroundColor($r('app.color.color_466afd')) + .onClick(() => { + if (this.material?.pic?.url) { + this.viewModel.checkVip() + } else { + ToastUtils.show('链接不存在') + } + }) + } + .backgroundColor($r('app.color.color_1b1b1b')) + .padding({left: 16, top: 9, right: 16, bottom: 30}) + } + .width('100%') + .height('100%') + .backgroundColor('#0F0E13') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/tools/AddAudioPage.ets b/entry/src/main/ets/pages/main/home/tools/AddAudioPage.ets new file mode 100644 index 0000000..4c5eb1c --- /dev/null +++ b/entry/src/main/ets/pages/main/home/tools/AddAudioPage.ets @@ -0,0 +1,329 @@ +import { PhotoHelper, PickerUtil } from '@pura/picker_utils' +import { TitleBar } from '../../../../view/TitleBar' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import { BusinessError, systemDateTime } from '@kit.BasicServicesKit' +import { AppUtil, FileUtil } from '@pura/harmony-utils' +import { ToastUtils } from '../../../../utils/ToastUtils' +import { fileIo, picker } from '@kit.CoreFileKit' +import { SaveUtils } from '../../../../utils/SaveUtils' +import { MP4Parser } from '@ohos/mp4parser' +import { LoadingDialog } from '../../../../dialog/LoadingDialog' +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog' +import { EventConstants } from '../../../../common/EventConstants' +import { TipDialog } from '../../../../dialog/TipDialog' +import { avSessionManager } from '../../../../manager/AVSessionManager' + +@Entry +@ComponentV2 +struct AddAudioPage { + private controller: VideoController = new VideoController() + @Local videoUri?: string + @Local audioUri?: string + @Local currentTime: number = 0 + @Local durationTime: number = 0 + @Local isPlaying: boolean = false + @Local isSuccess: boolean = false + + mirrorVideo() { + LoadingDialog.show(this.getUIContext()) + this.isSuccess = false + let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp4` + if (FileUtil.accessSync(cacheVideoPath)) { + FileUtil.unlinkSync(cacheVideoPath) + } + let videoFile = FileUtil.openSync(this.videoUri!!, fileIo.OpenMode.READ_ONLY) + // 复制视频文件到缓存目录下 + FileUtil.copyFileSync(videoFile.fd, cacheVideoPath) + + let cacheAudioPath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp3` + if (FileUtil.accessSync(cacheAudioPath)) { + FileUtil.unlinkSync(cacheAudioPath) + } + let audioFile = FileUtil.openSync(this.audioUri!!, fileIo.OpenMode.READ_ONLY) + // 复制音频文件到缓存目录下 + FileUtil.copyFileSync(audioFile.fd, cacheAudioPath) + + let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + let cmd = `ffmpeg -i ${cacheVideoPath} -stream_loop -1 -i ${cacheAudioPath} -c:v copy -c:a aac -shortest -map 0:v -map 1:a ${outputPath}` + MP4Parser.ffmpegCmd(cmd, { + callBackResult: (code: number) => { + if (code === 0) { + this.videoUri = FileUtil.getUriFromPath(outputPath) + this.isSuccess = true + this.isPlaying = false + ToastUtils.show('处理成功') + } else { + ToastUtils.show('处理失败') + } + LoadingDialog.dismiss() + } + }) + } + + selectVideo() { + PhotoHelper.selectEasy({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE, + maxSelectNumber: 1, + isPhotoTakingSupported: false, + isEditSupported: false, + isOriginalSupported: false + }) + .then((uris) => { + if (uris.length != 0) { + this.isSuccess = false + this.videoUri = uris[0] + } + }) + } + + selectAudio() { + /*PickerUtil.selectAudio({maxSelectNumber: 1}) + .then((uris) => { + if (uris.length != 0) { + this.audioUri = uris[0] + } + })*/ + PickerUtil.selectDocument({maxSelectNumber: 1, fileSuffixFilters: ['.mp3']}) + .then((uris) => { + if (uris.length != 0) { + this.audioUri = uris[0] + } + }) + } + + showDownloadDialog() { + DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: { + confirm: () => { + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0) + this.getUIContext().getRouter().back() + } + } }) + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } + + onBackPress(): boolean | void { + if (this.isSuccess) { + TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: { + confirm: () => { + this.getUIContext().getRouter().back() + } + }}) + return true + } + return false + } + + build() { + Column() { + TitleBar({ title: '加音乐' }) + + Column() { + Row() { + Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium) + Text('(仅支持mp4格式)').fontColor($r('app.color.color_50ffffff')).fontSize(12) + }.alignSelf(ItemAlign.Start) + + RelativeContainer() { + Stack() { + Image($r('app.media.ic_add_video')).width(44).height(44) + } + .width(140) + .height(140) + .borderRadius(10) + .backgroundColor($r('app.color.color_333333')) + .alignRules({ + start: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + end: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onClick(() => { + this.selectVideo() + }) + } + .height(220) + .margin({ top: 12 }) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + }.margin({ left: 16, top: 16, right: 16 }) + .visibility(this.videoUri ? Visibility.None : Visibility.Visible) + + Column() { + RelativeContainer() { + Video({ + src: this.videoUri, // 设置视频源 + controller: this.controller, //设置视频控制器,可以控制视频的播放状态 + posterOptions: { showFirstFrame: true } + }) + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + .controls(false) // 设置是否显示默认控制条 + .autoPlay(false) // 设置是否自动播放 + .loop(false) // 设置是否循环播放 + .objectFit(ImageFit.Contain) // 设置视频填充模式 + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onStart(() => { + this.isPlaying = true + }) + .onPause(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onStop(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onFinish(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onError(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onDisAppear(() => { + avSessionManager.deactivate() + }) + + Image($r('app.media.ic_play_video')) + .width(50) + .height(50) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + .onClick(async () => { + await avSessionManager.activate() + this.controller.start() + }) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + }) + + Row() { + Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play')) + .width(20) + .height(20) + .margin({ right: 20 }) + .onClick(async () => { + if (this.isPlaying) { + this.controller.pause() + } else { + await avSessionManager.activate() + this.controller.start() + } + }) + Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .blockColor(Color.White) + .trackColor($r('app.color.color_60ffffff')) + .onChange((value: number, mode: SliderChangeMode) => { + this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处 + }) + .layoutWeight(1) + Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12) + } + .opacity(0.8) + .width("100%") + .padding({ left: 30, right: 30 }) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + } + .layoutWeight(1) + + Image($r('app.media.ic_add_audio')).width(50).height(50).margin({top: 20}) + .onClick(() => { + this.selectAudio() + }) + Text('音频').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({top: 10}) + + Row() { + Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + this.selectVideo() + } else { + this.getUIContext().getRouter().back() + } + }) + Blank().layoutWeight(1) + Text(this.isSuccess ? '保存' : '确定').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + SaveUtils.saveImageVideoToAlbumDialog([this.videoUri!!]) + .then((saved) => { + if (saved) { + this.videoUri = undefined + this.showDownloadDialog() + } else { + ToastUtils.show('保存失败') + } + }) + .catch((e: BusinessError) => { + ToastUtils.show('保存失败:' + e.message) + }) + + } else { + if (this.audioUri) { + this.mirrorVideo() + } else { + ToastUtils.show('请上传音频') + } + } + }) + } + .margin({ top: 50, bottom: 30 }) + } + .layoutWeight(1) + .visibility(this.videoUri ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/tools/AddWaterMarkerPage.ets b/entry/src/main/ets/pages/main/home/tools/AddWaterMarkerPage.ets new file mode 100644 index 0000000..3210aa8 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/tools/AddWaterMarkerPage.ets @@ -0,0 +1,425 @@ +import { PhotoHelper } from '@pura/picker_utils' +import { TitleBar } from '../../../../view/TitleBar' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import { BusinessError, systemDateTime } from '@kit.BasicServicesKit' +import { AppUtil, DisplayUtil, FileUtil, ImageUtil, StrUtil } from '@pura/harmony-utils' +import { ToastUtils } from '../../../../utils/ToastUtils' +import { fileIo } from '@kit.CoreFileKit' +import { SaveUtils } from '../../../../utils/SaveUtils' +import { LoadingDialog } from '../../../../dialog/LoadingDialog' +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog' +import { EventConstants } from '../../../../common/EventConstants' +import { RectPosition } from '../../../../view/RectCropView' +import { media } from '@kit.MediaKit' +import { MediaUtils } from '../../../../utils/MediaUtils' +import { MP4Parser } from '@ohos/mp4parser' +import { TipDialog } from '../../../../dialog/TipDialog' +import { WaterMarkerView } from '../../../../view/WaterMarkerView' +import { EditTextDialog } from '../../../../dialog/EditTextDialog' +import { image } from '@kit.ImageKit' +import { avSessionManager } from '../../../../manager/AVSessionManager' + +@Entry +@ComponentV2 +struct AddWaterMarkerPage { + @Local uri?: string + @Local currentTime: number = 0 + @Local durationTime: number = 0 + @Local isPlaying: boolean = false + @Local isSuccess: boolean = false + @Local playerSize: media.PixelMapParams = { width: 0, height: 0 } + + @Local showWaterMaker: boolean = false + @Local textContent: string = '' + @Local imagePath: string = '' + + private controller: VideoController = new VideoController() + private videoSize: media.PixelMapParams = { width: 0, height: 0 } + private rect: RectPosition = { x: 0, y: 0, width: 0, height: 0 } + + addWaterMarker() { + LoadingDialog.show(this.getUIContext()) + this.isSuccess = false + let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + if (FileUtil.accessSync(cacheVideoPath)) { + FileUtil.unlinkSync(cacheVideoPath) + } + + let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) + // 复制文件到缓存目录下 + FileUtil.copyFileSync(file.fd, cacheVideoPath) + + let imageX = (vp2px(this.rect.x) * this.videoSize.width!!) / this.playerSize.width!! + let imageY = (vp2px(this.rect.y) * this.videoSize.height!!) / this.playerSize.height!! + + let imageWidth = (vp2px(this.rect.width * this.videoSize.width!!) / this.playerSize.width!!) + let imageHeight = (vp2px(this.rect.height * this.videoSize.height!!) / this.playerSize.height!!) + + this.getUIContext().getComponentSnapshot().get(StrUtil.isNotEmpty(this.textContent) ? 'textWaterMarker' : 'imageWaterMarker') + .then(async (image: image.PixelMap) => { + let imagePath = await ImageUtil.savePixelMap(image, FileUtil.getCacheDirPath(), `cache_${systemDateTime.getTime()}.png`) + let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + let cmd = `ffmpeg -i ${cacheVideoPath} -i ${imagePath} -filter_complex [1:v]scale=${Math.round(imageWidth)}:${Math.round(imageHeight)}[wm];[0:v][wm]overlay=${imageX}:${imageY} -c:v h264 -pix_fmt yuv420p -y ${outputPath}` + MP4Parser.ffmpegCmd(cmd, { + callBackResult: (code: number) => { + if (code === 0) { + this.uri = FileUtil.getUriFromPath(outputPath) + this.isSuccess = true + this.isPlaying = false + ToastUtils.show('处理成功') + } else { + ToastUtils.show('处理失败') + } + LoadingDialog.dismiss() + } + }) + }) + .catch((error: BusinessError) => { + ToastUtils.show('处理失败:' + error.message) + LoadingDialog.dismiss() + }) + } + + selectVideo() { + PhotoHelper.selectEasy({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE, + maxSelectNumber: 1, + isPhotoTakingSupported: false, + isEditSupported: false, + isOriginalSupported: false + }) + .then((uris) => { + if (uris.length != 0) { + this.isSuccess = false + this.uri = uris[0] + + this.showWaterMaker = false + this.textContent = '' + this.imagePath = '' + + MediaUtils.getVideoSize(this.uri) + .then((size) => { + this.videoSize = size + if (size.width && size.height) { + const ratio = (DisplayUtil.getWidth() - 180) / size.width + this.playerSize = {width: Math.ceil(size.width * ratio), height: Math.ceil(size.height * ratio)} + } + }) + } + }) + } + + showDownloadDialog() { + DownloadDialog.show(this.getUIContext(), { + status: DownloadStatus.COMPLETED, + totalSize: 0, + progress: 0, + totalCount: 1, + index: 0, + callback: { + confirm: () => { + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0) + this.getUIContext().getRouter().back() + } + } + }) + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } + + onBackPress(): boolean | void { + if (this.isSuccess) { + TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: { + confirm: () => { + this.getUIContext().getRouter().back() + } + }}) + return true + } + return false + } + + build() { + Column() { + TitleBar({ title: '加水印' }) + + Column() { + Row() { + Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium) + Text('(仅支持mp4格式)').fontColor($r('app.color.color_50ffffff')).fontSize(12) + }.alignSelf(ItemAlign.Start) + + RelativeContainer() { + Stack() { + Image($r('app.media.ic_add_video')).width(44).height(44) + } + .width(140) + .height(140) + .borderRadius(10) + .backgroundColor($r('app.color.color_333333')) + .alignRules({ + start: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + end: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onClick(() => { + this.selectVideo() + }) + } + .height(220) + .margin({ top: 12 }) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + }.margin({ left: 16, top: 16, right: 16 }) + .visibility(this.uri ? Visibility.None : Visibility.Visible) + + Column() { + RelativeContainer() { + Video({ + src: this.uri, // 设置视频源 + controller: this.controller, //设置视频控制器,可以控制视频的播放状态 + posterOptions: { showFirstFrame: true } + }) + .id('video') + .width(this.playerSize ? px2vp(this.playerSize.width) : '100%') + .height(this.playerSize ? px2vp(this.playerSize.height) : '100%') + .backgroundColor($r('app.color.window_background')) + .controls(false) // 设置是否显示默认控制条 + .autoPlay(false) // 设置是否自动播放 + .loop(false) // 设置是否循环播放 + .objectFit(ImageFit.Cover) // 设置视频填充模式 + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onStart(() => { + this.isPlaying = true + }) + .onPause(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onStop(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onFinish(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onError(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onDisAppear(() => { + avSessionManager.deactivate() + }) + + Image($r('app.media.ic_play_video')) + .width(50) + .height(50) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + .onClick(async () => { + await avSessionManager.activate() + this.controller.start() + }) + .alignRules({ + left: { anchor: 'video', align: HorizontalAlign.Start }, + top: { anchor: 'video', align: VerticalAlign.Top }, + right: { anchor: 'video', align: HorizontalAlign.End }, + bottom: { anchor: 'video', align: VerticalAlign.Bottom } + }) + + Row() { + Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play')) + .width(20) + .height(20) + .margin({ right: 20 }) + .onClick(async () => { + if (this.isPlaying) { + this.controller.pause() + } else { + await avSessionManager.activate() + this.controller.start() + } + }) + Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .blockColor(Color.White) + .trackColor($r('app.color.color_60ffffff')) + .onChange((value: number, mode: SliderChangeMode) => { + this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处 + }) + .layoutWeight(1) + Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12) + } + .opacity(0.8) + .width(this.playerSize ? px2vp(this.playerSize.width) : "100%") + .alignRules({ + left: { anchor: 'video', align: HorizontalAlign.Start }, + right: { anchor: 'video', align: HorizontalAlign.End }, + bottom: { anchor: 'video', align: VerticalAlign.Bottom } + }) + + if (this.showWaterMaker && this.uri && !this.isSuccess) { + WaterMarkerView({ + content: this.textContent, + imagePath: this.imagePath, + onRectChange: (rect) => { + this.rect = rect + }, + onClose: () => { + this.showWaterMaker = false + this.textContent = '' + this.imagePath = '' + } + }) + .width(this.playerSize ? px2vp(this.playerSize.width) : '100%') + .height(this.playerSize ? px2vp(this.playerSize.height) : '100%') + .alignRules({ + left: { anchor: 'video', align: HorizontalAlign.Start }, + top: { anchor: 'video', align: VerticalAlign.Top }, + right: { anchor: 'video', align: HorizontalAlign.End }, + bottom: { anchor: 'video', align: VerticalAlign.Bottom } + }) + } + } + .layoutWeight(1) + + Row() { + Column(){ + Image($r('app.media.ic_text_water_marker')).width(50).height(50) + Text('文字').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({ top: 8 }) + } + .onClick(() => { + if (!this.showWaterMaker) { + this.controller.stop() + EditTextDialog.show(this.getUIContext(), {title: '添加水印', hintText: '请输入文字', confirm: (text) => { + this.textContent = text + this.showWaterMaker = true + }}) + } + }) + Column(){ + Image($r('app.media.ic_image_water_marker')).width(50).height(50) + Text('图片').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({ top: 8 }) + } + .margin({ left: 50 }) + .onClick(() => { + if (!this.showWaterMaker) { + this.controller.stop() + PhotoHelper.selectEasy({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE, + maxSelectNumber: 1, + isPhotoTakingSupported: false, + isEditSupported: false, + isOriginalSupported: false + }) + .then((uris) => { + if (uris.length != 0) { + this.isSuccess = false + this.imagePath = uris[0] + this.showWaterMaker = true + } + }) + } + }) + } + .margin({ top: 20 }) + + Row() { + Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')) + .fontSize(17) + .margin({ left: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + this.selectVideo() + } else { + this.getUIContext().getRouter().back() + } + }) + Blank().layoutWeight(1) + Text(this.isSuccess ? '保存' : '确定') + .fontColor($r("app.color.color_466afd")) + .fontSize(17) + .margin({ right: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + SaveUtils.saveImageVideoToAlbumDialog([this.uri!!]) + .then((saved) => { + if (saved) { + this.uri = undefined + this.showDownloadDialog() + } else { + ToastUtils.show('保存失败') + } + }) + .catch((e: BusinessError) => { + ToastUtils.show('保存失败:' + e.message) + }) + + } else { + if (this.showWaterMaker) { + this.addWaterMarker() + } else { + ToastUtils.show('请添加水印') + } + } + }) + } + .margin({ top: 20, bottom: 30 }) + } + .layoutWeight(1) + .visibility(this.uri ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/tools/ClipVideoPage.ets b/entry/src/main/ets/pages/main/home/tools/ClipVideoPage.ets new file mode 100644 index 0000000..b894281 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/tools/ClipVideoPage.ets @@ -0,0 +1,467 @@ +import { PhotoHelper } from '@pura/picker_utils' +import { TitleBar } from '../../../../view/TitleBar' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import { BusinessError, systemDateTime } from '@kit.BasicServicesKit' +import { AppUtil, DisplayUtil, FileUtil } from '@pura/harmony-utils' +import { ToastUtils } from '../../../../utils/ToastUtils' +import { fileIo } from '@kit.CoreFileKit' +import { SaveUtils } from '../../../../utils/SaveUtils' +import { LoadingDialog } from '../../../../dialog/LoadingDialog' +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog' +import { EventConstants } from '../../../../common/EventConstants' +import { RectCropView, RectPosition } from '../../../../view/RectCropView' +import { media } from '@kit.MediaKit' +import { MediaUtils } from '../../../../utils/MediaUtils' +import { MP4Parser } from '@ohos/mp4parser' +import { TipDialog } from '../../../../dialog/TipDialog' +import { avSessionManager } from '../../../../manager/AVSessionManager' + +@Entry +@ComponentV2 +struct ClipVideoPage { + @Local uri?: string + @Local currentTime: number = 0 + @Local durationTime: number = 0 + @Local isPlaying: boolean = false + @Local isSuccess: boolean = false + @Local playerSize: media.PixelMapParams = { width: 0, height: 0 } + @Local currentIndex: number = 0 + + private controller: VideoController = new VideoController() + private rectArray : Array = ['自由', '1:1', '4:3', '3:4', '16:9', '9:16'] + private videoSize: media.PixelMapParams = { width: 0, height: 0 } + private rect: RectPosition = { x: 0, y: 0, width: 0, height: 0 } + + clipVideo() { + LoadingDialog.show(this.getUIContext()) + this.isSuccess = false + let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + if (FileUtil.accessSync(cacheVideoPath)) { + FileUtil.unlinkSync(cacheVideoPath) + } + + let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) + // 复制文件到缓存目录下 + FileUtil.copyFileSync(file.fd, cacheVideoPath) + + let clipWidth: number = 0 + let clipHeight: number = 0 + if (this.currentIndex === 0) { + clipWidth = (vp2px(this.rect.width * this.videoSize.width!!) / this.playerSize.width!!) + clipHeight = (vp2px(this.rect.height * this.videoSize.height!!) / this.playerSize.height!!) + } else { + const ratio = (DisplayUtil.getWidth() - 180) / this.videoSize.width!! + let originPlayerSize: media.PixelMapParams = {width: Math.ceil(this.videoSize.width!! * ratio), height: Math.ceil(this.videoSize.height!! * ratio)} + clipWidth = (this.playerSize.width!! * this.videoSize.width!!) / originPlayerSize.width!! + clipHeight = (this.playerSize.height!! * this.videoSize.height!!) / originPlayerSize.height!! + } + + let clipX: number = 0 + let clipY: number = 0 + if (this.currentIndex === 0) { + clipX = (vp2px(this.rect.x) * this.videoSize.width!!) / this.playerSize.width!! + clipY = (vp2px(this.rect.y) * this.videoSize.width!!) / this.playerSize.height!! + } else { + const ratio = (DisplayUtil.getWidth() - 180) / this.videoSize.width!! + let originPlayerSize: media.PixelMapParams = {width: Math.ceil(this.videoSize.width!! * ratio), height: Math.ceil(this.videoSize.height!! * ratio)} + clipX = clipWidth === originPlayerSize.width ? 0 : (originPlayerSize.width!! - this.playerSize.width!!) / 2 * (this.videoSize.width!!) / originPlayerSize.width!! + clipY = clipHeight === originPlayerSize.height ? 0 : (originPlayerSize.height!! - this.playerSize.height!!) / 2 * (this.videoSize.height!!) / originPlayerSize.height!! + } + + let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + let cmd = `ffmpeg -i ${cacheVideoPath} -vf \"crop=${Math.ceil(clipWidth)}:${Math.ceil(clipHeight)}:${Math.ceil(clipX)}:${Math.ceil(clipY)}\" -c:v h264 -pix_fmt yuv420p -y ${outputPath}` + MP4Parser.ffmpegCmd(cmd, { + callBackResult: (code: number) => { + if (code === 0) { + this.uri = FileUtil.getUriFromPath(outputPath) + MediaUtils.getVideoSize(this.uri) + .then((size) => { + this.videoSize = size + if (size.width && size.height) { + const ratio = (DisplayUtil.getWidth() - 180) / size.width + this.playerSize = {width: Math.ceil(size.width * ratio), height: Math.ceil(size.height * ratio)} + } + }) + this.isSuccess = true + this.isPlaying = false + ToastUtils.show('处理成功') + } else { + ToastUtils.show('处理失败') + } + LoadingDialog.dismiss() + } + }) + } + + setVideoRatio() { + if (this.videoSize.width && this.videoSize.height) { + const ratio = (DisplayUtil.getWidth() - 180) / this.videoSize.width + let originPlayerSize: media.PixelMapParams = {width: Math.ceil(this.videoSize.width * ratio), height: Math.ceil(this.videoSize.height * ratio)} + + if (originPlayerSize.width && originPlayerSize.height) { + switch (this.currentIndex) { + case 0: { + this.playerSize = originPlayerSize + break + } + + case 1: { + if (originPlayerSize.width >= originPlayerSize.height) { + this.playerSize = {width: originPlayerSize.height, height: originPlayerSize.height} + } else { + this.playerSize = {width: originPlayerSize.width, height: originPlayerSize.width} + } + break + } + + case 2: { + if (originPlayerSize.width / originPlayerSize.height >= 4 / 3) { + this.playerSize = {width: Math.ceil((originPlayerSize.height!!) / 3 * 4), height: originPlayerSize.height} + } else { + this.playerSize = {width: originPlayerSize.width, height: Math.ceil((originPlayerSize.width!!) / 4 * 3)} + } + break + } + + case 3: { + if (originPlayerSize.width / originPlayerSize.height <= 3 / 4) { + this.playerSize = {width: originPlayerSize.width, height: Math.ceil((originPlayerSize.width!!) / 3 * 4)} + } else { + this.playerSize = {width: Math.ceil((originPlayerSize.height!!) / 4 * 3), height: originPlayerSize.height} + } + break + } + + case 4: { + if (originPlayerSize.width / originPlayerSize.height >= 16 / 9) { + this.playerSize = {width: Math.ceil((originPlayerSize.height!!) / 9 * 16), height: originPlayerSize.height} + } else { + this.playerSize = {width: originPlayerSize.width, height: Math.ceil((originPlayerSize.width!!) / 16 * 9)} + } + break + } + + case 5: { + if (originPlayerSize.width / originPlayerSize.height <= 9 / 16) { + this.playerSize = {width: originPlayerSize.width, height: Math.ceil((originPlayerSize.width!!) / 9 * 16)} + } else { + this.playerSize = {width: Math.ceil((originPlayerSize.height!!) / 16 * 9), height: originPlayerSize.height} + } + break + } + } + } + } + } + + selectVideo() { + PhotoHelper.selectEasy({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE, + maxSelectNumber: 1, + isPhotoTakingSupported: false, + isEditSupported: false, + isOriginalSupported: false + }) + .then((uris) => { + if (uris.length != 0) { + this.isSuccess = false + this.uri = uris[0] + this.currentIndex = 0 + + MediaUtils.getVideoSize(this.uri) + .then((size) => { + this.videoSize = size + if (size.width && size.height) { + const ratio = (DisplayUtil.getWidth() - 180) / size.width + this.playerSize = {width: Math.ceil(size.width * ratio), height: Math.ceil(size.height * ratio)} + } + }) + } + }) + } + + showDownloadDialog() { + DownloadDialog.show(this.getUIContext(), { + status: DownloadStatus.COMPLETED, + totalSize: 0, + progress: 0, + totalCount: 1, + index: 0, + callback: { + confirm: () => { + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0) + this.getUIContext().getRouter().back() + } + } + }) + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } + + onBackPress(): boolean | void { + if (this.isSuccess) { + TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: { + confirm: () => { + this.getUIContext().getRouter().back() + } + }}) + return true + } + return false + } + + build() { + Column() { + TitleBar({ title: '视频裁剪' }) + + Column() { + Row() { + Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium) + Text('(仅支持mp4格式)').fontColor($r('app.color.color_50ffffff')).fontSize(12) + }.alignSelf(ItemAlign.Start) + + RelativeContainer() { + Stack() { + Image($r('app.media.ic_add_video')).width(44).height(44) + } + .width(140) + .height(140) + .borderRadius(10) + .backgroundColor($r('app.color.color_333333')) + .alignRules({ + start: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + end: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onClick(() => { + this.selectVideo() + }) + } + .height(220) + .margin({ top: 12 }) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + }.margin({ left: 16, top: 16, right: 16 }) + .visibility(this.uri ? Visibility.None : Visibility.Visible) + + Column() { + RelativeContainer() { + Video({ + src: this.uri, // 设置视频源 + controller: this.controller, //设置视频控制器,可以控制视频的播放状态 + posterOptions: { showFirstFrame: true } + }) + .id('video') + .width(this.playerSize ? px2vp(this.playerSize.width) : '100%') + .height(this.playerSize ? px2vp(this.playerSize.height) : '100%') + .backgroundColor($r('app.color.window_background')) + .controls(false) // 设置是否显示默认控制条 + .autoPlay(false) // 设置是否自动播放 + .loop(false) // 设置是否循环播放 + .objectFit(ImageFit.Cover) // 设置视频填充模式 + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onStart(() => { + this.isPlaying = true + }) + .onPause(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onStop(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onFinish(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onError(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onDisAppear(() => { + avSessionManager.deactivate() + }) + + Image($r('app.media.ic_play_video')) + .width(50) + .height(50) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + .onClick(async () => { + await avSessionManager.activate() + this.controller.start() + }) + .alignRules({ + left: { anchor: 'video', align: HorizontalAlign.Start }, + top: { anchor: 'video', align: VerticalAlign.Top }, + right: { anchor: 'video', align: HorizontalAlign.End }, + bottom: { anchor: 'video', align: VerticalAlign.Bottom } + }) + + Row() { + Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play')) + .width(20) + .height(20) + .margin({ right: 20 }) + .onClick(async () => { + if (this.isPlaying) { + this.controller.pause() + } else { + await avSessionManager.activate() + this.controller.start() + } + }) + Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .blockColor(Color.White) + .trackColor($r('app.color.color_60ffffff')) + .onChange((value: number, mode: SliderChangeMode) => { + this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处 + }) + .layoutWeight(1) + Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12) + } + .opacity(0.8) + .width(this.playerSize ? px2vp(this.playerSize.width) : "100%") + .alignRules({ + left: { anchor: 'video', align: HorizontalAlign.Start }, + right: { anchor: 'video', align: HorizontalAlign.End }, + bottom: { anchor: 'video', align: VerticalAlign.Bottom } + }) + + RectCropView({ + onRectChange: (rect) => { + this.rect = rect + } + }) + .width(this.playerSize ? px2vp(this.playerSize.width) : '100%') + .height(this.playerSize ? px2vp(this.playerSize.height) : '100%') + .alignRules({ + left: { anchor: 'video', align: HorizontalAlign.Start }, + top: { anchor: 'video', align: VerticalAlign.Top }, + right: { anchor: 'video', align: HorizontalAlign.End }, + bottom: { anchor: 'video', align: VerticalAlign.Bottom } + }) + .visibility(this.uri && this.currentIndex === 0 && !this.isSuccess ? Visibility.Visible : Visibility.None) + } + .layoutWeight(1) + + Grid() { + ForEach(this.rectArray, (item: string, index) => { + GridItem() { + Text(item).width(50).height(50) + .textAlign(TextAlign.Center) + .fontColor(Color.White) + .borderRadius(4) + .borderWidth(1.5) + .borderColor(this.currentIndex === index ? '#D33952' : Color.Transparent) + .backgroundColor('#282828') + .onClick(() => { + this.currentIndex = index + this.controller.stop() + this.setVideoRatio() + }) + } + .width('100%') + }) + } + .height(50) + .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr') + .layoutDirection(GridDirection.Row) + .visibility(this.uri && !this.isSuccess ? Visibility.Visible : Visibility.None) + + Row() { + Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')) + .fontSize(17) + .margin({ left: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + this.selectVideo() + } else { + this.getUIContext().getRouter().back() + } + }) + Blank().layoutWeight(1) + Text(this.isSuccess ? '保存' : '确定') + .fontColor($r("app.color.color_466afd")) + .fontSize(17) + .margin({ right: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + SaveUtils.saveImageVideoToAlbumDialog([this.uri!!]) + .then((saved) => { + if (saved) { + this.uri = undefined + this.showDownloadDialog() + } else { + ToastUtils.show('保存失败') + } + }) + .catch((e: BusinessError) => { + ToastUtils.show('保存失败:' + e.message) + }) + + } else { + this.clipVideo() + } + }) + } + .margin({ top: 20, bottom: 30 }) + } + .layoutWeight(1) + .visibility(this.uri ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/tools/MD5ResetPage.ets b/entry/src/main/ets/pages/main/home/tools/MD5ResetPage.ets new file mode 100644 index 0000000..b23c078 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/tools/MD5ResetPage.ets @@ -0,0 +1,289 @@ +import { PhotoHelper } from '@pura/picker_utils' +import { TitleBar } from '../../../../view/TitleBar' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import { BusinessError, systemDateTime } from '@kit.BasicServicesKit' +import { AppUtil, FileUtil } from '@pura/harmony-utils' +import { ToastUtils } from '../../../../utils/ToastUtils' +import { fileIo } from '@kit.CoreFileKit' +import { SaveUtils } from '../../../../utils/SaveUtils' +import { LoadingDialog } from '../../../../dialog/LoadingDialog' +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog' +import { EventConstants } from '../../../../common/EventConstants' +import { TipDialog } from '../../../../dialog/TipDialog' +import { avSessionManager } from '../../../../manager/AVSessionManager' + +@Entry +@ComponentV2 +struct MD5ResetPage { + private controller: VideoController = new VideoController() + @Local uri?: string + @Local currentTime: number = 0 + @Local durationTime: number = 0 + @Local isPlaying: boolean = false + @Local isSuccess: boolean = false + + modifyMD5() { + LoadingDialog.show(this.getUIContext()) + this.isSuccess = false + let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + if (FileUtil.accessSync(outputPath)) { + FileUtil.unlinkSync(outputPath) + } + + let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) + // 复制文件到缓存目录下 + FileUtil.copyFileSync(file.fd, outputPath) + + if (FileUtil.accessSync(outputPath)) { + this.uri = FileUtil.getUriFromPath(outputPath) + this.isSuccess = true + this.isPlaying = false + ToastUtils.show('处理成功') + } else { + ToastUtils.show('处理失败') + } + LoadingDialog.dismiss() + } + + selectVideo() { + PhotoHelper.selectEasy({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE, + maxSelectNumber: 1, + isPhotoTakingSupported: false, + isEditSupported: false, + isOriginalSupported: false + }) + .then((uris) => { + if (uris.length != 0) { + this.isSuccess = false + this.uri = uris[0] + } + }) + } + + showDownloadDialog() { + DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: { + confirm: () => { + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0) + this.getUIContext().getRouter().back() + } + } }) + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } + + onBackPress(): boolean | void { + if (this.isSuccess) { + TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: { + confirm: () => { + this.getUIContext().getRouter().back() + } + }}) + return true + } + return false + } + + build() { + Column() { + TitleBar({ title: 'MD5去重' }) + + Column() { + Row() { + Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium) + Text('(仅支持mp4格式)').fontColor($r('app.color.color_50ffffff')).fontSize(12) + }.alignSelf(ItemAlign.Start) + + RelativeContainer() { + Stack() { + Image($r('app.media.ic_add_video')).width(44).height(44) + } + .width(140) + .height(140) + .borderRadius(10) + .backgroundColor($r('app.color.color_333333')) + .alignRules({ + start: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + end: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onClick(() => { + this.selectVideo() + }) + } + .height(220) + .margin({ top: 12 }) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + }.margin({ left: 16, top: 16, right: 16 }) + .visibility(this.uri ? Visibility.None : Visibility.Visible) + + Column() { + RelativeContainer() { + Video({ + src: this.uri, // 设置视频源 + controller: this.controller, //设置视频控制器,可以控制视频的播放状态 + posterOptions: { showFirstFrame: true } + }) + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + .controls(false) // 设置是否显示默认控制条 + .autoPlay(false) // 设置是否自动播放 + .loop(false) // 设置是否循环播放 + .objectFit(ImageFit.Contain) // 设置视频填充模式 + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onStart(() => { + this.isPlaying = true + }) + .onPause(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onStop(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onFinish(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onError(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onDisAppear(() => { + avSessionManager.deactivate() + }) + + Image($r('app.media.ic_play_video')) + .width(50) + .height(50) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + .onClick(async () => { + await avSessionManager.activate() + this.controller.start() + }) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + }) + + Row() { + Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play')) + .width(20) + .height(20) + .margin({ right: 20 }) + .onClick(async () => { + if (this.isPlaying) { + this.controller.pause() + } else { + await avSessionManager.activate() + this.controller.start() + } + }) + Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .blockColor(Color.White) + .trackColor($r('app.color.color_60ffffff')) + .onChange((value: number, mode: SliderChangeMode) => { + this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处 + }) + .layoutWeight(1) + Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12) + } + .opacity(0.8) + .width("100%") + .padding({ left: 30, right: 30 }) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + } + .layoutWeight(1) + + Row() { + Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + this.selectVideo() + } else { + this.getUIContext().getRouter().back() + } + }) + Blank().layoutWeight(1) + Text(this.isSuccess ? '保存' : '确定').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + SaveUtils.saveImageVideoToAlbumDialog([this.uri!!]) + .then((saved) => { + if (saved) { + this.uri = undefined + this.showDownloadDialog() + } else { + ToastUtils.show('保存失败') + } + }) + .catch((e: BusinessError) => { + ToastUtils.show('保存失败:' + e.message) + }) + + } else { + this.modifyMD5() + } + }) + } + .margin({ top: 140, bottom: 30 }) + } + .layoutWeight(1) + .visibility(this.uri ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/tools/RemoveAudioPage.ets b/entry/src/main/ets/pages/main/home/tools/RemoveAudioPage.ets new file mode 100644 index 0000000..74f5344 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/tools/RemoveAudioPage.ets @@ -0,0 +1,295 @@ +import { PhotoHelper } from '@pura/picker_utils' +import { TitleBar } from '../../../../view/TitleBar' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import { BusinessError, systemDateTime } from '@kit.BasicServicesKit' +import { AppUtil, FileUtil } from '@pura/harmony-utils' +import { ToastUtils } from '../../../../utils/ToastUtils' +import { fileIo } from '@kit.CoreFileKit' +import { SaveUtils } from '../../../../utils/SaveUtils' +import { MP4Parser } from '@ohos/mp4parser' +import { LoadingDialog } from '../../../../dialog/LoadingDialog' +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog' +import { EventConstants } from '../../../../common/EventConstants' +import { TipDialog } from '../../../../dialog/TipDialog' +import { avSessionManager } from '../../../../manager/AVSessionManager' + +@Entry +@ComponentV2 +struct RemoveAudioPage { + private controller: VideoController = new VideoController() + @Local uri?: string + @Local currentTime: number = 0 + @Local durationTime: number = 0 + @Local isPlaying: boolean = false + @Local isSuccess: boolean = false + + mirrorVideo() { + LoadingDialog.show(this.getUIContext()) + this.isSuccess = false + let cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp4` + if (FileUtil.accessSync(cachePath)) { + FileUtil.unlinkSync(cachePath) + } + let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) + // 复制文件到缓存目录下 + FileUtil.copyFileSync(file.fd, cachePath) + + let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + let cmd = `ffmpeg -i ${cachePath} -an -c:v copy ${outputPath}` + MP4Parser.ffmpegCmd(cmd, { + callBackResult: (code: number) => { + if (code === 0) { + this.uri = FileUtil.getUriFromPath(outputPath) + this.isSuccess = true + this.isPlaying = false + ToastUtils.show('处理成功') + } else { + ToastUtils.show('处理失败') + } + LoadingDialog.dismiss() + } + }) + } + + selectVideo() { + PhotoHelper.selectEasy({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE, + maxSelectNumber: 1, + isPhotoTakingSupported: false, + isEditSupported: false, + isOriginalSupported: false + }) + .then((uris) => { + if (uris.length != 0) { + this.isSuccess = false + this.uri = uris[0] + } + }) + } + + showDownloadDialog() { + DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: { + confirm: () => { + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0) + this.getUIContext().getRouter().back() + } + } }) + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } + + onBackPress(): boolean | void { + if (this.isSuccess) { + TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: { + confirm: () => { + this.getUIContext().getRouter().back() + } + }}) + return true + } + return false + } + + build() { + Column() { + TitleBar({ title: '去音乐' }) + + Column() { + Row() { + Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium) + Text('(仅支持mp4格式)').fontColor($r('app.color.color_50ffffff')).fontSize(12) + }.alignSelf(ItemAlign.Start) + + RelativeContainer() { + Stack() { + Image($r('app.media.ic_add_video')).width(44).height(44) + } + .width(140) + .height(140) + .borderRadius(10) + .backgroundColor($r('app.color.color_333333')) + .alignRules({ + start: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + end: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onClick(() => { + this.selectVideo() + }) + } + .height(220) + .margin({ top: 12 }) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + }.margin({ left: 16, top: 16, right: 16 }) + .visibility(this.uri ? Visibility.None : Visibility.Visible) + + Column() { + RelativeContainer() { + Video({ + src: this.uri, // 设置视频源 + controller: this.controller, //设置视频控制器,可以控制视频的播放状态 + posterOptions: { showFirstFrame: true } + }) + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + .controls(false) // 设置是否显示默认控制条 + .autoPlay(false) // 设置是否自动播放 + .loop(false) // 设置是否循环播放 + .objectFit(ImageFit.Contain) // 设置视频填充模式 + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onStart(() => { + this.isPlaying = true + }) + .onPause(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onStop(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onFinish(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onError(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onDisAppear(() => { + avSessionManager.deactivate() + }) + + Image($r('app.media.ic_play_video')) + .width(50) + .height(50) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + .onClick(async () => { + await avSessionManager.activate() + this.controller.start() + }) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + }) + + Row() { + Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play')) + .width(20) + .height(20) + .margin({ right: 20 }) + .onClick(async () => { + if (this.isPlaying) { + this.controller.pause() + } else { + await avSessionManager.activate() + this.controller.start() + } + }) + Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .blockColor(Color.White) + .trackColor($r('app.color.color_60ffffff')) + .onChange((value: number, mode: SliderChangeMode) => { + this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处 + }) + .layoutWeight(1) + Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12) + } + .opacity(0.8) + .width("100%") + .padding({ left: 30, right: 30 }) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + } + .layoutWeight(1) + + Row() { + Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + this.selectVideo() + } else { + this.getUIContext().getRouter().back() + } + }) + Blank().layoutWeight(1) + Text(this.isSuccess ? '保存' : '确定').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + SaveUtils.saveImageVideoToAlbumDialog([this.uri!!]) + .then((saved) => { + if (saved) { + this.uri = undefined + this.showDownloadDialog() + } else { + ToastUtils.show('保存失败') + } + }) + .catch((e: BusinessError) => { + ToastUtils.show('保存失败:' + e.message) + }) + + } else { + this.mirrorVideo() + } + }) + } + .margin({ top: 140, bottom: 30 }) + } + .layoutWeight(1) + .visibility(this.uri ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/tools/TakeAudioPage.ets b/entry/src/main/ets/pages/main/home/tools/TakeAudioPage.ets new file mode 100644 index 0000000..a1cab76 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/tools/TakeAudioPage.ets @@ -0,0 +1,287 @@ +import { PhotoHelper } from '@pura/picker_utils' +import { TitleBar } from '../../../../view/TitleBar' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import { BusinessError, systemDateTime } from '@kit.BasicServicesKit' +import { AppUtil, FileUtil } from '@pura/harmony-utils' +import { ToastUtils } from '../../../../utils/ToastUtils' +import { fileIo } from '@kit.CoreFileKit' +import { SaveUtils } from '../../../../utils/SaveUtils' +import { MP4Parser } from '@ohos/mp4parser' +import { LoadingDialog } from '../../../../dialog/LoadingDialog' +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog' +import { EventConstants } from '../../../../common/EventConstants' +import { avSessionManager } from '../../../../manager/AVSessionManager' + +@Entry +@ComponentV2 +struct TakeAudioPage { + private controller: VideoController = new VideoController() + @Local uri?: string + @Local currentTime: number = 0 + @Local durationTime: number = 0 + @Local isPlaying: boolean = false + @Local isSuccess: boolean = false + + takeAudio() { + LoadingDialog.show(this.getUIContext()) + this.isSuccess = false + let cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp4` + if (FileUtil.accessSync(cachePath)) { + FileUtil.unlinkSync(cachePath) + } + let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) + // 复制文件到缓存目录下 + FileUtil.copyFileSync(file.fd, cachePath) + + let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp3` + let cmd = `ffmpeg -i ${cachePath} -vn -c:a mp3 ${outputPath}` + MP4Parser.ffmpegCmd(cmd, { + callBackResult: (code: number) => { + if (code === 0) { + SaveUtils.saveAudioToMusic([outputPath]) + .then(() => { + this.uri = undefined + this.isSuccess = true + this.isPlaying = false + this.showDownloadDialog() + }) + .catch((e: BusinessError) => { + ToastUtils.show('提取失败:' + e.message) + }) + } else { + ToastUtils.show('提取失败') + } + LoadingDialog.dismiss() + } + }) + } + + selectVideo() { + PhotoHelper.selectEasy({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE, + maxSelectNumber: 1, + isPhotoTakingSupported: false, + isEditSupported: false, + isOriginalSupported: false + }) + .then((uris) => { + if (uris.length != 0) { + this.isSuccess = false + this.uri = uris[0] + } + }) + } + + showDownloadDialog() { + DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, isAudio: true, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: { + confirm: () => { + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 2) + this.getUIContext().getRouter().back() + } + } }) + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } + + build() { + Column() { + TitleBar({ title: '视频转音频' }) + + Column() { + Row() { + Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium) + Text('(仅支持mp4格式)').fontColor($r('app.color.color_50ffffff')).fontSize(12) + }.alignSelf(ItemAlign.Start) + + RelativeContainer() { + Stack() { + Stack() { + Image($r('app.media.ic_add_video')).width(44).height(44) + } + .width(140) + .height(140) + .borderRadius(10) + .backgroundColor($r('app.color.color_333333')) + .margin({top: 40}) + .onClick(() => { + this.selectVideo() + }) + .visibility(this.uri ? Visibility.None : Visibility.Visible) + + RelativeContainer() { + Video({ + src: this.uri, // 设置视频源 + controller: this.controller, //设置视频控制器,可以控制视频的播放状态 + posterOptions: { showFirstFrame: true } + }) + .width('100%') + .height('100%') + .borderRadius(12) + .backgroundColor($r('app.color.window_background')) + .controls(false) // 设置是否显示默认控制条 + .autoPlay(false) // 设置是否自动播放 + .loop(false) // 设置是否循环播放 + .objectFit(ImageFit.Contain) // 设置视频填充模式 + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onStart(() => { + this.isPlaying = true + }) + .onPause(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onStop(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onFinish(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onError(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onDisAppear(() => { + avSessionManager.deactivate() + }) + + Image($r('app.media.ic_play_video')) + .width(50) + .height(50) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + .onClick(async () => { + await avSessionManager.activate() + this.controller.start() + }) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + }) + + Row() { + Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play')) + .width(20) + .height(20) + .margin({ right: 20 }) + .onClick(async () => { + if (this.isPlaying) { + this.controller.pause() + } else { + await avSessionManager.activate() + this.controller.start() + } + }) + Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .blockColor(Color.White) + .trackColor($r('app.color.color_60ffffff')) + .onChange((value: number, mode: SliderChangeMode) => { + this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处 + }) + .layoutWeight(1) + Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12) + } + .opacity(0.8) + .width("100%") + .padding({ left: 20, right: 20 }) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + } + .height(350) + .margin({top: 50}) + .visibility(this.uri ? Visibility.Visible : Visibility.None) + } + .alignRules({ + start: { anchor: '__container__', align: HorizontalAlign.Start }, + end: { anchor: '__container__', align: HorizontalAlign.End } + }) + .id('layout_content') + + Image($r('app.media.ic_reupload_video')).width(20).height(20) + .alignRules({ + right: {anchor: '__container__', align: HorizontalAlign.End} + }) + .margin({top: 20}) + .onClick(() => { + this.controller.stop() + this.selectVideo() + }) + .visibility(this.uri ? Visibility.Visible : Visibility.None) + + Button('确认提取', {type: ButtonType.Capsule ,stateEffect:true}) + .width('100%') + .height(42) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(16) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .alignRules({ + top: {anchor: 'layout_content', align: VerticalAlign.Bottom} + }) + .margin({top: 40}) + .onClick(() => { + if (this.uri) { + this.takeAudio() + } else { + ToastUtils.show('请先上传视频') + } + }) + } + .height('auto') + .margin({ top: 12 }) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + .padding({left: 20, right: 20, bottom: 30}) + }.margin({ left: 16, top: 16, right: 16 }) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/tools/VideoMirrorPage.ets b/entry/src/main/ets/pages/main/home/tools/VideoMirrorPage.ets new file mode 100644 index 0000000..f478d14 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/tools/VideoMirrorPage.ets @@ -0,0 +1,321 @@ +import { PhotoHelper } from '@pura/picker_utils' +import { TitleBar } from '../../../../view/TitleBar' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import { BusinessError, systemDateTime } from '@kit.BasicServicesKit' +import { AppUtil, FileUtil } from '@pura/harmony-utils' +import { ToastUtils } from '../../../../utils/ToastUtils' +import { fileIo } from '@kit.CoreFileKit' +import { SaveUtils } from '../../../../utils/SaveUtils' +import { LoadingDialog } from '../../../../dialog/LoadingDialog' +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog' +import { EventConstants } from '../../../../common/EventConstants' +import { MP4Parser } from '@ohos/mp4parser' +import { TipDialog } from '../../../../dialog/TipDialog' +import { avSessionManager } from '../../../../manager/AVSessionManager' + +@Entry +@ComponentV2 +struct VideoMirrorPage { + private controller: VideoController = new VideoController() + @Local uri?: string + @Local currentTime: number = 0 + @Local durationTime: number = 0 + @Local isPlaying: boolean = false + @Local isSuccess: boolean = false + + private orientation: number = 0 + + mirrorVideo() { + LoadingDialog.show(this.getUIContext()) + this.isSuccess = false + let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + if (FileUtil.accessSync(cacheVideoPath)) { + FileUtil.unlinkSync(cacheVideoPath) + } + + let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) + // 复制文件到缓存目录下 + FileUtil.copyFileSync(file.fd, cacheVideoPath) + + let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + let cmd = `ffmpeg -i ${cacheVideoPath} -vf ${this.orientation === 1 ? "hflip" : "vflip"} -c:v h264 -pix_fmt yuv420p -y ${outputPath}` + MP4Parser.ffmpegCmd(cmd, { + callBackResult: (code: number) => { + if (code === 0) { + this.uri = FileUtil.getUriFromPath(outputPath) + this.isSuccess = true + this.isPlaying = false + ToastUtils.show('处理成功') + } else { + ToastUtils.show('处理失败') + } + LoadingDialog.dismiss() + } + }) + } + + selectVideo() { + PhotoHelper.selectEasy({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE, + maxSelectNumber: 1, + isPhotoTakingSupported: false, + isEditSupported: false, + isOriginalSupported: false + }) + .then((uris) => { + if (uris.length != 0) { + this.isSuccess = false + this.uri = uris[0] + } + }) + } + + showDownloadDialog() { + DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: { + confirm: () => { + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0) + this.getUIContext().getRouter().back() + } + } }) + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } + + onBackPress(): boolean | void { + if (this.isSuccess) { + TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: { + confirm: () => { + this.getUIContext().getRouter().back() + } + }}) + return true + } + return false + } + + build() { + Column() { + TitleBar({ title: '视频镜像' }) + + Column() { + Row() { + Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium) + Text('(仅支持mp4格式)').fontColor($r('app.color.color_50ffffff')).fontSize(12) + }.alignSelf(ItemAlign.Start) + + RelativeContainer() { + Stack() { + Image($r('app.media.ic_add_video')).width(44).height(44) + } + .width(140) + .height(140) + .borderRadius(10) + .backgroundColor($r('app.color.color_333333')) + .alignRules({ + start: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + end: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onClick(() => { + this.selectVideo() + }) + } + .height(220) + .margin({ top: 12 }) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + }.margin({ left: 16, top: 16, right: 16 }) + .visibility(this.uri ? Visibility.None : Visibility.Visible) + + Column() { + RelativeContainer() { + Video({ + src: this.uri, // 设置视频源 + controller: this.controller, //设置视频控制器,可以控制视频的播放状态 + posterOptions: { showFirstFrame: true } + }) + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + .controls(false) // 设置是否显示默认控制条 + .autoPlay(false) // 设置是否自动播放 + .loop(false) // 设置是否循环播放 + .objectFit(ImageFit.Contain) // 设置视频填充模式 + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onStart(() => { + this.isPlaying = true + }) + .onPause(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onStop(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onFinish(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onError(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onDisAppear(() => { + avSessionManager.deactivate() + }) + + Image($r('app.media.ic_play_video')) + .width(50) + .height(50) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + .onClick(async () => { + await avSessionManager.activate() + this.controller.start() + }) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + }) + + Row() { + Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play')) + .width(20) + .height(20) + .margin({ right: 20 }) + .onClick(async () => { + if (this.isPlaying) { + this.controller.pause() + } else { + await avSessionManager.activate() + this.controller.start() + } + }) + Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .blockColor(Color.White) + .trackColor($r('app.color.color_60ffffff')) + .onChange((value: number, mode: SliderChangeMode) => { + this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处 + }) + .layoutWeight(1) + Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12) + } + .opacity(0.8) + .width("100%") + .padding({ left: 30, right: 30 }) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + } + .layoutWeight(1) + + Row() { + Column(){ + Image($r('app.media.ic_mirror_h')).width(50).height(50) + Text('水平').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({ top: 8 }) + } + .onClick(() => { + this.orientation = 1 + this.controller.stop() + this.mirrorVideo() + }) + Column(){ + Image($r('app.media.ic_mirror_v')).width(50).height(50) + Text('垂直').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({ top: 8 }) + } + .margin({ left: 50 }) + .onClick(() => { + this.orientation = 2 + this.controller.stop() + this.mirrorVideo() + }) + .visibility(Visibility.None) + } + .margin({ top: 20 }) + + Row() { + Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + this.selectVideo() + } else { + this.getUIContext().getRouter().back() + } + }) + Blank().layoutWeight(1) + Text('保存').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 }) + .onClick(() => { + this.controller.stop() + if (this.orientation !== 0) { + SaveUtils.saveImageVideoToAlbumDialog([this.uri!!]) + .then((saved) => { + if (saved) { + this.uri = undefined + this.showDownloadDialog() + } else { + ToastUtils.show('保存失败') + } + }) + .catch((e: BusinessError) => { + ToastUtils.show('保存失败:' + e.message) + }) + } else { + ToastUtils.show('请选择镜像方向') + } + }) + } + .margin({ top: 20, bottom: 30 }) + } + .layoutWeight(1) + .visibility(this.uri ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/tools/VideoReversePage.ets b/entry/src/main/ets/pages/main/home/tools/VideoReversePage.ets new file mode 100644 index 0000000..e382dd7 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/tools/VideoReversePage.ets @@ -0,0 +1,296 @@ +import { PhotoHelper } from '@pura/picker_utils' +import { TitleBar } from '../../../../view/TitleBar' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import { BusinessError, systemDateTime } from '@kit.BasicServicesKit' +import { AppUtil, FileUtil } from '@pura/harmony-utils' +import { ToastUtils } from '../../../../utils/ToastUtils' +import { fileIo } from '@kit.CoreFileKit' +import { SaveUtils } from '../../../../utils/SaveUtils' +import { LoadingDialog } from '../../../../dialog/LoadingDialog' +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog' +import { EventConstants } from '../../../../common/EventConstants' +import { MP4Parser } from '@ohos/mp4parser' +import { TipDialog } from '../../../../dialog/TipDialog' +import { avSessionManager } from '../../../../manager/AVSessionManager' + +@Entry +@ComponentV2 +struct VideoReversePage { + private controller: VideoController = new VideoController() + @Local uri?: string + @Local currentTime: number = 0 + @Local durationTime: number = 0 + @Local isPlaying: boolean = false + @Local isSuccess: boolean = false + + videoReverse() { + LoadingDialog.show(this.getUIContext()) + this.isSuccess = false + let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + if (FileUtil.accessSync(cacheVideoPath)) { + FileUtil.unlinkSync(cacheVideoPath) + } + + let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) + // 复制文件到缓存目录下 + FileUtil.copyFileSync(file.fd, cacheVideoPath) + + let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4` + let cmd = `ffmpeg -i ${cacheVideoPath} -vf reverse -af areverse -c:v h264 -pix_fmt yuv420p -y ${outputPath}` + MP4Parser.ffmpegCmd(cmd, { + callBackResult: (code: number) => { + if (code === 0) { + this.uri = FileUtil.getUriFromPath(outputPath) + this.isSuccess = true + this.isPlaying = false + ToastUtils.show('处理成功') + } else { + ToastUtils.show('处理失败') + } + LoadingDialog.dismiss() + } + }) + } + + selectVideo() { + PhotoHelper.selectEasy({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE, + maxSelectNumber: 1, + isPhotoTakingSupported: false, + isEditSupported: false, + isOriginalSupported: false + }) + .then((uris) => { + if (uris.length != 0) { + this.isSuccess = false + this.uri = uris[0] + } + }) + } + + showDownloadDialog() { + DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: { + confirm: () => { + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0) + this.getUIContext().getRouter().back() + } + } }) + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } + + onBackPress(): boolean | void { + if (this.isSuccess) { + TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: { + confirm: () => { + this.getUIContext().getRouter().back() + } + }}) + return true + } + return false + } + + build() { + Column() { + TitleBar({ title: '视频倒放' }) + + Column() { + Row() { + Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium) + Text('(仅支持mp4格式)').fontColor($r('app.color.color_50ffffff')).fontSize(12) + }.alignSelf(ItemAlign.Start) + + RelativeContainer() { + Stack() { + Image($r('app.media.ic_add_video')).width(44).height(44) + } + .width(140) + .height(140) + .borderRadius(10) + .backgroundColor($r('app.color.color_333333')) + .alignRules({ + start: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + end: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onClick(() => { + this.selectVideo() + }) + } + .height(220) + .margin({ top: 12 }) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + }.margin({ left: 16, top: 16, right: 16 }) + .visibility(this.uri ? Visibility.None : Visibility.Visible) + + Column() { + RelativeContainer() { + Video({ + src: this.uri, // 设置视频源 + controller: this.controller, //设置视频控制器,可以控制视频的播放状态 + posterOptions: { showFirstFrame: true } + }) + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + .controls(false) // 设置是否显示默认控制条 + .autoPlay(false) // 设置是否自动播放 + .loop(false) // 设置是否循环播放 + .objectFit(ImageFit.Contain) // 设置视频填充模式 + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onStart(() => { + this.isPlaying = true + }) + .onPause(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onStop(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onFinish(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onError(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onDisAppear(() => { + avSessionManager.deactivate() + }) + + Image($r('app.media.ic_play_video')) + .width(50) + .height(50) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + .onClick(async () => { + await avSessionManager.activate() + this.controller.start() + }) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + }) + + Row() { + Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play')) + .width(20) + .height(20) + .margin({ right: 20 }) + .onClick(async () => { + if (this.isPlaying) { + this.controller.pause() + } else { + await avSessionManager.activate() + this.controller.start() + } + }) + Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .blockColor(Color.White) + .trackColor($r('app.color.color_60ffffff')) + .onChange((value: number, mode: SliderChangeMode) => { + this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处 + }) + .layoutWeight(1) + Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12) + } + .opacity(0.8) + .width("100%") + .padding({ left: 30, right: 30 }) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + } + .layoutWeight(1) + + Row() { + Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + this.selectVideo() + } else { + this.getUIContext().getRouter().back() + } + }) + Blank().layoutWeight(1) + Text(this.isSuccess ? '保存' : '确定').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 }) + .onClick(() => { + this.controller.stop() + if (this.isSuccess) { + SaveUtils.saveImageVideoToAlbumDialog([this.uri!!]) + .then((saved) => { + if (saved) { + this.uri = undefined + this.showDownloadDialog() + } else { + ToastUtils.show('保存失败') + } + }) + .catch((e: BusinessError) => { + ToastUtils.show('保存失败:' + e.message) + }) + + } else { + this.videoReverse() + } + }) + } + .margin({ top: 140, bottom: 30 }) + } + .layoutWeight(1) + .visibility(this.uri ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/wx/WxVideoPage.ets b/entry/src/main/ets/pages/main/home/wx/WxVideoPage.ets new file mode 100644 index 0000000..9a3c9fe --- /dev/null +++ b/entry/src/main/ets/pages/main/home/wx/WxVideoPage.ets @@ -0,0 +1,633 @@ +import { AppUtil, FileUtil, ObjectUtil, + PasteboardUtil, + PermissionUtil, RandomUtil, StrUtil } from '@pura/harmony-utils'; +import { EventConstants } from '../../../../common/EventConstants'; +import { RouterUrls } from '../../../../common/RouterUrls'; +import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'; +import { ImageMaterial, MaterialInfoEntity, MediaEntity, VideoMaterial } from '../../../../entity/MaterialInfoEntity'; +import { VipPermissionEntity } from '../../../../entity/VipPermissionEntity'; +import { LoginManager } from '../../../../manager/LoginGlobalManager'; +import { ShareManager } from '../../../../manager/ShareManager'; +import { ConfigManager } from '../../../../manager/UserConfigManager'; +import { MediaDownloader } from '../../../../net/MediaDownloader'; +import { SaveUtils } from '../../../../utils/SaveUtils'; +import { ToastUtils } from '../../../../utils/ToastUtils'; +import { TitleBar } from '../../../../view/TitleBar'; +import { WxVideoViewModel } from '../../../../viewModel/WxVideoViewModel'; +import { WxVideoMaterialPage } from './material/WxVideoMaterialPage'; +import { LevelMode, router } from '@kit.ArkUI'; +import { WxVideoEntity } from '../../../../entity/WxVideoEntity'; +import { WxImageMaterialPage } from './material/WxImageMaterialPage'; +import { OnWXResp, WXApi, WXEventHandler } from '../../../../utils/wechat/WXApiEventHandlerImpl'; +import * as WxOpenSdk from '@tencent/wechat_open_sdk'; +import { ErrCode, SendAuthResp } from '@tencent/wechat_open_sdk'; +import { LoadingDialog } from '../../../../dialog/LoadingDialog'; +import BuildProfile from 'BuildProfile'; +import { WxServiceEntity } from '../../../../entity/WxServiceEntity'; +import { Constants } from '../../../../common/Constants'; +import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager'; +import { TipDialog } from '../../../../dialog/TipDialog'; +import { SimpleTipDialog } from '../../../../dialog/SimpleTipDialog'; +import { JoinWxGroupCourseDialog } from '../../../../dialog/JoinWxGroupCourseDialog'; +import { PrefUtils } from '../../../../utils/PrefUtils'; + +@Entry +@ComponentV2 +struct WxVideoPage { + @Local isPlayback: boolean = false + @Local currentIndex: number = 0; + @Local isRefreshing: boolean = false; + + @Local videoList: Array = []; + @Local imageList: Array = []; + + @Local videoRowCount: number = 1; + @Local imageRowCount: number = 1; + + viewModel: WxVideoViewModel = new WxVideoViewModel(this.getUIContext()); + tabController: TabsController = new TabsController(); + titles: Array = ['视频', '图片']; + type: number = 0 + + mediaDownloader?: MediaDownloader | null + selectedList: Array = [] + cacheFileUris: Array = [] + downloadIndex: number = 0 + totalSize = 0 + downloadStatus = DownloadStatus.DOWNLOADING + + joinGroupDialogController?: CustomDialogController | null; + + //从微信返回的回调 + onWXResp: OnWXResp = (resp) => { + //微信返回的数据 + if (resp instanceof SendAuthResp && resp.state?.endsWith('video')) { + const authResult = JSON.stringify(resp ?? {}, null , 2); + const errCode = JSON.parse(authResult).errCode as number; + if (errCode === ErrCode.ERR_OK) { + const authCode = JSON.parse(authResult).code as string; + if (ConfigManager.getPlaybackJoinType() === "img") { + this.showJoinGroupCourseDialog() + } else { + this.viewModel.bindWxUserinfo(authCode) + } + } else { + ToastUtils.show(JSON.parse(authResult).errStr); + } + } + } + + @Monitor('viewModel.wxVideo') + onMaterialInfoChange(monitor: IMonitor) { + const entity = monitor.value()?.now as WxVideoEntity + this.createVideoList(entity.items); + this.createImageList(entity.items); + if (this.videoList.length > 0) { + this.tabController.changeIndex(0); + this.currentIndex = 0 + } else if (this.imageList.length > 0) { + this.tabController.changeIndex(1); + this.currentIndex = 1 + } + this.isRefreshing = false + } + + @Monitor('viewModel.permissionInfo') + onPermissionInfoChange(monitor: IMonitor) { + const info = monitor.value()?.now as VipPermissionEntity; + if (info.auth) { + if (!LoginManager.isLogin()) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.LOGIN_PAGE, params: {from: 1}}, router.RouterMode.Single) + return; + } + this.shareOrDownload() + } else { + if (!info.auth_ad) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIP_PAGE, params: {origin: 'download_wechat_video'}}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_MEMBER_RECHARGE, 'download_wechat_video') + return; + } + } + } + + @Monitor('viewModel.errorCode') + onErrorCodeChange(monitor: IMonitor) { + const errorCode = monitor.value()?.now as number; + if (errorCode === 12002 || errorCode === 12003 || errorCode === 12004) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.RECHARGE_DIAMOND_PAGE}); + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_RECHARGE_DIAMOND, 'download_wechat_video') + ToastUtils.show('钻石已用完') + } + } + + @Monitor('viewModel.deleteVideo') + onDeleteVideo(monitor: IMonitor) { + this.isRefreshing = true + ToastUtils.show('删除成功') + } + + @Monitor('viewModel.wxService') + onWxServiceChange(monitor: IMonitor) { + const info = monitor.value()?.now as WxServiceEntity; + if (StrUtil.isEmpty(info.corpid) || StrUtil.isEmpty(info.address)) { + ToastUtils.show('获取客服信息错误') + } else { + ConfigManager.saveBindWxVideoHelper(true) + this.contactWxService(info) + } + } + + @Monitor('viewModel.wxUserinfo') + onWxUserinfoChange(monitor: IMonitor) { + if (this.isPlayback) { + ConfigManager.saveBindWxPlaybackHelper(true) + } else { + ConfigManager.saveBindWxVideoHelper(true) + } + this.jumpToMiniProgram() + } + + aboutToAppear(): void { + WXEventHandler.registerOnWXRespCallback(this.onWXResp) + this.initParams() + this.checkBindStatus() + } + + aboutToDisappear() { + WXEventHandler.unregisterOnWXRespCallback(this.onWXResp) + } + + onPageShow(): void { + this.isRefreshing = true + } + + initParams() { + const params = this.getUIContext().getRouter().getParams() as Record; + if (params) { + this.isPlayback = params.isPlayback as boolean + } + } + + showSaveTip() { + if (PrefUtils.getBoolean('show_save_tip', true)) { + SimpleTipDialog.show(this.getUIContext(), {title: '重要提示', content: '下载完成后需要您点击弹窗允许保存之后才能保存文件到相册', callback: { + confirm: () => { + this.viewModel.checkVip(); + PrefUtils.put('show_save_tip', false) + } + }}) + } else { + this.viewModel.checkVip() + } + } + + shareOrDownload() { + this.cacheFileUris.length = 0 + if (this.type == 0) { + if (this.selectedList.length > 0) { + this.shareMedia(this.selectedList[0]) + } + } else { + if (this.selectedList.length > 0) { + this.downloadIndex = 0 + this.downloadMedia(this.selectedList[this.downloadIndex]) + } + } + } + + shareMedia(media: MediaEntity) { + let filePath = FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName() + if (FileUtil.accessSync(filePath) && FileUtil.isFile(filePath) && !(media.totalSize === 0 || media.currentLen !== media.totalSize)) { + this.shareFile(media) + } else { + this.showDownloadDialog(media instanceof VideoMaterial && media.isMerge ? DownloadStatus.VIDEO_DOWNLOADING : DownloadStatus.DOWNLOADING) + this.download(media, true) + } + } + + async downloadMedia(media: MediaEntity) { + let filePath = FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName() + if (FileUtil.accessSync(filePath) && FileUtil.isFile(filePath) && !(media.totalSize === 0 || media.currentLen !== media.totalSize)) { + this.cacheFileUris.push(FileUtil.getUriFromPath(filePath)) + if (this.downloadIndex < this.selectedList.length - 1) { + this.downloadIndex++ + this.downloadMedia(this.selectedList[this.downloadIndex]) + } else { + let saved = await this.saveFile() + if (saved) { + if (this.downloadIndex < this.selectedList.length - 1) { + this.downloadIndex++ + this.downloadMedia(this.selectedList[this.downloadIndex]) + } else { + this.updateDownloadDialog(DownloadStatus.COMPLETED, this.totalSize, this.totalSize) + } + } else { + ToastUtils.show('保存失败') + this.dismissDownloadDialog() + } + this.isRefreshing = true + } + } else { + this.showDownloadDialog(media instanceof VideoMaterial && media.isMerge ? DownloadStatus.VIDEO_DOWNLOADING : DownloadStatus.DOWNLOADING) + this.download(media) + } + } + + async download(media: MediaEntity, isShare: boolean = false) { + this.viewModel.reportStatus(media.logid, '1') + media.initFileName() + this.mediaDownloader = MediaDownloader.getInstance() + this.mediaDownloader + .callback({ + onSuccess: async (path) => { + if (isShare) { + this.shareFile(media) + } else { + this.cacheFileUris.push(FileUtil.getUriFromPath(path)) + if (this.downloadIndex < this.selectedList.length - 1) { + this.downloadIndex++ + this.downloadMedia(this.selectedList[this.downloadIndex]) + } else { + let saved = await this.saveFile() + if (saved) { + if (this.downloadIndex < this.selectedList.length - 1) { + this.downloadIndex++ + this.downloadMedia(this.selectedList[this.downloadIndex]) + } else { + this.updateDownloadDialog(DownloadStatus.COMPLETED, this.totalSize, this.totalSize) + } + } else { + ToastUtils.show('保存失败') + this.dismissDownloadDialog() + } + this.isRefreshing = true + } + } + }, + onGetTotal: (total) => { + this.totalSize = total + this.updateDownloadDialog(this.downloadStatus, total) + }, + onProgress: (progress) => { + this.updateDownloadDialog(this.downloadStatus, this.totalSize, progress) + }, + onMerge: (step) => { + if (step === 4) { + this.updateDownloadDialog(DownloadStatus.AUDIO_DOWNLOADING) + } else { + this.updateDownloadDialog(DownloadStatus.PROCESSING) + } + }, + onPause:() => { + this.dismissDownloadDialog() + }, + onCancel: () => { + this.dismissDownloadDialog() + }, + onFailed: (msg) => { + ToastUtils.show('下载失败:' + msg) + this.dismissDownloadDialog() + this.viewModel.reportStatus(media.logid, '-1', `${media.totalSize}`, msg) + this.reportErrorEvent(media, msg) + } + }) + .down(media); + } + + shareFile(media: MediaEntity) { + ShareManager.shareFile(FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName()) + this.viewModel.reportStatus(media.logid, '2', `${media.totalSize}`) + this.dismissDownloadDialog() + } + + async saveFile(): Promise { + let saved = await SaveUtils.saveImageVideoToAlbumDialog(this.cacheFileUris) + if (saved) { + this.cacheFileUris.forEach((uri, index) => { + let stat = FileUtil.statSync(FileUtil.getFilePath(uri)) + this.viewModel.reportStatus(this.selectedList[index].logid, '2', `${stat.size}`) + }) + } + return Promise.resolve(saved) + } + + showDownloadDialog(status: DownloadStatus = DownloadStatus.DOWNLOADING) { + this.downloadStatus = status + DownloadDialog.show(this.getUIContext(), { status: status, totalSize: 0, progress: 0, totalCount: this.selectedList.length, index: this.downloadIndex, callback: { + confirm: () => { + if (this.downloadStatus === DownloadStatus.COMPLETED) { + EventReportGlobalManager.eventReport(EventConstants.DIALOG_GO_TO_VIEW, this.titles[this.currentIndex]) + AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, this.currentIndex) + this.getUIContext().getRouter().back() + } else { + //todo 后台下载 + } + }, + cancel: () => { + if (this.downloadStatus !== DownloadStatus.COMPLETED) { + if (this.mediaDownloader) { + this.mediaDownloader.cancel() + EventReportGlobalManager.eventReport(EventConstants.CANCEL_DOWNLOAD_VIDEO, this.selectedList[this.downloadIndex].url) + } + } else { + EventReportGlobalManager.eventReport(EventConstants.DIALOG_CONFIRM_SAVE_FILE, this.titles[this.currentIndex]) + } + } + } }) + } + + updateDownloadDialog(status: DownloadStatus = DownloadStatus.DOWNLOADING, totalSize: number = 0, progress: number = 0, isAudio: boolean = false) { + this.downloadStatus = status + DownloadDialog.update({ status: status, totalSize: totalSize, progress: progress, totalCount: this.selectedList.length, index: this.downloadIndex, isAudio: isAudio }) + } + + dismissDownloadDialog() { + this.downloadStatus = DownloadStatus.DOWNLOADING + this.totalSize = 0 + this.downloadIndex = 0 + this.mediaDownloader = null + DownloadDialog.dismiss() + } + + createVideoList(materials?: Array) { + const list = new Array(); + materials?.forEach((info) => { + info.material?.video?.forEach((video, index) => { + video.logid = info.logid + video.isThreading = info.material!!.threading + if (info.material?.image && info.material.image.length > index) { + video.thumb = info.material.image[index].url + } + video.flag = RandomUtil.getRandomNumber(0, 1000) + list.push(video) + }) + }) + this.videoList = list.map(item => ObjectUtil.assign(new VideoMaterial(), item) as VideoMaterial); + if (this.videoList.length > 0) { + this.videoList[0].isChecked = true + } + this.videoRowCount = this.computeRowCount(this.videoList); + } + + createImageList(materials?: Array) { + const list = new Array(); + materials?.forEach((info) => { + info.material?.image?.forEach((image) => { + image.logid = info.logid + image.flag = RandomUtil.getRandomNumber(0, 1000) + list.push(image) + }) + }) + this.imageList = list.map(item => ObjectUtil.assign(new ImageMaterial(), item) as ImageMaterial); + if (this.imageList.length > 0) { + this.imageList[0].isChecked = true + } + this.imageRowCount = this.computeRowCount(this.imageList); + } + + computeRowCount(list: Array): number { + if (list.length > 8) { + return 3; + } else if (list.length >= 3 && list.length <= 8) { + return 2; + } else { + return 1; + } + } + + checkBindStatus() { + if (this.isPlayback && !ConfigManager.isBindWxPlaybackHelper() || !this.isPlayback && !ConfigManager.isBindWxVideoHelper()) { + if (LoginManager.getLastUserInfo()?.user_id !== LoginManager.getUserInfo()?.user_id) { + TipDialog.show(this.getUIContext(), {title: '提示', content: '系统检测到您更换了账号,请重新添加助手', callback: { + confirm: () => { + if (this.isPlayback || !ConfigManager.isWxVideoServiceEnable()) { + if (ConfigManager.getPlaybackJoinType() === "img") { + this.showJoinGroupCourseDialog() + } else { + this.wxAuth() + } + } else { + this.getWxServiceInfo() + } + } + }}) + } + } + } + + showJoinGroupCourseDialog() { + this.joinGroupDialogController = new CustomDialogController({ + builder: JoinWxGroupCourseDialog({ isPlayback: this.isPlayback }), + width: '100%', + cornerRadius: 20, + autoCancel: true, + maskColor: '#CC000000', + levelMode: LevelMode.EMBEDDED, + backgroundBlurStyle: BlurStyle.NONE, + alignment: DialogAlignment.Bottom + }) + this.joinGroupDialogController.open(); + } + + async wxAuth() { + if (!WXApi.isWXAppInstalled()) { + ToastUtils.show('未安装微信客户端,请先下载安装微信客户端'); + return; + } + LoadingDialog.show(this.getUIContext()); + let req = new WxOpenSdk.SendAuthReq; + req.isOption1 = false; + req.nonAutomatic = true; + req.scope = 'snsapi_userinfo'; + req.state = BuildProfile.BUNDLE_NAME + RandomUtil.getRandomInt(0, 1000) + '_video'; + req.transaction =''; + + await WXApi.sendReq(AppUtil.getContext(), req) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_SHARE_WX_PLAYBACK, '前往微信分享直播回放') + LoadingDialog.dismiss(); + } + + async jumpToMiniProgram() { + //保存成功跳转直播回放加群小程序 + try { + let wxVideoConfig = ConfigManager.getWxVideoConfig() + if (wxVideoConfig != null && LoginManager.getUserInfo()) { + PasteboardUtil.setDataText(LoginManager.getUserInfo()!!.user_id) + + wxVideoConfig.id = LoginManager.getUserInfo()!!.user_id + let params = encodeURI(JSON.stringify(wxVideoConfig)).replace('+', '%20') + let launchMiniProgramReq = new WxOpenSdk.LaunchMiniProgramReq() + launchMiniProgramReq.userName = Constants.MINI_PROGRAM_APP_ID //拉起的小程序的原始id + launchMiniProgramReq.path = `pages/index/index?path=share¶m=${params}` + launchMiniProgramReq.miniprogramType = 0 //拉起小程序的类型 0-正式版 1-开发版 2-体验版 + await WXApi.sendReq(AppUtil.getContext(), launchMiniProgramReq) + } else { + ToastUtils.show('获取配置失败,请联系客服') + } + } catch (e) { + ToastUtils.show('跳转失败,请联系客服') + } + } + + getWxServiceInfo() { + if (!WXApi.isWXAppInstalled()) { + ToastUtils.show('未安装微信客户端,请先下载安装微信客户端'); + return; + } + this.viewModel.wxServiceInfo() + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_SHARE_WX_VIDEO, "前往微信分享视频号", '') + } + + /** + * 跳转客服 + * @param service + */ + contactWxService(service: WxServiceEntity) { + let req = new WxOpenSdk.OpenCustomerServiceChatReq() + req.corpId = service.corpid; // 企业ID + req.url = service.address; // 客服URL + WXApi.sendReq(AppUtil.getContext(), req) + } + + reportErrorEvent(media: MediaEntity, message: string) { + if (media instanceof VideoMaterial) { + EventReportGlobalManager.eventReport(EventConstants.ERROR_CLIENT_DOWNLOAD_VIDEO, media.url, message) + } else if (media instanceof ImageMaterial) { + EventReportGlobalManager.eventReport(EventConstants.ERROR_CLIENT_DOWNLOAD_IMG, media.url, message) + } + } + + build() { + RelativeContainer() { + Column() { + TitleBar({ title: this.isPlayback ? '直播回放' : '视频号' }) + + Stack({ alignContent: Alignment.TopStart }) { + Refresh({refreshing: this.isRefreshing}) { + Tabs({ barPosition: BarPosition.Start, controller: this.tabController }) { + TabContent() { + WxVideoMaterialPage({mediaList: this.videoList, rowCount: this.videoRowCount, isPlayback: this.isPlayback, + onShare:(video) => { + this.type = 0 + this.selectedList.length = 0 + this.selectedList.push(video) + this.viewModel.checkVip(); + }, + onSave: (list) => { + this.type = 1 + this.selectedList = list + this.showSaveTip(); + }, + onItemDelete: (video) => { + TipDialog.show(this.getUIContext(), {title: '提示', content: this.isPlayback ? '确定删除该直播回放?' : '确定删除该视频号?', callback: { + confirm: () => { + this.viewModel.deleteWxVideo(video.logid) + } + }}) + } + }) + } + + TabContent() { + WxImageMaterialPage({mediaList: this.imageList, rowCount: this.imageRowCount, isPlayback: this.isPlayback, + onShare:(image) => { + this.type = 0 + this.selectedList.length = 0 + this.selectedList.push(image) + this.viewModel.checkVip(); + }, + onSave: (list) => { + this.type = 1 + this.selectedList = list + this.showSaveTip(); + }, + onItemDelete: (image) => { + TipDialog.show(this.getUIContext(), {title: '提示', content: this.isPlayback ? '确定删除该直播回放?' : '确定删除该视频号?', callback: { + confirm: () => { + this.viewModel.deleteWxVideo(image.logid) + } + }}) + } + }) + } + } + .scrollable(false) + /*.onSelected((index: number) => { + this.currentIndex = index; + })*/ + } + .onRefreshing(() => { + this.isRefreshing = true + this.viewModel.videoList(this.isPlayback ? 'playback' : 'wechatvideo') + }) + .refreshOffset(50) + .pullToRefresh(true) + + Row({ space: 40 }) { + ForEach(this.titles, (title: string, index) => { + this.tab(title, index); + }) + Blank().layoutWeight(1) + Row() { + Image($r('app.media.ic_wx_video_course')).width(16).height(16) + Text('视频教程').fontColor($r('app.color.color_80ffffff')).fontSize(12).margin({ left: 4 }) + } + .onClick(() => { + if (this.isPlayback) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getPlaybackCourse(), title: '视频教程'}}) + } else { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getWxVideoCourse(), title: '视频教程'}}) + } + }) + }.padding({ left: 16, right: 16 }) + .margin({ top: 10 }) + }.layoutWeight(1) + } + + Row() { + Image(this.isPlayback ? $r('app.media.ic_playback_helper') : $r('app.media.ic_video_helper')).width(26).height(26) + Text(this.isPlayback ? '添加直播\n回放助手' : '添加助手').margin({left: 6}).fontColor(Color.White).fontSize(12) + Image($r('app.media.ic_arrow_dp16')).width(16).height(16).margin({left: 2}) + } + .padding(8) + .backgroundColor($r('app.color.color_333333')) + .borderRadius({topLeft: 10, bottomLeft: 10}) + .alignRules({ + right: {anchor:'__container__', align: HorizontalAlign.End}, + bottom: {anchor:'__container__', align: VerticalAlign.Bottom}, + }) + .margin({bottom: 100}) + .onClick(() => { + if (this.isPlayback || !ConfigManager.isWxVideoServiceEnable()) { + if (ConfigManager.getPlaybackJoinType() === "img") { + this.showJoinGroupCourseDialog() + } else { + this.wxAuth() + } + } else { + this.getWxServiceInfo() + } + }) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } + + @Builder + tab(tabName: string, tabIndex: number) { + Row() { + Text(tabName) + .fontSize(this.currentIndex === tabIndex ? 17 : 14) + .fontWeight(this.currentIndex === tabIndex ? FontWeight.Medium : FontWeight.Regular) + .lineHeight(24) + .fontColor(tabIndex === this.currentIndex ? $r("app.color.color_466afd") : $r('app.color.color_50ffffff')) + } + .width('auto') + .height('auto') + .onClick(() => { + this.tabController.changeIndex(tabIndex); + this.currentIndex = tabIndex; + }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/wx/material/WxImageMaterialPage.ets b/entry/src/main/ets/pages/main/home/wx/material/WxImageMaterialPage.ets new file mode 100644 index 0000000..ac1fad9 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/wx/material/WxImageMaterialPage.ets @@ -0,0 +1,146 @@ +import { RouterUrls } from '../../../../../common/RouterUrls'; +import { ImageMaterial } from '../../../../../entity/MaterialInfoEntity'; +import { ConfigManager } from '../../../../../manager/UserConfigManager'; +import { ToastUtils } from '../../../../../utils/ToastUtils'; +import { ImageMaterialItemView } from '../../../../../view/MaterialItemView'; + +@ComponentV2 +export struct WxImageMaterialPage { + @Param isPlayback: boolean = false + @Param mediaList: Array = []; + @Param rowCount: number = 1; + @Param onShare?: (video: ImageMaterial) => void = undefined + @Param onSave?: (list: Array) => void = undefined + @Param onItemDelete?: (video: ImageMaterial) => void = undefined + @Local isCheckAll: boolean = false + + selectedItems(): Array { + const list = new Array(); + this.mediaList.forEach((item) => { + if (item.isChecked) { + list.push(item); + } + }) + return list; + } + + build() { + Stack() { + Column() { + Grid() { + ForEach(this.mediaList, (item: ImageMaterial, index) => { + GridItem() { + ImageMaterialItemView({ media: item, rowCount: this.rowCount, isWxVideo: true, + onDelete: (image) => { + if (this.onItemDelete) { + this.onItemDelete(image) + } + } + }) + } + .onClick(() => { + item.isChecked = !item.isChecked; + this.isCheckAll = this.mediaList.every(item => item.isChecked) + }) + }) + } + .scrollBar(BarState.Off) + .columnsTemplate(this.rowCount === 1 ? '1fr' : this.rowCount === 2 ? '1fr 1fr' : '1fr 1fr 1fr') + .rowsGap(10) + .columnsGap(10) + .margin({ left: 16, right: 16, bottom: 15 }) + .layoutWeight(1) + + Row() { + Row() { + Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') : + $r('app.media.ic_check_false')).width(18).height(18) + Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 }) + } + .onClick(() => { + this.isCheckAll = !this.isCheckAll; + this.mediaList.forEach((item) => { + item.isChecked = this.isCheckAll; + }) + }) + + Blank().layoutWeight(1) + + Button('转发', { type: ButtonType.Capsule, stateEffect: true }) + .width(122) + .height(40) + .backgroundColor($r('app.color.color_333333')) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(15) + .onClick(() => { + if (this.onShare) { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要转发的图片'); + } else if (list.length > 1) { + ToastUtils.show('一次只能转发一张图片'); + } else { + this.onShare(list[0]); + } + } + }) + + Button('保存', { type: ButtonType.Capsule, stateEffect: true }) + .width(122) + .height(40) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .margin({ left: 12 }) + .onClick(() => { + if (this.onSave) { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要保存的图片'); + } else { + this.onSave(list); + } + } + }) + } + .backgroundColor($r('app.color.color_222222')) + .padding({ + left: 16, + top: 10, + right: 16, + bottom: 30 + }) + .visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None) + } + + Column() { + Stack() { + Image($r('app.media.ic_course_thumb')).width(240).height(240) + Image($r('app.media.ic_play_video')).width(50).height(50) + }.width('100%').height(290).backgroundColor(Color.Black) + .onClick(() => { + if (this.isPlayback) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getPlaybackCourse(), title: '视频教程'}}) + } else { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getWxVideoCourse(), title: '视频教程'}}) + } + }) + + Column() { + Text('操作步骤:').fontColor($r('app.color.color_90ffffff')).fontSize(14) + Text(this.isPlayback ? $r('app.string.wx_playback_course') : $r('app.string.wx_video_course')) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(12) + .lineHeight(20) + .margin({ top: 9 }) + }.alignItems(HorizontalAlign.Start).width(245).layoutWeight(1).margin({ top: 37 }) + } + .visibility(this.mediaList.length > 0 ? Visibility.None : Visibility.Visible) + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/home/wx/material/WxVideoMaterialPage.ets b/entry/src/main/ets/pages/main/home/wx/material/WxVideoMaterialPage.ets new file mode 100644 index 0000000..8cf88c3 --- /dev/null +++ b/entry/src/main/ets/pages/main/home/wx/material/WxVideoMaterialPage.ets @@ -0,0 +1,145 @@ +import { RouterUrls } from '../../../../../common/RouterUrls'; +import { VideoMaterial } from '../../../../../entity/MaterialInfoEntity'; +import { ConfigManager } from '../../../../../manager/UserConfigManager'; +import { ToastUtils } from '../../../../../utils/ToastUtils'; +import { VideoMaterialItemView } from '../../../../../view/MaterialItemView'; + +@ComponentV2 +export struct WxVideoMaterialPage { + @Param isPlayback: boolean = false + @Param mediaList: Array = []; + @Param rowCount: number = 1; + @Param onShare?: (video: VideoMaterial) => void = undefined + @Param onSave?: (list: Array) => void = undefined + @Param onItemDelete?: (video: VideoMaterial) => void = undefined + @Local isCheckAll: boolean = false + + selectedItems(): Array { + const list = new Array(); + this.mediaList.forEach((item) => { + if (item.isChecked) { + list.push(item); + } + }) + return list; + } + + build() { + Stack() { + Column() { + Grid() { + ForEach(this.mediaList, (item: VideoMaterial, index) => { + GridItem() { + VideoMaterialItemView({ media: item, rowCount: this.rowCount, isWxVideo: true, onDelete:(video) => { + if (this.onItemDelete) { + this.onItemDelete(video) + } + } + }) + } + .onClick(() => { + item.isChecked = !item.isChecked; + this.isCheckAll = this.mediaList.every(item => item.isChecked) + }) + }) + } + .scrollBar(BarState.Off) + .columnsTemplate(this.rowCount === 1 ? '1fr' : this.rowCount === 2 ? '1fr 1fr' : '1fr 1fr 1fr') + .rowsGap(10) + .columnsGap(10) + .margin({ left: 16, right: 16, bottom: 15 }) + .layoutWeight(1) + + Row() { + Row() { + Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') : + $r('app.media.ic_check_false')).width(18).height(18) + Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 }) + } + .onClick(() => { + this.isCheckAll = !this.isCheckAll; + this.mediaList.forEach((item) => { + item.isChecked = this.isCheckAll; + }) + }) + + Blank().layoutWeight(1) + + Button('转发', { type: ButtonType.Capsule, stateEffect: true }) + .width(122) + .height(40) + .backgroundColor($r('app.color.color_333333')) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(15) + .onClick(() => { + if (this.onShare) { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要转发的视频'); + } else if (list.length > 1) { + ToastUtils.show('一次只能转发一个视频'); + } else { + this.onShare(list[0]); + } + } + }) + + Button('保存', { type: ButtonType.Capsule, stateEffect: true }) + .width(122) + .height(40) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .margin({ left: 12 }) + .onClick(() => { + if (this.onSave) { + const list = this.selectedItems(); + if (list.length === 0) { + ToastUtils.show('请选择要保存的视频'); + } else { + this.onSave(list); + } + } + }) + } + .backgroundColor($r('app.color.color_222222')) + .padding({ + left: 16, + top: 10, + right: 16, + bottom: 30 + }) + .visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None) + } + + Column() { + Stack() { + Image($r('app.media.ic_course_thumb')).width(240).height(240) + Image($r('app.media.ic_play_video')).width(50).height(50) + }.width('100%').height(290).backgroundColor(Color.Black) + .onClick(() => { + if (this.isPlayback) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getPlaybackCourse(), title: '视频教程'}}) + } else { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getWxVideoCourse(), title: '视频教程'}}) + } + }) + + Column() { + Text('操作步骤:').fontColor($r('app.color.color_90ffffff')).fontSize(14) + Text(this.isPlayback ? $r('app.string.wx_playback_course') : $r('app.string.wx_video_course')) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(12) + .lineHeight(20) + .margin({ top: 9 }) + }.alignItems(HorizontalAlign.Start).width(245).layoutWeight(1).margin({ top: 37 }) + } + .visibility(this.mediaList.length > 0 ? Visibility.None : Visibility.Visible) + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/MinePage.ets b/entry/src/main/ets/pages/main/mine/MinePage.ets new file mode 100644 index 0000000..b91a5f6 --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/MinePage.ets @@ -0,0 +1,436 @@ +import { AppUtil, FileUtil, FormatUtil, NumberUtil, PasteboardUtil, StrUtil } from '@pura/harmony-utils'; +import { EventConstants } from '../../../common/EventConstants'; +import { RouterUrls } from '../../../common/RouterUrls'; +import { UserEntity } from '../../../entity/UserEntity'; +import { LoginManager } from '../../../manager/LoginGlobalManager'; +import { ToastUtils } from '../../../utils/ToastUtils'; +import { TextItemView } from '../../../view/TextItemView'; +import MineViewModel from '../../../viewModel/MineViewModel'; +import { router } from '@kit.ArkUI'; +import { TipDialog } from '../../../dialog/TipDialog'; +import { fileIo, storageStatistics } from '@kit.CoreFileKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { LoadingDialog } from '../../../dialog/LoadingDialog'; +import { DiamondDetailEntity } from '../../../entity/DiamondDetailEntity'; +import { EventReportGlobalManager } from '../../../manager/EventReportGlobalManager'; +import { WxServiceEntity } from '../../../entity/WxServiceEntity'; +import { WXApi } from '../../../utils/wechat/WXApiEventHandlerImpl'; +import * as WxOpenSdk from '@tencent/wechat_open_sdk'; +import { ScanUtil } from '@pura/picker_utils'; +import { scanCore } from '@kit.ScanKit'; + +@ComponentV2 +export struct MinePage { + @Local showChallenge: boolean = false; + @Local showShare: boolean = false; + @Local isLogin: boolean = LoginManager.isLogin(); + @Local userinfo?: UserEntity; + @Local diamondInfo?: DiamondDetailEntity + @Local cacheSize: number = 0 + + scroller: Scroller = new Scroller(); + + viewModel: MineViewModel = new MineViewModel(this.getUIContext()); + + + @Monitor('viewModel.userEntity') + onUserinfoChange(monitor: IMonitor) { + this.userinfo = monitor.value()?.now as UserEntity; + } + + @Monitor('viewModel.wxService') + onWxServiceChange(monitor: IMonitor) { + const info = monitor.value()?.now as WxServiceEntity + if (StrUtil.isEmpty(info.corpid) || StrUtil.isEmpty(info.address)) { + ToastUtils.show('获取客服信息错误') + } else { + this.contactWxService(info) + } + } + + @Monitor('viewModel.diamondInfo') + onDiamondInfoChange(monitor: IMonitor) { + this.diamondInfo = monitor.value()?.now as DiamondDetailEntity; + } + + aboutToAppear(): void { + this.initObserver(); + this.getCache() + this.viewModel.userinfo(); + this.viewModel.getDiamondInfo() + } + + initObserver() { + AppUtil.getContext().eventHub.on(EventConstants.MineRefreshEvent, () => { + this.isLogin = LoginManager.isLogin(); + this.viewModel.userinfo(); + this.getCache(); + }); + } + + getCache() { + storageStatistics.getCurrentBundleStats((error: BusinessError, bundleStats: storageStatistics.BundleStats) => { + if (error) { + console.error('getCurrentBundleStats failed with error:' + JSON.stringify(error)); + } else { + console.info('getCurrentBundleStats successfully:' + JSON.stringify(bundleStats)); + this.cacheSize = bundleStats.cacheSize + } + }); + } + + // Clear cache + clearCache() { + LoadingDialog.show(this.getUIContext()) + let cacheDir = AppUtil.getContext().cacheDir; + fileIo.listFile(cacheDir).then((filenames) => { + for (let i = 0; i < filenames.length; i++) { + let dirPath = cacheDir + '/' + filenames[i]; + let isDirectory: boolean = false; + try { + isDirectory = fileIo.statSync(dirPath).isDirectory(); + } catch (e) { + console.error(JSON.stringify(e)); + } + if (isDirectory) { + fileIo.rmdirSync(dirPath); + } else { + fileIo.unlinkSync(dirPath); + } + } + }) + setTimeout(() => { this.getCache() }, 200) + LoadingDialog.dismiss() + } + + /** + * 跳转客服 + * @param service + */ + contactWxService(service: WxServiceEntity) { + let req = new WxOpenSdk.OpenCustomerServiceChatReq() + req.corpId = service.corpid; // 企业ID + req.url = service.address; // 客服URL + WXApi.sendReq(AppUtil.getContext(), req) + } + + build() { + Scroll(this.scroller) { + Stack() { + Image($r('app.media.ic_mine_top_bg')) + .width('100%') + .aspectRatio(0.8) + .id('iv_top_bg') + + Image(this.userinfo?.vip === 3 ? $r('app.media.ic_vip_bg2') : $r('app.media.ic_vip_bg1')) + .width('100%') + .aspectRatio(1.4) + .visibility(this.userinfo?.vip !== 1 ? Visibility.Visible : Visibility.Hidden) + .id('iv_vip_bg') + + Column() { + RelativeContainer() { + Image(StrUtil.isNotEmpty(this.userinfo?.avater) ? this.userinfo?.avater : $r('app.media.ic_default_avatar')) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .margin({ top: 20 }) + .padding(1) + .borderRadius(45) + .width(90) + .height(90) + .backgroundColor(Color.White) + .id('iv_avatar') + .onClick(() => { + if (!LoginManager.isLogin()) { + this.getUIContext().getRouter().pushUrl({ url: RouterUrls.LOGIN_PAGE, params: { from : 1 }}, router.RouterMode.Single) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_LOGIN, 'center') + } else { + this.getUIContext().getRouter().pushUrl({ url: RouterUrls.USER_SETTINGS_PAGE }) + } + }) + + Text('Hi! 快登录') + .fontColor(Color.White) + .fontSize(12) + .textAlign(TextAlign.Center) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + left: { anchor: 'iv_avatar', align: HorizontalAlign.End } + }) + .padding({ + left: 6, + right: 6, + }) + .margin({ left: -25 }) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .borderRadius({ topLeft: 6, topRight: 6, bottomRight: 6 }) + .width('auto') + .height(24) + .visibility(this.isLogin ? Visibility.None : Visibility.Visible) + .id('tv_nologin_tip') + + Text(this.userinfo?.ip_area) + .fontColor(Color.White) + .fontSize(12) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + left: { anchor: 'iv_avatar', align: HorizontalAlign.End } + }) + .padding({ + left: 6, + right: 6, + bottom: 4 + }) + .margin({ left: -25 }) + .width('auto') + .height(21) + .backgroundImage($r('app.media.ic_area_bg')) + .backgroundImageSize({width: 'auto', height:21}) + .visibility(StrUtil.isNotEmpty(this.userinfo?.ip_area) && this.userinfo?.ip_area !== 'CN' && LoginManager.isLogin() ? Visibility.Visible : Visibility.None) + .id('tv_area') + + Text(StrUtil.isNotEmpty(this.userinfo?.name) ? this.userinfo?.name : '游客') + .fontColor($r('app.color.color_90ffffff')) + .fontSize(18) + .fontWeight(FontWeight.Medium) + .margin({ top: 12 }) + .alignRules({ + top: { anchor: 'iv_avatar', align: VerticalAlign.Bottom }, + left: { anchor: 'iv_avatar', align: HorizontalAlign.Start }, + right: { anchor: 'iv_avatar', align: HorizontalAlign.End } + }) + .width('auto') + .height('auto') + .id('tv_username') + + Row() { + Text('ID:' + this.userinfo?.user_id) + .fontColor($r('app.color.color_999999')) + .fontSize(14) + .id('tv_user_id') + Image($r('app.media.ic_copy_id')) + .margin({ left: 4 }) + .width(14) + .height(14) + } + .alignRules({ + top: { anchor: 'tv_username', align: VerticalAlign.Bottom }, + left: { anchor: 'tv_username', align: HorizontalAlign.Start }, + right: { anchor: 'tv_username', align: HorizontalAlign.End } + }) + .margin({ top: 4 }) + .width('auto') + .id('layout_copy_id') + .onClick(() => { + if (StrUtil.isNotEmpty(this.userinfo?.user_id)) { + PasteboardUtil.setDataTextSync(this.userinfo?.user_id!!) + ToastUtils.show('复制成功') + } + }) + + Image($r('app.media.ic_scan')).width(24).height(24) + .alignRules({ + top: {anchor: '__container__', align: VerticalAlign.Top}, + right: {anchor: '__container__', align: HorizontalAlign.End} + }) + .margin({ right: 20 }) + .onClick(() => { + try { + ScanUtil.startScanForResult({ scanTypes: [scanCore.ScanType.QR_CODE], enableMultiMode: true, enableAlbum: true}) + .then((result) => { + const code = result.originalValue + const array = code.split('-') + if (code.length === 36 && array.length === 5) { + this.getUIContext().getRouter().pushUrl({ url: RouterUrls.QRCODE_LOGIN_PAGE, params: { code: code }}) + } else { + ToastUtils.show('无效二维码') + } + }) + .catch(() => { + ToastUtils.show('此设备不支持二维码扫描') + }) + } catch (e) { + ToastUtils.show('此设备不支持二维码扫描') + } + }) + } + .margin({ top: 70 }) + .width('100%') + .height(180) + .id('layout_userinfo') + + Row() { + Column() { + Text(FormatUtil.getFormatFileSize(NumberUtil.toNumber(this.userinfo?.month_download_size!!))) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(18) + .fontFamily('ddp500m') + .id('tv_month_size') + + Text('本月流量') + .fontColor($r("app.color.color_50ffffff")) + .fontSize(12) + .margin({ top: 4 }) + } + .layoutWeight(1) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.DOWNLOAD_HISTORY_PAGE}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_DOWNLOAD_HISTORY) + }) + + Divider().vertical(true).color('#1affffff').strokeWidth(1).height(26) + + Column() { + Text(this.userinfo?.month_download_count) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(18) + .fontFamily('ddp500m') + .id('tv_month_count') + + Text('本月下载') + .fontColor($r("app.color.color_50ffffff")) + .fontSize(12) + .margin({ top: 4 }) + } + .layoutWeight(1) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.DOWNLOAD_HISTORY_PAGE}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_DOWNLOAD_HISTORY) + }) + + Divider().vertical(true).color('#1affffff').strokeWidth(1).height(26) + + Column() { + Text('0') + .fontColor($r('app.color.color_90ffffff')) + .fontSize(18) + .fontFamily('ddp500m') + .id('tv_task_count') + + Text('下载任务') + .fontColor($r("app.color.color_50ffffff")) + .fontSize(12) + .margin({ top: 4 }) + } + .layoutWeight(1) + .visibility(Visibility.None) + } + .padding({ top: 9, bottom: 9 }) + .width('100%') + .height('auto') + .id('layout_download_info') + + RelativeContainer() { + Text(this.userinfo?.vip === 1 ? '素材魔方·会员' : this.userinfo?.vip_name) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(18) + .fontWeight(FontWeight.Bold) + .fontFamily('almmsht') + .id('tv_vip_name') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Center } + }) + + Text(this.userinfo?.vip === 1 ? '点击成为会员解锁所有功能' : this.userinfo?.vip === 2 ? this.userinfo.vip_expire + ' 会员到期' : '享受永久会员专属权益') + .fontColor($r('app.color.color_60ffffff')) + .fontSize(14) + .margin({ top: 8 }) + .id('tv_vip_expire') + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Center } + }) + + Button(this.userinfo?.vip === 1 ? '立即开通' : this.userinfo?.vip === 2 ? '立即续费' : '永久会员' , { type: ButtonType.Capsule, stateEffect: true }) + .fontColor('#291966') + .fontSize(14) + .fontWeight(FontWeight.Medium) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .linearGradient({ + colors: [['#D4C4F9', 0.0], ['#E9E6F9', 1.0]], + direction: GradientDirection.Top + }) + .width(88) + .height(32) + .visibility(this.userinfo?.vip === 3 ? Visibility.None : Visibility.Visible) + .id('btn_to_vip') + .onClick(() => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIP_PAGE, params: {origin: 'center'}}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_MEMBER_RECHARGE, 'center') + }) + } + .margin({ left: 16, top: 10, right: 16 }) + .padding({ left: 16, right: 16 }) + .backgroundImage($r('app.media.ic_mine_vip_bg')) + .backgroundImageSize({ width: '100%' }) + .aspectRatio(4.28) + .id('layout_vip_info') + + Column() { + TextItemView({ image: $r("app.media.ic_mine_icon1"), leftText: '免单挑战' }) + .height(60) + .id('item_challenge') + .visibility(/*this.showChallenge ? Visibility.Visible : Visibility.None*/Visibility.None) + TextItemView({ image: $r("app.media.ic_mine_icon2"), leftText: '兑换钻石' }) + .height(60) + .id('item_diamond') + .onClick(() => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.RECHARGE_DIAMOND_PAGE}); + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_RECHARGE_DIAMOND, 'center') + }) + .visibility(this.diamondInfo && this.diamondInfo?.buy_total > 0 ? Visibility.Visible : Visibility.None) + TextItemView({ image: $r("app.media.ic_mine_icon3"), leftText: '优惠券' }).height(60).id('item_coupon') + .visibility(Visibility.None) + TextItemView({ image: $r("app.media.ic_mine_icon4"), leftText: '分享APP' }) + .height(60) + .id('item_share') + .visibility(/*this.showShare ? Visibility.Visible : Visibility.None*/Visibility.None) + TextItemView({ image: $r("app.media.ic_mine_icon5"), leftText: '联系客服' }).height(60).id('item_service') + .onClick(() => { + if (!WXApi.isWXAppInstalled()) { + ToastUtils.show('未安装微信客户端,请先下载安装微信客户端'); + return; + } + this.viewModel.getWxService() + }) + TextItemView({ image: $r("app.media.ic_mine_icon6"), leftText: '清除缓存', rightText: FileUtil.getFormatFileSize(this.cacheSize) }).height(60).id('item_cache') + .onClick(() => { + if (this.cacheSize > 0) { + TipDialog.show(this.getUIContext(), {title: '提示', content: '确定清除缓存?', callback: { + confirm: () => { + this.clearCache() + } + }}) + } + }) + TextItemView({ image: $r("app.media.ic_mine_icon7"), leftText: '设置' }).height(60).id('item_setting') + .onClick(() => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.SETTING_PAGE}); + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_SYSTEM_SETTING) + }) + } + .margin({ top: 16, left: 16, right: 16 }) + .backgroundColor('#1E1D24') + .borderRadius(8) + .id('layout_menu') + + Blank().layoutWeight(1) + } + } + .alignContent(Alignment.Top) + } + .padding({ bottom: 20 }) + .scrollBar(BarState.Off) + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/diamond/DiamondPage.ets b/entry/src/main/ets/pages/main/mine/diamond/DiamondPage.ets new file mode 100644 index 0000000..f0e765a --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/diamond/DiamondPage.ets @@ -0,0 +1,436 @@ +import { RouterUrls } from '../../../../common/RouterUrls'; +import { VipMealEntity } from '../../../../entity/VipMealEntity' +import { LoginManager } from '../../../../manager/LoginGlobalManager'; +import { ConfigManager } from '../../../../manager/UserConfigManager'; +import { ToastUtils } from '../../../../utils/ToastUtils'; +import { TitleBar } from '../../../../view/TitleBar' +import { LevelMode, router } from '@kit.ArkUI'; +import { OnWXResp, WXApi, WXEventHandler } from '../../../../utils/wechat/WXApiEventHandlerImpl'; +import { PayOrderEntity } from '../../../../entity/OrderPayEntity'; +import { PayUtils } from '../../../../utils/PayUtils'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { NumberUtil, ObjectUtil, StrUtil } from '@pura/harmony-utils'; +import { ErrCode, PayResp } from '@tencent/wechat_open_sdk'; +import { DiamondViewModel } from '../../../../viewModel/DiamondViewModel'; +import { LoadingDialog } from '../../../../dialog/LoadingDialog'; +import { DiamondDetailEntity } from '../../../../entity/DiamondDetailEntity'; +import { DiamondItemView } from '../../../../view/DiamondItemView'; +import { DiamondRuleDialog } from '../../../../dialog/DiamondRuleDialog'; +import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager'; +import { EventConstants } from '../../../../common/EventConstants'; +import { OrderEntity } from '../../../../entity/OrderEntity'; +import { TipDialog } from '../../../../dialog/TipDialog'; + +@Entry +@ComponentV2 +struct DiamondPage { + @Local origin: string = 'center'; + @Local diamondInfo?: DiamondDetailEntity + @Local diamondList: Array = []; + @Local vipMeal?: VipMealEntity; + @Local isAgree: boolean = false; + @Local payType: number = 0; //0微信支付 1支付宝支付 + @Local totalPrice: number = 0; + + showQueryTip: boolean = false //是否显示支付状态查询提示 + + diamondRuleDialogController?: CustomDialogController | null; + + viewModel: DiamondViewModel = new DiamondViewModel(this.getUIContext()); + orderEntity?: PayOrderEntity; + + //从微信返回的回调 + onWXResp: OnWXResp = (resp) => { + //微信返回的数据 + if (resp instanceof PayResp) { + const payResult = JSON.stringify(resp ?? {}, null, 2); + const errCode = JSON.parse(payResult).errCode as number; + if (errCode === ErrCode.ERR_OK) { + this.viewModel.getOrderInfo(this.orderEntity!!.orderId) + } else { + ToastUtils.show(JSON.parse(payResult).errStr); + EventReportGlobalManager.eventReport( + EventConstants.PAY_CANCEL, + `weixin:${this.origin}`, `{type:\"diamond\", orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.diamondInfo)}}` + ) + } + } + LoadingDialog.dismiss(); + } + + @Monitor('viewModel.diamondInfo') + onDiamondInfoChange(monitor: IMonitor) { + this.diamondInfo = monitor.value()?.now as DiamondDetailEntity; + } + + @Monitor('viewModel.goodsList') + onGoodsListChange(monitor: IMonitor) { + const list = monitor.value()?.now as Array; + this.diamondList = list.map(item => ObjectUtil.assign(new VipMealEntity(), item) as VipMealEntity); + if (this.diamondList.length > 0) { + this.vipMeal = this.diamondList.find(item => item.checked === true); + if (!this.vipMeal) { + this.vipMeal = this.diamondList[0]; + } + if (this.vipMeal) { + this.totalPrice = NumberUtil.toNumber(this.vipMeal.price); + this.releasePayType(); + } + } + } + + @Monitor('viewModel.payOrderEntity') + onPayOrderChange(monitor: IMonitor) { + const payOrderEntity = monitor.value()?.now as PayOrderEntity; + this.orderEntity = payOrderEntity; + if (this.payType === 0) { + if (StrUtil.isNotEmpty(this.vipMeal?.weixinMpOriId)) { + PayUtils.toWXMPPay(payOrderEntity.outTradeNo, this.vipMeal!!.weixinMpOriId) + this.showQueryTip = true + } else { + PayUtils.toWXPay(payOrderEntity); + } + } else { + PayUtils.toAliPay(payOrderEntity.payParam) + .then((result) => { + this.parseAlipayResult(result); + }) + .catch((error: BusinessError) => { + console.log(error.message); + ToastUtils.show('支付失败'); + }); + } + } + + @Monitor('viewModel.orderInfoEntity') + onOrderInfoChange(monitor: IMonitor) { + const orderEntity = monitor.value()?.now as OrderEntity; + if (orderEntity.status == "2") { + ToastUtils.show('支付成功'); + EventReportGlobalManager.eventReport( + EventConstants.PAY_SUCCESS, + `alipay:${this.origin}`, `{type:\"diamond\", orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.diamondInfo)}}` + ) + this.viewModel.getDiamondInfo() + } + } + + aboutToAppear() { + WXEventHandler.registerOnWXRespCallback(this.onWXResp) + this.initParams(); + this.viewModel.getDiamondInfo() + this.viewModel.mealList(); + } + + aboutToDisappear() { + WXEventHandler.unregisterOnWXRespCallback(this.onWXResp) + this.diamondRuleDialogController = null + this.viewModel.cancelInterval() + } + + onPageShow(): void { + if (this.showQueryTip) { + TipDialog.show(this.getUIContext(), { title: '温馨提示', content: '是否已完成支付', leftText: '未支付', rightText: '已支付', callback: { + confirm: () => { + this.viewModel.getOrderInfo(this.orderEntity!!.orderId) + } + } }, false) + this.showQueryTip = false + } + } + + initParams() { + const params = this.getUIContext().getRouter().getParams() as Record; + if (params) { + this.origin = params.origin as string; + } + } + + createOrder() { + if (!ConfigManager.isNoLoginPayEnable() && !LoginManager.isLogin()) { + ToastUtils.show('请登录后支付'); + this.getUIContext().getRouter().pushUrl({ url: RouterUrls.LOGIN_PAGE, params: { from: 1 } }, router.RouterMode.Single); + return; + } + if (this.payType === 0 && !WXApi.isWXAppInstalled()) { + ToastUtils.show('请先下载安装微信客户端'); + return; + } + if (this.payType === 0) { + if (StrUtil.isNotEmpty(this.vipMeal!!.weixinMpOriId)) { + this.viewModel.createOrder(this.vipMeal?.goods_id!!, 'combo', this.origin, ''); + } else { + this.viewModel.createOrder(this.vipMeal?.goods_id!!, 'weixin', this.origin, ''); + } + } else { + this.viewModel.createOrder(this.vipMeal?.goods_id!!, 'alipay', this.origin, ''); + } + } + + parseAlipayResult(result: Map) { + const resultStatus = result.get('resultStatus'); + if (resultStatus === '9000') { + this.viewModel.getOrderInfo(this.orderEntity!!.orderId) + } else if (resultStatus === '6001') { + ToastUtils.show('支付取消'); + EventReportGlobalManager.eventReport( + EventConstants.PAY_CANCEL, + `alipay:${this.origin}`, `{type:\"diamond\", orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.diamondInfo)}}` + ) + } else { + ToastUtils.show('支付失败'); + EventReportGlobalManager.eventReport( + EventConstants.ERROR_CLIENT_ALIPAY_ERR, + `{orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.diamondInfo)}}`, resultStatus + ) + } + } + + releasePayType() { + if (this.vipMeal?.pay_type.startsWith('alipay')) { + this.payType = 1; + } else if (this.vipMeal?.pay_type.startsWith('weixin')) { + this.payType = 0; + } + } + + showRuleDialog() { + this.diamondRuleDialogController = new CustomDialogController({ + builder: DiamondRuleDialog(), + width: '100%', + cornerRadius: 20, + maskColor: '#CC000000', + levelMode: LevelMode.EMBEDDED, + backgroundBlurStyle: BlurStyle.NONE, + alignment: DialogAlignment.Bottom + }) + this.diamondRuleDialogController.open(); + } + + build() { + RelativeContainer() { + Image($r('app.media.ic_diamond_top_bg')).width('100%').aspectRatio(1.524) + + Column() { + Scroll() { + Column() { + RelativeContainer() { + Image(LoginManager.getUserInfo()?.avater) + .width(46) + .height(46) + .borderRadius(25) + .margin({ left: 7 }) + .id('iv_avatar') + Text(LoginManager.getUserInfo()?.vip === 1 ? '非会员' : LoginManager.getUserInfo()?.vip === 3 ? '终身会员' : LoginManager.getUserInfo()?.vip_name) + .fontColor('#4F59FF') + .fontSize(12) + .width('auto') + .height(22) + .padding({ left: 6, right: 6 }) + .borderRadius(25) + .linearGradient({ + colors: [['#EFF0FB', 0.0], ['#ABC3FF', 1.0]], + direction: GradientDirection.Right + }) + .alignRules({ + top: { anchor: 'iv_avatar', align: VerticalAlign.Bottom }, + left: { anchor: 'iv_avatar', align: HorizontalAlign.Start }, + right: { anchor: 'iv_avatar', align: HorizontalAlign.End } + }) + .margin({ top: -9 }) + Text(LoginManager.getUserInfo()?.name) + .fontColor('#FFF4D0') + .fontSize(15) + .alignRules({ + top: { anchor: 'iv_avatar', align: VerticalAlign.Top }, + bottom: { anchor: 'iv_avatar', align: VerticalAlign.Center }, + left: { anchor: 'iv_avatar', align: HorizontalAlign.End } + }) + .margin({ left: 16 }) + .id('tv_username') + Text('ID:' + LoginManager.getUserInfo()?.user_id).fontColor('#FFF4D0').fontSize(12) + .alignRules({ + left: { anchor: 'tv_username', align: HorizontalAlign.Start }, + top: { anchor: 'iv_avatar', align: VerticalAlign.Center }, + bottom: { anchor: 'iv_avatar', align: VerticalAlign.Bottom } + }) + + Text() { + Span('剩余总次数 ') + Span(`${this.diamondInfo?.remain}`).fontSize(22).fontFamily('ddp500m') + Span(' 次') + }.fontColor('#FFF4D0').fontSize(12) + .alignRules({ + top: { anchor: 'iv_avatar', align: VerticalAlign.Bottom }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + } + .aspectRatio(2.584) + .margin({ top: 120, left: 26, right: 26 }) + .padding({ top: 16, left: 12, right: 12 }) + .backgroundImage($r('app.media.ic_diamond_vip_bg1')) + .backgroundImageSize({ width: '100%' }) + + Image($r('app.media.ic_diamond_vip_bg2')).width('100%').aspectRatio(4.866) + .margin({ top: -30 }) + + Column() { + Text('钻石消耗数量').fontColor('#8F4A2A').fontSize(16).fontWeight(FontWeight.Medium).margin({ top: 4 }) + + Column() { + Row() { + Text('本月固定钻石数量').fontColor($r('app.color.color_1a1a1a')).fontSize(14) + Text('每月重置') + .textAlign(TextAlign.Center) + .fontColor('#FFA61E') + .fontSize(12) + .height(18) + .padding({ left: 4, right: 4 }) + .borderWidth(1) + .borderRadius(4) + .borderColor('#FFA61E') + .margin({left: 8}) + Blank().layoutWeight(1) + Text() { + Span(`${this.diamondInfo?.month_used}`).fontColor('#FFA61E').fontSize(20).fontFamily('ddp500m') + Span(`/${this.diamondInfo?.month_total}`).fontColor($r('app.color.color_1a1a1a')).fontSize(14) + } + } + + Progress({ value: this.diamondInfo?.month_used, total: this.diamondInfo?.month_total, type: ProgressType.Linear }) + .width('100%') + .height(10) + .style({ strokeWidth: 10, strokeRadius: 5 }) + .color('#FF9026') + .borderRadius(5) + .margin({top: 16}) + + Row() { + Text('兑换钻石数量').fontColor($r('app.color.color_1a1a1a')).fontSize(14) + Text('用完即止') + .textAlign(TextAlign.Center) + .fontColor('#FFA61E') + .fontSize(12) + .height(18) + .padding({ left: 4, right: 4 }) + .borderWidth(1) + .borderRadius(4) + .borderColor('#FFA61E') + .margin({left: 8}) + Blank().layoutWeight(1) + Text() { + Span(`${this.diamondInfo?.buy_used}`).fontColor('#FFA61E').fontSize(20).fontFamily('ddp500m') + Span(`/${this.diamondInfo?.buy_total}`).fontColor($r('app.color.color_1a1a1a')).fontSize(14) + } + }.margin({top: 24}) + + Progress({ value: this.diamondInfo?.buy_used, total: this.diamondInfo?.buy_total, type: ProgressType.Linear }) + .width('100%') + .height(10) + .style({ strokeWidth: 10, strokeRadius: 5 }) + .color('#FF9026') + .borderRadius(5) + .margin({top: 16}) + } + .backgroundColor(Color.White) + .borderRadius(8) + .margin({ top: 10 }) + .padding({ + left: 12, + top: 16, + right: 12, + bottom: 16 + }) + } + .alignItems(HorizontalAlign.Start) + .height('auto') + .padding(10) + .margin({ top: -45, left: 16, right: 16 }) + .backgroundImage($r('app.media.ic_diamond_count_bg')) + .backgroundImageSize({ width: '100%' }) + .borderRadius(10) + .clip(true) + + Text('钻石套餐').fontColor($r('app.color.color_1a1a1a')).fontSize(16).fontWeight(FontWeight.Medium).margin({left: 16, top: 26}) + .alignSelf(ItemAlign.Start) + + List({space: 12}) { + ForEach(this.diamondList, (item: VipMealEntity) => { + ListItem() { + DiamondItemView({ goodInfo: item }) + } + .onClick(() => { + let goodsInfo = this.diamondList.find(item => item.checked) as VipMealEntity + goodsInfo.checked = false + item.checked = true + this.vipMeal = item + this.releasePayType() + }) + }) + } + .width('auto') + .height('auto') + .margin({left: 16, top: 14, right: 16, bottom: 15 }) + } + } + .layoutWeight(1) + .scrollBar(BarState.Off) + + Column() { + Row() { + Row() { + Image($r('app.media.ic_wx_pay3')).width(26).height(26) + Text('微信').fontColor($r('app.color.color_1a1a1a')).fontSize(15).margin({left: 10}) + Blank().layoutWeight(1) + Image(this.payType === 0 ? $r('app.media.ic_pay_true2') : $r('app.media.ic_pay_false2')).width(18).height(18) + }.layoutWeight(1) + .visibility(this.vipMeal?.pay_type.includes('weixin') ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.payType = 0; + }) + Divider().width(1).strokeWidth(1).height(26).color('#EEEEEE').margin({left: 20, right: 20}) + Row() { + Image($r('app.media.ic_ali_pay3')).width(26).height(26) + Text('支付宝').fontColor($r('app.color.color_1a1a1a')).fontSize(15).margin({left: 10}) + Blank().layoutWeight(1) + Image(this.payType === 1 ? $r('app.media.ic_pay_true2') : $r('app.media.ic_pay_false2')).width(18).height(18) + }.layoutWeight(1) + .visibility(this.vipMeal?.pay_type.includes('alipay') ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.payType = 1; + }) + } + + Button('立即支付', {type: ButtonType.Capsule, stateEffect: true}) + .width('100%') + .height(46) + .fontColor(Color.White) + .fontSize(16) + .fontWeight(FontWeight.Medium) + .backgroundColor('#FF8D1B') + .margin({top: 17}) + .onClick(() => { + if (this.vipMeal) { + this.createOrder() + } + }) + }.backgroundColor(Color.White) + .padding({left: 16, top: 8, right: 16, bottom: 30}) + .shadow({radius: 6, offsetY: -2, color: '#1d000000'}) + } + + TitleBar({ + title: '素材加油站', + isDark: false, + rightText: '规则说明', + rightColor: '#6F3A21', + onRightClick: () => { + this.showRuleDialog() + } + }).id('titleBar') + } + .width('100%') + .height('100%') + .backgroundColor(Color.White) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/history/DownloadHistoryItemPage.ets b/entry/src/main/ets/pages/main/mine/history/DownloadHistoryItemPage.ets new file mode 100644 index 0000000..66b8acf --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/history/DownloadHistoryItemPage.ets @@ -0,0 +1,143 @@ +import { AppUtil, DateUtil } from "@pura/harmony-utils"; +import { EventConstants } from "../../../../common/EventConstants"; +import { DownloadHistoryEntity } from "../../../../entity/DownloadHistoryEntity"; +import { DownloadHistoryItemView } from "../../../../view/DownloadHistoryItemView"; +import { EmptyView, PageStatus } from "../../../../view/EmptyView"; +import { DownloadHistoryViewModel } from "../../../../viewModel/DownloadHistoryViewModel"; + +@ComponentV2 +export struct DownloadHistoryItemPage { + @Local isRefreshing: boolean = false; + @Local isLoading: boolean = false; + @Local refreshOffset: number = 0; + @Local refreshState: RefreshStatus = RefreshStatus.Inactive; + @Local historyList: Array = [] + + @Param type: number = 0 + + viewModel: DownloadHistoryViewModel = new DownloadHistoryViewModel(this.getUIContext()) + page: number = 1 + canLoadMore: boolean = false + + @Monitor('viewModel.getHistory') + onGetDownloadHistory(monitor: IMonitor) { + const list = monitor.value()?.now as Array + if (this.page === 1) { + this.historyList = list + this.isRefreshing = false + } else { + this.historyList = this.historyList.concat(list) + this.isLoading = false + } + this.canLoadMore = list.length === 20 + } + + aboutToAppear(): void { + this.isRefreshing = true + this.initObserver() + } + + aboutToDisappear(): void { + AppUtil.getContext().eventHub.off(EventConstants.DownloadHistoryRefreshEvent) + } + + initObserver() { + AppUtil.getContext().eventHub.on(EventConstants.DownloadHistoryRefreshEvent, (type: number) => { + if (type === this.type) { + this.isRefreshing = true + } + }) + } + + getStartTime(): number { + let nowDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 1, 1) + let lastDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 2, 1) + if (this.type === 0) { + return Math.trunc(nowDate.getTime() / 1000) + } else { + return Math.trunc(lastDate.getTime() / 1000) + } + } + + getEndTime(): number { + let nowDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth(), 1) + let lastDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 1, 1) + if (this.type === 0) { + return Math.trunc(nowDate.getTime() / 1000) + } else { + return Math.trunc(lastDate.getTime() / 1000) + } + } + + build() { + Stack() { + Refresh({refreshing: this.isRefreshing, builder: this.refreshBuilder()}) { + List({space: 12}) { + ForEach(this.historyList, (item: DownloadHistoryEntity) => { + ListItem() { + DownloadHistoryItemView({ historyEntity: item }) + } + }) + ListItem() { + this.footer(); + } + } + .width('100%') + .height('100%') + .onScrollIndex((start: number, end: number) => { + // 当达到列表末尾时,触发新数据加载。 + if (this.canLoadMore && end >= this.historyList.length - 1) { + this.page++ + this.isLoading = true; + this.viewModel.getHistoryList(`${this.page}`, `${this.getStartTime()}`, `${this.getEndTime()}`) + } + }) + } + .onRefreshing(() => { + this.page = 1 + this.isRefreshing = true + this.viewModel.getHistoryList(`${this.page}`, `${this.getStartTime()}`, `${this.getEndTime()}`) + }) + .refreshOffset(50) + .pullToRefresh(true) + .visibility(this.historyList.length > 0 ? Visibility.Visible : Visibility.None) + + EmptyView({ + status: this.historyList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA, + noDataImage: $r('app.media.ic_empty_data'), + noDataText: '暂无数据' + }) + } + .width('100%') + .height('100%') + } + + @Builder + refreshBuilder() { + Stack({ alignContent: Alignment.Bottom }) { + // 可以通过刷新状态控制是否存在Progress组件。 + // 当刷新状态处于下拉中或刷新中状态时Progress组件才存在。 + if (this.refreshState != RefreshStatus.Inactive && this.refreshState != RefreshStatus.Done) { + Progress({ value: this.refreshOffset, total: 50, type: ProgressType.Ring }) + .width(32).height(32) + .style({ status: this.isRefreshing ? ProgressStatus.LOADING : ProgressStatus.PROGRESSING }) + .margin(10) + } + } + .clip(true) + .height("100%") + .width("100%") + } + + @Builder + footer() { + Row() { + LoadingProgress().height(32).width(48) + Text("加载中") + }.width("100%") + .height(50) + .justifyContent(FlexAlign.Center) + // 当不处于加载中状态时隐藏组件。 + .visibility(this.isLoading ? Visibility.Visible : Visibility.Hidden) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/history/DownloadHistoryPage.ets b/entry/src/main/ets/pages/main/mine/history/DownloadHistoryPage.ets new file mode 100644 index 0000000..0bc72e3 --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/history/DownloadHistoryPage.ets @@ -0,0 +1,102 @@ +import { AppUtil, DateUtil } from '@pura/harmony-utils'; +import { EventConstants } from '../../../../common/EventConstants'; +import { TipDialog } from '../../../../dialog/TipDialog'; +import { TitleBar } from '../../../../view/TitleBar'; +import { DownloadHistoryViewModel } from '../../../../viewModel/DownloadHistoryViewModel'; +import { DownloadHistoryItemPage } from './DownloadHistoryItemPage'; + +@Entry +@ComponentV2 +struct DownloadHistory { + @Local currentIndex: number = 0; + tabController: TabsController = new TabsController(); + titles: Array = ['本月', '上月']; + + viewModel: DownloadHistoryViewModel = new DownloadHistoryViewModel(this.getUIContext()) + + @Monitor('viewModel.deleteHistory') + onDeleteDownloadHistory(monitor: IMonitor) { + AppUtil.getContext().eventHub.emit(EventConstants.DownloadHistoryRefreshEvent, this.currentIndex) + } + + getStartTime(): number { + let nowDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 1, 1) + let lastDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 2, 1) + if (this.currentIndex === 0) { + return Math.trunc(nowDate.getTime() / 1000) + } else { + return Math.trunc(lastDate.getTime() / 1000) + } + } + + getEndTime(): number { + let nowDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth(), 1) + let lastDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 1, 1) + if (this.currentIndex === 0) { + return Math.trunc(nowDate.getTime() / 1000) + } else { + return Math.trunc(lastDate.getTime() / 1000) + } + } + + build() { + Column() { + TitleBar({ title: '本月下载' }) + + Stack({ alignContent: Alignment.TopStart }) { + Tabs({ barPosition: BarPosition.Start, controller: this.tabController }) { + TabContent() { + DownloadHistoryItemPage({type: 0}) + } + + TabContent() { + DownloadHistoryItemPage({type: 1}) + } + } + .scrollable(false) + /*.onSelected((index: number) => { + this.currentIndex = index; + })*/ + + Row({ space: 40 }) { + ForEach(this.titles, (title: string, index) => { + this.tab(title, index); + }) + Blank().layoutWeight(1) + Row() { + Image($r('app.media.ic_clear_record')).width(16).height(16) + Text('全部清空').fontColor($r('app.color.color_80ffffff')).fontSize(12).margin({ left: 4 }) + } + .onClick(() => { + TipDialog.show(this.getUIContext(), {title: '提示', content: '确定清空记录?', callback: { + confirm: () => { + this.viewModel.deleteHistoryList(`${this.getStartTime()}`, `${this.getEndTime()}`) + } + }}) + }) + }.padding({ left: 16, right: 16 }) + .margin({ top: 10 }) + }.layoutWeight(1) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } + + @Builder + tab(tabName: string, tabIndex: number) { + Row() { + Text(tabName) + .fontSize(this.currentIndex === tabIndex ? 17 : 14) + .fontWeight(this.currentIndex === tabIndex ? FontWeight.Medium : FontWeight.Regular) + .lineHeight(24) + .fontColor(tabIndex === this.currentIndex ? $r("app.color.color_466afd") : $r('app.color.color_50ffffff')) + } + .width('auto') + .height('auto') + .onClick(() => { + this.tabController.changeIndex(tabIndex); + this.currentIndex = tabIndex; + }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/setting/SettingsPage.ets b/entry/src/main/ets/pages/main/mine/setting/SettingsPage.ets new file mode 100644 index 0000000..62bb71d --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/setting/SettingsPage.ets @@ -0,0 +1,103 @@ +import { AppUtil } from '@pura/harmony-utils' +import { EventConstants } from '../../../../common/EventConstants' +import { RouterUrls } from '../../../../common/RouterUrls' +import { TipDialog } from '../../../../dialog/TipDialog' +import { LoginManager } from '../../../../manager/LoginGlobalManager' +import { ConfigManager } from '../../../../manager/UserConfigManager' +import { ToastUtils } from '../../../../utils/ToastUtils' +import { TextItemChildView } from '../../../../view/TextItemChildView' +import { TitleBar } from '../../../../view/TitleBar' +import { SettingsViewModel } from '../../../../viewModel/SettingsViewModel' +import { router } from '@kit.ArkUI' +import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager' + +@Entry +@ComponentV2 +struct SettingsPage { + viewModel: SettingsViewModel = new SettingsViewModel(this.getUIContext()); + + @Monitor('viewModel.destroy') + onDestroy(monitor: IMonitor) { + EventReportGlobalManager.eventReport(EventConstants.CANCEL_ACCOUNT) + this.logout(); + ToastUtils.show('账户已注销'); + } + + logout() { + LoginManager.saveLastUserInfo(LoginManager.getUserInfo()!!); + ConfigManager.saveBindWxPlaybackHelper(false); + ConfigManager.saveBindWxVideoHelper(false); + LoginManager.logout(); + AppUtil.getContext().eventHub.emit(EventConstants.LogoutSuccessEvent); + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.LOGIN_PAGE }, router.RouterMode.Single); + } + + build() { + Column() { + TitleBar({ title: '设置' }).width('100%') + Scroll() { + Column() { + TextItemChildView({ text: '意见反馈' }).height(60).margin({ left: 16, right: 16 }) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.FEEDBACK_PAGE}) + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_FEEDBACK, 'center') + }) + TextItemChildView({ text: '账号绑定' }).height(60).margin({ left: 16, right: 16 }) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.BIND_ACCOUNT_PAGE}); + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_ACCOUNT_BIND) + }) + TextItemChildView({ text: '账号管理' }).height(60).margin({ left: 16, right: 16 }) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.MANAGE_ACCOUNT_PAGE}); + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_ACCOUNT_MANAGE) + }) + TextItemChildView({ text: '关于我们' }).height(60).margin({ left: 16, right: 16 }) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.ABOUT_PAGE}); + EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_ABOUT_US) + }) + TextItemChildView({ text: '注销账号' }).height(60).margin({ left: 16, right: 16 }) + .onClick(() => { + TipDialog.show(this.getUIContext(), {title: '提示', content: '为了您的账户安全,注销账户后将会永久清除与该账户相关的所有信息,服务器不再保存', callback: { + confirm: () => { + this.viewModel.userDestroy(); + } + }}) + }) + .visibility(LoginManager.isLogin() ? Visibility.Visible : Visibility.None) + } + .justifyContent(FlexAlign.Start) + .height('100%') + } + .layoutWeight(1) + + Stack() { + Button('退出登录', { type: ButtonType.Capsule, stateEffect: true }) + .fontColor(Color.White) + .fontSize(15) + .fontWeight(FontWeight.Medium) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .width('100%') + .height(46) + .onClick(() => { + TipDialog.show(this.getUIContext(), {title: '温馨提示', content: '确定退出登录?', callback: { + confirm: () => { + EventReportGlobalManager.eventReport(EventConstants.EXIT_LOGIN) + this.logout(); + } + }}) + }) + } + .padding({ left: 16, right: 16 }) + .margin({ bottom: 30 }) + .visibility(LoginManager.isLogin() ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/setting/about/AboutPage.ets b/entry/src/main/ets/pages/main/mine/setting/about/AboutPage.ets new file mode 100644 index 0000000..b746816 --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/setting/about/AboutPage.ets @@ -0,0 +1,43 @@ +import { AppUtil } from '@pura/harmony-utils' +import { Constants } from '../../../../../common/Constants' +import { RouterUrls } from '../../../../../common/RouterUrls' +import { TextItemChildView } from '../../../../../view/TextItemChildView' +import { TitleBar } from '../../../../../view/TitleBar' + +@Entry +@ComponentV2 +struct AboutPage { + build() { + Column() { + TitleBar({ title: '关于' }).width('100%') + Image($r('app.media.ic_login_logo')).width(100).height(100).margin({ top: 100 }) + Text($r('app.string.app_name')) + .fontColor(Color.White) + .fontSize(16) + .fontWeight(FontWeight.Medium) + .margin({ top: 16 }) + .width('auto') + Text('版本号:v' + AppUtil.getVersionName()) + .fontColor($r('app.color.color_999999')) + .fontSize(14) + .margin({ top: 6 }) + .width('auto') + TextItemChildView({ text: '用户协议' }).height(60).margin({ left: 16, top: 52, right: 16 }) + .onClick(() => { + this.getUIContext() + .getRouter() + .pushUrl({ url: RouterUrls.WEB_PAGE, params: { title: '用户协议', url: Constants.USER_AGREEMENT } }) + }) + TextItemChildView({ text: '隐私政策', divider: false }).height(60).margin({ left: 16, right: 16 }) + .onClick(() => { + this.getUIContext() + .getRouter() + .pushUrl({ url: RouterUrls.WEB_PAGE, params: { title: '隐私政策', url: Constants.PRIVACY_POLICY } }) + }) + } + .justifyContent(FlexAlign.Start) + .backgroundColor($r('app.color.window_background')) + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/setting/account/BindAccountPage.ets b/entry/src/main/ets/pages/main/mine/setting/account/BindAccountPage.ets new file mode 100644 index 0000000..23007a2 --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/setting/account/BindAccountPage.ets @@ -0,0 +1,186 @@ +import { AppUtil, RandomUtil, StrUtil } from '@pura/harmony-utils'; +import { TipDialog } from '../../../../../dialog/TipDialog'; +import { UserEntity } from '../../../../../entity/UserEntity'; +import { ConfigManager } from '../../../../../manager/UserConfigManager'; +import { ToastUtils } from '../../../../../utils/ToastUtils'; +import { TitleBar } from '../../../../../view/TitleBar'; +import BindAccountViewModel from '../../../../../viewModel/BindAccountViewModel'; +import * as WxOpenSdk from '@tencent/wechat_open_sdk'; +import { ErrCode, SendAuthResp } from '@tencent/wechat_open_sdk'; +import { OnWXResp, WXApi, WXEventHandler } from '../../../../../utils/wechat/WXApiEventHandlerImpl'; +import BuildProfile from 'BuildProfile'; +import { BindPhoneDialog } from '../../../../../dialog/BindPhoneDialog'; +import { LevelMode } from '@kit.ArkUI'; +import { EventConstants } from '../../../../../common/EventConstants'; +import { LoadingDialog } from '../../../../../dialog/LoadingDialog'; +import { EventReportGlobalManager } from '../../../../../manager/EventReportGlobalManager'; + +@Entry +@ComponentV2 +struct BindAccountPage { + bindPhoneDialogController?: CustomDialogController | null; + + viewModel: BindAccountViewModel = new BindAccountViewModel(this.getUIContext()); + + @Local userEntity?: UserEntity; + + //从微信返回的回调 + onWXResp: OnWXResp = (resp) => { + //微信返回的数据 + if (resp instanceof SendAuthResp && resp.state?.endsWith('bind')) { + const authResult = JSON.stringify(resp ?? {}, null , 2); + const errCode = JSON.parse(authResult).errCode as number; + if (errCode === ErrCode.ERR_OK) { + const authCode = JSON.parse(authResult).code as string; + this.viewModel.bindWx(authCode); + } else { + ToastUtils.show(JSON.parse(authResult).errStr); + } + } + } + + @Monitor('viewModel.userEntity') + onUserinfoChange(monitor: IMonitor) { + this.userEntity = monitor.value()?.now as UserEntity; + } + + @Monitor('viewModel.bindInfo') + onBindInfoChange(monitor: IMonitor) { + ToastUtils.show('绑定成功'); + this.refreshData(); + EventReportGlobalManager.eventReport(EventConstants.ACCOUNT_BIND) + } + + @Monitor('viewModel.unbindInfo') + onUnbindInfoChange(monitor: IMonitor) { + ToastUtils.show('解绑成功'); + this.refreshData(); + } + + async aboutToAppear() { + WXEventHandler.registerOnWXRespCallback(this.onWXResp) + this.viewModel.userinfo(); + } + + aboutToDisappear() { + WXEventHandler.unregisterOnWXRespCallback(this.onWXResp) + this.bindPhoneDialogController = null + } + + showBindPhoneDialog() { + this.bindPhoneDialogController = new CustomDialogController({ + builder: BindPhoneDialog({ + success: () => { + this.refreshData(); + } + }), + width: '100%', + cornerRadius: 20, + autoCancel: false, + maskColor: '#CC000000', + levelMode: LevelMode.EMBEDDED, + backgroundBlurStyle: BlurStyle.NONE, + alignment: DialogAlignment.Bottom + }) + this.bindPhoneDialogController.open(); + } + + async wxAuth() { + if (!WXApi.isWXAppInstalled()) { + ToastUtils.show('请先下载安装微信客户端'); + return; + } + LoadingDialog.show(this.getUIContext()); + let req = new WxOpenSdk.SendAuthReq; + req.isOption1 = false; + req.nonAutomatic = true; + req.scope = 'snsapi_userinfo'; + req.state = BuildProfile.BUNDLE_NAME + RandomUtil.getRandomInt(0, 1000) + '_bind'; + req.transaction =''; + + await WXApi.sendReq(AppUtil.getContext(), req) + LoadingDialog.dismiss(); + } + + refreshData() { + ConfigManager.userConfig() + .then(() => { + this.viewModel.userinfo(); + AppUtil.getContext().eventHub.emit(EventConstants.HomeRefreshEvent); + AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent); + }) + } + + build() { + Column() { + TitleBar({ title: '账号绑定' }).width('100%') + Row() { + Image($r('app.media.ic_bind_wx')).width(30).height(30) + Text('微信绑定').fontColor(Color.White).fontSize(15).layoutWeight(1).margin({left: 14}) + Text(StrUtil.isEmpty(this.userEntity?.unionid) ? '立即绑定' : '解除绑定') + .textAlign(TextAlign.Center) + .fontColor(Color.White) + .fontSize(12) + .borderColor(Color.White) + .borderWidth(1) + .borderRadius(4) + .width(64) + .height(24) + .onClick(() => { + if (StrUtil.isEmpty(this.userEntity?.unionid)) { + this.wxAuth(); + } else { + TipDialog.show(this.getUIContext(), {title: '解除微信绑定', content: '解绑后将无法使用该微信号登录此账号,请谨慎操作!', callback: { + confirm: () => { + if (StrUtil.isNotEmpty(this.userEntity?.phone)) { + this.viewModel.unbind('weixin'); + } else { + TipDialog.show(this.getUIContext(), {title: '预警提示', content: '您若继续解除绑定,您的账号将没有绑定账号,软件在使用过程中可能会造成账号丢失!是否继续解除绑定!', callback: { + confirm: () => { + this.viewModel.unbind('weixin'); + } + }}) + } + } + }}) + } + }) + }.width('100%').height(60).padding({ left: 16, right: 16 }) + Divider().color($r('app.color.color_10ffffff')).strokeWidth(1).margin({left: 16, right: 16}) + Row() { + Image($r('app.media.ic_bind_phone')).width(30).height(30) + Text('手机绑定').fontColor(Color.White).fontSize(15).layoutWeight(1).margin({left: 14}) + Text(StrUtil.isEmpty(this.userEntity?.phone) ? '立即绑定' : '解除绑定') + .textAlign(TextAlign.Center) + .fontColor(Color.White) + .fontSize(12) + .borderColor(Color.White) + .borderWidth(1) + .borderRadius(4) + .width(64) + .height(24) + .onClick(() => { + if (StrUtil.isEmpty(this.userEntity?.phone)) { + this.showBindPhoneDialog(); + } else { + TipDialog.show(this.getUIContext(), {title: '解除手机绑定', content: '解绑后将无法使用该手机号登录此账号,请谨慎操作!', callback: { + confirm: () => { + if (StrUtil.isNotEmpty(this.userEntity?.unionid)) { + this.viewModel.unbind('phone'); + } else { + TipDialog.show(this.getUIContext(), {title: '预警提示', content: '您若继续解除绑定,您的账号将没有绑定账号,软件在使用过程中可能会造成账号丢失!是否继续解除绑定!', callback: { + confirm: () => { + this.viewModel.unbind('phone'); + } + }}) + } + } + }}) + } + }) + }.width('100%').height(60).padding({ left: 16, right: 16 }) + } + .backgroundColor($r('app.color.window_background')) + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/setting/account/ManageAccountPage.ets b/entry/src/main/ets/pages/main/mine/setting/account/ManageAccountPage.ets new file mode 100644 index 0000000..7cfd1b6 --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/setting/account/ManageAccountPage.ets @@ -0,0 +1,130 @@ +import { AppUtil, StrUtil } from '@pura/harmony-utils'; +import { EventConstants } from '../../../../../common/EventConstants'; +import { LoadingDialog } from '../../../../../dialog/LoadingDialog'; +import { TipDialog } from '../../../../../dialog/TipDialog'; +import { AccountEntity } from '../../../../../entity/AccountEntity'; +import { LoginEntity } from '../../../../../entity/LoginEntity'; +import { EventReportGlobalManager } from '../../../../../manager/EventReportGlobalManager'; +import { LoginManager } from '../../../../../manager/LoginGlobalManager'; +import { ConfigManager } from '../../../../../manager/UserConfigManager'; +import { ToastUtils } from '../../../../../utils/ToastUtils'; +import { AccountItemView } from '../../../../../view/AccountItemView'; +import { TitleBar } from '../../../../../view/TitleBar'; +import ManageAccountViewModel from '../../../../../viewModel/ManageAccountViewModel'; + +@Entry +@ComponentV2 +struct ManageAccountPage { + viewModel: ManageAccountViewModel = new ManageAccountViewModel(this.getUIContext()); + + @Local accountList: AccountEntity[] = []; + + @Local currentAccount?: AccountEntity; + + @Monitor('viewModel.accounts') + onAccountsChange(monitor: IMonitor) { + const list = monitor.value()?.now as Array; + list.forEach((item) => { + if (item.user_id === LoginManager.getUserInfo()?.user_id) { + this.currentAccount = item; + } else { + this.accountList.push(item); + } + }) + if (this.currentAccount === null && LoginManager.getUserInfo()) { + let userinfo = LoginManager.getUserInfo()!! + let account = new AccountEntity() + let bind: string[] = [] + if (StrUtil.isNotEmpty(userinfo.unionid)) { + bind.push("weixin") + } + if (StrUtil.isNotEmpty(userinfo.phone)) { + bind.push('phone') + } + account.user_id = userinfo.user_id + account.name = userinfo.name + account.avater = userinfo.avater + account.phone = userinfo.phone + account.vip_name = userinfo.vip_name + account.vip_type = userinfo.vip + account.bind = bind + this.currentAccount = account + } + } + + @Monitor('viewModel.loginEntity') + onLoginChange(monitor: IMonitor) { + const loginEntity = monitor.value()?.now as LoginEntity; + LoginManager.saveToken(loginEntity.token); + LoginManager.saveLastUserInfo(LoginManager.getUserInfo()!!); + ConfigManager.saveBindWxPlaybackHelper(false); + ConfigManager.saveBindWxVideoHelper(false); + ConfigManager.userConfig() + .then(() => { + ToastUtils.show('切换成功'); + AppUtil.getContext().eventHub.emit(EventConstants.LoginSuccessEvent); + this.getUIContext().getRouter().back(); + }) + .finally(() => { + LoadingDialog.dismiss(); + }) + } + + aboutToAppear(): void { + this.viewModel.accountList(); + } + + build() { + Column() { + TitleBar({ title: '名下账户管理' }).width('100%') + + Scroll() { + Column() { + Text('当前登录的账户') + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .fontWeight(FontWeight.Medium) + .margin({ left: 20, top: 24, right: 20 }) + .alignSelf(ItemAlign.Start) + + AccountItemView({ account: this.currentAccount }).margin({ left: 20, top: 14, right: 20 }) + + Text('你名下其他账户') + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .fontWeight(FontWeight.Medium) + .margin({ left: 20, top: 30, right: 20 }) + .alignSelf(ItemAlign.Start) + .visibility(this.accountList.length > 0 ? Visibility.Visible : Visibility.Hidden) + + List() { + ForEach(this.accountList, (item: AccountEntity) => { + ListItem() { + AccountItemView({account: item}) + } + .margin({ left: 20, top: 14, right: 20 }) + .onClick(() => { + TipDialog.show(this.getUIContext(), {content: '您确定要切换账户吗?', callback: { + confirm: () => { + LoginManager.saveToken(''); + this.viewModel.changeAccount(item.user_id); + EventReportGlobalManager.eventReport(EventConstants.SWITCH_ACCOUNT,item.user_id) + } + }}) + }) + }) + } + .width('100%') + .height('auto') + .layoutWeight(1) + .margin({ bottom: 30 }) + .visibility(this.accountList.length > 0 ? Visibility.Visible : Visibility.Hidden) + } + } + .layoutWeight(1) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/setting/feedback/FeedbackPage.ets b/entry/src/main/ets/pages/main/mine/setting/feedback/FeedbackPage.ets new file mode 100644 index 0000000..eb3748d --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/setting/feedback/FeedbackPage.ets @@ -0,0 +1,247 @@ +import { PhotoHelper } from '@pura/picker_utils' +import { UploadImgEntity } from '../../../../../entity/UploadImgEntity' +import { AddImageItemView } from '../../../../../view/AddImageItemView' +import { TitleBar } from '../../../../../view/TitleBar' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import { BusinessError } from '@kit.BasicServicesKit' +import { ToastUtils } from '../../../../../utils/ToastUtils' +import { StrUtil } from '@pura/harmony-utils' +import { FeedbackViewModel } from '../../../../../viewModel/FeedbackViewModel' + +enum FeedbackType { + SUGGESTION, BUG, OTHER +} + +@Entry +@ComponentV2 +struct FeedbackPage { + @Local type: FeedbackType = FeedbackType.SUGGESTION + @Local contentText: string = '' + @Local contactText: string = '' + @Local selectedPhotos: Array = [] + + selectedUris: Array = [] + selectedImages: Array = [] + + viewModel: FeedbackViewModel = new FeedbackViewModel(this.getUIContext()) + + @Monitor('viewModel.feedback') + onFeedbackChange(monitor: IMonitor) { + ToastUtils.show('提交成功') + this.getUIContext().getRouter().back() + } + + commit() { + if (StrUtil.isEmpty(this.contentText)) { + ToastUtils.show('请输入详情内容') + return + } + const typeStr = this.type === FeedbackType.SUGGESTION ? '产品建议' : this.type === FeedbackType.BUG ? '功能异常' : '其他问题' + if (this.selectedPhotos.length > 0) { + this.viewModel.uploadImages(this.selectedPhotos) + .then((uploadImages) => { + this.viewModel.sendFeedback(typeStr, this.contentText, this.contactText, uploadImages) + }) + } else { + this.viewModel.sendFeedback(typeStr, this.contentText, this.contactText, []) + } + } + + build() { + Column() { + TitleBar({ title: '意见反馈' }) + + Scroll() { + Column() { + Row() { + Text('请选择您要反馈的问题').fontColor($r('app.color.color_90ffffff')).fontSize(14) + Text('*').fontColor('#FF3838') + } + .width('100%') + + Row() { + Button('产品建议', { type: ButtonType.Normal }) + .height(32) + .fontColor(this.type === FeedbackType.SUGGESTION ? $r('app.color.color_90ffffff') : + $r('app.color.color_50ffffff')) + .fontSize(14) + .linearGradient({ + colors: [[this.type === FeedbackType.SUGGESTION ? '#F62C6C' : Color.Transparent, 0.0], + [this.type === FeedbackType.SUGGESTION ? '#FC4F54' : Color.Transparent, 1.0]], + direction: GradientDirection.Right + }) + .backgroundColor(this.type === FeedbackType.SUGGESTION ? Color.Transparent : $r('app.color.color_222222')) + .borderRadius(4) + .padding({ left: 14, right: 14 }) + .onClick(() => { + this.type = FeedbackType.SUGGESTION + }) + + Button('功能异常', { type: ButtonType.Normal }) + .height(32) + .fontColor(this.type === FeedbackType.BUG ? $r('app.color.color_90ffffff') : + $r('app.color.color_50ffffff')) + .fontSize(14) + .linearGradient({ + colors: [[this.type === FeedbackType.BUG ? '#F62C6C' : Color.Transparent, 0.0], + [this.type === FeedbackType.BUG ? '#FC4F54' : Color.Transparent, 1.0]], + direction: GradientDirection.Right + }) + .backgroundColor(this.type === FeedbackType.BUG ? Color.Transparent : $r('app.color.color_222222')) + .borderRadius(4) + .padding({ left: 14, right: 14 }) + .margin({ left: 12 }) + .onClick(() => { + this.type = FeedbackType.BUG + }) + + Button('其他问题', { type: ButtonType.Normal }) + .height(32) + .fontColor(this.type === FeedbackType.OTHER ? $r('app.color.color_90ffffff') : + $r('app.color.color_50ffffff')) + .fontSize(14) + .linearGradient({ + colors: [[this.type === FeedbackType.OTHER ? '#F62C6C' : Color.Transparent, 0.0], + [this.type === FeedbackType.OTHER ? '#FC4F54' : Color.Transparent, 1.0]], + direction: GradientDirection.Right + }) + .backgroundColor(this.type === FeedbackType.OTHER ? Color.Transparent : $r('app.color.color_222222')) + .borderRadius(4) + .padding({ left: 14, right: 14 }) + .margin({ left: 12 }) + .onClick(() => { + this.type = FeedbackType.OTHER + }) + }.width('100%') + .margin({ top: 16 }) + + Row() { + Text('问题描述').fontColor($r('app.color.color_90ffffff')).fontSize(14) + Text('*').fontColor('#FF3838') + } + .width('100%') + .margin({ top: 20 }) + + RelativeContainer() { + TextArea({ placeholder: '请输入详情内容' }) + .height('100%') + .placeholderColor($r('app.color.color_30ffffff')) + .placeholderFont({ size: 14 }) + .fontSize(14) + .fontColor($r('app.color.color_80ffffff')) + .maxLength(200) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + .onChange((value: string) => { + this.contentText = value + }) + + Text(`${this.contentText.length}/200`).fontColor($r('app.color.color_999999')).fontSize(10) + .alignRules({ + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .margin({ right: 8, bottom: 8 }) + }.height(142).margin({ top: 16 }) + + Row() { + Text('上传图片').fontColor($r('app.color.color_90ffffff')).fontSize(14) + Text('(选填,最多可上传3张)').fontColor($r('app.color.color_999999')).fontSize(10) + } + .width('100%') + .margin({ top: 20 }) + + Grid() { + ForEach(this.selectedPhotos, (item: string, index) => { + GridItem() { + AddImageItemView({ uri: item, index: index, onDelete: (uri) => { + for (let i = 0; i < this.selectedPhotos.length; i++) { + const photo = this.selectedPhotos[i]; + if (photo === uri) { + this.selectedPhotos.splice(i, 1) + this.selectedUris.splice(i, 1) + break + } + } + } }) + } + }) + GridItem() { + AddImageItemView({index: this.selectedPhotos.length}) + .onClick(() => { + PhotoHelper.select({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE, + maxSelectNumber: 3, + preselectedUris: this.selectedUris, + isOriginalSupported: false + }) + .then((result: photoAccessHelper.PhotoSelectResult) => { + if (result.photoUris.length > 0) { + this.selectedPhotos = result.photoUris + this.selectedUris = result.photoUris + } + }) + .catch((e: BusinessError) => { + ToastUtils.show(e.message) + }) + }) + } + .visibility(this.selectedImages.length < 3 ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height(80) + .scrollBar(BarState.Off) + .columnsTemplate('1fr 1fr 1fr') + .margin({ top: 16 }) + + Row() { + Text('联系方式').fontColor($r('app.color.color_90ffffff')).fontSize(14) + Text('(选填,可留下您的手机号、微信、邮箱)').fontColor($r('app.color.color_999999')).fontSize(10) + } + .width('100%') + .margin({ top: 20 }) + + TextInput({ placeholder: '请输入您的联系方式' }) + .height(50) + .placeholderColor($r('app.color.color_30ffffff')) + .placeholderFont({ size: 14 }) + .fontSize(14) + .fontColor($r('app.color.color_80ffffff')) + .maxLength(200) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + .margin({ top: 16 }) + .onChange((value: string) => { + this.contactText = value + }) + } + .height('100%') + .padding({ left: 16, right: 16 }) + } + .scrollBar(BarState.Off) + .layoutWeight(1) + .margin({ top: 16 }) + + Stack() { + Button('提交', { type: ButtonType.Capsule, stateEffect: true }) + .fontColor(Color.White) + .fontSize(15) + .fontWeight(FontWeight.Medium) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .width('100%') + .height(46) + .onClick(() => { + this.commit() + }) + } + .padding({ left: 16, right: 16 }) + .margin({ bottom: 30 }) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/user/UserSettingsPage.ets b/entry/src/main/ets/pages/main/mine/user/UserSettingsPage.ets new file mode 100644 index 0000000..39c75c0 --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/user/UserSettingsPage.ets @@ -0,0 +1,108 @@ +import { UserEntity } from '../../../../entity/UserEntity' +import { TitleBar } from '../../../../view/TitleBar' +import { UserSettingsViewModel } from '../../../../viewModel/UserSettingsViewModel' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import { PhotoHelper } from '@pura/picker_utils' +import { ToastUtils } from '../../../../utils/ToastUtils' +import { BusinessError } from '@kit.BasicServicesKit' +import { UploadImgEntity } from '../../../../entity/UploadImgEntity' +import { EditTextDialog } from '../../../../dialog/EditTextDialog' +import { StrUtil } from '@pura/harmony-utils' + +@Entry +@ComponentV2 +struct UserSettingsPage { + viewModel: UserSettingsViewModel = new UserSettingsViewModel(this.getUIContext()) + + @Local userinfo?: UserEntity + + @Monitor('viewModel.userEntity') + onUserinfoChange(monitor: IMonitor) { + this.userinfo = monitor.value()?.now as UserEntity; + } + + @Monitor('viewModel.imageEntity') + onImageChange(monitor: IMonitor) { + const image = monitor.value()?.now as UploadImgEntity; + const params: Record = { + 'avater': image.url + } + this.viewModel.updateUserinfo(params) + } + + @Monitor('viewModel.update') + onUpdate(monitor: IMonitor) { + this.viewModel.userinfo() + ToastUtils.show('修改成功') + } + + aboutToAppear(): void { + this.viewModel.userinfo() + } + + build() { + Column() { + TitleBar() + + Image(StrUtil.isNotEmpty(this.userinfo?.avater) ? this.userinfo?.avater : $r('app.media.ic_default_avatar')) + .width(100) + .height(100) + .borderRadius(50) + .margin({ top: 40 }) + .padding(1) + .backgroundColor(Color.White) + .onClick(() => { + PhotoHelper.select({ + MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE, + maxSelectNumber: 1, + isOriginalSupported: false + }) + .then((result: photoAccessHelper.PhotoSelectResult) => { + if (result.photoUris.length > 0) { + this.viewModel.uploadImage(result.photoUris[0]) + } + }) + .catch((e: BusinessError) => { + ToastUtils.show(e.message) + }) + }) + + Text('昵称').fontSize(15).fontColor($r('app.color.color_90ffffff')).margin({ left: '6%', top: 55 }).alignSelf(ItemAlign.Start) + + Text(this.userinfo?.name) + .width('88%') + .height(48) + .fontSize(15) + .fontColor($r('app.color.color_90ffffff')) + .maxLines(1) + .padding({ left: 14, right: 14 }) + .margin({ top: 12 }) + .borderRadius(25) + .backgroundColor($r('app.color.color_222222')) + .onClick(() => { + EditTextDialog.show(this.getUIContext(), { title: '编辑昵称', content: this.userinfo?.name, confirm: (content) => { + const params: Record = { + 'name': content + } + this.viewModel.updateUserinfo(params) + }}) + }) + + Text('会员').fontSize(15).fontColor($r('app.color.color_90ffffff')).margin({ left: '6%', top: 30 }).alignSelf(ItemAlign.Start) + + Text(this.userinfo?.vip === 1 ? '未开通' : this.userinfo?.vip === 2 ? this.userinfo?.vip_expire + ' 到期' : '终身会员') + .width('88%') + .height(48) + .fontSize(15) + .fontColor($r('app.color.color_90ffffff')) + .maxLines(1) + .padding({ left: 14, right: 14 }) + .margin({ top: 12 }) + .borderRadius(25) + .backgroundColor($r('app.color.color_222222')) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/mine/vip/VipPage.ets b/entry/src/main/ets/pages/main/mine/vip/VipPage.ets new file mode 100644 index 0000000..b62ae5e --- /dev/null +++ b/entry/src/main/ets/pages/main/mine/vip/VipPage.ets @@ -0,0 +1,453 @@ +import { AppUtil, NumberUtil, StrUtil } from '@pura/harmony-utils'; +import { ErrCode, PayResp } from '@tencent/wechat_open_sdk'; +import { Constants } from '../../../../common/Constants'; +import { RouterUrls } from '../../../../common/RouterUrls'; +import { LoadingDialog } from '../../../../dialog/LoadingDialog'; +import { PayOrderEntity } from '../../../../entity/OrderPayEntity'; +import { VipMealEntity } from '../../../../entity/VipMealEntity'; +import { LoginManager } from '../../../../manager/LoginGlobalManager'; +import { ConfigManager } from '../../../../manager/UserConfigManager'; +import { PayUtils } from '../../../../utils/PayUtils'; +import { ToastUtils } from '../../../../utils/ToastUtils'; +import { OnWXResp, WXApi, WXEventHandler } from '../../../../utils/wechat/WXApiEventHandlerImpl'; +import { TitleBar } from '../../../../view/TitleBar'; +import { VipMealItemView } from '../../../../view/VipMealItemView'; +import { VipViewModel } from '../../../../viewModel/VipViewModel'; +import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'; +import router from '@ohos.router'; +import { EventConstants } from '../../../../common/EventConstants'; +import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager'; +import { SimpleTipDialog } from '../../../../dialog/SimpleTipDialog'; +import { OrderEntity } from '../../../../entity/OrderEntity'; +import { TipDialog } from '../../../../dialog/TipDialog'; + +@Entry +@ComponentV2 +struct VipPage { + @Local origin: string = 'center'; + @Local isGuide: boolean = false; + @Local vipMealList: Array = []; + @Local vipMeal?: VipMealEntity; + @Local isAgree: boolean = false; + @Local payType: number = 0; //0微信支付 1支付宝支付 + @Local totalPrice: number = 0; + + showQueryTip: boolean = false //是否显示支付状态查询提示 + + viewModel: VipViewModel = new VipViewModel(this.getUIContext()); + orderEntity?: PayOrderEntity; + pageStartTime: number = systemDateTime.getTime() + + //从微信返回的回调 + onWXResp: OnWXResp = (resp) => { + //微信返回的数据 + if (resp instanceof PayResp) { + const payResult = JSON.stringify(resp ?? {}, null, 2); + const errCode = JSON.parse(payResult).errCode as number; + if (errCode === ErrCode.ERR_OK) { + this.viewModel.getOrderInfo(this.orderEntity!!.orderId) + } else { + ToastUtils.show(JSON.parse(payResult).errStr); + EventReportGlobalManager.eventReport( + EventConstants.ERROR_CLIENT_WXPAY_ERR, + `{isGuide:${this.isGuide}, orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.vipMeal)}}`, + JSON.parse(payResult).errStr + ) + } + } + LoadingDialog.dismiss(); + } + + @Monitor('viewModel.goodsList') + onGoodsListChange(monitor: IMonitor) { + const list = monitor.value()?.now as Array; + this.vipMealList = list; + if (this.vipMealList.length > 0) { + this.vipMeal = this.vipMealList.find(item => item.checked === true); + if (!this.vipMeal) { + this.vipMeal = this.vipMealList[0]; + } + if (this.vipMeal) { + this.totalPrice = NumberUtil.toNumber(this.vipMeal.price); + this.releasePayType(); + } + } + } + + @Monitor('viewModel.payOrderEntity') + onPayOrderChange(monitor: IMonitor) { + const payOrderEntity = monitor.value()?.now as PayOrderEntity; + this.orderEntity = payOrderEntity; + if (this.payType === 0) { + if (StrUtil.isNotEmpty(this.vipMeal?.weixinMpOriId)) { + PayUtils.toWXMPPay(payOrderEntity.outTradeNo, this.vipMeal!!.weixinMpOriId) + this.showQueryTip = true + } else { + PayUtils.toWXPay(payOrderEntity); + } + } else { + PayUtils.toAliPay(payOrderEntity.payParam) + .then((result) => { + this.parseAlipayResult(result); + }) + .catch((error: BusinessError) => { + console.log(error.message); + ToastUtils.show('支付失败'); + }); + } + } + + @Monitor('viewModel.orderInfoEntity') + onOrderInfoChange(monitor: IMonitor) { + const orderEntity = monitor.value()?.now as OrderEntity; + if (orderEntity.status == "2") { + ToastUtils.show('支付成功'); + EventReportGlobalManager.eventReport( + EventConstants.PAY_SUCCESS, + 'alipay', `{isGuide:${this.isGuide}, orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.vipMeal)}}` + ) + this.doBack(); + } + } + + aboutToAppear() { + WXEventHandler.registerOnWXRespCallback(this.onWXResp) + this.initParams(); + this.viewModel.mealList(); + } + + aboutToDisappear() { + WXEventHandler.unregisterOnWXRespCallback(this.onWXResp) + this.viewModel.cancelInterval() + } + + onPageShow(): void { + if (this.showQueryTip) { + TipDialog.show(this.getUIContext(), { title: '温馨提示', content: '是否已完成支付', leftText: '未支付', rightText: '已支付', callback: { + confirm: () => { + this.viewModel.getOrderInfo(this.orderEntity!!.orderId) + } + } }, false) + this.showQueryTip = false + } + } + + initParams() { + const params = this.getUIContext().getRouter().getParams() as Record; + if (params) { + this.origin = params.origin as string; + this.isGuide = this.origin === 'bootpage'; + } + } + + createOrder() { + if (!ConfigManager.isNoLoginPayEnable() && !LoginManager.isLogin()) { + ToastUtils.show('请登录后支付'); + this.getUIContext().getRouter().pushUrl({ url: RouterUrls.LOGIN_PAGE, params: { from: 1 } }, router.RouterMode.Single); + return; + } + if (this.payType === 0 && !WXApi.isWXAppInstalled()) { + ToastUtils.show('请先下载安装微信客户端'); + return; + } + if (this.payType === 0) { + if (StrUtil.isNotEmpty(this.vipMeal!!.weixinMpOriId)) { + this.viewModel.createOrder(this.vipMeal?.goods_id!!, 'combo', this.origin, ''); + } else { + this.viewModel.createOrder(this.vipMeal?.goods_id!!, 'weixin', this.origin, ''); + } + } else { + this.viewModel.createOrder(this.vipMeal?.goods_id!!, 'alipay', this.origin, ''); + } + } + + parseAlipayResult(result: Map) { + const resultStatus = result.get('resultStatus'); + if (resultStatus === '9000') { + this.viewModel.getOrderInfo(this.orderEntity!!.orderId) + } else if (resultStatus === '6001') { + ToastUtils.show('支付取消'); + EventReportGlobalManager.eventReport( + EventConstants.PAY_CANCEL, + 'alipay', `{isGuide:${this.isGuide}, orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.vipMeal)}}` + ) + } else { + ToastUtils.show('支付失败'); + EventReportGlobalManager.eventReport( + EventConstants.ERROR_CLIENT_ALIPAY_ERR, + `{orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.vipMeal)}}`, resultStatus + ) + } + } + + formatPricePeriod(value?: string): string { + if (value === '#') { + return '/终生'; + } else if (value?.endsWith('m')) { + const monthStr = value.replace('m', ''); + if (StrUtil.isNotEmpty(monthStr)) { + const month = NumberUtil.toInt(monthStr); + if (month >= 12) { + return `/${month / 12 > 1 ? month / 12 : ''}年` + } else if (month % 3 === 0) { + return `/${month / 3 > 1 ? month / 3 : ''}季` + } else { + return `/${month > 1 ? month : ''}月` + } + } + } + return ''; + } + + releasePayType() { + if (this.vipMeal?.pay_type.startsWith('alipay')) { + this.payType = 1; + } else if (this.vipMeal?.pay_type.startsWith('weixin')) { + this.payType = 0; + } + } + + pageDuration(): string { + let duration = systemDateTime.getTime() - this.pageStartTime + if (duration < 1000) { + return `停留时间:${duration}ms` + } else { + return `停留时间:${Math.trunc(duration / 1000)}s` + } + } + + doBack() { + if (this.isGuide) { + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.MAIN_PAGE }, router.RouterMode.Single); + AppUtil.getContext().eventHub.emit(EventConstants.HomeRefreshEvent); + AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent); + } else { + this.getUIContext().getRouter().back(); + } + } + + onBackPress(): boolean | void { + if (this.isGuide) { + EventReportGlobalManager.eventReport(EventConstants.GUIDE_SKIP, "back", this.pageDuration()) + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.MAIN_PAGE }, router.RouterMode.Single) + return true; + } + } + + build() { + RelativeContainer() { + Column() { + Scroll() { + RelativeContainer() { + Image($r("app.media.ic_vip_top_bg")) + .width('100%') + .aspectRatio(1.4) + .id('iv_top_bg') + + Column() { + Image($r('app.media.ic_vip_tips')) + .width('100%') + .aspectRatio(0.997) + .margin({ left: 20, right: 20 }) + .id('iv_tips') + + List() { + ForEach(this.vipMealList, (item: VipMealEntity) => { + ListItem() { + VipMealItemView({ + entity: item, + isChecked: this.vipMeal?.goods_id === item.goods_id, + isGrid: this.vipMealList.length <= 3 + }) + } + .onClick(() => { + this.vipMealList.forEach((value) => { + value.checked = false; + }) + item.checked = true; + this.vipMeal = item; + this.totalPrice = NumberUtil.toNumber(item.price); + this.releasePayType(); + EventReportGlobalManager.eventReport(EventConstants.GOODS_SELECT, `${this.origin}:${item.goods_name}`, JSON.stringify(item)) + }) + }) + } + .width('auto') + .height('auto') + .listDirection(this.vipMealList.length > 3 ? Axis.Horizontal : Axis.Vertical) + .alignListItem(ListItemAlign.Center) + .margin({ left: 14, top: 30, right: 14 }) + .lanes(this.vipMealList.length > 3 ? 1 : 3) + .id('li_meals') + + Row() { + Image($r('app.media.ic_coupon')).width(30).height(30) + Text('优惠券').fontColor(Color.White).fontSize(15).margin({ left: 12 }) + Blank().layoutWeight(1) + Text('暂无可用的优惠券').fontColor($r('app.color.color_50ffffff')).fontSize(13) + Image($r('app.media.ic_arrow_dp16')).width(16).height(16) + } + .padding({ left: 20, right: 16 }) + .margin({ top: 22 }) + .visibility(Visibility.None) + + Text('支付方式') + .fontColor(Color.White) + .fontSize(14) + .fontWeight(FontWeight.Medium) + .margin({ left: 20, top: 22 }) + .alignSelf(ItemAlign.Start) + + Row() { + Image($r('app.media.ic_wx_pay')).width(30).height(30) + Text('微信').fontColor('#CCEEEEEE').fontSize(15).margin({ left: 12 }) + Blank().layoutWeight(1) + Image(this.payType === 0 ? $r('app.media.ic_pay_true') : $r('app.media.ic_pay_false')) + .width(18) + .height(18) + } + .margin({ left: 20, top: 4, right: 20 }) + .padding({ top: 16, bottom: 16 }) + .visibility(this.vipMeal?.pay_type.includes('weixin') ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.payType = 0; + EventReportGlobalManager.eventReport(EventConstants.PAY_SELECT, 'weixin', this.origin) + }) + + Divider().color($r('app.color.color_10ffffff')).strokeWidth(1) + + Row() { + Image($r('app.media.ic_ali_pay')).width(30).height(30) + Text('支付宝').fontColor('#CCEEEEEE').fontSize(15).margin({ left: 12 }) + Blank().layoutWeight(1) + Image(this.payType === 1 ? $r('app.media.ic_pay_true') : $r('app.media.ic_pay_false')) + .width(18) + .height(18) + } + .margin({ left: 20, right: 20 }) + .padding({ top: 16, bottom: 16 }) + .visibility(this.vipMeal?.pay_type.includes('alipay') ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.payType = 1; + EventReportGlobalManager.eventReport(EventConstants.PAY_SELECT, "alipay", this.origin) + }) + + Divider().color($r('app.color.color_10ffffff')).strokeWidth(1) + + Row() { + Image(this.isAgree ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')) + .width(16) + .height(16) + Text() { + Span('我已阅读并同意') + Span('《会员服务协议规则》').fontColor($r("app.color.color_466afd")) + .onClick(() => { + this.getUIContext() + .getRouter() + .pushUrl({ + url: RouterUrls.WEB_PAGE, + params: { title: '会员服务协议规则', url: Constants.USER_AGREEMENT } + }) + }) + if (StrUtil.isNotEmpty(this.vipMeal?.sign_value) && this.payType === 1) { + Span('和') + Span('《自动续费服务规则》').fontColor($r("app.color.color_466afd")) + .onClick(() => { + this.getUIContext() + .getRouter() + .pushUrl({ + url: RouterUrls.WEB_PAGE, + params: { title: '自动续费服务规则', url: Constants.RENEW_AGREEMENT } + }) + }) + } + } + .fontColor($r('app.color.color_90ffffff')) + .fontSize(12) + .margin({ left: 4, top: 1 }) + .layoutWeight(1) + } + .alignItems(VerticalAlign.Top) + .margin(20) + .visibility(ConfigManager.isPayAgreementEnable() ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.isAgree = !this.isAgree; + }) + } + .alignRules({ + top: { anchor: 'iv_top_bg', align: VerticalAlign.Bottom } + }) + .margin({ top: -32 }) + } + .height('auto') + } + .layoutWeight(1) + .scrollBar(BarState.Off) + + Row() { + Text() { + Span('¥').fontSize(13) + Span(`${this.totalPrice}`).fontSize(24) + Span(this.formatPricePeriod(this.vipMeal?.value)).fontSize(13) + Span(' ') + Span('¥' + this.vipMeal?.origin_price) + .fontColor($r('app.color.color_30ffffff')) + .fontSize(12) + .decoration({ type: TextDecorationType.LineThrough, color: $r('app.color.color_30ffffff') }) + } + .fontColor('#94F2FE') + + Blank().layoutWeight(1) + + Text('立即开通') + .width(170) + .height(45) + .textAlign(TextAlign.Center) + .fontColor('#201847') + .fontSize(16) + .fontWeight(FontWeight.Medium) + .backgroundImage($r('app.media.ic_vip_pay_btn')) + .backgroundImageSize({ width: 170, height: 46 }) + .onClick(() => { + if (this.vipMeal) { + if (ConfigManager.isPayAgreementEnable() && !this.isAgree) { + SimpleTipDialog.show(this.getUIContext(), { title: '提示', content: StrUtil.isNotEmpty(this.vipMeal?.sign_value) && this.payType === 1 ? '请阅读并同意《会员服务协议规则》和《自动续费服务规则》' : '请阅读并同意《会员服务协议规则》', callback: { + confirm: () => { + this.isAgree = true + this.createOrder(); + EventReportGlobalManager.eventReport(EventConstants.PAY_PAY, this.payType === 0 ? 'weixin' : 'alipay', JSON.stringify(this.vipMeal)) + } + }}) + } else { + this.createOrder(); + EventReportGlobalManager.eventReport(EventConstants.PAY_PAY, this.payType === 0 ? 'weixin' : 'alipay', JSON.stringify(this.vipMeal)) + } + } + }) + } + .backgroundColor('#29253D') + .borderRadius({ topLeft: 20, topRight: 20 }) + .padding({ + top: 9, + left: 20, + right: 20, + bottom: 30 + }) + } + .width('100%') + .height('100%') + + TitleBar({ + onBackClick: () => { + if (this.isGuide) { + EventReportGlobalManager.eventReport(EventConstants.GUIDE_SKIP, "icon", this.pageDuration()) + } + this.doBack(); + } + }).width('100%') + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/record/AudioRecordPage.ets b/entry/src/main/ets/pages/main/record/AudioRecordPage.ets new file mode 100644 index 0000000..76d3760 --- /dev/null +++ b/entry/src/main/ets/pages/main/record/AudioRecordPage.ets @@ -0,0 +1,86 @@ +import { EmptyView, PageStatus } from '../../../view/EmptyView'; +import { AudioRecordItemView, VideoRecordItemView } from '../../../view/RecordItemView'; +import { AppUtil, PermissionUtil } from '@pura/harmony-utils'; +import { ToastUtils } from '../../../utils/ToastUtils'; +import { MediaRecordEntity } from '../../../entity/MediaRecordEntity'; +import { EventConstants } from '../../../common/EventConstants'; +import { MediaAction, MediaManager, MediaType } from '../../../manager/MediaManager'; +import { TipDialog } from '../../../dialog/TipDialog'; +import { photoAccessHelper } from '@kit.MediaLibraryKit'; +import { LocalMediaManager } from '../../../manager/LocalMediaManager'; +import { fileIo } from '@kit.CoreFileKit'; + +@ComponentV2 +export struct AudioRecordPage { + @Local mediaList: Array = []; + @Local rowCount: number = 1; + @Local isCheckAll: boolean = false + + aboutToAppear(): void { + this.refresh() + this.initObserver() + } + + initObserver() { + AppUtil.getContext().eventHub.on(EventConstants.MediaActionEvent, (type: MediaType, action: MediaAction) => { + if (type === MediaType.AUDIO) { + if (action === MediaAction.CLEAR && this.mediaList.length > 0) { + this.deleteAll() + } else { + this.refresh() + } + } + }) + + AppUtil.getContext().eventHub.on(EventConstants.RecordRefreshEvent, () => { + this.refresh() + }) + } + + async refresh() { + this.mediaList = await MediaManager.getAudioList() + } + + deleteAll() { + TipDialog.show(this.getUIContext(), { + title: '提示', content: '确定清空音频?', callback: { + confirm: () => { + try { + this.mediaList.forEach((item) => { + fileIo.unlinkSync(item.uri) + }) + LocalMediaManager.deleteAllAudios() + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.AUDIO, MediaAction.DELETE) + ToastUtils.show('删除成功') + } catch (e) { + ToastUtils.show('删除失败, 请到文件管理中手动删除') + } + } + } + }) + } + + build() { + Stack() { + Column() { + List() { + ForEach(this.mediaList, (item: MediaRecordEntity, index) => { + ListItem() { + AudioRecordItemView({ media: item }) + } + }) + } + .width('auto') + .layoutWeight(1) + .scrollBar(BarState.Off) + .margin({ left: 16, right: 16, bottom: 15 }) + } + + EmptyView({ + status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA, + noDataImage: $r('app.media.ic_empty_audio'), + noDataText: '暂无音频' + }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/record/ImageRecordPage.ets b/entry/src/main/ets/pages/main/record/ImageRecordPage.ets new file mode 100644 index 0000000..3ce8b2f --- /dev/null +++ b/entry/src/main/ets/pages/main/record/ImageRecordPage.ets @@ -0,0 +1,101 @@ +import { EmptyView, PageStatus } from '../../../view/EmptyView'; +import { ImageRecordItemView } from '../../../view/RecordItemView'; +import { AppUtil, PermissionUtil } from '@pura/harmony-utils'; +import { ToastUtils } from '../../../utils/ToastUtils'; +import { MediaRecordEntity } from '../../../entity/MediaRecordEntity'; +import { EventConstants } from '../../../common/EventConstants'; +import { MediaAction, MediaManager, MediaType } from '../../../manager/MediaManager'; +import { TipDialog } from '../../../dialog/TipDialog'; +import { photoAccessHelper } from '@kit.MediaLibraryKit'; +import { LocalMediaManager } from '../../../manager/LocalMediaManager'; + +@ComponentV2 +export struct ImageRecordPage { + @Local mediaList: Array = []; + @Local rowCount: number = 1; + @Local isCheckAll: boolean = false + + aboutToAppear(): void { + this.refresh() + this.initObserver() + } + + initObserver() { + AppUtil.getContext().eventHub.on(EventConstants.MediaActionEvent, (type: MediaType, action: MediaAction) => { + if (type === MediaType.IMAGE) { + if (action === MediaAction.CLEAR && this.mediaList.length > 0) { + this.deleteAll() + } else { + this.refresh() + } + } + }) + + AppUtil.getContext().eventHub.on(EventConstants.RecordRefreshEvent, () => { + this.refresh() + }) + } + + async refresh() { + this.mediaList = await MediaManager.getImageList() + this.rowCount = this.computeRowCount(this.mediaList) + } + + deleteAll() { + TipDialog.show(this.getUIContext(), { + title: '提示', content: '确定清空图片?', callback: { + confirm: () => { + let uriArray = new Array() + this.mediaList.forEach((item) => { + uriArray.push(item.uri!!) + }) + photoAccessHelper.MediaAssetChangeRequest.deleteAssets(AppUtil.getContext(), uriArray) + .then(() => { + LocalMediaManager.deleteAllImages() + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.IMAGE, MediaAction.DELETE) + ToastUtils.show('删除成功') + }) + .catch(() => { + ToastUtils.show('删除失败, 请到相册中手动删除') + }) + } + } + }) + } + + computeRowCount(list: Array): number { + if (list.length > 8) { + return 3; + } else if (list.length >= 3 && list.length <= 8) { + return 2; + } else { + return 1; + } + } + + build() { + Stack() { + Column() { + Grid() { + ForEach(this.mediaList, (item: MediaRecordEntity, index) => { + GridItem() { + ImageRecordItemView({ media: item, rowCount: this.rowCount }) + } + }) + } + .scrollBar(BarState.Off) + .columnsTemplate(this.rowCount === 1 ? '1fr' : this.rowCount === 2 ? '1fr 1fr' : '1fr 1fr 1fr') + .rowsGap(10) + .columnsGap(10) + .margin({ left: 16, right: 16, bottom: 15 }) + .layoutWeight(1) + } + + EmptyView({ + status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA, + noDataImage: $r('app.media.ic_empty_image'), + noDataText: '暂无图片' + }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/record/RecordPage.ets b/entry/src/main/ets/pages/main/record/RecordPage.ets new file mode 100644 index 0000000..8ab72d4 --- /dev/null +++ b/entry/src/main/ets/pages/main/record/RecordPage.ets @@ -0,0 +1,137 @@ +import { TitleBar } from '../../../view/TitleBar'; +import { ImageRecordPage } from './ImageRecordPage'; +import { VideoRecordPage } from './VideoRecordPage'; +import { AppUtil } from '@pura/harmony-utils'; +import { EventConstants } from '../../../common/EventConstants'; +import { MediaAction, MediaType } from '../../../manager/MediaManager'; +import { AudioRecordPage } from './AudioRecordPage'; +import { CommonModifier } from '@kit.ArkUI'; + +@ComponentV2 +export struct RecordPage { + @Consumer() recordIndex: number = 0; + @Local tabBarModifier: CommonModifier = new CommonModifier() + @Local currentIndex: number = 0; + @Local indicatorLeftMargin: number = 0; + @Local indicatorWidth: number = 0; + @Local tabsWidth: number = 0; + + tabController: TabsController = new TabsController(); + titles: Array = ['视频', '图片', '音频']; + + aboutToAppear(): void { + this.initObserver() + this.tabBarModifier.align(Alignment.Start) + } + + initObserver() { + AppUtil.getContext().eventHub.on(EventConstants.MediaActionEvent, (type: MediaType, action: MediaAction) => { + if (action === MediaAction.ADD) { + switch (type) { + case MediaType.VIDEO: { + if (this.currentIndex !== 0) { + this.tabController.changeIndex(0) + this.currentIndex = 0 + } + break + } + case MediaType.IMAGE: { + if (this.currentIndex !== 1) { + this.tabController.changeIndex(1) + this.currentIndex = 1 + } + break + } + case MediaType.AUDIO: { + if (this.currentIndex !== 2) { + this.tabController.changeIndex(2) + this.currentIndex = 2 + } + break + } + } + } + }) + } + + @Builder + tabBuilder(title: string, tabIndex: number) { + Text(title) + .width('auto') + .height(50) + .fontColor(this.currentIndex === tabIndex ? $r("app.color.color_466afd") : $r('app.color.color_50ffffff')) + .fontSize(this.currentIndex === tabIndex ? 17 : 14) + .fontWeight(this.currentIndex === tabIndex ? FontWeight.Medium : FontWeight.Regular) + .margin({ left: tabIndex === 0 ? 16 : 40 }) + } + + build() { + Column() { + TitleBar({title: '我的记录', showBack: false}) + Stack({alignContent: Alignment.TopEnd}) { + Tabs({ barPosition: BarPosition.Start, controller: this.tabController, barModifier: this.tabBarModifier }) { + TabContent() { + VideoRecordPage() + } + .tabBar(this.tabBuilder(this.titles[0], 0)) + + TabContent() { + ImageRecordPage() + } + .tabBar(this.tabBuilder(this.titles[1], 1)) + + TabContent() { + AudioRecordPage() + } + .tabBar(this.tabBuilder(this.titles[2], 2)) + } + .scrollable(false) + .barMode(BarMode.Scrollable) + .onTabBarClick((index) => { + this.currentIndex = index; + }) + + Row() { + Image($r('app.media.ic_clear_record')).width(16).height(16) + Text('全部清空').fontColor($r('app.color.color_80ffffff')).fontSize(12).margin({left: 4}) + } + .height(50) + .margin({ top: 3, right: 16 }) + .onClick(() => { + switch (this.currentIndex) { + case 0: { + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.VIDEO, MediaAction.CLEAR) + } + case 1: { + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.IMAGE, MediaAction.CLEAR) + } + case 2: { + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.AUDIO, MediaAction.CLEAR) + } + } + }) + .visibility(this.currentIndex === 2 ? Visibility.Visible : Visibility.None) + }.layoutWeight(1) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } + + @Builder + tab(tabName: string, tabIndex: number) { + Row() { + Text(tabName) + .fontSize(this.currentIndex === tabIndex ? 17 : 14) + .fontWeight(this.currentIndex === tabIndex ? FontWeight.Medium : FontWeight.Regular) + .lineHeight(24) + .fontColor(tabIndex === this.currentIndex ? $r("app.color.color_466afd") : $r('app.color.color_50ffffff')) + } + .width('auto') + .height('auto') + .onClick(() => { + this.tabController.changeIndex(tabIndex); + this.currentIndex = tabIndex; + }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/main/record/VideoRecordPage.ets b/entry/src/main/ets/pages/main/record/VideoRecordPage.ets new file mode 100644 index 0000000..6dff1d2 --- /dev/null +++ b/entry/src/main/ets/pages/main/record/VideoRecordPage.ets @@ -0,0 +1,101 @@ +import { EmptyView, PageStatus } from '../../../view/EmptyView'; +import { VideoRecordItemView } from '../../../view/RecordItemView'; +import { AppUtil, PermissionUtil } from '@pura/harmony-utils'; +import { ToastUtils } from '../../../utils/ToastUtils'; +import { MediaRecordEntity } from '../../../entity/MediaRecordEntity'; +import { EventConstants } from '../../../common/EventConstants'; +import { MediaAction, MediaManager, MediaType } from '../../../manager/MediaManager'; +import { TipDialog } from '../../../dialog/TipDialog'; +import { photoAccessHelper } from '@kit.MediaLibraryKit'; +import { LocalMediaManager } from '../../../manager/LocalMediaManager'; + +@ComponentV2 +export struct VideoRecordPage { + @Local mediaList: Array = []; + @Local rowCount: number = 1; + @Local isCheckAll: boolean = false + + aboutToAppear(): void { + this.refresh() + this.initObserver() + } + + initObserver() { + AppUtil.getContext().eventHub.on(EventConstants.MediaActionEvent, (type: MediaType, action: MediaAction) => { + if (type === MediaType.VIDEO) { + if (action === MediaAction.CLEAR && this.mediaList.length > 0) { + this.deleteAll() + } else { + this.refresh() + } + } + }) + + AppUtil.getContext().eventHub.on(EventConstants.RecordRefreshEvent, () => { + this.refresh() + }) + } + + async refresh() { + this.mediaList = await MediaManager.getVideoList() + this.rowCount = this.computeRowCount(this.mediaList) + } + + deleteAll() { + TipDialog.show(this.getUIContext(), { + title: '提示', content: '确定清空视频?', callback: { + confirm: () => { + let uriArray = new Array() + this.mediaList.forEach((item) => { + uriArray.push(item.uri!!) + }) + photoAccessHelper.MediaAssetChangeRequest.deleteAssets(AppUtil.getContext(), uriArray) + .then(() => { + LocalMediaManager.deleteAllVideos() + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.VIDEO, MediaAction.DELETE) + ToastUtils.show('删除成功') + }) + .catch(() => { + ToastUtils.show('删除失败, 请到相册中手动删除') + }) + } + } + }) + } + + computeRowCount(list: Array): number { + if (list.length > 8) { + return 3; + } else if (list.length >= 3 && list.length <= 8) { + return 2; + } else { + return 1; + } + } + + build() { + Stack() { + Column() { + Grid() { + ForEach(this.mediaList, (item: MediaRecordEntity, index) => { + GridItem() { + VideoRecordItemView({ media: item, rowCount: this.rowCount }) + } + }) + } + .scrollBar(BarState.Off) + .columnsTemplate(this.rowCount === 1 ? '1fr' : this.rowCount === 2 ? '1fr 1fr' : '1fr 1fr 1fr') + .rowsGap(10) + .columnsGap(10) + .margin({ left: 16, right: 16, bottom: 15 }) + .layoutWeight(1) + } + + EmptyView({ + status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA, + noDataImage: $r('app.media.ic_empty_video'), + noDataText: '暂无视频' + }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/photo/PhotoViewPage.ets b/entry/src/main/ets/pages/photo/PhotoViewPage.ets new file mode 100644 index 0000000..745e572 --- /dev/null +++ b/entry/src/main/ets/pages/photo/PhotoViewPage.ets @@ -0,0 +1,32 @@ +import { TitleBar } from '../../view/TitleBar' + +@Entry +@ComponentV2 +struct PhotoViewPage { + @Local title: string = '' + @Local uri?: string = '' + + aboutToAppear(): void { + this.initParams() + } + + initParams() { + const params = this.getUIContext().getRouter().getParams() as Record; + if (params) { + this.title = params.title as string + this.uri = params.uri as string; + } + } + + build() { + Column() { + TitleBar({title: this.title}).width('100%') + Image(this.uri).width('100%').layoutWeight(1) + .margin({bottom: 50}) + .objectFit(ImageFit.Contain) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/splash/SplashPage.ets b/entry/src/main/ets/pages/splash/SplashPage.ets new file mode 100644 index 0000000..8363d50 --- /dev/null +++ b/entry/src/main/ets/pages/splash/SplashPage.ets @@ -0,0 +1,149 @@ +import { ConfigManager } from '../../manager/UserConfigManager'; +import { RouterUrls } from '../../common/RouterUrls'; +import { PrivacyPolicyDialog } from '../../dialog/PrivacyPolicyDialog'; +import { common } from '@kit.AbilityKit'; +import { LevelMode, router } from '@kit.ArkUI'; +import { LoginManager } from '../../manager/LoginGlobalManager'; +import { LoadingDialog } from '../../dialog/LoadingDialog'; +import { AppUtil, StrUtil } from '@pura/harmony-utils'; +import { EventReportGlobalManager } from '../../manager/EventReportGlobalManager'; +import { EventConstants } from '../../common/EventConstants'; +import { GyConfig, GyManager } from '@getui/gysdk'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +@Entry +@ComponentV2 +struct SplashPage { + privacyDialogController?: CustomDialogController | null; + + UIContext = this.getUIContext(); + uiAbilityContext = this.UIContext.getHostContext() as common.UIAbilityContext; + private windowClass = this.uiAbilityContext.windowStage.getMainWindowSync(); + + aboutToAppear(): void { + this.registerFont() + this.windowClass.setWindowLayoutFullScreen(true) + if (ConfigManager.isAgreePrivacy()) { + LoadingDialog.show(this.getUIContext()); + ConfigManager.getOaid() + .then(() => { + ConfigManager.getUserConfig() + .then(() => { + LoadingDialog.dismiss(); + // this.initGY(); + this.toNextPage(); + }); + }) + .catch((error: Error) => { + LoadingDialog.dismiss(); + console.error(error.message) + }) + } else { + this.showPrivacyDialog(); + } + } + + aboutToDisappear() { + this.privacyDialogController = null; + } + + showPrivacyDialog() { + this.privacyDialogController = new CustomDialogController({ + builder: PrivacyPolicyDialog({ + confirm: () => { + EventReportGlobalManager.eventReport(EventConstants.APP_LAUNCH, '', '') + LoadingDialog.show(this.getUIContext()); + ConfigManager.saveIsAgreePrivacy(true); + ConfigManager.getOaid() + .then(() => { + ConfigManager.getUserConfig() + .then(() => { + LoadingDialog.dismiss(); + // this.initGY(); + this.toNextPage(); + }); + }) + .catch((error: Error) => { + LoadingDialog.dismiss(); + console.error(error.message) + }) + }, + cancel: () => { + (this.getUIContext().getHostContext() as common.UIAbilityContext)?.terminateSelf() + } + }), + onWillDismiss: () => { + return false + }, + width: '80%', + cornerRadius: 20, + maskColor: '#CC000000', + levelMode: LevelMode.EMBEDDED, + backgroundBlurStyle: BlurStyle.NONE + }) + this.privacyDialogController.open(); + } + + async initGY() { + const response = await GyManager.getInstance().init(new GyConfig(AppUtil.getContext()) + .setDebug(false) + .setPreLoginUseCache(true) + .setChannel("scmf_hmos")) + if (response.isSuccess()) { + hilog.debug(0x0000, "GY", `init success ==>${response}`) + } else { + hilog.debug(0x0000, "GY", `init failed==>${response}`) + } + } + + toNextPage() { + if (LoginManager.isLogin()) { + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.MAIN_PAGE }, router.RouterMode.Single) + } else { + if (ConfigManager.isFirstUse()) { + ConfigManager.saveFirstUse(false); + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.GUIDE_PAGE }, router.RouterMode.Single) + } else { + if (StrUtil.isNotEmpty(LoginManager.getLastLoginType())) { + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.LOGIN_PAGE }, router.RouterMode.Single) + } else { + this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.MAIN_PAGE }, router.RouterMode.Single) + } + } + } + } + + registerFont() { + this.getUIContext().getFont().registerFont({ + familyName: "almmsht", + familySrc: $rawfile('font/almmShuHeiTi.ttf') + }) + this.getUIContext().getFont().registerFont({ + familyName: "ddp500m", + familySrc: $rawfile('font/ddp500m.otf') + }) + this.getUIContext().getFont().registerFont({ + familyName: "ysbth", + familySrc: $rawfile('font/youSheBiaoTiHei.ttf') + }) + } + + build() { + Stack({alignContent: Alignment.Top}) { + Image($r('app.media.ic_splash_bg')) + .width('100%') + .height('100%') + + Image($r('app.media.ic_splash_logo')) + .width(288) + .height(288) + .margin({top: 50}) + } + .width('100%') + .height('100%') + } + + onBackPress(): boolean | void { + return true; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/video/VideoPlayerPage.ets b/entry/src/main/ets/pages/video/VideoPlayerPage.ets new file mode 100644 index 0000000..b7195c9 --- /dev/null +++ b/entry/src/main/ets/pages/video/VideoPlayerPage.ets @@ -0,0 +1,209 @@ +import { TitleBar } from '../../view/TitleBar' +import { ShareManager } from '../../manager/ShareManager' +import { PrefUtils } from '../../utils/PrefUtils' +import { SimpleTipDialog } from '../../dialog/SimpleTipDialog' +import { WantUtils } from '../../utils/WantUtils' +import { avSessionManager } from '../../manager/AVSessionManager' + +@Entry +@ComponentV2 +struct VideoPlayerPage { + private controller: VideoController = new VideoController() + @Local title: string = '' + @Local uri: string = '' + @Local showActions: boolean = false + @Local currentTime: number = 0 + @Local durationTime: number = 0 + @Local isPlaying: boolean = false + + aboutToAppear(): void { + this.initParams() + } + + initParams() { + const params = this.getUIContext().getRouter().getParams() as Record; + if (params) { + this.title = params.title as string + this.uri = params.uri as string; + this.showActions = params.showActions as boolean + } + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } + + build() { + Column() { + TitleBar().width('100%') + + RelativeContainer() { + Video({ + src: this.uri, // 设置视频源 + controller: this.controller, //设置视频控制器,可以控制视频的播放状态 + posterOptions: { showFirstFrame: true } + }) + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + .controls(false) // 设置是否显示默认控制条 + .autoPlay(true) // 设置是否自动播放 + .loop(false) // 设置是否循环播放 + .objectFit(ImageFit.Contain) // 设置视频填充模式 + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onStart(() => { + this.isPlaying = true + }) + .onPause(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onStop(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onFinish(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onError(() => { + this.isPlaying = false + avSessionManager.deactivate() + }) + .onDisAppear(() => { + avSessionManager.deactivate() + }) + + Image($r('app.media.ic_play_video')) + .width(50) + .height(50) + .visibility(this.isPlaying ? Visibility.None : Visibility.Visible) + .onClick(async () => { + await avSessionManager.activate() + this.controller.start() + }) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + }) + + Row() { + Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play')) + .width(20) + .height(20) + .margin({ right: 20 }) + .onClick(async () => { + if (this.isPlaying) { + this.controller.pause() + } else { + await avSessionManager.activate() + this.controller.start() + } + }) + Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .blockColor(Color.White) + .trackColor($r('app.color.color_60ffffff')) + .onChange((value: number, mode: SliderChangeMode) => { + this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处 + }) + .layoutWeight(1) + Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12) + } + .opacity(0.8) + .width("100%") + .padding({ left: 30, right: 30 }) + .margin({ bottom: 50 }) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + }.layoutWeight(1) + + Row() { + Column() { + Image($r('app.media.ic_action_share')).width(50).height(50) + Text('转发').fontSize(12).fontColor($r('app.color.color_50ffffff')).margin({ top: 13 }) + } + .id('btn_share') + .alignRules({ + right: { anchor: '__container__', align: HorizontalAlign.Center } + }) + .margin({ right: 40 }) + .onClick(() => { + ShareManager.shareFile(this.uri) + }) + + Column() { + Image($r('app.media.ic_action_delete')).width(50).height(50) + Text('删除').fontSize(12).fontColor($r('app.color.color_50ffffff')).margin({ top: 13 }) + } + .id('btn_delete') + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Center }, + }) + .margin({ left: 40 }) + .onClick(() => { + this.controller.pause() + if (PrefUtils.getBoolean('show_record_delete_tip', true)) { + SimpleTipDialog.show(this.getUIContext(), { + title: '提示', content: '因系统限制,请到相册中手动删除', buttonText: '知道了', callback: { + confirm: () => { + WantUtils.toPhotoGallery() + PrefUtils.put('show_record_delete_tip', false) + } + } + }) + } else { + WantUtils.toPhotoGallery() + } + }) + } + .height(200) + .margin({ top: 10 }) + .visibility(this.showActions ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.window_background')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/web/WebPage.ets b/entry/src/main/ets/pages/web/WebPage.ets new file mode 100644 index 0000000..8e8e71b --- /dev/null +++ b/entry/src/main/ets/pages/web/WebPage.ets @@ -0,0 +1,46 @@ +import { webview } from '@kit.ArkWeb'; +import { TitleBar } from '../../view/TitleBar'; +import { window } from '@kit.ArkUI'; + +@Entry +@ComponentV2 +struct WebPage { + windowStage: window.WindowStage = AppStorage.get("windowStage") as window.WindowStage; + + controller: webview.WebviewController = new webview.WebviewController(); + + @Local title: string = ''; + @Local url: string = ''; + + aboutToAppear(): void { + this.windowStage.getMainWindowSync().setWindowSystemBarProperties({ + statusBarColor: '#00000000', + statusBarContentColor: '#000000' + }); + this.initParams(); + } + + aboutToDisappear(): void { + this.windowStage.getMainWindowSync().setWindowSystemBarProperties({ + statusBarColor: '#00000000', + statusBarContentColor: '#ffffff' + }); + } + + initParams() { + const params = this.getUIContext().getRouter().getParams() as Record; + if (params) { + this.title = params.title as string; + this.url = params.url as string; + } + } + + build() { + Column() { + TitleBar({ title: this.title, isDark: false }).width('100%') + Web({ src: this.url, controller: this.controller }).width('100%').layoutWeight(1) + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/AESpkcs7paddingUtil.ets b/entry/src/main/ets/utils/AESpkcs7paddingUtil.ets new file mode 100644 index 0000000..c111960 --- /dev/null +++ b/entry/src/main/ets/utils/AESpkcs7paddingUtil.ets @@ -0,0 +1,11 @@ +import { AES, CryptoHelper, CryptoUtil, StrUtil } from '@pura/harmony-utils'; + +export class AESpkcs7paddingUtil { + static decryptNormal(encryptStr: string, key: string): string { + let dataBlob = CryptoHelper.strToDataBlob(encryptStr, 'base64'); + let keyBytes = CryptoUtil.getConvertSymKeySync('AES256', key, 'utf-8') + let ivParams= CryptoUtil.getIvParamsSpec(key.substring(0, 16), 'utf-8') + let plain = AES.decryptCBCSync(dataBlob, keyBytes, ivParams); + return StrUtil.unit8ArrayToStr(plain.data); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/MediaUtils.ets b/entry/src/main/ets/utils/MediaUtils.ets new file mode 100644 index 0000000..b11ae96 --- /dev/null +++ b/entry/src/main/ets/utils/MediaUtils.ets @@ -0,0 +1,38 @@ +import { media } from "@kit.MediaKit"; +import { FileUtil } from "@pura/harmony-utils"; +import { fileIo } from "@kit.CoreFileKit"; + +export class MediaUtils { + + static async getVideoSize(uri: string): Promise { + let videoSize: media.PixelMapParams = {}; + let avMetaDataExtractor: media.AVMetadataExtractor = await media.createAVMetadataExtractor(); + try { + let file = FileUtil.openSync(uri); + avMetaDataExtractor.fdSrc = file; + let metadata = await avMetaDataExtractor.fetchMetadata() + videoSize.width = parseInt(metadata.videoWidth as string); + videoSize.height = parseInt(metadata.videoHeight as string); + return Promise.resolve(videoSize) + } catch (e) { + let cacheFilePath = FileUtil.getCacheDirPath() + '/' + FileUtil.getFileName(uri) + try { + let file = FileUtil.openSync(uri, fileIo.OpenMode.READ_ONLY); + // 复制文件到缓存目录下 + FileUtil.copyFileSync(file.fd, cacheFilePath) + + avMetaDataExtractor.fdSrc = FileUtil.openSync(cacheFilePath); + let metadata = await avMetaDataExtractor.fetchMetadata() + videoSize.width = parseInt(metadata.videoWidth as string); + videoSize.height = parseInt(metadata.videoHeight as string); + return Promise.resolve(videoSize) + } catch (e) { + return Promise.reject(e) + } finally { + if (FileUtil.accessSync(cacheFilePath)) { + FileUtil.unlink(cacheFilePath) + } + } + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/PasteboardUtils.ets b/entry/src/main/ets/utils/PasteboardUtils.ets new file mode 100644 index 0000000..238975b --- /dev/null +++ b/entry/src/main/ets/utils/PasteboardUtils.ets @@ -0,0 +1,63 @@ +import { ConfigManager } from '../manager/UserConfigManager'; +import { pasteboard } from '@kit.BasicServicesKit'; +import { preferences } from '@kit.ArkData'; + +export class PasteboardUtils { + private static systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard(); + private static patterns: pasteboard.Pattern[] = [pasteboard.Pattern.URL, pasteboard.Pattern.EMAIL_ADDRESS]; + private static dataPreferences: preferences.Preferences | null = null; + + static clipText?: string + + static async isNeedGetPermissionFromUser(): Promise { + try { + let hasData: boolean = await PasteboardUtils.systemPasteboard.hasData() + if (!hasData) { + // 剪贴板不存在数据,无需申请权限 + return false + } + // 获取剪贴板的内容变化次数 + let result: number = PasteboardUtils.systemPasteboard.getChangeCount() + // 从 Preferences 中读取上次保存的 changeCount + let storedChangeCount: number = PasteboardUtils.dataPreferences ? Number(PasteboardUtils.dataPreferences.getSync('pasteboardChangeCount', 0)) : 0 + if (result === storedChangeCount) { + // 剪贴板无数据变化,无需申请权限 + return false + } + } catch (err) { + return false + } + + // 查询剪贴板是否存在应用所需数据类型 + try { + // (可选)判断是否有应用需要的数据类型 + let result: boolean = PasteboardUtils.systemPasteboard.hasDataType(pasteboard.MIMETYPE_TEXT_PLAIN) + if (!result) { + // 剪贴板不存在应用所需数据类型,无需申请权限 + return false + } + // (可选)涉及口令等应用自身特殊复制内容的,使用detectPatterns过滤口令格式 + let data: pasteboard.Pattern[] = await PasteboardUtils.systemPasteboard.detectPatterns(PasteboardUtils.patterns) + if (PasteboardUtils.patterns.sort().join('') != data.sort().join('')) { + return false + } + } catch (err) { + return false + } + return true + } + + /** + * 是否是有效链接 + */ + static isValidUrl(text: string): boolean { + let isValid = false + for (let i = 0;i < ConfigManager.getCopyContainsList().length;i++) { + if (text.includes(ConfigManager.getCopyContainsList()[i])) { + isValid = true + break + } + } + return isValid + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/PayUtils.ets b/entry/src/main/ets/utils/PayUtils.ets new file mode 100644 index 0000000..df126ba --- /dev/null +++ b/entry/src/main/ets/utils/PayUtils.ets @@ -0,0 +1,50 @@ +import { Constants } from '../common/Constants'; +import * as WxOpenSdk from '@tencent/wechat_open_sdk'; +import { WXApi } from './wechat/WXApiEventHandlerImpl'; +import { AppUtil } from '@pura/harmony-utils'; +import { PayOrderEntity } from '../entity/OrderPayEntity'; +import { Pay } from '@cashier_alipay/cashiersdk/src/main/ets/api/Pay'; +import { ToastUtils } from './ToastUtils'; + +export class PayUtils { + + /** + * 微信支付 + * @param orderEntity + */ + static async toWXPay(orderEntity: PayOrderEntity) { + let req = new WxOpenSdk.PayReq + req.partnerId = orderEntity.partnerId + req.appId = Constants.WX_APP_ID + req.packageValue = 'Sign=WXPay' + req.prepayId = orderEntity.prepayId + req.nonceStr = orderEntity.nonceStr + req.timeStamp = orderEntity.timeStamp + req.sign = orderEntity.sign + WXApi.sendReq(AppUtil.getContext(), req) + } + + /** + * 微信小程序支付 + */ + static async toWXMPPay(outTradeNo: string, mpAppId: string) { + try { + let launchMiniProgramReq = new WxOpenSdk.LaunchMiniProgramReq() + launchMiniProgramReq.userName = mpAppId //拉起的小程序的原始id + launchMiniProgramReq.path = `pages/index/index?outTradeNo=${outTradeNo}` + launchMiniProgramReq.miniprogramType = 0 //拉起小程序的类型 0-正式版 1-开发版 2-体验版 + await WXApi.sendReq(AppUtil.getContext(), launchMiniProgramReq) + } catch (e) { + ToastUtils.show('跳转失败,请联系客服') + } + } + + /** + * 支付宝支付 + * @param orderInfo + * @returns + */ + static async toAliPay(orderInfo: string): Promise> { + return new Pay().pay(orderInfo, true); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/PrefUtils.ets b/entry/src/main/ets/utils/PrefUtils.ets new file mode 100644 index 0000000..6204198 --- /dev/null +++ b/entry/src/main/ets/utils/PrefUtils.ets @@ -0,0 +1,68 @@ +import { preferences } from '@kit.ArkData'; +import { Context } from '@kit.AbilityKit'; +import { StrUtil } from '@pura/harmony-utils'; +import { JSON } from '@kit.ArkTS'; + + +export class PrefUtils { + private static dataPreferences?: preferences.Preferences; + private static readonly PREF_NAME = 'share_data' + + private constructor() { + } + + /** + * 初始化preferences,必须最先调用 + * @param context + */ + static init(context: Context) { + if (PrefUtils.dataPreferences == null) { + try { + let isGskvSupported = preferences.isStorageTypeSupported(preferences.StorageType.GSKV); + if (isGskvSupported) { + let options: preferences.Options = { name: PrefUtils.PREF_NAME, storageType: preferences.StorageType.GSKV }; + PrefUtils.dataPreferences = preferences.getPreferencesSync(context, options); + } else { + let options: preferences.Options = { name: PrefUtils.PREF_NAME, storageType: preferences.StorageType.XML }; + PrefUtils.dataPreferences = preferences.getPreferencesSync(context, options); + } + } catch (e) { + let options: preferences.Options = { name: PrefUtils.PREF_NAME }; + PrefUtils.dataPreferences = preferences.getPreferencesSync(context, options); + } + } + } + + static put(name: string, value: preferences.ValueType) { + if (value !== undefined) { + PrefUtils.dataPreferences?.putSync(name, value) + PrefUtils.dataPreferences?.flushSync() + } + } + + static getString(name: string): string { + return PrefUtils.dataPreferences?.getSync(name, "") as string; + } + + static getBoolean(name: string, defaultValue: boolean = false): boolean { + return PrefUtils.dataPreferences?.getSync(name, defaultValue) as boolean; + } + + static getNumber(name: string): number { + return PrefUtils.dataPreferences?.getSync(name, 0) as number; + } + + static getStringArray(name: string): Array { + let str = PrefUtils.dataPreferences?.getSync(name, '') as string + if (StrUtil.isNotEmpty(str)) { + return JSON.parse(str) as Array + } + return new Array() + } + + static remove(name: string) { + PrefUtils.dataPreferences?.deleteSync(name); + PrefUtils.dataPreferences?.flushSync() + } +} + diff --git a/entry/src/main/ets/utils/SaveUtils.ets b/entry/src/main/ets/utils/SaveUtils.ets new file mode 100644 index 0000000..7b5910a --- /dev/null +++ b/entry/src/main/ets/utils/SaveUtils.ets @@ -0,0 +1,118 @@ +import { PhotoHelper } from '@pura/picker_utils'; +import { photoAccessHelper } from '@kit.MediaLibraryKit'; +import { AppUtil, FileUtil, RandomUtil } from '@pura/harmony-utils'; +import { fileIo, fileUri, picker } from '@kit.CoreFileKit'; +import { EventConstants } from '../common/EventConstants'; +import { MediaAction, MediaType } from '../manager/MediaManager'; +import { LocalMediaManager } from '../manager/LocalMediaManager'; +import { systemDateTime } from '@kit.BasicServicesKit'; + +export class SaveUtils { + + /** + * 保存视频和图片到相册, 弹窗授权 + * @param path + * @param name + * @returns + */ + static async saveImageVideoToAlbumDialog(srcFileUris: Array, record: boolean = true): Promise { + try { + // 基于弹窗授权的方式获取媒体库的目标uri + let desFileUris: Array = await PhotoHelper.showAssetsCreationDialog(srcFileUris); + // 将来源于应用沙箱的照片内容写入媒体库的目标uri + for (let i = 0; i < srcFileUris.length; i++) { + let desFile: fileIo.File = await fileIo.open(desFileUris[i], fileIo.OpenMode.WRITE_ONLY); + let srcFile: fileIo.File = await fileIo.open(srcFileUris[i], fileIo.OpenMode.READ_ONLY); + await fileIo.copyFile(srcFile.fd, desFile.fd); + fileIo.closeSync(srcFile); + fileIo.closeSync(desFile); + + if (record) { + LocalMediaManager.add(desFileUris[i]) + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, srcFileUris[i].endsWith('mp4') ? MediaType.VIDEO : MediaType.IMAGE, MediaAction.ADD) + console.debug('保存成功') + } + } + return Promise.resolve(true) + } catch (e) { + console.error(e) + return Promise.resolve(false) + } + } + + /** + * 保存音频到本地, 无需ACL权限 + * @param path + * @param name + * @returns + */ + static async saveAudioToMusic(srcFileUris: Array): Promise { + try { + const documentViewPicker = new picker.DocumentViewPicker(AppUtil.getContext()) + const documentSaveOptions = new picker.DocumentSaveOptions() + documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD + const uriArray = await documentViewPicker.save(documentSaveOptions) + let dirUri = uriArray[0] + srcFileUris.forEach(async (srcUri) => { + const srcPath = FileUtil.getFilePath(srcUri) + const desUri = new fileUri.FileUri(dirUri + FileUtil.separator + FileUtil.getFileName(srcPath)).path + const desFile = FileUtil.openSync(desUri, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE) + await FileUtil.copyFile(srcPath, desFile.fd) + LocalMediaManager.add(desUri) + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.AUDIO, MediaAction.ADD) + FileUtil.closeSync(desFile.fd) + console.debug('保存成功') + }) + return Promise.resolve(true) + } catch (e) { + console.error(e) + return Promise.resolve(false) + } + } + + /** + * 保存视频到相册, 需要ACL权限 + * @param path + * @param name + * @returns + */ + static async saveVideoToAlbum(path: string, name: string): Promise { + try { + let name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp4` + const uri = await PhotoHelper.save(photoAccessHelper.PhotoType.VIDEO, 'mp4', { title: name.replace('.mp4', '') }); + let file = FileUtil.openSync(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); + await FileUtil.copyFile(path, file.fd) + LocalMediaManager.add(name) + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.VIDEO, MediaAction.ADD) + FileUtil.close(file) + console.debug('保存成功') + return Promise.resolve(true) + } catch (e) { + console.error(e) + return Promise.resolve(false) + } + } + + /** + * 保存图片到相册, 需要ACL权限 + * @param path + * @param name + * @returns + */ + static async saveImageToAlbum(path: string, name: string): Promise { + try { + if (FileUtil.accessSync(path)) name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.jpeg` + const uri = await PhotoHelper.save(photoAccessHelper.PhotoType.IMAGE, 'jpeg', { title: name.replace('.jpeg', '') }); + let file = FileUtil.openSync(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); + await FileUtil.copyFile(path, file.fd) + LocalMediaManager.add(name) + AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.IMAGE, MediaAction.ADD) + FileUtil.close(file) + console.debug('保存成功') + return Promise.resolve(true) + } catch (e) { + console.error(e) + return Promise.resolve(false) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/ToastUtils.ets b/entry/src/main/ets/utils/ToastUtils.ets new file mode 100644 index 0000000..a7aaf54 --- /dev/null +++ b/entry/src/main/ets/utils/ToastUtils.ets @@ -0,0 +1,20 @@ +import { StrUtil } from '@pura/harmony-utils' +import { promptAction } from '@kit.ArkUI' + +export class ToastUtils { + + static show(msg: string, isResponse: boolean = false) { + if (StrUtil.isEmpty(msg)) return + let message = msg + if (isResponse) { + let messageArray = message.split(',') + if (messageArray.length > 0) { + message = messageArray[0] + } + } + promptAction.showToast({ + message: message, + duration: 1000 + }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/WantUtils.ets b/entry/src/main/ets/utils/WantUtils.ets new file mode 100644 index 0000000..e248026 --- /dev/null +++ b/entry/src/main/ets/utils/WantUtils.ets @@ -0,0 +1,16 @@ +import { Want } from '@kit.AbilityKit' +import { AppUtil } from '@pura/harmony-utils' + +export class WantUtils { + + /** + * 跳转到相册 + */ + static toPhotoGallery() { + let want: Want = { + bundleName: 'com.huawei.hmos.photos', + abilityName: 'com.huawei.hmos.photos.MainAbility' + } + AppUtil.getContext().startAbility(want) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/wechat/WXApiEventHandlerImpl.ets b/entry/src/main/ets/utils/wechat/WXApiEventHandlerImpl.ets new file mode 100644 index 0000000..41a89ae --- /dev/null +++ b/entry/src/main/ets/utils/wechat/WXApiEventHandlerImpl.ets @@ -0,0 +1,45 @@ +import * as WxOpenSdk from '@tencent/wechat_open_sdk'; +import { Constants } from '../../common/Constants'; + +export const WXApi = WxOpenSdk.WXAPIFactory.createWXAPI(Constants.WX_APP_ID) + +export type OnWXReq = (req: WxOpenSdk.BaseReq) => void +export type OnWXResp = (resp: WxOpenSdk.BaseResp) => void + +// WXApiEventHandler为微信数据的回调 +class WXApiEventHandlerImpl implements WxOpenSdk.WXApiEventHandler { + private TAG = 'WXApiEventHandlerImpl'; + + private onReqCallbacks: Map = new Map + private onRespCallbacks: Map = new Map + + registerOnWXReqCallback(on: OnWXReq) { + this.onReqCallbacks.set(on, on) + } + unregisterOnWXReqCallback(on: OnWXReq) { + this.onReqCallbacks.delete(on) + } + + registerOnWXRespCallback(on: OnWXResp) { + this.onRespCallbacks.set(on, on) + } + unregisterOnWXRespCallback(on: OnWXResp) { + this.onRespCallbacks.delete(on) + } + + onReq(req: WxOpenSdk.BaseReq): void { + console.log(this.TAG, "onReq:%s", JSON.stringify(req)) + this.onReqCallbacks.forEach((on) => { + on(req) + }) + } + + onResp(resp: WxOpenSdk.BaseResp): void { + console.log(this.TAG, "onResp:%s", JSON.stringify(resp)) + this.onRespCallbacks.forEach((on) => { + on(resp) + }) + } +} + +export const WXEventHandler = new WXApiEventHandlerImpl \ No newline at end of file diff --git a/entry/src/main/ets/view/AccountItemView.ets b/entry/src/main/ets/view/AccountItemView.ets new file mode 100644 index 0000000..802f615 --- /dev/null +++ b/entry/src/main/ets/view/AccountItemView.ets @@ -0,0 +1,108 @@ +import { StrUtil } from "@pura/harmony-utils"; +import { AccountEntity } from "../entity/AccountEntity" + +@ComponentV2 +export struct AccountItemView { + @Param account?: AccountEntity = new AccountEntity(); + + build() { + RelativeContainer() { + Image(this.account?.avater).width(44).height(44).id('iv_avatar').borderRadius(25) + + Text(this.account?.name) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(16) + .fontWeight(FontWeight.Medium) + .alignRules({ + left: { anchor: 'iv_avatar', align: HorizontalAlign.End }, + top: { anchor: 'iv_avatar', align: VerticalAlign.Top } + }) + .margin({ left: 12 }) + .id('tv_username') + + Text(this.account?.vip_type === 3 ? '终身会员' : this.account?.vip_name) + .height(16) + .textAlign(TextAlign.Center) + .fontColor(Color.White) + .fontSize(10) + .linearGradient({ + colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]], + direction: GradientDirection.Right + }) + .borderRadius(3) + .padding({ + left: 3, + right: 3 + }) + .margin({ left: 6 }) + .alignRules({ + left: { anchor: 'tv_username', align: HorizontalAlign.End }, + top: { anchor: 'tv_username', align: VerticalAlign.Top }, + bottom: { anchor: 'tv_username', align: VerticalAlign.Bottom } + }) + .visibility(this.account?.vip_type !== 1 ? Visibility.Visible : Visibility.None) + + Text('ID:' + this.account?.user_id).fontColor($r('app.color.color_999999')).fontSize(12) + .alignRules({ + left: { anchor: 'tv_username', align: HorizontalAlign.Start }, + bottom: { anchor: 'iv_avatar', align: VerticalAlign.Bottom } + }) + + Text(this.account?.create_time + ' 注册').fontColor($r('app.color.color_999999')).fontSize(12) + .alignRules({ + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: 'iv_avatar', align: VerticalAlign.Bottom } + }) + .visibility(StrUtil.isNotEmpty(this.account?.create_time) ? Visibility.Visible : Visibility.None) + + Divider() + .color($r('app.color.color_10ffffff')) + .strokeWidth(1) + .alignRules({ + top: { anchor: 'iv_avatar', align: VerticalAlign.Bottom } + }) + .margin({ top: 18 }) + .id('divider') + + Row() { + Text('账户绑定') + .fontColor($r('app.color.color_50ffffff')) + .fontSize(14) + + Image($r('app.media.ic_bind_phone')).width(20).height(20).margin({ left: 10 }) + .visibility(this.account?.bind.includes('phone') ? Visibility.Visible : Visibility.None) + + Image($r('app.media.ic_bind_wx')).width(20).height(20).margin({ left: 10 }) + .visibility(this.account?.bind.includes('weixin') ? Visibility.Visible : Visibility.None) + + Text(this.getPrivacyPhone(this.account?.phone)) + .layoutWeight(1) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(15) + .textAlign(TextAlign.End) + } + .alignRules({ + top: { anchor: 'divider', align: VerticalAlign.Bottom } + }) + .margin({ top: 14 }) + } + .height('auto') + .borderRadius(10) + .backgroundColor($r('app.color.color_222222')) + .padding({ + left: 16, + top: 18, + right: 16, + bottom: 14 + }) + } + + getPrivacyPhone(phone?: string): string { + if (StrUtil.isNotEmpty(phone)) { + const start = phone?.substring(0, 3); + const end = phone?.substring(7); + return start + '****' + end; + } + return ''; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/AddImageItemView.ets b/entry/src/main/ets/view/AddImageItemView.ets new file mode 100644 index 0000000..db4602d --- /dev/null +++ b/entry/src/main/ets/view/AddImageItemView.ets @@ -0,0 +1,50 @@ +import { UploadImgEntity } from "../entity/UploadImgEntity" + +@ComponentV2 +export struct AddImageItemView { + @Param uri?: string = undefined + @Param index: number = 0 + @Param onDelete?: (uri: string) => void = undefined + + build() { + Stack() { + RelativeContainer() { + Text('+') + .fontColor($r('app.color.color_30ffffff')) + .fontSize(40) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .width('auto') + .visibility(this.uri ? Visibility.None : Visibility.Visible) + + Image(this.uri).width('100%').height('100%') + .visibility(this.uri ? Visibility.Visible : Visibility.None) + + Image($r('app.media.ic_delete_img')) + .width(18) + .height(18) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .onClick(() => { + if (this.onDelete) { + this.onDelete(this.uri!!) + } + }) + .visibility(this.uri ? Visibility.Visible : Visibility.None) + } + .width(80) + .height(80) + .borderRadius(8) + .backgroundColor($r('app.color.color_222222')) + .clip(true) + } + .width('100%') + .alignContent(this.index % 3 === 0 ? Alignment.Start : this.index % 3 === 1 ? Alignment.Center : Alignment.End) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/DiamondItemView.ets b/entry/src/main/ets/view/DiamondItemView.ets new file mode 100644 index 0000000..0051f9a --- /dev/null +++ b/entry/src/main/ets/view/DiamondItemView.ets @@ -0,0 +1,69 @@ +import { NumberUtil, StrUtil } from "@pura/harmony-utils" +import { VipMealEntity } from "../entity/VipMealEntity" + +@ComponentV2 +export struct DiamondItemView { + @Param goodInfo?: VipMealEntity = undefined + + build() { + RelativeContainer() { + Image(this.goodInfo?.image).width(24).height(24).id('iv_icon') + .margin({left: 16, top: 17}) + Text() { + Span('兑换钻石 ').fontSize(15).fontWeight(FontWeight.Medium) + Span(this.goodInfo?.value).fontSize(24).fontFamily('ddp500m') + Span('个').fontSize(12) + } + .fontColor($r('app.color.color_1a1a1a')) + .alignRules({ + left: {anchor: 'iv_icon', align: HorizontalAlign.End}, + }) + .margin({left: 12, top: 13}) + .id('tv_count') + + Text(NumberUtil.toNumber(this.goodInfo?.price) / NumberUtil.toNumber(this.goodInfo?.value) + '元一个钻石巨优惠') + .fontColor($r('app.color.color_666666')) + .fontSize(12) + .alignRules({ + left: {anchor: 'tv_count', align: HorizontalAlign.Start}, + top: {anchor: 'tv_count', align: VerticalAlign.Bottom} + }) + .margin({top: 10}) + .id('tv_desc') + + Text() { + Span('¥').fontSize(13).fontWeight(FontWeight.Medium) + Span(this.goodInfo?.price).fontSize(24).fontFamily('ddp500m') + } + .fontColor('#FF4529') + .alignRules({ + top: {anchor: 'tv_desc', align: VerticalAlign.Top}, + right: {anchor: '__container__', align: HorizontalAlign.End}, + bottom: {anchor: 'tv_desc', align: VerticalAlign.Bottom} + }) + .margin({right: 16}) + + if (StrUtil.isNotEmpty(this.goodInfo?.tips)) { + Text(this.goodInfo?.tips).fontColor('#9E5C0B').fontSize(13) + .height(22) + .textAlign(TextAlign.Center) + .padding({left: 8, right: 8}) + .linearGradient({ + colors:[['#FFEACA', 0.0], ['#FFD7A6', 1.0]], + direction: GradientDirection.Right + }) + .alignRules({ + top: {anchor: '__container__', align: VerticalAlign.Top}, + right: {anchor: '__container__', align: HorizontalAlign.End} + }) + .borderRadius({bottomLeft: 10 , topRight: 10}) + } + } + .width('100%') + .height(86) + .borderRadius(10) + .borderWidth(1) + .borderColor(this.goodInfo?.checked ? '#FF8C1B' : '#DDDDDD') + .backgroundColor(this.goodInfo?.checked ? '#FFFAEF' : Color.White) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/DiamondRuleItemView.ets b/entry/src/main/ets/view/DiamondRuleItemView.ets new file mode 100644 index 0000000..7fa3050 --- /dev/null +++ b/entry/src/main/ets/view/DiamondRuleItemView.ets @@ -0,0 +1,15 @@ +import { DiamondRuleEntity } from "../entity/DiamondRuleEntity" + +@ComponentV2 +export struct DiamondRuleItemView { + @Param entity?: DiamondRuleEntity = undefined + + build() { + Column() { + Text(this.entity?.title).fontColor($r('app.color.color_666666')).fontSize(15) + Text(this.entity?.desc).fontColor($r('app.color.color_666666')).fontSize(14).margin({top: 10}) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/DownloadHistoryItemView.ets b/entry/src/main/ets/view/DownloadHistoryItemView.ets new file mode 100644 index 0000000..1ca7e59 --- /dev/null +++ b/entry/src/main/ets/view/DownloadHistoryItemView.ets @@ -0,0 +1,68 @@ +import { RouterUrls } from "../common/RouterUrls" +import { DownloadHistoryEntity } from "../entity/DownloadHistoryEntity" +import { ConfigManager } from "../manager/UserConfigManager" + +@ComponentV2 +export struct DownloadHistoryItemView { + @Param historyEntity?: DownloadHistoryEntity = undefined + + build() { + Stack() { + Row() { + Column() { + Row() { + Text(ConfigManager.getDomainMap()[this.historyEntity?.domain!!] ? ConfigManager.getDomainMap()[this.historyEntity?.domain!!] : '其他') + .height(16) + .textAlign(TextAlign.Center) + .fontColor(this.historyEntity?.getTypeTextColor()) + .fontSize(10) + .borderRadius(2) + .backgroundColor(this.historyEntity?.getTypeBgColor()) + .padding({left: 4, right: 4}) + Text(this.historyEntity?.title) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .ellipsisMode(EllipsisMode.END) + .margin({left: 8}) + .layoutWeight(1) + } + Text(this.historyEntity?.description ? this.historyEntity?.description : this.historyEntity?.title) + .fontColor($r('app.color.color_60ffffff')) + .fontSize(12) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .ellipsisMode(EllipsisMode.END) + .margin({top: 5}) + + Row() { + Text(this.historyEntity?.save_size).fontColor($r('app.color.color_50ffffff')).fontSize(12) + Text(this.historyEntity?.create_time).fontColor($r('app.color.color_999999')).fontSize(10).margin({left: 10}) + }.margin({top: 8}) + } + .padding({left: 16, right: 16}) + .alignItems(HorizontalAlign.Start) + .layoutWeight(1) + + Divider().width(1).height(60).strokeWidth(1).backgroundColor($r('app.color.color_10ffffff')) + + Stack(){ + Image(this.historyEntity?.request ? $r('app.media.ic_download_enable') : $r('app.media.ic_download_disable')).width(26).height(26) + } + .padding({left: 16, right: 16}) + .onClick(() => { + if (this.historyEntity?.request) { + this.getUIContext().getRouter().pushUrl({url: RouterUrls.TAKE_MATERIAL_PAGE, params: {url: this.historyEntity?.request}}) + } + }) + } + .width('100%') + .padding({ top: 16, bottom: 16 }) + .backgroundColor($r('app.color.color_222222')) + .borderRadius(6) + } + .width('100%') + .padding({ left: 16, right: 16 }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/EmptyView.ets b/entry/src/main/ets/view/EmptyView.ets new file mode 100644 index 0000000..0e88861 --- /dev/null +++ b/entry/src/main/ets/view/EmptyView.ets @@ -0,0 +1,54 @@ +import { StrUtil } from "@pura/harmony-utils"; + +export enum PageStatus { + LOADING, + NO_DATA, + GONE, + ERROR +} + +@ComponentV2 +export struct EmptyView { + @Param status: PageStatus = PageStatus.LOADING; + @Param noDataImage: Resource = $r('app.media.ic_empty_data'); + @Param noDataText: string = '暂无数据'; + @Param noDataBtnText: string = '' + + @Param onBtnClick?: () => void = undefined; + + build() { + Stack() { + Column() { + Image(this.noDataImage).width(162).height(162).margin({ top: -50 }) + Text(this.noDataText).fontColor($r('app.color.color_999999')).fontSize(12).margin({ top: 10 }) + Button(this.noDataBtnText) + .height(30) + .fontColor($r("app.color.color_466afd")) + .fontSize(14) + .borderRadius(20) + .borderWidth(1) + .borderColor($r("app.color.color_466afd")) + .backgroundColor(Color.Transparent) + .padding({ + left: 10, + right: 10 + }) + .margin({ top: 24 }) + .visibility(StrUtil.isNotEmpty(this.noDataBtnText) ? Visibility.Visible : Visibility.Hidden) + .onClick(() => { + if (this.onBtnClick) { + this.onBtnClick(); + } + }) + }.visibility(this.status === PageStatus.NO_DATA ? Visibility.Visible : Visibility.None) + + Column() { + LoadingProgress().width(60).height(60).color($r('app.color.color_999999')).margin({ top: -50 }) + Text('正在加载...').fontColor($r('app.color.color_999999')).fontSize(12).margin({ top: 10 }) + }.visibility(this.status === PageStatus.LOADING ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + .visibility(this.status !== PageStatus.GONE ? Visibility.Visible : Visibility.None) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/MaterialItemView.ets b/entry/src/main/ets/view/MaterialItemView.ets new file mode 100644 index 0000000..dbb9b94 --- /dev/null +++ b/entry/src/main/ets/view/MaterialItemView.ets @@ -0,0 +1,170 @@ +import { AudioMaterial, ImageMaterial, TextMaterial, VideoMaterial } from '../entity/MaterialInfoEntity'; + +@ComponentV2 +export struct VideoMaterialItemView { + @Param media?: VideoMaterial = undefined; + @Param rowCount: number = 1; + @Param isWxVideo: boolean = false; + @Param onDelete?: (media: VideoMaterial) => void = undefined + + build() { + Column() { + RelativeContainer() { + Image(this.media?.thumb) + .width('100%') + .height('100%') + .borderRadius(6) + .backgroundColor($r('app.color.color_222222')) + .id('iv_thumb') + + Image($r('app.media.ic_play_video')).width(50).height(50) + .alignRules({ + left: { anchor: 'iv_thumb', align: HorizontalAlign.Start }, + top: { anchor: 'iv_thumb', align: VerticalAlign.Top }, + right: { anchor: 'iv_thumb', align: HorizontalAlign.End }, + bottom: { anchor: 'iv_thumb', align: VerticalAlign.Bottom }, + }) + .onClick(() => { + if (this.media?.play) { + + } + }) + + Image(this.media?.isChecked ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')) + .width(18) + .height(18) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .margin({ top: 6, right: 6 }) + + Stack() { + Image($r('app.media.ic_delete_material')).height(18).width(18) + } + .width('100%') + .height(34) + .alignRules({ + bottom: {anchor: '__container__', align: VerticalAlign.Bottom} + }) + .borderRadius({ bottomLeft: 6, bottomRight: 6 }) + .backdropBlur(20) + .visibility(this.isWxVideo ? Visibility.Visible : Visibility.None) + .onClick(() => { + if (this.onDelete) { + this.onDelete(this.media!!) + } + }) + }.width('100%').aspectRatio(this.rowCount === 1 ? 1.715 : this.rowCount === 2 ? 0.677 : 1) + + Text(this.media?.title) + .textAlign(TextAlign.Center) + .fontColor($r('app.color.color_50ffffff')) + .fontSize(12) + .maxLines(2) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .ellipsisMode(EllipsisMode.END) + .margin({ top: 10 }) + .visibility(this.isWxVideo ? Visibility.None : Visibility.Visible) + } + } +} + +@ComponentV2 +export struct ImageMaterialItemView { + @Param media?: ImageMaterial = undefined; + @Param rowCount: number = 1; + @Param isWxVideo: boolean = false; + @Param onDelete?: (media: ImageMaterial) => void = undefined + + build() { + Column() { + RelativeContainer() { + Image(this.media?.url) + .width('100%') + .height('100%') + .borderRadius(6) + .backgroundColor($r('app.color.color_222222')) + .id('iv_thumb') + + Image(this.media?.isChecked ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')) + .width(18) + .height(18) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .margin({ top: 6, right: 6 }) + + Stack() { + Image($r('app.media.ic_delete_material')).height(18).width(18) + } + .width('100%') + .height(34) + .alignRules({ + bottom: {anchor: '__container__', align: VerticalAlign.Bottom} + }) + .borderRadius({ bottomLeft: 6, bottomRight: 6 }) + .backdropBlur(20) + .visibility(this.isWxVideo ? Visibility.Visible : Visibility.None) + .onClick(() => { + if (this.onDelete) { + this.onDelete(this.media!!) + } + }) + }.width('100%').aspectRatio(this.rowCount === 1 ? 1.715 : this.rowCount === 2 ? 0.677 : 1) + } + } +} + +@ComponentV2 +export struct AudioMaterialItemView { + @Param media?: AudioMaterial = undefined; + + build() { + Row() { + Text(this.media?.title) + .layoutWeight(1) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .maxLines(2) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + + Image(this.media?.isChecked ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')) + .width(18).height(18).margin({ left: 10 }) + } + .borderRadius(6) + .backgroundColor($r('app.color.color_222222')) + .padding({ + left: 12, + top: 16, + right: 12, + bottom: 16 + }) + } +} + +@ComponentV2 +export struct TextMaterialItemView { + @Param media?: TextMaterial = undefined; + + build() { + Row() { + Text(this.media?.desc) + .layoutWeight(1) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(14) + + Image(this.media?.isChecked ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')) + .width(18).height(18).margin({ left: 10 }) + } + .borderRadius(6) + .backgroundColor($r('app.color.color_222222')) + .padding({ + left: 12, + top: 16, + right: 12, + bottom: 16 + }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/RecordItemView.ets b/entry/src/main/ets/view/RecordItemView.ets new file mode 100644 index 0000000..f063a5f --- /dev/null +++ b/entry/src/main/ets/view/RecordItemView.ets @@ -0,0 +1,224 @@ +import { RouterUrls } from '../common/RouterUrls'; +import { MediaRecordEntity } from '../entity/MediaRecordEntity'; +import { router } from '@kit.ArkUI'; +import { AppUtil, DateUtil, StrUtil } from '@pura/harmony-utils'; +import { ShareManager } from '../manager/ShareManager'; +import { Want } from '@kit.AbilityKit'; +import { SimpleTipDialog } from '../dialog/SimpleTipDialog'; +import { PrefUtils } from '../utils/PrefUtils'; +import { WantUtils } from '../utils/WantUtils'; + +@ComponentV2 +export struct VideoRecordItemView { + @Param onShare?: (entity: MediaRecordEntity) => void = undefined + @Param onDelete?: (entity: MediaRecordEntity) => void = undefined + @Param media?: MediaRecordEntity = undefined; + @Param rowCount: number = 1; + + build() { + RelativeContainer() { + Image(this.media?.thumb) + .width('100%') + .height('100%') + .borderRadius(6) + .backgroundColor($r('app.color.color_222222')) + .id('iv_thumb') + + Image($r('app.media.ic_play_video')).width(50).height(50) + .alignRules({ + left: { anchor: 'iv_thumb', align: HorizontalAlign.Start }, + top: { anchor: 'iv_thumb', align: VerticalAlign.Top }, + right: { anchor: 'iv_thumb', align: HorizontalAlign.End }, + bottom: { anchor: 'iv_thumb', align: VerticalAlign.Bottom }, + }) + .onClick(() => { + this.getUIContext() + .getRouter() + .pushUrl({ + url: RouterUrls.VIDEO_PLAYER_PAGE, + params: { title: '', uri: this.media?.uri, showActions: true } + }, router.RouterMode.Single) + }) + + Row() { + Stack() { + Image($r('app.media.ic_share_material')).height(18).width(18) + }.layoutWeight(1).height(34) + .onClick(() => { + if (StrUtil.isNotEmpty(this.media?.uri)) { + ShareManager.shareFile(this.media?.uri!!) + } + }) + + Stack() { + Image($r('app.media.ic_delete_material')).height(18).width(18) + }.layoutWeight(1).height(34) + .onClick(() => { + if (PrefUtils.getBoolean('show_record_delete_tip', true)) { + SimpleTipDialog.show(this.getUIContext(), { + title: '提示', content: '因系统限制,请到相册中手动删除', buttonText: '知道了', callback: { + confirm: () => { + WantUtils.toPhotoGallery() + PrefUtils.put('show_record_delete_tip', false) + } + } + }) + } else { + WantUtils.toPhotoGallery() + } + }) + } + .borderRadius({ bottomLeft: 6, bottomRight: 6 }) + .backdropBlur(20) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + }) + .visibility(Visibility.Visible) + }.width('100%').aspectRatio(this.rowCount === 1 ? 1.715 : this.rowCount === 2 ? 0.677 : 1) + } +} + +@ComponentV2 +export struct ImageRecordItemView { + @Param onShare?: (entity: MediaRecordEntity) => void = undefined + @Param onDelete?: (entity: MediaRecordEntity) => void = undefined + @Param media?: MediaRecordEntity = undefined; + @Param rowCount: number = 1; + + build() { + RelativeContainer() { + Image(this.media?.uri) + .width('100%') + .height('100%') + .borderRadius(6) + .backgroundColor($r('app.color.color_222222')) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({ url: RouterUrls.PHOTO_VIEW_PAGE, params: { uri : this.media?.uri } }) + }) + + Row() { + Stack() { + Image($r('app.media.ic_share_material')).height(18).width(18) + }.layoutWeight(1).height(34) + .onClick(() => { + if (StrUtil.isNotEmpty(this.media?.uri)) { + ShareManager.shareFile(this.media?.uri!!) + } + }) + + Stack() { + Image($r('app.media.ic_delete_material')).height(18).width(18) + }.layoutWeight(1).height(34) + .onClick(() => { + if (PrefUtils.getBoolean('show_record_delete_tip', true)) { + SimpleTipDialog.show(this.getUIContext(), { + title: '提示', content: '因系统限制,请到相册中手动删除', buttonText: '知道了', callback: { + confirm: () => { + WantUtils.toPhotoGallery() + PrefUtils.put('show_record_delete_tip', false) + } + } + }) + } else { + WantUtils.toPhotoGallery() + } + }) + } + .borderRadius({ bottomLeft: 6, bottomRight: 6 }) + .backdropBlur(20) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + }) + .visibility(Visibility.Visible) + }.width('100%').aspectRatio(this.rowCount === 1 ? 1.715 : this.rowCount === 2 ? 0.677 : 1) + } +} + +@ComponentV2 +export struct AudioRecordItemView { + @Param media?: MediaRecordEntity = undefined; + + build() { + RelativeContainer() { + Text(this.media?.name) + .layoutWeight(1) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(15) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Center } + }) + .margin({ bottom: 2 }) + + Text(this.formatTime(Math.trunc(this.media!!.duration / 1000))) + .fontColor($r('app.color.color_60ffffff')) + .fontSize(14) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Center } + }) + .margin({ top: 2 }) + .id('tv_duration') + + Text(DateUtil.getFormatDateStr(this.media!!.createTime, 'yyyy年MM月dd日 HH:mm:ss')) + .fontColor($r('app.color.color_30ffffff')) + .fontSize(12) + .alignRules({ + left: { anchor: 'tv_duration', align: HorizontalAlign.End }, + top: { anchor: 'tv_duration', align: VerticalAlign.Top }, + bottom: { anchor: 'tv_duration', align: VerticalAlign.Bottom } + }) + .margin({ left: 12 }) + + Image($r('app.media.ic_arrow_dp22')).width(24).height(24) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + + Divider().color($r('app.color.color_10ffffff')).width('100%').strokeWidth(1) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + } + .height(74) + .onClick(() => { + this.getUIContext() + .getRouter() + .pushUrl({ + url: RouterUrls.AUDIO_PLAYER_PAGE, + params: { title: this.media?.name, uri: this.media?.uri, showActions: true } + }, router.RouterMode.Single) + }) + } + + formatTime(time: number): string { + let minute: number = 0 + let second: number = 0 + if (time > 60) { + minute = Math.trunc(time / 60) + second = time % 60 + if (minute < 10) { + if (second < 10) { + return `0${minute}:0${second}` + } else { + return `0${minute}:${second}` + } + } else { + if (second < 10) { + return `${minute}:0${second}` + } else { + return `${minute}:${second}` + } + } + } else { + second = time + if (second < 10) { + return `00:0${second}` + } else { + return `00:${second}` + } + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/RectCropView.ets b/entry/src/main/ets/view/RectCropView.ets new file mode 100644 index 0000000..6b50bc6 --- /dev/null +++ b/entry/src/main/ets/view/RectCropView.ets @@ -0,0 +1,218 @@ + +export interface RectPosition { + x: number; + y: number; + height: number; + width: number; +} + +export enum ActionType { + topLeft, + topRight, + bottomLeft, + bottomRight, + move +} + +export interface Position { + x: number; + y: number; +} + +@ComponentV2 +export struct RectCropView { + private settings: RenderingContextSettings = new RenderingContextSettings(true); + private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); + private actionType: ActionType = ActionType.move; + private touchPosition: Position = { x: 0, y: 0 }; + private sw: number = 0; //图片展示框固定宽度 + private sh: number = 0; //图片展示框固定高度 + + @Param onRectChange?: (rect: RectPosition) => void = undefined + + @Local clipRect: RectPosition = { + x: 0, + y: 0, + width: 0, + height: 0 + }; + + @Local initPosition: Position = { + x: 0, + y: 0 + } + + build() { + Stack({ alignContent: Alignment.TopStart }) { + // 裁剪框 + Canvas(this.canvasContext) + .position({ + x: this.clipRect.x, + y: this.clipRect.y + }) + .width(this.clipRect.width) + .height(this.clipRect.height) + .onReady(() => { + this.drawClipImage() + }) + .onTouch(event => { + if (event.type === TouchType.Down) { + this.isMove(event.target.area, event.touches[0]); + this.touchPosition = { + x: event.touches[0].screenX, + y: event.touches[0].screenY + } + } else if (event.type === TouchType.Move) { + let moveX = event.changedTouches[0].screenX - this.touchPosition.x; + let moveY = event.changedTouches[0].screenY - this.touchPosition.y; + this.touchPosition = { + x: event.changedTouches[0].screenX, + y: event.changedTouches[0].screenY + } + this.moveClipCanvas(moveX, moveY); + } + }) + } + .width('100%') + .height('100%') + .onAreaChange((_oldArea, newArea) => { + this.sw = newArea.width as number + this.sh = newArea.height as number + this.clipRect.width = newArea.width as number + this.clipRect.height = newArea.height as number + this.moveClipCanvas(0, 0) + }) + } + + // 绘制裁剪框 + drawClipImage() { + this.canvasContext.clearRect(0, 0, this.clipRect.width, this.clipRect.height); + this.canvasContext.lineWidth = 6 + this.canvasContext.strokeStyle = Color.White + this.canvasContext.beginPath() + + this.canvasContext.moveTo(0, 20) + this.canvasContext.lineTo(0, 0); + this.canvasContext.lineTo(20, 0); + + this.canvasContext.moveTo(this.clipRect.width - 20, 0); + this.canvasContext.lineTo(this.clipRect.width, 0); + this.canvasContext.lineTo(this.clipRect.width, 20); + + this.canvasContext.moveTo(0, this.clipRect.height - 20); + this.canvasContext.lineTo(0, this.clipRect.height); + this.canvasContext.lineTo(20, this.clipRect.height); + + this.canvasContext.moveTo(this.clipRect.width - 20, this.clipRect.height); + this.canvasContext.lineTo(this.clipRect.width, this.clipRect.height); + this.canvasContext.lineTo(this.clipRect.width, this.clipRect.height - 20); + this.canvasContext.stroke() + + this.canvasContext.beginPath(); + this.canvasContext.lineWidth = 0.5; + let height = Math.round(this.clipRect.height / 3); + for (let index = 0; index <= 3; index++) { + let y = index === 3 ? this.clipRect.height : height * index; + this.canvasContext.moveTo(0, y); + this.canvasContext.lineTo(this.clipRect.width, y); + } + let width = Math.round(this.clipRect.width / 3); + for (let index = 0; index <= 3; index++) { + let x = index === 3 ? this.clipRect.width : width * index; + this.canvasContext.moveTo(x, 0); + this.canvasContext.lineTo(x, this.clipRect.height); + } + this.canvasContext.stroke(); + } + + // 裁剪框位置和大小变化 初始位置为图片的初始坐标 移动的坐标 + moveClipCanvas(moveX: number, moveY: number) { + let clipRect: RectPosition = { + x: this.clipRect.x, + y: this.clipRect.y, + width: this.clipRect.width, + height: this.clipRect.height + } + switch (this.actionType) { + case ActionType.move: + clipRect.x += moveX; + clipRect.y += moveY; + break; + case ActionType.topLeft: + clipRect.x += moveX; + clipRect.y += moveY; + clipRect.width += -moveX; + clipRect.height += -moveY; + break; + case ActionType.topRight: + clipRect.y += moveY; + clipRect.width += moveX; + clipRect.height += -moveY; + break; + case ActionType.bottomLeft: + clipRect.x += moveX; + clipRect.width += -moveX; + clipRect.height += moveY; + break; + case ActionType.bottomRight: + clipRect.width += moveX; + clipRect.height += moveY; + break; + default: + break; + } + + // 偏移坐标小于初始位置 + if (clipRect.x < this.initPosition.x) { + clipRect.x = this.initPosition.x; + } + + if (clipRect.y < this.initPosition.y) { + clipRect.y = this.initPosition.y; + } + + // 横坐标限制位置 + if (clipRect.width + clipRect.x > this.sw + this.initPosition.x) { + if (this.actionType === ActionType.move) { + clipRect.x = this.sw + this.initPosition.x - clipRect.width; + } else { + clipRect.width = this.sw + this.initPosition.x - clipRect.x; + } + } + + // 纵坐标限制 + if (clipRect.height + clipRect.y > this.sh + this.initPosition.y) { + if (this.actionType === ActionType.move) { + clipRect.y = this.sh + this.initPosition.y - clipRect.height; + } else { + clipRect.height = this.sh + this.initPosition.y - clipRect.y; + } + } + + //裁剪框位置大小 + this.clipRect = { + x: Math.round(clipRect.x), + y: Math.round(clipRect.y), + width: Math.max(Math.round(clipRect.width), 100), + height: Math.max(Math.round(clipRect.height), 50) + }; + if (this.onRectChange) { + this.onRectChange(this.clipRect) + } + } + + // 判断操作类型 + isMove(area: Area, touch: TouchObject) { + if (touch.x < 30 && touch.y < 30) { // 左上角 + this.actionType = ActionType.topLeft + } else if (touch.x < 30 && touch.y > (Number(area.height) - 30)) { // 左下 + this.actionType = ActionType.bottomLeft + } else if (touch.x > Number(area.width) - 30 && touch.y < 30) { // 右上 + this.actionType = ActionType.topRight + } else if (touch.x > Number(area.width) - 30 && touch.y > (Number(area.height) - 30)) { // 右下 + this.actionType = ActionType.bottomRight + } else { + this.actionType = ActionType.move + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/TextItemChildView.ets b/entry/src/main/ets/view/TextItemChildView.ets new file mode 100644 index 0000000..e964aa5 --- /dev/null +++ b/entry/src/main/ets/view/TextItemChildView.ets @@ -0,0 +1,28 @@ +@ComponentV2 +export struct TextItemChildView { + @Param text: string = ''; + @Param divider: boolean = true; + + build() { + Column() { + Row() { + Text(this.text) + .fontColor($r('app.color.color_90ffffff')) + .fontSize(14) + .layoutWeight(1) + + Image($r('app.media.ic_arrow_dp16')) + .width(16) + .height(16) + } + .layoutWeight(1) + + Divider() + .color($r('app.color.color_10ffffff')) + .strokeWidth(1) + .visibility(this.divider ? Visibility.Visible : Visibility.None) + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/TextItemView.ets b/entry/src/main/ets/view/TextItemView.ets new file mode 100644 index 0000000..352dd08 --- /dev/null +++ b/entry/src/main/ets/view/TextItemView.ets @@ -0,0 +1,35 @@ + +@ComponentV2 +export struct TextItemView { + @Param image?: Resource = undefined; + @Param leftText: string = ''; + @Param rightText: string = ''; + + build() { + Row() { + Image(this.image) + .width(20) + .height(20) + .margin({ left: 14 }) + .visibility(this.image ? Visibility.Visible : Visibility.None) + + Text(this.leftText) + .fontColor($r('app.color.color_80ffffff')) + .fontSize(15) + .layoutWeight(1) + .margin({ left: 10 }) + + Text(this.rightText) + .fontColor(Color.White) + .fontSize(15) + .margin({ right: 1 }) + + Image($r('app.media.ic_arrow_dp22')) + .margin({ right: 8 }) + .width(22) + .height(22) + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/TitleBar.ets b/entry/src/main/ets/view/TitleBar.ets new file mode 100644 index 0000000..31e9d27 --- /dev/null +++ b/entry/src/main/ets/view/TitleBar.ets @@ -0,0 +1,77 @@ + +@ComponentV2 +export struct TitleBar { + @Param title: string = ''; + @Param isDark: boolean = false; + @Param showBack: boolean = true; + @Param rightText: string = '' + @Param rightColor: ResourceColor = Color.White + @Param rightIcon?: Resource = undefined + @Param onRightClick?: () => void = undefined + + @Param onBackClick?: () => void = undefined; + + build() { + RelativeContainer() { + Button({ type: ButtonType.Circle, stateEffect: true }) { + Image(this.isDark ? $r('app.media.ic_back') : $r('app.media.ic_black_back')).width(24).height(24) + } + .width(40) + .height(40) + .margin({ left: 10 }) + .alignRules({ + left: { anchor: '__container__', align: HorizontalAlign.Start }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onClick(() => { + if (this.onBackClick) { + this.onBackClick(); + } else { + this.getUIContext().getRouter().back() + } + }) + .backgroundColor(Color.Transparent) + .visibility(this.showBack ? Visibility.Visible : Visibility.Hidden) + .id('btn_back') + + Text(this.title) + .fontColor(this.isDark ? $r('app.color.color_90ffffff') : Color.Black) + .fontSize(18) + .fontWeight(FontWeight.Medium) + .textAlign(TextAlign.Center) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .ellipsisMode(EllipsisMode.END) + .margin({right: 50}) + .alignRules({ + left: { anchor: 'btn_back', align: HorizontalAlign.End }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .width('auto') + + Row() { + if (this.rightIcon) { + Image(this.rightIcon).width(18).height(18) + } + Text(this.rightText).width('auto').fontSize(14).fontColor(this.rightColor) + } + .margin({right: 16}) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: '__container__', align: VerticalAlign.Bottom } + }) + .onClick(() => { + if (this.onRightClick) { + this.onRightClick() + } + }) + } + .padding({top: 50}) + .width('100%') + .height(100) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/VipMealItemView.ets b/entry/src/main/ets/view/VipMealItemView.ets new file mode 100644 index 0000000..72800c4 --- /dev/null +++ b/entry/src/main/ets/view/VipMealItemView.ets @@ -0,0 +1,84 @@ +import { NumberUtil, StrUtil } from "@pura/harmony-utils"; +import { VipMealEntity } from "../entity/VipMealEntity"; + +@ComponentV2 +export struct VipMealItemView { + @Param entity: VipMealEntity = new VipMealEntity(); + @Param isChecked: boolean = false; + @Param isGrid: boolean = true; + + build() { + RelativeContainer() { + Column() { + Text(this.entity.goods_name) + .fontColor(this.isChecked ? Color.White : '#ABABAB') + .fontSize(this.isChecked ? 15 : 12) + .fontWeight(this.isChecked ? FontWeight.Medium : FontWeight.Regular) + .margin({ top: this.isChecked ? 18 : 12 }) + Text() { + Span('¥').fontSize(this.isChecked ? 12 : 10) + Span(this.entity.price).fontSize(this.isChecked ? 28: 22) + } + .fontColor(this.isChecked ? '#94F2FE' : '#EAEAEA') + .fontWeight(FontWeight.Medium) + .margin({ top: this.isChecked ? 10 : 4 }) + + Text() { + Span('¥' + this.entity.origin_price).fontColor(this.isChecked ? $r('app.color.color_30ffffff') : '#6B6B6B').fontSize(this.isChecked ? 13 : 10) + .decoration({ type: TextDecorationType.LineThrough, color: this.isChecked ? $r('app.color.color_30ffffff') : '#6B6B6B' }) + } + .margin({ top:this.isChecked ? 5 : 2 }) + + Text('立省¥' + + (NumberUtil.toNumber(this.entity.origin_price) - NumberUtil.toNumber(this.entity.price))) + .width('100%') + .height(this.isChecked ? 28 : 22) + .textAlign(TextAlign.Center) + .fontColor(this.isChecked ? $r('app.color.color_90ffffff') : $r('app.color.color_80ffffff')) + .fontSize(this.isChecked ? 12 : 10) + .backgroundColor(this.isChecked ? '#181331' : '#4B4B53') + .borderRadius({bottomLeft: this.isChecked ? 10 : 8, bottomRight: this.isChecked ? 10 : 8}) + .margin({ top: 10 }) + } + .height('auto') + .linearGradient({ + colors: [[this.isChecked ? '#3F2D67' : Color.Transparent, 0.0], [this.isChecked ? '#202443' : Color.Transparent, 1.0]], + direction: GradientDirection.Right + }) + .backgroundColor(this.isChecked ? Color.Transparent : '#2B2B38') + .borderRadius(this.isChecked ? 10 : 8) + .borderWidth(1) + .borderColor(this.isChecked ? '#94F2FE' : Color.Transparent) + .margin({top: this.isChecked ? 11 : 9}) + .alignRules({ + top: {anchor: '__container__', align: VerticalAlign.Top}, + bottom: {anchor: '__container__', align: VerticalAlign.Bottom}, + }) + .id('layout_content') + + Row() { + Image($r('app.media.ic_vip_fire_tag')).width(this.isChecked ? 14 : 12).height(this.isChecked ? 14 : 12) + Text(this.entity.tips).fontColor(Color.White).fontSize(this.isChecked ? 12 : 10).margin({ left: 2 }) + } + .width('auto') + .height(this.isChecked ? 22 : 18) + .padding({left: this.isChecked ? 4 : 2, right: this.isChecked ? 6 : 4}) + .margin({top: this.isChecked ? -11 : -9}) + .borderRadius({ + topLeft: 9, + topRight: 2, + bottomLeft: 2, + bottomRight: 9 + }) + .alignRules({ + top: {anchor: 'layout_content', align: VerticalAlign.Top} + }) + .backgroundColor('#F94747') + .visibility(StrUtil.isNotEmpty(this.entity.tips) ? Visibility.Visible : Visibility.None) + } + .width(this.isGrid ? 'auto' : this.isChecked ? 125 : 110) + .height(148) + .padding({left: this.isChecked ? 0 : 5, right: this.isChecked ? 0 : 5}) + .margin({ left: 6, right: 6 }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/WaterMarkerView.ets b/entry/src/main/ets/view/WaterMarkerView.ets new file mode 100644 index 0000000..7d915f0 --- /dev/null +++ b/entry/src/main/ets/view/WaterMarkerView.ets @@ -0,0 +1,239 @@ +import { StrUtil } from '@pura/harmony-utils'; +import { ActionType, Position, RectPosition } from './RectCropView'; + +@ComponentV2 +export struct WaterMarkerView { + private settings: RenderingContextSettings = new RenderingContextSettings(true); + private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); + private actionType: ActionType = ActionType.move; + private touchPosition: Position = { x: 0, y: 0 }; + private sw: number = 0; //图片展示框固定宽度 + private sh: number = 0; //图片展示框固定高度 + + @Param onRectChange?: (rect: RectPosition) => void = undefined + @Param onClose?: () => void = undefined + + @Param content: string = '' + @Param imagePath: string = '' + + @Local clipRect: RectPosition = { + x: 0, + y: 0, + width: 80, + height: 40 + }; + @Local initPosition: Position = { + x: 0, + y: 0 + } + @Local fontSize: number = 15 + + build() { + Stack() { + if (StrUtil.isNotEmpty(this.content)) { + Text(this.content) + .position({ + x: this.clipRect.x, + y: this.clipRect.y + }) + .width(this.clipRect.width) + .height(this.clipRect.height) + .fontColor(Color.White) + .fontSize(this.fontSize) + .textAlign(TextAlign.Center) + .onSizeChange(() => { + this.caleTextSize() + }) + .id("textWaterMarker") + } else if (StrUtil.isNotEmpty(this.imagePath)) { + Image(this.imagePath) + .position({ + x: this.clipRect.x, + y: this.clipRect.y + }) + .width(this.clipRect.width) + .height(this.clipRect.height) + .id("imageWaterMarker") + } + + Image($r('app.media.ic_right_bottom_rect')) + .position({ + x: this.clipRect.x + this.clipRect.width - 9, + y: this.clipRect.y + this.clipRect.height - 9 + }) + .width(15) + .height(15) + + // 裁剪框 + Canvas(this.canvasContext) + .position({ + x: this.clipRect.x, + y: this.clipRect.y + }) + .width(this.clipRect.width) + .height(this.clipRect.height) + .onReady(() => { + this.drawClipImage() + }) + .onTouch(event => { + if (event.type === TouchType.Down) { + this.isMove(event.target.area, event.touches[0]); + this.touchPosition = { + x: event.touches[0].screenX, + y: event.touches[0].screenY + } + } else if (event.type === TouchType.Move) { + let moveX = event.changedTouches[0].screenX - this.touchPosition.x; + let moveY = event.changedTouches[0].screenY - this.touchPosition.y; + this.touchPosition = { + x: event.changedTouches[0].screenX, + y: event.changedTouches[0].screenY + } + this.moveClipCanvas(moveX, moveY); + } + }) + + Image($r('app.media.ic_left_top_rect')) + .position({ + x: this.clipRect.x - 7, + y: this.clipRect.y - 7 + }) + .width(16) + .height(16) + .onClick(() => { + if (this.onClose) { + this.onClose() + } + }) + } + .width('100%') + .height('100%') + .onAreaChange((_oldArea, newArea) => { + this.sw = newArea.width as number + this.sh = newArea.height as number + if (StrUtil.isNotEmpty(this.imagePath)) { + this.clipRect.width = 80 + this.clipRect.height = 80 + this.moveClipCanvas(0, 0) + } + }) + } + + // 绘制裁剪框 + drawClipImage() { + this.canvasContext.clearRect(0, 0, this.clipRect.width, this.clipRect.height); + this.canvasContext.lineWidth = 2 + this.canvasContext.strokeStyle = Color.White + this.canvasContext.beginPath() + this.canvasContext.rect(0, 0, this.clipRect.width, this.clipRect.height) + this.canvasContext.stroke() + } + + // 裁剪框位置和大小变化 初始位置为图片的初始坐标 移动的坐标 + moveClipCanvas(moveX: number, moveY: number) { + let clipRect: RectPosition = { + x: this.clipRect.x, + y: this.clipRect.y, + width: this.clipRect.width, + height: this.clipRect.height + } + switch (this.actionType) { + case ActionType.move: + clipRect.x += moveX; + clipRect.y += moveY; + break; + case ActionType.topLeft: + clipRect.x += moveX; + clipRect.y += moveY; + clipRect.width += -moveX; + clipRect.height += -moveY; + break; + case ActionType.topRight: + clipRect.y += moveY; + clipRect.width += moveX; + clipRect.height += -moveY; + break; + case ActionType.bottomLeft: + clipRect.x += moveX; + clipRect.width += -moveX; + clipRect.height += moveY; + break; + case ActionType.bottomRight: + clipRect.width += moveX; + clipRect.height += moveY; + break; + default: + break; + } + + // 偏移坐标小于初始位置 + if (clipRect.x < this.initPosition.x) { + clipRect.x = this.initPosition.x; + } + + if (clipRect.y < this.initPosition.y) { + clipRect.y = this.initPosition.y; + } + + // 横坐标限制位置 + if (clipRect.width + clipRect.x > this.sw + this.initPosition.x) { + if (this.actionType === ActionType.move) { + clipRect.x = this.sw + this.initPosition.x - clipRect.width; + } else { + clipRect.width = this.sw + this.initPosition.x - clipRect.x; + } + } + + // 纵坐标限制 + if (clipRect.height + clipRect.y > this.sh + this.initPosition.y) { + if (this.actionType === ActionType.move) { + clipRect.y = this.sh + this.initPosition.y - clipRect.height; + } else { + clipRect.height = this.sh + this.initPosition.y - clipRect.y; + } + } + + //裁剪框位置大小 + this.clipRect = { + x: Math.round(clipRect.x), + y: Math.round(clipRect.y), + width: Math.max(Math.round(clipRect.width), StrUtil.isNotEmpty(this.content) ? 80 : 40), + height: Math.max(Math.round(clipRect.height), 40) + }; + + if (this.onRectChange) { + this.onRectChange(this.clipRect) + } + } + + // 判断操作类型 + isMove(area: Area, touch: TouchObject) { + if (touch.x < 30 && touch.y < 30) { // 左上角 + this.actionType = ActionType.topLeft + } else if (touch.x < 30 && touch.y > (Number(area.height) - 30)) { // 左下 + this.actionType = ActionType.bottomLeft + } else if (touch.x > Number(area.width) - 30 && touch.y < 30) { // 右上 + this.actionType = ActionType.topRight + } else if (touch.x > Number(area.width) - 30 && touch.y > (Number(area.height) - 30)) { // 右下 + this.actionType = ActionType.bottomRight + } else { + this.actionType = ActionType.move + } + } + + // 缩放文字大小 + caleTextSize() { + let textSize = this.getUIContext().getMeasureUtils().measureTextSize({ + textContent: this.content, + fontSize: this.fontSize + }) + let textWidth = textSize.width + let textHeight = textSize.height + let ratioW = vp2px(this.clipRect.width - 10) / (textWidth as number) + let ratioH = vp2px(this.clipRect.height - 10) / (textHeight as number) + let fontSize = Math.floor(this.fontSize * Math.min(ratioW, ratioH)) + if (fontSize > 8) { + this.fontSize = fontSize + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/AuthViewModel.ets b/entry/src/main/ets/viewModel/AuthViewModel.ets new file mode 100644 index 0000000..696ec41 --- /dev/null +++ b/entry/src/main/ets/viewModel/AuthViewModel.ets @@ -0,0 +1,29 @@ +import { plainToInstance } from "class-transformer"; +import { VipPermissionEntity } from "../entity/VipPermissionEntity"; +import { apiService } from "../net/ApiService"; +import { ToastUtils } from "../utils/ToastUtils"; +import { BaseViewModel } from "./BaseViewModel"; + +@ObservedV2 +export class AuthViewModel extends BaseViewModel { + @Trace permissionInfo?: VipPermissionEntity + @Trace errorCode: number = 0; + + async checkVip() { + this.showLoading(); + try { + const result = await apiService.checkPermission('download'); + if (result.isSuccess()) { + this.permissionInfo = plainToInstance(VipPermissionEntity, result.data); + } else { + this.errorCode = result.code + ToastUtils.show(result.message, true); + } + this.dismissLoading() + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/BaseViewModel.ets b/entry/src/main/ets/viewModel/BaseViewModel.ets new file mode 100644 index 0000000..6580dde --- /dev/null +++ b/entry/src/main/ets/viewModel/BaseViewModel.ets @@ -0,0 +1,17 @@ +import { LoadingDialog } from "../dialog/LoadingDialog"; + +export class BaseViewModel { + ctx: UIContext; + + constructor(context: UIContext) { + this.ctx = context; + } + + showLoading(cancelable: boolean = false) { + LoadingDialog.show(this.ctx, cancelable); + } + + dismissLoading() { + LoadingDialog.dismiss(); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/BindAccountViewModel.ets b/entry/src/main/ets/viewModel/BindAccountViewModel.ets new file mode 100644 index 0000000..9e25a46 --- /dev/null +++ b/entry/src/main/ets/viewModel/BindAccountViewModel.ets @@ -0,0 +1,65 @@ +import { plainToInstance } from 'class-transformer'; +import { UserEntity } from '../entity/UserEntity'; +import { LoginManager } from '../manager/LoginGlobalManager'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export default class BindAccountViewModel extends BaseViewModel { + @Trace userEntity?: UserEntity; + @Trace bindInfo?: object; + @Trace unbindInfo?: string; + + async userinfo() { + this.showLoading(); + try { + const result = await apiService.userinfo(); + if (result.isSuccess()) { + this.userEntity = plainToInstance(UserEntity, result.data); + LoginManager.setUserInfo(this.userEntity); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async bindWx(code: string) { + this.showLoading(); + try { + const result = await apiService.loginByWX(code, true); + if (result.isSuccess()) { + this.bindInfo = new Object(); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async unbind(loginType: string) { + this.showLoading(); + try { + const result = await apiService.unbind(loginType); + if (result.isSuccess()) { + this.unbindInfo = loginType; + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/DiamondViewModel.ets b/entry/src/main/ets/viewModel/DiamondViewModel.ets new file mode 100644 index 0000000..e32a83b --- /dev/null +++ b/entry/src/main/ets/viewModel/DiamondViewModel.ets @@ -0,0 +1,107 @@ +import { plainToInstance } from "class-transformer"; +import { DiamondDetailEntity } from "../entity/DiamondDetailEntity"; +import { OrderEntity } from "../entity/OrderEntity"; +import { PayOrderEntity } from "../entity/OrderPayEntity"; +import { VipMealEntity } from "../entity/VipMealEntity"; +import { apiService } from "../net/ApiService"; +import { ToastUtils } from "../utils/ToastUtils"; +import { BaseViewModel } from "./BaseViewModel"; + +@ObservedV2 +export class DiamondViewModel extends BaseViewModel { + @Trace diamondInfo?: DiamondDetailEntity + @Trace goodsList?: Array; + @Trace payOrderEntity?: PayOrderEntity; + @Trace orderInfoEntity?: OrderEntity; + + private intervalId = 0 + + async getDiamondInfo() { + this.showLoading(); + try { + const result = await apiService.getDiamondInfo(); + if (result.isSuccess()) { + this.diamondInfo = plainToInstance(DiamondDetailEntity, result.data); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async mealList() { + this.showLoading(); + try { + const result = await apiService.goodsList('recharge'); + if (result.isSuccess()) { + this.goodsList = plainToInstance(VipMealEntity, result.data as Array); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async createOrder(goodsId: string, payType: string, source: string, coupon: string){ + this.showLoading(); + try { + const result = await apiService.createOrder(goodsId, payType, source, coupon); + if(result.isSuccess()) { + this.payOrderEntity = plainToInstance(PayOrderEntity, result.data); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async getOrderInfo(orderId: string) { + this.showLoading(true) + this.cancelInterval() + try { + let count = 10; + this.intervalId = setInterval(async () => { + if (count > 0) { + count--; + const result = await apiService.getOrderInfo(orderId); + if (result.isSuccess()) { + this.orderInfoEntity = plainToInstance(OrderEntity, result.data); + if (this.orderInfoEntity.status == '2') { + this.dismissLoading() + this.cancelInterval() + } + } else { + ToastUtils.show(result.message, true); + this.dismissLoading() + this.cancelInterval() + } + } else { + this.dismissLoading() + this.cancelInterval() + } + }, 2000) + } catch (e) { + console.log(e); + this.dismissLoading() + this.cancelInterval() + } + } + + cancelInterval() { + if (this.intervalId !== 0) { + clearInterval(this.intervalId) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/DownloadHistoryViewModel.ets b/entry/src/main/ets/viewModel/DownloadHistoryViewModel.ets new file mode 100644 index 0000000..d8d2469 --- /dev/null +++ b/entry/src/main/ets/viewModel/DownloadHistoryViewModel.ets @@ -0,0 +1,45 @@ +import { plainToInstance } from 'class-transformer'; +import { DownloadHistoryEntity } from '../entity/DownloadHistoryEntity'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export class DownloadHistoryViewModel extends BaseViewModel { + @Trace getHistory?: Array; + @Trace deleteHistory?: object; + + async getHistoryList(page: string = '1', startTime: string, endTime: string) { + this.showLoading(); + try { + const result = await apiService.getDownloadHistoryList(page, startTime, endTime); + if (result.isSuccess()) { + this.getHistory = plainToInstance(DownloadHistoryEntity, result.data!!['items'] as Array); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async deleteHistoryList(startTime: string, endTime: string) { + this.showLoading(); + try { + const result = await apiService.deleteDownloadHistory(startTime, endTime); + if (result.isSuccess()) { + this.deleteHistory = new Object(); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/FeedbackViewModel.ets b/entry/src/main/ets/viewModel/FeedbackViewModel.ets new file mode 100644 index 0000000..ef4bc32 --- /dev/null +++ b/entry/src/main/ets/viewModel/FeedbackViewModel.ets @@ -0,0 +1,78 @@ +import { Base64Util, FileUtil } from '@pura/harmony-utils'; +import { plainToInstance } from 'class-transformer'; +import { UploadImgEntity } from '../entity/UploadImgEntity'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; +import { fileIo } from '@kit.CoreFileKit'; + +@ObservedV2 +export class FeedbackViewModel extends BaseViewModel { + @Trace feedback?: object + + async sendFeedback(type: string, content: string, contact: string, images: Array) { + this.showLoading(); + try { + const result = await apiService.feedback(type, content, contact, images) + if (result.isSuccess()) { + this.feedback = new Object() + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async uploadImages(photos: Array): Promise> { + this.showLoading(); + let uploadList = new Array() + for (let i = 0;i < photos.length;i++) { + let imageEntity = await this.uploadImage(photos[i]) + if (imageEntity) { + uploadList.push(imageEntity.url) + } else { + break + } + } + this.dismissLoading(); + if (uploadList.length === photos.length) { + return Promise.resolve(uploadList) + } else { + ToastUtils.show('图片上传失败') + return Promise.reject() + } + } + + async uploadImage(uri: string): Promise { + try { + let file = FileUtil.openSync(uri, fileIo.OpenMode.READ_ONLY); + // 复制文件到缓存目录下 + let cacheFilePath = FileUtil.getCacheDirPath() + '/' + FileUtil.getFileName(uri) + FileUtil.copyFileSync(file.fd, cacheFilePath) + // 读取文件为ArrayBuffer + const file2 = FileUtil.openSync(cacheFilePath, 0o2); + const stat = FileUtil.lstatSync(cacheFilePath); + const buffer = new ArrayBuffer(stat.size); + FileUtil.readSync(file2.fd, buffer); + FileUtil.fsyncSync(file2.fd); + FileUtil.closeSync(file2.fd); + + const base64Str = Base64Util.encodeToStrSync(new Uint8Array(buffer)) + + const result = await apiService.uploadImage(base64Str, 'feedback'); + if (result.isSuccess()) { + const imageEntity = plainToInstance(UploadImgEntity, result.data); + return Promise.resolve(imageEntity) + } else { + return Promise.resolve(null) + } + } catch (e) { + console.log(e); + return Promise.resolve(null) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/HomeViewModel.ets b/entry/src/main/ets/viewModel/HomeViewModel.ets new file mode 100644 index 0000000..7722481 --- /dev/null +++ b/entry/src/main/ets/viewModel/HomeViewModel.ets @@ -0,0 +1,40 @@ +import { plainToInstance } from 'class-transformer'; +import { MaterialEntity } from '../entity/MaterialEntity'; +import { NoticeEntity } from '../entity/NoticeEntity'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export class HomeViewModel extends BaseViewModel { + @Trace noticeEntity?: NoticeEntity; + @Trace materialList?: Array; + + async noticeList() { + try { + const result = await apiService.noticeList(); + if (result.isSuccess()) { + this.noticeEntity = plainToInstance(NoticeEntity, result.data); + } else { + ToastUtils.show(result.message, true); + } + } catch (e) { + console.log(e); + ToastUtils.show(e); + } + } + + async getMaterialList(page: string) { + try { + const result = await apiService.getMaterialList(page); + if (result.isSuccess()) { + this.materialList = plainToInstance(MaterialEntity, result.data!!['items'] as Array); + } else { + ToastUtils.show(result.message, true); + } + } catch (e) { + console.log(e); + ToastUtils.show(e); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/LinkRecognizeViewModel.ets b/entry/src/main/ets/viewModel/LinkRecognizeViewModel.ets new file mode 100644 index 0000000..84bdb79 --- /dev/null +++ b/entry/src/main/ets/viewModel/LinkRecognizeViewModel.ets @@ -0,0 +1,95 @@ +import { plainToInstance } from 'class-transformer'; +import { MaterialInfoEntity } from '../entity/MaterialInfoEntity'; +import { VipPermissionEntity } from '../entity/VipPermissionEntity'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export class LinkRecognizeViewModel extends BaseViewModel { + @Trace materialInfo?: MaterialInfoEntity; + @Trace analysisInfo?: MaterialInfoEntity; + @Trace permissionInfo?: VipPermissionEntity; + @Trace errorCode: number = 0; + + private intervalId = 0 + + async getMaterialInfo(content: string) { + this.showLoading(); + try { + const result = await apiService.getMaterialInfo(content); + if (result.isSuccess()) { + this.materialInfo = plainToInstance(MaterialInfoEntity, result.data); + } else { + this.errorCode = result.code + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async analysisMaterial(logId: string, timeout: number) { + this.cancelInterval() + try { + let count = timeout; + this.intervalId = setInterval(async () => { + if (count > 0) { + count--; + const result = await apiService.analysisMaterial(logId); + if (result.isSuccess()) { + this.analysisInfo = plainToInstance(MaterialInfoEntity, result.data); + if (this.analysisInfo.material !== null || this.analysisInfo.status === -2) { + this.cancelInterval() + } + } else { + this.errorCode = result.code + ToastUtils.show(result.message, true); + this.cancelInterval() + } + } else { + this.cancelInterval() + } + }, 1000) + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.cancelInterval() + } + } + + async reportStatus(logId: string, status: string, size: string = '', message: string = '') { + try { + apiService.reportDownLoadStatus(logId, status, size, message) + } catch (e) { + console.log(e); + } + } + + async checkVip() { + this.showLoading(); + try { + const result = await apiService.checkPermission('download'); + if (result.isSuccess()) { + this.permissionInfo = plainToInstance(VipPermissionEntity, result.data); + } else { + this.errorCode = result.code + ToastUtils.show(result.message, true); + } + this.dismissLoading() + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + cancelInterval() { + if (this.intervalId !== 0) { + clearInterval(this.intervalId) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/LoginViewModel.ets b/entry/src/main/ets/viewModel/LoginViewModel.ets new file mode 100644 index 0000000..82e5639 --- /dev/null +++ b/entry/src/main/ets/viewModel/LoginViewModel.ets @@ -0,0 +1,64 @@ +import { plainToInstance } from 'class-transformer'; +import { LoginEntity } from '../entity/LoginEntity'; +import { SendCodeEntity } from '../entity/SendCodeEntity'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export default class LoginViewModel extends BaseViewModel { + @Trace codeEntity?: SendCodeEntity; + @Trace phoneLoginEntity?: LoginEntity; + @Trace wxLoginEntity?: LoginEntity; + + async sendCode(phone: string) { + this.showLoading(); + try { + const result = await apiService.sendCode(phone); + if (result.isSuccess()) { + this.codeEntity = plainToInstance(SendCodeEntity, result.data); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async phoneLogin(phone: string, code: string, timestamp: string) { + this.showLoading(); + try { + const result = await apiService.loginByPhone(phone, code, timestamp); + if (result.isSuccess()) { + this.phoneLoginEntity = plainToInstance(LoginEntity, result.data); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async wxLogin(code: string) { + this.showLoading(); + try { + const result = await apiService.loginByWX(code); + if (result.isSuccess()) { + this.wxLoginEntity = plainToInstance(LoginEntity, result.data); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/MainViewModel.ets b/entry/src/main/ets/viewModel/MainViewModel.ets new file mode 100644 index 0000000..1608411 --- /dev/null +++ b/entry/src/main/ets/viewModel/MainViewModel.ets @@ -0,0 +1,26 @@ +import { plainToInstance } from 'class-transformer'; +import { UserEntity } from '../entity/UserEntity'; +import { LoginManager } from '../manager/LoginGlobalManager'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export class MainViewModel extends BaseViewModel { + @Trace userEntity?: UserEntity; + + async userinfo() { + try { + const result = await apiService.userinfo(); + if (result.isSuccess()) { + this.userEntity = plainToInstance(UserEntity, result.data); + LoginManager.setUserInfo(this.userEntity); + } else { + ToastUtils.show(result.message, true); + } + } catch (e) { + console.log(e); + ToastUtils.show(e); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/ManageAccountViewModel.ets b/entry/src/main/ets/viewModel/ManageAccountViewModel.ets new file mode 100644 index 0000000..90e827d --- /dev/null +++ b/entry/src/main/ets/viewModel/ManageAccountViewModel.ets @@ -0,0 +1,46 @@ +import { plainToInstance } from 'class-transformer'; +import { AccountEntity } from '../entity/AccountEntity'; +import { LoginEntity } from '../entity/LoginEntity'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export default class ManageAccountViewModel extends BaseViewModel { + @Trace accounts?: Array; + @Trace loginEntity?: LoginEntity; + + async accountList() { + this.showLoading(); + try { + const result = await apiService.accountList('account'); + if (result.isSuccess()) { + this.accounts = plainToInstance(AccountEntity, result.data as Array); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async changeAccount(userId: string) { + this.showLoading(); + try { + const result = await apiService.changeAccount(userId) + if (result.isSuccess()) { + this.loginEntity = plainToInstance(LoginEntity, result.data); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/MineViewModel.ets b/entry/src/main/ets/viewModel/MineViewModel.ets new file mode 100644 index 0000000..2f2a1a8 --- /dev/null +++ b/entry/src/main/ets/viewModel/MineViewModel.ets @@ -0,0 +1,64 @@ +import { plainToInstance } from 'class-transformer'; +import { DiamondDetailEntity } from '../entity/DiamondDetailEntity'; +import { UserEntity } from '../entity/UserEntity'; +import { WxServiceEntity } from '../entity/WxServiceEntity'; +import { LoginManager } from '../manager/LoginGlobalManager'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export default class MineViewModel extends BaseViewModel { + @Trace userEntity?: UserEntity + @Trace wxService?: WxServiceEntity + @Trace diamondInfo?: DiamondDetailEntity + + async userinfo() { + try { + const result = await apiService.userinfo(); + if (result.isSuccess()) { + this.userEntity = plainToInstance(UserEntity, result.data); + LoginManager.setUserInfo(this.userEntity); + } else { + ToastUtils.show(result.message, true); + } + } catch (e) { + console.log(e); + ToastUtils.show(e); + } + } + + async getWxService() { + this.showLoading() + try { + const result = await apiService.wxService() + if (result.isSuccess()) { + this.wxService = plainToInstance(WxServiceEntity, result.data) + } else { + ToastUtils.show(result.message, true) + } + this.dismissLoading() + } catch (e) { + console.log(e) + ToastUtils.show(e) + this.dismissLoading() + } + } + + async getDiamondInfo() { + this.showLoading(); + try { + const result = await apiService.getDiamondInfo(); + if (result.isSuccess()) { + this.diamondInfo = plainToInstance(DiamondDetailEntity, result.data); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/QrcodeLoginViewModel.ets b/entry/src/main/ets/viewModel/QrcodeLoginViewModel.ets new file mode 100644 index 0000000..459e310 --- /dev/null +++ b/entry/src/main/ets/viewModel/QrcodeLoginViewModel.ets @@ -0,0 +1,25 @@ +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export class QrcodeLoginViewModel extends BaseViewModel { + @Trace loginResult?: object + + async login(code: string) { + this.showLoading() + try { + const result = await apiService.loginByCode(code); + if (result.isSuccess()) { + this.loginResult = new Object() + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + this.dismissLoading() + console.debug(e) + ToastUtils.show(e) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/SettingsViewModel.ets b/entry/src/main/ets/viewModel/SettingsViewModel.ets new file mode 100644 index 0000000..38e2c0c --- /dev/null +++ b/entry/src/main/ets/viewModel/SettingsViewModel.ets @@ -0,0 +1,25 @@ +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export class SettingsViewModel extends BaseViewModel { + @Trace destroy?: object; + + async userDestroy() { + this.showLoading(); + try { + const result = await apiService.userDestroy(); + if (result.isSuccess()) { + this.destroy = new Object(); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/UserSettingsViewModel.ets b/entry/src/main/ets/viewModel/UserSettingsViewModel.ets new file mode 100644 index 0000000..bfa9427 --- /dev/null +++ b/entry/src/main/ets/viewModel/UserSettingsViewModel.ets @@ -0,0 +1,82 @@ +import { Base64Util, FileUtil } from '@pura/harmony-utils'; +import { plainToInstance } from 'class-transformer'; +import { UploadImgEntity } from '../entity/UploadImgEntity'; +import { UserEntity } from '../entity/UserEntity'; +import { LoginManager } from '../manager/LoginGlobalManager'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; +import { fileIo } from '@kit.CoreFileKit'; + +@ObservedV2 +export class UserSettingsViewModel extends BaseViewModel { + @Trace userEntity?: UserEntity + @Trace imageEntity?: UploadImgEntity + @Trace update?: object + + async userinfo() { + this.showLoading() + try { + const result = await apiService.userinfo(); + if (result.isSuccess()) { + this.userEntity = plainToInstance(UserEntity, result.data); + LoginManager.setUserInfo(this.userEntity); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading() + } catch (e) { + this.dismissLoading() + console.log(e); + ToastUtils.show(e); + } + } + + async updateUserinfo(params: Record) { + this.showLoading() + try { + const result = await apiService.updateUserinfo(params); + if (result.isSuccess()) { + this.update = new Object() + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading() + } catch (e) { + this.dismissLoading() + console.log(e); + ToastUtils.show(e); + } + } + + async uploadImage(uri: string) { + this.showLoading() + try { + let file = FileUtil.openSync(uri, fileIo.OpenMode.READ_ONLY); + // 复制文件到缓存目录下 + let cacheFilePath = FileUtil.getCacheDirPath() + '/' + FileUtil.getFileName(uri) + FileUtil.copyFileSync(file.fd, cacheFilePath) + // 读取文件为ArrayBuffer + const file2 = FileUtil.openSync(cacheFilePath, 0o2); + const stat = FileUtil.lstatSync(cacheFilePath); + const buffer = new ArrayBuffer(stat.size); + FileUtil.readSync(file2.fd, buffer); + FileUtil.fsyncSync(file2.fd); + FileUtil.closeSync(file2.fd); + + const base64Str = Base64Util.encodeToStrSync(new Uint8Array(buffer)) + + const result = await apiService.uploadImage(base64Str, 'feedback'); + if (result.isSuccess()) { + this.imageEntity = plainToInstance(UploadImgEntity, result.data); + } else { + ToastUtils.show(result.message, true) + } + this.dismissLoading() + } catch (e) { + this.dismissLoading() + console.log(e); + ToastUtils.show(e); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/VipViewModel.ets b/entry/src/main/ets/viewModel/VipViewModel.ets new file mode 100644 index 0000000..5dde1e6 --- /dev/null +++ b/entry/src/main/ets/viewModel/VipViewModel.ets @@ -0,0 +1,88 @@ +import { plainToInstance } from "class-transformer"; +import { OrderEntity } from "../entity/OrderEntity"; +import { PayOrderEntity as PayOrderEntity } from "../entity/OrderPayEntity"; +import { VipMealEntity } from "../entity/VipMealEntity"; +import { apiService } from "../net/ApiService"; +import { ToastUtils } from "../utils/ToastUtils"; +import { BaseViewModel } from "./BaseViewModel"; + +@ObservedV2 +export class VipViewModel extends BaseViewModel { + @Trace goodsList?: Array; + @Trace payOrderEntity?: PayOrderEntity; + @Trace orderInfoEntity?: OrderEntity; + + private intervalId = 0 + + async mealList() { + this.showLoading(); + try { + const result = await apiService.goodsList('member'); + if (result.isSuccess()) { + this.goodsList = plainToInstance(VipMealEntity, result.data as Array); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async createOrder(goodsId: string, payType: string, source: string, coupon: string){ + this.showLoading(); + try { + const result = await apiService.createOrder(goodsId, payType, source, coupon); + if(result.isSuccess()) { + this.payOrderEntity = plainToInstance(PayOrderEntity, result.data); + } else { + ToastUtils.show(result.message, true); + } + this.dismissLoading(); + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async getOrderInfo(orderId: string) { + this.showLoading(true) + this.cancelInterval() + try { + let count = 10; + this.intervalId = setInterval(async () => { + if (count > 0) { + count--; + const result = await apiService.getOrderInfo(orderId); + if (result.isSuccess()) { + this.orderInfoEntity = plainToInstance(OrderEntity, result.data); + if (this.orderInfoEntity.status == '2') { + this.dismissLoading() + this.cancelInterval() + } + } else { + ToastUtils.show(result.message, true); + this.dismissLoading() + this.cancelInterval() + } + } else { + this.dismissLoading() + this.cancelInterval() + } + }, 2000) + } catch (e) { + console.log(e); + this.dismissLoading() + this.cancelInterval() + } + } + + cancelInterval() { + if (this.intervalId !== 0) { + clearInterval(this.intervalId) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/WxVideoViewModel.ets b/entry/src/main/ets/viewModel/WxVideoViewModel.ets new file mode 100644 index 0000000..d3e47df --- /dev/null +++ b/entry/src/main/ets/viewModel/WxVideoViewModel.ets @@ -0,0 +1,112 @@ +import { plainToInstance } from 'class-transformer'; +import { VipPermissionEntity } from '../entity/VipPermissionEntity'; +import { WxServiceEntity } from '../entity/WxServiceEntity'; +import { WxVideoEntity } from '../entity/WxVideoEntity'; +import { apiService } from '../net/ApiService'; +import { ToastUtils } from '../utils/ToastUtils'; +import { BaseViewModel } from './BaseViewModel'; + +@ObservedV2 +export class WxVideoViewModel extends BaseViewModel { + @Trace wxVideo?: WxVideoEntity; + @Trace deleteVideo?: object; + @Trace permissionInfo?: VipPermissionEntity; + @Trace errorCode: number = 0; + @Trace wxService?: WxServiceEntity + @Trace wxUserinfo?: object + + async videoList(scene: string) { + this.showLoading() + try { + const result = await apiService.wxVideoList(scene) + if (result.isSuccess()) { + this.wxVideo = plainToInstance(WxVideoEntity, result.data); + } else { + this.errorCode = result.code + ToastUtils.show(result.message, true) + } + this.dismissLoading() + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading() + } + } + + async deleteWxVideo(logId: string) { + this.showLoading() + try { + const result = await apiService.deleteWxVideo(logId) + if (result.isSuccess()) { + this.deleteVideo = new Object() + } else { + ToastUtils.show(result.message, true) + } + this.dismissLoading() + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading() + } + } + + async reportStatus(logId: string, status: string, size: string = '', message: string = '') { + try { + apiService.reportDownLoadStatus(logId, status, size, message) + } catch (e) { + console.log(e); + } + } + + async checkVip() { + this.showLoading(); + try { + const result = await apiService.checkPermission('download'); + if (result.isSuccess()) { + this.permissionInfo = plainToInstance(VipPermissionEntity, result.data); + } else { + this.errorCode = result.code + ToastUtils.show(result.message, true); + } + this.dismissLoading() + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async wxServiceInfo() { + this.showLoading() + try { + const result = await apiService.wxVideoService() + if (result.isSuccess()) { + this.wxService = plainToInstance(WxServiceEntity, result.data) + } else { + ToastUtils.show(result.message, true) + } + this.dismissLoading() + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } + + async bindWxUserinfo(code: string) { + this.showLoading() + try { + const result = await apiService.bindWxUserInfo(code) + if (result.isSuccess()) { + this.wxUserinfo = new Object() + } else { + ToastUtils.show(result.message, true) + } + this.dismissLoading() + } catch (e) { + console.log(e); + ToastUtils.show(e); + this.dismissLoading(); + } + } +} \ No newline at end of file diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 new file mode 100644 index 0000000..5715cc2 --- /dev/null +++ b/entry/src/main/module.json5 @@ -0,0 +1,107 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone", + "tablet" + ], + "querySchemes": [ + "https", + "alipays", + "weixin", + "wxopensdk" + ], + "metadata": [ + { + "name": "GETUI_APPID", + "value": "HE9nqTugoL8t42IrENGKf6" + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.INTERNET" + }, + { + "name": "ohos.permission.GET_NETWORK_INFO" + }, + { + "name": "ohos.permission.GET_WIFI_INFO" + }, + { + "name": "ohos.permission.KEEP_BACKGROUND_RUNNING" + }, + { + "name": "ohos.permission.RUNNING_LOCK" + }, + { + "name": "ohos.permission.STORE_PERSISTENT_DATA" + }, + { + "name": "ohos.permission.APP_TRACKING_CONSENT", + "reason": "$string:oaid_reason", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.READ_PASTEBOARD", + "reason": "$string:read_pasteboard_reason", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + } + ], + "srcEntry": "./ets/MyAbilityStage.ets", + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "label": "$string:EntryAbility_label", + "icon": "$media:layer_logo", + "startWindowIcon": "$media:ic_splash_logo", + "startWindowBackground": "$color:window_background", + "exported": true, + "orientation": "portrait", + "launchType": "singleton", + "removeMissionAfterTerminate": true, + "skills": [ + { + "entities": ["entity.system.home"], + "actions": ["action.system.home"] + }, + { + "entities": ["entity.system.default"], + "actions": ["action.system.view"] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000..fb2cdde --- /dev/null +++ b/entry/src/main/resources/base/element/color.json @@ -0,0 +1,106 @@ +{ + "color": [ + { + "name": "window_background", + "value": "#F3F5F9" + }, + { + "name": "color_466afd", + "value": "#466AFD" + }, + { + "name": "color_212226", + "value": "#212226" + }, + { + "name": "color_727686", + "value": "#727686" + }, + { + "name": "color_80859B", + "value": "#80859B" + }, + { + "name": "color_f1f2f6", + "value": "#F1F2F6" + }, + { + "name": "color_1a1a1a", + "value": "#1A1A1A" + }, + { + "name": "color_bcbcbc", + "value": "#BCBCBC" + }, + { + "name": "color_dfdfdf", + "value": "#DFDFDF" + }, + { + "name": "color_1b1b1b", + "value": "#1B1B1B" + }, + + + { + "name": "color_222222", + "value": "#222222" + }, + { + "name": "color_333333", + "value": "#333333" + }, + { + "name": "color_90ffffff", + "value": "#E5FFFFFF" + }, + { + "name": "color_80ffffff", + "value": "#CCFFFFFF" + }, + { + "name": "color_60ffffff", + "value": "#99FFFFFF" + }, + { + "name": "color_50ffffff", + "value": "#80FFFFFF" + }, + { + "name": "color_30ffffff", + "value": "#4DFFFFFF" + }, + { + "name": "color_10ffffff", + "value": "#1AFFFFFF" + }, + { + "name": "color_9c9c9c", + "value": "#9C9C9C" + }, + { + "name": "color_f0365e", + "value": "#F0365E" + }, + { + "name": "color_757575", + "value": "#757575" + }, + { + "name": "color_999999", + "value": "#999999" + }, + { + "name": "color_666666", + "value": "#666666" + }, + { + "name": "color_bebebe", + "value": "#BEBEBE" + }, + { + "name": "color_ededed", + "value": "#EDEDED" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/float.json b/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000..33ea223 --- /dev/null +++ b/entry/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000..30341a9 --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "$string:app_name" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_action_delete.webp b/entry/src/main/resources/base/media/ic_action_delete.webp new file mode 100644 index 0000000..e26e00e Binary files /dev/null and b/entry/src/main/resources/base/media/ic_action_delete.webp differ diff --git a/entry/src/main/resources/base/media/ic_action_share.webp b/entry/src/main/resources/base/media/ic_action_share.webp new file mode 100644 index 0000000..db2f83e Binary files /dev/null and b/entry/src/main/resources/base/media/ic_action_share.webp differ diff --git a/entry/src/main/resources/base/media/ic_add_audio.png b/entry/src/main/resources/base/media/ic_add_audio.png new file mode 100644 index 0000000..1e83232 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_add_audio.png differ diff --git a/entry/src/main/resources/base/media/ic_add_image.webp b/entry/src/main/resources/base/media/ic_add_image.webp new file mode 100644 index 0000000..5958091 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_add_image.webp differ diff --git a/entry/src/main/resources/base/media/ic_add_video.png b/entry/src/main/resources/base/media/ic_add_video.png new file mode 100644 index 0000000..a5ed379 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_add_video.png differ diff --git a/entry/src/main/resources/base/media/ic_ali_pay.webp b/entry/src/main/resources/base/media/ic_ali_pay.webp new file mode 100644 index 0000000..76da22e Binary files /dev/null and b/entry/src/main/resources/base/media/ic_ali_pay.webp differ diff --git a/entry/src/main/resources/base/media/ic_ali_pay3.webp b/entry/src/main/resources/base/media/ic_ali_pay3.webp new file mode 100644 index 0000000..34b0ce8 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_ali_pay3.webp differ diff --git a/entry/src/main/resources/base/media/ic_area_bg.webp b/entry/src/main/resources/base/media/ic_area_bg.webp new file mode 100644 index 0000000..e2cc2e5 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_area_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_arrow_dp16.png b/entry/src/main/resources/base/media/ic_arrow_dp16.png new file mode 100644 index 0000000..c592d54 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_arrow_dp16.png differ diff --git a/entry/src/main/resources/base/media/ic_arrow_dp22.png b/entry/src/main/resources/base/media/ic_arrow_dp22.png new file mode 100644 index 0000000..63db46e Binary files /dev/null and b/entry/src/main/resources/base/media/ic_arrow_dp22.png differ diff --git a/entry/src/main/resources/base/media/ic_audio_thumb.webp b/entry/src/main/resources/base/media/ic_audio_thumb.webp new file mode 100644 index 0000000..d31ce81 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_audio_thumb.webp differ diff --git a/entry/src/main/resources/base/media/ic_back.svg b/entry/src/main/resources/base/media/ic_back.svg new file mode 100644 index 0000000..f716132 --- /dev/null +++ b/entry/src/main/resources/base/media/ic_back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_bind_phone.webp b/entry/src/main/resources/base/media/ic_bind_phone.webp new file mode 100644 index 0000000..cb57812 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_bind_phone.webp differ diff --git a/entry/src/main/resources/base/media/ic_bind_wx.webp b/entry/src/main/resources/base/media/ic_bind_wx.webp new file mode 100644 index 0000000..df05b6a Binary files /dev/null and b/entry/src/main/resources/base/media/ic_bind_wx.webp differ diff --git a/entry/src/main/resources/base/media/ic_black_back.svg b/entry/src/main/resources/base/media/ic_black_back.svg new file mode 100644 index 0000000..c8965ac --- /dev/null +++ b/entry/src/main/resources/base/media/ic_black_back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_check_false.webp b/entry/src/main/resources/base/media/ic_check_false.webp new file mode 100644 index 0000000..fa69c45 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_check_false.webp differ diff --git a/entry/src/main/resources/base/media/ic_check_true.webp b/entry/src/main/resources/base/media/ic_check_true.webp new file mode 100644 index 0000000..219fd23 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_check_true.webp differ diff --git a/entry/src/main/resources/base/media/ic_clear_record.webp b/entry/src/main/resources/base/media/ic_clear_record.webp new file mode 100644 index 0000000..f326ecc Binary files /dev/null and b/entry/src/main/resources/base/media/ic_clear_record.webp differ diff --git a/entry/src/main/resources/base/media/ic_clear_text.webp b/entry/src/main/resources/base/media/ic_clear_text.webp new file mode 100644 index 0000000..0d8c6be Binary files /dev/null and b/entry/src/main/resources/base/media/ic_clear_text.webp differ diff --git a/entry/src/main/resources/base/media/ic_close.webp b/entry/src/main/resources/base/media/ic_close.webp new file mode 100644 index 0000000..8b9bd70 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_close.webp differ diff --git a/entry/src/main/resources/base/media/ic_close2.webp b/entry/src/main/resources/base/media/ic_close2.webp new file mode 100644 index 0000000..6166bdc Binary files /dev/null and b/entry/src/main/resources/base/media/ic_close2.webp differ diff --git a/entry/src/main/resources/base/media/ic_close_dialog.webp b/entry/src/main/resources/base/media/ic_close_dialog.webp new file mode 100644 index 0000000..9c83439 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_close_dialog.webp differ diff --git a/entry/src/main/resources/base/media/ic_completed.png b/entry/src/main/resources/base/media/ic_completed.png new file mode 100644 index 0000000..335d851 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_completed.png differ diff --git a/entry/src/main/resources/base/media/ic_copy_id.webp b/entry/src/main/resources/base/media/ic_copy_id.webp new file mode 100644 index 0000000..9fc7d7b Binary files /dev/null and b/entry/src/main/resources/base/media/ic_copy_id.webp differ diff --git a/entry/src/main/resources/base/media/ic_coupon.webp b/entry/src/main/resources/base/media/ic_coupon.webp new file mode 100644 index 0000000..2d5922b Binary files /dev/null and b/entry/src/main/resources/base/media/ic_coupon.webp differ diff --git a/entry/src/main/resources/base/media/ic_course1.webp b/entry/src/main/resources/base/media/ic_course1.webp new file mode 100644 index 0000000..2ddc5bf Binary files /dev/null and b/entry/src/main/resources/base/media/ic_course1.webp differ diff --git a/entry/src/main/resources/base/media/ic_course2.webp b/entry/src/main/resources/base/media/ic_course2.webp new file mode 100644 index 0000000..a724304 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_course2.webp differ diff --git a/entry/src/main/resources/base/media/ic_course3.webp b/entry/src/main/resources/base/media/ic_course3.webp new file mode 100644 index 0000000..893f5cf Binary files /dev/null and b/entry/src/main/resources/base/media/ic_course3.webp differ diff --git a/entry/src/main/resources/base/media/ic_course_thumb.webp b/entry/src/main/resources/base/media/ic_course_thumb.webp new file mode 100644 index 0000000..a58ca9c Binary files /dev/null and b/entry/src/main/resources/base/media/ic_course_thumb.webp differ diff --git a/entry/src/main/resources/base/media/ic_default_avatar.webp b/entry/src/main/resources/base/media/ic_default_avatar.webp new file mode 100644 index 0000000..dfc1101 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_default_avatar.webp differ diff --git a/entry/src/main/resources/base/media/ic_delete_img.webp b/entry/src/main/resources/base/media/ic_delete_img.webp new file mode 100644 index 0000000..f15b4ea Binary files /dev/null and b/entry/src/main/resources/base/media/ic_delete_img.webp differ diff --git a/entry/src/main/resources/base/media/ic_delete_material.webp b/entry/src/main/resources/base/media/ic_delete_material.webp new file mode 100644 index 0000000..6990afc Binary files /dev/null and b/entry/src/main/resources/base/media/ic_delete_material.webp differ diff --git a/entry/src/main/resources/base/media/ic_diamond.webp b/entry/src/main/resources/base/media/ic_diamond.webp new file mode 100644 index 0000000..4064470 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_diamond.webp differ diff --git a/entry/src/main/resources/base/media/ic_diamond_count_bg.webp b/entry/src/main/resources/base/media/ic_diamond_count_bg.webp new file mode 100644 index 0000000..81483d4 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_diamond_count_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_diamond_meal_checked.webp b/entry/src/main/resources/base/media/ic_diamond_meal_checked.webp new file mode 100644 index 0000000..1c831d8 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_diamond_meal_checked.webp differ diff --git a/entry/src/main/resources/base/media/ic_diamond_rule_indicator.webp b/entry/src/main/resources/base/media/ic_diamond_rule_indicator.webp new file mode 100644 index 0000000..a0805ed Binary files /dev/null and b/entry/src/main/resources/base/media/ic_diamond_rule_indicator.webp differ diff --git a/entry/src/main/resources/base/media/ic_diamond_rule_top_bg.webp b/entry/src/main/resources/base/media/ic_diamond_rule_top_bg.webp new file mode 100644 index 0000000..3962c82 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_diamond_rule_top_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_diamond_top_bg.webp b/entry/src/main/resources/base/media/ic_diamond_top_bg.webp new file mode 100644 index 0000000..2e39ff3 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_diamond_top_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_diamond_used_count_bg.webp b/entry/src/main/resources/base/media/ic_diamond_used_count_bg.webp new file mode 100644 index 0000000..81483d4 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_diamond_used_count_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_diamond_vip_bg1.webp b/entry/src/main/resources/base/media/ic_diamond_vip_bg1.webp new file mode 100644 index 0000000..d3cfd61 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_diamond_vip_bg1.webp differ diff --git a/entry/src/main/resources/base/media/ic_diamond_vip_bg2.webp b/entry/src/main/resources/base/media/ic_diamond_vip_bg2.webp new file mode 100644 index 0000000..b642837 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_diamond_vip_bg2.webp differ diff --git a/entry/src/main/resources/base/media/ic_download1.webp b/entry/src/main/resources/base/media/ic_download1.webp new file mode 100644 index 0000000..3d48ebc Binary files /dev/null and b/entry/src/main/resources/base/media/ic_download1.webp differ diff --git a/entry/src/main/resources/base/media/ic_download_disable.webp b/entry/src/main/resources/base/media/ic_download_disable.webp new file mode 100644 index 0000000..c842453 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_download_disable.webp differ diff --git a/entry/src/main/resources/base/media/ic_download_enable.webp b/entry/src/main/resources/base/media/ic_download_enable.webp new file mode 100644 index 0000000..7b0112a Binary files /dev/null and b/entry/src/main/resources/base/media/ic_download_enable.webp differ diff --git a/entry/src/main/resources/base/media/ic_downloading.png b/entry/src/main/resources/base/media/ic_downloading.png new file mode 100644 index 0000000..a5d627e Binary files /dev/null and b/entry/src/main/resources/base/media/ic_downloading.png differ diff --git a/entry/src/main/resources/base/media/ic_empty_audio.webp b/entry/src/main/resources/base/media/ic_empty_audio.webp new file mode 100644 index 0000000..84992dd Binary files /dev/null and b/entry/src/main/resources/base/media/ic_empty_audio.webp differ diff --git a/entry/src/main/resources/base/media/ic_empty_coupon.webp b/entry/src/main/resources/base/media/ic_empty_coupon.webp new file mode 100644 index 0000000..f56729f Binary files /dev/null and b/entry/src/main/resources/base/media/ic_empty_coupon.webp differ diff --git a/entry/src/main/resources/base/media/ic_empty_data.webp b/entry/src/main/resources/base/media/ic_empty_data.webp new file mode 100644 index 0000000..1604f15 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_empty_data.webp differ diff --git a/entry/src/main/resources/base/media/ic_empty_image.webp b/entry/src/main/resources/base/media/ic_empty_image.webp new file mode 100644 index 0000000..74a6d19 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_empty_image.webp differ diff --git a/entry/src/main/resources/base/media/ic_empty_text.webp b/entry/src/main/resources/base/media/ic_empty_text.webp new file mode 100644 index 0000000..be715ef Binary files /dev/null and b/entry/src/main/resources/base/media/ic_empty_text.webp differ diff --git a/entry/src/main/resources/base/media/ic_empty_video.webp b/entry/src/main/resources/base/media/ic_empty_video.webp new file mode 100644 index 0000000..063aa19 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_empty_video.webp differ diff --git a/entry/src/main/resources/base/media/ic_guide_1.webp b/entry/src/main/resources/base/media/ic_guide_1.webp new file mode 100644 index 0000000..4d72306 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_guide_1.webp differ diff --git a/entry/src/main/resources/base/media/ic_guide_2.webp b/entry/src/main/resources/base/media/ic_guide_2.webp new file mode 100644 index 0000000..24c706a Binary files /dev/null and b/entry/src/main/resources/base/media/ic_guide_2.webp differ diff --git a/entry/src/main/resources/base/media/ic_guide_3.webp b/entry/src/main/resources/base/media/ic_guide_3.webp new file mode 100644 index 0000000..8663780 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_guide_3.webp differ diff --git a/entry/src/main/resources/base/media/ic_guide_4.webp b/entry/src/main/resources/base/media/ic_guide_4.webp new file mode 100644 index 0000000..3bbaabb Binary files /dev/null and b/entry/src/main/resources/base/media/ic_guide_4.webp differ diff --git a/entry/src/main/resources/base/media/ic_guide_bg.webp b/entry/src/main/resources/base/media/ic_guide_bg.webp new file mode 100644 index 0000000..ba0633a Binary files /dev/null and b/entry/src/main/resources/base/media/ic_guide_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_guide_cover.webp b/entry/src/main/resources/base/media/ic_guide_cover.webp new file mode 100644 index 0000000..422d88f Binary files /dev/null and b/entry/src/main/resources/base/media/ic_guide_cover.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_default.webp b/entry/src/main/resources/base/media/ic_home_default.webp new file mode 100644 index 0000000..4a22997 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_default.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_icon1.webp b/entry/src/main/resources/base/media/ic_home_icon1.webp new file mode 100644 index 0000000..7299054 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_icon1.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_icon10.webp b/entry/src/main/resources/base/media/ic_home_icon10.webp new file mode 100644 index 0000000..ebbbde6 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_icon10.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_icon2.webp b/entry/src/main/resources/base/media/ic_home_icon2.webp new file mode 100644 index 0000000..c854aac Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_icon2.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_icon3.webp b/entry/src/main/resources/base/media/ic_home_icon3.webp new file mode 100644 index 0000000..ee831b0 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_icon3.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_icon4.webp b/entry/src/main/resources/base/media/ic_home_icon4.webp new file mode 100644 index 0000000..165e841 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_icon4.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_icon5.webp b/entry/src/main/resources/base/media/ic_home_icon5.webp new file mode 100644 index 0000000..aa8bd2c Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_icon5.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_icon6.webp b/entry/src/main/resources/base/media/ic_home_icon6.webp new file mode 100644 index 0000000..5b6987b Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_icon6.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_icon7.webp b/entry/src/main/resources/base/media/ic_home_icon7.webp new file mode 100644 index 0000000..990a85c Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_icon7.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_icon8.webp b/entry/src/main/resources/base/media/ic_home_icon8.webp new file mode 100644 index 0000000..37acd6d Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_icon8.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_icon9.webp b/entry/src/main/resources/base/media/ic_home_icon9.webp new file mode 100644 index 0000000..df875d5 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_icon9.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_link_start.webp b/entry/src/main/resources/base/media/ic_home_link_start.webp new file mode 100644 index 0000000..44e4297 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_link_start.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_link_support_icon1.webp b/entry/src/main/resources/base/media/ic_home_link_support_icon1.webp new file mode 100644 index 0000000..9756f0b Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_link_support_icon1.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_link_support_icon2.webp b/entry/src/main/resources/base/media/ic_home_link_support_icon2.webp new file mode 100644 index 0000000..ccaee75 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_link_support_icon2.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_link_support_icon3.webp b/entry/src/main/resources/base/media/ic_home_link_support_icon3.webp new file mode 100644 index 0000000..2318473 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_link_support_icon3.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_select.webp b/entry/src/main/resources/base/media/ic_home_select.webp new file mode 100644 index 0000000..2632450 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_select.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_top_bg.webp b/entry/src/main/resources/base/media/ic_home_top_bg.webp new file mode 100644 index 0000000..01cd58a Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_top_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_top_icon1.webp b/entry/src/main/resources/base/media/ic_home_top_icon1.webp new file mode 100644 index 0000000..95ff02d Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_top_icon1.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_top_icon2.webp b/entry/src/main/resources/base/media/ic_home_top_icon2.webp new file mode 100644 index 0000000..44ee4ea Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_top_icon2.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_top_icon3.webp b/entry/src/main/resources/base/media/ic_home_top_icon3.webp new file mode 100644 index 0000000..779339a Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_top_icon3.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_top_menu_arrow1.webp b/entry/src/main/resources/base/media/ic_home_top_menu_arrow1.webp new file mode 100644 index 0000000..bc9e148 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_top_menu_arrow1.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_top_menu_arrow2.webp b/entry/src/main/resources/base/media/ic_home_top_menu_arrow2.webp new file mode 100644 index 0000000..951f6e4 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_top_menu_arrow2.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_top_menu_arrow3.webp b/entry/src/main/resources/base/media/ic_home_top_menu_arrow3.webp new file mode 100644 index 0000000..0f9ffed Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_top_menu_arrow3.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_top_menu_bg1.webp b/entry/src/main/resources/base/media/ic_home_top_menu_bg1.webp new file mode 100644 index 0000000..af649e5 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_top_menu_bg1.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_top_menu_bg2.webp b/entry/src/main/resources/base/media/ic_home_top_menu_bg2.webp new file mode 100644 index 0000000..0ccc238 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_top_menu_bg2.webp differ diff --git a/entry/src/main/resources/base/media/ic_home_top_menu_bg3.webp b/entry/src/main/resources/base/media/ic_home_top_menu_bg3.webp new file mode 100644 index 0000000..e52922c Binary files /dev/null and b/entry/src/main/resources/base/media/ic_home_top_menu_bg3.webp differ diff --git a/entry/src/main/resources/base/media/ic_image_water_marker.webp b/entry/src/main/resources/base/media/ic_image_water_marker.webp new file mode 100644 index 0000000..a43c4a2 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_image_water_marker.webp differ diff --git a/entry/src/main/resources/base/media/ic_left_top_rect.webp b/entry/src/main/resources/base/media/ic_left_top_rect.webp new file mode 100644 index 0000000..4a5f027 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_left_top_rect.webp differ diff --git a/entry/src/main/resources/base/media/ic_link.webp b/entry/src/main/resources/base/media/ic_link.webp new file mode 100644 index 0000000..3995e77 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_link.webp differ diff --git a/entry/src/main/resources/base/media/ic_loading.png b/entry/src/main/resources/base/media/ic_loading.png new file mode 100644 index 0000000..b8deebb Binary files /dev/null and b/entry/src/main/resources/base/media/ic_loading.png differ diff --git a/entry/src/main/resources/base/media/ic_login_code.webp b/entry/src/main/resources/base/media/ic_login_code.webp new file mode 100644 index 0000000..94a90b6 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_login_code.webp differ diff --git a/entry/src/main/resources/base/media/ic_login_logo.webp b/entry/src/main/resources/base/media/ic_login_logo.webp new file mode 100644 index 0000000..e7f8891 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_login_logo.webp differ diff --git a/entry/src/main/resources/base/media/ic_login_phone.webp b/entry/src/main/resources/base/media/ic_login_phone.webp new file mode 100644 index 0000000..c4f06cf Binary files /dev/null and b/entry/src/main/resources/base/media/ic_login_phone.webp differ diff --git a/entry/src/main/resources/base/media/ic_login_top_bg.webp b/entry/src/main/resources/base/media/ic_login_top_bg.webp new file mode 100644 index 0000000..c770208 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_login_top_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_material_default.webp b/entry/src/main/resources/base/media/ic_material_default.webp new file mode 100644 index 0000000..8448558 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_material_default.webp differ diff --git a/entry/src/main/resources/base/media/ic_material_select.webp b/entry/src/main/resources/base/media/ic_material_select.webp new file mode 100644 index 0000000..0cff546 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_material_select.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_default.webp b/entry/src/main/resources/base/media/ic_mine_default.webp new file mode 100644 index 0000000..cfc2dde Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_default.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_icon1.webp b/entry/src/main/resources/base/media/ic_mine_icon1.webp new file mode 100644 index 0000000..75ae310 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_icon1.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_icon2.webp b/entry/src/main/resources/base/media/ic_mine_icon2.webp new file mode 100644 index 0000000..4860098 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_icon2.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_icon3.webp b/entry/src/main/resources/base/media/ic_mine_icon3.webp new file mode 100644 index 0000000..2be8949 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_icon3.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_icon4.webp b/entry/src/main/resources/base/media/ic_mine_icon4.webp new file mode 100644 index 0000000..f8af9b8 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_icon4.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_icon5.webp b/entry/src/main/resources/base/media/ic_mine_icon5.webp new file mode 100644 index 0000000..8cf5bbd Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_icon5.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_icon6.webp b/entry/src/main/resources/base/media/ic_mine_icon6.webp new file mode 100644 index 0000000..81d94fa Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_icon6.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_icon7.webp b/entry/src/main/resources/base/media/ic_mine_icon7.webp new file mode 100644 index 0000000..243d33c Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_icon7.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_select.webp b/entry/src/main/resources/base/media/ic_mine_select.webp new file mode 100644 index 0000000..4477eb3 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_select.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_top_bg.webp b/entry/src/main/resources/base/media/ic_mine_top_bg.webp new file mode 100644 index 0000000..d00bee7 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_top_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_mine_vip_bg.webp b/entry/src/main/resources/base/media/ic_mine_vip_bg.webp new file mode 100644 index 0000000..346c32d Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mine_vip_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_mirror_h.webp b/entry/src/main/resources/base/media/ic_mirror_h.webp new file mode 100644 index 0000000..604b357 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mirror_h.webp differ diff --git a/entry/src/main/resources/base/media/ic_mirror_v.webp b/entry/src/main/resources/base/media/ic_mirror_v.webp new file mode 100644 index 0000000..218b40c Binary files /dev/null and b/entry/src/main/resources/base/media/ic_mirror_v.webp differ diff --git a/entry/src/main/resources/base/media/ic_notice.webp b/entry/src/main/resources/base/media/ic_notice.webp new file mode 100644 index 0000000..b52953b Binary files /dev/null and b/entry/src/main/resources/base/media/ic_notice.webp differ diff --git a/entry/src/main/resources/base/media/ic_notify_icon.webp b/entry/src/main/resources/base/media/ic_notify_icon.webp new file mode 100644 index 0000000..94f365d Binary files /dev/null and b/entry/src/main/resources/base/media/ic_notify_icon.webp differ diff --git a/entry/src/main/resources/base/media/ic_onekey_login.webp b/entry/src/main/resources/base/media/ic_onekey_login.webp new file mode 100644 index 0000000..a23110c Binary files /dev/null and b/entry/src/main/resources/base/media/ic_onekey_login.webp differ diff --git a/entry/src/main/resources/base/media/ic_pay_false.webp b/entry/src/main/resources/base/media/ic_pay_false.webp new file mode 100644 index 0000000..b1d7832 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_pay_false.webp differ diff --git a/entry/src/main/resources/base/media/ic_pay_false2.webp b/entry/src/main/resources/base/media/ic_pay_false2.webp new file mode 100644 index 0000000..2ab34e7 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_pay_false2.webp differ diff --git a/entry/src/main/resources/base/media/ic_pay_true.webp b/entry/src/main/resources/base/media/ic_pay_true.webp new file mode 100644 index 0000000..34f6d83 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_pay_true.webp differ diff --git a/entry/src/main/resources/base/media/ic_pay_true2.webp b/entry/src/main/resources/base/media/ic_pay_true2.webp new file mode 100644 index 0000000..f2d78f0 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_pay_true2.webp differ diff --git a/entry/src/main/resources/base/media/ic_placeholder.webp b/entry/src/main/resources/base/media/ic_placeholder.webp new file mode 100644 index 0000000..7af9197 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_placeholder.webp differ diff --git a/entry/src/main/resources/base/media/ic_placeholder_black.webp b/entry/src/main/resources/base/media/ic_placeholder_black.webp new file mode 100644 index 0000000..94f195d Binary files /dev/null and b/entry/src/main/resources/base/media/ic_placeholder_black.webp differ diff --git a/entry/src/main/resources/base/media/ic_play_video.webp b/entry/src/main/resources/base/media/ic_play_video.webp new file mode 100644 index 0000000..68cc151 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_play_video.webp differ diff --git a/entry/src/main/resources/base/media/ic_playback_helper.webp b/entry/src/main/resources/base/media/ic_playback_helper.webp new file mode 100644 index 0000000..2626d81 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_playback_helper.webp differ diff --git a/entry/src/main/resources/base/media/ic_player_controls_pause.png b/entry/src/main/resources/base/media/ic_player_controls_pause.png new file mode 100644 index 0000000..b219f83 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_player_controls_pause.png differ diff --git a/entry/src/main/resources/base/media/ic_player_controls_play.png b/entry/src/main/resources/base/media/ic_player_controls_play.png new file mode 100644 index 0000000..899637a Binary files /dev/null and b/entry/src/main/resources/base/media/ic_player_controls_play.png differ diff --git a/entry/src/main/resources/base/media/ic_qrcode_login_computer_black.webp b/entry/src/main/resources/base/media/ic_qrcode_login_computer_black.webp new file mode 100644 index 0000000..cca2d53 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_qrcode_login_computer_black.webp differ diff --git a/entry/src/main/resources/base/media/ic_reupload_video.png b/entry/src/main/resources/base/media/ic_reupload_video.png new file mode 100644 index 0000000..bcf3398 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_reupload_video.png differ diff --git a/entry/src/main/resources/base/media/ic_right_bottom_rect.webp b/entry/src/main/resources/base/media/ic_right_bottom_rect.webp new file mode 100644 index 0000000..144e72e Binary files /dev/null and b/entry/src/main/resources/base/media/ic_right_bottom_rect.webp differ diff --git a/entry/src/main/resources/base/media/ic_scan.webp b/entry/src/main/resources/base/media/ic_scan.webp new file mode 100644 index 0000000..a7b460c Binary files /dev/null and b/entry/src/main/resources/base/media/ic_scan.webp differ diff --git a/entry/src/main/resources/base/media/ic_share_material.webp b/entry/src/main/resources/base/media/ic_share_material.webp new file mode 100644 index 0000000..b3ae209 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_share_material.webp differ diff --git a/entry/src/main/resources/base/media/ic_splash_bg.webp b/entry/src/main/resources/base/media/ic_splash_bg.webp new file mode 100644 index 0000000..7295f88 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_splash_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_splash_logo.webp b/entry/src/main/resources/base/media/ic_splash_logo.webp new file mode 100644 index 0000000..5ee1812 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_splash_logo.webp differ diff --git a/entry/src/main/resources/base/media/ic_tab_indicator.webp b/entry/src/main/resources/base/media/ic_tab_indicator.webp new file mode 100644 index 0000000..808de6f Binary files /dev/null and b/entry/src/main/resources/base/media/ic_tab_indicator.webp differ diff --git a/entry/src/main/resources/base/media/ic_text_water_marker.webp b/entry/src/main/resources/base/media/ic_text_water_marker.webp new file mode 100644 index 0000000..680d14c Binary files /dev/null and b/entry/src/main/resources/base/media/ic_text_water_marker.webp differ diff --git a/entry/src/main/resources/base/media/ic_tip_dialog_top_bg.webp b/entry/src/main/resources/base/media/ic_tip_dialog_top_bg.webp new file mode 100644 index 0000000..4651426 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_tip_dialog_top_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_tool_default.webp b/entry/src/main/resources/base/media/ic_tool_default.webp new file mode 100644 index 0000000..fd8217b Binary files /dev/null and b/entry/src/main/resources/base/media/ic_tool_default.webp differ diff --git a/entry/src/main/resources/base/media/ic_tool_select.webp b/entry/src/main/resources/base/media/ic_tool_select.webp new file mode 100644 index 0000000..4ec4cdc Binary files /dev/null and b/entry/src/main/resources/base/media/ic_tool_select.webp differ diff --git a/entry/src/main/resources/base/media/ic_video_helper.webp b/entry/src/main/resources/base/media/ic_video_helper.webp new file mode 100644 index 0000000..ea57a7f Binary files /dev/null and b/entry/src/main/resources/base/media/ic_video_helper.webp differ diff --git a/entry/src/main/resources/base/media/ic_vip_bg1.webp b/entry/src/main/resources/base/media/ic_vip_bg1.webp new file mode 100644 index 0000000..23235c0 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_vip_bg1.webp differ diff --git a/entry/src/main/resources/base/media/ic_vip_bg2.webp b/entry/src/main/resources/base/media/ic_vip_bg2.webp new file mode 100644 index 0000000..a8475e9 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_vip_bg2.webp differ diff --git a/entry/src/main/resources/base/media/ic_vip_fire_tag.webp b/entry/src/main/resources/base/media/ic_vip_fire_tag.webp new file mode 100644 index 0000000..ff63d78 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_vip_fire_tag.webp differ diff --git a/entry/src/main/resources/base/media/ic_vip_pay_btn.webp b/entry/src/main/resources/base/media/ic_vip_pay_btn.webp new file mode 100644 index 0000000..26b39bc Binary files /dev/null and b/entry/src/main/resources/base/media/ic_vip_pay_btn.webp differ diff --git a/entry/src/main/resources/base/media/ic_vip_tips.webp b/entry/src/main/resources/base/media/ic_vip_tips.webp new file mode 100644 index 0000000..3ae3563 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_vip_tips.webp differ diff --git a/entry/src/main/resources/base/media/ic_vip_top_bg.webp b/entry/src/main/resources/base/media/ic_vip_top_bg.webp new file mode 100644 index 0000000..423fcf8 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_vip_top_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_wx_group_tip1.webp b/entry/src/main/resources/base/media/ic_wx_group_tip1.webp new file mode 100644 index 0000000..a253c30 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_group_tip1.webp differ diff --git a/entry/src/main/resources/base/media/ic_wx_group_tip2.png b/entry/src/main/resources/base/media/ic_wx_group_tip2.png new file mode 100644 index 0000000..84ce9c7 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_group_tip2.png differ diff --git a/entry/src/main/resources/base/media/ic_wx_group_tip3.webp b/entry/src/main/resources/base/media/ic_wx_group_tip3.webp new file mode 100644 index 0000000..7903606 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_group_tip3.webp differ diff --git a/entry/src/main/resources/base/media/ic_wx_group_tip4.webp b/entry/src/main/resources/base/media/ic_wx_group_tip4.webp new file mode 100644 index 0000000..5d5a5f3 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_group_tip4.webp differ diff --git a/entry/src/main/resources/base/media/ic_wx_group_tip5.webp b/entry/src/main/resources/base/media/ic_wx_group_tip5.webp new file mode 100644 index 0000000..7f2da14 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_group_tip5.webp differ diff --git a/entry/src/main/resources/base/media/ic_wx_group_tip_bg.webp b/entry/src/main/resources/base/media/ic_wx_group_tip_bg.webp new file mode 100644 index 0000000..087f78e Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_group_tip_bg.webp differ diff --git a/entry/src/main/resources/base/media/ic_wx_group_tip_indicator.webp b/entry/src/main/resources/base/media/ic_wx_group_tip_indicator.webp new file mode 100644 index 0000000..7eb1629 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_group_tip_indicator.webp differ diff --git a/entry/src/main/resources/base/media/ic_wx_login.webp b/entry/src/main/resources/base/media/ic_wx_login.webp new file mode 100644 index 0000000..94b2500 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_login.webp differ diff --git a/entry/src/main/resources/base/media/ic_wx_pay.webp b/entry/src/main/resources/base/media/ic_wx_pay.webp new file mode 100644 index 0000000..a6d1306 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_pay.webp differ diff --git a/entry/src/main/resources/base/media/ic_wx_pay3.webp b/entry/src/main/resources/base/media/ic_wx_pay3.webp new file mode 100644 index 0000000..3cab8ea Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_pay3.webp differ diff --git a/entry/src/main/resources/base/media/ic_wx_video_course.png b/entry/src/main/resources/base/media/ic_wx_video_course.png new file mode 100644 index 0000000..835f1cf Binary files /dev/null and b/entry/src/main/resources/base/media/ic_wx_video_course.png differ diff --git a/entry/src/main/resources/base/media/yq_0.webp b/entry/src/main/resources/base/media/yq_0.webp new file mode 100644 index 0000000..5706eab Binary files /dev/null and b/entry/src/main/resources/base/media/yq_0.webp differ diff --git a/entry/src/main/resources/base/media/yq_1.webp b/entry/src/main/resources/base/media/yq_1.webp new file mode 100644 index 0000000..038c05c Binary files /dev/null and b/entry/src/main/resources/base/media/yq_1.webp differ diff --git a/entry/src/main/resources/base/media/yq_10.webp b/entry/src/main/resources/base/media/yq_10.webp new file mode 100644 index 0000000..4c0081f Binary files /dev/null and b/entry/src/main/resources/base/media/yq_10.webp differ diff --git a/entry/src/main/resources/base/media/yq_11.webp b/entry/src/main/resources/base/media/yq_11.webp new file mode 100644 index 0000000..9d6a8f6 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_11.webp differ diff --git a/entry/src/main/resources/base/media/yq_12.webp b/entry/src/main/resources/base/media/yq_12.webp new file mode 100644 index 0000000..9ce047d Binary files /dev/null and b/entry/src/main/resources/base/media/yq_12.webp differ diff --git a/entry/src/main/resources/base/media/yq_13.webp b/entry/src/main/resources/base/media/yq_13.webp new file mode 100644 index 0000000..b7676aa Binary files /dev/null and b/entry/src/main/resources/base/media/yq_13.webp differ diff --git a/entry/src/main/resources/base/media/yq_14.webp b/entry/src/main/resources/base/media/yq_14.webp new file mode 100644 index 0000000..0579a61 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_14.webp differ diff --git a/entry/src/main/resources/base/media/yq_15.webp b/entry/src/main/resources/base/media/yq_15.webp new file mode 100644 index 0000000..0aec656 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_15.webp differ diff --git a/entry/src/main/resources/base/media/yq_16.webp b/entry/src/main/resources/base/media/yq_16.webp new file mode 100644 index 0000000..1a5b0c6 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_16.webp differ diff --git a/entry/src/main/resources/base/media/yq_17.webp b/entry/src/main/resources/base/media/yq_17.webp new file mode 100644 index 0000000..0627b15 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_17.webp differ diff --git a/entry/src/main/resources/base/media/yq_18.webp b/entry/src/main/resources/base/media/yq_18.webp new file mode 100644 index 0000000..a2355f8 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_18.webp differ diff --git a/entry/src/main/resources/base/media/yq_19.webp b/entry/src/main/resources/base/media/yq_19.webp new file mode 100644 index 0000000..899866c Binary files /dev/null and b/entry/src/main/resources/base/media/yq_19.webp differ diff --git a/entry/src/main/resources/base/media/yq_2.webp b/entry/src/main/resources/base/media/yq_2.webp new file mode 100644 index 0000000..bf825b3 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_2.webp differ diff --git a/entry/src/main/resources/base/media/yq_20.webp b/entry/src/main/resources/base/media/yq_20.webp new file mode 100644 index 0000000..6d9b279 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_20.webp differ diff --git a/entry/src/main/resources/base/media/yq_21.webp b/entry/src/main/resources/base/media/yq_21.webp new file mode 100644 index 0000000..ba02486 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_21.webp differ diff --git a/entry/src/main/resources/base/media/yq_22.webp b/entry/src/main/resources/base/media/yq_22.webp new file mode 100644 index 0000000..42013e5 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_22.webp differ diff --git a/entry/src/main/resources/base/media/yq_23.webp b/entry/src/main/resources/base/media/yq_23.webp new file mode 100644 index 0000000..0323b8f Binary files /dev/null and b/entry/src/main/resources/base/media/yq_23.webp differ diff --git a/entry/src/main/resources/base/media/yq_24.webp b/entry/src/main/resources/base/media/yq_24.webp new file mode 100644 index 0000000..ee2e42e Binary files /dev/null and b/entry/src/main/resources/base/media/yq_24.webp differ diff --git a/entry/src/main/resources/base/media/yq_25.webp b/entry/src/main/resources/base/media/yq_25.webp new file mode 100644 index 0000000..60078e9 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_25.webp differ diff --git a/entry/src/main/resources/base/media/yq_26.webp b/entry/src/main/resources/base/media/yq_26.webp new file mode 100644 index 0000000..4eb796e Binary files /dev/null and b/entry/src/main/resources/base/media/yq_26.webp differ diff --git a/entry/src/main/resources/base/media/yq_27.webp b/entry/src/main/resources/base/media/yq_27.webp new file mode 100644 index 0000000..2a15955 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_27.webp differ diff --git a/entry/src/main/resources/base/media/yq_28.webp b/entry/src/main/resources/base/media/yq_28.webp new file mode 100644 index 0000000..b97443e Binary files /dev/null and b/entry/src/main/resources/base/media/yq_28.webp differ diff --git a/entry/src/main/resources/base/media/yq_29.webp b/entry/src/main/resources/base/media/yq_29.webp new file mode 100644 index 0000000..b964ca7 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_29.webp differ diff --git a/entry/src/main/resources/base/media/yq_3.webp b/entry/src/main/resources/base/media/yq_3.webp new file mode 100644 index 0000000..54812f5 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_3.webp differ diff --git a/entry/src/main/resources/base/media/yq_30.webp b/entry/src/main/resources/base/media/yq_30.webp new file mode 100644 index 0000000..12667b8 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_30.webp differ diff --git a/entry/src/main/resources/base/media/yq_31.webp b/entry/src/main/resources/base/media/yq_31.webp new file mode 100644 index 0000000..8cae083 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_31.webp differ diff --git a/entry/src/main/resources/base/media/yq_32.webp b/entry/src/main/resources/base/media/yq_32.webp new file mode 100644 index 0000000..16c5a07 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_32.webp differ diff --git a/entry/src/main/resources/base/media/yq_33.webp b/entry/src/main/resources/base/media/yq_33.webp new file mode 100644 index 0000000..5545026 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_33.webp differ diff --git a/entry/src/main/resources/base/media/yq_34.webp b/entry/src/main/resources/base/media/yq_34.webp new file mode 100644 index 0000000..8be2d62 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_34.webp differ diff --git a/entry/src/main/resources/base/media/yq_35.webp b/entry/src/main/resources/base/media/yq_35.webp new file mode 100644 index 0000000..9aed01b Binary files /dev/null and b/entry/src/main/resources/base/media/yq_35.webp differ diff --git a/entry/src/main/resources/base/media/yq_36.webp b/entry/src/main/resources/base/media/yq_36.webp new file mode 100644 index 0000000..4684978 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_36.webp differ diff --git a/entry/src/main/resources/base/media/yq_37.webp b/entry/src/main/resources/base/media/yq_37.webp new file mode 100644 index 0000000..e02b439 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_37.webp differ diff --git a/entry/src/main/resources/base/media/yq_38.webp b/entry/src/main/resources/base/media/yq_38.webp new file mode 100644 index 0000000..00f83be Binary files /dev/null and b/entry/src/main/resources/base/media/yq_38.webp differ diff --git a/entry/src/main/resources/base/media/yq_39.webp b/entry/src/main/resources/base/media/yq_39.webp new file mode 100644 index 0000000..6ec781c Binary files /dev/null and b/entry/src/main/resources/base/media/yq_39.webp differ diff --git a/entry/src/main/resources/base/media/yq_4.webp b/entry/src/main/resources/base/media/yq_4.webp new file mode 100644 index 0000000..f7e8b8a Binary files /dev/null and b/entry/src/main/resources/base/media/yq_4.webp differ diff --git a/entry/src/main/resources/base/media/yq_40.webp b/entry/src/main/resources/base/media/yq_40.webp new file mode 100644 index 0000000..50305e1 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_40.webp differ diff --git a/entry/src/main/resources/base/media/yq_41.webp b/entry/src/main/resources/base/media/yq_41.webp new file mode 100644 index 0000000..27e37c6 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_41.webp differ diff --git a/entry/src/main/resources/base/media/yq_42.webp b/entry/src/main/resources/base/media/yq_42.webp new file mode 100644 index 0000000..3bf8090 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_42.webp differ diff --git a/entry/src/main/resources/base/media/yq_43.webp b/entry/src/main/resources/base/media/yq_43.webp new file mode 100644 index 0000000..d08cd33 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_43.webp differ diff --git a/entry/src/main/resources/base/media/yq_44.webp b/entry/src/main/resources/base/media/yq_44.webp new file mode 100644 index 0000000..d71af5e Binary files /dev/null and b/entry/src/main/resources/base/media/yq_44.webp differ diff --git a/entry/src/main/resources/base/media/yq_45.webp b/entry/src/main/resources/base/media/yq_45.webp new file mode 100644 index 0000000..109e861 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_45.webp differ diff --git a/entry/src/main/resources/base/media/yq_46.webp b/entry/src/main/resources/base/media/yq_46.webp new file mode 100644 index 0000000..b2cf737 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_46.webp differ diff --git a/entry/src/main/resources/base/media/yq_47.webp b/entry/src/main/resources/base/media/yq_47.webp new file mode 100644 index 0000000..cb17555 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_47.webp differ diff --git a/entry/src/main/resources/base/media/yq_48.webp b/entry/src/main/resources/base/media/yq_48.webp new file mode 100644 index 0000000..8f17c8a Binary files /dev/null and b/entry/src/main/resources/base/media/yq_48.webp differ diff --git a/entry/src/main/resources/base/media/yq_49.webp b/entry/src/main/resources/base/media/yq_49.webp new file mode 100644 index 0000000..9a811a4 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_49.webp differ diff --git a/entry/src/main/resources/base/media/yq_5.webp b/entry/src/main/resources/base/media/yq_5.webp new file mode 100644 index 0000000..0196d94 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_5.webp differ diff --git a/entry/src/main/resources/base/media/yq_50.webp b/entry/src/main/resources/base/media/yq_50.webp new file mode 100644 index 0000000..4d97ec3 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_50.webp differ diff --git a/entry/src/main/resources/base/media/yq_6.webp b/entry/src/main/resources/base/media/yq_6.webp new file mode 100644 index 0000000..416d0dc Binary files /dev/null and b/entry/src/main/resources/base/media/yq_6.webp differ diff --git a/entry/src/main/resources/base/media/yq_7.webp b/entry/src/main/resources/base/media/yq_7.webp new file mode 100644 index 0000000..604219b Binary files /dev/null and b/entry/src/main/resources/base/media/yq_7.webp differ diff --git a/entry/src/main/resources/base/media/yq_8.webp b/entry/src/main/resources/base/media/yq_8.webp new file mode 100644 index 0000000..1448928 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_8.webp differ diff --git a/entry/src/main/resources/base/media/yq_9.webp b/entry/src/main/resources/base/media/yq_9.webp new file mode 100644 index 0000000..89430d5 Binary files /dev/null and b/entry/src/main/resources/base/media/yq_9.webp differ diff --git a/entry/src/main/resources/base/profile/backup_config.json b/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000..78f40ae --- /dev/null +++ b/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000..41c749c --- /dev/null +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,34 @@ +{ + "src": [ + "pages/web/WebPage", + "pages/splash/SplashPage", + "pages/guide/GuidePage", + "pages/login/LoginPage", + "pages/login/qrcode/QrcodeLoginPage", + "pages/main/MainPage", + "pages/main/home/link/TakeMaterialPage", + "pages/main/home/course/CoursePage", + "pages/main/home/wx/WxVideoPage", + "pages/main/home/tools/AddWaterMarkerPage", + "pages/main/home/tools/MD5ResetPage", + "pages/main/home/tools/VideoReversePage", + "pages/main/home/tools/VideoMirrorPage", + "pages/main/home/tools/ClipVideoPage", + "pages/main/home/tools/RemoveAudioPage", + "pages/main/home/tools/AddAudioPage", + "pages/main/home/tools/TakeAudioPage", + "pages/main/home/material/MaterialDetailPage", + "pages/main/mine/user/UserSettingsPage", + "pages/main/mine/vip/VipPage", + "pages/main/mine/history/DownloadHistoryPage", + "pages/main/mine/diamond/DiamondPage", + "pages/main/mine/setting/SettingsPage", + "pages/main/mine/setting/feedback/FeedbackPage", + "pages/main/mine/setting/about/AboutPage", + "pages/main/mine/setting/account/BindAccountPage", + "pages/main/mine/setting/account/ManageAccountPage", + "pages/video/VideoPlayerPage", + "pages/audio/AudioPlayerPage", + "pages/photo/PhotoViewPage" + ] +} diff --git a/entry/src/main/resources/dark/element/color.json b/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000..ecf565d --- /dev/null +++ b/entry/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "window_background", + "value": "#F3F5F9" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/rawfile/effect/download_complete.pag b/entry/src/main/resources/rawfile/effect/download_complete.pag new file mode 100644 index 0000000..42b4991 Binary files /dev/null and b/entry/src/main/resources/rawfile/effect/download_complete.pag differ diff --git a/entry/src/main/resources/rawfile/effect/downloading.pag b/entry/src/main/resources/rawfile/effect/downloading.pag new file mode 100644 index 0000000..3f17501 Binary files /dev/null and b/entry/src/main/resources/rawfile/effect/downloading.pag differ diff --git a/entry/src/main/resources/rawfile/effect/loading.pag b/entry/src/main/resources/rawfile/effect/loading.pag new file mode 100644 index 0000000..9e6bb48 Binary files /dev/null and b/entry/src/main/resources/rawfile/effect/loading.pag differ diff --git a/entry/src/main/resources/rawfile/effect/processing.pag b/entry/src/main/resources/rawfile/effect/processing.pag new file mode 100644 index 0000000..9e6bb48 Binary files /dev/null and b/entry/src/main/resources/rawfile/effect/processing.pag differ diff --git a/entry/src/main/resources/rawfile/font/almmShuHeiTi.ttf b/entry/src/main/resources/rawfile/font/almmShuHeiTi.ttf new file mode 100644 index 0000000..854c844 Binary files /dev/null and b/entry/src/main/resources/rawfile/font/almmShuHeiTi.ttf differ diff --git a/entry/src/main/resources/rawfile/font/ddp500m.otf b/entry/src/main/resources/rawfile/font/ddp500m.otf new file mode 100644 index 0000000..d72d454 Binary files /dev/null and b/entry/src/main/resources/rawfile/font/ddp500m.otf differ diff --git a/entry/src/main/resources/rawfile/font/youSheBiaoTiHei.ttf b/entry/src/main/resources/rawfile/font/youSheBiaoTiHei.ttf new file mode 100644 index 0000000..3729151 Binary files /dev/null and b/entry/src/main/resources/rawfile/font/youSheBiaoTiHei.ttf differ diff --git a/entry/src/mock/mock-config.json5 b/entry/src/mock/mock-config.json5 new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/entry/src/mock/mock-config.json5 @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/Ability.test.ets b/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000..85c78f6 --- /dev/null +++ b/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/List.test.ets b/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000..794c7dc --- /dev/null +++ b/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/entry/src/ohosTest/module.json5 b/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000..1647b75 --- /dev/null +++ b/entry/src/ohosTest/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "phone", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/entry/src/test/List.test.ets b/entry/src/test/List.test.ets new file mode 100644 index 0000000..bb5b5c3 --- /dev/null +++ b/entry/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/entry/src/test/LocalUnit.test.ets b/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000..165fc16 --- /dev/null +++ b/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000..85e8d4b --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.1.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000..f3cb9f1 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/oh-package-lock.json5 b/oh-package-lock.json5 new file mode 100644 index 0000000..1ad9d6e --- /dev/null +++ b/oh-package-lock.json5 @@ -0,0 +1,197 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@alipay/blueshieldsdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har": "@alipay/blueshieldsdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har", + "@cashier_alipay/cashiersdk@^15.8.36": "@cashier_alipay/cashiersdk@15.8.36", + "@ohos/crypto-js@^2.0.4": "@ohos/crypto-js@2.0.4", + "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", + "@ohos/hypium@1.0.21": "@ohos/hypium@1.0.21", + "@ohos/imageknifepro@^1.0.12": "@ohos/imageknifepro@1.0.12", + "@ohos/mp4parser@^2.0.7": "@ohos/mp4parser@2.0.7", + "@pura/harmony-utils@1.3.5": "@pura/harmony-utils@1.3.5", + "@pura/picker_utils@^1.0.1": "@pura/picker_utils@1.0.1", + "@rv/image-preview@^2.1.2": "@rv/image-preview@2.1.2", + "@taobao-ohos/utdid_sdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.2.5.har": "@taobao-ohos/utdid_sdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.2.5.har", + "@tencent/libpag@^4.4.31": "@tencent/libpag@4.4.31", + "@tencent/wechat_open_sdk@^1.0.15": "@tencent/wechat_open_sdk@1.0.15", + "@types/libmp4parser_napi.so@oh_modules/.ohpm/@ohos+mp4parser@2.0.7/oh_modules/@ohos/mp4parser/src/main/cpp/types/libmp4parser_napi": "@types/libmp4parser_napi.so@oh_modules/.ohpm/@ohos+mp4parser@2.0.7/oh_modules/@ohos/mp4parser/src/main/cpp/types/libmp4parser_napi", + "class-transformer@^0.5.1": "class-transformer@0.5.1", + "libblueshield.so@oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield": "libblueshield.so@oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield", + "libimageknifepro.so@oh_modules/.ohpm/@ohos+imageknifepro@1.0.12/oh_modules/@ohos/imageknifepro/src/main/cpp/types/libentry": "libimageknifepro.so@oh_modules/.ohpm/@ohos+imageknifepro@1.0.12/oh_modules/@ohos/imageknifepro/src/main/cpp/types/libentry", + "libpag.so@oh_modules/.ohpm/@tencent+libpag@4.4.31/oh_modules/@tencent/libpag/src/main/cpp/types/libpag": "libpag.so@oh_modules/.ohpm/@tencent+libpag@4.4.31/oh_modules/@tencent/libpag/src/main/cpp/types/libpag", + "libutdid_native.so@oh_modules/.ohpm/@taobao-ohos+utdid_sdk@ecjyoomnzz6zaipo1koz9+avemnpw7+togfp5uwrtiu=/oh_modules/@taobao-ohos/utdid_sdk/src/main/cpp/types/libutdid_native": "libutdid_native.so@oh_modules/.ohpm/@taobao-ohos+utdid_sdk@ecjyoomnzz6zaipo1koz9+avemnpw7+togfp5uwrtiu=/oh_modules/@taobao-ohos/utdid_sdk/src/main/cpp/types/libutdid_native", + "pako@^2.1.0": "pako@2.1.0", + "reflect-metadata@^0.2.1": "reflect-metadata@0.2.1" + }, + "packages": { + "@alipay/blueshieldsdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har": { + "name": "@alipay/blueshieldsdk", + "version": "1.0.29", + "resolved": "oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har", + "registryType": "local", + "dependencies": { + "libblueshield.so": "file:./src/main/cpp/types/libblueshield" + } + }, + "@cashier_alipay/cashiersdk@15.8.36": { + "name": "@cashier_alipay/cashiersdk", + "version": "15.8.36", + "integrity": "sha512-1wB5xVU63etcqG+KnogirYLQBeb/5FJBlPt+eKK/OFlhdul/ABZbMjT1zraKrzxImsLPb4FmRFomSF3RMG6jDg==", + "resolved": "https://repo.harmonyos.com/ohpm/@cashier_alipay/cashiersdk/-/cashiersdk-15.8.36.har", + "registryType": "ohpm", + "dependencies": { + "@taobao-ohos/utdid_sdk": "file:./lib/utdid_sdk-1.2.5.har", + "@alipay/blueshieldsdk": "file:./lib/blueshieldsdk-1.0.29.har", + "pako": "^2.1.0" + } + }, + "@ohos/crypto-js@2.0.4": { + "name": "@ohos/crypto-js", + "version": "2.0.4", + "integrity": "sha512-589ur6oqU1UNibqefMly2cwEeEhkSoCAA3uc+oNUwRnYYtevn/kQnO+Coi36N+VJSeeg/uFzZk1K/wUMdovpOA==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/crypto-js/-/crypto-js-2.0.4.har", + "registryType": "ohpm" + }, + "@ohos/hamock@1.0.0": { + "name": "@ohos/hamock", + "version": "1.0.0", + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hamock/-/hamock-1.0.0.har", + "registryType": "ohpm" + }, + "@ohos/hypium@1.0.21": { + "name": "@ohos/hypium", + "version": "1.0.21", + "integrity": "sha512-iyKGMXxE+9PpCkqEwu0VykN/7hNpb+QOeIuHwkmZnxOpI+dFZt6yhPB7k89EgV1MiSK/ieV/hMjr5Z2mWwRfMQ==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.21.har", + "registryType": "ohpm" + }, + "@ohos/imageknifepro@1.0.12": { + "name": "@ohos/imageknifepro", + "version": "1.0.12", + "integrity": "sha512-OpEbZU6Csu7cCk1Rfv+OoI4irhXR2WjLOXc+CPkJGbqcRLyZ9m8VcKP2dl6UxPJvbculdt8jbxGd2+sSvqmqPQ==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/imageknifepro/-/imageknifepro-1.0.12.har", + "registryType": "ohpm", + "dependencies": { + "libimageknifepro.so": "file:./src/main/cpp/types/libentry" + } + }, + "@ohos/mp4parser@2.0.7": { + "name": "@ohos/mp4parser", + "version": "2.0.7", + "integrity": "sha512-3g4dd43j0JCFZdlTeqtUxsh7yQJDmxnKdMqHZLgqnWus9OAfG7fpJx/yjtFCtp2IXPjYt5YtB9RB6UbFr3+glA==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/mp4parser/-/mp4parser-2.0.7.har", + "registryType": "ohpm", + "dependencies": { + "@types/libmp4parser_napi.so": "file:./src/main/cpp/types/libmp4parser_napi" + } + }, + "@pura/harmony-utils@1.3.5": { + "name": "@pura/harmony-utils", + "version": "1.3.5", + "integrity": "sha512-3Cag8gzCT40j9Y1jt6yHeIdW1iYB9kMFRebnDb361fUsf0owazlNkZqSoNA2nkxeI1jyaQC1VVX6avcIwA+RwA==", + "resolved": "https://repo.harmonyos.com/ohpm/@pura/harmony-utils/-/harmony-utils-1.3.5.har", + "registryType": "ohpm" + }, + "@pura/picker_utils@1.0.1": { + "name": "@pura/picker_utils", + "version": "1.0.1", + "integrity": "sha512-m9M0CFNQxhfHoEZKMP19JX8d5KD5bQR40ZUXOCHXZc5k1qp4Um08z4aoPT3GbfwfWCPVf2zlnB8F1o2I4ZC35g==", + "resolved": "https://repo.harmonyos.com/ohpm/@pura/picker_utils/-/picker_utils-1.0.1.har", + "registryType": "ohpm" + }, + "@rv/image-preview@2.1.2": { + "name": "@rv/image-preview", + "version": "2.1.2", + "integrity": "sha512-QP6xNRQrRImVFHiUlvgFkY7hy0gEv2PGHe/ODJ9m1CCI3yBGh4RqmcoFcr8aqqaGohgwdJ6lu90VVM+QNdWb+A==", + "resolved": "https://repo.harmonyos.com/ohpm/@rv/image-preview/-/image-preview-2.1.2.har", + "registryType": "ohpm" + }, + "@taobao-ohos/utdid_sdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.2.5.har": { + "name": "@taobao-ohos/utdid_sdk", + "version": "1.2.5", + "resolved": "oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.2.5.har", + "registryType": "local", + "dependencies": { + "libutdid_native.so": "file:./src/main/cpp/types/libutdid_native", + "@ohos/crypto-js": "^2.0.4" + } + }, + "@tencent/libpag@4.4.31": { + "name": "@tencent/libpag", + "version": "4.4.31", + "integrity": "sha512-VhEMtoJx49KIx7pI3BMFWYE6Wlaescx9nO5EROcl92E8h4yLqsg08nU6dbN70gd8leDriA+gud6mt+SuDlAxNA==", + "resolved": "https://repo.harmonyos.com/ohpm/@tencent/libpag/-/libpag-4.4.31.har", + "registryType": "ohpm", + "dependencies": { + "libpag.so": "file:./src/main/cpp/types/libpag" + } + }, + "@tencent/wechat_open_sdk@1.0.15": { + "name": "@tencent/wechat_open_sdk", + "version": "1.0.15", + "integrity": "sha512-O9W8Gj16YBu0fl3d78r4eQQdnljczJPcIDws9i+ErVg94y8twHBkLWdyysiWfhGA7+ltB3mu8EZsk/EAr8LZSw==", + "resolved": "https://repo.harmonyos.com/ohpm/@tencent/wechat_open_sdk/-/wechat_open_sdk-1.0.15.har", + "registryType": "ohpm" + }, + "@types/libmp4parser_napi.so@oh_modules/.ohpm/@ohos+mp4parser@2.0.7/oh_modules/@ohos/mp4parser/src/main/cpp/types/libmp4parser_napi": { + "name": "libmp4parser_napi.so", + "version": "0.0.0", + "resolved": "oh_modules/.ohpm/@ohos+mp4parser@2.0.7/oh_modules/@ohos/mp4parser/src/main/cpp/types/libmp4parser_napi", + "registryType": "local" + }, + "class-transformer@0.5.1": { + "name": "class-transformer", + "version": "0.5.1", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "resolved": "https://repo.harmonyos.com/ohpm/class-transformer/-/class-transformer-0.5.1.tgz", + "shasum": "24147d5dffd2a6cea930a3250a677addf96ab336", + "registryType": "ohpm" + }, + "libblueshield.so@oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield": { + "name": "libblueshield.so", + "version": "1.0.8", + "resolved": "oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield", + "registryType": "local" + }, + "libimageknifepro.so@oh_modules/.ohpm/@ohos+imageknifepro@1.0.12/oh_modules/@ohos/imageknifepro/src/main/cpp/types/libentry": { + "name": "libimageknifepro.so", + "version": "1.0.0", + "resolved": "oh_modules/.ohpm/@ohos+imageknifepro@1.0.12/oh_modules/@ohos/imageknifepro/src/main/cpp/types/libentry", + "registryType": "local" + }, + "libpag.so@oh_modules/.ohpm/@tencent+libpag@4.4.31/oh_modules/@tencent/libpag/src/main/cpp/types/libpag": { + "name": "libpag.so", + "version": "1.0.0", + "resolved": "oh_modules/.ohpm/@tencent+libpag@4.4.31/oh_modules/@tencent/libpag/src/main/cpp/types/libpag", + "registryType": "local" + }, + "libutdid_native.so@oh_modules/.ohpm/@taobao-ohos+utdid_sdk@ecjyoomnzz6zaipo1koz9+avemnpw7+togfp5uwrtiu=/oh_modules/@taobao-ohos/utdid_sdk/src/main/cpp/types/libutdid_native": { + "name": "libutdid_native.so", + "version": "0.0.1", + "resolved": "oh_modules/.ohpm/@taobao-ohos+utdid_sdk@ecjyoomnzz6zaipo1koz9+avemnpw7+togfp5uwrtiu=/oh_modules/@taobao-ohos/utdid_sdk/src/main/cpp/types/libutdid_native", + "registryType": "local" + }, + "pako@2.1.0": { + "name": "pako", + "version": "2.1.0", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "resolved": "https://repo.harmonyos.com/ohpm/pako/-/pako-2.1.0.tgz", + "shasum": "266cc37f98c7d883545d11335c00fbd4062c9a86", + "registryType": "ohpm" + }, + "reflect-metadata@0.2.1": { + "name": "reflect-metadata", + "version": "0.2.1", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==", + "resolved": "https://repo.harmonyos.com/ohpm/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "shasum": "8d5513c0f5ef2b4b9c3865287f3c0940c1f67f74", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000..c3ecd3a --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,21 @@ +{ + "modelVersion": "5.1.0", + "description": "Please describe the basic information.", + "dependencies": { + "class-transformer": "^0.5.1", + "reflect-metadata": "^0.2.1", + "@pura/harmony-utils": "1.3.5", + "@tencent/wechat_open_sdk": "^1.0.15", + "@cashier_alipay/cashiersdk": "^15.8.36", + "@pura/picker_utils": "^1.0.1", + "@tencent/libpag": "^4.4.31", + "@ohos/mp4parser": "^2.0.7", + "@ohos/imageknifepro": "^1.0.12", + "@rv/image-preview": "^2.1.2" + }, + "devDependencies": { + "@ohos/hypium": "1.0.21", + "@ohos/hamock": "1.0.0" + }, + "dynamicDependencies": {} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4cfd404 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "kcVideo", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "reflect-metadata": "^0.2.2" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2019462 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "reflect-metadata": "^0.2.2" + } +}