Browse Source

增加视频恢复

zk 1 year ago
parent
commit
8e6e8acadc

+ 3 - 0
app/src/main/AndroidManifest.xml

@@ -59,6 +59,9 @@
         <activity
             android:name=".module.preview.PreviewActivity"
             android:screenOrientation="portrait" />
+        <activity
+            android:name=".module.videorecover.VideoRecoverActivity"
+            android:screenOrientation="portrait" />
 
     </application>
 

+ 2 - 0
app/src/main/java/com/datarecovery/master/module/homepage/HomePageFragment.java

@@ -11,6 +11,7 @@ import com.atmob.app.lib.base.BaseFragment;
 import com.datarecovery.master.R;
 import com.datarecovery.master.databinding.FragmentHomePageBinding;
 import com.datarecovery.master.dialog.CommonSureDialog;
+import com.datarecovery.master.module.videorecover.VideoRecoverActivity;
 import com.datarecovery.master.utils.PermissionUtil;
 import com.datarecovery.master.utils.xfile.XFilePermission;
 import com.datarecovery.master.widget.InformationSwitchBanner;
@@ -99,6 +100,7 @@ public class HomePageFragment extends BaseFragment<FragmentHomePageBinding> {
                 case FunctionBean.IMG_CLEARING:
                     break;
                 case FunctionBean.VIDEO_RECOVERY:
+                    VideoRecoverActivity.start(getActivity());
                     break;
             }
         });

+ 7 - 19
app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverViewModel.java

@@ -1,7 +1,5 @@
 package com.datarecovery.master.module.imgrecover;
 
-import android.net.Uri;
-
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
 import androidx.lifecycle.Transformations;
@@ -19,8 +17,6 @@ import org.reactivestreams.Subscription;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
@@ -29,8 +25,6 @@ import atmob.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 import atmob.reactivex.rxjava3.annotations.NonNull;
 import atmob.reactivex.rxjava3.core.FlowableSubscriber;
 import atmob.reactivex.rxjava3.disposables.Disposable;
-import atmob.reactivex.rxjava3.functions.Action;
-import atmob.reactivex.rxjava3.functions.Consumer;
 import atmob.rxjava.utils.RxJavaUtil;
 import dagger.hilt.android.lifecycle.HiltViewModel;
 
