Ver Fonte

增加订阅页

zk há 2 anos atrás
pai
commit
9709afb66f
44 ficheiros alterados com 1669 adições e 13 exclusões
  1. 12 0
      app/src/main/java/com/atmob/voiceai/data/api/AtmobApi.java
  2. 138 0
      app/src/main/java/com/atmob/voiceai/data/api/bean/GoodsBean.java
  3. 46 0
      app/src/main/java/com/atmob/voiceai/data/api/bean/PayOptionsBean.java
  4. 5 0
      app/src/main/java/com/atmob/voiceai/data/api/request/BaseRequest.java
  5. 23 0
      app/src/main/java/com/atmob/voiceai/data/api/request/OrderPayRequest.java
  6. 25 0
      app/src/main/java/com/atmob/voiceai/data/api/request/OrderStatusRequest.java
  7. 18 0
      app/src/main/java/com/atmob/voiceai/data/api/request/PayGoodsRequest.java
  8. 13 0
      app/src/main/java/com/atmob/voiceai/data/api/response/OrderPayResponse.java
  9. 14 0
      app/src/main/java/com/atmob/voiceai/data/api/response/OrderStatusResponse.java
  10. 43 0
      app/src/main/java/com/atmob/voiceai/data/api/response/PayGoodsResponse.java
  11. 3 3
      app/src/main/java/com/atmob/voiceai/data/consts/Constants.java
  12. 20 1
      app/src/main/java/com/atmob/voiceai/data/repositories/MemberRepository.java
  13. 44 0
      app/src/main/java/com/atmob/voiceai/dialog/SubRetentionDialog.java
  14. 7 2
      app/src/main/java/com/atmob/voiceai/module/clonevoice/CloneVoiceFragment.java
  15. 118 0
      app/src/main/java/com/atmob/voiceai/module/subscription/SubscriptionAdapter.java
  16. 104 0
      app/src/main/java/com/atmob/voiceai/module/subscription/SubscriptionPageActivity.java
  17. 168 0
      app/src/main/java/com/atmob/voiceai/module/subscription/SubscriptionPageViewModel.java
  18. BIN
      app/src/main/res/drawable-xxhdpi/bg_retention_mask.webp
  19. BIN
      app/src/main/res/drawable-xxhdpi/bg_sub_rectangle.webp
  20. BIN
      app/src/main/res/drawable-xxhdpi/bg_subscription_page.webp
  21. BIN
      app/src/main/res/drawable-xxhdpi/icon_default_avatar.png
  22. BIN
      app/src/main/res/drawable-xxhdpi/icon_default_avatar.webp
  23. BIN
      app/src/main/res/drawable-xxhdpi/icon_sub_allow.webp
  24. BIN
      app/src/main/res/drawable-xxhdpi/icon_sub_goods_not_select.webp
  25. BIN
      app/src/main/res/drawable-xxhdpi/icon_sub_goods_selected.webp
  26. BIN
      app/src/main/res/drawable-xxhdpi/icon_sub_popular.webp
  27. BIN
      app/src/main/res/drawable-xxhdpi/icon_subscription_back.webp
  28. 10 0
      app/src/main/res/drawable/bg_dialog_retention_member.xml
  29. 5 0
      app/src/main/res/drawable/bg_popular.xml
  30. 7 0
      app/src/main/res/drawable/bg_retention_operation.xml
  31. 5 0
      app/src/main/res/drawable/bg_retention_sub.xml
  32. 5 0
      app/src/main/res/drawable/bg_sub_goods_description.xml
  33. 9 0
      app/src/main/res/drawable/bg_sub_goods_list.xml
  34. 7 0
      app/src/main/res/drawable/bg_sub_goods_selected.xml
  35. 8 0
      app/src/main/res/drawable/bg_sub_line.xml
  36. 5 0
      app/src/main/res/drawable/bg_sub_no.xml
  37. 7 0
      app/src/main/res/drawable/bg_sub_pro.xml
  38. 8 0
      app/src/main/res/drawable/bg_sub_pro_card.xml
  39. 515 4
      app/src/main/res/layout/activity_subscription_page.xml
  40. 132 0
      app/src/main/res/layout/dialog_sub_retention.xml
  41. 0 1
      app/src/main/res/layout/fragment_clone_voice.xml
  42. 122 0
      app/src/main/res/layout/item_sub_goods_list.xml
  43. 22 1
      app/src/main/res/values/strings.xml
  44. 1 1
      build.gradle

+ 12 - 0
app/src/main/java/com/atmob/voiceai/data/api/AtmobApi.java

@@ -4,10 +4,16 @@ package com.atmob.voiceai.data.api;
 import com.atmob.app.lib.base.BaseResponse;
 import com.atmob.voiceai.data.api.request.BaseRequest;
 import com.atmob.voiceai.data.api.request.CloneDeleteRequest;
+import com.atmob.voiceai.data.api.request.OrderPayRequest;
+import com.atmob.voiceai.data.api.request.OrderStatusRequest;
+import com.atmob.voiceai.data.api.request.PayGoodsRequest;
 import com.atmob.voiceai.data.api.request.TextTosSpeechRequest;
 import com.atmob.voiceai.data.api.request.VoiceCloneRequest;
 import com.atmob.voiceai.data.api.request.VoiceHistoryRequest;
 import com.atmob.voiceai.data.api.request.VoiceListRequest;
+import com.atmob.voiceai.data.api.response.OrderPayResponse;
+import com.atmob.voiceai.data.api.response.OrderStatusResponse;
+import com.atmob.voiceai.data.api.response.PayGoodsResponse;
 import com.atmob.voiceai.data.api.response.TextToSpeechResponse;
 import com.atmob.voiceai.data.api.response.UserInfoResponse;
 import com.atmob.voiceai.data.api.response.VoiceCloneListResponse;
@@ -46,6 +52,12 @@ public interface AtmobApi {
     Single<BaseResponse<UserInfoResponse>> userInfo(@Body BaseRequest request);
 
 
+    @POST("/project/voice/v1/item/list")
+    Single<BaseResponse<PayGoodsResponse>> payGoodsList(@Body PayGoodsRequest request);
 
+    @POST("/project/voice/v1/order/pay")
+    Single<BaseResponse<OrderPayResponse>> orderPay(@Body OrderPayRequest request);
 
+    @POST("/project/voice/v1/order/status")
+    Single<BaseResponse<OrderStatusResponse>> orderStatus(@Body OrderStatusRequest request);
 }

+ 138 - 0
app/src/main/java/com/atmob/voiceai/data/api/bean/GoodsBean.java

