Bladeren bron

[1001689]增加音频恢复功能

zk 2 jaren geleden
bovenliggende
commit
a8b5ec0c3c
30 gewijzigde bestanden met toevoegingen van 1048 en 31 verwijderingen
  1. 3 0
      app/src/main/AndroidManifest.xml
  2. 104 0
      app/src/main/java/com/datarecovery/master/module/audiorecover/AudioItemAdapter.java
  3. 210 0
      app/src/main/java/com/datarecovery/master/module/audiorecover/AudioRecoverActivity.java
  4. 349 0
      app/src/main/java/com/datarecovery/master/module/audiorecover/AudioRecoverViewModel.java
  5. 2 0
      app/src/main/java/com/datarecovery/master/module/homepage/HomePageViewModel.java
  6. 69 8
      app/src/main/java/com/datarecovery/master/module/preview/PreviewActivity.java
  7. 22 17
      app/src/main/java/com/datarecovery/master/module/preview/PreviewViewModel.java
  8. 0 1
      app/src/main/java/com/datarecovery/master/module/videorecover/VideoRecoverViewModel.java
  9. 3 3
      app/src/main/java/com/datarecovery/master/utils/FileUtil.java
  10. BIN
      app/src/main/res/drawable-xxhdpi/icon_file_audio.webp
  11. BIN
      app/src/main/res/drawable-xxhdpi/icon_filter_arrow_down.webp
  12. BIN
      app/src/main/res/drawable-xxhdpi/icon_filter_arrow_up.webp
  13. 7 0
      app/src/main/res/drawable/bg_recover_filter.xml
  14. 142 0
      app/src/main/res/layout/activity_audio_recover.xml
  15. 2 1
      app/src/main/res/layout/activity_preview.xml
  16. 107 0
      app/src/main/res/layout/item_data_audio.xml
  17. 2 1
      app/src/main/res/layout/item_data_file.xml
  18. 16 0
      app/src/main/res/layout/popup_audio_recover_filter.xml
  19. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.webp
  20. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  21. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.webp
  22. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  23. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  24. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  25. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  26. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  27. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  28. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  29. 2 0
      app/src/main/res/values/colors.xml
  30. 8 0
      app/src/main/res/values/strings.xml

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

@@ -65,6 +65,9 @@
         <activity
             android:name=".module.filerecover.FileRecoverActivity"
             android:screenOrientation="portrait" />
+        <activity
+            android:name=".module.audiorecover.AudioRecoverActivity"
+            android:screenOrientation="portrait" />
 
     </application>
 

+ 104 - 0
app/src/main/java/com/datarecovery/master/module/audiorecover/AudioItemAdapter.java

@@ -0,0 +1,104 @@
+package com.datarecovery.master.module.audiorecover;
+
+import android.annotation.SuppressLint;
+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.ItemDataAudioBinding;
+import com.datarecovery.master.utils.FilesSearch;
+
+import java.util.List;
+
+public class AudioItemAdapter extends RecyclerView.Adapter<AudioItemAdapter.ViewHolder> {
+
+
+    private List<FilesSearch.DocumentFile> list;
+    private final LifecycleOwner lifecycleOwner;
+    private int size;
+
+    private AudioItemAdapter.onItemClick onItemClick;
+
+    public AudioItemAdapter(LifecycleOwner lifecycleOwner) {
+        this.lifecycleOwner = lifecycleOwner;
+    }
+
+    public void setOnItemClick(onItemClick onItemClick) {
+        this.onItemClick = onItemClick;
+    }
+
+    @NonNull
+    @Override
+    public AudioItemAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        Context context = parent.getContext();
+        LayoutInflater layoutInflater = LayoutInflater.from(context);
+        ItemDataAudioBinding binding = ItemDataAudioBinding.inflate(layoutInflater, parent, false);
+        return new AudioItemAdapter.ViewHolder(binding);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull AudioItemAdapter.ViewHolder holder, int position) {
+        holder.bind(list.get(position));
+    }
+
+    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);
+    }
+
+    @SuppressLint("NotifyDataSetChanged")
+    public void setData(List<FilesSearch.DocumentFile> videoList) {
+        this.list = videoList;
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public int getItemCount() {
+        return (list == null) ? 0 : list.size();
+    }
+
+    public class ViewHolder extends RecyclerView.ViewHolder {
+
+        ItemDataAudioBinding binding;
+
+        public ViewHolder(@NonNull ItemDataAudioBinding binding) {
+            super(binding.getRoot());
+            this.binding = binding;
+            binding.setLifecycleOwner(lifecycleOwner);
+            binding.setOnCheckClick(v -> {
+                if (onItemClick != null) {
+                    onItemClick.onCheck(binding.getFile());
+                }
+            });
+            binding.setOnDetailClick(v -> {
+                if (onItemClick != null) {
+                    onItemClick.onItemClick(binding.getFile());
+                }
+            });
+        }
+
+        public void bind(FilesSearch.DocumentFile file) {
+            binding.setFile(file);
+        }
+    }
+
+    public interface onItemClick {
+        void onCheck(FilesSearch.DocumentFile file);
+
+        void onItemClick(FilesSearch.DocumentFile file);
+    }
+}

