Browse Source

增加图片恢复界面

zk 2 years ago
parent
commit
a3f8f8f0b8
32 changed files with 1180 additions and 61 deletions
  1. 78 0
      app/src/main/java/com/datarecovery/master/dialog/ScanProgressDialog.java
  2. 93 0
      app/src/main/java/com/datarecovery/master/module/imgrecover/ImageItemAdapter.java
  3. 227 1
      app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverActivity.java
  4. 0 44
      app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverAdapter.java
  5. 216 1
      app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverViewModel.java
  6. 52 0
      app/src/main/java/com/datarecovery/master/module/imgrecover/ImageTitleAdapter.java
  7. 1 1
      app/src/main/java/com/datarecovery/master/module/splash/SplashActivity.java
  8. 53 0
      app/src/main/java/com/datarecovery/master/utils/GridLayoutItemDecoration.java
  9. 78 0
      app/src/main/java/com/datarecovery/master/utils/GridRecoverItemDecoration.java
  10. 18 1
      app/src/main/java/com/datarecovery/master/utils/ImageDeepDetector.java
  11. 32 0
      app/src/main/java/com/datarecovery/master/utils/StringUtil.java
  12. BIN
      app/src/main/res/drawable-xxhdpi/bg_example.webp
  13. BIN
      app/src/main/res/drawable-xxhdpi/bg_scan_progress_value.webp
  14. BIN
      app/src/main/res/drawable-xxhdpi/icon_example.webp
  15. BIN
      app/src/main/res/drawable-xxhdpi/icon_recover_checked.webp
  16. BIN
      app/src/main/res/drawable-xxhdpi/icon_recover_un_check.webp
  17. 0 0
      app/src/main/res/drawable/bg_common_disable_btn.xml
  18. 0 0
      app/src/main/res/drawable/bg_feedback_print.xml
  19. 17 0
      app/src/main/res/drawable/bg_progress_bar_horizontal_scan.xml
  20. 7 0
      app/src/main/res/drawable/bg_recover_label.xml
  21. 5 0
      app/src/main/res/drawable/bg_tab_selected.xml
  22. 36 4
      app/src/main/res/layout/activity_image_recover.xml
  23. 1 1
      app/src/main/res/layout/activity_login.xml
  24. 1 1
      app/src/main/res/layout/activity_user_feedback.xml
  25. 148 0
      app/src/main/res/layout/dialog_scan_progress.xml
  26. 1 1
      app/src/main/res/layout/fragment_example.xml
  27. 52 4
      app/src/main/res/layout/item_data_img.xml
  28. 30 0
      app/src/main/res/layout/item_img_recover_title.xml
  29. 13 0
      app/src/main/res/layout/item_tab_image_recover.xml
  30. 1 0
      app/src/main/res/values/colors.xml
  31. 19 2
      app/src/main/res/values/strings.xml
  32. 1 0
      app/src/main/res/values/style.xml

+ 78 - 0
app/src/main/java/com/datarecovery/master/dialog/ScanProgressDialog.java

@@ -0,0 +1,78 @@
+package com.datarecovery.master.dialog;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.atmob.app.lib.base.BaseDialog;
+import com.datarecovery.master.R;
+import com.datarecovery.master.databinding.DialogScanProgressBinding;
+
+
+@BaseDialog.FullScreen(height = false)
+public class ScanProgressDialog extends BaseDialog<DialogScanProgressBinding> {
+
+
+    private OnCancelListener onCancelListener;
+
+
+    public ScanProgressDialog(@NonNull Context context, LiveData<Integer> totalDetectedCount) {
+        super(context, R.style.Theme_Common_Dialog);
+        setCancelable(false);
+        binding.setCancelClick(v -> {
+            if (onCancelListener != null)
+                onCancelListener.onCancel();
+            dismiss();
+        });
+        binding.setDetectedCount(totalDetectedCount);
+    }
+
+    public void setOnCancelListener(OnCancelListener onCancelListener) {
+        this.onCancelListener = onCancelListener;
+    }
+
+    @Override
+    public void show() {
+        super.show();
+        startProgress();
+    }
+
+    private void startProgress() {
+        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 99);
+        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+        valueAnimator.addUpdateListener(animation -> {
+            float value = (float) valueAnimator.getAnimatedValue();
+            binding.setPercentageValue(value);
+        });
+        valueAnimator.setDuration(1000 * 60 * 3);
+        valueAnimator.start();
+    }
+
+    public void detectedFinish() {
+        ValueAnimator valueAnimator = ValueAnimator.ofFloat(binding.getPercentageValue(), 100);
+        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+        valueAnimator.addUpdateListener(animation -> {
+            float value = (float) valueAnimator.getAnimatedValue();
+            binding.setPercentageValue(value);
+        });
+        valueAnimator.setDuration(2000);
+        valueAnimator.start();
+        valueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                dismiss();
+            }
+        });
+    }
+
+
+    public interface OnCancelListener {
+        void onCancel();
+    }
+}

+ 93 - 0
app/src/main/java/com/datarecovery/master/module/imgrecover/ImageItemAdapter.java

