Преглед на файлове

[1001685]增加图片恢复功能

zk преди 2 години
родител
ревизия
18420eb4ea
променени са 26 файла, в които са добавени 1006 реда и са изтрити 27 реда
  1. 3 0
      app/src/main/AndroidManifest.xml
  2. 197 0
      app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverActivity.java
  3. 30 0
      app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverPageAdapter.java
  4. 269 0
      app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverViewModel.java
  5. 98 0
      app/src/main/java/com/datarecovery/master/module/filerecover/fragment/FileRecoverAdapter.java
  6. 97 0
      app/src/main/java/com/datarecovery/master/module/filerecover/fragment/FileRecoverFragment.java
  7. 28 0
      app/src/main/java/com/datarecovery/master/module/filerecover/fragment/FileRecoverFragmentViewModel.java
  8. 1 14
      app/src/main/java/com/datarecovery/master/module/homepage/HomePageFragment.java
  9. 21 3
      app/src/main/java/com/datarecovery/master/module/homepage/HomePageViewModel.java
  10. 1 4
      app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverViewModel.java
  11. 1 0
      app/src/main/java/com/datarecovery/master/module/preview/PreviewActivity.java
  12. 0 1
      app/src/main/java/com/datarecovery/master/module/videorecover/VideoRecoverViewModel.java
  13. 1 0
      app/src/main/java/com/datarecovery/master/utils/DateUtil.java
  14. 18 0
      app/src/main/java/com/datarecovery/master/utils/FileUtil.java
  15. 11 1
      app/src/main/java/com/datarecovery/master/utils/FilesSearch.java
  16. 1 4
      app/src/main/java/com/datarecovery/master/utils/ImageDeepDetector.java
  17. BIN
      app/src/main/res/drawable-xxhdpi/icon_file_excel.webp
  18. BIN
      app/src/main/res/drawable-xxhdpi/icon_file_pdf.webp
  19. BIN
      app/src/main/res/drawable-xxhdpi/icon_file_ppt.webp
  20. BIN
      app/src/main/res/drawable-xxhdpi/icon_file_word.webp
  21. BIN
      app/src/main/res/drawable-xxhdpi/icon_recover_un_check_gray.webp
  22. 91 0
      app/src/main/res/layout/activity_file_recover.xml
  23. 15 0
      app/src/main/res/layout/fragment_file_recover_list.xml
  24. 88 0
      app/src/main/res/layout/item_data_file.xml
  25. 30 0
      app/src/main/res/layout/item_tab_file_recover.xml
  26. 5 0
      app/src/main/res/values/strings.xml

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

@@ -62,6 +62,9 @@
         <activity
             android:name=".module.videorecover.VideoRecoverActivity"
             android:screenOrientation="portrait" />
+        <activity
+            android:name=".module.filerecover.FileRecoverActivity"
+            android:screenOrientation="portrait" />
 
     </application>
 

+ 197 - 0
app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverActivity.java