+ 210 - 0
app/src/main/java/com/datarecovery/master/module/audiorecover/AudioRecoverActivity.java

@@ -0,0 +1,210 @@
+package com.datarecovery.master.module.audiorecover;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.atmob.app.lib.base.BaseActivity;
+import com.atmob.common.ui.SizeUtil;
+import com.datarecovery.master.R;
+import com.datarecovery.master.databinding.ActivityAudioRecoverBinding;
+import com.datarecovery.master.databinding.PopupAudioRecoverFilterBinding;
+import com.datarecovery.master.dialog.CommonLoadingDialog;
+import com.datarecovery.master.dialog.CommonSureDialog;
+import com.datarecovery.master.dialog.ScanProgressDialog;
+import com.datarecovery.master.module.preview.PreviewActivity;
+import com.datarecovery.master.utils.BoxingUtil;
+import com.datarecovery.master.utils.FilesSearch;
+import com.gyf.immersionbar.ImmersionBar;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+
+@AndroidEntryPoint
+public class AudioRecoverActivity extends BaseActivity<ActivityAudioRecoverBinding> {
+
+
+    private AudioRecoverViewModel audioRecoverViewModel;
+
+    private CommonLoadingDialog loadingDialog;
+    private CommonSureDialog backDialog;
+    private ScanProgressDialog scanProgressDialog;
+
+    private AudioItemAdapter audioItemAdapter;
+
+    private PopupWindow filterPopup;
+
+    private int popupHeight;
+
+
+    public static void start(Context context) {
+        Intent intent = new Intent(context, AudioRecoverActivity.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 initView() {
+        addTopStatusBarHeight(binding.toolBar);
+        binding.toolBar.setNavigationOnClickListener(v -> onBackPressed());
+        audioItemAdapter = new AudioItemAdapter(this);
+        binding.ryAudioRecover.setLayoutManager(new LinearLayoutManager(getBaseContext()));
+        binding.ryAudioRecover.setAdapter(audioItemAdapter);
+        audioItemAdapter.setOnItemClick(new AudioItemAdapter.onItemClick() {
+            @Override
+            public void onCheck(FilesSearch.DocumentFile file) {
+                if (file == null) {
+                    return;
+                }
+                audioRecoverViewModel.setItemCheck(file);
+            }
+
+            @Override
+            public void onItemClick(FilesSearch.DocumentFile file) {
+                PreviewActivity.start(AudioRecoverActivity.this, PreviewActivity.TYPE_AUDIO, file.getUri());
+            }
+        });
+    }
+
+    @SuppressLint("NotifyDataSetChanged")
+    private void initObserver() {
+        audioRecoverViewModel.getShowLoadingEvent().observe(this, this::showLoadingDialog);
+        audioRecoverViewModel.getNotifyAudioData().observe(this, o -> audioItemAdapter.notifyDataSetChanged());
+        audioRecoverViewModel.getDetectedVideoList().observe(this, list -> audioItemAdapter.submit(list));
+        audioRecoverViewModel.getDetectedFinish().observe(this, o -> scanProgressDialog.detectedFinish());
+        audioRecoverViewModel.getShowScanDialogEvent().observe(this, this::showScanProgressDialog);
+        audioRecoverViewModel.getShowFilterPopup().observe(this, o -> showFilterPopup());
+    }
+
+    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 showFilterPopup() {
+        if (filterPopup == null) {
+            PopupAudioRecoverFilterBinding audioRecoverFilterBinding = PopupAudioRecoverFilterBinding.inflate(LayoutInflater.from(getBaseContext()));
+            int[] dateFilterArray = audioRecoverViewModel.getDateFilterArray();
+            audioRecoverFilterBinding.llFilterContainer.removeAllViews();
+            int leftPadding = (int) (SizeUtil.dp2px(41));
+            int verPadding = (int) (SizeUtil.dp2px(10));
+            audioRecoverFilterBinding.getRoot().setOnClickListener(v -> filterPopup.dismiss());
+            for (int txt : dateFilterArray) {
+                TextView filterTv = new TextView(getBaseContext());
+                filterTv.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+                filterTv.setPadding(leftPadding, verPadding, 0, verPadding);
+                filterTv.setText(txt);
+                filterTv.setTextSize(14);
+                filterTv.setBackgroundResource(R.drawable.bg_ripple_common_mask);
+                filterTv.setTextColor(getResources().getColor(R.color.color_404040));
+                filterTv.setOnClickListener(v -> {
+                    audioRecoverViewModel.setFilter(txt);
+                    filterPopup.dismiss();
+                });
+                audioRecoverFilterBinding.llFilterContainer.addView(filterTv);
+            }
+            filterPopup = new PopupWindow(audioRecoverFilterBinding.getRoot(), ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+            filterPopup.setOutsideTouchable(true);
+            filterPopup.setFocusable(true);
+            filterPopup.setOnDismissListener(() -> audioRecoverViewModel.setIsDateFilterArrowDown());
+        }
+        if (popupHeight == 0) {
+            binding.vFilter.post(() -> {
+                if (isFinishing() || isDestroyed()) {
+                    return;
+                }
+                popupHeight = SizeUtil.getScreenHeight() - binding.vFilter.getBottom();
+                filterPopup.setHeight(popupHeight);
+                filterPopup.showAsDropDown(binding.vFilter, 0, 0);
+            });
+        } else {
+            filterPopup.showAsDropDown(binding.vFilter, 0, 0);
+        }
+
+    }
+
+    @Override
+    public void onBackPressed() {
+        showBackDialog();
+    }
+
+    public void showScanProgressDialog(Boolean show) {
+        if (BoxingUtil.boxing(show)) {
+            if (scanProgressDialog == null) {
+                scanProgressDialog = new ScanProgressDialog(this, audioRecoverViewModel.getTotalDetectedCount());
+                scanProgressDialog.setOnCancelListener(() -> audioRecoverViewModel.cancelScan());
+            }
+            scanProgressDialog.show();
+        } else {
+            if (scanProgressDialog != null) {
+                scanProgressDialog.dismiss();
+            }
+        }
+    }
+
+    private void showBackDialog() {
+        if (backDialog == null) {
+            backDialog = new CommonSureDialog(this);
+            backDialog.setDialogTitle(R.string.img_recovery_back_title)
+                    .setDialogContent(R.string.img_recovery_back_content)
+                    .setOnDialogClickListener(this::finish);
+        }
+        backDialog.show();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            onBackPressed();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    protected void configImmersion(@NonNull ImmersionBar immersionBar) {
+        immersionBar.statusBarDarkFont(true);
+    }
+
+    @Override
+    protected void initViewModel() {
+        super.initViewModel();
+        audioRecoverViewModel = getViewModelProvider().get(AudioRecoverViewModel.class);
+        binding.setAudioRecoverViewModel(audioRecoverViewModel);
+    }
+
+    @Override
+    protected boolean shouldImmersion() {
+        return true;
+    }
+}

+ 349 - 0
app/src/main/java/com/datarecovery/master/module/audiorecover/AudioRecoverViewModel.java

@@ -0,0 +1,349 @@
+package com.datarecovery.master.module.audiorecover;
+
+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.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.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import atmob.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import atmob.reactivex.rxjava3.annotations.NonNull;
+import atmob.reactivex.rxjava3.disposables.Disposable;
+import atmob.rxjava.utils.RxJavaUtil;
+import dagger.hilt.android.lifecycle.HiltViewModel;
+
+
+@HiltViewModel
+public class AudioRecoverViewModel extends BaseViewModel {
+
+    private final long SCANNING_COUNTDOWN = 1000 * 60 * 6;
+    private final LiveData<String> selectedCountTxt;
+    private final MutableLiveData<Boolean> checkAll = new MutableLiveData<>(false);
+    private final List<FilesSearch.DocumentFile> originalDetectedList = new ArrayList<>();
+    private final MutableLiveData<List<FilesSearch.DocumentFile>> detectedVideoList = new MutableLiveData<>(new ArrayList<>());
+    private final MutableLiveData<List<FilesSearch.DocumentFile>> selectedList = new MutableLiveData<>(new ArrayList<>());
+    private final SingleLiveEvent<?> detectedFinish = new SingleLiveEvent<>();
+    private final SingleLiveEvent<?> showFilterPopup = new SingleLiveEvent<>();
+    private final SingleLiveEvent<Boolean> showScanDialogEvent = new SingleLiveEvent<>();
+    private final SingleLiveEvent<?> notifyAudioData = new SingleLiveEvent<>();
+    private final SingleLiveEvent<Boolean> showLoadingEvent = new SingleLiveEvent<>();
+    private int totalCount = 0;
+    private final MutableLiveData<Integer> totalDetectedCount = new MutableLiveData<>();
+    private final MutableLiveData<Boolean> isDateFilterArrowUp = new MutableLiveData<>(false);
+    private final MutableLiveData<Boolean> isSizeSortArrowUp = new MutableLiveData<>(false);
+    private final MutableLiveData<Integer> dataFilterCondition = new MutableLiveData<>();
+
+    private Disposable scanDisposable;
+
+    private final int[] dateFilterArray = new int[]{
+            R.string.all,
+            R.string.within_a_week,
+            R.string.away_a_week,
+            R.string.a_month_ago,
+            R.string.a_year_ago
+    };
+
+    public LiveData<?> getNotifyAudioData() {
+        return notifyAudioData;
+    }
+
+    @Inject
+    public AudioRecoverViewModel() {
+        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());
+        });
+        startAudioScanning();
+    }
+
+
+    public LiveData<Integer> getDataFilterCondition() {
+        return dataFilterCondition;
+    }
+
+    public LiveData<Boolean> getIsDateFilterArrowUp() {
+        return isDateFilterArrowUp;
+    }
+
+    public LiveData<Boolean> getIsSizeSortArrowUp() {
+        return isSizeSortArrowUp;
+    }
+
+    public LiveData<?> getShowFilterPopup() {
+        return showFilterPopup;
+    }
+
+    public int[] getDateFilterArray() {
+        return dateFilterArray;
+    }
+
+    public LiveData<String> getSelectedCountTxt() {
+        return selectedCountTxt;
+    }
+
+    public LiveData<?> getDetectedFinish() {
+        return detectedFinish;
+    }
+
+    public LiveData<Boolean> getShowLoadingEvent() {
+        return showLoadingEvent;
+    }
+
+    public LiveData<Integer> getTotalDetectedCount() {
+        return totalDetectedCount;
+    }
+
+    public LiveData<Boolean> getCheckAll() {
+        return checkAll;
+    }
+
+    public LiveData<List<FilesSearch.DocumentFile>> getDetectedVideoList() {
+        return detectedVideoList;
+    }
+
+    public LiveData<List<FilesSearch.DocumentFile>> getSelectedList() {
+        return selectedList;
+    }
+
+    public LiveData<Boolean> getShowScanDialogEvent() {
+        return showScanDialogEvent;
+    }
+
+    private void startAudioScanning() {
+        FilesSearch.search(ContextUtil.getContext(), FilesSearch.DocumentFile.AUDIO)
+                .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);
+                        getList(detectedVideoList).clear();
+                        originalDetectedList.clear();
+                    }
+
+                    @Override
+                    public void onNext(List<FilesSearch.DocumentFile> documentFiles) {
+                        if (documentFiles == null) {
+                            return;
+                        }
+                        totalCount += documentFiles.size();
+                        totalDetectedCount.setValue(totalCount);
+                        originalDetectedList.addAll(0, documentFiles);
+                        List<FilesSearch.DocumentFile> list = getList(detectedVideoList);
+                        list.addAll(0, documentFiles);
+                        detectedVideoList.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;
+        }
+        checkAll.setValue(isCheck);
+        List<FilesSearch.DocumentFile> detectedList = getList(detectedVideoList);
+        for (FilesSearch.DocumentFile videoFile : detectedList) {
+            videoFile.setCheck(isCheck);
+        }
+        List<FilesSearch.DocumentFile> selectedList = getList(this.selectedList);
+        if (isCheck) {
+            selectedList.clear();
+            selectedList.addAll(detectedList);
+        } else {
+            selectedList.clear();
+        }
+        this.selectedList.setValue(selectedList);
+    }
+
+    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_AUDIO, item.newInputStream(), FileUtil.getCreateFileName(item.getName(), ""));
+                item.setCheck(false);
+            }
+            return true;
+        }, o -> {
+            checkAll.setValue(false);
+            showLoadingEvent.setValue(false);
+            ToastUtil.show(R.string.export_success, ToastUtil.LENGTH_SHORT);
+            list.clear();
+            selectedList.setValue(list);
+        }, throwable -> {
+            showLoadingEvent.setValue(false);
+            ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);
+        });
+    }
+
+    public void setItemCheck(@NonNull FilesSearch.DocumentFile file) {
+        file.setCheck(!file.isCheck());
+        List<FilesSearch.DocumentFile> list = getList(selectedList);
+        if (file.isCheck()) {
+            list.add(file);
+        } else {
+            list.remove(file);
+        }
+        selectedList.setValue(list);
+    }
+
+    public void onDateFilterClick() {
+        showFilterPopup.call();
+        isDateFilterArrowUp.setValue(true);
+    }
+
+    public void setIsDateFilterArrowDown() {
+        isDateFilterArrowUp.setValue(false);
+    }
+
+    public void onSizeSortClick(boolean isUp) {
+        List<FilesSearch.DocumentFile> list = getList(detectedVideoList);
+        Collections.sort(list, (o1, o2) -> {
+            if (o1.getSize() > o2.getSize()) {
+                return (isUp) ? 1 : -1;
+            } else if (o1.getSize() < o2.getSize()) {
+                return (isUp) ? -1 : 1;
+            }
+            return 0;
+        });
+        isSizeSortArrowUp.setValue(isUp);
+        notifyAudioData.call();
+    }
+
+    public void setFilter(int filterTxtId) {
+        if (filterTxtId == R.string.all) {
+            dataFilterCondition.setValue(null);
+        } else {
+            dataFilterCondition.setValue(filterTxtId);
+        }
+        if (filterTxtId == R.string.all) {
+            getAllList();
+        } else if (filterTxtId == R.string.within_a_week) {
+            getWithinWeekList();
+        } else if (filterTxtId == R.string.away_a_week) {
+            getAwayWeekList();
+        } else if (filterTxtId == R.string.a_month_ago) {
+            getMonthAgoList();
+        } else if (filterTxtId == R.string.a_year_ago) {
+            getYearAgoList();
+        }
+        notifyAudioData.call();
+    }
+
+    private void getAllList() {
+        if (originalDetectedList.isEmpty()) {
+            return;
+        }
+        List<FilesSearch.DocumentFile> list = getList(detectedVideoList);
+        list.clear();
+        list.addAll(originalDetectedList);
+    }
+
+    private List<FilesSearch.DocumentFile> getAwayWeekList() {
+        if (originalDetectedList.isEmpty()) {
+            return originalDetectedList;
+        }
+        List<FilesSearch.DocumentFile> list = getList(detectedVideoList);
+        list.clear();
+        for (FilesSearch.DocumentFile file : originalDetectedList) {
+            if (file.getLastModified() < System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 7) {
+                list.add(file);
+            }
+        }
+        return list;
+    }
+
+    private List<FilesSearch.DocumentFile> getYearAgoList() {
+        if (originalDetectedList.isEmpty()) {
+            return originalDetectedList;
+        }
+        List<FilesSearch.DocumentFile> list = getList(detectedVideoList);
+        list.clear();
+        for (FilesSearch.DocumentFile file : originalDetectedList) {
+            if (file.getLastModified() < System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 365) {
+                list.add(file);
+            }
+        }
+        return list;
+    }
+
+    private List<FilesSearch.DocumentFile> getMonthAgoList() {
+        if (originalDetectedList.isEmpty()) {
+            return originalDetectedList;
+        }
+        List<FilesSearch.DocumentFile> list = getList(detectedVideoList);
+        list.clear();
+        for (FilesSearch.DocumentFile file : originalDetectedList) {
+            if (file.getLastModified() < System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30) {
+                list.add(file);
+            }
+        }
+        return list;
+    }
+
+
+    private List<FilesSearch.DocumentFile> getWithinWeekList() {
+        if (originalDetectedList.isEmpty()) {
+            return originalDetectedList;
+        }
+        List<FilesSearch.DocumentFile> list = getList(detectedVideoList);
+        list.clear();
+        for (FilesSearch.DocumentFile file : originalDetectedList) {
+            if (file.getLastModified() > System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 7) {
+                list.add(file);
+            }
+        }
+        return list;
+    }
+
+}

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