@@ -0,0 +1,93 @@
+package com.datarecovery.master.module.imgrecover;
+
+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.utils.ImageDeepDetector;
+
+import java.util.List;
+
+public class ImageItemAdapter extends RecyclerView.Adapter<ImageItemAdapter.ViewHolder> {
+
+
+    private List<ImageDeepDetector.ImageFile> list;
+    private final LifecycleOwner lifecycleOwner;
+    private final int category;
+    private int size;
+
+    private onItemClick onItemClick;
+
+    public ImageItemAdapter(int category, LifecycleOwner lifecycleOwner) {
+        this.category = category;
+        this.lifecycleOwner = lifecycleOwner;
+    }
+
+    public void setOnItemClick(ImageItemAdapter.onItemClick onItemClick) {
+        this.onItemClick = onItemClick;
+    }
+
+    @NonNull
+    @Override
+    public ImageItemAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        Context context = parent.getContext();
+        LayoutInflater layoutInflater = LayoutInflater.from(context);
+        ItemDataImgBinding binding = ItemDataImgBinding.inflate(layoutInflater, parent, false);
+        return new ImageItemAdapter.ViewHolder(binding);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+        holder.bind(list.get(position));
+    }
+
+    public void submit(List<ImageDeepDetector.ImageFile> imageList) {
+        if (imageList == null) {
+            return;
+        }
+        int index = 0;
+        if (list != null) {
+            index = imageList.size() - size;
+        } else {
+            index = imageList.size();
+        }
+        this.size = imageList.size();
+        this.list = imageList;
+        notifyItemRangeInserted(0, index);
+    }
+
+    @Override
+    public int getItemCount() {
+        return (list == null) ? 0 : list.size();
+    }
+
+    public class ViewHolder extends RecyclerView.ViewHolder {
+
+        ItemDataImgBinding binding;
+
+        public ViewHolder(@NonNull ItemDataImgBinding binding) {
+            super(binding.getRoot());
+            this.binding = binding;
+            binding.setLifecycleOwner(lifecycleOwner);
+            binding.setCheckBoxClick(v -> {
+                ImageDeepDetector.ImageFile imageFile = binding.getFile();
+                if (onItemClick != null) {
+                    onItemClick.onClick(imageFile);
+                }
+            });
+        }
+
+        public void bind(ImageDeepDetector.ImageFile imageFile) {
+            binding.setFile(imageFile);
+        }
+    }
+
+    public interface onItemClick {
+        void onClick(ImageDeepDetector.ImageFile imageFile);
+    }
+}

+ 227 - 1
app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverActivity.java

@@ -1,26 +1,51 @@
 package com.datarecovery.master.module.imgrecover;
 
+import android.annotation.SuppressLint;
 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.ConcatAdapter;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.atmob.app.lib.base.BaseActivity;
+import com.datarecovery.master.R;
 import com.datarecovery.master.databinding.ActivityImageRecoverBinding;
+import com.datarecovery.master.databinding.ItemMainTabLayoutBinding;
+import com.datarecovery.master.databinding.ItemTabImageRecoverBinding;
+import com.datarecovery.master.dialog.CommonSureDialog;
+import com.datarecovery.master.dialog.ScanProgressDialog;
+import com.datarecovery.master.utils.BoxingUtil;
+import com.datarecovery.master.utils.GridLayoutItemDecoration;
+import com.datarecovery.master.utils.GridRecoverItemDecoration;
+import com.datarecovery.master.utils.ImageDeepDetector;
+import com.google.android.material.tabs.TabLayout;
 import com.gyf.immersionbar.ImmersionBar;
 
 import dagger.hilt.android.AndroidEntryPoint;
 
 
 @AndroidEntryPoint