@@ -41,10 +35,10 @@ public class ImageRecoverViewModel extends BaseViewModel {
 
     private final long SCANNING_COUNTDOWN = 1000 * 60 * 6;
 
-    private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedPhotoImg = new MutableLiveData<>();
-    private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedWxImg = new MutableLiveData<>();
-    private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedQQImg = new MutableLiveData<>();
-    private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedOtherImg = new MutableLiveData<>();
+    private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedPhotoImg = new MutableLiveData<>(new ArrayList<>());
+    private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedWxImg = new MutableLiveData<>(new ArrayList<>());
+    private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedQQImg = new MutableLiveData<>(new ArrayList<>());
+    private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedOtherImg = new MutableLiveData<>(new ArrayList<>());
     private LiveData<String> detectedPhotoTitle;
     private LiveData<String> detectedWxTitle;
     private LiveData<String> detectedQQTitle;
@@ -214,11 +208,6 @@ public class ImageRecoverViewModel extends BaseViewModel {
                         scanDisposable = Disposable.fromSubscription(s);
                         addDisposable(scanDisposable);
                         showScanDialogEvent.setValue(true);
-                        detectedPhotoImg.setValue(new ArrayList<>());
-                        detectedWxImg.setValue(new ArrayList<>());
-                        detectedQQImg.setValue(new ArrayList<>());
-                        detectedOtherImg.setValue(new ArrayList<>());
-                        selectedList.setValue(new ArrayList<>());
                         checkAll.setValue(false);
                         totalCount = 0;
                         totalDetectedCount.setValue(0);
@@ -292,7 +281,7 @@ public class ImageRecoverViewModel extends BaseViewModel {
         showLoadingEvent.setValue(true);
         RxJavaUtil.doInBackground(() -> {
             for (ImageDeepDetector.ImageFile item : list) {
-                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_IMAGE, item.newInputStream(), FileUtil.getCreateFileName(item.getName()));
+                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_IMAGE, item.newInputStream(), FileUtil.getCreateFileName(item.getName(), ".jpg"));
                 item.setCheck(false);
             }
             return true;
@@ -300,9 +289,8 @@ public class ImageRecoverViewModel extends BaseViewModel {
             checkAll.setValue(false);
             showLoadingEvent.setValue(false);
             ToastUtil.show(R.string.export_success, ToastUtil.LENGTH_SHORT);
-            List<ImageDeepDetector.ImageFile> sList = getList(selectedList);
-            sList.clear();
-            selectedList.setValue(sList);
+            list.clear();
+            selectedList.setValue(list);
         }, throwable -> {
             showLoadingEvent.setValue(false);
             ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);

+ 1 - 2
app/src/main/java/com/datarecovery/master/module/preview/PreviewViewModel.java

@@ -16,7 +16,6 @@ import com.datarecovery.master.utils.ToastUtil;
 
 import java.lang.ref.WeakReference;
 import java.util.List;
-import java.util.UUID;
 
 import javax.inject.Inject;
 
@@ -116,7 +115,7 @@ public class PreviewViewModel extends BaseViewModel {
                 return;
             }
             try {
-                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_IMAGE, value.newInputStream(), FileUtil.getCreateFileName(value.getName()));
+                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_IMAGE, value.newInputStream(), FileUtil.getCreateFileName(value.getName(), ".jpg"));
                 ToastUtil.show(R.string.export_success, ToastUtil.LENGTH_SHORT);
             } catch (Exception e) {
                 ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);

+ 99 - 0
app/src/main/java/com/datarecovery/master/module/videorecover/VideoItemAdapter.java

@@ -0,0 +1,99 @@
+package com.datarecovery.master.module.videorecover;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.datarecovery.master.databinding.ItemDataImgBinding;
+import com.datarecovery.master.databinding.ItemDataVideoBinding;
+import com.datarecovery.master.utils.FilesSearch;
+import com.datarecovery.master.utils.ImageDeepDetector;
+
+import java.util.List;
+
+public class VideoItemAdapter extends RecyclerView.Adapter<VideoItemAdapter.ViewHolder> {
+
+
+    private List<FilesSearch.DocumentFile> list;
+    private final LifecycleOwner lifecycleOwner;
+    private int size;
+
+    private onItemClick onItemClick;
+
+    public VideoItemAdapter(LifecycleOwner lifecycleOwner) {
+        this.lifecycleOwner = lifecycleOwner;
+    }
+
+    public void setOnItemClick(VideoItemAdapter.onItemClick onItemClick) {
+        this.onItemClick = onItemClick;
+    }
+
+    @NonNull
+    @Override
+    public VideoItemAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        Context context = parent.getContext();
+        LayoutInflater layoutInflater = LayoutInflater.from(context);
+        ItemDataVideoBinding binding = ItemDataVideoBinding.inflate(layoutInflater, parent, false);
+        return new VideoItemAdapter.ViewHolder(binding);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+        holder.bind(list.get(position));
+    }
+
+    public void submit(List<FilesSearch.DocumentFile> videoList) {
+        if (videoList == null) {
+            return;
+        }
+        int index = 0;
+        if (list != null) {
+            index = videoList.size() - size;
+        } else {
+            index = videoList.size();
+        }
+        this.size = videoList.size();
+        this.list = videoList;
+        notifyItemRangeInserted(0, index);
+    }
+
+    @Override
+    public int getItemCount() {
+        return (list == null) ? 0 : list.size();
+    }
+
+    public class ViewHolder extends RecyclerView.ViewHolder {
+
+        ItemDataVideoBinding binding;
+
+        public ViewHolder(@NonNull ItemDataVideoBinding binding) {
+            super(binding.getRoot());
+            this.binding = binding;
+            binding.setLifecycleOwner(lifecycleOwner);
+            binding.setCheckBoxClick(v -> {
+                if (onItemClick != null) {
+                    onItemClick.onCheck(binding.getFile());
+                }
+            });
+            binding.getRoot().setOnClickListener(v -> {
+                if (onItemClick != null) {
+                    onItemClick.onItemClick(binding.getFile());
+                }
+            });
+        }
+
+        public void bind(FilesSearch.DocumentFile file) {
+            binding.setFile(file);
+        }
+    }
+
+    public interface onItemClick {
+        void onCheck(FilesSearch.DocumentFile file);
+
+        void onItemClick(FilesSearch.DocumentFile file);
+    }
+}