@@ -5,6 +5,7 @@ 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.module.audiorecover.AudioRecoverActivity;
 import com.datarecovery.master.module.filerecover.FileRecoverActivity;
 import com.datarecovery.master.module.imgrecover.ImageRecoverActivity;
 import com.datarecovery.master.module.videorecover.VideoRecoverActivity;
@@ -140,6 +141,7 @@ public class HomePageViewModel extends BaseViewModel {
         // TODO: 2024/1/9 需要判断是否会员
         switch (bean.getFunctionId()) {
             case FunctionBean.AUDIO_RECOVERY:
+                AudioRecoverActivity.start(ActivityUtil.getTopActivity());
                 break;
             case FunctionBean.FILE_RECOVERY:
                 FileRecoverActivity.start(ActivityUtil.getTopActivity());

+ 69 - 8
app/src/main/java/com/datarecovery/master/module/preview/PreviewActivity.java

@@ -8,6 +8,7 @@ import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Bundle;
 import android.view.SurfaceHolder;
+import android.widget.SeekBar;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -17,12 +18,15 @@ import androidx.viewpager2.widget.ViewPager2;
 import com.atmob.app.lib.base.BaseActivity;
 import com.atmob.common.ui.SizeUtil;
 import com.datarecovery.master.databinding.ActivityPreviewBinding;
+import com.datarecovery.master.utils.BoxingUtil;
 import com.datarecovery.master.utils.ImageDeepDetector;
 import com.datarecovery.master.utils.SafeMediaPlayer;
 import com.gyf.immersionbar.ImmersionBar;
 
 import java.lang.ref.WeakReference;
 import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
 
 import dagger.hilt.android.AndroidEntryPoint;
 
@@ -36,7 +40,9 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
     private MediaPlayer mediaPlayer;
     private boolean isSurfaceCreated;
 
-    private PreviewImagePagerAdapter imagePagerAdapter;
+    private Timer timer;
+    private boolean isSeekbarChanging;
+
     private ViewPager2.OnPageChangeCallback onPageChangeCallback;
 
     @IntDef({TYPE_IMG, TYPE_VIDEO, TYPE_AUDIO})
@@ -111,6 +117,39 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
         }
     }
 