-public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBinding> {
+public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBinding> implements ImageItemAdapter.onItemClick {
 
 
     ImageRecoverViewModel imageRecoverViewModel;
 
+    private final int[] tabTitle = {R.string.photo, R.string.wx, R.string.qq, R.string.other};
+
+    private CommonSureDialog backDialog;
+    private ScanProgressDialog scanProgressDialog;
+    private ImageItemAdapter photoAdapter;
+    private ImageItemAdapter wxAdapter;
+    private ImageItemAdapter qqAdapter;
+    private ImageItemAdapter otherAdapter;
+    private GridLayoutManager gridLayoutManager;
 
     public static void start(Context context) {
         Intent intent = new Intent(context, ImageRecoverActivity.class);
@@ -38,11 +63,176 @@ public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBindi
     }
 
     private void initView() {
+        binding.toolBar.setNavigationOnClickListener(v -> onBackPressed());
         addTopStatusBarHeight(binding.toolBar);
+        initRecycleView();
+        initTabLayout();
+    }
+
+    private void initRecycleView() {
+        ImageTitleAdapter photoTitleAdapter = new ImageTitleAdapter(this, imageRecoverViewModel.getDetectedPhotoTitle());
+        photoAdapter = new ImageItemAdapter(ImageDeepDetector.ImageFile.CATEGORY_GALLERY, this);
+        photoAdapter.setOnItemClick(this);
+
+        ImageTitleAdapter wxTitleAdapter = new ImageTitleAdapter(this, imageRecoverViewModel.getDetectedWxTitle());
+        wxAdapter = new ImageItemAdapter(ImageDeepDetector.ImageFile.CATEGORY_WECHAT, this);
+        wxAdapter.setOnItemClick(this);
+
+        ImageTitleAdapter qqTitleAdapter = new ImageTitleAdapter(this, imageRecoverViewModel.getDetectedQQTitle());
+        qqAdapter = new ImageItemAdapter(ImageDeepDetector.ImageFile.CATEGORY_QQ, this);
+        qqAdapter.setOnItemClick(this);
+
+        ImageTitleAdapter otherTitleAdapter = new ImageTitleAdapter(this, imageRecoverViewModel.getDetectedOtherTitle());
+        otherAdapter = new ImageItemAdapter(ImageDeepDetector.ImageFile.CATEGORY_OTHER, this);
+        otherAdapter.setOnItemClick(this);
+
+        ConcatAdapter concatAdapter = new ConcatAdapter(
+                photoTitleAdapter,
+                photoAdapter,
+                wxTitleAdapter,
+                wxAdapter,
+                qqTitleAdapter,
+                qqAdapter,
+                otherTitleAdapter,
+                otherAdapter
+        );
+        binding.ryImageRecover.setAdapter(concatAdapter);
+        gridLayoutManager = new GridLayoutManager(this, 3);
+        gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
+            @Override
+            public int getSpanSize(int position) {
+                if (position == getPhotoPosition() || position == getWxPosition() || position == getQQPosition() || position == getOtherPosition()) {
+                    return 3;
+                }
+                return 1;
+            }
+        });
+        binding.ryImageRecover.addItemDecoration(new GridRecoverItemDecoration(3, 0.0282222222222222f, 0.0202222222222222f));
+        binding.ryImageRecover.setLayoutManager(gridLayoutManager);
+
+        RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
+
+            @Override
+            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                int firstVisibleItem = gridLayoutManager.findFirstVisibleItemPosition();
+                int lastVisibleItem = gridLayoutManager.findLastVisibleItemPosition();
+
+                int photoPosition = getPhotoPosition();
+                int wxPosition = getWxPosition();
+                int qqPosition = getQQPosition();
+                int otherPosition = getOtherPosition();
+                if (firstVisibleItem == photoPosition || (firstVisibleItem >= photoPosition && lastVisibleItem < wxPosition)) {
+                    binding.tabLayout.selectTab(binding.tabLayout.getTabAt(0));
+                } else if (firstVisibleItem == wxPosition || (firstVisibleItem >= wxPosition && lastVisibleItem < qqPosition)) {
+                    binding.tabLayout.selectTab(binding.tabLayout.getTabAt(1));
+                } else if (firstVisibleItem == qqPosition || (firstVisibleItem >= qqPosition && lastVisibleItem < otherPosition)) {
+                    binding.tabLayout.selectTab(binding.tabLayout.getTabAt(2));
+                } else if (firstVisibleItem >= otherPosition) {
+                    binding.tabLayout.selectTab(binding.tabLayout.getTabAt(3));
+                }
+            }
+        };
+        binding.ryImageRecover.addOnScrollListener(onScrollListener);
+    }
+
+    public int getPhotoPosition() {
+        return 0;
+    }
+
+    public int getWxPosition() {
+        return getPhotoPosition() + photoAdapter.getItemCount() + 1;
+    }
+
+    public int getQQPosition() {
+        return getWxPosition() + wxAdapter.getItemCount() + 1;
+    }
+
+    public int getOtherPosition() {
+        return getQQPosition() + qqAdapter.getItemCount() + 1;
+    }
+
+    @Override
+    public void onClick(ImageDeepDetector.ImageFile imageFile) {
+        if (imageFile == null) {
+            return;
+        }
+        imageRecoverViewModel.setItemCheck(imageFile);
+    }
+
+    private void initTabLayout() {
+        binding.tabLayout.removeAllTabs();
+        binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+            @SuppressLint("UseCompatLoadingForDrawables")
+            @Override
+            public void onTabSelected(TabLayout.Tab tab) {
+                if (tab.getCustomView() == null) {
+                    return;
+                }
+                ItemTabImageRecoverBinding itemBinding = ItemTabImageRecoverBinding.bind(tab.getCustomView());
+                itemBinding.tabTitle.setTextColor(getResources().getColor(com.atmob.app.base.R.color.white));
+                itemBinding.tabTitle.setBackground(getResources().getDrawable(R.drawable.bg_tab_selected));
+            }
+
+            @Override
+            public void onTabUnselected(TabLayout.Tab tab) {
+                if (tab.getCustomView() == null) {
+                    return;
+                }
+                ItemTabImageRecoverBinding itemBinding = ItemTabImageRecoverBinding.bind(tab.getCustomView());
+                itemBinding.tabTitle.setTextColor(getResources().getColor(R.color.tab_un_select_text_color));
+                itemBinding.tabTitle.setBackground(null);
+            }
+
+            @Override
+            public void onTabReselected(TabLayout.Tab tab) {
+
+            }
+        });
+        for (int i = 0; i < tabTitle.length; i++) {
+            TabLayout.Tab tab = binding.tabLayout.newTab();
+            ItemTabImageRecoverBinding tabLayoutBinding = ItemTabImageRecoverBinding.inflate(getLayoutInflater());
+            int finalI = i;
+            tabLayoutBinding.getRoot().setOnClickListener(v -> {
+                switch (finalI) {
+                    case 0:
+                        gridLayoutManager.scrollToPositionWithOffset(getPhotoPosition(), 0);
+                        break;
+                    case 1:
+                        gridLayoutManager.scrollToPositionWithOffset(getWxPosition(), 0);
+                        break;
+                    case 2:
+                        gridLayoutManager.scrollToPositionWithOffset(getQQPosition(), 0);
+                        break;
+                    case 3:
+                        gridLayoutManager.scrollToPositionWithOffset(getOtherPosition(), 0);
+                        break;
+                }
+                binding.tabLayout.selectTab(binding.tabLayout.getTabAt(finalI));
+            });
+            tabLayoutBinding.tabTitle.setText(tabTitle[i]);
+            tab.setCustomView(tabLayoutBinding.getRoot());
+            binding.tabLayout.addTab(tab);
+        }
     }
 
     private void initObserver() {
         imageRecoverViewModel.getFinishEvent().observe(this, o -> finish());
+        imageRecoverViewModel.getShowScanDialogEvent().observe(this, this::showScanProgressDialog);
+        imageRecoverViewModel.getDetectedPhotoImg().observe(this, list -> photoAdapter.submit(list));
+        imageRecoverViewModel.getDetectedWxImg().observe(this, list -> wxAdapter.submit(list));
+        imageRecoverViewModel.getDetectedQQImg().observe(this, list -> qqAdapter.submit(list));
+        imageRecoverViewModel.getDetectedOtherImg().observe(this, list -> otherAdapter.submit(list));
+        imageRecoverViewModel.getDetectedFinish().observe(this, o -> scanProgressDialog.detectedFinish());
+    }
+
+    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
@@ -50,6 +240,41 @@ public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBindi
         immersionBar.statusBarDarkFont(true);
     }
 
+    public void showScanProgressDialog(Boolean show) {
+        if (BoxingUtil.boxing(show)) {
+            if (scanProgressDialog == null) {
+                scanProgressDialog = new ScanProgressDialog(this, imageRecoverViewModel.getTotalDetectedCount());
+                scanProgressDialog.setOnCancelListener(() -> imageRecoverViewModel.cancelScan());
+            }
+            scanProgressDialog.show();
+        } else {
+            if (scanProgressDialog != null) {
+                scanProgressDialog.dismiss();
+            }
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        showBackDialog();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            onBackPressed();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        binding.tabLayout.clearOnTabSelectedListeners();
+    }
+
     @Override
     protected boolean shouldImmersion() {
         return true;
@@ -61,4 +286,5 @@ public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBindi
         imageRecoverViewModel = getViewModelProvider().get(ImageRecoverViewModel.class);
         binding.setImageRecoverViewModel(imageRecoverViewModel);
     }
+
 }

+ 0 - 44
app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverAdapter.java