@@ -0,0 +1,197 @@
+package com.datarecovery.master.module.filerecover;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.atmob.app.lib.base.BaseActivity;
+import com.datarecovery.master.R;
+import com.datarecovery.master.databinding.ActivityFileRecoverBinding;
+import com.datarecovery.master.databinding.ItemTabFileRecoverBinding;
+import com.datarecovery.master.dialog.CommonLoadingDialog;
+import com.datarecovery.master.dialog.CommonSureDialog;
+import com.datarecovery.master.dialog.ScanProgressDialog;
+import com.datarecovery.master.utils.BoxingUtil;
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+
+@AndroidEntryPoint
+public class FileRecoverActivity extends BaseActivity<ActivityFileRecoverBinding> {
+
+
+    private FileRecoverViewModel fileRecoverViewModel;
+
+    private FileRecoverPageAdapter fileRecoverPageAdapter;
+    private TabLayoutMediator tabLayoutMediator;
+
+    private CommonLoadingDialog loadingDialog;
+    private CommonSureDialog backDialog;
+    private ScanProgressDialog scanProgressDialog;
+    private ViewPager2.OnPageChangeCallback onPageChangeCallback;
+
+    public static void start(Context context) {
+        Intent intent = new Intent(context, FileRecoverActivity.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() {
+        fileRecoverViewModel.getShowLoadingEvent().observe(this, this::showLoadingDialog);
+        fileRecoverViewModel.getDetectedFinish().observe(this, o -> scanProgressDialog.detectedFinish());
+        fileRecoverViewModel.getShowScanDialogEvent().observe(this, this::showScanProgressDialog);
+    }
+
+    private void initView() {
+        binding.toolBar.setNavigationOnClickListener(v -> onBackPressed());
+        addTopStatusBarHeight(binding.toolBar);
+        initViewPager();
+        initTabLayout();
+    }
+
+    public void showLoadingDialog(Boolean show) {
+        if (BoxingUtil.boxing(show)) {
+            if (loadingDialog == null) {
+                loadingDialog = new CommonLoadingDialog(this);
+            }
+            loadingDialog.show();
+        } else {
+            if (loadingDialog != null) {
+                loadingDialog.dismiss();
+            }
+        }
+    }
+
+    public void showScanProgressDialog(Boolean show) {
+        if (BoxingUtil.boxing(show)) {
+            if (scanProgressDialog == null) {
+                scanProgressDialog = new ScanProgressDialog(this, fileRecoverViewModel.getTotalDetectedCount());
+                scanProgressDialog.setOnCancelListener(() -> fileRecoverViewModel.cancelScan());
+            }
+            scanProgressDialog.show();
+        } else {
+            if (scanProgressDialog != null) {
+                scanProgressDialog.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);
+    }
+
+    private void initViewPager() {
+        fileRecoverPageAdapter = new FileRecoverPageAdapter(this, fileRecoverViewModel.getTabTitle());
+        binding.fileViewPager.setAdapter(fileRecoverPageAdapter);
+        binding.fileViewPager.setOffscreenPageLimit(fileRecoverViewModel.getTabTitle().length);
+        onPageChangeCallback = new ViewPager2.OnPageChangeCallback() {
+            @Override
+            public void onPageSelected(int position) {
+                fileRecoverViewModel.setCheckPosition(position);
+            }
+        };
+        binding.fileViewPager.registerOnPageChangeCallback(onPageChangeCallback);
+    }
+
+    private void initTabLayout() {
+        tabLayoutMediator = new TabLayoutMediator(binding.tabLayout, binding.fileViewPager, true, true, (tab, position) -> {
+            int id = fileRecoverViewModel.getTabTitle()[position];
+            ItemTabFileRecoverBinding binding = ItemTabFileRecoverBinding.inflate(getLayoutInflater());
+            binding.setLifecycleOwner(this);
+            if (id != 0) {
+                binding.setTitle(id);
+            }
+            tab.view.setClipChildren(false);
+            tab.view.setClipToPadding(false);
+            tab.setCustomView(binding.getRoot());
+            tab.setTag(binding);
+        });
+        binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+            @Override
+            public void onTabSelected(TabLayout.Tab tab) {
+                ViewGroup parent = (ViewGroup) tab.view.getParent();
+                if (parent != null) {
+                    parent.setClipChildren(false);
+                    parent.setClipToPadding(false);
+                }
+                Object tag = tab.getTag();
+                ItemTabFileRecoverBinding binding = tag instanceof ItemTabFileRecoverBinding ? ((ItemTabFileRecoverBinding) tag) : null;
+                if (binding != null) {
+                    binding.setIsCheck(true);
+                }
+            }
+
+            @Override
+            public void onTabUnselected(TabLayout.Tab tab) {
+                Object tag = tab.getTag();
+                ItemTabFileRecoverBinding binding = tag instanceof ItemTabFileRecoverBinding ? ((ItemTabFileRecoverBinding) tag) : null;
+                if (binding != null) {
+                    binding.setIsCheck(false);
+                }
+            }
+
+            @Override
+            public void onTabReselected(TabLayout.Tab tab) {
+
+            }
+        });
+        tabLayoutMediator.attach();
+    }
+
+
+    @Override
+    protected void initViewModel() {
+        super.initViewModel();
+        fileRecoverViewModel = getViewModelProvider().get(FileRecoverViewModel.class);
+        binding.setFileRecoverViewModel(fileRecoverViewModel);
+    }
+
+    @Override
+    protected boolean shouldImmersion() {
+        return true;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        binding.fileViewPager.unregisterOnPageChangeCallback(onPageChangeCallback);
+    }
+}

+ 30 - 0
app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverPageAdapter.java

@@ -0,0 +1,30 @@
+package com.datarecovery.master.module.filerecover;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import com.datarecovery.master.module.filerecover.fragment.FileRecoverFragment;
+
+public class FileRecoverPageAdapter extends FragmentStateAdapter {
+
+    private final int[] pageList;
+
+
+    public FileRecoverPageAdapter(@NonNull FragmentActivity fragmentActivity, @NonNull int[] pageList) {
+        super(fragmentActivity);
+        this.pageList = pageList;
+    }
+
+    @NonNull
+    @Override
+    public Fragment createFragment(int position) {
+        return FileRecoverFragment.newInstance(pageList[position]);
+    }
+
+    @Override
+    public int getItemCount() {
+        return pageList.length;
+    }
+}

+ 269 - 0
app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverViewModel.java

@@ -0,0 +1,269 @@
+package com.datarecovery.master.module.filerecover;
+
+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.runtime.ContextUtil;
+import com.datarecovery.master.R;
+import com.datarecovery.master.utils.BoxingUtil;
+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.disposables.Disposable;
+import atmob.rxjava.utils.RxJavaUtil;
+import dagger.hilt.android.lifecycle.HiltViewModel;
+
+
+@HiltViewModel
+public class FileRecoverViewModel extends BaseViewModel {
+
+    private final long SCANNING_COUNTDOWN = 1000 * 60 * 6;
+
+    private final int[] tabTitle = {R.string.word, R.string.excel, R.string.ppt, R.string.pdf};
+
+    private final List<MutableLiveData<Boolean>> checkList = new ArrayList<>();
+    private final MutableLiveData<List<FilesSearch.DocumentFile>> detectedWordList = new MutableLiveData<>(new ArrayList<>());
+    private final MutableLiveData<List<FilesSearch.DocumentFile>> detectedExcelList = new MutableLiveData<>(new ArrayList<>());
+    private final MutableLiveData<List<FilesSearch.DocumentFile>> detectedPPTList = new MutableLiveData<>(new ArrayList<>());
+    private final MutableLiveData<List<FilesSearch.DocumentFile>> detectedPDFList = new MutableLiveData<>(new ArrayList<>());
+    private final MutableLiveData<List<FilesSearch.DocumentFile>> selectedList = new MutableLiveData<>(new ArrayList<>());
+
+    private final MutableLiveData<Integer> checkPosition = new MutableLiveData<>();
+    private final SingleLiveEvent<Boolean> showLoadingEvent = new SingleLiveEvent<>();
+    private final SingleLiveEvent<?> detectedFinish = new SingleLiveEvent<>();
+    private final SingleLiveEvent<Boolean> showScanDialogEvent = new SingleLiveEvent<>();
+    private int totalCount = 0;
+    private final MutableLiveData<Integer> totalDetectedCount = new MutableLiveData<>();
+    private final LiveData<String> selectedCountTxt;
+    private Disposable scanDisposable;
+
+    @Inject
+    public FileRecoverViewModel() {
+        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());
+        });
+        startFileScanning();
+        for (int i = 0; i < tabTitle.length; i++) checkList.add(new MutableLiveData<>(false));
+    }
+
+    public LiveData<Integer> getCheckPosition() {
+        return checkPosition;
+    }
+
+    public int[] getTabTitle() {
+        return tabTitle;
+    }
+
+    public LiveData<List<FilesSearch.DocumentFile>> getDetectedWordList() {
+        return detectedWordList;
+    }
+
+    public LiveData<List<FilesSearch.DocumentFile>> getDetectedExcelList() {
+        return detectedExcelList;
+    }
+
+    public LiveData<List<FilesSearch.DocumentFile>> getDetectedPPTList() {
+        return detectedPPTList;
+    }
+
+    public LiveData<List<FilesSearch.DocumentFile>> getDetectedPDFList() {
+        return detectedPDFList;
+    }
+
+
+    public LiveData<Boolean> getShowLoadingEvent() {
+        return showLoadingEvent;
+    }
+
+    public LiveData<?> getDetectedFinish() {
+        return detectedFinish;
+    }
+
+    public LiveData<Integer> getTotalDetectedCount() {
+        return totalDetectedCount;
+    }
+
+    public LiveData<List<FilesSearch.DocumentFile>> getSelectedList() {
+        return selectedList;
+    }
+
+    public LiveData<String> getSelectedCountTxt() {
+        return selectedCountTxt;
+    }
+
+
+    public LiveData<Boolean> getShowScanDialogEvent() {
+        return showScanDialogEvent;
+    }
+
+
+    public void setCheckPosition(int position) {
+        this.checkPosition.setValue(position);
+    }
+
+    private void startFileScanning() {
+        FilesSearch.search(ContextUtil.getContext(), FilesSearch.DocumentFile.WORD, FilesSearch.DocumentFile.EXCEL, FilesSearch.DocumentFile.PPT, FilesSearch.DocumentFile.PDF)
+                .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 || documentFiles.isEmpty()) {
+                            return;
+                        }
+                        totalCount += documentFiles.size();
+                        totalDetectedCount.setValue(totalCount);
+                        for (FilesSearch.DocumentFile item : documentFiles) {
+                            MutableLiveData<List<FilesSearch.DocumentFile>> liveData = null;
+                            if (item.getCategory() == FilesSearch.DocumentFile.WORD) {
+                                liveData = detectedWordList;
+                            } else if (item.getCategory() == FilesSearch.DocumentFile.EXCEL) {
+                                liveData = detectedExcelList;
+                            } else if (item.getCategory() == FilesSearch.DocumentFile.PPT) {
+                                liveData = detectedPPTList;
+                            } else if (item.getCategory() == FilesSearch.DocumentFile.PDF) {
+                                liveData = detectedPDFList;
+                            }
+                            if (liveData == null) {
+                                continue;
+                            }
+                            List<FilesSearch.DocumentFile> list = getList(liveData);
+                            list.add(0, item);
+                            liveData.setValue(list);
+                        }
+                    }
+
+                    @Override
+                    public void onError(Throwable t) {
+                        showScanDialogEvent.setValue(false);
+                    }
+
+                    @Override
+                    public void onComplete() {
+                        detectedFinish.call();
+                    }
+                });
+    }
+
+    public void cancelScan() {
+        if (scanDisposable != null) scanDisposable.dispose();
+    }
+
+
+    private List<FilesSearch.DocumentFile> getList(LiveData<List<FilesSearch.DocumentFile>> liveData) {
+        List<FilesSearch.DocumentFile> selectList = liveData.getValue();
+        if (selectList == null) {
+            selectList = new ArrayList<>();
+        }
+        return selectList;
+    }
+
+    public void onCheckAllClick(boolean isCheck) {
+        if (!scanDisposable.isDisposed()) {
+            return;
+        }
+        int index = BoxingUtil.boxing(checkPosition.getValue());
+        getCheckAll(index).setValue(isCheck);
+        int id = tabTitle[index];
+        MutableLiveData<List<FilesSearch.DocumentFile>> liveData = null;
+        if (id == R.string.word) {
+            liveData = detectedWordList;
+        } else if (id == R.string.excel) {
+            liveData = detectedExcelList;
+        } else if (id == R.string.ppt) {
+            liveData = detectedPPTList;
+        } else if (id == R.string.pdf) {
+            liveData = detectedPDFList;
+        }
+        if (liveData == null) {
+            return;
+        }
+        List<FilesSearch.DocumentFile> list = getList(liveData);
+        if (list.isEmpty()) {
+            return;
+        }
+        List<FilesSearch.DocumentFile> selectedList = getList(this.selectedList);
+        for (FilesSearch.DocumentFile item : list) {
+            if (item.isCheck() != isCheck) {
+                if (isCheck) {
+                    selectedList.add(item);
+                } else {
+                    selectedList.remove(item);
+                }
+            }
+            item.setCheck(isCheck);
+        }
+        this.selectedList.setValue(selectedList);
+    }
+
+    public MutableLiveData<Boolean> getCheckAll(int position) {
+        return checkList.get(position);
+    }
+
+    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_FILES, item.newInputStream(), FileUtil.getCreateFileName(item.getName(), ""));
+                item.setCheck(false);
+            }
+            return true;
+        }, o -> {
+            getCheckAll(BoxingUtil.boxing(checkPosition.getValue())).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 itemClick(FilesSearch.DocumentFile documentFile) {
+        if (documentFile == null) {
+            return;
+        }
+        documentFile.setCheck(!documentFile.isCheck());
+        List<FilesSearch.DocumentFile> list = getList(selectedList);
+        if (documentFile.isCheck()) {
+            list.add(documentFile);
+        } else {
+            list.remove(documentFile);
+        }
+        this.selectedList.setValue(list);
+    }
+}

