ScanFilePage.ets 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. import TitleBar from '../view/TitleBar'
  2. import { router, window } from '@kit.ArkUI'
  3. import { AppUtil, DeviceUtil, DisplayUtil, FileUtil, LogUtil, PreferencesUtil, ToastUtil } from '@pura/harmony-utils'
  4. import lottie from '@ohos/lottie'
  5. import { AnimationItem } from '@ohos/lottie'
  6. import { MessageEvents, taskpool, worker } from '@kit.ArkTS'
  7. import { fileUri, picker } from '@kit.CoreFileKit'
  8. import { LocalMusic } from '../view/LocalMusic'
  9. import { Utility } from '../common/util/Utility'
  10. import MediaTable from '../common/util/MediaTable'
  11. import { VideoItem } from '../viewmodel/VideoItem'
  12. import Logger from '../common/util/Logger'
  13. import { CommonConstants } from '../common/constants/CommonConstants'
  14. import { emitter } from '@kit.BasicServicesKit'
  15. import { BreakpointTypeEnum } from '../common/util/BreakpointSystem'
  16. import { resourceManager } from '@kit.LocalizationKit'
  17. import { ConfigurationConstant } from '@kit.AbilityKit'
  18. import { DialogHelper } from '@pura/harmony-dialog'
  19. @Preview
  20. // @Entry
  21. @Component
  22. export struct ScanFilePage{
  23. @State rootPath:string = '' //音频根目录
  24. @State lockPath:string = ''
  25. @Consume isShowDrawer: boolean;
  26. @Consume offsetX: number;
  27. @State strText:string = ''
  28. @State isStart:boolean = false
  29. @State isStartCover:boolean = false
  30. @State appName:string = ''
  31. //lottie动画构建渲染上下文
  32. private mainRenderingSettings: RenderingContextSettings = new RenderingContextSettings(true)
  33. private path:string = "common/lottie/leida.json"
  34. private successpath:string = "common/lottie/success2.json"
  35. private mainCanvasRenderingContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.mainRenderingSettings)
  36. private animateItem: AnimationItem | null = null;
  37. /** 当前断点类型(如大屏/小屏) */
  38. @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD;
  39. @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
  40. @State isDarkMode: boolean = false
  41. @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number =
  42. ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
  43. private windowClass: window.Window = globalThis.windowClass
  44. @StorageProp('topRectHeight') topRectHeight: number = px2vp(AppUtil.getStatusBarHeight());
  45. initLottie(lpath:string,isLoop:boolean){
  46. lottie.destroy(); //加载动画前先销毁之前加载的动画
  47. this.animateItem = lottie.loadAnimation({
  48. container: this.mainCanvasRenderingContext, // 渲染上下文
  49. renderer: 'canvas', // 渲染方式
  50. loop: isLoop, // 是否循环播放,默认true
  51. autoplay: true, // 是否自动播放,默认true
  52. name: '2024', // 动画名称
  53. contentMode: 'Contain', // 填充的模式
  54. frameRate: 30, //设置animator的刷帧率为30
  55. imagePath: 'lottie/images/', // 加载读取指定路径下的图片资源
  56. path: lpath, // json路径
  57. // initialSegment: [0,70]
  58. })
  59. lottie.setSpeed(0.7)
  60. }
  61. onPageShow() {
  62. this.initLottie(this.path,true)
  63. setTimeout(()=>{
  64. lottie.pause()
  65. },88)
  66. }
  67. // 组件生命周期
  68. async aboutToAppear() {
  69. this.topRectHeight = px2vp(AppUtil.getStatusBarHeight());
  70. Utility.getAppName(getContext(this)).then((appName:string)=>{
  71. this.appName = appName
  72. })
  73. let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
  74. AppStorage.setOrCreate('themeColor', themeColor);
  75. this.themeColor = themeColor;
  76. this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
  77. const documentViewPicker = new picker.DocumentViewPicker()
  78. let documentSaveResult = await documentViewPicker.save({ pickerMode: picker.DocumentPickerMode.DOWNLOAD })
  79. this.rootPath = new fileUri.FileUri(documentSaveResult[0]).path
  80. this.lockPath = this.rootPath +'/'+ LocalMusic.STR_LOCK_VIDEO
  81. LogUtil.info('onecold 文件扫描 aboutToAppear')
  82. }
  83. onColorModeChange() {
  84. this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
  85. }
  86. endScan(isOnekey:boolean){
  87. const eventData: emitter.EventData = {};
  88. emitter.emit({ eventId: 101 }, eventData); // 发送视频打开广播事件
  89. this.watchStatus(false)
  90. ToastUtil.showToast('扫描文件入库成功!')
  91. if(isOnekey){
  92. this.isStartCover = false
  93. }else{
  94. this.isStart = false
  95. }
  96. this.strText = ''
  97. //结束动画
  98. lottie.pause()
  99. lottie.stop()
  100. this.initLottie(this.successpath,false)
  101. lottie.play()
  102. }
  103. //扫描过程中屏幕要保存常亮
  104. watchStatus(isKeep:boolean) {
  105. this.windowClass.setWindowKeepScreenOn(isKeep);
  106. }
  107. // 组件生命周期
  108. aboutToDisappear() {
  109. lottie.destroy();
  110. }
  111. // @State titleBarModel: TitleBar.Model = new TitleBar.Model()
  112. // .setTitleTextStyle(FontStyle.Normal)
  113. // .setTitleBarStyle(TitleBar.BarStyle.TRANSPARENT)
  114. // .setLeftIcon($r('app.media.left_back_white'))
  115. // .setTitleName("文件扫描")
  116. // .setTitleFontColor(Color.White)
  117. // .setTitleBarBackground(this.themeColor)
  118. // .setLeftTitleBackground(this.themeColor)
  119. // .setTitleBarBottomLineColor(this.themeColor)
  120. // .setOnLeftClickListener(() => {
  121. // router.back()
  122. //
  123. // })
  124. initState(isOnekey:boolean){
  125. this.initLottie(this.path,true)
  126. if(isOnekey){
  127. this.isStartCover = true
  128. }else{
  129. this.isStart = true
  130. }
  131. this.strText = Utility.resourceToString(getContext(this),$r('app.string.scaning'))
  132. lottie.play()
  133. }
  134. doOptimize(isOnekey:boolean){
  135. this.watchStatus(true)
  136. this.initState(isOnekey)
  137. const task = new taskpool.Task(scanDirectoryTask, getContext(this), this.rootPath,
  138. this.lockPath,PreferencesUtil.getStringSync('COVER_API',''));
  139. taskpool.execute(task, taskpool.Priority.HIGH).then(()=>{
  140. this.endScan(isOnekey)
  141. }).catch((e:object)=>{
  142. console.info("task1 catch e: " + e);
  143. })
  144. }
  145. build() {
  146. Column() {
  147. // 顶部安全区和自定义标题栏
  148. Column() {
  149. // 顶部安全区
  150. Blank()
  151. .height(this.topRectHeight)
  152. .backgroundColor(this.isDarkMode?$r('app.color.title_bar_bg'):this.themeColor)
  153. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
  154. // 自定义标题栏(Stack实现绝对居中)
  155. Stack() {
  156. // 居中标题
  157. Text('文件扫描')
  158. .fontSize(18)
  159. .fontColor(Color.White)
  160. .align(Alignment.Center)
  161. // 左右按钮
  162. Row() {
  163. Image($r('app.media.menu'))
  164. .width(28)
  165. .height(28)
  166. .margin({ left: 12, right: 8 })
  167. .onClick(() => {
  168. animateTo({ duration: 555 }, () => {
  169. // 动画闭包内控制Image组件的出现和消失
  170. this.isShowDrawer = !this.isShowDrawer
  171. this.offsetX = 0
  172. })
  173. })
  174. Blank().flexGrow(1)
  175. Blank().width(32)
  176. }
  177. .height(48)
  178. .width('100%')
  179. .alignItems(VerticalAlign.Center)
  180. }
  181. .height(48)
  182. .width('100%')
  183. .backgroundColor(this.isDarkMode?$r('app.color.title_bar_bg'):this.themeColor)
  184. }
  185. Scroll(){
  186. Column() {
  187. Stack(){
  188. Image($r('app.media.file_search'))
  189. .width(48)
  190. .height(48)
  191. .fillColor(this.themeColor)
  192. .alignSelf(ItemAlign.Center)
  193. //lottie动画
  194. Canvas(this.mainCanvasRenderingContext)
  195. .width(160)
  196. .height(160)
  197. .onReady(()=>{
  198. //抗锯齿的设置
  199. this.mainCanvasRenderingContext.imageSmoothingEnabled = true;
  200. this.mainCanvasRenderingContext.imageSmoothingQuality = 'medium'
  201. })
  202. }
  203. .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
  204. .animation({ duration: 380, curve: Curve.Ease,delay:30 }))
  205. // Text(this.strText)
  206. // .margin({ right: 20,top:20,bottom:20 })
  207. // .fontSize(16)
  208. // .fontColor(Color.Black)
  209. // .fontWeight(480)
  210. Column() {
  211. Row() {
  212. Image($r('app.media.selected'))
  213. .width(16)
  214. .height(16)
  215. .fillColor(this.themeColor)
  216. .margin({ left:10})
  217. .alignSelf(ItemAlign.Center)
  218. Text(Utility.resourceToString(getContext(this),$r('app.string.file_scan_tip_one'))
  219. +`${this.appName} `+
  220. Utility.resourceToString(getContext(this),$r('app.string.file_scan_tip_one_1')))
  221. .margin({ left: 10, right: 20 })
  222. .fontSize(15)
  223. .fontColor(Color.Gray)
  224. .fontWeight(480)
  225. .layoutWeight(1)
  226. }
  227. // .width('100%')
  228. .margin({ left:25,right: 25 ,top:20 })
  229. Row() {
  230. Image($r('app.media.selected'))
  231. .width(16)
  232. .height(16)
  233. .fillColor(this.themeColor)
  234. .margin({ left:10})
  235. .alignSelf(ItemAlign.Center)
  236. Text($r('app.string.file_scan_tip_two'))
  237. .margin({ left: 10, right: 20 })
  238. .fontSize(15)
  239. .fontColor(Color.Gray)
  240. .fontWeight(480)
  241. .layoutWeight(1)
  242. }
  243. // .width('100%')
  244. .margin({ left:25,right: 25 ,top:20,bottom:20 })
  245. }
  246. .width(this.currentBreakpoint !== BreakpointTypeEnum.SM?450:350)
  247. .backgroundColor($r('app.color.index_background'))
  248. .margin({ left:25,right: 25,top:20,bottom:20 })
  249. .borderRadius(20)
  250. .justifyContent(FlexAlign.Center)
  251. .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
  252. .animation({ duration: 380, curve: Curve.Ease,delay:60 }))
  253. Column() {
  254. Button(this.isStart?'正在扫描':$r('app.string.start_scan'), { type: ButtonType.Capsule, stateEffect: false })
  255. .width(180)
  256. .height(55)
  257. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
  258. .margin({ top: 20, bottom: 10 })
  259. .linearGradient({
  260. direction: GradientDirection.Right,
  261. colors: [['#ff37a0fc', 0.0], ['#67e667', 0.5], ['#f5856e', 1.0]]
  262. })
  263. .enabled(this.isStart ?false:true)
  264. .onClick(() => {
  265. this.doOptimize(false)
  266. })
  267. .alignSelf(ItemAlign.Center)
  268. Button(this.isStartCover?'正在扫描':$r('app.string.onekey_cover'), { type: ButtonType.Capsule, stateEffect: false })
  269. .width(180)
  270. .height(55)
  271. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
  272. .margin({ top: 10, bottom: 20 })
  273. .linearGradient({
  274. direction: GradientDirection.Right,
  275. colors: [['#ff37a0fc', 0.0], ['#67e667', 0.5], ['#f5856e', 1.0]]
  276. })
  277. .enabled(this.isStartCover ?false:true)
  278. .onClick(() => {
  279. if(PreferencesUtil.getStringSync('COVER_API','')===''){
  280. this.showTipsDialog()
  281. return
  282. }
  283. this.doOptimize(true)
  284. })
  285. .alignSelf(ItemAlign.Center)
  286. }
  287. .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
  288. .animation({ duration: 380, curve: Curve.Ease,delay:90 }))
  289. }
  290. .height('100%')
  291. .width('100%')
  292. .align(Alignment.Top)
  293. }
  294. .height(1000)
  295. }
  296. }
  297. showTipsDialog() {
  298. DialogHelper.showCustomContentDialog({
  299. dialogId: 'tips',
  300. title: "友情提示",
  301. autoCancel: false, //点击遮障层时,不关闭弹窗
  302. backCancel: true, //点击返回键,不关闭弹窗
  303. contentBuilder: () => {
  304. this.customTipsBuilder("请到设置界面配置封面服务器地址!")
  305. },
  306. buttons: [],
  307. })
  308. }
  309. @Builder
  310. customTipsBuilder(content: string) {
  311. Column() {
  312. Text(content)
  313. .fontColor(Color.Gray)
  314. .fontSize(16)
  315. .alignSelf(ItemAlign.Start)
  316. .margin({ bottom: 15 })
  317. .fontSize(16)
  318. Row() {
  319. Button('取消')
  320. .fontColor(Color.White)
  321. .backgroundColor($r('app.color.title_bar_bg'))//.linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]})
  322. .height(50)
  323. .layoutWeight(1)
  324. .stateEffect(true)
  325. .margin({ right: 6 })
  326. .onClick(() => {
  327. DialogHelper.closeDialog('tips'); //关闭弹框
  328. })
  329. Button('跳转')
  330. .fontColor(Color.White)//.linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]})
  331. .layoutWeight(1)
  332. .height(50)
  333. .backgroundColor($r('app.color.title_bar_bg'))
  334. .stateEffect(true)
  335. .margin({ left: 6 })
  336. .onClick(() => {
  337. DialogHelper.closeDialog('tips'); //关闭弹框
  338. router.pushUrl({
  339. url: 'pages/SettingPage'
  340. }, router.RouterMode.Single);
  341. })
  342. }
  343. }
  344. .width("100%")
  345. .padding(10)
  346. }
  347. }
  348. @Concurrent
  349. async function scanDirectoryTask(context: Context, dirPath: string, lockPath: string,cover_api:string) {
  350. const stack: string[] = [dirPath];
  351. const table: MediaTable = new MediaTable(context);
  352. while (stack.length > 0) {
  353. const currentPath = stack.pop()!;
  354. const files = FileUtil.listFileSync(currentPath);
  355. await Promise.all(files.map(async (file) => {
  356. const fPath = `${currentPath}/${file}`;
  357. if (fPath === lockPath) return;
  358. if (FileUtil.isDirectory(fPath)) {
  359. stack.push(fPath); // 使用栈结构代替递归
  360. } else {
  361. if (fPath.endsWith('.lrc') || fPath.endsWith('.srt')) return;
  362. if (Utility.isMeidaByExtension(fPath)) {
  363. const mediaItem = await Utility.uriGetMusicAssetsFromFile(
  364. context, fPath, CommonConstants.TYPE_LOCAL, true
  365. );
  366. table.insert(mediaItem, (id: number) => {
  367. },cover_api);
  368. }
  369. }
  370. }));
  371. }
  372. }