@@ -0,0 +1,138 @@
+package com.atmob.voiceai.data.api.bean;
+
+import androidx.databinding.BaseObservable;
+import androidx.databinding.Bindable;
+
+import com.atmob.common.text.TextUtil;
+import com.atmob.voiceai.BR;
+import com.google.gson.annotations.SerializedName;
+
+public class GoodsBean extends BaseObservable {
+    @SerializedName("id")
+    private int id;
+    @SerializedName("sort")
+    private int sort;
+    @SerializedName("name")
+    private String name;
+    @SerializedName("amount")
+    private int amount;
+    @SerializedName("originalAmount")
+    private int originalAmount;
+    @SerializedName("googleProductId")
+    private String googleProductId;
+    @SerializedName("auth")
+    private String auth;
+    @SerializedName("content")
+    private String content;
+    @SerializedName("description")
+    private String description;
+    @SerializedName("subscriptionMillis")
+    private long subscriptionMillis;
+    @SerializedName("popular")
+    private boolean popular;
+
+    private boolean isSelect;
+
+    @Bindable
+    public boolean isSelect() {
+        return isSelect;
+    }
+
+    public void setSelect(boolean select) {
+        isSelect = select;
+        notifyPropertyChanged(BR.select);
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public int getSort() {
+        return sort;
+    }
+
+    public void setSort(int sort) {
+        this.sort = sort;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getAmount() {
+        return amount;
+    }
+
+    public void setAmount(int amount) {
+        this.amount = amount;
+    }
+
+    public int getOriginalAmount() {
+        return originalAmount;
+    }
+
+    public void setOriginalAmount(int originalAmount) {
+        this.originalAmount = originalAmount;
+    }
+
+    public String getGoogleProductId() {
+        return googleProductId;
+    }
+
+    public void setGoogleProductId(String googleProductId) {
+        this.googleProductId = googleProductId;
+    }
+
+    public String getAuth() {
+        return auth;
+    }
+
+    public void setAuth(String auth) {
+        this.auth = auth;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public long getSubscriptionMillis() {
+        return subscriptionMillis;
+    }
+
+    public void setSubscriptionMillis(long subscriptionMillis) {
+        this.subscriptionMillis = subscriptionMillis;
+    }
+
+    public boolean isPopular() {
+        return popular;
+    }
+
+    public void setPopular(boolean popular) {
+        this.popular = popular;
+    }
+
+    public String getDollarAmount(int nub) {
+        return TextUtil.formatFloatWithout0End((float) (amount / 100.0), nub);
+    }
+
+}

+ 46 - 0
app/src/main/java/com/atmob/voiceai/data/api/bean/PayOptionsBean.java

@@ -0,0 +1,46 @@
+package com.atmob.voiceai.data.api.bean;
+
+import com.google.gson.annotations.SerializedName;
+
+public class PayOptionsBean {
+    @SerializedName("id")
+    private int id;
+    @SerializedName("payMethod")
+    private int payMethod;
+    @SerializedName("payPlatform")
+    private int payPlatform;
+    @SerializedName("title")
+    private String title;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public int getPayMethod() {
+        return payMethod;
+    }
+
+    public void setPayMethod(int payMethod) {
+        this.payMethod = payMethod;
+    }
+
+    public int getPayPlatform() {
+        return payPlatform;
+    }
+
+    public void setPayPlatform(int payPlatform) {
+        this.payPlatform = payPlatform;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+}

+ 5 - 0
app/src/main/java/com/atmob/voiceai/data/api/request/BaseRequest.java

@@ -2,13 +2,18 @@ package com.atmob.voiceai.data.api.request;
 
 import com.atmob.user.param.AtmobParams;
 import com.atmob.voiceai.BuildConfig;
+import com.google.gson.annotations.SerializedName;
 
 import java.lang.reflect.Field;
 
 public class BaseRequest extends AtmobParams {
 
+    @SerializedName("appPlatform")
+    private final int appPlatform;
+
 
     public BaseRequest() {
+        this.appPlatform = 1;
 //        if (BuildConfig.DEBUG) {
 //            try {
 //                Field androidId = AtmobParams.class.getDeclaredField("androidId");

+ 23 - 0
app/src/main/java/com/atmob/voiceai/data/api/request/OrderPayRequest.java

@@ -0,0 +1,23 @@
+package com.atmob.voiceai.data.api.request;
+
+import com.google.gson.annotations.SerializedName;
+
+public class OrderPayRequest extends BaseRequest {
+
+
+    @SerializedName("itemId")
+    private String itemId;
+
+    @SerializedName("payPlatform")
+    private String payPlatform;
+
+    @SerializedName("payMethod")
+    private String payMethod;
+
+
+    public OrderPayRequest(String itemId, String payPlatform, String payMethod) {
+        this.itemId = itemId;
+        this.payPlatform = payPlatform;
+        this.payMethod = payMethod;
+    }
+}

+ 25 - 0
app/src/main/java/com/atmob/voiceai/data/api/request/OrderStatusRequest.java

@@ -0,0 +1,25 @@
+package com.atmob.voiceai.data.api.request;
+
+import com.google.gson.annotations.SerializedName;
+
+public class OrderStatusRequest extends BaseRequest {
+
+    @SerializedName("outTradeNo")
+    private String outTradeNo;
+
+    @SerializedName("receiptData")
+    private String receiptData;
+
+    public OrderStatusRequest(String outTradeNo, String receiptData) {
+        this.outTradeNo = outTradeNo;
+        this.receiptData = receiptData;
+    }
+
+    public String getOutTradeNo() {
+        return outTradeNo;
+    }
+
+    public String getReceiptData() {
+        return receiptData;
+    }
+}

+ 18 - 0
app/src/main/java/com/atmob/voiceai/data/api/request/PayGoodsRequest.java

@@ -0,0 +1,18 @@
+package com.atmob.voiceai.data.api.request;
+
+import com.google.gson.annotations.SerializedName;
+
+public class PayGoodsRequest extends BaseRequest {
+
+
+    @SerializedName("attribution")
+    private boolean attribution;
+
+    public PayGoodsRequest(boolean attribution) {
+        this.attribution = attribution;
+    }
+
+    public boolean isAttribution() {
+        return attribution;
+    }
+}

+ 13 - 0
app/src/main/java/com/atmob/voiceai/data/api/response/OrderPayResponse.java

@@ -0,0 +1,13 @@
+package com.atmob.voiceai.data.api.response;
+
+import com.google.gson.annotations.SerializedName;
+
+public class OrderPayResponse {
+
+    @SerializedName("outTradeNo")
+    private String outTradeNo;
+
+    public String getOutTradeNo() {
+        return outTradeNo;
+    }
+}

+ 14 - 0
app/src/main/java/com/atmob/voiceai/data/api/response/OrderStatusResponse.java

@@ -0,0 +1,14 @@
+package com.atmob.voiceai.data.api.response;
+
+import com.google.gson.annotations.SerializedName;
+
+public class OrderStatusResponse {
+
+    @SerializedName("payStatus")
+    private int payStatus;
+
+
+    public int getPayStatus() {
+        return payStatus;
+    }
+}

+ 43 - 0
app/src/main/java/com/atmob/voiceai/data/api/response/PayGoodsResponse.java

@@ -0,0 +1,43 @@
+package com.atmob.voiceai.data.api.response;
+
+import com.atmob.voiceai.data.api.bean.GoodsBean;
+import com.atmob.voiceai.data.api.bean.PayOptionsBean;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+public class PayGoodsResponse {
+
+
+    @SerializedName("list")
+    private List<GoodsBean> list;
+    @SerializedName("payOptions")
+    private List<PayOptionsBean> payOptions;
+    @SerializedName("attribution")
+    private boolean attribution;
+
+    public List<GoodsBean> getList() {
+        return list;
+    }
+
+    public void setList(List<GoodsBean> list) {
+        this.list = list;
+    }
+
+    public List<PayOptionsBean> getPayOptions() {
+        return payOptions;
+    }
+
+    public void setPayOptions(List<PayOptionsBean> payOptions) {
+        this.payOptions = payOptions;
+    }
+
+    public boolean isAttribution() {
+        return attribution;
+    }
+
+    public void setAttribution(boolean attribution) {
+        this.attribution = attribution;
+    }
+
+}

+ 3 - 3
app/src/main/java/com/atmob/voiceai/data/consts/Constants.java

@@ -7,10 +7,10 @@ public class Constants {
 
     public static final String Atmob_Server_Base_URL = BuildConfig.HOST;
 
-    public static final String PRIVACY_POLICY = "http://cdn.myaskai.cn/manyue/static/wjsjhfds-manyue-a-privacy.html";
+    public static final String PRIVACY_POLICY = "https://sites.google.com/view/funnyvoiceai-policy/home";
 
-    public static final String USER_AGREEMENT = "http://cdn.myaskai.cn/manyue/static/wjsjhfds-manyue-clause.html";
-    public static final String SEND_EMAIL_ADDRESS = "zhoukun@atmob.com";
+    public static final String USER_AGREEMENT = "https://sites.google.com/view/funny-voice-ai-termsofservice/home";
+    public static final String SEND_EMAIL_ADDRESS = "matanetworktech@gmail.com";
 
 
 }

+ 20 - 1
app/src/main/java/com/atmob/voiceai/data/repositories/MemberRepository.java

@@ -3,18 +3,30 @@ package com.atmob.voiceai.data.repositories;
 
 import androidx.lifecycle.MutableLiveData;
 
+import com.atmob.app.lib.handler.RxHttpHandler;
+import com.atmob.voiceai.data.api.AtmobApi;
+import com.atmob.voiceai.data.api.request.PayGoodsRequest;
+import com.atmob.voiceai.data.api.request.TextTosSpeechRequest;
+import com.atmob.voiceai.data.api.response.PayGoodsResponse;
+import com.atmob.voiceai.data.api.response.TextToSpeechResponse;
+
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import atmob.reactivex.rxjava3.core.Single;
+import atmob.rxjava.utils.RxJavaUtil;
+
 @Singleton
 public class MemberRepository {
 
 
     private final MutableLiveData<Boolean> isMember = new MutableLiveData<>();
+    private final AtmobApi atmobApi;
 
 
     @Inject
-    public MemberRepository() {
+    public MemberRepository(AtmobApi atmobApi) {
+        this.atmobApi = atmobApi;
     }
 
 
@@ -25,4 +37,11 @@ public class MemberRepository {
     public void setIsMember(Boolean isMember) {
         this.isMember.setValue(isMember);
     }
+
+
+    public Single<PayGoodsResponse> payGoodsList() {
+        return atmobApi.payGoodsList(new PayGoodsRequest(false))
+                .compose(RxHttpHandler.handle(true))
+                .compose(RxJavaUtil.SingleSchedule.io2Main());
+    }
 }

+ 44 - 0
app/src/main/java/com/atmob/voiceai/dialog/SubRetentionDialog.java

@@ -0,0 +1,44 @@
+package com.atmob.voiceai.dialog;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.atmob.app.lib.base.BaseDialog;
+import com.atmob.voiceai.R;
+import com.atmob.voiceai.databinding.DialogSubRetentionBinding;
+
+@BaseDialog.FullScreen
+public class SubRetentionDialog extends BaseDialog<DialogSubRetentionBinding> {
+
+
+    private ActionHandler actionHandler;
+
+    public SubRetentionDialog(@NonNull Context context) {
+        super(context, R.style.Theme_Common_Dialog);
+        setCancelable(false);
+        binding.setOnSubClick(v -> {
+            dismiss();
+            if (actionHandler != null) {
+                actionHandler.onSubRetentionClick();
+            }
+        });
+        binding.setOnCancelClick(v -> {
+            dismiss();
+            if (actionHandler != null) {
+                actionHandler.onCancelClick();
+            }
+        });
+    }
+
+    public void setActionHandler(ActionHandler actionHandler) {
+        this.actionHandler = actionHandler;
+    }
+
+    public interface ActionHandler {
+
+        void onSubRetentionClick();
+
+        void onCancelClick();
+    }
+}

+ 7 - 2
app/src/main/java/com/atmob/voiceai/module/clonevoice/CloneVoiceFragment.java

@@ -7,6 +7,7 @@ import android.os.Bundle;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.widget.SeekBar;
+
 import androidx.activity.result.ActivityResult;
 import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.NonNull;
@@ -16,6 +17,7 @@ import androidx.media3.common.MediaItem;
 import androidx.media3.common.PlaybackException;
 import androidx.media3.common.Player;
 import androidx.media3.exoplayer.ExoPlayer;
+
 import com.atmob.app.lib.base.BaseFragment;
 import com.atmob.common.logging.AtmobLog;
 import com.atmob.common.ui.SizeUtil;
@@ -28,7 +30,9 @@ import com.atmob.voiceai.utils.AudioRecorder;
 import com.atmob.voiceai.utils.ToastUtil;
 import com.atmob.voiceai.widget.AudioProgressBar;
 import com.gyf.immersionbar.ImmersionBar;
+
 import java.io.IOException;
+
 import dagger.hilt.android.AndroidEntryPoint;
 
 @AndroidEntryPoint
@@ -169,8 +173,9 @@ public class CloneVoiceFragment extends BaseFragment<FragmentCloneVoiceBinding>
                 binding.vStatusBar.setAlpha(1f);
                 binding.vMenu.setAlpha(1f);
             } else {
-                binding.vStatusBar.setAlpha(scrollY * 1f / maxScrollY);
-                binding.vMenu.setAlpha(scrollY * 1f / maxScrollY);
+                float alpha = scrollY * 1f / maxScrollY;
+                binding.vStatusBar.setAlpha(alpha);
+                binding.vMenu.setAlpha(alpha);
             }
         });
     }

+ 118 - 0
app/src/main/java/com/atmob/voiceai/module/subscription/SubscriptionAdapter.java

@@ -0,0 +1,118 @@
+package com.atmob.voiceai.module.subscription;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.recyclerview.widget.AsyncListDiffer;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.atmob.voiceai.data.api.bean.GoodsBean;
+
+import com.atmob.voiceai.databinding.ItemSubGoodsListBinding;
+
+import java.util.List;
+import java.util.Objects;
+
+public class SubscriptionAdapter extends RecyclerView.Adapter<SubscriptionAdapter.ViewHolder> {
+
+
+    private ActionHandler actionHandler;
+    private final AsyncListDiffer<GoodsBean> listDiffer;
+    private final LifecycleOwner lifecycleOwner;
+
+    private GoodsBean checkedGoodsBean;
+
+
+    public SubscriptionAdapter(LifecycleOwner lifecycleOwner) {
+        this.lifecycleOwner = lifecycleOwner;
+        listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<GoodsBean>() {
+            @Override
+            public boolean areItemsTheSame(@NonNull GoodsBean oldItem, @NonNull GoodsBean newItem) {
+                return oldItem.getId() == newItem.getId();
+            }
+
+            @Override
+            public boolean areContentsTheSame(@NonNull GoodsBean oldItem, @NonNull GoodsBean newItem) {
+                return Objects.equals(oldItem.getName(), newItem.getName()) &&
+                        oldItem.getAmount() == newItem.getAmount() &&
+                        oldItem.getOriginalAmount() == newItem.getOriginalAmount() &&
+                        Objects.equals(oldItem.getDescription(), newItem.getDescription()) &&
+                        Objects.equals(oldItem.getContent(), newItem.getContent()) &&
+                        oldItem.isPopular() == newItem.isPopular();
+            }
+        });
+    }
+
+    public void setActionHandler(ActionHandler actionHandler) {
+        this.actionHandler = actionHandler;
+    }
+
+    @NonNull
+    @Override
+    public SubscriptionAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        Context context = parent.getContext();
+        LayoutInflater layoutInflater = LayoutInflater.from(context);
+        return new SubscriptionAdapter.ViewHolder(ItemSubGoodsListBinding.inflate(layoutInflater, parent, false));
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull SubscriptionAdapter.ViewHolder holder, int position) {
+        holder.bind(listDiffer.getCurrentList().get(position), position);
+    }
+
+    @Override
+    public int getItemCount() {
+        return listDiffer.getCurrentList().size();
+    }
+
+    public void submitList(List<GoodsBean> list) {
+        if (list != null && !list.isEmpty()) {
+            checkedGoodsBean = list.get(0);
+            checkedGoodsBean.setSelect(true);
+        } else {
+            checkedGoodsBean = null;
+        }
+        if (actionHandler != null) {
+            actionHandler.onItemClick(checkedGoodsBean);
+        }
+        listDiffer.submitList(list);
+    }
+
+    public class ViewHolder extends RecyclerView.ViewHolder {
+
+        ItemSubGoodsListBinding binding;
+
+        public ViewHolder(@NonNull ItemSubGoodsListBinding binding) {
+            super(binding.getRoot());
+            this.binding = binding;
+            binding.setLifecycleOwner(lifecycleOwner);
+            binding.setOnItemClick(v -> {
+                GoodsBean viewBean = binding.getGoodsBean();
+                if (viewBean == checkedGoodsBean) {
+                    return;
+                }
+                if (checkedGoodsBean != null) {
+                    checkedGoodsBean.setSelect(false);
+                }
+                checkedGoodsBean = viewBean;
+                checkedGoodsBean.setSelect(true);
+                if (actionHandler != null) {
+                    actionHandler.onItemClick(viewBean);
+                }
+            });
+        }
+
+        public void bind(GoodsBean goodsBean, int position) {
+            binding.setShowDivider(position != listDiffer.getCurrentList().size() - 1);
+            binding.setGoodsBean(goodsBean);
+        }
+    }
+
+    public interface ActionHandler {
+        void onItemClick(GoodsBean goodsBean);
+    }
+}

+ 104 - 0
app/src/main/java/com/atmob/voiceai/module/subscription/SubscriptionPageActivity.java

@@ -3,9 +3,23 @@ package com.atmob.voiceai.module.subscription;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.widget.NestedScrollView;
+import androidx.media3.common.C;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.atmob.app.lib.base.BaseActivity;
+import com.atmob.common.ui.SizeUtil;
+import com.atmob.voiceai.data.api.bean.GoodsBean;
 import com.atmob.voiceai.databinding.ActivitySubscriptionPageBinding;
+import com.atmob.voiceai.dialog.SubRetentionDialog;
+import com.atmob.voiceai.utils.BoxingUtil;
 
 import dagger.hilt.android.AndroidEntryPoint;
 
@@ -13,6 +27,15 @@ import dagger.hilt.android.AndroidEntryPoint;
 public class SubscriptionPageActivity extends BaseActivity<ActivitySubscriptionPageBinding> {
 
 
+    private SubscriptionPageViewModel subscriptionPageViewModel;
+
+    private int maxScrollY;
+    private int minScrollY;
+
+    private SubscriptionAdapter subscriptionAdapter;
+
+    private SubRetentionDialog subRetentionDialog;
+
     public static void start(Context context) {
         Intent intent = new Intent(context, SubscriptionPageActivity.class);
         if (!(context instanceof Activity)) {
@@ -21,9 +44,90 @@ public class SubscriptionPageActivity extends BaseActivity<ActivitySubscriptionP
         context.startActivity(intent);
     }
 
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        initView();
+        initObserver();
+    }
+
+    private void initObserver() {
+        subscriptionPageViewModel.getShowRetentionDialogEvent().observe(this, o -> showSubRetentionDialog());
+        subscriptionPageViewModel.getFinishEvent().observe(this, o -> finish());
+        subscriptionPageViewModel.getGoodsList().observe(this, list -> subscriptionAdapter.submitList(list));
+    }
+
+    private void initView() {
+        initScrollView();
+        initGoodsList();
+    }
+
+
+    private void showSubRetentionDialog() {
+        if (subRetentionDialog == null) {
+            subRetentionDialog = new SubRetentionDialog(this);
+            subRetentionDialog.setActionHandler(new SubRetentionDialog.ActionHandler() {
+                @Override
+                public void onSubRetentionClick() {
+                    subscriptionPageViewModel.onSubRetentionClick();
+                }
+
+                @Override
+                public void onCancelClick() {
+                    finish();
+                }
+            });
+        }
+        subscriptionPageViewModel.setRetentionTime();
+        subRetentionDialog.show();
+    }
+
+    private void initGoodsList() {
+        subscriptionAdapter = new SubscriptionAdapter(this);
+        binding.rvGoodsList.setAdapter(subscriptionAdapter);
+        binding.rvGoodsList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
+        subscriptionAdapter.setActionHandler(goodsBean -> subscriptionPageViewModel.setCheckGoodsBean(goodsBean));
+    }
+
+    private void initScrollView() {
+        maxScrollY = (int) SizeUtil.dp2px(46);
+        minScrollY = (int) SizeUtil.dp2px(20);
+        binding.scrollView.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) (v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            if (scrollY > maxScrollY) {
+                binding.vStatusBar.setAlpha(1f);
+                binding.vMenu.setAlpha(1f);
+                binding.tvBarTitle.setAlpha(1f);
+            } else if (scrollY <= minScrollY) {
+                binding.vStatusBar.setAlpha(0f);
+                binding.vMenu.setAlpha(0f);
+                binding.tvBarTitle.setAlpha(0f);
+            } else {
+                float alpha = (scrollY - minScrollY) * 1f / (maxScrollY - minScrollY);
+                binding.vStatusBar.setAlpha(alpha);
+                binding.vMenu.setAlpha(alpha);
+                binding.tvBarTitle.setAlpha(alpha);
+            }
+        });
+    }
+
 
     @Override
     protected boolean shouldImmersion() {
         return true;
     }
+
+    @Override
+    protected void initViewModel() {
+        super.initViewModel();
+        subscriptionPageViewModel = getViewModelProvider().get(SubscriptionPageViewModel.class);
+        binding.setSubscriptionViewModel(subscriptionPageViewModel);
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (!BoxingUtil.boxing(subscriptionPageViewModel.getIsShowCloseBtn().getValue())) {
+            return;
+        }
+        subscriptionPageViewModel.onBackClick();
+    }
 }