+    private void initSeekBar() {
+        binding.previewAudioSeekbar.setProgress(0);
+        binding.previewAudioSeekbar.setMax(mediaPlayer.getDuration());
+        binding.previewAudioSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (fromUser) {
+                    mediaPlayer.seekTo(progress);
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+                isSeekbarChanging = true;
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                isSeekbarChanging = false;
+                mediaPlayer.seekTo(seekBar.getProgress());
+            }
+        });
+        timer = new Timer();
+        timer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                if (!isSeekbarChanging) {
+                    binding.previewAudioSeekbar.setProgress(mediaPlayer.getCurrentPosition());
+                }
+            }
+        }, 0, 100);
+    }
+
     private void initView() {
         binding.previewHeader.setNavigationOnClickListener(v -> finish());
         addTopStatusBarHeight(binding.previewHeader);
@@ -125,10 +164,18 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
                 previewViewModel.setIsPlaying(mediaPlayer.isPlaying());
             }
         });
+        binding.previewAudioPlay.setOnClickListener(v -> {
+            if (mediaPlayer.isPlaying()) {
+                mediaPlayer.pause();
+            } else {
+                mediaPlayer.start();
+            }
+            previewViewModel.setIsPlaying(mediaPlayer.isPlaying());
+        });
     }
 
     private void initImagePreviewPager() {
-        imagePagerAdapter = new PreviewImagePagerAdapter(previewViewModel.getImagePreviewList());
+        PreviewImagePagerAdapter imagePagerAdapter = new PreviewImagePagerAdapter(previewViewModel.getImagePreviewList());
         binding.previewViewPager.setAdapter(imagePagerAdapter);
         onPageChangeCallback = new ViewPager2.OnPageChangeCallback() {
             @Override
@@ -167,14 +214,26 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
         try {
             mediaPlayer.setDataSource(this, uri);
             mediaPlayer.setOnPreparedListener(mp -> {
-                int videoWidth = mediaPlayer.getVideoWidth();
-                int screenWidth = SizeUtil.getScreenWidth();
-                float scaleX = screenWidth / (videoWidth * 1f);
-                int width = (int) (videoWidth * scaleX);
-                int height = (int) (mediaPlayer.getVideoHeight() * scaleX);
-                binding.previewVideo.resize(width, height);
+                if (previewViewModel.getType() == TYPE_VIDEO) {
+                    int videoWidth = mediaPlayer.getVideoWidth();
+                    int screenWidth = SizeUtil.getScreenWidth();
+                    float scaleX = screenWidth / (videoWidth * 1f);
+                    int width = (int) (videoWidth * scaleX);
+                    int height = (int) (mediaPlayer.getVideoHeight() * scaleX);
+                    binding.previewVideo.resize(width, height);
+                } else if (previewViewModel.getType() == TYPE_AUDIO) {
+                    initSeekBar();
+                }
             });
             mediaPlayer.prepareAsync();
+            mediaPlayer.setOnCompletionListener(mp -> {
+                if (previewViewModel.getType() == TYPE_VIDEO) {
+                    previewViewModel.setIsPlaying(false);
+                } else if (previewViewModel.getType() == TYPE_AUDIO) {
+                    mediaPlayer.seekTo(0);
+                    previewViewModel.setIsPlaying(false);
+                }
+            });
         } catch (Exception ignored) {
         }
     }
@@ -182,6 +241,8 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
     @Override
     protected void onDestroy() {
         super.onDestroy();
+        if (mediaPlayer != null) mediaPlayer.release();
+        if (timer != null) timer.cancel();
         binding.previewViewPager.unregisterOnPageChangeCallback(onPageChangeCallback);
     }
 }

