NewIndex.ets 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. import { AppUtil, ArrayUtil, DisplayUtil, GlobalContext,
  2. JSONUtil,
  3. PickerUtil,
  4. PreferencesUtil, StrUtil,
  5. ToastUtil } from '@pura/harmony-utils';
  6. import TitleBar from '../view/TitleBar';
  7. import { router } from '@kit.ArkUI';
  8. import mainViewModel, { MainViewModel } from '../viewmodel/MainViewModel';
  9. import ItemData from '../viewmodel/ItemData';
  10. import { CommonConstants } from '../common/constants/CommonConstants';
  11. import { ToolView } from '../view/ToolView';
  12. import { VideoItem } from '../viewmodel/VideoItem';
  13. import ScreenUtil from '../common/util/ScreenUtil';
  14. import Logger from '../common/util/Logger';
  15. import { Utility } from '../common/util/Utility';
  16. import { photoAccessHelper } from '@kit.MediaLibraryKit';
  17. import { BusinessError, emitter } from '@kit.BasicServicesKit';
  18. import { common } from '@kit.AbilityKit';
  19. import { AdSlot, AdSlotBuilder,
  20. CSJAdCreator,
  21. CSJAdSdk, CSJNativeExpressAd, NativeExpressAdListener } from '@csj/openadsdk';
  22. import { PrintBiddingTokenUtils } from '../common/util/PrintBiddintTokenUtils';
  23. import { ArrayList } from '@kit.ArkTS';
  24. import { CSJUtil } from '../common/util/CSJUtil';
  25. import { LocalMusic } from '../view/LocalMusic';
  26. import { StreamContent } from '../view/StreamContent';
  27. import { AvSessionController } from '../controller/AvSessionController';
  28. import { ImageKnife } from '@ohos/imageknife';
  29. import { BreakpointSystem, BreakpointTypeEnum } from '../common/util/BreakpointSystem';
  30. import { bundleManager } from '@kit.AbilityKit';
  31. import { hilog } from '@kit.PerformanceAnalysisKit';
  32. const TAG = 'NewIndex'; // 日志标签
  33. /**
  34. * 首页主组件,包含顶部标题栏、主内容区(本地音乐/网络内容)、侧边抽屉菜单等。
  35. * 支持多端适配、广告集成、导航、生命周期管理等功能。
  36. */
  37. @Preview
  38. @Entry
  39. @Component
  40. struct NewIndex{
  41. /** 列表滚动器,用于抽屉菜单列表滚动 */
  42. private scroller: Scroller = new Scroller();
  43. /** 应用包名 */
  44. @State bundleName:string = ''
  45. /** 是否显示侧边抽屉菜单 */
  46. @Provide isShowDrawer:boolean = false;
  47. /** 抽屉菜单 X 轴偏移量,用于手势滑动动画 */
  48. @Provide offsetX: number = 0;
  49. /** 本地视频列表,页面参数传入 */
  50. @Provide videoLocalList: Array<VideoItem> = []
  51. /** Y 轴偏移量(预留) */
  52. @State offsetY: number = 0;
  53. /** 主内容类型(0:本地音乐,1:网络内容) */
  54. @State mType:number = 0
  55. /** 模式类型(如歌手、专辑等) */
  56. @Provide modeType:number = 0
  57. /** 是否为零状态(预留) */
  58. @Provide('isZero') isZero:boolean = false;
  59. /** 是否显示赞助入口 */
  60. @State isShowSponsorship:boolean = false
  61. /** 页面上下文 */
  62. context = getContext(this);
  63. /** 音乐是否为零状态(预留) */
  64. @Provide('isMusicZero') isMusicZero:boolean = false;
  65. /** 本地音乐列表,持久化存储 */
  66. @StorageLink('musicLocalList') musicLocalList: Array<VideoItem> = []
  67. /** 标题栏配置模型 */
  68. @State titleBarModel: TitleBar.Model = new TitleBar.Model()
  69. .setTitleTextStyle(FontStyle.Normal)
  70. .setTitleBarStyle(TitleBar.BarStyle.TRANSPARENT)
  71. .setLeftIcon($r('app.media.menu'))
  72. .setLeftIconWidth(28)
  73. .setLeftIconHeight(28)
  74. .setTitleBarMinHeight(64)
  75. .setTitleName('首页')
  76. // .setRightIcon($r('app.media.add'))
  77. .setTitleFontSize(19)
  78. .setTitleFontColor(Color.White)
  79. .setTitleBarBackground($r('app.color.title_bar_bg'))
  80. .setLeftTitleBackground($r('app.color.title_bar_bg'))
  81. .setOnLeftClickListener(() => {
  82. // 打开菜单栏动画
  83. animateTo({ duration: 555 }, () => {
  84. this.isShowDrawer = true
  85. this.offsetX = 0
  86. })
  87. })
  88. .setRightTitleBackground($r('app.color.title_bar_bg'))
  89. // .setOnRightClickListener(()=>{
  90. // // this.goSelectVideo()
  91. // this.showSheelDialog()
  92. // })
  93. /** 应用版本号 */
  94. @State versionName:string =''
  95. /** 音频根目录路径 */
  96. @Provide rootPath:string = ''
  97. /** 当前路径 */
  98. @Provide currentPath:string = ''
  99. /** 是否为历史记录页面 */
  100. @Provide isHistory:boolean = false
  101. /** 是否可以返回上一级 */
  102. @Provide isCanBack:boolean= false
  103. /**
  104. * 一多界面适配断点系统
  105. */
  106. private breakpointSystem: BreakpointSystem = new BreakpointSystem();
  107. /** 当前断点类型(如大屏/小屏) */
  108. @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD;
  109. /** 记录上一次点击返回键的时间戳,用于双击退出 */
  110. private backTime:number=0;
  111. /**
  112. * 返回键处理逻辑:
  113. * - 如果不是根目录或有历史记录,发送广播通知更新列表
  114. * - 如果是根目录,双击返回键退出应用,否则提示
  115. */
  116. onBackPress(): boolean | void {
  117. if(this.currentPath !== this.rootPath||this.isHistory||(this.modeType!==0&&this.isCanBack)){
  118. const eventData: emitter.EventData = {};
  119. emitter.emit({ eventId: 888 }, eventData); // 发送音频广播通知更新doSwipBack
  120. }else{
  121. let nowtime = Date.now();
  122. if(nowtime - this.backTime < 1000){
  123. const mContext = getContext(this) as common.UIAbilityContext
  124. mContext.terminateSelf(); // 关闭应用
  125. AvSessionController.getInstance(true).unregisterSessionListener()
  126. }else {
  127. if(this.isShowDrawer){
  128. // 如果抽屉菜单打开,先关闭抽屉
  129. animateTo({ duration: 555 }, () => {
  130. this.isShowDrawer = false
  131. })
  132. }else{
  133. this.backTime=nowtime;
  134. ToastUtil.showToast("再按一次将退出当前应用")
  135. }
  136. }
  137. }
  138. return true;
  139. }
  140. /**
  141. * 页面显示生命周期钩子
  142. * - 注册断点系统
  143. * - 获取页面参数
  144. * - 设置状态栏样式
  145. * - 初始化图片缓存
  146. * - 判断是否显示赞助入口
  147. */
  148. async aboutToAppear() {
  149. this.breakpointSystem.register();
  150. let params = router.getParams() as Record<string, Object>;
  151. this.videoLocalList = params.videoList as VideoItem[];
  152. Utility.setStatusBarLight()
  153. // ScreenUtil 设置屏幕尺寸信息
  154. ScreenUtil.setScreenSize();
  155. this.bundleName = await AppUtil.getBundleName()
  156. this.versionName = await AppUtil.getVersionName();
  157. // 初始化图片缓存
  158. await ImageKnife.getInstance().initFileCache(this.context, 256, 256 * 1024 * 1024)
  159. // 判断是否显示赞助入口
  160. this.isShowSponsorship = Utility.isOpenTime()
  161. }
  162. /**
  163. * 页面消失生命周期钩子
  164. * 注销断点系统
  165. */
  166. aboutToDisappear() {
  167. console.info('NewIndex aboutToDisappear');
  168. this.breakpointSystem.unregister();
  169. }
  170. /**
  171. * 构建主页面结构,包括顶部横线、主内容区和抽屉菜单
  172. */
  173. build() {
  174. Stack({ alignContent: Alignment.TopStart }) {
  175. // 顶部横线
  176. Column(){
  177. Line()
  178. .width('100%')
  179. .height(1)
  180. }
  181. .backgroundColor($r('app.color.title_bar_bg'))
  182. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
  183. // 主内容区
  184. Column() {
  185. Stack(){
  186. // 本地音乐内容区
  187. LocalMusic()
  188. .visibility(this.mType===0?Visibility.Visible:Visibility.None)
  189. // 网络内容区
  190. StreamContent()
  191. .visibility(this.mType===1?Visibility.Visible:Visibility.None)
  192. }
  193. }
  194. .width('100%')
  195. .height('100%')
  196. .justifyContent(FlexAlign.Start)
  197. // 侧边抽屉菜单
  198. if(this.isShowDrawer){
  199. Column() {
  200. Column() {
  201. // 抽屉顶部背景图
  202. Image($r('app.media.bg_music'))
  203. .width('100%')
  204. .height(180)
  205. // 动态生成抽屉菜单内容
  206. this.getDrawerView()
  207. }
  208. .width(this.currentBreakpoint !== BreakpointTypeEnum.SM?'40%':'80%')
  209. .height('100%')
  210. .backgroundColor(Color.White)
  211. .onClick((event:ClickEvent)=>{
  212. // 阻止事件冒泡
  213. })
  214. }
  215. .width('100%')
  216. .height('100%')
  217. .backgroundColor($r('app.color.ban_touming'))
  218. .alignItems(HorizontalAlign.Start)
  219. .onClick((event:ClickEvent)=>{
  220. // 点击抽屉外部关闭抽屉
  221. animateTo({ duration: 555 }, () => {
  222. this.isShowDrawer = false
  223. })
  224. })
  225. .translate({ x: this.offsetX, y: 0, z: 0 })
  226. .transition({ type: TransitionType.Insert, translate: { x: -DisplayUtil.getWidth(), y: 0 } })
  227. .transition({ type: TransitionType.Delete, translate: { x: -DisplayUtil.getWidth(), y: 0 } })
  228. .gesture(
  229. PanGesture()
  230. .onActionUpdate((event:GestureEvent)=>{
  231. // 手势滑动更新抽屉偏移
  232. if(event.offsetX<0){
  233. this.offsetX = event.offsetX;
  234. }
  235. })
  236. .onActionEnd(() => {
  237. // 手势结束判断是否关闭抽屉
  238. if(this.offsetX<-DisplayUtil.getWidth()/9){
  239. animateTo({ duration: 555 }, () => {
  240. this.isShowDrawer = false
  241. })
  242. }else{
  243. animateTo({ duration: 555 }, () => {
  244. this.isShowDrawer = true
  245. this.offsetX = 0
  246. })
  247. }
  248. })
  249. )
  250. }
  251. }
  252. }
  253. /**
  254. * 动态生成抽屉菜单内容
  255. * 根据 isShowSponsorship 展示不同菜单项
  256. */
  257. @Builder
  258. getDrawerView(){
  259. List({space: 0, scroller: this.scroller}){
  260. ForEach(this.isShowSponsorship?mainViewModel.getDrawerData2():mainViewModel.getDrawerData(),(item: ItemData)=>{
  261. ListItem(){
  262. Row() {
  263. // 菜单图标
  264. Image(item.img)
  265. .height(22)
  266. .alignSelf(ItemAlign.Center)
  267. .margin({ left: 2 })
  268. // 菜单标题
  269. Text(item.title)
  270. .margin({ left: 10, right: 20 })
  271. .fontSize(15)
  272. .fontColor(Color.Gray)
  273. .fontWeight(480)
  274. Blank()
  275. // 右侧箭头
  276. Image($r('app.media.arrow_right'))
  277. .width(22)
  278. .height(22)
  279. .margin({ left: 20, right: 0 })
  280. .align(Alignment.Center)
  281. }
  282. .width('100%')
  283. .height(55)
  284. .onClick(async () => {
  285. // 根据 item.id 跳转或切换功能
  286. switch (item.id) {
  287. case MainViewModel.MENU_MUSIC:
  288. this.mType = 0
  289. animateTo({ duration: 555 }, () => {
  290. this.isShowDrawer = false
  291. })
  292. break
  293. case MainViewModel.MENU_FILE_SCAN:
  294. router.pushUrl({
  295. url: 'pages/ScanFilePage'
  296. });
  297. break
  298. case MainViewModel.MENU_HOME:
  299. this.mType = 0
  300. this.modeType = 0
  301. animateTo({ duration: 555 }, () => {
  302. this.isShowDrawer = false
  303. })
  304. break
  305. case MainViewModel.MENU_MIEDIA_KU:
  306. this.modeType = 1
  307. animateTo({ duration: 555 }, () => {
  308. this.isShowDrawer = false
  309. })
  310. break
  311. case MainViewModel.MENU_MIEDIA_ARTIST:
  312. this.modeType = 2
  313. animateTo({ duration: 555 }, () => {
  314. this.isShowDrawer = false
  315. })
  316. break
  317. case MainViewModel.MENU_MIEDIA_ALBUM:
  318. this.modeType = 3
  319. animateTo({ duration: 555 }, () => {
  320. this.isShowDrawer = false
  321. })
  322. break
  323. case MainViewModel.MENU_SETTING:
  324. router.pushUrl({
  325. url: 'pages/SettingPage'
  326. }, router.RouterMode.Single);
  327. break
  328. case MainViewModel.MENU_VIP:
  329. router.pushUrl({
  330. url: 'pages/VipPage'
  331. }, router.RouterMode.Single);
  332. break
  333. case MainViewModel.MENU_NET_CONNECT:
  334. this.mType = 1
  335. animateTo({ duration: 555 }, () => {
  336. this.isShowDrawer = false
  337. })
  338. break
  339. case MainViewModel.MENU_SIMI:
  340. router.pushUrl({
  341. url: 'pages/VerifyPage'
  342. });
  343. break
  344. case MainViewModel.MENU_LIKE:
  345. router.pushUrl({
  346. url: 'pages/LikeVideoPage'
  347. });
  348. break
  349. case MainViewModel.MENU_DUTY:
  350. router.pushUrl({
  351. url: 'pages/WebIndex',
  352. params: { titleName: '用户协议', webUrl: CommonConstants.NEW_DUTY }
  353. });
  354. break
  355. case MainViewModel.MENU_HAOPING:
  356. Utility.gotoMarket(getContext(this) as common.UIAbilityContext,this.bundleName)
  357. break
  358. case MainViewModel.MENU_TEST_SPEED:
  359. router.pushUrl({
  360. url: 'pages/SpeedIndex'
  361. });
  362. break
  363. case MainViewModel.MENU_OPTIMIZE:
  364. router.pushUrl({
  365. url: 'pages/AddSpeedPage'
  366. });
  367. break
  368. case MainViewModel.MENU_ABOUT:
  369. router.pushUrl({
  370. url: 'pages/AboutPage'
  371. }, router.RouterMode.Single);
  372. break
  373. case MainViewModel.MENU_UPDATE:
  374. AlertDialog.show({title:'版本更新',message:'当前版本为最新版本:'+this.versionName,
  375. autoCancel:true, alignment:DialogAlignment.Center,
  376. offset:{dx:0,dy:-20},//在Y轴方向上的编译量
  377. confirm:{
  378. value:'确定',fontColor:Color.White,backgroundColor:$r('app.color.title_bar_bg'),
  379. action:()=>{
  380. }
  381. }
  382. })
  383. break
  384. case MainViewModel.MENU_YSZC:
  385. router.pushUrl({
  386. url: 'pages/WebIndex',
  387. params: { titleName: '隐私政策', webUrl: CommonConstants.NEW_YS_HW }
  388. });
  389. break
  390. }
  391. })
  392. }
  393. .width('90%')
  394. .height(55)
  395. })
  396. }
  397. .width('100%')
  398. .alignListItem(ListItemAlign.Center)
  399. .backgroundColor($r('app.color.white'))
  400. .layoutWeight(1)
  401. .divider({strokeWidth: 1, color: '#ffe9f0f0'})
  402. .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果
  403. }
  404. /**
  405. * ================== 穿山甲广告相关 ==================
  406. */
  407. /** Banner广告对象 */
  408. private declare bannerAd: CSJNativeExpressAd;
  409. /** 广告加载状态 */
  410. @State private status: string = "未加载"
  411. /** 是否展示广告 */
  412. @State private isShowAd: boolean = false
  413. /** 广告位配置信息 */
  414. private declare mAdSlot: AdSlot;
  415. /** 服务端bidding广告内容(可选) */
  416. private mBiddingAdm = ''
  417. /** 广告加载监听器 */
  418. private expressLoadAdListener: NativeExpressAdListener = {
  419. /**
  420. * 广告加载失败回调
  421. */
  422. onError: (code: number, message: string) => {
  423. console.error("加载广告失败,code=" + code + ",message=" + message);
  424. this.status = "加载广告失败,code=" + code + ",message=" + message;
  425. },
  426. /**
  427. * 广告加载成功回调
  428. * @param ads 返回的广告列表
  429. */
  430. onNativeExpressAdLoad: (ads: ArrayList<CSJNativeExpressAd>) => {
  431. console.log("BannerExpressAdPage==onNativeExpressAdLoad......success");
  432. if (ads && ads.length > 0) {
  433. ads.forEach((ad: CSJNativeExpressAd, idx: number) => {
  434. // 设置广告交互监听
  435. ad.setExpressInteractionListener({
  436. /** 广告点击回调 */
  437. onAdClicked: (type: number) => {
  438. console.log("BannerExpressAdPage==onAdClicked......");
  439. },
  440. /** 广告展示回调 */
  441. onAdShow: (type: number) => {
  442. console.log("BannerExpressAdPage==onAdShow......");
  443. },
  444. /** 模板渲染失败回调 */
  445. onRenderFail: (code: number, msg: string) => {
  446. console.log("BannerExpressAdPage==onRenderFail...code=" + code + ",msg=" + msg);
  447. },
  448. /** 模板渲染成功回调 */
  449. onRenderSuccess: (width: number, height: number) => {
  450. console.log("BannerExpressAdPage==onRenderSuccess......width=" + width + ",height=" + height);
  451. this.bannerAd = ad;
  452. this.status = "广告加载完成待展示";
  453. this.showBannerAd()
  454. }
  455. })
  456. // 设置广告轮播时间
  457. ad.setSlideIntervalTime(30 * 1000);//设置轮播时间长
  458. this.setDislikeCallback(ad); //设置dislike
  459. ad.render(this.getUIContext()) // 开始渲染广告
  460. this.status = "广告加载中...";
  461. });
  462. }
  463. }
  464. }
  465. /**
  466. * 设置广告 dislike 回调
  467. * @param ad 广告对象
  468. */
  469. private setDislikeCallback(ad: CSJNativeExpressAd) {
  470. ad.setDislikeCallback({
  471. /** dislike 弹窗显示 */
  472. onShow: () => {
  473. console.log("BannerExpressAdPage==dislike......show");
  474. },
  475. /** 选择 dislike 选项 */
  476. onSelected: (position: number, value: string, enforceRemove: boolean) => {
  477. this.isShowAd = false;
  478. console.log("BannerExpressAdPage==dislike......onSelected:position=" + position + ",value:" + value + ",enforce=" + enforceRemove);
  479. },
  480. /** 点击取消 dislike */
  481. onCancel: () => {
  482. console.log("BannerExpressAdPage==dislike......onCancel");
  483. }
  484. })
  485. }
  486. /**
  487. * 加载Banner广告
  488. * @param rit 广告位ID
  489. */
  490. loadBannerAd(rit: string) {
  491. // 仅在满足条件时加载广告
  492. if(!Utility.isNoble()){
  493. return
  494. }
  495. if(!Utility.isPassInstallTime(2)){
  496. return
  497. }
  498. this.isShowAd = false;
  499. let adCreator:CSJAdCreator = CSJAdSdk.getAdCreator()
  500. this.mAdSlot = new AdSlotBuilder()
  501. .setCodeId(rit)
  502. .setAcceptSize(CSJUtil.BANNER_WIGTH, CSJUtil.BANNER_HEIGHT)
  503. .setAdCount(1)
  504. .setBidAdm(this.mBiddingAdm)
  505. .build()
  506. PrintBiddingTokenUtils.printBiddingToken(this.mAdSlot, adCreator);
  507. adCreator.loadBannerAd(this.mAdSlot, this.expressLoadAdListener)
  508. this.status = "广告加载中..."
  509. }
  510. /**
  511. * 展示Banner广告
  512. */
  513. showBannerAd() {
  514. if (!this.bannerAd) {
  515. return
  516. }
  517. this.isShowAd = true
  518. this.status = "已展示"
  519. }
  520. /**
  521. * ================== 穿山甲广告相关结束 ==================
  522. */
  523. // geIDD() {
  524. // // 获取应用签名等信息示例代码
  525. // let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION |
  526. // bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO;
  527. // try {
  528. // bundleManager.getBundleInfoForSelf(bundleFlags).then((data) => {
  529. // hilog.info(0x0000, 'testTag11', 'getBundleInfoForSelf successfully. Data: %{public}s', JSON.stringify(data));
  530. // }).catch((err: BusinessError) => {
  531. // hilog.error(0x0000, 'testTag11', 'getBundleInfoForSelf failed. Cause: %{public}s', err.message);
  532. // });
  533. // } catch (err) {
  534. // let message = (err as BusinessError).message;
  535. // hilog.error(0x0000, 'testTag33', 'getBundleInfoForSelf failed: %{public}s', message);
  536. // }
  537. // }
  538. }