+ 168 - 0
app/src/main/java/com/atmob/voiceai/module/subscription/SubscriptionPageViewModel.java

@@ -0,0 +1,168 @@
+package com.atmob.voiceai.module.subscription;
+
+import android.os.SystemClock;
+
+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.data.KVUtils;
+import com.atmob.common.runtime.ContextUtil;
+import com.atmob.voiceai.R;
+import com.atmob.voiceai.data.api.bean.GoodsBean;
+import com.atmob.voiceai.data.api.bean.PayOptionsBean;
+import com.atmob.voiceai.data.api.response.PayGoodsResponse;
+import com.atmob.voiceai.data.repositories.MemberRepository;
+import com.atmob.voiceai.helper.ErrorHelper;
+import com.atmob.voiceai.utils.SpannableUtil;
+import com.atmob.voiceai.utils.ToastUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import atmob.reactivex.rxjava3.annotations.NonNull;
+import atmob.reactivex.rxjava3.core.SingleObserver;
+import atmob.reactivex.rxjava3.disposables.Disposable;
+import atmob.reactivex.rxjava3.functions.Action;
+import atmob.rxjava.utils.RxJavaUtil;
+import dagger.hilt.android.lifecycle.HiltViewModel;
+
+@HiltViewModel
+public class SubscriptionPageViewModel extends BaseViewModel {
+
+
+    private final String LAST_BACK_SHOW_RETAIN_TIME = "last_back_show_retain_time";
+
+    private final SingleLiveEvent<?> showRetentionDialogEvent = new SingleLiveEvent<>();
+    private final SingleLiveEvent<?> finishEvent = new SingleLiveEvent<>();
+
+    private final MutableLiveData<Boolean> isShowCloseBtn = new MutableLiveData<>();
+    private final MutableLiveData<List<GoodsBean>> goodsList = new MutableLiveData<>();
+    private List<PayOptionsBean> payList;
+    private final MutableLiveData<CharSequence> autoRenewableText = new MutableLiveData<>();
+    private final MutableLiveData<CharSequence> subCancelTxt = new MutableLiveData<>();
+    private final MemberRepository memberRepository;
+
+    private GoodsBean checkGoodsBean;
+
+    @Inject
+    public SubscriptionPageViewModel(MemberRepository memberRepository) {
+        this.memberRepository = memberRepository;
+        init();
+        refreshSubscriptionDetail();
+    }
+
+    public void setCheckGoodsBean(GoodsBean checkGoodsBean) {
+        this.checkGoodsBean = checkGoodsBean;
+    }
+
+    public LiveData<?> getShowRetentionDialogEvent() {
+        return showRetentionDialogEvent;
+    }
+
+    public LiveData<?> getFinishEvent() {
+        return finishEvent;
+    }
+
+    public LiveData<Boolean> getIsShowCloseBtn() {
+        return isShowCloseBtn;
+    }
+
+    public LiveData<List<GoodsBean>> getGoodsList() {
+        return goodsList;
+    }
+
+    private void init() {
+        initSubTxt();
+        addDisposable(RxJavaUtil.timer(3000, TimeUnit.MILLISECONDS, () -> {
+            isShowCloseBtn.setValue(true);
+        }));
+    }
+
+    private void initSubTxt() {
+        String allRenewableTxt = "Subscription is auto-renewable.";
+        String targetRenewableTxt = "auto-renewable";
+        autoRenewableText.setValue(SpannableUtil.getSpannableStringBuilder(allRenewableTxt, targetRenewableTxt, ContextUtil.getContext().getResources().getColor(R.color.colorPrimaryVariant), false));
+        String allCancelTxt = "You can cancel your subscription at any time.";
+        String targetCancelTxt = "at any time";
+        subCancelTxt.setValue(SpannableUtil.getSpannableStringBuilder(allCancelTxt, targetCancelTxt, ContextUtil.getContext().getResources().getColor(R.color.colorPrimaryVariant), false));
+    }
+
+    public LiveData<CharSequence> getAutoRenewableText() {
+        return autoRenewableText;
+    }
+
+    public LiveData<CharSequence> getSubCancelTxt() {
+        return subCancelTxt;
+    }
+
+    public void onBackClick() {
+        if (isShowRetentionDialog()) {
+            showRetentionDialogEvent.call();
+        } else {
+            finishEvent.call();
+        }
+    }
+
+    public void onRestoreClick() {
+
+    }
+
+
+    private void refreshSubscriptionDetail() {
+        memberRepository.payGoodsList().subscribe(new SingleObserver<PayGoodsResponse>() {
+            @Override
+            public void onSubscribe(@NonNull Disposable d) {
+                addDisposable(d);
+            }
+
+            @Override
+            public void onSuccess(@NonNull PayGoodsResponse payGoodsResponse) {
+                goodsList.setValue(payGoodsResponse.getList());
+                payList = payGoodsResponse.getPayOptions();
+            }
+
+            @Override
+            public void onError(@NonNull Throwable e) {
+                ErrorHelper.errorThrowableToast(e, ToastUtil.LENGTH_SHORT);
+            }
+        });
+    }
+
+    public void setRetentionTime() {
+        KVUtils.getDefault().putLong(LAST_BACK_SHOW_RETAIN_TIME, SystemClock.elapsedRealtime());
+    }
+
+    public boolean isShowRetentionDialog() {
+        long lastShowTime = KVUtils.getDefault().getLong(LAST_BACK_SHOW_RETAIN_TIME, 0);
+        return SystemClock.elapsedRealtime() - lastShowTime > 12 * 60 * 60 * 1000;
+    }
+
+    @Override
+    protected void onCleared() {
+        super.onCleared();
+    }
+
+    public void onSubNowClick() {
+        if (checkGoodsBean == null) {
+            ToastUtil.show(R.string.please_select_goods, ToastUtil.LENGTH_SHORT);
+            return;
+        }
+        if (payList == null || payList.isEmpty()) {
+            ToastUtil.show(R.string.no_pay_way, ToastUtil.LENGTH_SHORT);
+            return;
+        }
+        PayOptionsBean payOptionsBean = payList.get(0);
+        int payMethod = payOptionsBean.getPayMethod();
+        int payPlatform = payOptionsBean.getPayPlatform();
+
+    }
+
+    public void onSubRetentionClick() {
+        onSubNowClick();
+    }
+}

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


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


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