+ 155 - 0
app/src/main/java/com/datarecovery/master/module/videorecover/VideoRecoverActivity.java

@@ -0,0 +1,155 @@
+package com.datarecovery.master.module.videorecover;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.GridLayoutManager;
+
+import com.atmob.app.lib.base.BaseActivity;
+import com.datarecovery.master.R;
+import com.datarecovery.master.databinding.ActivityVideoRecoverBinding;
+import com.datarecovery.master.dialog.CommonLoadingDialog;
+import com.datarecovery.master.dialog.CommonSureDialog;
+import com.datarecovery.master.dialog.ScanProgressDialog;
+import com.datarecovery.master.module.preview.PreviewActivity;
+import com.datarecovery.master.utils.BoxingUtil;
+import com.datarecovery.master.utils.FilesSearch;
+import com.datarecovery.master.utils.GridRecoverItemDecoration;
+import com.gyf.immersionbar.ImmersionBar;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+
+@AndroidEntryPoint
+public class VideoRecoverActivity extends BaseActivity<ActivityVideoRecoverBinding> {
+
+
+    private VideoRecoverViewModel videoRecoverViewModel;
+    private CommonLoadingDialog loadingDialog;
+    private CommonSureDialog backDialog;
+    private ScanProgressDialog scanProgressDialog;
+    private VideoItemAdapter videoItemAdapter;
+
+
+    public static void start(Context context) {
+        Intent intent = new Intent(context, VideoRecoverActivity.class);
+        if (!(context instanceof Activity)) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        }
+        context.startActivity(intent);
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        initView();
+        initObserver();
+    }
+
+    private void initObserver() {
+        videoRecoverViewModel.getShowLoadingEvent().observe(this, this::showLoadingDialog);
+        videoRecoverViewModel.getDetectedVideoList().observe(this, list -> videoItemAdapter.submit(list));
+        videoRecoverViewModel.getDetectedFinish().observe(this, o -> scanProgressDialog.detectedFinish());
+        videoRecoverViewModel.getShowScanDialogEvent().observe(this, this::showScanProgressDialog);
+    }
+
+    private void initView() {
+        binding.toolBar.setNavigationOnClickListener(v -> onBackPressed());
+        addTopStatusBarHeight(binding.toolBar);
+        initRecycleView();
+    }
+
+    private void initRecycleView() {
+        videoItemAdapter = new VideoItemAdapter(this);
+        binding.ryVideoRecover.setLayoutManager(new GridLayoutManager(this, 3));
+        binding.ryVideoRecover.setAdapter(videoItemAdapter);
+        binding.ryVideoRecover.addItemDecoration(new GridRecoverItemDecoration(3, 0.0282222222222222f, 0.0202222222222222f));
+        videoItemAdapter.setOnItemClick(new VideoItemAdapter.onItemClick() {
+            @Override
+            public void onCheck(FilesSearch.DocumentFile file) {
+                if (file == null) {
+                    return;
+                }
+                videoRecoverViewModel.setItemCheck(file);
+            }
+
+            @Override
+            public void onItemClick(FilesSearch.DocumentFile file) {
+                PreviewActivity.start(VideoRecoverActivity.this, PreviewActivity.TYPE_VIDEO, file.getUri());
+            }
+        });
+    }
+
+    public void showScanProgressDialog(Boolean show) {
+        if (BoxingUtil.boxing(show)) {
+            if (scanProgressDialog == null) {
+                scanProgressDialog = new ScanProgressDialog(this, videoRecoverViewModel.getTotalDetectedCount());
+                scanProgressDialog.setOnCancelListener(() -> videoRecoverViewModel.cancelScan());
+            }
+            scanProgressDialog.show();
+        } else {
+            if (scanProgressDialog != null) {
+                scanProgressDialog.dismiss();
+            }
+        }
+    }
+
+    public void showLoadingDialog(Boolean show) {
+        if (BoxingUtil.boxing(show)) {
+            if (loadingDialog == null) {
+                loadingDialog = new CommonLoadingDialog(this);
+            }
+            loadingDialog.show();
+        } else {
+            if (loadingDialog != null) {
+                loadingDialog.dismiss();
+            }
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        showBackDialog();
+    }
+
+    private void showBackDialog() {
+        if (backDialog == null) {
+            backDialog = new CommonSureDialog(this);
+            backDialog.setDialogTitle(R.string.img_recovery_back_title)
+                    .setDialogContent(R.string.img_recovery_back_content)
+                    .setOnDialogClickListener(this::finish);
+        }
+        backDialog.show();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            onBackPressed();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    protected void initViewModel() {
+        super.initViewModel();
+        videoRecoverViewModel = getViewModelProvider().get(VideoRecoverViewModel.class);
+        binding.setVideoRecoverViewModel(videoRecoverViewModel);
+    }
+
+    @Override
+    protected boolean shouldImmersion() {
+        return true;
+    }
+
+    @Override
+    protected void configImmersion(@NonNull ImmersionBar immersionBar) {
+        immersionBar.statusBarDarkFont(true);
+    }
+}

+ 204 - 0
app/src/main/java/com/datarecovery/master/module/videorecover/VideoRecoverViewModel.java

@@ -0,0 +1,204 @@
+package com.datarecovery.master.module.videorecover;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Transformations;
+
+import com.atmob.app.lib.base.BaseViewModel;
+import com.atmob.app.lib.livedata.SingleLiveEvent;
+import com.atmob.common.data.KVUtils;
+import com.atmob.common.runtime.ContextUtil;
+import com.datarecovery.master.R;
+import com.datarecovery.master.utils.FileUtil;
+import com.datarecovery.master.utils.FilesSearch;
+import com.datarecovery.master.utils.MediaStoreHelper;
+import com.datarecovery.master.utils.ToastUtil;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import atmob.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import atmob.reactivex.rxjava3.annotations.NonNull;
+import atmob.reactivex.rxjava3.disposables.Disposable;
+import atmob.rxjava.utils.RxJavaUtil;
+import dagger.hilt.android.lifecycle.HiltViewModel;
+
+
+@HiltViewModel
+public class VideoRecoverViewModel extends BaseViewModel {
+
+    private final String IS_SHOW_VIDEO_HINT = "isShowVideoHint";
+    private final long SCANNING_COUNTDOWN = 1000 * 60 * 6;
+    private LiveData<String> selectedCountTxt;
+    private final MutableLiveData<Boolean> checkAll = new MutableLiveData<>();
+    private final MutableLiveData<List<FilesSearch.DocumentFile>> detectedVideoList = new MutableLiveData<>();
+    private final MutableLiveData<List<FilesSearch.DocumentFile>> selectedList = new MutableLiveData<>();
+
+    private final SingleLiveEvent<?> detectedFinish = new SingleLiveEvent<>();
+    private final SingleLiveEvent<?> finishEvent = new SingleLiveEvent<>();
+    private final SingleLiveEvent<Boolean> showScanDialogEvent = new SingleLiveEvent<>();
+    private final SingleLiveEvent<Boolean> showLoadingEvent = new SingleLiveEvent<>();
+    private int totalCount = 0;
+    private final MutableLiveData<Integer> totalDetectedCount = new MutableLiveData<>();
+    private final MutableLiveData<Boolean> isShowHint = new MutableLiveData<>();
+    private Disposable scanDisposable;
+
+
+    @Inject
+    public VideoRecoverViewModel() {
+        startVideoScanning();
+        selectedCountTxt = Transformations.map(selectedList, list -> {
+            if (list == null || list.isEmpty()) {
+                return ContextUtil.getContext().getString(R.string.export);
+            }
+            return ContextUtil.getContext().getString(R.string.export_count, list.size());
+        });
+        if (KVUtils.getDefault().getBoolean(IS_SHOW_VIDEO_HINT, true)) {
+            isShowHint.setValue(true);
+        }
+    }
+
+    public LiveData<Boolean> getIsShowHint() {
+        return isShowHint;
+    }
+
+    public LiveData<List<FilesSearch.DocumentFile>> getDetectedVideoList() {
+        return detectedVideoList;
+    }
+
+    public LiveData<?> getDetectedFinish() {
+        return detectedFinish;
+    }
+
+    public LiveData<?> getFinishEvent() {
+        return finishEvent;
+    }
+
+    public LiveData<Boolean> getShowScanDialogEvent() {
+        return showScanDialogEvent;
+    }
+
+    public LiveData<Integer> getTotalDetectedCount() {
+        return totalDetectedCount;
+    }
+
+    public LiveData<List<FilesSearch.DocumentFile>> getSelectedList() {
+        return selectedList;
+    }
+
+    public LiveData<Boolean> getCheckAll() {
+        return checkAll;
+    }
+
+    public LiveData<String> getSelectedCountTxt() {
+        return selectedCountTxt;
+    }
+
+    public void onCheckAllClick(boolean isCheck) {
+        if (!scanDisposable.isDisposed()) {
+            return;
+        }
+        checkAll.setValue(isCheck);
+
+    }
+
+    public LiveData<Boolean> getShowLoadingEvent() {
+        return showLoadingEvent;
+    }
+
+    private void startVideoScanning() {
+        FilesSearch.search(ContextUtil.getContext(), FilesSearch.DocumentFile.VIDEO)
+                .take(SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(new Subscriber<List<FilesSearch.DocumentFile>>() {
+                    @Override
+                    public void onSubscribe(Subscription s) {
+                        s.request(Integer.MAX_VALUE);
+                        scanDisposable = Disposable.fromSubscription(s);
+                        addDisposable(scanDisposable);
+                        showScanDialogEvent.setValue(true);
+                        totalCount = 0;
+                        totalDetectedCount.setValue(0);
+                    }
+
+                    @Override
+                    public void onNext(List<FilesSearch.DocumentFile> documentFiles) {
+                        if (documentFiles == null) {
+                            return;
+                        }
+                        totalCount += documentFiles.size();
+                        totalDetectedCount.setValue(totalCount);
+                        List<FilesSearch.DocumentFile> videoList = detectedVideoList.getValue();
+                        if (videoList == null) {
+                            videoList = new ArrayList<>();
+                        }
+                        videoList.addAll(0, documentFiles);
+                        detectedVideoList.setValue(videoList);
+                    }
+
+                    @Override
+                    public void onError(Throwable t) {
+                        showScanDialogEvent.setValue(false);
+                    }
+
+                    @Override
+                    public void onComplete() {
+                        detectedFinish.call();
+                    }
+                });
+    }
+
+    public void cancelScan() {
+        if (scanDisposable != null) scanDisposable.dispose();
+    }
+
+    public void onExportClick() {
+        List<FilesSearch.DocumentFile> list = selectedList.getValue();
+        if (list == null || list.size() == 0) {
+            return;
+        }
+        //TODO 判断是否有会员
+        showLoadingEvent.setValue(true);
+        RxJavaUtil.doInBackground(() -> {
+            for (FilesSearch.DocumentFile item : list) {
+                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_VIDEO, item.newInputStream(), FileUtil.getCreateFileName(item.getName(), ".mp4"));
+                item.setCheck(false);
+            }
+            return true;
+        }, o -> {
+            checkAll.setValue(false);
+            showLoadingEvent.setValue(false);
+            ToastUtil.show(R.string.export_success, ToastUtil.LENGTH_SHORT);
+            list.clear();
+            selectedList.setValue(list);
+        }, throwable -> {
+            showLoadingEvent.setValue(false);
+            ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);
+        });
+    }
+
+    public void setItemCheck(@NonNull FilesSearch.DocumentFile file) {
+        file.setCheck(!file.isCheck());
+        List<FilesSearch.DocumentFile> list = selectedList.getValue();
+        if (list == null) {
+            list = new ArrayList<>();
+        }
+        if (file.isCheck()) {
+            list.add(file);
+        } else {
+            list.remove(file);
+        }
+        selectedList.setValue(list);
+    }
+
+    public void closeHintClick() {
+        KVUtils.getDefault().putBoolean(IS_SHOW_VIDEO_HINT, false);
+        isShowHint.setValue(false);
+    }
+}