+ 98 - 0
app/src/main/java/com/datarecovery/master/module/filerecover/fragment/FileRecoverAdapter.java

@@ -0,0 +1,98 @@
+package com.datarecovery.master.module.filerecover.fragment;
+
+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.ItemDataFileBinding;
+import com.datarecovery.master.utils.FileUtil;
+import com.datarecovery.master.utils.FilesSearch;
+
+import java.util.List;
+
+public class FileRecoverAdapter extends RecyclerView.Adapter<FileRecoverAdapter.ViewHolder> {
+
+    private final int fileType;
+    private List<FilesSearch.DocumentFile> list;
+
+    private final LifecycleOwner lifecycleOwner;
+
+
+    private int size;
+
+    private onItemClick onItemClick;
+
+    public FileRecoverAdapter(LifecycleOwner lifecycleOwner, int fileType) {
+        this.lifecycleOwner = lifecycleOwner;
+        this.fileType = fileType;
+    }
+
+
+    public void setOnItemClick(FileRecoverAdapter.onItemClick onItemClick) {
+        this.onItemClick = onItemClick;
+    }
+
+
+    public void submit(List<FilesSearch.DocumentFile> videoList) {
+        if (videoList == null) {
+            return;
+        }
+        int index;
+        if (list != null) {
+            index = videoList.size() - size;
+        } else {
+            index = videoList.size();
+        }
+        this.size = videoList.size();
+        this.list = videoList;
+        notifyItemRangeInserted(0, index);
+    }
+
+    @NonNull
+    @Override
+    public FileRecoverAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        Context context = parent.getContext();
+        LayoutInflater layoutInflater = LayoutInflater.from(context);
+        ItemDataFileBinding binding = ItemDataFileBinding.inflate(layoutInflater, parent, false);
+        return new FileRecoverAdapter.ViewHolder(binding);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull FileRecoverAdapter.ViewHolder holder, int position) {
+        holder.bind(list.get(position));
+    }
+
+    @Override
+    public int getItemCount() {
+        return (list == null) ? 0 : list.size();
+    }
+
+    public class ViewHolder extends RecyclerView.ViewHolder {
+
+        private final ItemDataFileBinding binding;
+
+        public ViewHolder(@NonNull ItemDataFileBinding binding) {
+            super(binding.getRoot());
+            this.binding = binding;
+            binding.setLifecycleOwner(lifecycleOwner);
+            binding.ivFileType.setImageDrawable(FileUtil.getFileRecoverIcon(fileType));
+            binding.getRoot().setOnClickListener(v -> {
+                if (onItemClick != null) {
+                    onItemClick.onCheck(binding.getFile());
+                }
+            });
+        }
+
+        public void bind(FilesSearch.DocumentFile documentFile) {
+            binding.setFile(documentFile);
+        }
+    }
+
+    public interface onItemClick {
+        void onCheck(FilesSearch.DocumentFile file);
+    }
+}