BIN
app/src/main/res/drawable-xxhdpi/icon_default_avatar.png


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


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


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


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


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


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


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

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners
+        android:topLeftRadius="20dp"
+        android:topRightRadius="20dp" />
+    <gradient
+        android:angle="-45"
+        android:endColor="#06D6A0"
+        android:startColor="#00BA82" />
+</shape>

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

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

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

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

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

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

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

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

+ 9 - 0
app/src/main/res/drawable/bg_sub_goods_list.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="20dp" />
+    <solid android:color="#202320" />
+
+    <stroke
+        android:width="2dp"
+        android:color="#32333C" />
+</shape>

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

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="20dp" />
+    <stroke
+        android:width="2dp"
+        android:color="@color/colorPrimaryVariant" />
+</shape>

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

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+        android:gradientRadius="500"
+        android:endColor="#00415355"
+        android:startColor="#415355"
+        android:type="radial" />
+</shape>

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

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

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

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="5dp" />
+    <gradient
+        android:startColor="#12F45D"
+        android:endColor="#0594D7" />
+</shape>

+ 8 - 0
app/src/main/res/drawable/bg_sub_pro_card.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="20dp" />
+    <stroke
+        android:width="2dp"
+        android:color="#415355" />
+    <solid android:color="#1A0B1A1D" />
+</shape>