@@ -1,44 +0,0 @@
-package com.datarecovery.master.module.imgrecover;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.datarecovery.master.databinding.ItemDataImgBinding;
-
-public class ImageRecoverAdapter extends RecyclerView.Adapter<ImageRecoverAdapter.ViewHolder> {
-
-
-    public ImageRecoverAdapter() {
-    }
-
-    @NonNull
-    @Override
-    public ImageRecoverAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        return null;
-    }
-
-    @Override
-    public void onBindViewHolder(@NonNull ImageRecoverAdapter.ViewHolder holder, int position) {
-
-    }
-
-    @Override
-    public int getItemCount() {
-        return 0;
-    }
-
-    public class ViewHolder extends RecyclerView.ViewHolder {
-
-        private ItemDataImgBinding binding;
-
-        public ViewHolder(@NonNull ItemDataImgBinding binding) {
-            super(binding.getRoot());
-            this.binding = binding;
-        }
-
-
-    }
-}

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

@@ -2,14 +2,26 @@ package com.datarecovery.master.module.imgrecover;
 
 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.ImageDeepDetector;
+
+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.core.FlowableSubscriber;
+import atmob.reactivex.rxjava3.disposables.Disposable;
 import dagger.hilt.android.lifecycle.HiltViewModel;
 
 
@@ -17,14 +29,87 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
 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 LiveData<String> detectedPhotoTitle;
+    private LiveData<String> detectedWxTitle;
+    private LiveData<String> detectedQQTitle;
+    private LiveData<String> detectedOtherTitle;
+
+    private final SingleLiveEvent<?> detectedFinish = new SingleLiveEvent<>();
     private final SingleLiveEvent<?> finishEvent = new SingleLiveEvent<>();
+    private final SingleLiveEvent<Boolean> showScanDialogEvent = new SingleLiveEvent<>();
     private final MutableLiveData<String> barTitle = new MutableLiveData<>();
     private final MutableLiveData<Boolean> checkAll = new MutableLiveData<>();