+ 97 - 0
app/src/main/java/com/datarecovery/master/module/filerecover/fragment/FileRecoverFragment.java

@@ -0,0 +1,97 @@
+package com.datarecovery.master.module.filerecover.fragment;
+
+
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.atmob.app.lib.base.BaseFragment;
+import com.datarecovery.master.R;
+import com.datarecovery.master.databinding.FragmentFileRecoverListBinding;
+import com.datarecovery.master.module.filerecover.FileRecoverViewModel;
+import com.datarecovery.master.utils.FilesSearch;
+import com.gyf.immersionbar.ImmersionBar;
+
+import java.util.List;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+
+@AndroidEntryPoint
+public class FileRecoverFragment extends BaseFragment<FragmentFileRecoverListBinding> {
+
+
+    public static final String ID = "ID";
+
+
+    private FileRecoverFragmentViewModel recoverFragmentViewModel;
+    private FileRecoverViewModel fileRecoverViewModel;
+
+    private FileRecoverAdapter fileRecoverAdapter;
+
+
+    public static FileRecoverFragment newInstance(int stringId) {
+        FileRecoverFragment fileRecoverFragment = new FileRecoverFragment();
+        Bundle bundle = new Bundle();
+        bundle.putInt(ID, stringId);
+        fileRecoverFragment.setArguments(bundle);
+        return fileRecoverFragment;
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        initData();
+        initView();
+        initObserver();
+    }
+
+    private void initView() {
+        fileRecoverAdapter = new FileRecoverAdapter(this, recoverFragmentViewModel.getId());
+        fileRecoverAdapter.setOnItemClick(file -> fileRecoverViewModel.itemClick(file));
+        binding.rvFileRecoverList.setLayoutManager(new LinearLayoutManager(getContext()));
+        binding.rvFileRecoverList.setAdapter(fileRecoverAdapter);
+    }
+
+    private void initObserver() {
+        LiveData<List<FilesSearch.DocumentFile>> detectedList = null;
+        if (recoverFragmentViewModel.getId() == R.string.word) {
+            detectedList = fileRecoverViewModel.getDetectedWordList();
+        } else if (recoverFragmentViewModel.getId() == R.string.excel) {
+            detectedList = fileRecoverViewModel.getDetectedExcelList();
+        } else if (recoverFragmentViewModel.getId() == R.string.ppt) {
+            detectedList = fileRecoverViewModel.getDetectedPPTList();
+        } else if (recoverFragmentViewModel.getId() == R.string.pdf) {
+            detectedList = fileRecoverViewModel.getDetectedPDFList();
+        }
+        if (detectedList != null) {
+            detectedList.observe(getViewLifecycleOwner(), list -> fileRecoverAdapter.submit(list));
+        }
+    }
+
+    @Override
+    protected void initViewModel() {
+        super.initViewModel();
+        recoverFragmentViewModel = getViewModelProvider().get(FileRecoverFragmentViewModel.class);
+        fileRecoverViewModel = getActivityViewModelProvider().get(FileRecoverViewModel.class);
+    }
+
+    private void initData() {
+        Bundle arguments = getArguments();
+        if (arguments == null) {
+            return;
+        }
+        recoverFragmentViewModel.setId(arguments.getInt(ID));
+    }
+
+
+    @Override
+    protected void configImmersion(@NonNull ImmersionBar immersionBar) {
+        immersionBar.statusBarDarkFont(true);
+    }
+
+}