+ 515 - 4
app/src/main/res/layout/activity_subscription_page.xml

@@ -1,6 +1,517 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
+<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">
 
-</androidx.constraintlayout.widget.ConstraintLayout>
+    <data>
+
+        <variable
+            name="subscriptionViewModel"
+            type="com.atmob.voiceai.module.subscription.SubscriptionPageViewModel" />
+
+        <import type="com.atmob.common.ui.SizeUtil" />
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:src="@drawable/bg_subscription_page"
+            app:layout_constraintDimensionRatio="1080:2400"
+            app:layout_constraintTop_toTopOf="parent" />
+
+
+        <androidx.core.widget.NestedScrollView
+            android:id="@+id/scroll_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layout_constrainedHeight="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/space_sub"
+            app:layout_constraintTop_toTopOf="parent">
+
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <Space
+                    android:id="@+id/space_status_bar"
+                    android:layout_width="match_parent"
+                    android:layout_height="@{SizeUtil.getStatusBarHeight(), default=@dimen/app_status_bar_height}"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+                <Space
+                    android:id="@+id/space1"
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp"
+                    app:layout_constraintDimensionRatio="360:58"
+                    app:layout_constraintTop_toBottomOf="@+id/space_status_bar" />
+
+                <TextView
+                    android:id="@+id/tv_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sub_title"
+                    android:textColor="@color/white"
+                    android:textSize="24sp"
+                    android:textStyle="bold"
+                    app:layout_constraintHorizontal_chainStyle="packed"
+                    app:layout_constraintLeft_toLeftOf="parent"
+                    app:layout_constraintRight_toLeftOf="@+id/tv_pro"
+                    app:layout_constraintTop_toBottomOf="@+id/space1" />
+
+                <TextView
+                    android:id="@+id/tv_pro"
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:layout_marginStart="7dp"
+                    android:background="@drawable/bg_sub_pro"
+                    android:gravity="center"
+                    android:text="@string/sub_pro"
+                    android:textColor="@color/white"
+                    android:textStyle="bold"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_title"
+                    app:layout_constraintDimensionRatio="48:23"
+                    app:layout_constraintLeft_toRightOf="@+id/tv_title"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toTopOf="@+id/tv_title"
+                    app:layout_constraintWidth_percent="0.1333333333333333" />
+
+                <TextView
+                    android:id="@+id/tv_sub_desc"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="4dp"
+                    android:text="@string/sub_unlock_features"
+                    android:textColor="@color/white80"
+                    android:textSize="14sp"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/tv_title" />
+
+                <Space
+                    android:id="@+id/space2"
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp"
+                    app:layout_constraintDimensionRatio="360:16"
+                    app:layout_constraintTop_toBottomOf="@+id/tv_sub_desc" />
+
+                <Space
+                    android:id="@+id/space3"
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp"
+                    app:layout_constraintDimensionRatio="360:5"
+                    app:layout_constraintTop_toBottomOf="@+id/space2" />
+
+                <View
+                    android:id="@+id/v_sub_card"
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp"
+                    android:layout_marginHorizontal="16dp"
+                    android:background="@drawable/bg_sub_pro_card"
+                    app:layout_constraintBottom_toBottomOf="@+id/iv_sub"
+                    app:layout_constraintTop_toBottomOf="@+id/space3" />
+
+                <View
+                    android:id="@+id/v_sub_line"
+                    android:layout_width="0dp"
+                    android:layout_height="1dp"
+                    android:layout_marginHorizontal="12dp"
+                    android:background="@drawable/bg_sub_line"
+                    app:layout_constraintBottom_toBottomOf="@+id/v_sub_card"
+                    app:layout_constraintEnd_toEndOf="@id/v_sub_card"
+                    app:layout_constraintStart_toStartOf="@id/v_sub_card"
+                    app:layout_constraintTop_toTopOf="@id/v_sub_card"
+                    app:layout_constraintVertical_bias="0.1546762589928058" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sub_basic"
+                    android:textColor="@color/white"
+                    android:textSize="14sp"
+                    app:layout_constraintBottom_toBottomOf="@+id/v_sub_line"
+                    app:layout_constraintEnd_toStartOf="@id/iv_sub"
+                    app:layout_constraintStart_toEndOf="@+id/space_guideline"
+                    app:layout_constraintTop_toTopOf="@id/v_sub_card" />
+
+                <TextView
+                    android:id="@+id/tv_function1"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sub_generation_times"
+                    android:textColor="@color/white"
+                    android:textSize="14sp"
+                    app:layout_constraintBottom_toTopOf="@+id/tv_function2"
+                    app:layout_constraintEnd_toEndOf="@id/space_guideline"
+                    app:layout_constraintStart_toEndOf="@+id/space_line_start"
+                    app:layout_constraintTop_toTopOf="@+id/v_sub_line" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:text="3/Day"
+                    android:textColor="@color/white"
+                    android:textSize="14sp"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_function1"
+                    app:layout_constraintEnd_toStartOf="@id/iv_sub"
+                    app:layout_constraintStart_toEndOf="@+id/tv_function1"
+                    app:layout_constraintTop_toTopOf="@+id/tv_function1" />
+
+
+                <TextView
+                    android:id="@+id/tv_function2"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sub_quantity"
+                    android:textColor="@color/white"
+                    android:textSize="14sp"
+                    app:layout_constraintBottom_toTopOf="@+id/tv_function3"
+                    app:layout_constraintEnd_toEndOf="@id/space_guideline"
+                    app:layout_constraintStart_toStartOf="@+id/tv_function1"
+                    app:layout_constraintTop_toBottomOf="@id/tv_function1" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:text="5"
+                    android:textColor="@color/white"
+                    android:textSize="14sp"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_function2"
+                    app:layout_constraintEnd_toStartOf="@id/iv_sub"
+                    app:layout_constraintStart_toEndOf="@+id/tv_function2"
+                    app:layout_constraintTop_toTopOf="@+id/tv_function2" />
+
+                <TextView
+                    android:id="@+id/tv_function3"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sub_clone_voice"
+                    android:textColor="@color/white"
+                    android:textSize="14sp"
+                    app:layout_constraintBottom_toTopOf="@+id/tv_function4"
+                    app:layout_constraintEnd_toEndOf="@id/space_guideline"
+                    app:layout_constraintStart_toStartOf="@+id/tv_function1"
+                    app:layout_constraintTop_toBottomOf="@id/tv_function2" />
+
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:background="@drawable/bg_sub_no"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_function3"
+                    app:layout_constraintDimensionRatio="15:3"
+                    app:layout_constraintEnd_toStartOf="@id/iv_sub"
+                    app:layout_constraintStart_toEndOf="@+id/tv_function3"
+                    app:layout_constraintTop_toTopOf="@+id/tv_function3"
+                    app:layout_constraintWidth_percent="0.0416666666666667" />
+
+                <TextView
+                    android:id="@+id/tv_function4"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sub_faster_processing"
+                    android:textColor="@color/white"
+                    android:textSize="14sp"
+                    app:layout_constraintBottom_toTopOf="@+id/tv_function5"
+                    app:layout_constraintEnd_toEndOf="@id/space_guideline"
+                    app:layout_constraintStart_toStartOf="@+id/tv_function1"
+                    app:layout_constraintTop_toBottomOf="@id/tv_function3" />
+
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:background="@drawable/bg_sub_no"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_function4"
+                    app:layout_constraintDimensionRatio="15:3"
+                    app:layout_constraintEnd_toStartOf="@id/iv_sub"
+                    app:layout_constraintStart_toEndOf="@+id/tv_function4"
+                    app:layout_constraintTop_toTopOf="@+id/tv_function4"
+                    app:layout_constraintWidth_percent="0.0416666666666667" />
+
+                <TextView
+                    android:id="@+id/tv_function5"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sub_remove_ads"
+                    android:textColor="@color/white"
+                    android:textSize="14sp"
+                    app:layout_constraintBottom_toBottomOf="@id/v_sub_card"
+                    app:layout_constraintEnd_toEndOf="@id/space_guideline"
+                    app:layout_constraintStart_toStartOf="@+id/tv_function1"
+                    app:layout_constraintTop_toBottomOf="@id/tv_function4" />
+
+
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:background="@drawable/bg_sub_no"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_function5"
+                    app:layout_constraintDimensionRatio="15:3"
+                    app:layout_constraintEnd_toStartOf="@id/iv_sub"
+                    app:layout_constraintStart_toEndOf="@+id/tv_function5"
+                    app:layout_constraintTop_toTopOf="@+id/tv_function5"
+                    app:layout_constraintWidth_percent="0.0416666666666667" />
+
+                <Space
+                    android:id="@+id/space_guideline"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    app:layout_constraintStart_toStartOf="@+id/v_sub_card"
+                    app:layout_constraintTop_toTopOf="@+id/v_sub_card"
+                    app:layout_constraintWidth_percent="0.45" />
+
+                <Space
+                    android:id="@+id/space_line_start"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    app:layout_constraintStart_toStartOf="@+id/v_sub_card"
+                    app:layout_constraintTop_toTopOf="@+id/v_sub_card"
+                    app:layout_constraintWidth_percent="0.0777777777777778" />
+
+
+                <ImageView
+                    android:id="@+id/iv_sub"
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:layout_marginEnd="16dp"
+                    android:src="@drawable/bg_sub_rectangle"
+                    app:layout_constraintDimensionRatio="291:849"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/space2"
+                    app:layout_constraintWidth_percent="0.2694444444444444" />
+
+                <ImageView
+                    android:id="@+id/iv_vip"
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:src="@drawable/icon_vip"
+                    app:layout_constraintBottom_toBottomOf="@+id/iv_sub"
+                    app:layout_constraintDimensionRatio="1:1"
+                    app:layout_constraintHorizontal_chainStyle="packed"
+                    app:layout_constraintLeft_toLeftOf="@+id/iv_sub"
+                    app:layout_constraintRight_toLeftOf="@+id/tv_sub_pro"
+                    app:layout_constraintTop_toTopOf="@+id/iv_sub"
+                    app:layout_constraintVertical_bias="0.060377358490566"
+                    app:layout_constraintWidth_percent="0.05" />
+
+                <TextView
+                    android:id="@+id/tv_sub_pro"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="5dp"
+                    android:text="@string/sub_pro"
+                    android:textColor="@color/colorPrimaryVariant"
+                    android:textSize="16sp"
+                    app:layout_constraintBottom_toBottomOf="@+id/iv_vip"
+                    app:layout_constraintLeft_toRightOf="@+id/iv_vip"
+                    app:layout_constraintRight_toRightOf="@+id/iv_sub"
+                    app:layout_constraintTop_toTopOf="@+id/iv_vip" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sub_unlimited"
+                    android:textColor="@color/white"
+                    android:textSize="14sp"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_function1"
+                    app:layout_constraintEnd_toEndOf="@+id/iv_sub"
+                    app:layout_constraintStart_toStartOf="@id/iv_sub"
+                    app:layout_constraintTop_toTopOf="@+id/tv_function1" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sub_unlimited"
+                    android:textColor="@color/white"
+                    android:textSize="14sp"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_function2"
+                    app:layout_constraintEnd_toEndOf="@+id/iv_sub"
+                    app:layout_constraintStart_toStartOf="@id/iv_sub"
+                    app:layout_constraintTop_toTopOf="@+id/tv_function2" />
+
+                <ImageView
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:src="@drawable/icon_sub_allow"
+                    android:textColor="@color/white"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_function3"
+                    app:layout_constraintDimensionRatio="1:1"
+                    app:layout_constraintEnd_toEndOf="@+id/iv_sub"
+                    app:layout_constraintStart_toStartOf="@id/iv_sub"
+                    app:layout_constraintTop_toTopOf="@+id/tv_function3" />
+
+                <ImageView
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:src="@drawable/icon_sub_allow"
+                    android:textColor="@color/white"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_function4"
+                    app:layout_constraintDimensionRatio="1:1"
+                    app:layout_constraintEnd_toEndOf="@+id/iv_sub"
+                    app:layout_constraintStart_toStartOf="@id/iv_sub"
+                    app:layout_constraintTop_toTopOf="@+id/tv_function4" />
+
+                <ImageView
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:src="@drawable/icon_sub_allow"
+                    android:textColor="@color/white"
+                    app:layout_constraintBottom_toBottomOf="@+id/tv_function5"
+                    app:layout_constraintDimensionRatio="1:1"
+                    app:layout_constraintEnd_toEndOf="@+id/iv_sub"
+                    app:layout_constraintStart_toStartOf="@id/iv_sub"
+                    app:layout_constraintTop_toTopOf="@+id/tv_function5" />
+
+                <Space
+                    android:id="@+id/space4"
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp"
+                    app:layout_constraintDimensionRatio="360:24"
+                    app:layout_constraintTop_toBottomOf="@id/v_sub_card" />
+
+                <androidx.recyclerview.widget.RecyclerView
+                    android:id="@+id/rv_goods_list"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginHorizontal="16dp"
+                    android:background="@drawable/bg_sub_goods_list"
+                    android:overScrollMode="never"
+                    app:layout_constraintTop_toBottomOf="@+id/space4"
+                    tools:itemCount="3"
+                    tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+                    tools:listitem="@layout/item_sub_goods_list"
+                    tools:orientation="vertical" />
+
+                <Space
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp"
+                    app:layout_constraintDimensionRatio="360:19"
+                    app:layout_constraintTop_toBottomOf="@+id/rv_goods_list" />
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+
+        </androidx.core.widget.NestedScrollView>
+
+        <View
+            android:id="@+id/v_status_bar"
+            android:layout_width="match_parent"
+            android:layout_height="@{SizeUtil.getStatusBarHeight(), default=@dimen/app_status_bar_height}"
+            android:alpha="0"
+            android:background="@color/colorPrimary"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <View
+            android:id="@+id/v_menu"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:alpha="0"
+            android:background="@color/colorPrimary"
+            app:layout_constraintDimensionRatio="360:65"
+            app:layout_constraintTop_toBottomOf="@+id/v_status_bar" />
+
+        <TextView
+            android:id="@+id/tv_bar_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:alpha="0"
+            android:text="@string/sub_bar_title"
+            android:textColor="@color/white"
+            android:textSize="17sp"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toBottomOf="@+id/v_menu"
+            app:layout_constraintEnd_toEndOf="@+id/v_menu"
+            app:layout_constraintStart_toStartOf="@+id/v_menu"
+            app:layout_constraintTop_toTopOf="@+id/v_menu" />
+
+        <ImageView
+            isGone="@{!subscriptionViewModel.isShowCloseBtn}"
+            android:id="@+id/iv_back"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="14dp"
+            android:background="@drawable/bg_ripple_common_oval_mask"
+            android:onClick="@{()-> subscriptionViewModel.onBackClick()}"
+            android:src="@drawable/icon_subscription_back"
+            app:layout_constraintBottom_toBottomOf="@+id/v_menu"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="@+id/v_menu"
+            app:layout_constraintWidth_percent="0.1" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="@dimen/app_common_page_horizontal_padding"
+            android:onClick="@{()-> subscriptionViewModel.onRestoreClick()}"
+            android:text="@string/sub_restore"
+            android:textColor="@color/white"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="@+id/iv_back"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="@+id/iv_back" />
+
+        <Space
+            android:id="@+id/space_sub"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintDimensionRatio="360:110" />
+
+        <TextView
+            android:onClick="@{()-> subscriptionViewModel.onSubNowClick()}"
+            android:id="@+id/tv_sub_now"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:background="@drawable/bg_voice_ai_btn"
+            android:gravity="center"
+            android:text="@string/sub_now"
+            android:textColor="@color/colorPrimary"
+            android:textSize="17sp"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toTopOf="@+id/tv_auto_renewable"
+            app:layout_constraintDimensionRatio="336:48"
+            app:layout_constraintEnd_toEndOf="@+id/space_sub"
+            app:layout_constraintStart_toStartOf="@+id/space_sub"
+            app:layout_constraintTop_toTopOf="@+id/space_sub"
+            app:layout_constraintVertical_chainStyle="packed"
+            app:layout_constraintWidth_percent="0.9333333333333333" />
+
+        <TextView
+            android:id="@+id/tv_auto_renewable"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="16dp"
+            android:layout_marginTop="6dp"
+            android:gravity="center"
+            android:text="@{subscriptionViewModel.autoRenewableText}"
+            android:textColor="@color/white80"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toTopOf="@+id/tv_cancel_txt"
+            app:layout_constraintEnd_toEndOf="@+id/space_sub"
+            app:layout_constraintStart_toStartOf="@+id/space_sub"
+            app:layout_constraintTop_toBottomOf="@+id/tv_sub_now"
+            tools:text="Subscription is auto-renewable." />
+
+
+        <TextView
+            android:id="@+id/tv_cancel_txt"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="16dp"
+            android:gravity="center"
+            android:text="@{subscriptionViewModel.subCancelTxt}"
+            android:textColor="@color/white80"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toBottomOf="@+id/space_sub"
+            app:layout_constraintEnd_toEndOf="@+id/space_sub"
+            app:layout_constraintStart_toStartOf="@+id/space_sub"
+            app:layout_constraintTop_toBottomOf="@+id/tv_auto_renewable"
+            tools:text="You can cancel your subscription at any time." />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 132 - 0
app/src/main/res/layout/dialog_sub_retention.xml