+ 4 - 4
app/src/main/java/com/datarecovery/master/utils/FileUtil.java

@@ -1,7 +1,6 @@
 package com.datarecovery.master.utils;
 
 import android.content.Context;
-import android.net.Uri;
 import android.text.format.Formatter;
 
 
@@ -33,16 +32,17 @@ public class FileUtil {
     }
 
 
-    public static String getCreateFileName(String fileName) {
+    public static String getCreateFileName(String fileName, String suffix) {
         String uuid = UUID.randomUUID().toString();
         if (fileName == null) {
-            return uuid + ".jpg";
+            return uuid + suffix;
         }
         String[] split = fileName.split("\\.");
         if (split.length <= 1) {
-            return uuid + ".jpg";
+            return uuid + suffix;
         }
         return uuid + "." + split[split.length - 1];
     }
 
+
 }

+ 22 - 1
app/src/main/java/com/datarecovery/master/utils/FilesSearch.java

@@ -8,6 +8,10 @@ import android.os.CancellationSignal;
 import android.os.PowerManager;
 import android.text.TextUtils;
 
+import androidx.databinding.BaseObservable;
+import androidx.databinding.Bindable;
+
+import com.datarecovery.master.BR;
 import com.datarecovery.master.utils.xfile.XFile;
 import com.datarecovery.master.utils.xfile.XFileSearch;
 