+ 28 - 0
app/src/main/java/com/datarecovery/master/module/filerecover/fragment/FileRecoverFragmentViewModel.java

@@ -0,0 +1,28 @@
+package com.datarecovery.master.module.filerecover.fragment;
+
+
+import com.atmob.app.lib.base.BaseViewModel;
+
+
+import javax.inject.Inject;
+
+import dagger.hilt.android.lifecycle.HiltViewModel;
+
+@HiltViewModel
+public class FileRecoverFragmentViewModel extends BaseViewModel {
+
+    private int id;
+
+    @Inject
+    public FileRecoverFragmentViewModel() {
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+}

+ 1 - 14
app/src/main/java/com/datarecovery/master/module/homepage/HomePageFragment.java

@@ -11,7 +11,6 @@ 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;
@@ -91,19 +90,7 @@ public class HomePageFragment extends BaseFragment<FragmentHomePageBinding> {
     private void initOtherFunction() {
         otherFunctionAdapter = new OtherFunctionAdapter(homePageViewModel.getFunctionList());
         binding.ryOtherFunction.setAdapter(otherFunctionAdapter);
-        otherFunctionAdapter.setOnItemClick(bean -> {
-            switch (bean.getFunctionId()) {
-                case FunctionBean.AUDIO_RECOVERY:
-                    break;
-                case FunctionBean.FILE_RECOVERY:
-                    break;
-                case FunctionBean.IMG_CLEARING:
-                    break;
-                case FunctionBean.VIDEO_RECOVERY:
-                    VideoRecoverActivity.start(getActivity());
-                    break;
-            }
-        });
+        otherFunctionAdapter.setOnItemClick(bean -> homePageViewModel.clickItemFunction(bean));
     }
 
     private void initTextViewBanner() {

+ 21 - 3
app/src/main/java/com/datarecovery/master/module/homepage/HomePageViewModel.java

@@ -5,8 +5,9 @@ import com.atmob.app.lib.livedata.SingleLiveEvent;
 import com.atmob.common.runtime.ActivityUtil;
 import com.atmob.common.runtime.ContextUtil;
 import com.datarecovery.master.R;
-import com.datarecovery.master.data.repositories.AccountRepository;
+import com.datarecovery.master.module.filerecover.FileRecoverActivity;
 import com.datarecovery.master.module.imgrecover.ImageRecoverActivity;
+import com.datarecovery.master.module.videorecover.VideoRecoverActivity;
 import com.datarecovery.master.utils.PermissionUtil;
 import com.datarecovery.master.utils.xfile.XFilePermission;
 
@@ -17,9 +18,7 @@ import javax.inject.Inject;
 
 import atmob.reactivex.rxjava3.annotations.NonNull;
 import atmob.reactivex.rxjava3.core.Single;
-import atmob.reactivex.rxjava3.core.SingleEmitter;
 import atmob.reactivex.rxjava3.core.SingleObserver;
-import atmob.reactivex.rxjava3.core.SingleOnSubscribe;
 import atmob.reactivex.rxjava3.disposables.Disposable;
 import atmob.rxjava.utils.RxJavaUtil;
 import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -134,6 +133,25 @@ public class HomePageViewModel extends BaseViewModel {
                 });
     }
 
+    public void clickItemFunction(FunctionBean bean) {
+        if (bean == null) {
+            return;
+        }
+        // TODO: 2024/1/9 需要判断是否会员
+        switch (bean.getFunctionId()) {
+            case FunctionBean.AUDIO_RECOVERY:
+                break;
+            case FunctionBean.FILE_RECOVERY:
+                FileRecoverActivity.start(ActivityUtil.getTopActivity());
+                break;
+            case FunctionBean.IMG_CLEARING:
+                break;
+            case FunctionBean.VIDEO_RECOVERY:
+                VideoRecoverActivity.start(ActivityUtil.getTopActivity());
+                break;
+        }
+    }
+
     public interface NextStepCallback {
         void onNextStep();
     }

+ 1 - 4
app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverViewModel.java

@@ -227,10 +227,7 @@ public class ImageRecoverViewModel extends BaseViewModel {
                             } else {
                                 liveData = detectedOtherImg;
                             }
-                            List<ImageDeepDetector.ImageFile> list = liveData.getValue();
-                            if (list == null) {
-                                list = new ArrayList<>();
-                            }
+                            List<ImageDeepDetector.ImageFile> list = getList(liveData);
                             list.add(0, imageFile);
                             liveData.setValue(list);
                         }

