Browse Source

增加试用功能

zk 1 year ago
parent
commit
43a20611f6
51 changed files with 1594 additions and 174 deletions
  1. 3 0
      app/build.gradle
  2. 5 1
      app/proguard-rules.pro
  3. 1 0
      app/src/main/AndroidManifest.xml
  4. BIN
      app/src/main/assets/svga/scan_file.svga
  5. BIN
      app/src/main/assets/svga/spin_in_circles.svga
  6. 16 0
      app/src/main/java/com/datarecovery/master/data/api/request/BaseRequest.java
  7. 33 0
      app/src/main/java/com/datarecovery/master/data/consts/ChannelId.java
  8. 89 0
      app/src/main/java/com/datarecovery/master/data/repositories/ConfigRepository.java
  9. 44 0
      app/src/main/java/com/datarecovery/master/dialog/ScanFileDialog.java
  10. 34 12
      app/src/main/java/com/datarecovery/master/module/audiorecover/AudioRecoverActivity.java
  11. 60 6
      app/src/main/java/com/datarecovery/master/module/audiorecover/AudioRecoverViewModel.java
  12. 27 5
      app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverActivity.java
  13. 73 4
      app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverViewModel.java
  14. 13 9
      app/src/main/java/com/datarecovery/master/module/homepage/HomePageViewModel.java
  15. 32 14
      app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverActivity.java
  16. 89 18
      app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverViewModel.java
  17. 34 0
      app/src/main/java/com/datarecovery/master/module/main/MainActivity.java
  18. 2 1
      app/src/main/java/com/datarecovery/master/module/main/MainViewModel.java
  19. 6 5
      app/src/main/java/com/datarecovery/master/module/member/MemberActivity.java
  20. 2 2
      app/src/main/java/com/datarecovery/master/module/mine/MineViewModel.java
  21. 33 18
      app/src/main/java/com/datarecovery/master/module/preview/PreviewActivity.java
  22. 11 1
      app/src/main/java/com/datarecovery/master/module/preview/PreviewImagePagerAdapter.java
  23. 102 9
      app/src/main/java/com/datarecovery/master/module/preview/PreviewViewModel.java
  24. 37 12
      app/src/main/java/com/datarecovery/master/module/videorecover/VideoRecoverActivity.java
  25. 63 6
      app/src/main/java/com/datarecovery/master/module/videorecover/VideoRecoverViewModel.java
  26. 4 1
      app/src/main/java/com/datarecovery/master/utils/FilePermissionHelper.java
  27. 11 0
      app/src/main/java/com/datarecovery/master/utils/FilesSearch.java
  28. 12 0
      app/src/main/java/com/datarecovery/master/utils/ImageDeepDetector.java
  29. 70 0
      app/src/main/java/com/datarecovery/master/utils/WatermarkView.java
  30. BIN
      app/src/main/res/drawable-xxhdpi/bg_trial_recover.webp
  31. BIN
      app/src/main/res/drawable-xxhdpi/icon_go_recover.webp
  32. BIN
      app/src/main/res/drawable-xxhdpi/icon_recover_right.webp
  33. BIN
      app/src/main/res/drawable-xxhdpi/icon_trial_title.webp
  34. 8 0
      app/src/main/res/drawable/bg_free_label.xml
  35. 10 0
      app/src/main/res/drawable/bg_go_recover_card.xml
  36. 8 0
      app/src/main/res/drawable/bg_scan_file_dialog.xml
  37. 10 0
      app/src/main/res/drawable/bg_trial_go_recover.xml
  38. 68 2
      app/src/main/res/layout/activity_audio_recover.xml
  39. 67 1
      app/src/main/res/layout/activity_file_recover.xml
  40. 70 5
      app/src/main/res/layout/activity_image_recover.xml
  41. 25 9
      app/src/main/res/layout/activity_preview.xml
  42. 84 22
      app/src/main/res/layout/activity_video_recover.xml
  43. 130 0
      app/src/main/res/layout/dialog_scan_file.xml
  44. 24 1
      app/src/main/res/layout/item_data_audio.xml
  45. 27 4
      app/src/main/res/layout/item_data_file.xml
  46. 16 2
      app/src/main/res/layout/item_data_img.xml
  47. 14 1
      app/src/main/res/layout/item_data_video.xml
  48. 21 3
      app/src/main/res/layout/item_preview_img.xml
  49. 85 0
      app/src/main/res/layout/layout_trial.xml
  50. 10 0
      app/src/main/res/values/attr.xml
  51. 11 0
      app/src/main/res/values/strings.xml

+ 3 - 0
app/build.gradle

@@ -193,4 +193,7 @@ dependencies {
     //Bugly
     implementation "com.tencent.bugly:crashreport:$rootProject.bugly_version"
 
+    //SVGA
+    implementation 'com.github.svga:SVGAPlayer-Android:2.6.1'
+
 }

+ 5 - 1
app/proguard-rules.pro

@@ -130,4 +130,8 @@ public <methods>;
 -keep class com.amap.api.mapcore2d.**{*;}
 -keep class com.amap.api.navi.**{*;}
 -keep class com.autonavi.**{*;}
-# AMAP end
+# AMAP end
+
+#SVGA
+-keep class com.squareup.wire.** { *; }
+-keep class com.opensource.svgaplayer.proto.** { *; }

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

@@ -54,6 +54,7 @@
             </intent-filter>
         </activity>
         <activity
+            android:launchMode="singleTask"
             android:name=".module.main.MainActivity"
             android:screenOrientation="portrait" />
         <activity

BIN
app/src/main/assets/svga/scan_file.svga


BIN
app/src/main/assets/svga/spin_in_circles.svga


+ 16 - 0
app/src/main/java/com/datarecovery/master/data/api/request/BaseRequest.java

@@ -1,9 +1,12 @@
 package com.datarecovery.master.data.api.request;
 
 import com.atmob.user.param.AtmobParams;
+import com.datarecovery.master.BuildConfig;
 import com.datarecovery.master.data.repositories.AccountRepository;
 import com.google.gson.annotations.SerializedName;
 
