import TitleBar from '../view/TitleBar' import { router, window } from '@kit.ArkUI' import { AppUtil, DeviceUtil, DisplayUtil, FileUtil, LogUtil, PreferencesUtil, ToastUtil } from '@pura/harmony-utils' import lottie from '@ohos/lottie' import { AnimationItem } from '@ohos/lottie' import { MessageEvents, taskpool, worker } from '@kit.ArkTS' import { fileUri, picker } from '@kit.CoreFileKit' import { LocalMusic } from '../view/LocalMusic' import { Utility } from '../common/util/Utility' import MediaTable from '../common/util/MediaTable' import { VideoItem } from '../viewmodel/VideoItem' import Logger from '../common/util/Logger' import { CommonConstants } from '../common/constants/CommonConstants' import { emitter } from '@kit.BasicServicesKit' import { BreakpointTypeEnum } from '../common/util/BreakpointSystem' import { resourceManager } from '@kit.LocalizationKit' import { ConfigurationConstant } from '@kit.AbilityKit' import { DialogHelper } from '@pura/harmony-dialog' @Preview // @Entry @Component export struct ScanFilePage{ @StorageProp('mediaKuList') mediaKuList: Array = []; //媒体库文件 @State rootPath:string = '' //音频根目录 @State lockPath:string = '' @Consume isShowDrawer: boolean; @Consume offsetX: number; @State strText:string = '' @State textVisi:Visibility=Visibility.Hidden @State isStart:boolean = false @State isStartCover:boolean = false @State isStartSync:boolean = false @State appName:string = '' @Consume mType: number; //lottie动画构建渲染上下文 private mainRenderingSettings: RenderingContextSettings = new RenderingContextSettings(true) private path:string = "common/lottie/leida.json" private successpath:string = "common/lottie/success2.json" private mainCanvasRenderingContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.mainRenderingSettings) private animateItem: AnimationItem | null = null; /** 当前断点类型(如大屏/小屏) */ @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD; @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR; @State isDarkMode: boolean = false @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT; private windowClass: window.Window = globalThis.windowClass @StorageProp('topRectHeight') topRectHeight: number = px2vp(AppUtil.getStatusBarHeight()); initLottie(lpath:string,isLoop:boolean){ lottie.destroy(); //加载动画前先销毁之前加载的动画 this.animateItem = lottie.loadAnimation({ container: this.mainCanvasRenderingContext, // 渲染上下文 renderer: 'canvas', // 渲染方式 loop: isLoop, // 是否循环播放,默认true autoplay: true, // 是否自动播放,默认true name: '2024', // 动画名称 contentMode: 'Contain', // 填充的模式 frameRate: 30, //设置animator的刷帧率为30 imagePath: 'lottie/images/', // 加载读取指定路径下的图片资源 path: lpath, // json路径 // initialSegment: [0,70] }) lottie.setSpeed(0.7) } onPageShow() { } // 组件生命周期 async aboutToAppear() { this.topRectHeight = px2vp(AppUtil.getStatusBarHeight()); Utility.getAppName(getContext(this)).then((appName:string)=>{ this.appName = appName }) let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR); AppStorage.setOrCreate('themeColor', themeColor); this.themeColor = themeColor; this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK const documentViewPicker = new picker.DocumentViewPicker() let documentSaveResult = await documentViewPicker.save({ pickerMode: picker.DocumentPickerMode.DOWNLOAD }) this.rootPath = new fileUri.FileUri(documentSaveResult[0]).path this.lockPath = this.rootPath +'/'+ LocalMusic.STR_LOCK_VIDEO LogUtil.info('onecold 文件扫描 aboutToAppear') this.initLottie(this.path,true) setTimeout(()=>{ lottie.pause() },88) } onColorModeChange() { this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK } endScan(isOnekey:boolean){ const eventData: emitter.EventData = {}; emitter.emit({ eventId: 101 }, eventData); // 发送视频打开广播事件 this.watchStatus(false) if(isOnekey){ this.isStartCover = false this.strText = '获取成功!' }else{ if(this.isStartSync){ this.isStartSync = false this.strText = '校正数据成功!' }else{ this.strText = '扫描文件入库成功!' this.isStart = false } } //结束动画 lottie.pause() lottie.stop() this.initLottie(this.successpath,false) lottie.play() } //扫描过程中屏幕要保存常亮 watchStatus(isKeep:boolean) { this.windowClass.setWindowKeepScreenOn(isKeep); } // 组件生命周期 aboutToDisappear() { lottie.destroy(); } initState(isOnekey:boolean){ this.textVisi = Visibility.Visible this.initLottie(this.path,true) if(isOnekey){ this.strText = '正在获取' this.isStartCover = true }else{ this.strText = Utility.resourceToString(getContext(this),$r('app.string.scaning')) this.isStart = true } lottie.play() } doOptimize(isOnekey:boolean){ this.watchStatus(true) this.initState(isOnekey) const task = new taskpool.Task(scanDirectoryTask, getContext(this), this.rootPath, this.lockPath,PreferencesUtil.getStringSync('COVER_API','')); taskpool.execute(task, taskpool.Priority.HIGH).then(()=>{ this.endScan(isOnekey) }).catch((e:object)=>{ console.info("task1 catch e: " + e); }) } build() { Column() { // 顶部安全区和自定义标题栏 Column() { // 顶部安全区 Blank() .height(this.topRectHeight) .backgroundColor(this.isDarkMode?$r('app.color.title_bar_bg'):this.themeColor) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP]) // 自定义标题栏(Stack实现绝对居中) Stack() { // 居中标题 Text('文件扫描') .fontSize(18) .fontColor(Color.White) .align(Alignment.Center) // 左右按钮 Row() { Image($r('app.media.menu')) .width(28) .height(28) .margin({ left: 12, right: 8 }) .onClick(() => { animateTo({ duration: 555 }, () => { // 动画闭包内控制Image组件的出现和消失 this.isShowDrawer = !this.isShowDrawer this.offsetX = 0 }) }) Blank().flexGrow(1) Blank().width(32) } .height(48) .width('100%') .alignItems(VerticalAlign.Center) } .height(48) .width('100%') .backgroundColor(this.isDarkMode?$r('app.color.title_bar_bg'):this.themeColor) } Scroll(){ Column() { Stack(){ Image($r('app.media.file_search')) .width(48) .height(48) .fillColor(this.themeColor) .alignSelf(ItemAlign.Center) //lottie动画 Canvas(this.mainCanvasRenderingContext) .width(160) .height(160) .onReady(()=>{ //抗锯齿的设置 this.mainCanvasRenderingContext.imageSmoothingEnabled = true; this.mainCanvasRenderingContext.imageSmoothingQuality = 'medium' }) } .transition(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 380, curve: Curve.Ease,delay:30 })) Text(this.strText) .height(50) .fontSize(16) .fontColor($r('app.color.text_color')) .fontWeight(480) .visibility(this.textVisi) Button($r('app.string.start_scan'), { type: ButtonType.Capsule, stateEffect: false }) .width(180) .height(55) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .margin({ top: 10, bottom: 10 }) .backgroundColor(this.themeColor) .enabled(this.isStart ?false:true) .onClick(() => { this.doOptimize(false) }) .alignSelf(ItemAlign.Center) .transition(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 380, curve: Curve.Ease,delay:30 })) Column() { Row() { Image($r('app.media.selected')) .width(16) .height(16) .fillColor(this.themeColor) .margin({ left:10}) .alignSelf(ItemAlign.Center) Text(Utility.resourceToString(getContext(this),$r('app.string.file_scan_tip_one')) +`${this.appName} `+ Utility.resourceToString(getContext(this),$r('app.string.file_scan_tip_one_1'))) .margin({ left: 10, right: 20 }) .fontSize(15) .fontColor(Color.Gray) .fontWeight(480) .layoutWeight(1) } // .width('100%') .margin({ left:25,right: 25 ,top:20 }) Row() { Image($r('app.media.selected')) .width(16) .height(16) .fillColor(this.themeColor) .margin({ left:10}) .alignSelf(ItemAlign.Center) Text($r('app.string.file_scan_tip_two')) .margin({ left: 10, right: 20 }) .fontSize(15) .fontColor(Color.Gray) .fontWeight(480) .layoutWeight(1) } // .width('100%') .margin({ left:25,right: 25 ,top:20,bottom:20 }) Row() { Image($r('app.media.selected')) .width(16) .height(16) .fillColor(this.themeColor) .margin({ left:10}) .alignSelf(ItemAlign.Center) Text($r('app.string.sync_tips')) .margin({ left: 10, right: 20 }) .fontSize(15) .fontColor(Color.Gray) .fontWeight(480) .layoutWeight(1) } // .width('100%') .margin({ left:25,right: 25 ,bottom:20 }) } .width(this.currentBreakpoint !== BreakpointTypeEnum.SM?450:350) .backgroundColor($r('app.color.index_background')) .margin({ left:25,right: 25,top:20,bottom:20 }) .borderRadius(20) .justifyContent(FlexAlign.Center) .transition(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 380, curve: Curve.Ease,delay:60 })) Row() { Button($r('app.string.onekey_cover'), { type: ButtonType.Capsule, stateEffect: false }) .width(150) .height(55) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .margin({ top: 10, bottom: 10,right:10 }) .backgroundColor(this.themeColor) .enabled(this.isStartCover ?false:true) .onClick(() => { if(PreferencesUtil.getStringSync('COVER_API','')===''){ this.showTipsDialog() return } this.doOptimize(true) }) .alignSelf(ItemAlign.Center) Button($r('app.string.sync_data'), { type: ButtonType.Capsule, stateEffect: false }) .width(150) .height(55) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .margin({ left:10,top: 10, bottom: 10 }) .backgroundColor(this.themeColor) .enabled(this.isStartSync ?false:true) .onClick(() => { this.doSyncData() }) .alignSelf(ItemAlign.Center) } .transition(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 380, curve: Curve.Ease,delay:90 })) } .height('100%') .width('100%') .align(Alignment.Top) } .height(1000) } } doSyncData() { this.watchStatus(true) this.initLottie(this.path,true) this.isStartSync = true this.textVisi = Visibility.Visible this.strText = Utility.resourceToString(getContext(this),$r('app.string.sync_dataing')) lottie.play() // 仅提取 filePath 列表 const filePaths = this.mediaKuList.map(item => item.filePath); Logger.info('xiaozheng 校正数据开始 this.filePaths length= ' + filePaths.length) const task2 = new taskpool.Task(syncDataTask, getContext(this), filePaths); taskpool.execute(task2, taskpool.Priority.HIGH).then(()=>{ this.endScan(false) }).catch((e:object)=>{ console.info("task2 catch e: " + e); }) } showTipsDialog() { DialogHelper.showCustomContentDialog({ dialogId: 'tips', title: "友情提示", autoCancel: false, //点击遮障层时,不关闭弹窗 backCancel: true, //点击返回键,不关闭弹窗 contentBuilder: () => { this.customTipsBuilder("请到设置界面配置封面服务器地址!") }, buttons: [], }) } @Builder customTipsBuilder(content: string) { Column() { Text(content) .fontColor(Color.Gray) .fontSize(16) .alignSelf(ItemAlign.Start) .margin({ bottom: 15 }) .fontSize(16) Row() { Button('取消') .fontColor(Color.White) .backgroundColor($r('app.color.title_bar_bg'))//.linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]}) .height(50) .layoutWeight(1) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7}) .margin({ right: 6 }) .onClick(() => { DialogHelper.closeDialog('tips'); //关闭弹框 }) Button('跳转') .fontColor(Color.White)//.linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]}) .layoutWeight(1) .height(50) .backgroundColor($r('app.color.title_bar_bg')) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7}) .margin({ left: 6 }) .onClick(() => { DialogHelper.closeDialog('tips'); //关闭弹框 this.mType = 3 // router.pushUrl({ // url: 'pages/SettingPage' // }, router.RouterMode.Single); }) } } .width("100%") .padding(10) } } //扫描数据库校正对比数据是否真实存在,不存在在删除数据 @Concurrent async function syncDataTask(context: Context, mediaKuList: Array) { const table: MediaTable = new MediaTable(context); for (const item of mediaKuList) { const filePath = item; // 检查 filePath 是否存在 const exists:boolean = await FileUtil.accessSync(filePath); // 如果 filePath 不存在,则删除数据库中的数据 if (!exists) { table.getRdbStore(context, async (err:Error) => { if (err) { return; } Logger.info('xiaozheng 校正数据开始 this.exists = ' + exists+' filepath = ' +filePath) await table.deleteDataFilePath(filePath, (result:boolean) => { if(result){ Logger.info(`xiaozheng Deleted item success with filePath: ${filePath}`); } }); }) } } } //扫描文件入库 @Concurrent async function scanDirectoryTask(context: Context, dirPath: string, lockPath: string,cover_api:string) { const stack: string[] = [dirPath]; const table: MediaTable = new MediaTable(context); while (stack.length > 0) { const currentPath = stack.pop()!; const files = FileUtil.listFileSync(currentPath); await Promise.all(files.map(async (file) => { const fPath = `${currentPath}/${file}`; if (fPath === lockPath) return; if (FileUtil.isDirectory(fPath)) { stack.push(fPath); // 使用栈结构代替递归 } else { if (fPath.endsWith('.lrc') || fPath.endsWith('.srt')) return; if (Utility.isMeidaByExtension(fPath)) { const mediaItem = await Utility.uriGetMusicAssetsFromFile( context, fPath, CommonConstants.TYPE_LOCAL, true ); table.insert(mediaItem, (id: number) => { },cover_api); } } })); } }