+ 1 - 0
app/src/main/java/com/datarecovery/master/module/preview/PreviewActivity.java

@@ -112,6 +112,7 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
     }
 
     private void initView() {
+        binding.previewHeader.setNavigationOnClickListener(v -> finish());
         addTopStatusBarHeight(binding.previewHeader);
         binding.previewVideoContainer.setOnClickListener(v -> {
             if (isSurfaceCreated) {

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

@@ -11,7 +11,6 @@ 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.ImageDeepDetector;
 import com.datarecovery.master.utils.MediaStoreHelper;
 import com.datarecovery.master.utils.ToastUtil;
 

+ 1 - 0
app/src/main/java/com/datarecovery/master/utils/DateUtil.java

@@ -8,6 +8,7 @@ public class DateUtil {
 
 
     public static final String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";
+    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
 
     /**
      * @param timestamp

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

@@ -1,10 +1,13 @@
 package com.datarecovery.master.utils;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.text.format.Formatter;
 
 
 import com.atmob.common.runtime.ContextUtil;
+import com.datarecovery.master.R;
 
 import java.util.Locale;
 import java.util.UUID;
@@ -44,5 +47,20 @@ public class FileUtil {
         return uuid + "." + split[split.length - 1];
     }
 
+    @SuppressLint("UseCompatLoadingForDrawables")
+    public static Drawable getFileRecoverIcon(int fileTypeId) {
+        Context context = ContextUtil.getContext();
+        if (fileTypeId == R.string.word) {
+            return context.getDrawable(R.drawable.icon_file_word);
+        } else if (fileTypeId == R.string.excel) {
+            return context.getDrawable(R.drawable.icon_file_excel);
+        } else if (fileTypeId == R.string.ppt) {
+            return context.getDrawable(R.drawable.icon_file_ppt);
+        } else if (fileTypeId == R.string.pdf) {
+            return context.getDrawable(R.drawable.icon_file_pdf);
+        }
+        return null;
+    }
+
 
 }

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

@@ -192,9 +192,11 @@ public class FilesSearch {
         private long size;
         private Uri uri;
         private String path;
-        private String sizeDescribe;
+        private final String sizeDescribe;
         private boolean isCheck;
 
+        private long lastModified;
+
         public DocumentFile(XFile xFile, int category) {
             this.xFile = xFile;
             this.category = category;
@@ -214,9 +216,17 @@ public class FilesSearch {
                 this.path = xFile.getPath();
             } catch (Exception ignore) {
             }
+            try {
+                this.lastModified = xFile.lastModified();
+            } catch (Exception ignore) {
+            }
             this.sizeDescribe = FileUtil.formatShortBytes(this.size);
         }
 
+        public long getLastModified() {
+            return lastModified;
+        }
+
         @Bindable
         public boolean isCheck() {
             return isCheck;

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

@@ -634,10 +634,7 @@ public class ImageDeepDetector {
         }
 
         private boolean isWechat() {
-            if (!TextUtils.isEmpty(path) && path.contains("com.tencent.mm")) {
-                return true;
-            }
-            return false;
+            return !TextUtils.isEmpty(path) && path.contains("com.tencent.mm");
         }
 
         private boolean isGallery() {

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


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


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


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


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


+ 91 - 0
app/src/main/res/layout/activity_file_recover.xml

@@ -0,0 +1,91 @@
+<?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="fileRecoverViewModel"
+            type="com.datarecovery.master.module.filerecover.FileRecoverViewModel" />
+    </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/file_recover_title" />
+
+
+            <ImageView
+                imageDraw="@{fileRecoverViewModel.getCheckAll(fileRecoverViewModel.checkPosition) ? @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="@{()->fileRecoverViewModel.onCheckAllClick(!fileRecoverViewModel.getCheckAll(fileRecoverViewModel.checkPosition))}"
+                tools:src="@drawable/icon_image_recover_uncheck" />
+        </androidx.appcompat.widget.Toolbar>
+
+
+        <com.google.android.material.tabs.TabLayout
+            android:id="@+id/tab_layout"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            app:layout_constraintDimensionRatio="360:40"
+            app:layout_constraintTop_toBottomOf="@+id/tool_bar"
+            app:tabIndicator="@null"
+            app:tabIndicatorHeight="0dp"
+            app:tabRippleColor="@color/transparent" />
+
+        <androidx.viewpager2.widget.ViewPager2
+            android:id="@+id/file_view_pager"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:background="#F8F8F8"
+            app:layout_constraintBottom_toTopOf="@+id/v_bottom"
+            app:layout_constraintTop_toBottomOf="@+id/tab_layout"
+            tools:itemCount="6"
+            tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
+            tools:listitem="@layout/item_data_file"
+            tools:spanCount="3" />
+
+        <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="@{fileRecoverViewModel.selectedList.size() > 0 ? @drawable/bg_common_btn : @drawable/bg_common_disable_btn}"
+            android:gravity="center"
+            android:onClick="@{()->fileRecoverViewModel.onExportClick()}"
+            android:text="@{fileRecoverViewModel.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>

+ 15 - 0
app/src/main/res/layout/fragment_file_recover_list.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <data>
+
+    </data>
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_file_recover_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+
+    </androidx.recyclerview.widget.RecyclerView>
+</layout>

+ 88 - 0
app/src/main/res/layout/item_data_file.xml

@@ -0,0 +1,88 @@
+<?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" />
+
+
+        <import type="com.datarecovery.master.utils.DateUtil" />
+
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp">
+
+        <ImageView
+            android:id="@+id/iv_check"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="@dimen/app_common_page_horizontal_padding"
+            android:src="@{file.check ? @drawable/icon_recover_checked : @drawable/icon_recover_un_check_gray}"
+            app:layout_constraintBottom_toBottomOf="@+id/iv_file_type"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="@+id/iv_file_type"
+            app:layout_constraintWidth_percent="0.0666666666666667"
+            tools:src="@drawable/icon_recover_un_check_gray" />
+
+        <ImageView
+            android:id="@+id/iv_file_type"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="12dp"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintStart_toEndOf="@+id/iv_check"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_percent="0.1333333333333333"
+            tools:src="@drawable/icon_file_word" />
+
+        <TextView
+            android:id="@+id/tv_file_name"
+            android:layout_width="0dp"
+            app:layout_constraintBottom_toTopOf="@+id/tv_file_date"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="12dp"
+            android:layout_marginEnd="@dimen/app_common_page_horizontal_padding"
+            android:text="@{file.name}"
+            app:layout_constraintVertical_chainStyle="packed"
+            android:textColor="#202020"
+            android:textSize="14sp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/iv_file_type"
+            app:layout_constraintTop_toTopOf="@+id/iv_file_type"
+            tools:text="记科技以大额外的们.doc" />
+
+        <TextView
+            app:layout_constraintBottom_toBottomOf="@id/iv_file_type"
+            android:id="@+id/tv_file_date"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@{DateUtil.formatNormalDate(DateUtil.YYYY_MM_DD_HH_MM_SS,file.lastModified)}"
+            android:textColor="#A7A7A7"
+            android:textSize="12sp"
+            app:layout_constraintLeft_toLeftOf="@+id/tv_file_name"
+            app:layout_constraintTop_toBottomOf="@+id/tv_file_name"
+            tools:text="2023-12-20 14:12:55" />
+
+        <TextView
+            android:id="@+id/tv_file_size"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="4dp"
+            android:text="@{file.sizeDescribe}"
+            android:textColor="#A7A7A7"
+            android:textSize="12sp"
+            app:layout_constraintBaseline_toBaselineOf="@+id/tv_file_date"
+            app:layout_constraintStart_toEndOf="@+id/tv_file_date"
+            tools:text="265.37KB" />
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 30 - 0
app/src/main/res/layout/item_tab_file_recover.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <data>
+
+        <variable
+            name="title"
+            type="Integer" />
+
+        <variable
+            name="isCheck"
+            type="Boolean" />
+    </data>
+
+    <TextView
+        android:background="@{isCheck ? @drawable/bg_tab_selected : null}"
+        android:text="@{title}"
+        android:textColor="@{isCheck? @color/white : @color/tab_un_select_text_color}"
+        android:id="@+id/tab_title"
+        android:layout_width="wrap_content"
+        android:gravity="center"
+        android:textSize="14sp"
+        android:minWidth="54dp"
+        android:textStyle="bold"
+        android:minHeight="32dp"
+        android:padding="4dp"
+        android:layout_height="wrap_content">
+
+    </TextView>
+</layout>

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

@@ -110,4 +110,9 @@
     <string name="video_recover_title">全部视频</string>
     <string name="video_recover_hint">点击可预览视频</string>
     <string name="video_hint_close">关闭</string>
+    <string name="word">word</string>
+    <string name="excel">Excel</string>
+    <string name="ppt">PPT</string>
+    <string name="pdf">PDF</string>
+    <string name="file_recover_title">全部文件</string>
 </resources>