+import java.lang.reflect.Field;
+
 public class BaseRequest extends AtmobParams {
 
     @SerializedName("appPlatform")
@@ -12,8 +15,21 @@ public class BaseRequest extends AtmobParams {
     @SerializedName("authToken")
     private String authToken;
 
+
     public BaseRequest() {
         this.appPlatform = 1;
         this.authToken = AccountRepository.token;
+        if (BuildConfig.DEBUG) {
+            try {
+                Field androidId = AtmobParams.class.getDeclaredField("androidId");
+                androidId.setAccessible(true);
+                androidId.set(this, "13123wdq22e1232");
+            } catch (NoSuchFieldException e) {
+
+            } catch (IllegalAccessException e) {
+
+            }
+        }
+
     }
 }

+ 33 - 0
app/src/main/java/com/datarecovery/master/data/consts/ChannelId.java

@@ -0,0 +1,33 @@
+package com.datarecovery.master.data.consts;
+
+public interface ChannelId {
+
+    // 内推渠道
+    int NT = 99;
+    //神马
+    int SM = 108;
+    //  OPPO
+    int OP = 107;
+    //万年历
+    int WNL = 106;
+    //微博
+    int WB = 105;
+    //百度
+    int BD = 104;
+    //爱奇艺
+    int IQY = 103;
+    // 快手
+    int KS = 102;
+    // 头条
+    int TT = 101;
+    //  广点通
+    int GDT = 100;
+    // 商店
+    int SD = 0;
+
+
+    String HUAWEI = "HW";
+    String VIVO = "VV";
+    String XIAOMI = "XM";
+    String HONOR = "RY";
+}

+ 89 - 0
app/src/main/java/com/datarecovery/master/data/repositories/ConfigRepository.java

@@ -0,0 +1,89 @@
+package com.datarecovery.master.data.repositories;
+
+
+import android.text.TextUtils;
+
+import com.atmob.user.AtmobUser;
+import com.datarecovery.master.BuildConfig;
+import com.datarecovery.master.data.consts.ChannelId;
+import com.datarecovery.master.module.member.MemberType;
+import com.datarecovery.master.sdk.gravity.GravityHelper;
+import com.datarecovery.master.utils.BoxingUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
+public class ConfigRepository {
+
+    private static boolean isOpenTrialMembership = false;
+
+    private static final String[] trialMember = new String[]{
+            MemberType.APP_IMAGE_RECOVER,
+            MemberType.APP_FILE_RECOVER,
+            MemberType.APP_VIDEO_RECOVER,
+            MemberType.APP_AUDIO_RECOVER,
+    };
+
+    private static final List<String> trialMemberList = new ArrayList<>(Arrays.asList(trialMember));
+
+    @Inject
+    public ConfigRepository() {
+        getTrialMembershipStatus();
+    }
+
+
+    private void getTrialMembershipStatus() {
+        if (BuildConfig.DEBUG) {
+            isOpenTrialMembership = true;
+            return;
+        }
+        //判断是否为商店聚道
+        if (AtmobUser.getAtmobTgPlatformId() != ChannelId.SD) {
+            isOpenTrialMembership = false;
+            return;
+        }
+        if (TextUtils.isEmpty(AtmobUser.getAtmobChannel())) {
+            isOpenTrialMembership = false;
+            return;
+        }
+        if (!isTargetSuffixChannel(ChannelId.HUAWEI) && !isTargetSuffixChannel(ChannelId.HONOR) && !isTargetSuffixChannel(ChannelId.VIVO) && !isTargetSuffixChannel(ChannelId.XIAOMI)) {
+            isOpenTrialMembership = false;
+            return;
+        }
+        GravityHelper.registerAttributionResultCallback(attributed -> isOpenTrialMembership = !BoxingUtil.boxing(attributed));
+    }
+
+
+    public static boolean isTargetSuffixChannel(String targetChannel) {
+        String atmobChannel = AtmobUser.getAtmobChannel();
+        if (atmobChannel == null || targetChannel == null) {
+            return false;
+        }
+        int index = atmobChannel.indexOf(targetChannel, atmobChannel.length() - targetChannel.length());
+        return index != -1;
+    }
+
+    public static boolean isIsOpenTrialMembership() {
+        return isOpenTrialMembership;
+    }
+
+    public static boolean isConformTrialAuths(@MemberType String auth) {
+        if (!isOpenTrialMembership) {
+            return false;
+        }
+        return isHaveTrialAuth(auth);
+    }
+
+    private static boolean isHaveTrialAuth(@MemberType String auth) {
+        if (TextUtils.isEmpty(auth)) {
+            return false;
+        }
+        return trialMemberList.contains(auth);
+    }
+}

+ 44 - 0
app/src/main/java/com/datarecovery/master/dialog/ScanFileDialog.java

@@ -0,0 +1,44 @@
+package com.datarecovery.master.dialog;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+
+import com.atmob.app.lib.base.BaseDialog;
+import com.datarecovery.master.R;
+import com.datarecovery.master.databinding.DialogScanFileBinding;
+
+
+@BaseDialog.FullScreen(height = false)
+public class ScanFileDialog extends BaseDialog<DialogScanFileBinding> {
+
+    private final LiveData<String> detectedLastFileName;
+
+    private OnCancelListener onCancelListener;
+
+    public ScanFileDialog(@NonNull Context context, LiveData<String> detectedLastFileName) {
+        super(context, R.style.Theme_Common_Dialog);
+        this.detectedLastFileName = detectedLastFileName;
+        setCancelable(false);
+        binding.setCancelClick(v -> {
+            if (onCancelListener != null) {
+                onCancelListener.onCancel();
+            }
+            dismiss();
+        });
+        binding.setDetectedFileName(detectedLastFileName);
+    }
+
+    public void setOnCancelListener(OnCancelListener onCancelListener) {
+        this.onCancelListener = onCancelListener;
+    }
+
+    public void detectedFinish() {
+        dismiss();
+    }
+
+    public interface OnCancelListener {
+        void onCancel();
+    }
+}

+ 34 - 12
app/src/main/java/com/datarecovery/master/module/audiorecover/AudioRecoverActivity.java

@@ -23,6 +23,7 @@ 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.ScanFileDialog;
 import com.datarecovery.master.dialog.ScanProgressDialog;
 import com.datarecovery.master.module.member.MemberType;
 import com.datarecovery.master.module.preview.PreviewActivity;
@@ -37,25 +38,27 @@ import dagger.hilt.android.AndroidEntryPoint;
 @AndroidEntryPoint
 public class AudioRecoverActivity extends BaseActivity<ActivityAudioRecoverBinding> {
 
-
+    private static final String IS_TRIAL = "is_trial";
     private AudioRecoverViewModel audioRecoverViewModel;
 
     private CommonLoadingDialog loadingDialog;
     private CommonSureDialog backDialog;
-    private ScanProgressDialog scanProgressDialog;
+    private ScanFileDialog scanFileDialog;
 
     private AudioItemAdapter audioItemAdapter;
 
     private PopupWindow filterPopup;
 
     private int popupHeight;
+    private CommonSureDialog showTrialExportFailDialog;
 
 
-    public static void start(Context context) {
+    public static void start(Context context, boolean isTrial) {
         Intent intent = new Intent(context, AudioRecoverActivity.class);
         if (!(context instanceof Activity)) {
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         }
+        intent.putExtra(IS_TRIAL, isTrial);
         context.startActivity(intent);
     }
 
@@ -63,13 +66,21 @@ public class AudioRecoverActivity extends BaseActivity<ActivityAudioRecoverBindi
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        initData();
         initView();
         initObserver();
         initPermission();
     }
 
+    private void initData() {
+        Intent intent = getIntent();
+        if (intent != null) {
+            audioRecoverViewModel.setIsTrial(intent.getBooleanExtra(IS_TRIAL, false));
+        }
+    }
+
     private void initPermission() {
-        new FilePermissionHelper().requestDataFilePermission(this, MemberType.APP_AUDIO_RECOVER, new FilePermissionHelper.NextStepCallback() {
+        new FilePermissionHelper().requestDataFilePermission(this, MemberType.APP_AUDIO_RECOVER, audioRecoverViewModel.isTrial(), new FilePermissionHelper.NextStepCallback() {
             @Override
             public void onNextStep() {
                 audioRecoverViewModel.startAudioScanning();
@@ -99,21 +110,32 @@ public class AudioRecoverActivity extends BaseActivity<ActivityAudioRecoverBindi
 
             @Override
             public void onItemClick(FilesSearch.DocumentFile file) {
-                PreviewActivity.startDocumentPreView(AudioRecoverActivity.this, PreviewActivity.TYPE_AUDIO, file);
+                PreviewActivity.startDocumentPreView(AudioRecoverActivity.this, PreviewActivity.TYPE_AUDIO, file, audioRecoverViewModel.isTrial());
             }
         });
     }
 
     @SuppressLint("NotifyDataSetChanged")
     private void initObserver() {
+        audioRecoverViewModel.getShowTrialExportFailDialog().observe(this, o -> showTrialExportFailDialog());
         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.getDetectedFinish().observe(this, o -> scanFileDialog.detectedFinish());
         audioRecoverViewModel.getShowScanDialogEvent().observe(this, this::showScanProgressDialog);
         audioRecoverViewModel.getShowFilterPopup().observe(this, o -> showFilterPopup());
     }
 
+    private void showTrialExportFailDialog() {
+        if (showTrialExportFailDialog == null) {
+            showTrialExportFailDialog = new CommonSureDialog(this);
+            showTrialExportFailDialog.setDialogTitle(R.string.trial_export_fail_title)
+                    .setDialogContent(R.string.trial_export_fail_content).setSureText(R.string.dialog_trial_recover);
+            showTrialExportFailDialog.setOnDialogClickListener(() -> audioRecoverViewModel.onTrialRecoverClick());
+        }
+        showTrialExportFailDialog.show();
+    }
+
     public void showLoadingDialog(Boolean show) {
         if (BoxingUtil.boxing(show)) {
             if (loadingDialog == null) {
@@ -176,14 +198,14 @@ public class AudioRecoverActivity extends BaseActivity<ActivityAudioRecoverBindi
 
     public void showScanProgressDialog(Boolean show) {
         if (BoxingUtil.boxing(show)) {
-            if (scanProgressDialog == null) {
-                scanProgressDialog = new ScanProgressDialog(this, audioRecoverViewModel.getTotalDetectedCount());
-                scanProgressDialog.setOnCancelListener(() -> audioRecoverViewModel.cancelScan());
+            if (scanFileDialog == null) {
+                scanFileDialog = new ScanFileDialog(this, audioRecoverViewModel.getDetectedLastFileName());
+                scanFileDialog.setOnCancelListener(() -> audioRecoverViewModel.cancelScan());
             }
-            scanProgressDialog.show();
+            scanFileDialog.show();
         } else {
-            if (scanProgressDialog != null) {
-                scanProgressDialog.dismiss();
+            if (scanFileDialog != null) {
+                scanFileDialog.dismiss();
             }
         }
     }

+ 60 - 6
app/src/main/java/com/datarecovery/master/module/audiorecover/AudioRecoverViewModel.java

@@ -6,12 +6,14 @@ import androidx.lifecycle.Transformations;
 
 import com.atmob.app.lib.base.BaseViewModel;
 import com.atmob.app.lib.livedata.SingleLiveEvent;
+import com.atmob.common.runtime.ActivityUtil;
 import com.atmob.common.runtime.ContextUtil;
 import com.datarecovery.master.R;
 import com.datarecovery.master.data.consts.EventId;
 import com.datarecovery.master.data.repositories.DeviceFuncRepository;
 import com.datarecovery.master.handler.EventHelper;
 import com.datarecovery.master.module.imgrecover.ImageRecoverActivity;
+import com.datarecovery.master.module.member.MemberActivity;
 import com.datarecovery.master.module.member.MemberType;
 import com.datarecovery.master.sdk.bugly.BuglyHelper;
 import com.datarecovery.master.utils.FileUtil;
@@ -40,11 +42,14 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
 public class AudioRecoverViewModel extends BaseViewModel {
 
     private final long SCANNING_COUNTDOWN = 1000 * 60 * 6;
+    private final long SCANNING_IS_TRIAL_COUNTDOWN = 1000 * 60;
     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 MutableLiveData<Boolean> showTrialView = new MutableLiveData<>();
+    private final SingleLiveEvent<?> showTrialExportFailDialog = new SingleLiveEvent<>();
     private final SingleLiveEvent<?> detectedFinish = new SingleLiveEvent<>();
     private final SingleLiveEvent<?> showFilterPopup = new SingleLiveEvent<>();
     private final SingleLiveEvent<Boolean> showScanDialogEvent = new SingleLiveEvent<>();
@@ -52,6 +57,7 @@ public class AudioRecoverViewModel extends BaseViewModel {
     private final SingleLiveEvent<Boolean> showLoadingEvent = new SingleLiveEvent<>();
     private final DeviceFuncRepository deviceFuncRepository;
     private int totalCount = 0;
+    private final MutableLiveData<String> detectedLastFileName = new MutableLiveData<>();
     private final MutableLiveData<Integer> totalDetectedCount = new MutableLiveData<>();
     private final MutableLiveData<Boolean> isDateFilterArrowUp = new MutableLiveData<>(false);
     private final MutableLiveData<Boolean> isSizeSortArrowUp = new MutableLiveData<>(false);
@@ -66,6 +72,7 @@ public class AudioRecoverViewModel extends BaseViewModel {
             R.string.a_month_ago,
             R.string.a_year_ago
     };
+    private boolean isTrial;
 
     public LiveData<?> getNotifyAudioData() {
         return notifyAudioData;
@@ -84,6 +91,21 @@ public class AudioRecoverViewModel extends BaseViewModel {
         EventHelper.timeEvent(EventId.hf1000527);
     }
 
+    public LiveData<?> getShowTrialExportFailDialog() {
+        return showTrialExportFailDialog;
+    }
+
+    public LiveData<Boolean> getShowTrialView() {
+        return showTrialView;
+    }
+
+    public LiveData<String> getDetectedLastFileName() {
+        return detectedLastFileName;
+    }
+
+    public boolean isTrial() {
+        return isTrial;
+    }
 
     public LiveData<Integer> getDataFilterCondition() {
         return dataFilterCondition;
@@ -142,7 +164,7 @@ public class AudioRecoverViewModel extends BaseViewModel {
             return;
         }
         FilesSearch.search(ContextUtil.getContext(), FilesSearch.DocumentFile.AUDIO)
-                .take(SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
+                .take(isTrial ? SCANNING_IS_TRIAL_COUNTDOWN : SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(new Subscriber<List<FilesSearch.DocumentFile>>() {
                     @Override
@@ -165,9 +187,12 @@ public class AudioRecoverViewModel extends BaseViewModel {
                         totalCount += documentFiles.size();
                         totalDetectedCount.setValue(totalCount);
                         originalDetectedList.addAll(0, documentFiles);
-                        List<FilesSearch.DocumentFile> list = getList(detectedVideoList);
-                        list.addAll(0, documentFiles);
-                        detectedVideoList.setValue(list);
+                        List<FilesSearch.DocumentFile> videoList = getList(detectedVideoList);
+                        for (FilesSearch.DocumentFile documentFile : documentFiles) {
+                            detectedLastFileName.setValue(documentFile.getName());
+                            videoList.add(0, documentFile);
+                            detectedVideoList.setValue(videoList);
+                        }
                     }
 
                     @Override
@@ -177,13 +202,25 @@ public class AudioRecoverViewModel extends BaseViewModel {
 
                     @Override
                     public void onComplete() {
+                        setFreeExport();
                         detectedFinish.call();
                     }
                 });
     }
 
+    private void setFreeExport() {
+        if (isTrial) {
+            showTrialView.setValue(true);
+        }
+        List<FilesSearch.DocumentFile> list = getList(detectedVideoList);
+        if (list.size() > 0) {
+            list.get(0).setTrial(true);
+        }
+    }
+
     public void cancelScan() {
         if (scanDisposable != null) scanDisposable.dispose();
+        setFreeExport();
     }
 
     private List<FilesSearch.DocumentFile> getList(LiveData<List<FilesSearch.DocumentFile>> liveData) {
@@ -215,8 +252,17 @@ public class AudioRecoverViewModel extends BaseViewModel {
         if (list == null || list.size() == 0) {
             return;
         }
-        if (!deviceFuncRepository.isHaveAuth(MemberType.APP_AUDIO_RECOVER)) {
-            return;
+        if (isTrial) {
+            for (FilesSearch.DocumentFile file : list) {
+                if (!file.isTrial()) {
+                    showTrialExportFailDialog.call();
+                    return;
+                }
+            }
+        } else {
+            if (!deviceFuncRepository.isHaveAuth(MemberType.APP_AUDIO_RECOVER)) {
+                return;
+            }
         }
         showLoadingEvent.setValue(true);
         RxJavaUtil.doInBackground(() -> {
@@ -363,4 +409,12 @@ public class AudioRecoverViewModel extends BaseViewModel {
         super.onCleared();
         EventHelper.report(EventId.hf1000527);
     }
+
+    public void setIsTrial(boolean isTrial) {
+        this.isTrial = isTrial;
+    }
+
+    public void onTrialRecoverClick() {
+        MemberActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_AUDIO_RECOVER);
+    }
 }

+ 27 - 5
app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverActivity.java

@@ -16,6 +16,7 @@ import com.datarecovery.master.databinding.ActivityFileRecoverBinding;
 import com.datarecovery.master.databinding.ItemTabFileRecoverBinding;
 import com.datarecovery.master.dialog.CommonLoadingDialog;
 import com.datarecovery.master.dialog.CommonSureDialog;
+import com.datarecovery.master.dialog.ScanFileDialog;
 import com.datarecovery.master.dialog.ScanProgressDialog;
 import com.datarecovery.master.module.member.MemberType;
 import com.datarecovery.master.utils.BoxingUtil;
@@ -30,7 +31,7 @@ import dagger.hilt.android.AndroidEntryPoint;
 @AndroidEntryPoint
 public class FileRecoverActivity extends BaseActivity<ActivityFileRecoverBinding> {
 
-
+    private static final String IS_TRIAL = "is_trial";
     private FileRecoverViewModel fileRecoverViewModel;
 
     private FileRecoverPageAdapter fileRecoverPageAdapter;
@@ -38,27 +39,37 @@ public class FileRecoverActivity extends BaseActivity<ActivityFileRecoverBinding
 
     private CommonLoadingDialog loadingDialog;
     private CommonSureDialog backDialog;
-    private ScanProgressDialog scanProgressDialog;
+    private ScanFileDialog scanProgressDialog;
     private ViewPager2.OnPageChangeCallback onPageChangeCallback;
+    private CommonSureDialog showTrialExportFailDialog;
 
-    public static void start(Context context) {
+    public static void start(Context context, boolean isTrial) {
         Intent intent = new Intent(context, FileRecoverActivity.class);
         if (!(context instanceof Activity)) {
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         }
+        intent.putExtra(IS_TRIAL, isTrial);
         context.startActivity(intent);
     }
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        initData();
         initView();
         initObserver();
         initPermission();
     }
 
+    private void initData() {
+        Intent intent = getIntent();
+        if (intent != null) {
+            fileRecoverViewModel.setIsTrial(intent.getBooleanExtra(IS_TRIAL, false));
+        }
+    }
+
     private void initPermission() {
-        new FilePermissionHelper().requestDataFilePermission(this, MemberType.APP_FILE_RECOVER, new FilePermissionHelper.NextStepCallback() {
+        new FilePermissionHelper().requestDataFilePermission(this, MemberType.APP_FILE_RECOVER, fileRecoverViewModel.isTrial(), new FilePermissionHelper.NextStepCallback() {
             @Override
             public void onNextStep() {
                 fileRecoverViewModel.startFileScanning();
@@ -72,6 +83,7 @@ public class FileRecoverActivity extends BaseActivity<ActivityFileRecoverBinding
     }
 
     private void initObserver() {
+        fileRecoverViewModel.getShowTrialExportFailDialog().observe(this, o -> showTrialExportFailDialog());
         fileRecoverViewModel.getShowLoadingEvent().observe(this, this::showLoadingDialog);
         fileRecoverViewModel.getDetectedFinish().observe(this, o -> scanProgressDialog.detectedFinish());
         fileRecoverViewModel.getShowScanDialogEvent().observe(this, this::showScanProgressDialog);
@@ -84,6 +96,16 @@ public class FileRecoverActivity extends BaseActivity<ActivityFileRecoverBinding
         initTabLayout();
     }
 
+    private void showTrialExportFailDialog() {
+        if (showTrialExportFailDialog == null) {
+            showTrialExportFailDialog = new CommonSureDialog(this);
+            showTrialExportFailDialog.setDialogTitle(R.string.trial_export_fail_title)
+                    .setDialogContent(R.string.trial_export_fail_content).setSureText(R.string.dialog_trial_recover);
+            showTrialExportFailDialog.setOnDialogClickListener(() -> fileRecoverViewModel.onTrialRecoverClick());
+        }
+        showTrialExportFailDialog.show();
+    }
+
     public void showLoadingDialog(Boolean show) {
         if (BoxingUtil.boxing(show)) {
             if (loadingDialog == null) {
@@ -100,7 +122,7 @@ public class FileRecoverActivity extends BaseActivity<ActivityFileRecoverBinding
     public void showScanProgressDialog(Boolean show) {
         if (BoxingUtil.boxing(show)) {
             if (scanProgressDialog == null) {
-                scanProgressDialog = new ScanProgressDialog(this, fileRecoverViewModel.getTotalDetectedCount());
+                scanProgressDialog = new ScanFileDialog(this, fileRecoverViewModel.getDetectedLastFileName());
                 scanProgressDialog.setOnCancelListener(() -> fileRecoverViewModel.cancelScan());
             }
             scanProgressDialog.show();

+ 73 - 4
app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverViewModel.java

@@ -6,16 +6,19 @@ import androidx.lifecycle.Transformations;
 
 import com.atmob.app.lib.base.BaseViewModel;
 import com.atmob.app.lib.livedata.SingleLiveEvent;
+import com.atmob.common.runtime.ActivityUtil;
 import com.atmob.common.runtime.ContextUtil;
 import com.datarecovery.master.R;
 import com.datarecovery.master.data.consts.EventId;
 import com.datarecovery.master.data.repositories.DeviceFuncRepository;
 import com.datarecovery.master.handler.EventHelper;
+import com.datarecovery.master.module.member.MemberActivity;
 import com.datarecovery.master.module.member.MemberType;
 import com.datarecovery.master.sdk.bugly.BuglyHelper;
 import com.datarecovery.master.utils.BoxingUtil;
 import com.datarecovery.master.utils.FileUtil;
 import com.datarecovery.master.utils.FilesSearch;
+import com.datarecovery.master.utils.ImageDeepDetector;
 import com.datarecovery.master.utils.MediaStoreHelper;
 import com.datarecovery.master.utils.ToastUtil;
 
@@ -24,6 +27,7 @@ import org.reactivestreams.Subscription;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
@@ -39,6 +43,8 @@ public class FileRecoverViewModel extends BaseViewModel {
 
     private final long SCANNING_COUNTDOWN = 1000 * 60 * 6;
 
+    private final long SCANNING_IS_TRIAL_COUNTDOWN = 1000 * 5;
+
     private final int[] tabTitle = {R.string.word, R.string.excel, R.string.ppt, R.string.pdf};
 
     private final List<MutableLiveData<Boolean>> checkList = new ArrayList<>();
@@ -47,8 +53,10 @@ public class FileRecoverViewModel extends BaseViewModel {
     private final MutableLiveData<List<FilesSearch.DocumentFile>> detectedPPTList = new MutableLiveData<>(new ArrayList<>());
     private final MutableLiveData<List<FilesSearch.DocumentFile>> detectedPDFList = new MutableLiveData<>(new ArrayList<>());
     private final MutableLiveData<List<FilesSearch.DocumentFile>> selectedList = new MutableLiveData<>(new ArrayList<>());
-
+    private final MutableLiveData<Boolean> showTrialView = new MutableLiveData<>();
+    private final MutableLiveData<String> detectedLastFileName = new MutableLiveData<>();
     private final MutableLiveData<Integer> checkPosition = new MutableLiveData<>();
+    private final SingleLiveEvent<?> showTrialExportFailDialog = new SingleLiveEvent<>();
     private final SingleLiveEvent<Boolean> showLoadingEvent = new SingleLiveEvent<>();
     private final SingleLiveEvent<?> detectedFinish = new SingleLiveEvent<>();
     private final SingleLiveEvent<Boolean> showScanDialogEvent = new SingleLiveEvent<>();
@@ -57,6 +65,7 @@ public class FileRecoverViewModel extends BaseViewModel {
     private final MutableLiveData<Integer> totalDetectedCount = new MutableLiveData<>();
     private final LiveData<String> selectedCountTxt;
     private Disposable scanDisposable;
+    private boolean isTrial;
 
     @Inject
     public FileRecoverViewModel(DeviceFuncRepository deviceFuncRepository) {
@@ -72,6 +81,23 @@ public class FileRecoverViewModel extends BaseViewModel {
         EventHelper.timeEvent(EventId.hf1000523);
     }
 
+    public LiveData<?> getShowTrialExportFailDialog() {
+        return showTrialExportFailDialog;
+    }
+
+
+    public LiveData<Boolean> getShowTrialView() {
+        return showTrialView;
+    }
+
+    public LiveData<String> getDetectedLastFileName() {
+        return detectedLastFileName;
+    }
+
+    public boolean isTrial() {
+        return isTrial;
+    }
+
     public LiveData<Integer> getCheckPosition() {
         return checkPosition;
     }
@@ -132,7 +158,7 @@ public class FileRecoverViewModel extends BaseViewModel {
             return;
         }
         FilesSearch.search(ContextUtil.getContext(), FilesSearch.DocumentFile.WORD, FilesSearch.DocumentFile.EXCEL, FilesSearch.DocumentFile.PPT, FilesSearch.DocumentFile.PDF)
-                .take(SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
+                .take(isTrial ? SCANNING_IS_TRIAL_COUNTDOWN : SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(new Subscriber<List<FilesSearch.DocumentFile>>() {
                     @Override
@@ -166,6 +192,7 @@ public class FileRecoverViewModel extends BaseViewModel {
                             if (liveData == null) {
                                 continue;
                             }
+                            detectedLastFileName.setValue(item.getName());
                             List<FilesSearch.DocumentFile> list = getList(liveData);
                             list.add(0, item);
                             liveData.setValue(list);
@@ -179,6 +206,7 @@ public class FileRecoverViewModel extends BaseViewModel {
 
                     @Override
                     public void onComplete() {
+                        setFreeExport();
                         detectedFinish.call();
                     }
                 });
@@ -186,6 +214,29 @@ public class FileRecoverViewModel extends BaseViewModel {
 
     public void cancelScan() {
         if (scanDisposable != null) scanDisposable.dispose();
+        setFreeExport();
+    }
+
+    private void setFreeExport() {
+        if (isTrial) {
+            showTrialView.setValue(true);
+            List<FilesSearch.DocumentFile> list = getList(detectedWordList);
+            if (list.size() > 0) {
+                list.get(0).setTrial(true);
+            }
+            list = getList(detectedExcelList);
+            if (list.size() > 0) {
+                list.get(0).setTrial(true);
+            }
+            list = getList(detectedPPTList);
+            if (list.size() > 0) {
+                list.get(0).setTrial(true);
+            }
+            list = getList(detectedPDFList);
+            if (list.size() > 0) {
+                list.get(0).setTrial(true);
+            }
+        }
     }
 
 
@@ -236,13 +287,23 @@ public class FileRecoverViewModel extends BaseViewModel {
         return checkList.get(position);
     }
 
+
     public void onExportClick() {
         List<FilesSearch.DocumentFile> list = selectedList.getValue();
         if (list == null || list.size() == 0) {
             return;
         }
-        if (!deviceFuncRepository.isHaveAuth(MemberType.APP_FILE_RECOVER)) {
-            return;
+        if (isTrial) {
+            for (FilesSearch.DocumentFile file : list) {
+                if (!file.isTrial()) {
+                    showTrialExportFailDialog.call();
+                    return;
+                }
+            }
+        } else {
+            if (!deviceFuncRepository.isHaveAuth(MemberType.APP_FILE_RECOVER)) {
+                return;
+            }
         }
         showLoadingEvent.setValue(true);
         RxJavaUtil.doInBackground(() -> {
@@ -283,4 +344,12 @@ public class FileRecoverViewModel extends BaseViewModel {
         super.onCleared();
         EventHelper.report(EventId.hf1000523);
     }
+
+    public void setIsTrial(boolean isTrial) {
+        this.isTrial = isTrial;
+    }
+
+    public void onTrialRecoverClick() {
+        MemberActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_FILE_RECOVER);
+    }
 }

+ 13 - 9
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.datarecovery.master.R;
 import com.datarecovery.master.data.consts.EventId;
+import com.datarecovery.master.data.repositories.ConfigRepository;
 import com.datarecovery.master.data.repositories.DeviceFuncRepository;
 import com.datarecovery.master.handler.EventHelper;
 import com.datarecovery.master.module.audiorecover.AudioRecoverActivity;
@@ -78,21 +79,23 @@ public class HomePageViewModel extends BaseViewModel {
     }
 
     public void onWxMessageRecoveryClick() {
-        isHaveAuths(MemberType.APP_WX_MESSAGE_RECOVER, () -> WeChatRecoverActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_WX_MESSAGE_RECOVER));
+        isHaveAuths(MemberType.APP_WX_MESSAGE_RECOVER, isTrial -> WeChatRecoverActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_WX_MESSAGE_RECOVER));
     }
 
     public void onWxFriendRecoveryClick() {
-        isHaveAuths(MemberType.APP_WX_FRIEND_RECOVER, () -> WeChatRecoverActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_WX_FRIEND_RECOVER));
+        isHaveAuths(MemberType.APP_WX_FRIEND_RECOVER, isTrial -> WeChatRecoverActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_WX_FRIEND_RECOVER));
     }
 
     public void onImgRecoveryClick() {
-        isHaveAuths(MemberType.APP_IMAGE_RECOVER, () -> ImageRecoverActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_IMAGE_RECOVER));
+        isHaveAuths(MemberType.APP_IMAGE_RECOVER, isTrial -> ImageRecoverActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_IMAGE_RECOVER, isTrial));
     }
 
     public void isHaveAuths(@MemberType String type, NextStepCallback stepCallback) {
         reportFirstClickFunction(type);
         if (deviceFuncRepository.isHaveAuth(type)) {
-            if (stepCallback != null) stepCallback.onNextStep();
+            if (stepCallback != null) stepCallback.onNextStep(false);
+        } else if (ConfigRepository.isConformTrialAuths(type)) {
+            if (stepCallback != null) stepCallback.onNextStep(true);
         } else {
             MemberActivity.start(ActivityUtil.getTopActivity(), type);
         }
@@ -112,23 +115,24 @@ public class HomePageViewModel extends BaseViewModel {
         }
         switch (bean.getFunctionId()) {
             case FunctionBean.AUDIO_RECOVERY:
-                isHaveAuths(MemberType.APP_AUDIO_RECOVER, () -> AudioRecoverActivity.start(ActivityUtil.getTopActivity()));
+                isHaveAuths(MemberType.APP_AUDIO_RECOVER, isTrial -> AudioRecoverActivity.start(ActivityUtil.getTopActivity(), isTrial));
                 break;
             case FunctionBean.FILE_RECOVERY:
-                isHaveAuths(MemberType.APP_FILE_RECOVER, () -> FileRecoverActivity.start(ActivityUtil.getTopActivity()));
+                isHaveAuths(MemberType.APP_FILE_RECOVER, isTrial -> FileRecoverActivity.start(ActivityUtil.getTopActivity(), isTrial));
                 break;
             case FunctionBean.IMG_CLEARING:
-                isHaveAuths(MemberType.APP_IMAGE_CLEAN, () -> ImageRecoverActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_IMAGE_CLEAN));
+                isHaveAuths(MemberType.APP_IMAGE_CLEAN, isTrial -> ImageRecoverActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_IMAGE_CLEAN));
                 break;
             case FunctionBean.VIDEO_RECOVERY:
-                isHaveAuths(MemberType.APP_VIDEO_RECOVER, () -> VideoRecoverActivity.start(ActivityUtil.getTopActivity()));
+                isHaveAuths(MemberType.APP_VIDEO_RECOVER, isTrial -> VideoRecoverActivity.start(ActivityUtil.getTopActivity(), isTrial));
                 break;
         }
 
     }
 
     public interface NextStepCallback {
-        void onNextStep();
+        void onNextStep(boolean isTrial);
+
     }
 
 

+ 32 - 14
app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverActivity.java

@@ -28,6 +28,7 @@ import com.datarecovery.master.databinding.ActivityImageRecoverBinding;
 import com.datarecovery.master.databinding.ItemTabImageRecoverBinding;
 import com.datarecovery.master.dialog.CommonLoadingDialog;
 import com.datarecovery.master.dialog.CommonSureDialog;
+import com.datarecovery.master.dialog.ScanFileDialog;
 import com.datarecovery.master.dialog.ScanProgressDialog;
 import com.datarecovery.master.module.member.MemberType;
 import com.datarecovery.master.module.preview.PreviewActivity;
@@ -54,26 +55,35 @@ public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBindi
 
 
     private static final String TYPE = "type";
+    private static final String IS_TRIAL = "is_trial";
     ImageRecoverViewModel imageRecoverViewModel;
 
     private final int[] tabTitle = {R.string.photo, R.string.wx, R.string.qq, R.string.other};
 
     private CommonSureDialog backDialog;
     private CommonSureDialog clearDialog;
+
+    private CommonSureDialog showTrialExportFailDialog;
     private CommonLoadingDialog loadingDialog;
-    private ScanProgressDialog scanProgressDialog;
+    private ScanFileDialog scanFileDialog;
     private ImageItemAdapter photoAdapter;
     private ImageItemAdapter wxAdapter;
     private ImageItemAdapter qqAdapter;
     private ImageItemAdapter otherAdapter;
     private GridLayoutManager gridLayoutManager;
 
+
     public static void start(Context context, @MemberType String type) {
+        start(context, type, false);
+    }
+
+    public static void start(Context context, @MemberType String type, boolean isTrial) {
         Intent intent = new Intent(context, ImageRecoverActivity.class);
         if (!(context instanceof Activity)) {
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         }
         intent.putExtra(TYPE, type);
+        intent.putExtra(IS_TRIAL, isTrial);
         context.startActivity(intent);
     }
 
@@ -87,7 +97,7 @@ public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBindi
     }
 
     private void initPermission() {
-        new FilePermissionHelper().requestDataFilePermission(this, imageRecoverViewModel.getType(), new FilePermissionHelper.NextStepCallback() {
+        new FilePermissionHelper().requestDataFilePermission(this, imageRecoverViewModel.getType(), imageRecoverViewModel.isTrial(), new FilePermissionHelper.NextStepCallback() {
             @Override
             public void onNextStep() {
                 imageRecoverViewModel.startImageScanning();
@@ -104,6 +114,7 @@ public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBindi
         Intent intent = getIntent();
         if (intent != null) {
             imageRecoverViewModel.setType(intent.getStringExtra(TYPE));
+            imageRecoverViewModel.setIsTrial(intent.getBooleanExtra(IS_TRIAL, false));
         }
     }
 
@@ -276,6 +287,7 @@ public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBindi
 
     @SuppressLint("NotifyDataSetChanged")
     private void initObserver() {
+        imageRecoverViewModel.getShowTrialExportFailDialog().observe(this, o -> showTrialExportFailDialog());
         imageRecoverViewModel.getDeleteUriListSdk11().observe(this, list -> {
             if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
                 PendingIntent pi = MediaStore.createDeleteRequest(getBaseContext().getContentResolver(), list);
@@ -296,16 +308,22 @@ public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBindi
         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());
-        imageRecoverViewModel.getPreviewEvent().observe(this, imageFile -> {
-            List<ImageDeepDetector.ImageFile> allDetectedList = imageRecoverViewModel.getAllDetectedList();
-            int position = imageRecoverViewModel.getPosition(allDetectedList, imageFile);
-            PreviewActivity.startImagePreView(this, allDetectedList, position);
-        });
+        imageRecoverViewModel.getDetectedFinish().observe(this, o -> scanFileDialog.detectedFinish());
+        imageRecoverViewModel.getPreviewEvent().observe(this, pair -> PreviewActivity.startImagePreView(this, pair.first, pair.second, imageRecoverViewModel.isTrial()));
         imageRecoverViewModel.getShowLoadingEvent().observe(this, this::showLoadingDialog);
         imageRecoverViewModel.getShowClearDialog().observe(this, o -> showClearDialog());
     }
 
+    private void showTrialExportFailDialog() {
+        if (showTrialExportFailDialog == null) {
+            showTrialExportFailDialog = new CommonSureDialog(this);
+            showTrialExportFailDialog.setDialogTitle(R.string.trial_export_fail_title)
+                    .setDialogContent(R.string.trial_export_fail_content).setSureText(R.string.dialog_trial_recover);
+            showTrialExportFailDialog.setOnDialogClickListener(() -> imageRecoverViewModel.onTrialRecoverClick());
+        }
+        showTrialExportFailDialog.show();
+    }
+
     private void showClearDialog() {
         if (clearDialog == null) {
             clearDialog = new CommonSureDialog(this);
@@ -332,14 +350,14 @@ public class ImageRecoverActivity extends BaseActivity<ActivityImageRecoverBindi
 
     public void showScanProgressDialog(Boolean show) {
         if (BoxingUtil.boxing(show)) {
-            if (scanProgressDialog == null) {
-                scanProgressDialog = new ScanProgressDialog(this, imageRecoverViewModel.getTotalDetectedCount());
-                scanProgressDialog.setOnCancelListener(() -> imageRecoverViewModel.cancelScan());
+            if (scanFileDialog == null) {
+                scanFileDialog = new ScanFileDialog(this, imageRecoverViewModel.getDetectedLastFileName());
+                scanFileDialog.setOnCancelListener(() -> imageRecoverViewModel.cancelScan());
             }
-            scanProgressDialog.show();
+            scanFileDialog.show();
         } else {
-            if (scanProgressDialog != null) {
-                scanProgressDialog.dismiss();
+            if (scanFileDialog != null) {
+                scanFileDialog.dismiss();
             }
         }
     }

+ 89 - 18
app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverViewModel.java

@@ -1,23 +1,17 @@
 package com.datarecovery.master.module.imgrecover;
 
-import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
-import android.content.IntentSender;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
 import android.provider.MediaStore;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.Pair;
 
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.Observer;
 import androidx.lifecycle.Transformations;
 
 import com.atmob.app.lib.base.BaseViewModel;
@@ -28,6 +22,7 @@ import com.datarecovery.master.R;
 import com.datarecovery.master.data.consts.EventId;
 import com.datarecovery.master.data.repositories.DeviceFuncRepository;
 import com.datarecovery.master.handler.EventHelper;
+import com.datarecovery.master.module.member.MemberActivity;
 import com.datarecovery.master.module.member.MemberType;
 import com.datarecovery.master.sdk.bugly.BuglyHelper;
 import com.datarecovery.master.utils.BoxingUtil;
@@ -35,12 +30,10 @@ import com.datarecovery.master.utils.FileUtil;
 import com.datarecovery.master.utils.ImageDeepDetector;
 import com.datarecovery.master.utils.MediaStoreHelper;
 import com.datarecovery.master.utils.ToastUtil;
-import com.datarecovery.master.utils.xfile.XSAFFile;
 
 import org.reactivestreams.Subscription;
 
 import java.io.File;
-import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -50,12 +43,12 @@ import javax.inject.Inject;
 
 import atmob.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 import atmob.reactivex.rxjava3.annotations.NonNull;
+import atmob.reactivex.rxjava3.core.Flowable;
 import atmob.reactivex.rxjava3.core.FlowableSubscriber;
 import atmob.reactivex.rxjava3.core.Single;
 import atmob.reactivex.rxjava3.core.SingleObserver;
 import atmob.reactivex.rxjava3.core.SingleSource;
 import atmob.reactivex.rxjava3.disposables.Disposable;
-import atmob.reactivex.rxjava3.functions.Function;
 import atmob.rxjava.utils.RxJavaUtil;
 import dagger.hilt.android.lifecycle.HiltViewModel;
 
@@ -65,6 +58,7 @@ public class ImageRecoverViewModel extends BaseViewModel {
 
 
     private final long SCANNING_COUNTDOWN = 1000 * 60 * 6;
+    private final long SCANNING_IS_TRIAL_COUNTDOWN = 1000 * 5;
 
     private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedPhotoImg = new MutableLiveData<>(new ArrayList<>());
     private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedWxImg = new MutableLiveData<>(new ArrayList<>());
@@ -72,13 +66,15 @@ public class ImageRecoverViewModel extends BaseViewModel {
     private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedOtherImg = new MutableLiveData<>(new ArrayList<>());
 
     private final SingleLiveEvent<List<Uri>> deleteUriListSdk11 = new SingleLiveEvent<>();
+    private final SingleLiveEvent<?> showTrialExportFailDialog = new SingleLiveEvent<>();
+    private final MutableLiveData<Boolean> showTrialView = new MutableLiveData<>();
     private final DeviceFuncRepository deviceFuncRepository;
     private LiveData<String> detectedPhotoTitle;
     private LiveData<String> detectedWxTitle;
     private LiveData<String> detectedQQTitle;
     private LiveData<String> detectedOtherTitle;
 
-    private final SingleLiveEvent<ImageDeepDetector.ImageFile> previewEvent = new SingleLiveEvent<>();
+    private final SingleLiveEvent<Pair<List<ImageDeepDetector.ImageFile>, Integer>> previewEvent = new SingleLiveEvent<>();
     private final SingleLiveEvent<?> detectedFinish = new SingleLiveEvent<>();
     private final SingleLiveEvent<?> showClearDialog = new SingleLiveEvent<>();
     private final SingleLiveEvent<Boolean> showScanDialogEvent = new SingleLiveEvent<>();
@@ -89,6 +85,7 @@ public class ImageRecoverViewModel extends BaseViewModel {
     //总探测到的图片数量
     private int totalCount = 0;
     private final MutableLiveData<Integer> totalDetectedCount = new MutableLiveData<>();
+    private final MutableLiveData<String> detectedLastFileName = new MutableLiveData<>();
     private final MutableLiveData<List<ImageDeepDetector.ImageFile>> selectedList = new MutableLiveData<>(new ArrayList<>());
     private LiveData<String> selectedCountTxt;
     private Disposable scanDisposable;
@@ -97,6 +94,8 @@ public class ImageRecoverViewModel extends BaseViewModel {
     private String type;
 
     private SingleObserver<? super Boolean> dealMediaObserver;
+    //是否会员试用
+    private boolean isTrial;
 
 
     @Inject
@@ -106,6 +105,22 @@ public class ImageRecoverViewModel extends BaseViewModel {
         initLiveData();
     }
 
+    public LiveData<?> getShowTrialExportFailDialog() {
+        return showTrialExportFailDialog;
+    }
+
+    public LiveData<String> getDetectedLastFileName() {
+        return detectedLastFileName;
+    }
+
+    public LiveData<Boolean> getShowTrialView() {
+        return showTrialView;
+    }
+
+    public boolean isTrial() {
+        return isTrial;
+    }
+
     public LiveData<List<Uri>> getDeleteUriListSdk11() {
         return deleteUriListSdk11;
     }
@@ -122,7 +137,7 @@ public class ImageRecoverViewModel extends BaseViewModel {
         return showLoadingEvent;
     }
 
-    public LiveData<ImageDeepDetector.ImageFile> getPreviewEvent() {
+    public LiveData<Pair<List<ImageDeepDetector.ImageFile>, Integer>> getPreviewEvent() {
         return previewEvent;
     }
 
@@ -244,7 +259,7 @@ public class ImageRecoverViewModel extends BaseViewModel {
             return;
         }
         ImageDeepDetector.detect(ContextUtil.getContext())
-                .take(SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
+                .take(isTrial ? SCANNING_IS_TRIAL_COUNTDOWN : SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(new FlowableSubscriber<List<ImageDeepDetector.ImageFile>>() {
                     @Override
@@ -276,6 +291,7 @@ public class ImageRecoverViewModel extends BaseViewModel {
                             } else {
                                 liveData = detectedOtherImg;
                             }
+                            detectedLastFileName.setValue(imageFile.getName());
                             List<ImageDeepDetector.ImageFile> list = getList(liveData);
                             list.add(0, imageFile);
                             liveData.setValue(list);
@@ -289,18 +305,46 @@ public class ImageRecoverViewModel extends BaseViewModel {
 
                     @Override
                     public void onComplete() {
+                        setFreeExport();
                         detectedFinish.call();
                     }
                 });
     }
 
+    private void setFreeExport() {
+        if (isTrial) {
+            showTrialView.setValue(true);
+            List<ImageDeepDetector.ImageFile> photoList = getList(detectedPhotoImg);
+            if (photoList.size() != 0) {
+                photoList.get(0).setTrial(true);
+                return;
+            }
+            List<ImageDeepDetector.ImageFile> wxList = getList(detectedWxImg);
+            if (wxList.size() != 0) {
+                wxList.get(0).setTrial(true);
+                return;
+            }
+            List<ImageDeepDetector.ImageFile> qqList = getList(detectedQQImg);
+            if (qqList.size() != 0) {
+                qqList.get(0).setTrial(true);
+                return;
+            }
+            List<ImageDeepDetector.ImageFile> otherList = getList(detectedOtherImg);
+            if (otherList.size() != 0) {
+                otherList.get(0).setTrial(true);
+                return;
+            }
+        }
+    }
+
     public void cancelScan() {
         if (scanDisposable != null) scanDisposable.dispose();
+        setFreeExport();
     }
 
     public void setItemCheck(@NonNull ImageDeepDetector.ImageFile imageFile) {
-        imageFile.setCheck(!imageFile.isCheck());
         List<ImageDeepDetector.ImageFile> list = getList(this.selectedList);
+        imageFile.setCheck(!imageFile.isCheck());
         if (imageFile.isCheck()) {
             list.add(imageFile);
         } else {
@@ -310,6 +354,10 @@ public class ImageRecoverViewModel extends BaseViewModel {
     }
 
     public void checkPreview(@NonNull ImageDeepDetector.ImageFile imageFile) {
+        if (isTrial) {
+            sendPreviewEvent(imageFile);
+            return;
+        }
         if (Objects.equals(type, MemberType.APP_IMAGE_CLEAN)) {
             return;
         }
@@ -318,7 +366,13 @@ public class ImageRecoverViewModel extends BaseViewModel {
         } else if (Objects.equals(type, MemberType.APP_IMAGE_CLEAN) && !deviceFuncRepository.isHaveAuth(type)) {
             return;
         }
-        previewEvent.setValue(imageFile);
+        sendPreviewEvent(imageFile);
+    }
+
+    public void sendPreviewEvent(@NonNull ImageDeepDetector.ImageFile imageFile) {
+        List<ImageDeepDetector.ImageFile> allDetectedList = getAllDetectedList();
+        int position = getPosition(allDetectedList, imageFile);
+        previewEvent.setValue(new Pair<>(allDetectedList, position));
     }
 
     public void onOperationClick() {
@@ -469,10 +523,19 @@ public class ImageRecoverViewModel extends BaseViewModel {
         if (list.size() == 0) {
             return;
         }
-        if (Objects.equals(type, MemberType.APP_IMAGE_RECOVER) && !deviceFuncRepository.isHaveAuth(type)) {
-            return;
-        } else if (Objects.equals(type, MemberType.APP_IMAGE_CLEAN) && !deviceFuncRepository.isHaveAuth(type)) {
-            return;
+        if (isTrial) {
+            for (ImageDeepDetector.ImageFile imageFile : list) {
+                if (!imageFile.isTrial()) {
+                    showTrialExportFailDialog.call();
+                    return;
+                }
+            }
+        } else {
+            if (Objects.equals(type, MemberType.APP_IMAGE_RECOVER) && !deviceFuncRepository.isHaveAuth(type)) {
+                return;
+            } else if (Objects.equals(type, MemberType.APP_IMAGE_CLEAN) && !deviceFuncRepository.isHaveAuth(type)) {
+                return;
+            }
         }
         showLoadingEvent.setValue(true);
         RxJavaUtil.doInBackground(() -> {
@@ -556,4 +619,12 @@ public class ImageRecoverViewModel extends BaseViewModel {
             dealMediaObserver.onSuccess(isDelete);
         }
     }
+
+    public void setIsTrial(boolean isTrial) {
+        this.isTrial = isTrial;
+    }
+
+    public void onTrialRecoverClick() {
+        MemberActivity.start(ActivityUtil.getTopActivity(), type);
+    }
 }

+ 34 - 0
app/src/main/java/com/datarecovery/master/module/main/MainActivity.java

@@ -15,10 +15,15 @@ import com.datarecovery.master.data.consts.EventId;
 import com.datarecovery.master.databinding.ActivityMainBinding;
 import com.datarecovery.master.databinding.ItemMainTabLayoutBinding;
 import com.datarecovery.master.handler.EventHelper;
+import com.datarecovery.master.module.audiorecover.AudioRecoverActivity;
 import com.datarecovery.master.module.example.ExampleFragment;
+import com.datarecovery.master.module.filerecover.FileRecoverActivity;
 import com.datarecovery.master.module.homepage.HomePageFragment;
+import com.datarecovery.master.module.imgrecover.ImageRecoverActivity;
+import com.datarecovery.master.module.member.MemberType;
 import com.datarecovery.master.module.mine.MineFragment;
 import com.datarecovery.master.module.order.OrderFragment;
+import com.datarecovery.master.module.videorecover.VideoRecoverActivity;
 import com.datarecovery.master.utils.Maps;
 import com.datarecovery.master.utils.ToastUtil;
 import com.google.android.material.tabs.TabLayout;
@@ -37,6 +42,8 @@ public class MainActivity extends BaseActivity<ActivityMainBinding> {
     private MainPagerAdapter mainPagerAdapter;
     private long mExitTime;
 
+    private static final String MEMBER_TYPE = "member_type";
+
     public static void start(Context context) {
         Intent intent = new Intent(context, MainActivity.class);
         if (!(context instanceof Activity)) {
@@ -45,6 +52,33 @@ public class MainActivity extends BaseActivity<ActivityMainBinding> {
         context.startActivity(intent);
     }
 
+    public static void startToTargetActivity(Context context, @MemberType String memberType) {
+        Intent intent = new Intent(context, MainActivity.class);
+        if (!(context instanceof Activity)) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        }
+        intent.putExtra(MEMBER_TYPE, memberType);
+        context.startActivity(intent);
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        if (intent != null) {
+            String targetType = intent.getStringExtra(MEMBER_TYPE);
+            if (targetType != null) {
+                if (targetType.equals(MemberType.APP_IMAGE_RECOVER)) {
+                    ImageRecoverActivity.start(this, MemberType.APP_IMAGE_RECOVER);
+                } else if (targetType.equals(MemberType.APP_FILE_RECOVER)) {
+                    FileRecoverActivity.start(this, false);
+                } else if (targetType.equals(MemberType.APP_VIDEO_RECOVER)) {
+                    VideoRecoverActivity.start(this, false);
+                } else if (targetType.equals(MemberType.APP_AUDIO_RECOVER)) {
+                    AudioRecoverActivity.start(this, false);
+                }
+            }
+        }
+    }
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {

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

@@ -1,6 +1,7 @@
 package com.datarecovery.master.module.main;
 
 import com.atmob.app.lib.base.BaseViewModel;
+import com.datarecovery.master.data.repositories.ConfigRepository;
 import com.datarecovery.master.data.repositories.MemberRepository;
 import com.datarecovery.master.utils.OrderReportHelper;
 import com.google.gson.Gson;
@@ -14,7 +15,7 @@ public class MainViewModel extends BaseViewModel {
 
 
     @Inject
-    public MainViewModel(Gson gson, MemberRepository memberRepository) {
+    public MainViewModel(Gson gson, MemberRepository memberRepository, ConfigRepository configRepository) {
         OrderReportHelper.init(gson, memberRepository);
     }
 }

+ 6 - 5
app/src/main/java/com/datarecovery/master/module/member/MemberActivity.java

@@ -25,6 +25,7 @@ import com.datarecovery.master.handler.EventHelper;
 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.main.MainActivity;
 import com.datarecovery.master.module.videorecover.VideoRecoverActivity;
 import com.datarecovery.master.module.wxrecover.WeChatRecoverActivity;
 import com.datarecovery.master.utils.BoxingUtil;
@@ -155,19 +156,19 @@ public class MemberActivity extends BaseActivity<ActivityMemberBinding> {
                 WeChatRecoverActivity.start(this, MemberType.APP_WX_FRIEND_RECOVER);
                 break;
             case MemberType.APP_IMAGE_RECOVER:
-                ImageRecoverActivity.start(this, MemberType.APP_IMAGE_RECOVER);
+                MainActivity.startToTargetActivity(this, MemberType.APP_IMAGE_RECOVER);
                 break;
             case MemberType.APP_FILE_RECOVER:
-                FileRecoverActivity.start(this);
+                MainActivity.startToTargetActivity(this, MemberType.APP_FILE_RECOVER);
                 break;
             case MemberType.APP_VIDEO_RECOVER:
-                VideoRecoverActivity.start(this);
+                MainActivity.startToTargetActivity(this, MemberType.APP_VIDEO_RECOVER);
                 break;
             case MemberType.APP_AUDIO_RECOVER:
-                AudioRecoverActivity.start(this);
+                MainActivity.startToTargetActivity(this, MemberType.APP_AUDIO_RECOVER);
                 break;
             case MemberType.APP_IMAGE_CLEAN:
-                ImageRecoverActivity.start(this, MemberType.APP_IMAGE_CLEAN);
+                MainActivity.startToTargetActivity(this, MemberType.APP_IMAGE_CLEAN);
                 break;
         }
     }

+ 2 - 2
app/src/main/java/com/datarecovery/master/module/mine/MineViewModel.java

@@ -108,12 +108,12 @@ public class MineViewModel extends BaseViewModel {
     public void onMemberClick() {
         EventHelper.report(EventId.hf1001101);
         //指定跳转至微信消息恢复
-        isHaveAuths(MemberType.APP_WX_MESSAGE_RECOVER, () -> WeChatRecoverActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_WX_MESSAGE_RECOVER));
+        isHaveAuths(MemberType.APP_WX_MESSAGE_RECOVER, isTrial -> WeChatRecoverActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_WX_MESSAGE_RECOVER));
     }
 
     public void isHaveAuths(@MemberType String type, HomePageViewModel.NextStepCallback stepCallback) {
         if (deviceFuncRepository.isHaveAuth(type)) {
-            if (stepCallback != null) stepCallback.onNextStep();
+            if (stepCallback != null) stepCallback.onNextStep(false);
         } else {
             MemberActivity.start(ActivityUtil.getTopActivity(), type);
         }

+ 33 - 18
app/src/main/java/com/datarecovery/master/module/preview/PreviewActivity.java

@@ -16,11 +16,15 @@ import androidx.annotation.Nullable;
 import androidx.viewpager2.widget.ViewPager2;
 
 import com.atmob.app.lib.base.BaseActivity;
+import com.atmob.common.logging.AtmobLog;
 import com.atmob.common.ui.SizeUtil;
+import com.datarecovery.master.R;
 import com.datarecovery.master.databinding.ActivityPreviewBinding;
+import com.datarecovery.master.dialog.CommonSureDialog;
 import com.datarecovery.master.utils.FilesSearch;
 import com.datarecovery.master.utils.ImageDeepDetector;
 import com.datarecovery.master.utils.SafeMediaPlayer;
+import com.datarecovery.master.utils.ToastUtil;
 import com.gyf.immersionbar.ImmersionBar;
 
 import java.lang.ref.WeakReference;
@@ -40,8 +44,8 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
     private MediaPlayer mediaPlayer;
     private boolean isSurfaceCreated;
 
-    private Timer timer;
-    private boolean isSeekbarChanging;
+
+    private CommonSureDialog showTrialExportFailDialog;
 
     private ViewPager2.OnPageChangeCallback onPageChangeCallback;
 
@@ -49,10 +53,11 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
     @interface Type {
     }
 
-    public static void startImagePreView(Context context, List<ImageDeepDetector.ImageFile> imageFileList, int position) {
+    public static void startImagePreView(Context context, List<ImageDeepDetector.ImageFile> imageFileList, int position, boolean isTrial) {
         Intent intent = new Intent(context, PreviewActivity.class);
         intent.putExtra("type", TYPE_IMG);
         intent.putExtra("position", position);
+        intent.putExtra("isTrial", isTrial);
         PreviewViewModel.weakReference = new WeakReference<>(imageFileList);
         if (!(context instanceof Activity)) {
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -60,9 +65,10 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
         context.startActivity(intent);
     }
 
-    public static void startDocumentPreView(Context context, @Type int type, FilesSearch.DocumentFile documentFile) {
+    public static void startDocumentPreView(Context context, @Type int type, FilesSearch.DocumentFile documentFile, boolean isTrial) {
         Intent intent = new Intent(context, PreviewActivity.class);
         intent.putExtra("type", type);
+        intent.putExtra("isTrial", isTrial);
         PreviewViewModel.weakReference = new WeakReference<>(documentFile);
         if (!(context instanceof Activity)) {
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -95,13 +101,26 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
     }
 
     private void initObserver() {
+        previewViewModel.getRefreshCurrentProgress().observe(this, o -> binding.previewAudioSeekbar.setProgress(mediaPlayer.getCurrentPosition()));
+        previewViewModel.getShowTrialExportFailDialog().observe(this, o -> showTrialExportFailDialog());
+    }
 
+    private void showTrialExportFailDialog() {
+        if (showTrialExportFailDialog == null) {
+            showTrialExportFailDialog = new CommonSureDialog(this);
+            showTrialExportFailDialog.setDialogTitle(R.string.trial_export_fail_title)
+                    .setDialogContent(R.string.trial_export_fail_content).setSureText(R.string.dialog_trial_recover);
+            showTrialExportFailDialog.setOnDialogClickListener(() -> previewViewModel.onTrialRecoverClick());
+        }
+        showTrialExportFailDialog.show();
     }
 
+
     private void initIntentData() {
         int type = getIntent().getIntExtra("type", 0);
         int position = getIntent().getIntExtra("position", 0);
-        previewViewModel.setPreviewData(type, position);
+        boolean isTrial = getIntent().getBooleanExtra("isTrial", false);
+        previewViewModel.setPreviewData(type, position, isTrial);
         if (type == TYPE_IMG) {
             initImagePreviewPager();
         } else {
@@ -127,24 +146,16 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
 
             @Override
             public void onStartTrackingTouch(SeekBar seekBar) {
-                isSeekbarChanging = true;
+                previewViewModel.setSeekbarChanging(true);
             }
 
             @Override
             public void onStopTrackingTouch(SeekBar seekBar) {
-                isSeekbarChanging = false;
+                previewViewModel.setSeekbarChanging(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);
+        previewViewModel.startMediaTimer();
     }
 
     private void initView() {
@@ -172,7 +183,7 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
     }
 
     private void initImagePreviewPager() {
-        PreviewImagePagerAdapter imagePagerAdapter = new PreviewImagePagerAdapter(previewViewModel.getImagePreviewList());
+        PreviewImagePagerAdapter imagePagerAdapter = new PreviewImagePagerAdapter(previewViewModel.getImagePreviewList(), previewViewModel.isTrial());
         binding.previewViewPager.setAdapter(imagePagerAdapter);
         onPageChangeCallback = new ViewPager2.OnPageChangeCallback() {
             @Override
@@ -223,9 +234,14 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
                 }
             });
             mediaPlayer.prepareAsync();
+            mediaPlayer.setOnErrorListener((mp, what, extra) -> {
+                ToastUtil.show(R.string.preview_source_error, ToastUtil.LENGTH_SHORT);
+                return false;
+            });
             mediaPlayer.setOnCompletionListener(mp -> {
                 if (previewViewModel.getType() == TYPE_VIDEO) {
                     previewViewModel.setIsPlaying(false);
+                    previewViewModel.setIsPlayStart(false);
                 } else if (previewViewModel.getType() == TYPE_AUDIO) {
                     mediaPlayer.seekTo(0);
                     previewViewModel.setIsPlaying(false);
@@ -239,7 +255,6 @@ public class PreviewActivity extends BaseActivity<ActivityPreviewBinding> {
     protected void onDestroy() {
         super.onDestroy();
         if (mediaPlayer != null) mediaPlayer.release();
-        if (timer != null) timer.cancel();
         binding.previewViewPager.unregisterOnPageChangeCallback(onPageChangeCallback);
     }
 }

+ 11 - 1
app/src/main/java/com/datarecovery/master/module/preview/PreviewImagePagerAdapter.java

@@ -1,6 +1,7 @@
 package com.datarecovery.master.module.preview;
 
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
@@ -14,9 +15,11 @@ import java.util.List;
 public class PreviewImagePagerAdapter extends RecyclerView.Adapter<PreviewImagePagerAdapter.ViewHolder> {
 
     private final List<ImageDeepDetector.ImageFile> imageFileList;
+    private final boolean watermark;
 
-    public PreviewImagePagerAdapter(List<ImageDeepDetector.ImageFile> imageFileList) {
+    public PreviewImagePagerAdapter(List<ImageDeepDetector.ImageFile> imageFileList, boolean watermark) {
         this.imageFileList = imageFileList;
+        this.watermark = watermark;
     }
 
     @NonNull
@@ -44,6 +47,13 @@ public class PreviewImagePagerAdapter extends RecyclerView.Adapter<PreviewImageP
         public ViewHolder(@NonNull ItemPreviewImgBinding binding) {
             super(binding.getRoot());
             this.binding = binding;
+            if (watermark) {
+                binding.watermarkView.setVisibility(View.VISIBLE);
+                binding.previewImg.setEnabled(false);
+            } else {
+                binding.watermarkView.setVisibility(View.GONE);
+                binding.previewImg.setEnabled(true);
+            }
         }
 
         public void bind(ImageDeepDetector.ImageFile imageFile) {

+ 102 - 9
app/src/main/java/com/datarecovery/master/module/preview/PreviewViewModel.java

@@ -6,11 +6,14 @@ import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
 
 import com.atmob.app.lib.base.BaseViewModel;
+import com.atmob.app.lib.livedata.SingleLiveEvent;
+import com.atmob.common.runtime.ActivityUtil;
 import com.atmob.common.runtime.ContextUtil;
 import com.datarecovery.master.R;
 import com.datarecovery.master.data.repositories.DeviceFuncRepository;
-import com.datarecovery.master.module.imgrecover.ImageRecoverActivity;
+import com.datarecovery.master.module.member.MemberActivity;
 import com.datarecovery.master.module.member.MemberType;
+import com.datarecovery.master.sdk.bugly.BuglyHelper;
 import com.datarecovery.master.utils.BoxingUtil;
 import com.datarecovery.master.utils.FileUtil;
 import com.datarecovery.master.utils.FilesSearch;
@@ -19,11 +22,14 @@ import com.datarecovery.master.utils.MediaStoreHelper;
 import com.datarecovery.master.utils.ToastUtil;
 
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Callable;
+import java.util.Timer;
+import java.util.TimerTask;
 
 import javax.inject.Inject;
 
+import atmob.reactivex.rxjava3.core.Completable;
 import atmob.rxjava.utils.RxJavaUtil;
 import dagger.hilt.android.lifecycle.HiltViewModel;
 
@@ -37,19 +43,36 @@ public class PreviewViewModel extends BaseViewModel {
     private final DeviceFuncRepository deviceFuncRepository;
     private List<ImageDeepDetector.ImageFile> imagePreviewList;
 
+    private final SingleLiveEvent<?> showTrialExportFailDialog = new SingleLiveEvent<>();
+    private final SingleLiveEvent<?> refreshCurrentProgress = new SingleLiveEvent<>();
+
     private int type;
 
     private final MutableLiveData<Boolean> isPlayStart = new MutableLiveData<>();
     private final MutableLiveData<ImageDeepDetector.ImageFile> currentImageFile = new MutableLiveData<>();
     private FilesSearch.DocumentFile currentFileFile;
     private int position;
-
+    private boolean isTrial;
+    private Timer timer;
+    private boolean isSeekbarChanging;
 
     @Inject
     public PreviewViewModel(DeviceFuncRepository deviceFuncRepository) {
         this.deviceFuncRepository = deviceFuncRepository;
     }
 
+    public LiveData<?> getRefreshCurrentProgress() {
+        return refreshCurrentProgress;
+    }
+
+    public void setSeekbarChanging(boolean seekbarChanging) {
+        isSeekbarChanging = seekbarChanging;
+    }
+
+    public LiveData<?> getShowTrialExportFailDialog() {
+        return showTrialExportFailDialog;
+    }
+
     public MutableLiveData<Boolean> getIsPlayStart() {
         return isPlayStart;
     }
@@ -74,6 +97,10 @@ public class PreviewViewModel extends BaseViewModel {
         return type;
     }
 
+    public boolean isTrial() {
+        return isTrial;
+    }
+
     public LiveData<Uri> getPreviewUri() {
         return previewUri;
     }
@@ -86,9 +113,10 @@ public class PreviewViewModel extends BaseViewModel {
         return isPlaying;
     }
 
-    public void setPreviewData(@PreviewActivity.Type int type, int position) {
+    public void setPreviewData(@PreviewActivity.Type int type, int position, boolean isTrial) {
         this.type = type;
         this.position = position;
+        this.isTrial = isTrial;
         switch (type) {
             case PreviewActivity.TYPE_AUDIO:
                 dealAudioInit();
@@ -102,6 +130,7 @@ public class PreviewViewModel extends BaseViewModel {
         }
     }
 
+
     private void dealVideoInit() {
         title.setValue(ContextUtil.getContext().getString(R.string.preview_video));
         if (weakReference != null) {
@@ -153,6 +182,18 @@ public class PreviewViewModel extends BaseViewModel {
             return;
         }
         this.isPlayStart.setValue(true);
+        if (isTrial) {
+            //倒计时播放一半
+            startHalfTimer();
+        }
+    }
+
+    private void startHalfTimer() {
+
+    }
+
+    public void setIsPlayStart(boolean isPlayStart) {
+        this.isPlayStart.setValue(isPlayStart);
     }
 
 
@@ -167,10 +208,27 @@ public class PreviewViewModel extends BaseViewModel {
     }
 
     public void onExportClick() {
-        if (type == PreviewActivity.TYPE_IMG && !deviceFuncRepository.isHaveAuth(MemberType.APP_IMAGE_RECOVER)) {
-            return;
-        } else if (type == PreviewActivity.TYPE_AUDIO && !deviceFuncRepository.isHaveAuth(MemberType.APP_AUDIO_RECOVER)) {
-            return;
+        if (isTrial) {
+            if (type == PreviewActivity.TYPE_IMG) {
+                ImageDeepDetector.ImageFile value = currentImageFile.getValue();
+                if (value == null) return;
+                if (!value.isTrial()) {
+                    showTrialExportFailDialog.call();
+                    return;
+                }
+            } else if (type == PreviewActivity.TYPE_AUDIO) {
+                if (currentFileFile == null) return;
+                if (!currentFileFile.isTrial()) {
+                    showTrialExportFailDialog.call();
+                    return;
+                }
+            }
+        } else {
+            if (type == PreviewActivity.TYPE_IMG && !deviceFuncRepository.isHaveAuth(MemberType.APP_IMAGE_RECOVER)) {
+                return;
+            } else if (type == PreviewActivity.TYPE_AUDIO && !deviceFuncRepository.isHaveAuth(MemberType.APP_AUDIO_RECOVER)) {
+                return;
+            }
         }
         RxJavaUtil.doInBackground(() -> {
                     if (type == PreviewActivity.TYPE_IMG) {
@@ -188,7 +246,18 @@ public class PreviewViewModel extends BaseViewModel {
                     return true;
                 }
                 , o -> ToastUtil.show(R.string.export_success, ToastUtil.LENGTH_SHORT)
-                , throwable -> ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT));
+                , throwable -> {
+                    ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);
+                    ArrayList<Object> failList = new ArrayList<>();
+                    if (type == PreviewActivity.TYPE_IMG) {
+                        failList.add(currentImageFile.getValue());
+                        BuglyHelper.postCatchedException(FileUtil.getFileListFailException(failList, "图片导出失败"));
+                    } else if (type == PreviewActivity.TYPE_AUDIO) {
+                        failList.add(currentFileFile);
+                        BuglyHelper.postCatchedException(FileUtil.getFileListFailException(failList, "音频导出失败"));
+                    }
+                }
+        );
 
     }
 
@@ -198,5 +267,29 @@ public class PreviewViewModel extends BaseViewModel {
         if (weakReference != null) {
             weakReference.clear();
         }
+        if (timer != null) timer.cancel();
+    }
+
+    public void onTrialRecoverClick() {
+        if (type == PreviewActivity.TYPE_IMG) {
+            MemberActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_IMAGE_RECOVER);
+        } else if (type == PreviewActivity.TYPE_AUDIO) {
+            MemberActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_AUDIO_RECOVER);
+        }
+    }
+
+    public void startMediaTimer() {
+        if (timer != null) {
+            return;
+        }
+        timer = new Timer();
+        timer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                if (!isSeekbarChanging) {
+                    refreshCurrentProgress.postValue(null);
+                }
+            }
+        }, 0, 100);
     }
 }

+ 37 - 12
app/src/main/java/com/datarecovery/master/module/videorecover/VideoRecoverActivity.java

@@ -15,6 +15,7 @@ import com.datarecovery.master.R;
 import com.datarecovery.master.databinding.ActivityVideoRecoverBinding;
 import com.datarecovery.master.dialog.CommonLoadingDialog;
 import com.datarecovery.master.dialog.CommonSureDialog;
+import com.datarecovery.master.dialog.ScanFileDialog;
 import com.datarecovery.master.dialog.ScanProgressDialog;
 import com.datarecovery.master.module.member.MemberType;
 import com.datarecovery.master.module.preview.PreviewActivity;
@@ -30,32 +31,42 @@ import dagger.hilt.android.AndroidEntryPoint;
 @AndroidEntryPoint
 public class VideoRecoverActivity extends BaseActivity<ActivityVideoRecoverBinding> {
 
-
+    private static final String IS_TRIAL = "is_trial";
     private VideoRecoverViewModel videoRecoverViewModel;
     private CommonLoadingDialog loadingDialog;
     private CommonSureDialog backDialog;
-    private ScanProgressDialog scanProgressDialog;
+    private ScanFileDialog scanFileDialog;
     private VideoItemAdapter videoItemAdapter;
+    private CommonSureDialog showTrialExportFailDialog;
 
 
-    public static void start(Context context) {
+    public static void start(Context context, boolean isTrial) {
         Intent intent = new Intent(context, VideoRecoverActivity.class);
         if (!(context instanceof Activity)) {
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         }
+        intent.putExtra(IS_TRIAL, isTrial);
         context.startActivity(intent);
     }
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        initData();
         initView();
         initObserver();
         initPermission();
     }
 
+    private void initData() {
+        Intent intent = getIntent();
+        if (intent != null) {
+            videoRecoverViewModel.setIsTrial(intent.getBooleanExtra(IS_TRIAL, false));
+        }
+    }
+
     private void initPermission() {
-        new FilePermissionHelper().requestDataFilePermission(this, MemberType.APP_VIDEO_RECOVER, new FilePermissionHelper.NextStepCallback() {
+        new FilePermissionHelper().requestDataFilePermission(this, MemberType.APP_VIDEO_RECOVER, videoRecoverViewModel.isTrial(), new FilePermissionHelper.NextStepCallback() {
             @Override
             public void onNextStep() {
                 videoRecoverViewModel.startVideoScanning();
@@ -69,9 +80,13 @@ public class VideoRecoverActivity extends BaseActivity<ActivityVideoRecoverBindi
     }
 
     private void initObserver() {
+        videoRecoverViewModel.getShowTrialExportFailDialog().observe(this, o -> showTrialExportFailDialog());
         videoRecoverViewModel.getShowLoadingEvent().observe(this, this::showLoadingDialog);
         videoRecoverViewModel.getDetectedVideoList().observe(this, list -> videoItemAdapter.submit(list));
-        videoRecoverViewModel.getDetectedFinish().observe(this, o -> scanProgressDialog.detectedFinish());
+        videoRecoverViewModel.getDetectedFinish().observe(this, o -> {
+            binding.ryVideoRecover.scrollToPosition(0);
+            scanFileDialog.detectedFinish();
+        });
         videoRecoverViewModel.getShowScanDialogEvent().observe(this, this::showScanProgressDialog);
     }
 
@@ -81,6 +96,16 @@ public class VideoRecoverActivity extends BaseActivity<ActivityVideoRecoverBindi
         initRecycleView();
     }
 
+    private void showTrialExportFailDialog() {
+        if (showTrialExportFailDialog == null) {
+            showTrialExportFailDialog = new CommonSureDialog(this);
+            showTrialExportFailDialog.setDialogTitle(R.string.trial_export_fail_title)
+                    .setDialogContent(R.string.trial_export_fail_content).setSureText(R.string.dialog_trial_recover);
+            showTrialExportFailDialog.setOnDialogClickListener(() -> videoRecoverViewModel.onTrialRecoverClick());
+        }
+        showTrialExportFailDialog.show();
+    }
+
     private void initRecycleView() {
         videoItemAdapter = new VideoItemAdapter(this);
         binding.ryVideoRecover.setLayoutManager(new GridLayoutManager(this, 3));
@@ -97,21 +122,21 @@ public class VideoRecoverActivity extends BaseActivity<ActivityVideoRecoverBindi
 
             @Override
             public void onItemClick(FilesSearch.DocumentFile file) {
-                PreviewActivity.startDocumentPreView(VideoRecoverActivity.this, PreviewActivity.TYPE_VIDEO, file);
+                PreviewActivity.startDocumentPreView(VideoRecoverActivity.this, PreviewActivity.TYPE_VIDEO, file, videoRecoverViewModel.isTrial());
             }
         });
     }
 
     public void showScanProgressDialog(Boolean show) {
         if (BoxingUtil.boxing(show)) {
-            if (scanProgressDialog == null) {
-                scanProgressDialog = new ScanProgressDialog(this, videoRecoverViewModel.getTotalDetectedCount());
-                scanProgressDialog.setOnCancelListener(() -> videoRecoverViewModel.cancelScan());
+            if (scanFileDialog == null) {
+                scanFileDialog = new ScanFileDialog(this, videoRecoverViewModel.getDetectedLastFileName());
+                scanFileDialog.setOnCancelListener(() -> videoRecoverViewModel.cancelScan());
             }
-            scanProgressDialog.show();
+            scanFileDialog.show();
         } else {
-            if (scanProgressDialog != null) {
-                scanProgressDialog.dismiss();
+            if (scanFileDialog != null) {
+                scanFileDialog.dismiss();
             }
         }
     }

+ 63 - 6
app/src/main/java/com/datarecovery/master/module/videorecover/VideoRecoverViewModel.java

@@ -7,15 +7,18 @@ import androidx.lifecycle.Transformations;
 import com.atmob.app.lib.base.BaseViewModel;
 import com.atmob.app.lib.livedata.SingleLiveEvent;
 import com.atmob.common.data.KVUtils;
+import com.atmob.common.runtime.ActivityUtil;
 import com.atmob.common.runtime.ContextUtil;
 import com.datarecovery.master.R;
 import com.datarecovery.master.data.consts.EventId;
 import com.datarecovery.master.data.repositories.DeviceFuncRepository;
 import com.datarecovery.master.handler.EventHelper;
+import com.datarecovery.master.module.member.MemberActivity;
 import com.datarecovery.master.module.member.MemberType;
 import com.datarecovery.master.sdk.bugly.BuglyHelper;
 import com.datarecovery.master.utils.FileUtil;
 import com.datarecovery.master.utils.FilesSearch;
+import com.datarecovery.master.utils.ImageDeepDetector;
 import com.datarecovery.master.utils.MediaStoreHelper;
 import com.datarecovery.master.utils.ToastUtil;
 
@@ -40,11 +43,15 @@ public class VideoRecoverViewModel extends BaseViewModel {
 
     private final String IS_SHOW_VIDEO_HINT = "isShowVideoHint";
     private final long SCANNING_COUNTDOWN = 1000 * 60 * 6;
+
+    private final long SCANNING_IS_TRIAL_COUNTDOWN = 1000 * 5;
     private final LiveData<String> selectedCountTxt;
+    private final MutableLiveData<String> detectedLastFileName = new MutableLiveData<>();
     private final MutableLiveData<Boolean> checkAll = new MutableLiveData<>(false);
     private final MutableLiveData<List<FilesSearch.DocumentFile>> detectedVideoList = new MutableLiveData<>(new ArrayList<>());
     private final MutableLiveData<List<FilesSearch.DocumentFile>> selectedList = new MutableLiveData<>(new ArrayList<>());
-
+    private final MutableLiveData<Boolean> showTrialView = new MutableLiveData<>();
+    private final SingleLiveEvent<?> showTrialExportFailDialog = new SingleLiveEvent<>();
     private final SingleLiveEvent<?> detectedFinish = new SingleLiveEvent<>();
     private final SingleLiveEvent<Boolean> showScanDialogEvent = new SingleLiveEvent<>();
     private final SingleLiveEvent<Boolean> showLoadingEvent = new SingleLiveEvent<>();
@@ -53,6 +60,7 @@ public class VideoRecoverViewModel extends BaseViewModel {
     private final MutableLiveData<Integer> totalDetectedCount = new MutableLiveData<>();
     private final MutableLiveData<Boolean> isShowHint = new MutableLiveData<>();
     private Disposable scanDisposable;
+    private boolean isTrial;
 
 
     @Inject
@@ -71,6 +79,23 @@ public class VideoRecoverViewModel extends BaseViewModel {
         EventHelper.timeEvent(EventId.hf1000525);
     }
 
+    public LiveData<String> getDetectedLastFileName() {
+        return detectedLastFileName;
+    }
+
+    public LiveData<?> getShowTrialExportFailDialog() {
+        return showTrialExportFailDialog;
+    }
+
+
+    public LiveData<Boolean> getShowTrialView() {
+        return showTrialView;
+    }
+
+    public boolean isTrial() {
+        return isTrial;
+    }
+
     public LiveData<Boolean> getIsShowHint() {
         return isShowHint;
     }
@@ -113,7 +138,7 @@ public class VideoRecoverViewModel extends BaseViewModel {
             return;
         }
         FilesSearch.search(ContextUtil.getContext(), FilesSearch.DocumentFile.VIDEO)
-                .take(SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
+                .take(isTrial ? SCANNING_IS_TRIAL_COUNTDOWN : SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(new Subscriber<List<FilesSearch.DocumentFile>>() {
                     @Override
@@ -134,8 +159,11 @@ public class VideoRecoverViewModel extends BaseViewModel {
                         totalCount += documentFiles.size();
                         totalDetectedCount.setValue(totalCount);
                         List<FilesSearch.DocumentFile> videoList = getList(detectedVideoList);
-                        videoList.addAll(0, documentFiles);
-                        detectedVideoList.setValue(videoList);
+                        for (FilesSearch.DocumentFile documentFile : documentFiles) {
+                            detectedLastFileName.setValue(documentFile.getName());
+                            videoList.add(0, documentFile);
+                            detectedVideoList.setValue(videoList);
+                        }
                     }
 
                     @Override
@@ -145,11 +173,22 @@ public class VideoRecoverViewModel extends BaseViewModel {
 
                     @Override
                     public void onComplete() {
+                        setFreeExport();
                         detectedFinish.call();
                     }
                 });
     }
 
+    private void setFreeExport() {
+        if (isTrial) {
+            showTrialView.setValue(true);
+        }
+        List<FilesSearch.DocumentFile> list = getList(detectedVideoList);
+        if (list.size() > 0) {
+            list.get(0).setTrial(true);
+        }
+    }
+
     public void onCheckAllClick(boolean isCheck) {
         checkAll.setValue(isCheck);
         List<FilesSearch.DocumentFile> detectedList = getList(detectedVideoList);
@@ -176,6 +215,7 @@ public class VideoRecoverViewModel extends BaseViewModel {
 
     public void cancelScan() {
         if (scanDisposable != null) scanDisposable.dispose();
+        setFreeExport();
     }
 
     public void onExportClick() {
@@ -183,8 +223,17 @@ public class VideoRecoverViewModel extends BaseViewModel {
         if (list == null || list.size() == 0) {
             return;
         }
-        if (!deviceFuncRepository.isHaveAuth(MemberType.APP_VIDEO_RECOVER)) {
-            return;
+        if (isTrial) {
+            for (FilesSearch.DocumentFile file : list) {
+                if (!file.isTrial()) {
+                    showTrialExportFailDialog.call();
+                    return;
+                }
+            }
+        } else {
+            if (!deviceFuncRepository.isHaveAuth(MemberType.APP_VIDEO_RECOVER)) {
+                return;
+            }
         }
         showLoadingEvent.setValue(true);
         RxJavaUtil.doInBackground(() -> {
@@ -227,4 +276,12 @@ public class VideoRecoverViewModel extends BaseViewModel {
         super.onCleared();
         EventHelper.report(EventId.hf1000525);
     }
+
+    public void setIsTrial(boolean isTrial) {
+        this.isTrial = isTrial;
+    }
+
+    public void onTrialRecoverClick() {
+        MemberActivity.start(ActivityUtil.getTopActivity(), MemberType.APP_VIDEO_RECOVER);
+    }
 }

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

@@ -31,9 +31,12 @@ public class FilePermissionHelper {
 
     Disposable disposable;
 
-    public void requestDataFilePermission(ComponentActivity activity, @MemberType String memberType, NextStepCallback stepCallback) {
+    public void requestDataFilePermission(ComponentActivity activity, @MemberType String memberType, boolean isSkipLogin, NextStepCallback stepCallback) {
         Single.just(Build.VERSION.SDK_INT)
                 .flatMap(sdkInt -> {
+                    if (isSkipLogin) {
+                        return Single.just(sdkInt);
+                    }
                     if (TextUtils.isEmpty(AccountRepository.token)) {
                         return (SingleSource<Integer>) observer -> activity.runOnUiThread(() -> {
                             PermissionDialog loginDialog = new PermissionDialog(activity);

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

@@ -196,6 +196,7 @@ public class FilesSearch {
         private boolean isCheck;
 
         private long lastModified;
+        private boolean isTrial;
 
         public DocumentFile(XFile xFile, int category) {
             this.xFile = xFile;
@@ -223,6 +224,16 @@ public class FilesSearch {
             this.sizeDescribe = FileUtil.formatShortBytes(this.size);
         }
 
+        @Bindable
+        public boolean isTrial() {
+            return isTrial;
+        }
+
+        public void setTrial(boolean trial) {
+            isTrial = trial;
+            notifyPropertyChanged(BR.trial);
+        }
+
         public long getLastModified() {
             return lastModified;
         }

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

@@ -541,6 +541,8 @@ public class ImageDeepDetector {
         private boolean isCheck;
         private long lastModified;
 
+        private boolean isTrial;
+
 
         public ImageFile(XFile xFile) {
             this(xFile, CATEGORY_UNKNOWN);
@@ -573,6 +575,16 @@ public class ImageDeepDetector {
             this.sizeDescribe = FileUtil.formatShortBytes(this.size);
         }
 
+        @Bindable
+        public boolean isTrial() {
+            return isTrial;
+        }
+
+        public void setTrial(boolean trial) {
+            isTrial = trial;
+            notifyPropertyChanged(BR.trial);
+        }
+
         public String getPath() {
             return path;
         }

+ 70 - 0
app/src/main/java/com/datarecovery/master/utils/WatermarkView.java

@@ -0,0 +1,70 @@
+package com.datarecovery.master.utils;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.atmob.common.ui.SizeUtil;
+import com.datarecovery.master.R;
+
+public class WatermarkView extends View {
+
+    private float textWidth;
+    private Paint paint;
+    private int textColor;
+    private int textSize;
+    private float rotate;
+    private int alpha;
+    private String watermarkText;
+
+    public WatermarkView(Context context) {
+        this(context, null);
+    }
+
+    public WatermarkView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public WatermarkView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WatermarkView);
+        textColor = typedArray.getColor(R.styleable.WatermarkView_watermark_color, 0);
+        alpha = typedArray.getInt(R.styleable.WatermarkView_watermark_alpha, 255);
+        textSize = typedArray.getDimensionPixelSize(R.styleable.WatermarkView_watermark_textSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()));
+        watermarkText = typedArray.getString(R.styleable.WatermarkView_watermark_text);
+        rotate = typedArray.getInt(R.styleable.WatermarkView_watermark_rotate, -15);
+        typedArray.recycle();
+
+        paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setTextSize(textSize);
+        paint.setColor(textColor);
+        paint.setAlpha(alpha);
+        paint.setFakeBoldText(true);
+
+        textWidth = paint.measureText(watermarkText);
+    }
+
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        int width = getMeasuredWidth();
+        int height = getMeasuredHeight();
+        canvas.rotate(rotate);
+        int index = 0;
+        for (int positionY = height / 10; positionY <= height; positionY += height / 10 + 70) {
+            float fromX = -width + (index++ % 2) * textWidth;
+            for (float positionX = fromX; positionX < width; positionX += textWidth * 2) {
+                canvas.drawText(watermarkText, positionX, positionY, paint);
+            }
+        }
+    }
+}

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


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


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


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


+ 8 - 0
app/src/main/res/drawable/bg_free_label.xml

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

+ 10 - 0
app/src/main/res/drawable/bg_go_recover_card.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <stroke
+        android:width="1dp"
+        android:color="@color/white" />
+    <corners android:radius="100dp" />
+    <gradient
+        android:endColor="#E3F0FC"
+        android:startColor="#C6DFFB" />
+</shape>

+ 8 - 0
app/src/main/res/drawable/bg_scan_file_dialog.xml

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

+ 10 - 0
app/src/main/res/drawable/bg_trial_go_recover.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="100dp" />
+    <stroke
+        android:width="1dp"
+        android:color="#FFF5DE" />
+    <gradient
+        android:endColor="#FFC671"
+        android:startColor="#FFE1B4" />
+</shape>

+ 68 - 2
app/src/main/res/layout/activity_audio_recover.xml

@@ -81,10 +81,10 @@
 
         <TextView
             android:id="@+id/tv_size_sort"
+            drawableEnd="@{audioRecoverViewModel.isSizeSortArrowUp ? @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_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"
@@ -102,16 +102,74 @@
             app:layout_constraintStart_toEndOf="@+id/v_line"
             app:layout_constraintTop_toTopOf="@+id/v_filter" />
 
+        <include
+            android:id="@+id/layout_trial"
+            layout="@layout/layout_trial"
+            progress="@{7}"
+            recoverClick="@{()->audioRecoverViewModel.onTrialRecoverClick()}"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_constraintTop_toBottomOf="@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"
+            app:layout_constraintTop_toBottomOf="@+id/layout_trial"
             tools:listitem="@layout/item_data_audio" />
 
         <View
+            android:id="@+id/v_go_recover"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="@dimen/app_common_page_horizontal_padding"
+            android:layout_marginBottom="12dp"
+            android:background="@drawable/bg_go_recover_card"
+            android:onClick="@{()->audioRecoverViewModel.onTrialRecoverClick()}"
+            app:layout_constraintBottom_toTopOf="@id/v_bottom"
+            app:layout_constraintDimensionRatio="255:36"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintWidth_percent="0.7083333333333333" />
+
+        <ImageView
+            android:id="@+id/iv_go_recover"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="5dp"
+            android:src="@drawable/icon_go_recover"
+            app:layout_constraintBottom_toBottomOf="@+id/v_go_recover"
+            app:layout_constraintDimensionRatio="58:26"
+            app:layout_constraintLeft_toLeftOf="@+id/v_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover"
+            app:layout_constraintWidth_percent="0.1611111111111111" />
+
+        <TextView
+            android:id="@+id/tv_go_recover"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="5dp"
+            android:text="@string/trial_go_recover_txt"
+            android:textColor="#333333"
+            android:textSize="12dp"
+            app:layout_constraintBottom_toBottomOf="@id/v_go_recover"
+            app:layout_constraintStart_toEndOf="@+id/iv_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover" />
+
+        <ImageView
+            android:id="@+id/iv_go_recover_right"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginEnd="9dp"
+            android:src="@drawable/icon_recover_right"
+            app:layout_constraintBottom_toBottomOf="@+id/v_go_recover"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintEnd_toEndOf="@+id/v_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover"
+            app:layout_constraintWidth_percent="0.05" />
+
+        <View
             android:id="@+id/v_bottom"
             android:layout_width="match_parent"
             android:layout_height="0dp"
@@ -138,5 +196,13 @@
             tools:background="@drawable/bg_common_btn"
             tools:text="立即导出" />
 
+
+        <androidx.constraintlayout.widget.Group
+            isGone="@{!audioRecoverViewModel.showTrialView}"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:constraint_referenced_ids="layout_trial,v_go_recover,iv_go_recover,tv_go_recover,iv_go_recover_right" />
+
+
     </androidx.constraintlayout.widget.ConstraintLayout>
 </layout>

+ 67 - 1
app/src/main/res/layout/activity_file_recover.xml

@@ -48,19 +48,78 @@
             app:tabIndicatorHeight="0dp"
             app:tabRippleColor="@color/transparent" />
 
+        <include
+            android:id="@+id/layout_trial"
+            layout="@layout/layout_trial"
+            progress="@{10}"
+            recoverClick="@{()->fileRecoverViewModel.onTrialRecoverClick()}"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_constraintTop_toBottomOf="@id/tab_layout" />
+
         <androidx.viewpager2.widget.ViewPager2
             android:id="@+id/file_view_pager"
             android:layout_width="match_parent"
             android:layout_height="0dp"
             android:background="#F8F8F8"
             app:layout_constraintBottom_toTopOf="@+id/v_bottom"
-            app:layout_constraintTop_toBottomOf="@+id/tab_layout"
+            app:layout_constraintTop_toBottomOf="@+id/layout_trial"
             tools:itemCount="6"
             tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
             tools:listitem="@layout/item_data_file"
             tools:spanCount="3" />
 
         <View
+            android:id="@+id/v_go_recover"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="@dimen/app_common_page_horizontal_padding"
+            android:layout_marginBottom="12dp"
+            android:background="@drawable/bg_go_recover_card"
+            android:onClick="@{()->fileRecoverViewModel.onTrialRecoverClick()}"
+            app:layout_constraintBottom_toTopOf="@+id/v_bottom"
+            app:layout_constraintDimensionRatio="255:36"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintWidth_percent="0.7083333333333333" />
+
+        <ImageView
+            android:id="@+id/iv_go_recover"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="5dp"
+            android:src="@drawable/icon_go_recover"
+            app:layout_constraintBottom_toBottomOf="@+id/v_go_recover"
+            app:layout_constraintDimensionRatio="58:26"
+            app:layout_constraintLeft_toLeftOf="@+id/v_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover"
+            app:layout_constraintWidth_percent="0.1611111111111111" />
+
+        <TextView
+            android:id="@+id/tv_go_recover"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="5dp"
+            android:text="@string/trial_go_recover_txt"
+            android:textColor="#333333"
+            android:textSize="12dp"
+            app:layout_constraintBottom_toBottomOf="@id/v_go_recover"
+            app:layout_constraintStart_toEndOf="@+id/iv_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover" />
+
+        <ImageView
+            android:id="@+id/iv_go_recover_right"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginEnd="9dp"
+            android:src="@drawable/icon_recover_right"
+            app:layout_constraintBottom_toBottomOf="@+id/v_go_recover"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintEnd_toEndOf="@+id/v_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover"
+            app:layout_constraintWidth_percent="0.05" />
+
+
+        <View
             android:id="@+id/v_bottom"
             android:layout_width="match_parent"
             android:layout_height="0dp"
@@ -87,5 +146,12 @@
             tools:background="@drawable/bg_common_btn"
             tools:text="立即导出" />
 
+        <androidx.constraintlayout.widget.Group
+            isGone="@{!fileRecoverViewModel.showTrialView}"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:constraint_referenced_ids="layout_trial,v_go_recover,iv_go_recover,tv_go_recover,iv_go_recover_right" />
+
+
     </androidx.constraintlayout.widget.ConstraintLayout>
 </layout>

+ 70 - 5
app/src/main/res/layout/activity_image_recover.xml

@@ -18,8 +18,8 @@
             android:id="@+id/tool_bar"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            app:navigationIcon="@drawable/icon_back"
-            app:layout_constraintTop_toTopOf="parent">
+            app:layout_constraintTop_toTopOf="parent"
+            app:navigationIcon="@drawable/icon_back">
 
             <TextView
                 style="@style/Tool_Bar_Title_Txt"
@@ -48,20 +48,78 @@
             app:tabIndicatorHeight="0dp"
             app:tabRippleColor="@color/transparent" />
 
+        <include
+            android:id="@+id/layout_trial"
+            layout="@layout/layout_trial"
+            progress="@{13}"
+            recoverClick="@{()->imageRecoverViewModel.onTrialRecoverClick()}"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_constraintTop_toBottomOf="@id/tab_layout" />
+
         <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/ry_image_recover"
-            android:background="#F8F8F8"
-            android:paddingHorizontal="@dimen/app_common_page_horizontal_padding"
             android:layout_width="match_parent"
             android:layout_height="0dp"
+            android:background="#F8F8F8"
+            android:paddingHorizontal="@dimen/app_common_page_horizontal_padding"
             app:layout_constraintBottom_toTopOf="@+id/v_bottom"
-            app:layout_constraintTop_toBottomOf="@+id/tab_layout"
+            app:layout_constraintTop_toBottomOf="@+id/layout_trial"
             tools:itemCount="6"
             tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
             tools:listitem="@layout/item_data_img"
             tools:spanCount="3" />
 
         <View
+            android:id="@+id/v_go_recover"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="@dimen/app_common_page_horizontal_padding"
+            android:layout_marginBottom="12dp"
+            android:background="@drawable/bg_go_recover_card"
+            android:onClick="@{()->imageRecoverViewModel.onTrialRecoverClick()}"
+            app:layout_constraintBottom_toTopOf="@+id/v_bottom"
+            app:layout_constraintDimensionRatio="255:36"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintWidth_percent="0.7083333333333333" />
+
+        <ImageView
+            android:id="@+id/iv_go_recover"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="5dp"
+            android:src="@drawable/icon_go_recover"
+            app:layout_constraintBottom_toBottomOf="@+id/v_go_recover"
+            app:layout_constraintDimensionRatio="58:26"
+            app:layout_constraintLeft_toLeftOf="@+id/v_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover"
+            app:layout_constraintWidth_percent="0.1611111111111111" />
+
+        <TextView
+            android:id="@+id/tv_go_recover"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="5dp"
+            android:text="@string/trial_go_recover_txt"
+            android:textColor="#333333"
+            android:textSize="12dp"
+            app:layout_constraintBottom_toBottomOf="@id/v_go_recover"
+            app:layout_constraintStart_toEndOf="@+id/iv_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover" />
+
+        <ImageView
+            android:id="@+id/iv_go_recover_right"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginEnd="9dp"
+            android:src="@drawable/icon_recover_right"
+            app:layout_constraintBottom_toBottomOf="@+id/v_go_recover"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintEnd_toEndOf="@+id/v_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover"
+            app:layout_constraintWidth_percent="0.05" />
+
+        <View
             android:id="@+id/v_bottom"
             android:layout_width="match_parent"
             android:layout_height="0dp"
@@ -70,6 +128,7 @@
             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}"
@@ -88,5 +147,11 @@
             tools:background="@drawable/bg_common_btn"
             tools:text="立即导出" />
 
+        <androidx.constraintlayout.widget.Group
+            isGone="@{!imageRecoverViewModel.showTrialView}"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:constraint_referenced_ids="layout_trial,v_go_recover,iv_go_recover,tv_go_recover,iv_go_recover_right" />
+
     </androidx.constraintlayout.widget.ConstraintLayout>
 </layout>

+ 25 - 9
app/src/main/res/layout/activity_preview.xml

@@ -60,18 +60,34 @@
             app:layout_constraintBottom_toTopOf="@+id/preview_bottom"
             app:layout_constraintTop_toBottomOf="@+id/preview_header">
 
-            <com.datarecovery.master.widget.ResizeAbleSurfaceView
-                android:id="@+id/preview_video"
+            <RelativeLayout
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
+                android:layout_height="0dp"
                 app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
+                app:layout_constraintTop_toTopOf="parent">
+
+                <com.datarecovery.master.widget.ResizeAbleSurfaceView
+                    android:id="@+id/preview_video"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerInParent="true" />
+
+                <com.datarecovery.master.utils.WatermarkView
+                    android:id="@+id/watermark_view"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    app:watermark_color="@color/white40"
+                    app:watermark_text="@string/export_without_watermark"
+                    isGone="@{!previewViewModel.isTrial}"
+                    app:watermark_textSize="14sp" />
+            </RelativeLayout>
+
 
             <View
-                android:background="#F8F8F8"
                 isGone="@{previewViewModel.isPlayStart}"
                 android:layout_width="match_parent"
-                android:layout_height="match_parent" />
+                android:layout_height="match_parent"
+                android:background="#F8F8F8" />
 
             <ImageView
                 imageUri="@{previewViewModel.previewUri}"
@@ -112,8 +128,8 @@
             app:layout_constraintTop_toTopOf="@id/preview_audio_play" />
 
         <ImageView
-            expandTouchSize="@{6}"
             android:id="@+id/preview_audio_play"
+            expandTouchSize="@{6}"
             android:layout_width="0dp"
             android:layout_height="0dp"
             android:layout_marginHorizontal="@dimen/app_common_page_horizontal_padding"
@@ -131,7 +147,6 @@
             android:layout_width="0dp"
             android:layout_height="0dp"
             android:layout_marginHorizontal="@dimen/app_common_page_horizontal_padding"
-            tools:progress="10"
             android:progressDrawable="@drawable/shape_preview_audio_seekbar"
             android:splitTrack="false"
             android:thumb="@drawable/shape_preview_audio_seekbar_thumb"
@@ -140,7 +155,8 @@
             app:layout_constraintDimensionRatio="252:14"
             app:layout_constraintLeft_toRightOf="@id/preview_audio_play"
             app:layout_constraintRight_toRightOf="@id/preview_audio_background"
-            app:layout_constraintTop_toTopOf="@id/preview_audio_play" />
+            app:layout_constraintTop_toTopOf="@id/preview_audio_play"
+            tools:progress="10" />
 
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/preview_bottom"

+ 84 - 22
app/src/main/res/layout/activity_video_recover.xml

@@ -37,6 +37,14 @@
 
         </androidx.appcompat.widget.Toolbar>
 
+        <include
+            android:id="@+id/layout_trial"
+            layout="@layout/layout_trial"
+            progress="@{8}"
+            recoverClick="@{()->videoRecoverViewModel.onTrialRecoverClick()}"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_constraintTop_toBottomOf="@id/tool_bar" />
 
         <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/ry_video_recover"
@@ -45,54 +53,102 @@
             android:background="#F8F8F8"
             android:paddingHorizontal="@dimen/app_common_page_horizontal_padding"
             app:layout_constraintBottom_toTopOf="@+id/v_bottom"
-            app:layout_constraintTop_toBottomOf="@+id/tool_bar"
+            app:layout_constraintTop_toBottomOf="@+id/layout_trial"
             tools:itemCount="6"
             tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
             tools:listitem="@layout/item_data_video"
             tools:spanCount="3" />
 
         <Space
-            app:layout_constraintBottom_toTopOf="@+id/v_bottom"
             android:id="@+id/space"
-            app:layout_constraintDimensionRatio="360:23"
             android:layout_width="match_parent"
-            android:layout_height="0dp" />
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toTopOf="@+id/v_bottom"
+            app:layout_constraintDimensionRatio="360:12" />
 
         <TextView
+            android:id="@+id/tv_hint"
             isGone="@{!videoRecoverViewModel.isShowHint}"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="@dimen/app_common_page_horizontal_padding"
+            android:background="@drawable/bg_video_preview_hint"
+            android:drawablePadding="4dp"
+            android:gravity="center_vertical"
             android:onClick="@{()-> videoRecoverViewModel.closeHintClick()}"
-            android:id="@+id/tv_hint"
-            android:text="@string/video_recover_hint"
             android:padding="5dp"
+            android:text="@string/video_recover_hint"
             android:textColor="#202020"
-            android:gravity="center_vertical"
             android:textSize="12dp"
-            android:drawablePadding="4dp"
+            app:drawableStartCompat="@drawable/icon_hint"
+            app:layout_constraintBottom_toTopOf="@+id/space"
             app:layout_constraintDimensionRatio="196:30"
-            android:background="@drawable/bg_video_preview_hint"
             app:layout_constraintStart_toStartOf="parent"
-            android:layout_marginStart="@dimen/app_common_page_horizontal_padding"
-            app:layout_constraintBottom_toTopOf="@+id/space"
-            app:layout_constraintWidth_percent="0.5444444444444444"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            app:drawableStartCompat="@drawable/icon_hint" />
+            app:layout_constraintWidth_percent="0.5444444444444444" />
 
         <TextView
             isGone="@{!videoRecoverViewModel.isShowHint}"
-            app:layout_constraintWidth_percent="0.1055555555555556"
-            android:gravity="center"
-            android:background="@drawable/bg_video_recover_close"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
             android:layout_marginEnd="5dp"
-            app:layout_constraintTop_toTopOf="@+id/tv_hint"
-            app:layout_constraintBottom_toBottomOf="@+id/tv_hint"
-            app:layout_constraintEnd_toEndOf="@+id/tv_hint"
+            android:background="@drawable/bg_video_recover_close"
+            android:gravity="center"
             android:text="@string/video_hint_close"
             android:textColor="@color/white"
             android:textSize="11dp"
+            app:layout_constraintBottom_toBottomOf="@+id/tv_hint"
             app:layout_constraintDimensionRatio="38:18"
+            app:layout_constraintEnd_toEndOf="@+id/tv_hint"
+            app:layout_constraintTop_toTopOf="@+id/tv_hint"
+            app:layout_constraintWidth_percent="0.1055555555555556" />
+
+        <View
+            android:id="@+id/v_go_recover"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="@dimen/app_common_page_horizontal_padding"
+            android:background="@drawable/bg_go_recover_card"
+            android:onClick="@{()->videoRecoverViewModel.onTrialRecoverClick()}"
+            app:layout_constraintBottom_toTopOf="@+id/space"
+            app:layout_constraintDimensionRatio="255:36"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintWidth_percent="0.7083333333333333" />
+
+        <ImageView
+            android:id="@+id/iv_go_recover"
             android:layout_width="0dp"
-            android:layout_height="0dp" />
+            android:layout_height="0dp"
+            android:layout_marginStart="5dp"
+            android:src="@drawable/icon_go_recover"
+            app:layout_constraintBottom_toBottomOf="@+id/v_go_recover"
+            app:layout_constraintDimensionRatio="58:26"
+            app:layout_constraintLeft_toLeftOf="@+id/v_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover"
+            app:layout_constraintWidth_percent="0.1611111111111111" />
+
+        <TextView
+            android:id="@+id/tv_go_recover"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="5dp"
+            android:text="@string/trial_go_recover_txt"
+            android:textColor="#333333"
+            android:textSize="12dp"
+            app:layout_constraintBottom_toBottomOf="@id/v_go_recover"
+            app:layout_constraintStart_toEndOf="@+id/iv_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover" />
+
+        <ImageView
+            android:id="@+id/iv_go_recover_right"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginEnd="9dp"
+            android:src="@drawable/icon_recover_right"
+            app:layout_constraintBottom_toBottomOf="@+id/v_go_recover"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintEnd_toEndOf="@+id/v_go_recover"
+            app:layout_constraintTop_toTopOf="@+id/v_go_recover"
+            app:layout_constraintWidth_percent="0.05" />
 
 
         <View
@@ -122,6 +178,12 @@
             tools:background="@drawable/bg_common_btn"
             tools:text="立即导出" />
 
+        <androidx.constraintlayout.widget.Group
+            isGone="@{!videoRecoverViewModel.showTrialView}"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:constraint_referenced_ids="layout_trial,v_go_recover,iv_go_recover,tv_go_recover,iv_go_recover_right" />
+
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 </layout>

+ 130 - 0
app/src/main/res/layout/dialog_scan_file.xml

@@ -0,0 +1,130 @@
+<?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="detectedFileName"
+            type="androidx.lifecycle.LiveData&lt;String>" />
+
+        <variable
+            name="cancelClick"
+            type="android.view.View.OnClickListener" />
+
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:keepScreenOn="true"
+        tools:background="@color/black70">
+
+
+        <Space
+            android:id="@+id/space"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            app:layout_constraintDimensionRatio="360:40"
+            app:layout_constraintTop_toTopOf="parent" />
+
+
+        <View
+            android:id="@+id/v_bg"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:background="@drawable/bg_scan_file_dialog"
+            app:layout_constraintDimensionRatio="280:200"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/space"
+            app:layout_constraintWidth_percent="0.7777777777777778" />
+
+        <TextView
+            android:id="@+id/tv_detected_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/scan_file_title"
+            android:textColor="#404040"
+            android:textSize="15sp"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toBottomOf="@id/v_bg"
+            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.3932584269662921" />
+
+        <Space
+            android:id="@+id/space2"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            app:layout_constraintDimensionRatio="360:10"
+            app:layout_constraintTop_toBottomOf="@+id/tv_detected_title" />
+
+
+        <com.opensource.svgaplayer.SVGAImageView
+            android:id="@+id/svga_scan"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:autoPlay="true"
+            app:layout_constraintDimensionRatio="156:104"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_percent="0.4333333333333333"
+            app:source="svga/scan_file.svga" />
+
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:orientation="horizontal"
+            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.62">
+
+            <com.opensource.svgaplayer.SVGAImageView
+                android:id="@+id/svga_file_icon"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                app:autoPlay="true"
+                app:source="svga/spin_in_circles.svga" />
+
+            <TextView
+                tools:text="sdadadadadadad"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="6dp"
+                android:ellipsize="start"
+                android:singleLine="true"
+                android:text="@{detectedFileName}"
+                android:textColor="#A7A7A7"
+                android:textSize="14sp" />
+
+        </LinearLayout>
+
+
+        <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.9146341463414634"
+            app:layout_constraintWidth_percent="0.3333333333333333" />
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 24 - 1
app/src/main/res/layout/item_data_audio.xml

@@ -63,6 +63,28 @@
             app:layout_constraintTop_toTopOf="parent" />
 
         <TextView
+            android:id="@+id/tv_free_label"
+            isGone="@{!file.isTrial}"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/bg_free_label"
+            android:paddingHorizontal="8dp"
+            android:paddingVertical="1dp"
+            android:text="@string/free"
+            android:textColor="@color/white"
+            android:textSize="12dp"
+            app:layout_constraintBaseline_toBaselineOf="@+id/tv_file_date"
+            app:layout_constraintLeft_toLeftOf="@+id/tv_file_name" />
+
+        <Space
+            isGone="@{!file.isTrial}"
+            android:id="@+id/space"
+            android:layout_width="4dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintStart_toEndOf="@+id/tv_free_label"
+            app:layout_constraintTop_toTopOf="@+id/tv_free_label" />
+
+        <TextView
             android:id="@+id/tv_file_name"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
@@ -79,6 +101,7 @@
             tools:text="记科技以大额外的们.doc" />
 
         <TextView
+            android:layout_marginTop="4dp"
             android:id="@+id/tv_file_date"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
@@ -86,7 +109,7 @@
             android:textColor="#A7A7A7"
             android:textSize="12sp"
             app:layout_constraintBottom_toBottomOf="@id/iv_file_type"
-            app:layout_constraintLeft_toLeftOf="@+id/tv_file_name"
+            app:layout_constraintStart_toEndOf="@+id/space"
             app:layout_constraintTop_toBottomOf="@+id/tv_file_name"
             tools:text="2023-12-20 14:12:55" />
 

+ 27 - 4
app/src/main/res/layout/item_data_file.xml

@@ -47,28 +47,51 @@
         <TextView
             android:id="@+id/tv_file_name"
             android:layout_width="0dp"
-            app:layout_constraintBottom_toTopOf="@+id/tv_file_date"
             android:layout_height="wrap_content"
             android:layout_marginStart="12dp"
             android:layout_marginEnd="@dimen/app_common_page_horizontal_padding"
             android:text="@{file.name}"
-            app:layout_constraintVertical_chainStyle="packed"
             android:textColor="#202020"
             android:textSize="14sp"
+            app:layout_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
-            app:layout_constraintBottom_toBottomOf="@id/iv_file_type"
+            android:id="@+id/tv_free_label"
+            isGone="@{!file.isTrial}"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/bg_free_label"
+            android:paddingHorizontal="8dp"
+            android:paddingVertical="1dp"
+            android:text="@string/free"
+            android:textColor="@color/white"
+            android:textSize="12dp"
+            app:layout_constraintBaseline_toBaselineOf="@+id/tv_file_date"
+            app:layout_constraintLeft_toLeftOf="@+id/tv_file_name" />
+
+        <Space
+            isGone="@{!file.isTrial}"
+            android:id="@+id/space"
+            android:layout_width="4dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintStart_toEndOf="@+id/tv_free_label"
+            app:layout_constraintTop_toTopOf="@+id/tv_free_label" />
+
+        <TextView
+            android:layout_marginTop="4dp"
             android:id="@+id/tv_file_date"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@{DateUtil.formatNormalDate(DateUtil.YYYY_MM_DD_HH_MM_SS,file.lastModified)}"
             android:textColor="#A7A7A7"
             android:textSize="12sp"
-            app:layout_constraintLeft_toLeftOf="@+id/tv_file_name"
+            app:layout_constraintBottom_toBottomOf="@id/iv_file_type"
+            app:layout_constraintStart_toEndOf="@+id/space"
             app:layout_constraintTop_toBottomOf="@+id/tv_file_name"
             tools:text="2023-12-20 14:12:55" />
 

+ 16 - 2
app/src/main/res/layout/item_data_img.xml

@@ -19,6 +19,7 @@
         android:layout_height="wrap_content">
 
         <ImageView
+            android:id="@+id/iv_bg"
             imageUri="@{file.uri}"
             radius="@{4}"
             android:layout_width="match_parent"
@@ -26,7 +27,20 @@
             android:scaleType="centerCrop"
             app:layout_constraintDimensionRatio="1:1"
             app:layout_constraintTop_toTopOf="parent"
-            tools:src="@color/colorPrimary" />
+            tools:src="@color/white" />
+
+        <TextView
+            isGone="@{!file.isTrial}"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/bg_free_label"
+            android:paddingHorizontal="8dp"
+            android:paddingVertical="1dp"
+            android:text="@string/free"
+            android:textColor="@color/white"
+            android:textSize="12dp"
+            app:layout_constraintEnd_toEndOf="@+id/iv_bg"
+            app:layout_constraintTop_toTopOf="@+id/iv_bg" />
 
         <View
             android:id="@+id/bg_label"
@@ -52,6 +66,7 @@
             expandTouchSize="@{4}"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:onClick="@{checkBoxClick}"
             android:src="@{file.check ? @drawable/icon_recover_checked : @drawable/icon_recover_un_check}"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintDimensionRatio="1:1"
@@ -59,7 +74,6 @@
             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" />

+ 14 - 1
app/src/main/res/layout/item_data_video.xml

@@ -21,12 +21,13 @@
         <ImageView
             imageUri="@{file.uri}"
             radius="@{4}"
+            android:id="@+id/iv_bg"
             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" />
+            tools:src="@color/white" />
 
         <View
             android:id="@+id/bg_label"
@@ -79,5 +80,17 @@
             app:layout_constraintWidth_percent="0.1923076923076923"
             tools:src="@drawable/icon_recover_un_check" />
 
+        <TextView
+            isGone="@{!file.isTrial}"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/bg_free_label"
+            android:paddingHorizontal="8dp"
+            android:paddingVertical="1dp"
+            android:text="@string/free"
+            android:textColor="@color/white"
+            android:textSize="12dp"
+            app:layout_constraintEnd_toEndOf="@+id/iv_bg"
+            app:layout_constraintTop_toTopOf="@+id/iv_bg" />
     </androidx.constraintlayout.widget.ConstraintLayout>
 </layout>

+ 21 - 3
app/src/main/res/layout/item_preview_img.xml

@@ -1,7 +1,25 @@
 <?xml version="1.0" encoding="utf-8"?>
-<com.github.chrisbanes.photoview.PhotoView xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:id="@+id/preview_img"
     android:layout_height="match_parent">
 
-</com.github.chrisbanes.photoview.PhotoView>
+    <com.github.chrisbanes.photoview.PhotoView
+        android:id="@+id/preview_img"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    </com.github.chrisbanes.photoview.PhotoView>
+
+
+    <com.datarecovery.master.utils.WatermarkView
+        android:id="@+id/watermark_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:watermark_color="@color/white40"
+        app:watermark_text="@string/export_without_watermark"
+        app:watermark_textSize="14sp">
+
+    </com.datarecovery.master.utils.WatermarkView>
+
+</FrameLayout>

+ 85 - 0
app/src/main/res/layout/layout_trial.xml

@@ -0,0 +1,85 @@
+<?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="progress"
+            type="Integer" />
+
+        <variable
+            name="recoverClick"
+            type="android.view.View.OnClickListener" />
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <View
+            android:onClick="@{recoverClick}"
+            android:id="@+id/v_trial"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:background="@drawable/bg_trial_recover"
+            app:layout_constraintDimensionRatio="360:72"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <ImageView
+            android:id="@+id/iv_trial_title"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="@dimen/app_common_page_horizontal_padding"
+            android:src="@drawable/icon_trial_title"
+            app:layout_constraintBottom_toBottomOf="@+id/v_trial"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintStart_toStartOf="@id/v_trial"
+            app:layout_constraintTop_toTopOf="@+id/v_trial"
+            app:layout_constraintVertical_bias="0.3076923076923077"
+            app:layout_constraintWidth_percent="0.0555555555555556" />
+
+        <TextView
+            android:id="@+id/tv_trial_progress"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="3dp"
+            android:textColor="#6A3420"
+            android:textSize="18sp"
+            android:textStyle="bold"
+            android:text="@{@string/trial_progress(progress)}"
+            app:layout_constraintBottom_toBottomOf="@+id/v_trial"
+            app:layout_constraintStart_toEndOf="@+id/iv_trial_title"
+            app:layout_constraintTop_toTopOf="@+id/v_trial"
+            app:layout_constraintVertical_bias="0.25"
+            tools:text="已扫描6%" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/trial_recover"
+            android:textColor="#B46B36"
+            android:textSize="13sp"
+            app:layout_constraintBottom_toBottomOf="@id/v_trial"
+            app:layout_constraintStart_toStartOf="@id/iv_trial_title"
+            app:layout_constraintTop_toTopOf="@id/v_trial"
+            app:layout_constraintVertical_bias="0.75" />
+
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginEnd="@dimen/app_common_page_horizontal_padding"
+            android:background="@drawable/bg_trial_go_recover"
+            android:gravity="center"
+            android:text="@string/trial_go_recover"
+            android:textColor="#6A3420"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="@id/v_trial"
+            app:layout_constraintDimensionRatio="84:32"
+            app:layout_constraintEnd_toEndOf="@+id/v_trial"
+            app:layout_constraintTop_toTopOf="@id/v_trial"
+            app:layout_constraintWidth_percent="0.2333333333333333" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 10 - 0
app/src/main/res/values/attr.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <declare-styleable name="WatermarkView">
+        <attr name="watermark_text" format="string" />
+        <attr name="watermark_alpha" format="integer" />
+        <attr name="watermark_color" format="color" />
+        <attr name="watermark_textSize" format="dimension" />
+        <attr name="watermark_rotate" format="float" />
+    </declare-styleable>
+</resources>

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

@@ -183,4 +183,15 @@
     <string name="order_auth_code">授权编码</string>
     <string name="net_error">网络异常</string>
     <string name="account_logout_fail">登出失败</string>
+    <string name="free">免费</string>
+    <string name="export_without_watermark">导出后无水印</string>
+    <string name="trial_recover">点击去恢复,开放深层扫描查找数据</string>
+    <string name="trial_go_recover">立即恢复</string>
+    <string name="trial_progress">已扫描%d%%</string>
+    <string name="scan_file_title">正在扫描数据</string>
+    <string name="trial_export_fail_title">无法导出</string>
+    <string name="trial_export_fail_content">您目前只可以导出带有「免费」图标的数据,如您想要导出其他数据,请点击去「去恢复」按钮。</string>
+    <string name="dialog_trial_recover">去恢复</string>
+    <string name="trial_go_recover_txt">开放深层扫描查找全部数据</string>
+    <string name="preview_source_error">资源加载异常</string>
 </resources>