@@ -175,7 +179,7 @@ public class FilesSearch {
         return name.endsWith(".doc") || name.endsWith(".docx") || name.endsWith(".wps");
     }
 
-    public static class DocumentFile {
+    public static class DocumentFile extends BaseObservable {
         public static final int WORD = 1;
         public static final int EXCEL = 2;
         public static final int PPT = 3;
@@ -188,6 +192,8 @@ public class FilesSearch {
         private long size;
         private Uri uri;
         private String path;
+        private String sizeDescribe;
+        private boolean isCheck;
 
         public DocumentFile(XFile xFile, int category) {
             this.xFile = xFile;
@@ -208,6 +214,21 @@ public class FilesSearch {
                 this.path = xFile.getPath();
             } catch (Exception ignore) {
             }
+            this.sizeDescribe = FileUtil.formatShortBytes(this.size);
+        }
+
+        @Bindable
+        public boolean isCheck() {
+            return isCheck;
+        }
+
+        public void setCheck(boolean check) {
+            isCheck = check;
+            notifyPropertyChanged(BR.check);
+        }
+
+        public String getSizeDescribe() {
+            return sizeDescribe;
         }
 
         public int getCategory() {

+ 0 - 3
app/src/main/java/com/datarecovery/master/utils/ImageDeepDetector.java

@@ -533,11 +533,8 @@ public class ImageDeepDetector {
         private Uri uri;
         private String path;
         private int category;
-
         private String fileType;
-
         private boolean isCheck;
-
         private long lastModified;
 
 

BIN
app/src/main/res/drawable-xxhdpi/icon_hint.webp


BIN
app/src/main/res/drawable-xxhdpi/icon_video_recover.webp


+ 11 - 0
app/src/main/res/drawable/bg_video_preview_hint.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="15dp" />
+    <gradient
+        android:angle="-90"
+        android:endColor="#FAFCFF"
+        android:startColor="#CEE1FF" />
+    <stroke
+        android:width="0.6dp"
+        android:color="@color/white" />
+</shape>

+ 5 - 0
app/src/main/res/drawable/bg_video_recover_close.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="100dp" />
+    <solid android:color="#81B2FF" />
+</shape>

+ 1 - 0
app/src/main/res/layout/activity_image_recover.xml

@@ -50,6 +50,7 @@
 
         <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/ry_image_recover"
+            android:background="#F8F8F8"
             android:paddingHorizontal="@dimen/app_common_page_horizontal_padding"
             android:layout_width="match_parent"
             android:layout_height="0dp"

+ 127 - 0
app/src/main/res/layout/activity_video_recover.xml

@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <data>
+
+        <variable
+            name="videoRecoverViewModel"
+            type="com.datarecovery.master.module.videorecover.VideoRecoverViewModel" />
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.appcompat.widget.Toolbar
+            android:id="@+id/tool_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_constraintTop_toTopOf="parent"
+            app:navigationIcon="@drawable/icon_back">
+
+            <TextView
+                style="@style/Tool_Bar_Title_Txt"
+                android:text="@string/video_recover_title" />
+
+
+            <ImageView
+                imageDraw="@{videoRecoverViewModel.checkAll ? @drawable/icon_image_recover_checked : @drawable/icon_image_recover_uncheck}"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="right"
+                android:layout_marginEnd="@dimen/app_common_page_horizontal_padding"
+                android:background="?android:attr/selectableItemBackgroundBorderless"
+                android:onClick="@{()->videoRecoverViewModel.onCheckAllClick(!videoRecoverViewModel.checkAll)}"
+                tools:src="@drawable/icon_image_recover_uncheck" />
+        </androidx.appcompat.widget.Toolbar>
+
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/ry_video_recover"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:background="#F8F8F8"
+            android:paddingHorizontal="@dimen/app_common_page_horizontal_padding"
+            app:layout_constraintBottom_toTopOf="@+id/v_bottom"
+            app:layout_constraintTop_toBottomOf="@+id/tool_bar"
+            tools:itemCount="6"
+            tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
+            tools:listitem="@layout/item_data_video"
+            tools:spanCount="3" />
+
+        <Space
+            app:layout_constraintBottom_toTopOf="@+id/v_bottom"
+            android:id="@+id/space"
+            app:layout_constraintDimensionRatio="360:23"
+            android:layout_width="match_parent"
+            android:layout_height="0dp" />
+
+        <TextView
+            isGone="@{!videoRecoverViewModel.isShowHint}"
+            android:onClick="@{()-> videoRecoverViewModel.closeHintClick()}"
+            android:id="@+id/tv_hint"
+            android:text="@string/video_recover_hint"
+            android:padding="5dp"
+            android:textColor="#202020"
+            android:gravity="center_vertical"
+            android:textSize="12dp"
+            android:drawablePadding="4dp"
+            app:layout_constraintDimensionRatio="196:30"
+            android:background="@drawable/bg_video_preview_hint"
+            app:layout_constraintStart_toStartOf="parent"
+            android:layout_marginStart="@dimen/app_common_page_horizontal_padding"
+            app:layout_constraintBottom_toTopOf="@+id/space"
+            app:layout_constraintWidth_percent="0.5444444444444444"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:drawableStartCompat="@drawable/icon_hint" />
+
+        <TextView
+            isGone="@{!videoRecoverViewModel.isShowHint}"
+            app:layout_constraintWidth_percent="0.1055555555555556"
+            android:gravity="center"
+            android:background="@drawable/bg_video_recover_close"
+            android:layout_marginEnd="5dp"
+            app:layout_constraintTop_toTopOf="@+id/tv_hint"
+            app:layout_constraintBottom_toBottomOf="@+id/tv_hint"
+            app:layout_constraintEnd_toEndOf="@+id/tv_hint"
+            android:text="@string/video_hint_close"
+            android:textColor="@color/white"
+            android:textSize="11dp"
+            app:layout_constraintDimensionRatio="38:18"
+            android:layout_width="0dp"
+            android:layout_height="0dp" />
+
+
+        <View
+            android:id="@+id/v_bottom"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:background="@color/white"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintDimensionRatio="360:72" />
+
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:background="@{videoRecoverViewModel.selectedList.size() > 0 ? @drawable/bg_common_btn : @drawable/bg_common_disable_btn}"
+            android:gravity="center"
+            android:onClick="@{()->videoRecoverViewModel.onExportClick()}"
+            android:text="@{videoRecoverViewModel.selectedCountTxt}"
+            android:textColor="@color/white"
+            android:textSize="16sp"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toBottomOf="@id/v_bottom"
+            app:layout_constraintDimensionRatio="328:44"
+            app:layout_constraintEnd_toEndOf="@+id/v_bottom"
+            app:layout_constraintStart_toStartOf="@id/v_bottom"
+            app:layout_constraintTop_toTopOf="@id/v_bottom"
+            app:layout_constraintWidth_percent="0.9111111111111111"
+            tools:background="@drawable/bg_common_btn"
+            tools:text="立即导出" />
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 83 - 0
app/src/main/res/layout/item_data_video.xml

@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <data>
+
+        <variable
+            name="file"
+            type="com.datarecovery.master.utils.FilesSearch.DocumentFile" />
+
+        <variable
+            name="checkBoxClick"
+            type="android.view.View.OnClickListener" />
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <ImageView
+            imageUri="@{file.uri}"
+            radius="@{4}"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:scaleType="centerCrop"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:src="@color/colorPrimary" />
+
+        <View
+            android:id="@+id/bg_label"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:background="@drawable/bg_recover_label"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintDimensionRatio="104:20" />
+
+        <ImageView
+            android:id="@+id/iv_icon"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:src="@drawable/icon_video_recover"
+            app:layout_constraintBottom_toBottomOf="@+id/bg_label"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintLeft_toLeftOf="@+id/bg_label"
+            app:layout_constraintRight_toLeftOf="@+id/tv_size"
+            app:layout_constraintTop_toTopOf="@+id/bg_label"
+            app:layout_constraintWidth_percent="0.1153846153846154" />
+
+        <TextView
+            android:id="@+id/tv_size"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="2dp"
+            android:text="@{file.sizeDescribe}"
+            android:textColor="@color/white"
+            android:textSize="12dp"
+            app:layout_constraintBottom_toBottomOf="@+id/bg_label"
+            app:layout_constraintLeft_toRightOf="@+id/iv_icon"
+            app:layout_constraintRight_toRightOf="@+id/bg_label"
+            app:layout_constraintTop_toTopOf="@+id/bg_label"
+            tools:text="41.8kb" />
+
+        <ImageView
+            expandTouchSize="@{4}"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:onClick="@{checkBoxClick}"
+            android:src="@{file.check ? @drawable/icon_recover_checked : @drawable/icon_recover_un_check}"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.0952380952380952"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0952380952380952"
+            app:layout_constraintWidth_percent="0.1923076923076923"
+            tools:src="@drawable/icon_recover_un_check" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -107,4 +107,7 @@
     <string name="preview_img_create_time">创建时间</string>
     <string name="preview_img_file_type">文件类型</string>
     <string name="preview_img_file_size">文件大小</string>
+    <string name="video_recover_title">全部视频</string>
+    <string name="video_recover_hint">点击可预览视频</string>
+    <string name="video_hint_close">关闭</string>
 </resources>