@@ -0,0 +1,132 @@
+<?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">
+
+    <data>
+
+        <variable
+            name="onSubClick"
+            type="android.view.View.OnClickListener" />
+
+        <variable
+            name="onCancelClick"
+            type="android.view.View.OnClickListener" />
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <Space
+            android:id="@+id/space_content"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintDimensionRatio="300:391"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_percent="0.8333333333333333" />
+
+        <View
+            android:id="@+id/v_dialog_retention_member"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:background="@drawable/bg_dialog_retention_member"
+            app:layout_constraintDimensionRatio="300:279"
+            app:layout_constraintEnd_toEndOf="@id/space_content"
+            app:layout_constraintStart_toStartOf="@id/space_content"
+            app:layout_constraintTop_toTopOf="@id/space_content" />
+
+
+        <View
+            android:id="@+id/v_dialog_retention_member_operation"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:background="@drawable/bg_retention_operation"
+            app:layout_constraintBottom_toBottomOf="@+id/space_content"
+            app:layout_constraintEnd_toEndOf="@+id/space_content"
+            app:layout_constraintStart_toStartOf="@+id/space_content"
+            app:layout_constraintTop_toBottomOf="@id/v_dialog_retention_member" />
+
+
+        <TextView
+            android:id="@+id/tv_dialog_retention_member_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:text="@string/dialog_retention_member_title"
+            android:textColor="@color/white"
+            android:textSize="18sp"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toBottomOf="@+id/v_dialog_retention_member"
+            app:layout_constraintStart_toStartOf="@+id/v_dialog_retention_member"
+            app:layout_constraintTop_toTopOf="@+id/v_dialog_retention_member"
+            app:layout_constraintVertical_bias="0.0852713178294574" />
+
+
+        <TextView
+            android:id="@+id/tv_dialog_retention_member_content"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:text="@string/dialog_retention_member_content"
+            android:textColor="#FFEF5F"
+            android:textSize="34sp"
+            android:textStyle="bold"
+            app:layout_constraintStart_toStartOf="@+id/tv_dialog_retention_member_title"
+            app:layout_constraintTop_toBottomOf="@+id/tv_dialog_retention_member_title" />
+
+        <ImageView
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:src="@drawable/bg_retention_mask"
+            app:layout_constraintBottom_toBottomOf="@id/v_dialog_retention_member"
+            app:layout_constraintDimensionRatio="978:429"
+            app:layout_constraintEnd_toEndOf="@+id/v_dialog_retention_member"
+            app:layout_constraintStart_toStartOf="@+id/v_dialog_retention_member"
+            app:layout_constraintWidth_percent="0.9055555555555556" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="3dp"
+            android:text="@string/dialog_renewal_member_hint"
+            android:textColor="@color/white70"
+            android:textSize="12sp"
+            app:layout_constraintStart_toStartOf="@+id/tv_dialog_retention_member_content"
+            app:layout_constraintTop_toBottomOf="@+id/tv_dialog_retention_member_content" />
+
+        <TextView
+            android:id="@+id/tv_dialog_retention_sub"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:background="@drawable/bg_retention_sub"
+            android:gravity="center"
+            android:onClick="@{onSubClick}"
+            android:text="@string/dialog_retention_sub"
+            android:textColor="@color/white"
+            android:textSize="17sp"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toTopOf="@+id/tv_dialog_retention_cancel"
+            app:layout_constraintDimensionRatio="276:56"
+            app:layout_constraintEnd_toEndOf="@+id/v_dialog_retention_member_operation"
+            app:layout_constraintStart_toStartOf="@+id/v_dialog_retention_member_operation"
+            app:layout_constraintTop_toTopOf="@+id/v_dialog_retention_member_operation"
+            app:layout_constraintVertical_chainStyle="packed"
+            app:layout_constraintWidth_percent="0.7666666666666667" />
+
+        <TextView
+            android:id="@+id/tv_dialog_retention_cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="10dp"
+            android:onClick="@{onCancelClick}"
+            android:text="@string/dialog_retention_cancel"
+            app:layout_constraintBottom_toBottomOf="@id/v_dialog_retention_member_operation"
+            app:layout_constraintEnd_toEndOf="@+id/tv_dialog_retention_sub"
+            app:layout_constraintStart_toStartOf="@+id/tv_dialog_retention_sub"
+            app:layout_constraintTop_toBottomOf="@+id/tv_dialog_retention_sub" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

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