+ 22 - 17
app/src/main/java/com/datarecovery/master/module/preview/PreviewViewModel.java

@@ -16,9 +16,11 @@ import com.datarecovery.master.utils.ToastUtil;
 
 import java.lang.ref.WeakReference;
 import java.util.List;
+import java.util.concurrent.Callable;
 
 import javax.inject.Inject;
 
+import atmob.rxjava.utils.RxJavaUtil;
 import dagger.hilt.android.lifecycle.HiltViewModel;
 
 @HiltViewModel
@@ -26,11 +28,12 @@ public class PreviewViewModel extends BaseViewModel {
 
     public static WeakReference<List<ImageDeepDetector.ImageFile>> weakReference;
     private final MutableLiveData<String> title = new MutableLiveData<>();
-    private final MutableLiveData<Integer> type = new MutableLiveData<>();
     private final MutableLiveData<Uri> previewUri = new MutableLiveData<>();
     private final MutableLiveData<Boolean> isPlaying = new MutableLiveData<>();
     private List<ImageDeepDetector.ImageFile> imagePreviewList;
 
+    private int type;
+
     private final MutableLiveData<Boolean> isPlayStart = new MutableLiveData<>();
     private final MutableLiveData<ImageDeepDetector.ImageFile> currentImageFile = new MutableLiveData<>();
     private int position;
@@ -61,7 +64,7 @@ public class PreviewViewModel extends BaseViewModel {
         return title;
     }
 
-    public LiveData<Integer> getType() {
+    public int getType() {
         return type;
     }
 
@@ -78,7 +81,7 @@ public class PreviewViewModel extends BaseViewModel {
     }
 
     public void setPreviewData(@PreviewActivity.Type int type, int position) {
-        this.type.setValue(type);
+        this.type = type;
         this.position = position;
         switch (type) {
             case PreviewActivity.TYPE_AUDIO:
@@ -115,8 +118,7 @@ public class PreviewViewModel extends BaseViewModel {
 
 
     public void setPosition(int position) {
-        int te = BoxingUtil.boxing(type.getValue());
-        if (te == PreviewActivity.TYPE_IMG) {
+        if (type == PreviewActivity.TYPE_IMG) {
             if (position != -1 && imagePreviewList != null && position < imagePreviewList.size()) {
                 ImageDeepDetector.ImageFile imageFile = imagePreviewList.get(position);
                 currentImageFile.setValue(imageFile);
@@ -126,18 +128,21 @@ public class PreviewViewModel extends BaseViewModel {
     }
 
     public void onExportClick() {
-        if (BoxingUtil.boxing(type.getValue()) == PreviewActivity.TYPE_IMG) {
-            ImageDeepDetector.ImageFile value = currentImageFile.getValue();
-            if (value == null) {
-                return;
-            }
-            try {
-                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_IMAGE, value.newInputStream(), FileUtil.getCreateFileName(value.getName(), ".jpg"));
-                ToastUtil.show(R.string.export_success, ToastUtil.LENGTH_SHORT);
-            } catch (Exception e) {
-                ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);
-            }
-        }
+        RxJavaUtil.doInBackground(() -> {
+                    if (type == PreviewActivity.TYPE_IMG) {
+                        ImageDeepDetector.ImageFile value = currentImageFile.getValue();
+                        if (value == null) {
+                            return false;
+                        }
+                        MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_IMAGE, value.newInputStream(), FileUtil.getCreateFileName(value.getName(), ".jpg"));
+                    } else if (type == PreviewActivity.TYPE_AUDIO) {
+//            MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_AUDIO, item.newInputStream(), FileUtil.getCreateFileName(item.getName(), ""));
+                    }
+                    return true;
+                }
+                , o -> ToastUtil.show(R.string.export_success, ToastUtil.LENGTH_SHORT)
+                , throwable -> ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT));
+
     }
 
     @Override

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

@@ -156,7 +156,6 @@ public class VideoRecoverViewModel extends BaseViewModel {
             selectedList.clear();
         }
         this.selectedList.setValue(selectedList);
-
     }
 
     private List<FilesSearch.DocumentFile> getList(LiveData<List<FilesSearch.DocumentFile>> liveData) {

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

@@ -35,14 +35,14 @@ public class FileUtil {
     }
 
 
-    public static String getCreateFileName(String fileName, String suffix) {
+    public static String getCreateFileName(String fileName, String defaultSuffix) {
         String uuid = UUID.randomUUID().toString();
         if (fileName == null) {
-            return uuid + suffix;
+            return uuid + defaultSuffix;
         }
         String[] split = fileName.split("\\.");
         if (split.length <= 1) {
-            return uuid + suffix;
+            return uuid + defaultSuffix;
         }
         return uuid + "." + split[split.length - 1];
     }

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


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


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


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

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

+ 142 - 0
app/src/main/res/layout/activity_audio_recover.xml

@@ -0,0 +1,142 @@
+<?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="audioRecoverViewModel"
+            type="com.datarecovery.master.module.audiorecover.AudioRecoverViewModel" />
+    </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/audio_recover_title" />
+
+            <ImageView
+                imageDraw="@{audioRecoverViewModel.checkAll ? @drawable/icon_image_recover_checked : @drawable/icon_image_recover_uncheck}"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="right"
+                android:layout_marginEnd="@dimen/app_common_page_horizontal_padding"
+                android:background="?android:attr/selectableItemBackgroundBorderless"
+                android:onClick="@{()->audioRecoverViewModel.onCheckAllClick(!audioRecoverViewModel.checkAll)}"
+                tools:src="@drawable/icon_image_recover_uncheck" />
+
+        </androidx.appcompat.widget.Toolbar>
+
+        <View
+            android:id="@+id/v_filter"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            app:layout_constraintDimensionRatio="360:36"
+            app:layout_constraintTop_toBottomOf="@+id/tool_bar" />
+
+        <TextView
+            android:id="@+id/tv_date_filter"
+            drawableEnd="@{audioRecoverViewModel.isDateFilterArrowUp ? @drawable/icon_filter_arrow_up : @drawable/icon_filter_arrow_down}"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/audio_recover_date_filter"
+            android:textColor="@{audioRecoverViewModel.dataFilterCondition !=null ? @color/colorPrimary : @color/tab_un_select_text_color}"
+            android:textSize="14sp"
+            app:drawableEndCompat="@drawable/icon_filter_arrow_down"
+            app:layout_constraintBottom_toBottomOf="@+id/v_filter"
+            app:layout_constraintLeft_toLeftOf="@+id/v_filter"
+            app:layout_constraintRight_toLeftOf="@+id/v_line"
+            app:layout_constraintTop_toTopOf="@+id/v_filter" />
+
+        <View
+            android:id="@+id/v_date_filter"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:onClick="@{()->audioRecoverViewModel.onDateFilterClick()}"
+            app:layout_constraintBottom_toBottomOf="@+id/v_filter"
+            app:layout_constraintEnd_toStartOf="@id/v_line"
+            app:layout_constraintStart_toStartOf="@id/v_filter"
+            app:layout_constraintTop_toTopOf="@+id/v_filter" />
+
+
+        <View
+            android:id="@+id/v_line"
+            android:layout_width="1dp"
+            android:layout_height="20dp"
+            android:background="#F5F5F5"
+            app:layout_constraintBottom_toBottomOf="@+id/v_filter"
+            app:layout_constraintLeft_toRightOf="@+id/tv_date_filter"
+            app:layout_constraintRight_toLeftOf="@+id/tv_size_sort"
+            app:layout_constraintTop_toTopOf="@id/v_filter" />
+
+        <TextView
+            android:id="@+id/tv_size_sort"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/audio_recover_size_sort"
+            drawableEnd="@{audioRecoverViewModel.isSizeSortArrowUp ? @drawable/icon_filter_arrow_up : @drawable/icon_filter_arrow_down}"
+            android:textColor="#666666"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="@+id/v_filter"
+            app:layout_constraintLeft_toRightOf="@+id/v_line"
+            app:layout_constraintRight_toRightOf="@+id/v_filter"
+            app:layout_constraintTop_toTopOf="@+id/v_filter" />
+
+        <View
+            android:id="@+id/v_size_sort"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:onClick="@{()->audioRecoverViewModel.onSizeSortClick(!audioRecoverViewModel.isSizeSortArrowUp)}"
+            app:layout_constraintBottom_toBottomOf="@+id/v_filter"
+            app:layout_constraintEnd_toEndOf="@+id/v_filter"
+            app:layout_constraintStart_toEndOf="@+id/v_line"
+            app:layout_constraintTop_toTopOf="@+id/v_filter" />
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/ry_audio_recover"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:background="#F8F8F8"
+            app:layout_constraintBottom_toTopOf="@+id/v_bottom"
+            app:layout_constraintTop_toBottomOf="@+id/v_filter"
+            tools:listitem="@layout/item_data_audio" />
+
+        <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="@{audioRecoverViewModel.selectedList.size() > 0 ? @drawable/bg_common_btn : @drawable/bg_common_disable_btn}"
+            android:gravity="center"
+            android:onClick="@{()->audioRecoverViewModel.onExportClick()}"
+            android:text="@{audioRecoverViewModel.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>

+ 2 - 1
app/src/main/res/layout/activity_preview.xml

@@ -112,6 +112,7 @@
             app:layout_constraintTop_toTopOf="@id/preview_audio_play" />
 
         <ImageView
+            expandTouchSize="@{6}"
             android:id="@+id/preview_audio_play"
             android:layout_width="0dp"
             android:layout_height="0dp"
@@ -130,7 +131,7 @@
             android:layout_width="0dp"
             android:layout_height="0dp"
             android:layout_marginHorizontal="@dimen/app_common_page_horizontal_padding"
-            android:progress="10"
+            tools:progress="10"
             android:progressDrawable="@drawable/shape_preview_audio_seekbar"
             android:splitTrack="false"
             android:thumb="@drawable/shape_preview_audio_seekbar_thumb"

+ 107 - 0
app/src/main/res/layout/item_data_audio.xml

@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <data>
+
+        <variable
+            name="file"
+            type="com.datarecovery.master.utils.FilesSearch.DocumentFile" />
+
+        <variable
+            name="onDetailClick"
+            type="android.view.View.OnClickListener" />
+
+        <variable
+            name="onCheckClick"
+            type="android.view.View.OnClickListener" />
+
+        <import type="com.datarecovery.master.utils.DateUtil" />
+
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@{file.check ? @color/item_data_checked_color : @color/transparent}"
+        android:paddingVertical="8dp">
+
+        <ImageView
+            android:id="@+id/iv_check"
+            expandTouchSize="@{6}"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="@dimen/app_common_page_horizontal_padding"
+            android:onClick="@{onCheckClick}"
+            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"
+            android:src="@drawable/icon_file_audio"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintStart_toEndOf="@+id/iv_check"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_percent="0.1333333333333333" />
+
+        <View
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:onClick="@{onDetailClick}"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@+id/iv_file_type"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <TextView
+            android:id="@+id/tv_file_name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="12dp"
+            android:layout_marginEnd="@dimen/app_common_page_horizontal_padding"
+            android:text="@{file.name}"
+            android:textColor="#202020"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toTopOf="@+id/tv_file_date"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/iv_file_type"
+            app:layout_constraintTop_toTopOf="@+id/iv_file_type"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:text="记科技以大额外的们.doc" />
+
+        <TextView
+            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_constraintBottom_toBottomOf="@id/iv_file_type"
+            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>

+ 2 - 1
app/src/main/res/layout/item_data_file.xml

@@ -17,7 +17,8 @@
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="16dp">
+        android:background="@{file.check ? @color/item_data_checked_color : @color/transparent}"
+        android:paddingVertical="8dp">
 
         <ImageView
             android:id="@+id/iv_check"

+ 16 - 0
app/src/main/res/layout/popup_audio_recover_filter.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:background="@color/black70"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:id="@+id/ll_filter_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/bg_recover_filter"
+        android:orientation="vertical"
+        android:paddingVertical="10dp">
+
+    </LinearLayout>
+</FrameLayout>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


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

@@ -10,5 +10,7 @@
     <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>
+    <color name="item_data_checked_color">#F2F2F2</color>
+    <color name="color_404040">#404040</color>
 
 </resources>

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

@@ -115,4 +115,12 @@
     <string name="ppt">PPT</string>
     <string name="pdf">PDF</string>
     <string name="file_recover_title">全部文件</string>
+    <string name="audio_recover_title">全部音频</string>
+    <string name="audio_recover_date_filter">时间</string>
+    <string name="audio_recover_size_sort">大小</string>
+    <string name="all">全部</string>
+    <string name="within_a_week">一周内</string>
+    <string name="away_a_week">一周前</string>
+    <string name="a_month_ago">一个月前</string>
+    <string name="a_year_ago">一个年前</string>
 </resources>