+    //总探测到的图片数量
+    private int totalCount = 0;
+    private final MutableLiveData<Integer> totalDetectedCount = new MutableLiveData<>();
+    private final MutableLiveData<List<ImageDeepDetector.ImageFile>> selectedList = new MutableLiveData<>();
+    private LiveData<String> selectedCountTxt;
+    private Disposable scanDisposable;
 
 
     @Inject
     public ImageRecoverViewModel() {
         barTitle.setValue(ContextUtil.getContext().getString(R.string.iamge_recover_all));
+        startImageScanning();
+        initLiveData();
+    }
+
+    public LiveData<?> getDetectedFinish() {
+        return detectedFinish;
+    }
+
+    public LiveData<Integer> getTotalDetectedCount() {
+        return totalDetectedCount;
+    }
+
+    public LiveData<String> getSelectedCountTxt() {
+        return selectedCountTxt;
+    }
+
+    public LiveData<List<ImageDeepDetector.ImageFile>> getSelectedList() {
+        return selectedList;
+    }
+
+    public LiveData<List<ImageDeepDetector.ImageFile>> getDetectedPhotoImg() {
+        return detectedPhotoImg;
+    }
+
+    public LiveData<List<ImageDeepDetector.ImageFile>> getDetectedWxImg() {
+        return detectedWxImg;
+    }
+
+    public LiveData<List<ImageDeepDetector.ImageFile>> getDetectedQQImg() {
+        return detectedQQImg;
+    }
+
+    public LiveData<List<ImageDeepDetector.ImageFile>> getDetectedOtherImg() {
+        return detectedOtherImg;
+    }
+
+    public LiveData<String> getDetectedPhotoTitle() {
+        return detectedPhotoTitle;
+    }
+
+    public LiveData<String> getDetectedWxTitle() {
+        return detectedWxTitle;
+    }
+
+    public LiveData<String> getDetectedQQTitle() {
+        return detectedQQTitle;
+    }
+
+    public LiveData<String> getDetectedOtherTitle() {
+        return detectedOtherTitle;
+    }
+
+    public LiveData<Boolean> getShowScanDialogEvent() {
+        return showScanDialogEvent;
     }
 
     public LiveData<?> getFinishEvent() {
@@ -39,7 +124,137 @@ public class ImageRecoverViewModel extends BaseViewModel {
         return barTitle;
     }
 
+
+    private void initLiveData() {
+        detectedPhotoTitle = Transformations.map(detectedPhotoImg, list ->
+                ContextUtil.getContext().getString(R.string.photo_count, (list == null || list.isEmpty()) ? 0 : list.size()));
+        detectedWxTitle = Transformations.map(detectedWxImg, list ->
+                ContextUtil.getContext().getString(R.string.wx_count, (list == null || list.isEmpty()) ? 0 : list.size()));
+        detectedQQTitle = Transformations.map(detectedQQImg, list ->
+                ContextUtil.getContext().getString(R.string.qq_count, (list == null || list.isEmpty()) ? 0 : list.size()));
+        detectedOtherTitle = Transformations.map(detectedOtherImg, list ->
+                ContextUtil.getContext().getString(R.string.other_count, (list == null || list.isEmpty()) ? 0 : list.size()));
+
+        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());
+        });
+    }
+
     public void onCheckAllClick(boolean isCheck) {
-        checkAll.setValue(!isCheck);
+        if (!scanDisposable.isDisposed()) {
+            return;
+        }
+        checkAll.setValue(isCheck);
+        List<ImageDeepDetector.ImageFile> selectList = getList(selectedList);
+        setListCheck(detectedPhotoImg.getValue(), isCheck);
+        setListCheck(detectedWxImg.getValue(), isCheck);
+        setListCheck(detectedQQImg.getValue(), isCheck);
+        setListCheck(detectedOtherImg.getValue(), isCheck);
+        if (isCheck) {
+            selectList.clear();
+            selectList.addAll(getList(detectedPhotoImg));
+            selectList.addAll(getList(detectedWxImg));
+            selectList.addAll(getList(detectedQQImg));
+            selectList.addAll(getList(detectedOtherImg));
+        } else {
+            selectList.clear();
+        }
+        selectedList.setValue(selectList);
+    }
+
+    private void setListCheck(List<ImageDeepDetector.ImageFile> list, boolean isCheck) {
+        if (list == null || list.isEmpty()) {
+            return;
+        }
+        for (ImageDeepDetector.ImageFile imageFile : list) {
+            imageFile.setCheck(isCheck);
+        }
+    }
+
+
+    private List<ImageDeepDetector.ImageFile> getList(LiveData<List<ImageDeepDetector.ImageFile>> liveData) {
+        List<ImageDeepDetector.ImageFile> selectList = liveData.getValue();
+        if (selectList == null) {
+            selectList = new ArrayList<>();
+        }
+        return selectList;
+    }
+
+    private void startImageScanning() {
+        ImageDeepDetector.detect(ContextUtil.getContext())
+                .take(SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
+                .subscribeOn(AndroidSchedulers.mainThread())
+                .subscribe(new FlowableSubscriber<List<ImageDeepDetector.ImageFile>>() {
+                    @Override
+                    public void onSubscribe(@NonNull Subscription s) {
+                        s.request(Integer.MAX_VALUE);
+                        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);
+                    }
+
+                    @Override
+                    public void onNext(List<ImageDeepDetector.ImageFile> imageFiles) {
+                        if (imageFiles == null) {
+                            return;
+                        }
+                        totalCount += imageFiles.size();
+                        totalDetectedCount.setValue(totalCount);
+                        for (ImageDeepDetector.ImageFile imageFile : imageFiles) {
+                            MutableLiveData<List<ImageDeepDetector.ImageFile>> liveData = null;
+                            if (imageFile.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_GALLERY) {
+                                liveData = detectedPhotoImg;
+                            } else if (imageFile.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_WECHAT) {
+                                liveData = detectedWxImg;
+                            } else if (imageFile.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_QQ) {
+                                liveData = detectedQQImg;
+                            } else {
+                                liveData = detectedOtherImg;
+                            }
+                            List<ImageDeepDetector.ImageFile> list = liveData.getValue();
+                            if (list == null) {
+                                list = new ArrayList<>();
+                            }
+                            list.add(0, imageFile);
+                            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();
+    }
+
+    public void setItemCheck(@NonNull ImageDeepDetector.ImageFile imageFile) {
+        imageFile.setCheck(!imageFile.isCheck());
+        List<ImageDeepDetector.ImageFile> list = getList(this.selectedList);
+        if (imageFile.isCheck()) {
+            list.add(imageFile);
+        } else {
+            list.remove(imageFile);
+        }
+        selectedList.setValue(list);
     }
 }

+ 52 - 0
app/src/main/java/com/datarecovery/master/module/imgrecover/ImageTitleAdapter.java

@@ -0,0 +1,52 @@
+package com.datarecovery.master.module.imgrecover;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.datarecovery.master.databinding.ItemImgRecoverTitleBinding;
+
+public class ImageTitleAdapter extends RecyclerView.Adapter<ImageTitleAdapter.ViewHolder> {
+
+
+    private final LiveData<String> countLiveData;
+    private final LifecycleOwner lifecycleOwner;
+
+    public ImageTitleAdapter(LifecycleOwner lifecycleOwner, LiveData<String> countLiveData) {
+        this.lifecycleOwner = lifecycleOwner;
+        this.countLiveData = countLiveData;
+    }
+
+    @NonNull
+    @Override
+    public ImageTitleAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+        ItemImgRecoverTitleBinding binding = ItemImgRecoverTitleBinding.inflate(layoutInflater, parent, false);
+        return new ImageTitleAdapter.ViewHolder(binding);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ImageTitleAdapter.ViewHolder holder, int position) {
+
+    }
+
+    @Override
+    public int getItemCount() {
+        return 1;
+    }
+
+    public class ViewHolder extends RecyclerView.ViewHolder {
+
+        public ViewHolder(@NonNull ItemImgRecoverTitleBinding binding) {
+            super(binding.getRoot());
+            binding.setLifecycleOwner(lifecycleOwner);
+            binding.setTitle(countLiveData);
+        }
+
+
+    }
+}

+ 1 - 1
app/src/main/java/com/datarecovery/master/module/splash/SplashActivity.java

@@ -20,7 +20,7 @@ import com.gyf.immersionbar.ImmersionBar;
 public class SplashActivity extends BaseActivity<ActivitySplashBinding> {
 
     private AgreementDialog agreementDialog;
-    private final int delayMillis = 3000;
+    private final int delayMillis = 1000;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {

+ 53 - 0
app/src/main/java/com/datarecovery/master/utils/GridLayoutItemDecoration.java

@@ -0,0 +1,53 @@
+package com.datarecovery.master.utils;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class GridLayoutItemDecoration extends RecyclerView.ItemDecoration {
+
+    private final int spanCount;
+    private final float horizontalGapPercent;
+    private final float verticalGapPercent;
+
+    private boolean showLastVertical = true;
+
+    public GridLayoutItemDecoration(int spanCount, float horizontalGapPercent, float verticalGapPercent) {
+        this(spanCount, horizontalGapPercent, verticalGapPercent, true);
+    }
+
+    public GridLayoutItemDecoration(int spanCount, float horizontalGapPercent, float verticalGapPercent, boolean showLastVertical) {
+        this.spanCount = spanCount;
+        this.horizontalGapPercent = horizontalGapPercent;
+        this.verticalGapPercent = verticalGapPercent;
+        this.showLastVertical = showLastVertical;
+    }
+
+    @Override
+    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+        super.getItemOffsets(outRect, view, parent, state);
+        int horizontalGap = (int) (parent.getMeasuredWidth() * horizontalGapPercent);
+        int verticalGap = (int) (parent.getMeasuredWidth() * verticalGapPercent);
+        int singleGap = horizontalGap * (spanCount - 1) / spanCount;
+        int childAdapterPosition = parent.getChildAdapterPosition(view);
+        int i = childAdapterPosition % spanCount;
+        outRect.top = 0;
+        if (!showLastVertical && parent.getAdapter() != null && parent.getAdapter().getItemCount() - spanCount <= childAdapterPosition) {
+            outRect.bottom = 0;
+        } else {
+            outRect.bottom = verticalGap;
+        }
+        outRect.left = 0;
+        outRect.right = 0;
+        if (i == 0) {
+            outRect.right = singleGap;
+        } else if (i == spanCount - 1) {
+            outRect.left = singleGap;
+        } else {
+            outRect.left = singleGap / 2;
+            outRect.right = singleGap / 2;
+        }
+    }
+}

+ 78 - 0
app/src/main/java/com/datarecovery/master/utils/GridRecoverItemDecoration.java

@@ -0,0 +1,78 @@
+package com.datarecovery.master.utils;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class GridRecoverItemDecoration extends RecyclerView.ItemDecoration {
+
+    private final int spanCount;
+    private final float horizontalGapPercent;
+    private final float verticalGapPercent;
+
+    private boolean showLastVertical = true;
+
+    public GridRecoverItemDecoration(int spanCount, float horizontalGapPercent, float verticalGapPercent) {
+        this(spanCount, horizontalGapPercent, verticalGapPercent, true);
+    }
+
+    public GridRecoverItemDecoration(int spanCount, float horizontalGapPercent, float verticalGapPercent, boolean showLastVertical) {
+        this.spanCount = spanCount;
+        this.horizontalGapPercent = horizontalGapPercent;
+        this.verticalGapPercent = verticalGapPercent;
+        this.showLastVertical = showLastVertical;
+    }
+
+    @Override
+    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+        super.getItemOffsets(outRect, view, parent, state);
+        int horizontalGap = (int) (parent.getMeasuredWidth() * horizontalGapPercent);
+        int verticalGap = (int) (parent.getMeasuredWidth() * verticalGapPercent);
+        int spanSize = ((GridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanSize();
+        int singleGap = horizontalGap * (spanCount - 1) / spanCount;
+        int childAdapterPosition = parent.getChildAdapterPosition(view);
+        int i = childAdapterPosition % spanSize;
+        outRect.top = 0;
+        if (spanSize == 1) {
+            if (!showLastVertical && parent.getAdapter() != null && parent.getAdapter().getItemCount() - spanSize <= childAdapterPosition) {
+                outRect.bottom = 0;
+            } else {
+                outRect.bottom = verticalGap;
+            }
+            outRect.left = 0;
+            outRect.right = 0;
+            if (i == 0) {
+                outRect.right = singleGap;
+            } else if (i == spanCount - 1) {
+                outRect.left = singleGap;
+            } else {
+                outRect.left = singleGap / 2;
+                outRect.right = singleGap / 2;
+            }
+        } else {
+            outRect.left = 0;
+            outRect.right = 0;
+            outRect.bottom = 0;
+        }
+//        outRect.top = 0;
+//        if (!showLastVertical && parent.getAdapter() != null && parent.getAdapter().getItemCount() - spanSize <= childAdapterPosition) {
+//            outRect.bottom = 0;
+//        } else {
+//            outRect.bottom = verticalGap;
+//        }
+//        outRect.left = 0;
+//        outRect.right = 0;
+//        if (i == 0) {
+//            outRect.right = singleGap;
+//        } else if (i == spanCount - 1) {
+//            outRect.left = singleGap;
+//        } else {
+//            outRect.left = singleGap / 2;
+//            outRect.right = singleGap / 2;
+//        }
+    }
+}

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

@@ -11,8 +11,12 @@ import android.os.Environment;
 import android.os.PowerManager;
 import android.text.TextUtils;
 
+import androidx.databinding.BaseObservable;
+import androidx.databinding.Bindable;
+
 import com.atmob.common.crypto.CryptoUtils;
 import com.atmob.common.runtime.ContextUtil;
+import com.datarecovery.master.BR;
 import com.datarecovery.master.utils.xfile.XFile;
 import com.datarecovery.master.utils.xfile.XFileSearch;
 import com.datarecovery.master.utils.xfile.XPathFile;
@@ -515,7 +519,7 @@ public class ImageDeepDetector {
         dir.delete();
     }
 
-    public static class ImageFile {
+    public static class ImageFile extends BaseObservable {
         public static int CATEGORY_UNKNOWN = -1;
         public static int CATEGORY_OTHER = 0;
         public static int CATEGORY_QQ = 1;
@@ -528,6 +532,9 @@ public class ImageDeepDetector {
         private String path;
         private int category;
 
+        private boolean isCheck;
+
+
         public ImageFile(XFile xFile) {
             this(xFile, CATEGORY_UNKNOWN);
         }
@@ -565,6 +572,16 @@ public class ImageDeepDetector {
             return uri;
         }
 
+        @Bindable
+        public boolean isCheck() {
+            return isCheck;
+        }
+
+        public void setCheck(boolean check) {
+            isCheck = check;
+            notifyPropertyChanged(BR.check);
+        }
+
         public InputStream newInputStream() throws Exception {
             return xFile.newInputStream();
         }

+ 32 - 0
app/src/main/java/com/datarecovery/master/utils/StringUtil.java

@@ -0,0 +1,32 @@
+package com.datarecovery.master.utils;
+
+import android.content.Context;
+import android.text.format.Formatter;
+
+import com.atmob.common.runtime.ContextUtil;
+
+import java.util.Locale;
+
+public class StringUtil {
+
+    private StringUtil() {
+
+    }
+
+    public static String formatShortBytes(long bytes) {
+        Context context = ContextUtil.getContext();
+        return Formatter.formatShortFileSize(context, bytes);
+    }
+
+    public static String getImageFileType(String fileName) {
+        if (fileName == null) {
+            return "IMG";
+        }
+        String[] split = fileName.split("\\.");
+        if (split.length <= 1) {
+            return "IMG";
+        }
+        return split[split.length - 1].toUpperCase(Locale.getDefault());
+    }
+
+}

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


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


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


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


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


app/src/main/res/drawable/login_disable_bg.xml → app/src/main/res/drawable/bg_common_disable_btn.xml


app/src/main/res/drawable/feedback_print_bg.xml → app/src/main/res/drawable/bg_feedback_print.xml


+ 17 - 0
app/src/main/res/drawable/bg_progress_bar_horizontal_scan.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/background">
+        <shape>
+            <corners android:radius="100dp" />
+            <solid android:color="#EEF4FE" />
+        </shape>
+    </item>
+    <item android:id="@android:id/progress">
+        <scale android:scaleWidth="100%">
+            <shape>
+                <corners android:radius="100dp" />
+                <solid android:color="@color/colorPrimary" />
+            </shape>
+        </scale>
+    </item>
+</layer-list>

+ 7 - 0
app/src/main/res/drawable/bg_recover_label.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners
+        android:bottomLeftRadius="4dp"
+        android:bottomRightRadius="4dp" />
+    <solid android:color="@color/black40" />
+</shape>

+ 5 - 0
app/src/main/res/drawable/bg_tab_selected.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="16dp" />
+    <solid android:color="@color/colorPrimary" />
+</shape>

+ 36 - 4
app/src/main/res/layout/activity_image_recover.xml

@@ -34,25 +34,57 @@
                 android:layout_gravity="right"
                 android:layout_marginEnd="@dimen/app_common_page_horizontal_padding"
                 android:background="?android:attr/selectableItemBackgroundBorderless"
-                android:onClick="@{()->imageRecoverViewModel.onCheckAllClick(imageRecoverViewModel.checkAll)}"
+                android:onClick="@{()->imageRecoverViewModel.onCheckAllClick(!imageRecoverViewModel.checkAll)}"
                 tools:src="@drawable/icon_image_recover_uncheck" />
         </Toolbar>
 
-        <TableLayout
+        <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:layout_constraintTop_toBottomOf="@+id/tool_bar"
+            app:tabIndicator="@null"
+            app:tabIndicatorHeight="0dp"
+            app:tabRippleColor="@color/transparent" />
 
         <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/ry_image_recover"
+            android:paddingHorizontal="@dimen/app_common_page_horizontal_padding"
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            app:layout_constraintBottom_toBottomOf="parent"
+            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_img"
             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="@{imageRecoverViewModel.selectedList.size() > 0 ? @drawable/bg_common_btn : @drawable/bg_common_disable_btn}"
+            android:gravity="center"
+            android:text="@{imageRecoverViewModel.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>

+ 1 - 1
app/src/main/res/layout/activity_login.xml

@@ -255,7 +255,7 @@
         <TextView
             android:layout_width="0dp"
             android:layout_height="0dp"
-            android:background="@{loginViewModel.isCanLogin(loginViewModel.phoneNum,loginViewModel.verificationCode) ? @drawable/bg_common_btn : @drawable/login_disable_bg}"
+            android:background="@{loginViewModel.isCanLogin(loginViewModel.phoneNum,loginViewModel.verificationCode) ? @drawable/bg_common_btn : @drawable/bg_common_disable_btn}"
             android:gravity="center"
             android:onClick="@{()-> loginViewModel.onLoginClick()}"
             android:text="@string/login_go"

+ 1 - 1
app/src/main/res/layout/activity_user_feedback.xml

@@ -47,7 +47,7 @@
             android:layout_width="0dp"
             android:layout_height="0dp"
             android:layout_marginTop="12dp"
-            android:background="@drawable/feedback_print_bg"
+            android:background="@drawable/bg_feedback_print"
             android:gravity="start"
             android:hint="@string/feedback_hint"
             android:imeOptions="actionDone"

+ 148 - 0
app/src/main/res/layout/dialog_scan_progress.xml

@@ -0,0 +1,148 @@
+<?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="detectedCount"
+            type="androidx.lifecycle.LiveData&lt;Integer>" />
+
+        <variable
+            name="percentageValue"
+            type="Float" />
+
+        <variable
+            name="cancelClick"
+            type="android.view.View.OnClickListener" />
+
+        <import type="com.atmob.common.text.TextUtil" />
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:background="@color/black70">
+
+
+        <View
+            android:id="@+id/v_bg"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:background="@drawable/bg_dialog_bg"
+            app:layout_constraintDimensionRatio="280:182"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_percent="0.7777777777777778" />
+
+
+        <TextView
+            android:id="@+id/tv_dialog_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="24dp"
+            android:text="@string/dialog_scanning_title"
+            android:textColor="#404040"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toBottomOf="@+id/v_bg"
+            app:layout_constraintStart_toStartOf="@+id/v_bg"
+            app:layout_constraintTop_toTopOf="@id/v_bg"
+            app:layout_constraintVertical_bias="0.3024691358024691" />
+
+        <TextView
+            android:id="@+id/tv_count"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="2dp"
+            android:text="@{String.valueOf(detectedCount)}"
+            android:textColor="#699CFF"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            app:layout_constraintBaseline_toBaselineOf="@+id/tv_dialog_title"
+            app:layout_constraintStart_toEndOf="@+id/tv_dialog_title"
+            tools:text="1245" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="2dp"
+            android:text="@string/dialog_item"
+            android:textColor="#404040"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            app:layout_constraintBaseline_toBaselineOf="@+id/tv_count"
+            app:layout_constraintStart_toEndOf="@+id/tv_count" />
+
+        <Space
+            android:id="@+id/space2"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            app:layout_constraintDimensionRatio="360:6"
+            app:layout_constraintTop_toBottomOf="@+id/tv_dialog_title" />
+
+        <ProgressBar
+            android:id="@+id/scan_progress_bar"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:max="100"
+            android:progress="@{(int)percentageValue}"
+            android:progressDrawable="@drawable/bg_progress_bar_horizontal_scan"
+            app:layout_constraintDimensionRatio="232:16"
+            app:layout_constraintEnd_toEndOf="@+id/v_bg"
+            app:layout_constraintStart_toStartOf="@id/v_bg"
+            app:layout_constraintTop_toBottomOf="@+id/space2"
+            app:layout_constraintWidth_percent="0.6444444444444444"
+            tools:progress="50" />
+
+
+        <ImageView
+            android:id="@+id/iv_scan_progress_bg"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginBottom="3.5dp"
+            android:src="@drawable/bg_scan_progress_value"
+            app:layout_constraintBottom_toTopOf="@+id/scan_progress_bar"
+            app:layout_constraintDimensionRatio="114:68"
+            app:layout_constraintEnd_toEndOf="@+id/scan_progress_bar"
+            app:layout_constraintWidth_percent="0.1055555555555556" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:text="@{@string/percentage(TextUtil.formatFloatWithout0End(percentageValue,1))}"
+            android:textColor="@color/white"
+            android:textSize="12dp"
+            app:layout_constraintBottom_toBottomOf="@+id/iv_scan_progress_bg"
+            app:layout_constraintEnd_toEndOf="@+id/iv_scan_progress_bg"
+            app:layout_constraintHorizontal_bias="0.5384615384615385"
+            app:layout_constraintStart_toStartOf="@+id/iv_scan_progress_bg"
+            app:layout_constraintTop_toTopOf="@+id/iv_scan_progress_bg"
+            app:layout_constraintVertical_bias="0.1818181818181818"
+            tools:text="99%" />
+
+        <TextView
+            android:id="@+id/tv_cancel"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:background="@drawable/bg_common_disable_btn"
+            android:gravity="center"
+            android:onClick="@{cancelClick}"
+            android:text="@string/dialog_cancel_scan"
+            android:textColor="@color/white"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="@+id/v_bg"
+            app:layout_constraintDimensionRatio="120:36"
+            app:layout_constraintEnd_toEndOf="@+id/v_bg"
+            app:layout_constraintStart_toStartOf="@+id/v_bg"
+            app:layout_constraintTop_toTopOf="@+id/v_bg"
+            app:layout_constraintVertical_bias="0.8493150684931507"
+            app:layout_constraintWidth_percent="0.3333333333333333" />
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 1 - 1
app/src/main/res/layout/fragment_example.xml

@@ -34,7 +34,7 @@
             android:id="@+id/space1"
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            app:layout_constraintDimensionRatio="360:20"
+            app:layout_constraintDimensionRatio="360:16"
             app:layout_constraintTop_toBottomOf="@+id/space_status_bar" />
 
         <ImageView

+ 52 - 4
app/src/main/res/layout/item_data_img.xml

@@ -1,10 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
+    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.ImageDeepDetector.ImageFile" />
+
+        <variable
+            name="checkBoxClick"
+            type="android.view.View.OnClickListener" />
+
+        <import type="com.datarecovery.master.utils.StringUtil" />
     </data>
 
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -12,11 +21,50 @@
         android:layout_height="wrap_content">
 
         <ImageView
-            tools:src="@color/colorPrimary"
+            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"
-            app:layout_constraintDimensionRatio="1:1" />
+            android:background="@drawable/bg_recover_label"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintDimensionRatio="104:20" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@{@string/recover_img_label_text(StringUtil.getImageFileType(file.name),StringUtil.formatShortBytes(file.size))}"
+            android:textColor="@color/white"
+            android:textSize="12dp"
+            app:layout_constraintBottom_toBottomOf="@+id/bg_label"
+            app:layout_constraintEnd_toEndOf="@+id/bg_label"
+            app:layout_constraintStart_toStartOf="@+id/bg_label"
+            app:layout_constraintTop_toTopOf="@+id/bg_label"
+            tools:text="PNG 41.8kb" />
+
+        <ImageView
+            expandTouchSize="@{4}"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            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"
+            android:onClick="@{checkBoxClick}"
+            app:layout_constraintVertical_bias="0.0952380952380952"
+            app:layout_constraintWidth_percent="0.1923076923076923"
+            tools:src="@drawable/icon_recover_un_check" />
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 </layout>

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

@@ -0,0 +1,30 @@
+<?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="title"
+            type="androidx.lifecycle.LiveData&lt;String>" />
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <TextView
+            android:text="@{title}"
+            tools:text="相册 (100张)"
+            android:paddingVertical="6dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="#666666"
+            android:textSize="14sp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 13 - 0
app/src/main/res/layout/item_tab_image_recover.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    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>

+ 1 - 0
app/src/main/res/values/colors.xml

@@ -9,5 +9,6 @@
 
     <color name="common_txt_color">#202020</color>
     <color name="main_tab_name_text_color">#999999</color>
+    <color name="tab_un_select_text_color">#666666</color>
 
 </resources>

+ 19 - 2
app/src/main/res/values/strings.xml

@@ -20,9 +20,9 @@
     <string name="privacy_policy_text">《隐私政策》</string>
     <string name="home_page_information">资讯</string>
     <string name="home_page_hot_function">热门功能</string>
-    <string name="home_page_wx_msg_recovery">消息恢复</string>
+    <string name="home_page_wx_msg_recovery">微信消息恢复</string>
     <string name="home_page_wx_msg_recovery_desc">微信聊天记录恢复</string>
-    <string name="home_page_wx_friend_recovery">好友恢复</string>
+    <string name="home_page_wx_friend_recovery">微信好友恢复</string>
     <string name="home_page_wx_friend_recovery_desc">微信好友误删恢复</string>
     <string name="home_page_img_recovery">图片恢复</string>
     <string name="home_page_img_recovery_desc">手机丢失图片恢复</string>
@@ -81,4 +81,21 @@
     <string name="request_android_data_permission_content">为了保证扫描效果,需要您授权文件目录的访问权限,否则会影响扫描结果。授权页面无需多余操作,您只需点击【使用此文件夹】即可。</string>
     <string name="teaching_limit">授权权限</string>
     <string name="wait_moment">等会在说</string>
+    <string name="photo">相册</string>
+    <string name="wx">微信</string>
+    <string name="qq">QQ</string>
+    <string name="other">其他</string>
+    <string name="img_recovery_back_title">您确定返回吗?</string>
+    <string name="img_recovery_back_content">返回后,重新进入需要重新扫描数据</string>
+    <string name="dialog_scanning_title">已扫描出</string>
+    <string name="dialog_cancel_scan">取消扫描</string>
+    <string name="photo_count">相册(%d张)</string>
+    <string name="wx_count">微信(%d张)</string>
+    <string name="qq_count">QQ(%d张)</string>
+    <string name="other_count">其他(%d张)</string>
+    <string name="export">立即导出</string>
+    <string name="export_count">立即导出(%d)</string>
+    <string name="recover_img_label_text">%s %s</string>
+    <string name="dialog_item">项</string>
+    <string name="percentage">%s%%</string>
 </resources>

+ 1 - 0
app/src/main/res/values/style.xml

@@ -57,4 +57,5 @@
         <item name="android:layout_gravity">center</item>
     </style>
 
+
 </resources>