@@ -691,7 +691,6 @@
                         android:layout_marginHorizontal="16dp"
                         android:background="#7C7D88"
                         max="@{(int)cloneVoiceViewModel.recordMaxTime}"
-                        tools:progressDrawable="@drawable/bg_progress_bar_horizontal_record_0_20"
                         app:layout_constraintEnd_toEndOf="@+id/v_record_audio_preview"
                         app:layout_constraintStart_toStartOf="@+id/v_record_audio_preview"
                         app:layout_constraintTop_toBottomOf="@+id/space7" />

+ 122 - 0
app/src/main/res/layout/item_sub_goods_list.xml

@@ -0,0 +1,122 @@
+<?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="goodsBean"
+            type="com.atmob.voiceai.data.api.bean.GoodsBean" />
+
+        <variable
+            name="onItemClick"
+            type="android.view.View.OnClickListener" />
+
+        <variable
+            name="showDivider"
+            type="Boolean" />
+
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+
+        <View
+            android:id="@+id/v_goods_bg"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:background="@{goodsBean.select ? @drawable/bg_sub_goods_selected : null}"
+            android:onClick="@{onItemClick}"
+            app:layout_constraintDimensionRatio="328:72"
+            app:layout_constraintTop_toTopOf="parent" />
+
+
+        <ImageView
+            android:id="@+id/iv_goods_select"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="16dp"
+            android:src="@{goodsBean.select ? @drawable/icon_sub_goods_selected :@drawable/icon_sub_goods_not_select }"
+            app:layout_constraintBottom_toBottomOf="@+id/v_goods_bg"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintStart_toStartOf="@+id/v_goods_bg"
+            app:layout_constraintTop_toTopOf="@+id/v_goods_bg"
+            app:layout_constraintWidth_percent="0.0731707317073171"
+            tools:src="@drawable/icon_sub_goods_selected" />
+
+        <TextView
+            android:id="@+id/tv_goods_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="12dp"
+            android:text="@{goodsBean.name}"
+            android:textColor="@{goodsBean.select ? @color/colorPrimaryVariant : @color/white}"
+            android:textSize="20sp"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toTopOf="@+id/tv_content"
+            app:layout_constraintStart_toEndOf="@+id/iv_goods_select"
+            app:layout_constraintTop_toTopOf="@+id/iv_goods_select"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:text="Yearly"
+            tools:textColor="@color/colorPrimaryVariant" />
+
+        <TextView
+            android:id="@+id/tv_content"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="4dp"
+            android:text="@{goodsBean.content}"
+            android:textColor="@color/white50"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toBottomOf="@+id/iv_goods_select"
+            app:layout_constraintStart_toStartOf="@+id/tv_goods_name"
+            app:layout_constraintTop_toBottomOf="@+id/tv_goods_name"
+            tools:text="$0.77/week" />
+
+        <TextView
+            android:id="@+id/tv_price"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="20dp"
+            android:text="@{@string/sub_goods_price(goodsBean.getDollarAmount(2))}"
+            android:textColor="@color/white"
+            android:textSize="20sp"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toTopOf="@+id/tv_description"
+            app:layout_constraintEnd_toEndOf="@id/v_goods_bg"
+            app:layout_constraintTop_toTopOf="@id/v_goods_bg"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:text="$39.99" />
+
+        <TextView
+            android:id="@+id/tv_description"
+            isGone="@{goodsBean.description == null || goodsBean.description.length() == 0}"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/bg_sub_goods_description"
+            android:paddingHorizontal="6dp"
+            android:paddingVertical="3dp"
+            android:text="@{goodsBean.description}"
+            android:textColor="@color/white"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toBottomOf="@id/v_goods_bg"
+            app:layout_constraintEnd_toEndOf="@+id/tv_price"
+            app:layout_constraintTop_toBottomOf="@+id/tv_price"
+            tools:text="80% OFF" />
+
+
+        <View
+            android:id="@+id/v_divider"
+            isGone="@{!showDivider}"
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:layout_marginHorizontal="9dp"
+            android:background="@drawable/bg_sub_line"
+            app:layout_constraintBottom_toBottomOf="@+id/v_goods_bg" />
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 22 - 1
app/src/main/res/values/strings.xml

