ChartsCount.ets 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. import { common, ConfigurationConstant } from '@kit.AbilityKit';
  2. import { CommonConstants } from '../common/constants/CommonConstants';
  3. import { VideoItem } from '../viewmodel/VideoItem';
  4. import { LazyDataSource } from '../common/util/LazyDataSource';
  5. import { LogUtil, PreferencesUtil, StrUtil, ToastUtil } from '@pura/harmony-utils';
  6. import fs from '@ohos.file.fs';
  7. import { changeMusicCover, findLocalCoverImage, getApiLyric, repairAudioMetadata,
  8. searchCover,
  9. syncLyricToDB} from '../common/util/MusicTagUtils';
  10. import { util } from '@kit.ArkTS';
  11. import { AudioQuality, FFMpegTags } from '../common/util/Utility';
  12. import MediaTable from '../common/util/MediaTable';
  13. import { McPieChart, Options } from '@mcui/mccharts'
  14. import { ComponentContent } from '@kit.ArkUI';
  15. // 批量编辑标签
  16. @Component
  17. export struct ChartsCount {
  18. private listScroller: ListScroller = new ListScroller()
  19. @State isLyric:boolean = true
  20. @State isCover:boolean = true
  21. @Link isShowDrawer: boolean;
  22. @Link offsetX: number;
  23. @State dataSource: LazyDataSource<VideoItem> = new LazyDataSource([])
  24. @State selectedFiles: Array<VideoItem> = []
  25. @StorageProp('topRectHeight') topRectHeight: number = 0;
  26. @State mediaKuCount: number = 0;
  27. @State albumCount: number = 0;
  28. @State artistCount: number = 0;
  29. @State lqCount: number = 0;
  30. @State sqCount: number = 0;
  31. @State hqCount: number = 0;
  32. @State losslessCount: number = 0;
  33. @State hrCount: number = 0;
  34. @State isDarkMode: boolean = false
  35. @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number =
  36. ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
  37. @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
  38. context = this.getUIContext().getHostContext() as common.UIAbilityContext
  39. private contentNode?: ComponentContent<Object> = undefined;
  40. @State isRefreshing: boolean = false;
  41. @State ratio: number = 1;
  42. @State maxRefreshingHeight: number = 100.0;
  43. private table: MediaTable = new MediaTable(this.context)
  44. @State defOption: Options = new Options({
  45. title: {
  46. show: true,
  47. text: '曲库统计',
  48. left: 40,
  49. top: 20
  50. },
  51. series:[
  52. {
  53. data:[
  54. {value:this.mediaKuCount, name:'媒体库'},
  55. {value:this.artistCount, name:'艺术家'},
  56. {value:this.albumCount, name:'专辑'},
  57. ]
  58. }
  59. ]
  60. })
  61. @State HrOption: Options = new Options({
  62. title: {
  63. show: true,
  64. text: '音质统计',
  65. left: 40,
  66. top: 20
  67. },
  68. series:[
  69. {
  70. data:[
  71. {value:this.lqCount, name:'LQ'},
  72. {value:this.sqCount, name:'SQ'},
  73. {value:this.hqCount, name:'HQ'},
  74. {value:this.losslessCount, name:'Lossless'},
  75. {value:this.hrCount, name:'Hi-Res'},
  76. ]
  77. }
  78. ]
  79. })
  80. onColorModeChange() {
  81. this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
  82. }
  83. async aboutToAppear() {
  84. let uiContext = this.getUIContext();
  85. this.contentNode = new ComponentContent(uiContext, wrapBuilder(customRefreshingContent));
  86. this.centerChart()
  87. }
  88. async centerChart(){
  89. this.mediaKuCount = PreferencesUtil.getNumberSync('mediaKuCount', 0)
  90. this.artistCount = PreferencesUtil.getNumberSync('artistCount', 0)
  91. this.albumCount = PreferencesUtil.getNumberSync('albumCount', 0)
  92. setTimeout(() => {
  93. // 使用Option实例对象的setVal方法来实现,修改什么属性就传什么
  94. this.defOption.setVal({
  95. animation:true,
  96. series: [
  97. {
  98. data:[
  99. {value:this.mediaKuCount, name:'媒体库'},
  100. {value:this.artistCount, name:'艺术家'},
  101. {value:this.albumCount, name:'专辑'},
  102. ]
  103. }
  104. ]
  105. })
  106. }, 1000)
  107. await new Promise<void>((resolve, reject) => {
  108. this.table.getRdbStore(this.context, (err:Error) => {
  109. err ? reject(err) : resolve();
  110. });
  111. });
  112. this.table.queryByPlayCountDesc(1000, async (result: VideoItem[]) => {
  113. this.selectedFiles = result
  114. // 确保 selectedFiles 有默认值后再初始化 dataSource
  115. if (this.selectedFiles && this.selectedFiles.length > 0) {
  116. // this.dataSource = new LazyDataSource(this.selectedFiles)
  117. this.dataSource.pushArrayData(this.selectedFiles)
  118. } else {
  119. this.dataSource = new LazyDataSource([])
  120. }
  121. })
  122. this.table.countSongsByQualityOptimized(async (result:Record<AudioQuality, number>) => {
  123. this.lqCount = result.LQ
  124. this.hqCount = result.HQ
  125. this.hrCount = result.HR
  126. this.sqCount = result.SQ
  127. setTimeout(() => {
  128. // 使用Option实例对象的setVal方法来实现,修改什么属性就传什么
  129. this.HrOption.setVal({
  130. animation:true,
  131. series:[
  132. {
  133. data:[
  134. {value:this.lqCount, name:'LQ'},
  135. {value:this.hqCount, name:'HQ'},
  136. {value:this.sqCount, name:'SQ'},
  137. {value:this.hrCount, name:'Hi-Res'},
  138. ]
  139. }
  140. ]
  141. })
  142. }, 1000)
  143. })
  144. }
  145. build() {
  146. Column(){
  147. this.topTitleBar()
  148. this.listView()
  149. }
  150. .height('100%')
  151. .width('100%')
  152. .backgroundColor($r('app.color.index_background'))
  153. }
  154. @Builder
  155. centerCharts() {
  156. Row() {
  157. Swiper() {
  158. McPieChart({
  159. options: this.defOption
  160. })
  161. McPieChart({
  162. options: this.HrOption
  163. })
  164. }
  165. }
  166. .borderRadius(14)
  167. .backgroundColor($r('app.color.start_window_background'))
  168. .height('35%')
  169. .margin({bottom:10})
  170. }
  171. @Builder
  172. listView() {
  173. Column(){
  174. Refresh({ refreshing: $$this.isRefreshing, refreshingContent: this.contentNode }) {
  175. List({ scroller: this.listScroller }) {
  176. ListItemGroup({ header: this.centerCharts() }) {
  177. ListItem() {
  178. Column() {
  179. Text('听歌次数排行榜')
  180. .fontSize(15)
  181. .maxLines(1)
  182. .fontWeight(FontWeight.Bold)
  183. .textAlign(TextAlign.Start)
  184. .padding({ left: 10 })
  185. .fontColor($r('app.color.text_color'))
  186. .margin({bottom:6,top:4,left: 1 })
  187. }
  188. }
  189. LazyForEach(this.dataSource, (item: VideoItem, index: number) => {
  190. ListItem() {
  191. Column() {
  192. this.MusicItem(item, index)
  193. }
  194. }
  195. .borderRadius(14)
  196. .backgroundColor($r('app.color.start_window_background'))
  197. .margin({top:7,bottom:7})
  198. .transition(TransitionEffect.asymmetric(
  199. TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 500 }),
  200. TransitionEffect.scale({ x: 0, y: 0 })
  201. ))
  202. .clickEffect({ level: ClickEffectLevel.LIGHT })
  203. }, (item: VideoItem) => item.filePath)
  204. }
  205. }
  206. .cachedCount(2)
  207. // .layoutWeight(1)
  208. // .height('100%')
  209. .transition(TransitionEffect.asymmetric(
  210. TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 500 }),
  211. TransitionEffect.scale({ x: 0, y: 0 })
  212. ))
  213. }
  214. .pullDownRatio(this.ratio)
  215. .pullToRefresh(true)
  216. .refreshOffset(64)
  217. .onOffsetChange((offset: number) => {
  218. // 越接近最大距离,下拉跟手系数越小。
  219. this.ratio = 1 - Math.pow((offset / this.maxRefreshingHeight), 3);
  220. })
  221. .onStateChange((refreshStatus: RefreshStatus) => {
  222. console.info('onecold Refresh onStatueChange state is ' + refreshStatus);
  223. })
  224. .onRefreshing(async () => {
  225. await this.centerChart()
  226. setTimeout(() => {
  227. this.isRefreshing = false;
  228. }, 2000)
  229. console.log('onRefreshing test');
  230. })
  231. }
  232. .borderRadius(20)
  233. .padding({top:10,bottom:6})
  234. .height('auto')
  235. .margin({top:10,right:20,left:20})
  236. .layoutWeight(1)
  237. .height('100%')
  238. }
  239. @Builder
  240. listHeader() {
  241. Stack({alignContent:Alignment.Start}){
  242. Text(`共${this.selectedFiles.length}首歌`)
  243. .fontSize(14)
  244. .maxLines(1)
  245. .textAlign(TextAlign.Start)
  246. .padding({ left: 25 })
  247. .fontColor($r('app.color.text_color'))
  248. .margin({bottom:6,top:4,left: 6 })
  249. }
  250. .height('auto').width('100%')
  251. }
  252. @Builder
  253. topTitleBar() {
  254. // 顶部安全区和自定义标题栏
  255. Column() {
  256. // 顶部安全区
  257. Blank()
  258. .height(this.topRectHeight)
  259. .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)
  260. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
  261. // 自定义标题栏(Stack实现绝对居中)
  262. Stack() {
  263. // 居中标题
  264. Text('歌曲统计')
  265. .fontSize(18)
  266. .fontColor(Color.White)
  267. .align(Alignment.Center)
  268. // 左右按钮
  269. Row() {
  270. Image($r('app.media.menu'))
  271. .width(26)
  272. .height(26)
  273. .margin({ left: 12, right: 8 })
  274. .onClick(() => {
  275. this.getUIContext().animateTo({ duration: 666 }, () => {
  276. // 动画闭包内控制Image组件的出现和消失
  277. this.isShowDrawer = !this.isShowDrawer
  278. this.offsetX = 0
  279. })
  280. })
  281. Blank().flexGrow(1)
  282. Blank().width(32)
  283. }
  284. .height(48)
  285. .width('100%')
  286. .alignItems(VerticalAlign.Center)
  287. }
  288. .height(48)
  289. .width('100%')
  290. .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)
  291. }
  292. }
  293. @Builder
  294. private MusicItem(item: VideoItem, index: number) {
  295. Button({ type: ButtonType.Normal, stateEffect: true }) {
  296. Column() {
  297. Row() {
  298. Stack({ alignContent: Alignment.TopStart }) {
  299. Image(item.pixelMapPath)
  300. .width(75)
  301. .height(70)
  302. .borderRadius(8)
  303. .alt($r('app.media.music_red'))
  304. .alignSelf(ItemAlign.Center)
  305. .interpolation(ImageInterpolation.Medium)// 用于重采样后的抗锯齿
  306. .autoResize(true) // 重采样,可减少内存占用
  307. .padding({ left: 10,top:3,bottom:3 })
  308. };
  309. Column(){
  310. Text(item.name)
  311. .fontSize(17)
  312. .maxLines(1)
  313. .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动
  314. .fontColor($r('app.color.text_color'))
  315. .padding({ left: 10 })
  316. .textAlign(TextAlign.Start)
  317. .margin({ left: 5 })
  318. Text(StrUtil.isEmpty(item.artist) ? item.cTime : item.artist )
  319. .fontSize(15)
  320. .fontColor(Color.Gray)
  321. .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动
  322. .textAlign(TextAlign.Start)
  323. .padding({ top: 5,left: 10 })
  324. .margin({ left: 5 })
  325. }
  326. .alignItems(HorizontalAlign.Start)
  327. Blank()
  328. // 根据嵌入状态显示勾选图标
  329. Text(item.playCount+'')
  330. .fontSize(index < 3?18:16)
  331. .maxLines(1)
  332. .padding({ right: 5 })
  333. .textOverflow({ overflow: TextOverflow.MARQUEE })
  334. .animation({
  335. duration: 555,
  336. curve: 'Linear',
  337. })
  338. .fontColor(index < 3?this.themeColor:$r('app.color.text_color'))
  339. .margin({ right: 18 })
  340. }
  341. .layoutWeight(1)
  342. .height('100%')
  343. .width('100%')
  344. }
  345. }
  346. .backgroundColor(Color.Transparent)
  347. .width('100%')
  348. .height(89)
  349. }
  350. }
  351. @Builder
  352. function customRefreshingContent() {
  353. Stack() {
  354. Row() {
  355. LoadingProgress().height(32)
  356. }
  357. .alignItems(VerticalAlign.Center)
  358. }
  359. .align(Alignment.Center)
  360. .clip(true)
  361. // 设置最小高度约束保证自定义组件高度随刷新区域高度变化时自定义组件高度不会低于minHeight。
  362. .constraintSize({ minHeight: 32 })
  363. .width("100%")
  364. }