@@ -1,5 +1,5 @@
 <resources>
-    <string name="app_name">VoiceAI</string>
+    <string name="app_name">Funny Voice AI</string>
     <string name="main_pager_voice_ai">Voice AI</string>
     <string name="main_pager_clone_voice">Clone Voice</string>
     <string name="main_pager_history">History</string>
@@ -85,4 +85,25 @@
     <string name="splash_privacy_all_txt"><![CDATA[After clicking the button, you agree to the app’s \nprivacy policy and Terms of service]]></string>
     <string name="splash_privacy_policy">privacy policy</string>
     <string name="splash_terms_of_service">Terms of service</string>
+    <string name="sub_restore">Restore</string>
+    <string name="sub_title">Funny Voice AI</string>
+    <string name="sub_pro">Pro</string>
+    <string name="sub_bar_title">Funny Voice AI PRO</string>
+    <string name="sub_unlock_features">Unlock All Features</string>
+    <string name="sub_basic">Basic</string>
+    <string name="sub_generation_times">Voice AI \ngeneration times</string>
+    <string name="sub_quantity">Voice AI quantity</string>
+    <string name="sub_clone_voice">Clone voice</string>
+    <string name="sub_faster_processing">Faster processing</string>
+    <string name="sub_remove_ads">Remove Ads</string>
+    <string name="sub_unlimited">Unlimited</string>
+    <string name="sub_now">Subscribe Now</string>
+    <string name="sub_goods_price">$%s</string>
+    <string name="dialog_retention_member_title">Are You Sure? This Is A</string>
+    <string name="dialog_retention_member_content">LIMITED-TIME \nOFFER</string>
+    <string name="dialog_renewal_member_hint">You won\'t have this opportunity again!</string>
+    <string name="dialog_retention_sub">Stay and unlock all features</string>
+    <string name="dialog_retention_cancel">No thanks,I will miss out</string>
+    <string name="please_select_goods">Please select a subscription product.</string>
+    <string name="no_pay_way">Unable to make a payment at the moment.</string>
 </resources>

+ 1 - 1
build.gradle

@@ -2,7 +2,7 @@
 buildscript {
     ext {
         compileSdkVersion = 34
-        applicationId = "com.atmob.vioceai"
+        applicationId = "com.funnyvoiceai.clonevoice"
         minSdkVersion = 21
         targetSdkVersion = 32