nilupeng
2022-08-12 7cd5e812882e999443220e9c71103b3e3c476c71
提现,支付密码
11 files added
1 files deleted
20 files modified
1194 ■■■■ changed files
app/src/main/AndroidManifest.xml 2 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/MyApplication.java 7 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/activities/LoadPageActivity.java 3 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java 8 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java 83 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/data/Results.java 2 ●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/listener/CrashHandler.java 65 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java 22 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/api/LoginApiCenter.java 7 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/adapter/CoinTransAdapter.java 2 ●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/adapter/NumAdapter.java 23 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/coin/CoinSettingActivity.java 9 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/coin/CoinSettingViewModel.java 19 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/coin/CoinViewModel.java 53 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/coin/WithDrawActivity.java 70 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/loadpage/PageActivitys.java 24 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/login/LoginViewModel.java 78 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/login/RegisterLoginActivity.java 12 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/login/UserBean.java 4 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/mine/MineFragment.java 24 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/paypass/PayPassViewModel.java 21 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/paypass/PaypassActivity.java 118 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java 18 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/util/DimensionUtils.java 12 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/widgets/PasswordInputView.java 245 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/activity_paypass.xml 34 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/activity_recycler.xml 31 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/activity_withdraw.xml 99 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/dialog_paypass.xml 63 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/item_num.xml 13 ●●●●● patch | view | raw | blame | history
app/src/main/res/values/dimens.xml 2 ●●●●● patch | view | raw | blame | history
app/src/main/res/values/styles.xml 21 ●●●●● patch | view | raw | blame | history
app/src/main/AndroidManifest.xml
@@ -50,6 +50,8 @@
        <activity android:name=".ui.msg.MsgDetailActivity" />
        <activity android:name=".ui.sign.SignInActivity" />
        <activity android:name=".ui.coin.CoinSettingActivity" />
        <activity android:name=".ui.coin.WithDrawActivity" />
        <activity android:name=".ui.paypass.PaypassActivity" />
        <activity android:name=".ui.loadpage.PageActivitys$CoinRecordActivity" tools:ignore="Instantiatable"/>
    </application>
app/src/main/java/com/runt/open/mvvm/MyApplication.java
@@ -5,6 +5,7 @@
import android.content.Context;
import android.os.Bundle;
import android.os.Process;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -113,14 +114,12 @@
        });
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init(getApplicationContext(), new CrashHandler.CrashListener() {
            @Override
            public void onCrash() {
        crashHandler.init(getApplicationContext(), () -> {
                for(Activity activity : activities){
                    activity.finish();
                }
            Process.killProcess(Process.myPid());
                System.exit(0);
            }
        });
    }
app/src/main/java/com/runt/open/mvvm/base/activities/LoadPageActivity.java
@@ -34,19 +34,20 @@
    @Override
    public void initViews() {
        setTitle(initTitle());
        try {
            Class<A> entityClass = (Class<A>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2];
            this.adapter = entityClass.newInstance();//实例化泛型
        } catch (Exception e) {
            e.printStackTrace();
        }
        refresh = mBinding.getRoot().findViewById(R.id.refresh);
        refresh.setRefreshHeader(new ClassicsHeader(mContext));
        refresh.setRefreshFooter(new ClassicsFooter(mContext));
        refresh.setOnRefreshLoadMoreListener(this);
        RecyclerView recycler = mBinding.getRoot().findViewById(R.id.recycler);
        recycler.setLayoutManager(new LinearLayoutManager(mContext));
        recycler.setAdapter(adapter);
        refresh = mBinding.getRoot().findViewById(R.id.refresh);
        refresh.setOnRefreshLoadMoreListener(this);
        mViewModel.getLiveData().observe(this, (Observer<List<RESULT>>) list -> {
            adapter.showNull = true;
app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java
@@ -3,13 +3,11 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding;
import com.runt.open.mvvm.databinding.LayoutNullBinding;
import com.runt.open.mvvm.util.DeviceUtil;
import com.runt.open.mvvm.util.DimensionUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -148,9 +146,9 @@
    protected void setBottomMargin(RecyclerView.ViewHolder holder, int position, float dp, float defaultDp){
        ViewGroup.MarginLayoutParams params1 = (ViewGroup.MarginLayoutParams) holder.itemView.getLayoutParams();
        if(position == dataList.size() -1){
            params1.setMargins(params1.leftMargin, params1.topMargin, params1.rightMargin, DeviceUtil.convertDpToPixel(dp,holder.itemView.getContext()));
            params1.setMargins(params1.leftMargin, params1.topMargin, params1.rightMargin, (int) DimensionUtils.convertDpToPixel(dp,holder.itemView.getContext()));
        }else{
            params1.setMargins(params1.leftMargin, params1.topMargin, params1.rightMargin, DeviceUtil.convertDpToPixel(defaultDp,holder.itemView.getContext()));
            params1.setMargins(params1.leftMargin, params1.topMargin, params1.rightMargin, (int) DimensionUtils.convertDpToPixel(defaultDp,holder.itemView.getContext()));
        }
    }
app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java
@@ -7,6 +7,7 @@
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.runt.open.mvvm.BuildConfig;
import com.runt.open.mvvm.base.activities.BaseActivity;
@@ -17,8 +18,8 @@
import com.runt.open.mvvm.retrofit.api.CommonApiCenter;
import com.runt.open.mvvm.retrofit.observable.HttpObserver;
import com.runt.open.mvvm.retrofit.utils.RetrofitUtils;
import com.runt.open.mvvm.ui.login.UserBean;
import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import okhttp3.*;
@@ -26,6 +27,9 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
 * Created by Administrator on 2021/11/11 0011.
@@ -34,9 +38,78 @@
    protected BaseActivity mActivity;
    protected CommonApiCenter commonApi = RetrofitUtils.getInstance().getCommonApi();
    MutableLiveData<Integer> verifyResult = new MutableLiveData<>();
    public MutableLiveData<Integer> getVerifyResult() {
        return verifyResult;
    }
    public void onCreate(BaseActivity activity) {
        this.mActivity = activity;
    }
    public void getUserBean(){
        httpObserverOn(commonApi.getUserBean(), new HttpObserver<UserBean>() {
            @Override
            protected void onSuccess(UserBean data) {
                UserBean.setUser(data);
            }
        });
    }
    /**
     * 获取验证码
     * @param url    验证码地址
     * @param phone 手机号
     */
    public void getVerifyCode(String url,String phone){
        String time = new Date().getTime()+"";
        httpObserverOnLoading(commonApi.getVerifyCode(url, phone, randomString(phone, time), time), new HttpObserver<Results.SmsResult>(){
            @Override
            protected void onSuccess(Results.SmsResult data) {
                verifyResult.setValue(0);
            }
            @Override
            protected void onFailed(HttpApiResult httpResult) {
                super.onFailed(httpResult);
                verifyResult.setValue(-1);
            }
        });
    }
    /**
     * 随机字符串
     * @param phone
     * @param time
     * @return
     */
    private String randomString(String phone,String time){
        int p =  (int) Math.round(phone.length()/6.0);
        int t = time.length()/6;
        List<String> list = new ArrayList<String>();
        for(int i = 0 ; i < 6 ; i ++){
            String str = "";
            if(i*p>phone.length()){
                str = phone.substring((i-1)*p);
            }else if((i+1)*p>phone.length()){
                str = phone.substring(i*p);
            }else{
                str = phone.substring(i*p,(i+1)*p);
            }
            String num = ((Integer.parseInt(str)*Long.parseLong(time))+"") ;
            list.add(num);
        }
        //return sb.toString();
        return plusSingle2(list);
    }
    private String plusSingle2(List<String> list){
        StringBuilder sb = new StringBuilder();
        for(int i = 0 ; i < list.size() ; i ++){
            sb.append(list.get(i).substring(list.get(i).length()-2<0?0:list.get(i).length()-2));
        }
        return sb.toString();
    }
    public void checkUpdate(boolean showTip){
@@ -197,11 +270,9 @@
                    mActivity.showLoadingDialog("");
                })
                .observeOn(AndroidScheduler.mainThread())
                .doOnError(new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                    }
                .doOnError(throwable -> {
                    mActivity.dissmissLoadingDialog();
                    Log.e("ViewModel",hashCode()+" httpObserverOnLoading "+throwable);
                })
                .doOnComplete(() -> {
                    mActivity.dissmissLoadingDialog();
app/src/main/java/com/runt/open/mvvm/data/Results.java
@@ -33,7 +33,7 @@
        public String id;
        public UserBean toUser,fromUser;
        public int count,after,before,type;
        public Date cTime;
        public String cTime;
        /**
         * 备注
         **/
app/src/main/java/com/runt/open/mvvm/listener/CrashHandler.java
@@ -5,20 +5,10 @@
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.io.*;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@@ -38,11 +28,9 @@
    //系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    CrashListener crashListener;
    //CrashHandler实例
    private static CrashHandler instance;
    CrashListener crashListener;
    //程序的Context对象
    private Context mContext;
    //用来存储设备信息和异常信息
@@ -74,9 +62,6 @@
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    /**
     * 初始化
     */
    public void init(Context context,CrashListener crashListener) {
        Log.i(TAG, "init context:"+context);
        mContext = context;
@@ -85,6 +70,9 @@
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    public interface CrashListener {
        void onCrash();
    }
    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
@@ -94,17 +82,9 @@
        if (!handleException(ex) && mDefaultHandler != null) {
            //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        } else if(crashListener != null){
            crashListener.onCrash();
        } else {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                Log.e(TAG, "error : ", e);
            }
            //退出程序
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(1);
        if (crashListener != null) {
            crashListener.onCrash();
        }
    }
@@ -122,17 +102,8 @@
        //收集设备参数信息
        collectDeviceInfo(mContext);
        //使用Toast来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }.start();
        //保存日志文件
        saveCatchInfo2File(ex);
        saveCatchInfoFile(ex);
        return true;
    }
@@ -172,7 +143,7 @@
     * @param ex
     * @return  返回文件名称,便于将文件传送到服务器
     */
    private String saveCatchInfo2File(Throwable ex) {
    private String saveCatchInfoFile(Throwable ex) {
        ex.printStackTrace();
        Log.i(TAG, "saveCatchInfo2File Throwable:"+ex);
@@ -199,12 +170,20 @@
            String time = formatter.format(new Date());
            String fileName = "crash-" + time + "-" + timestamp + ".log";
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String path = "/mnt/sdcard/crash/";
                String path = mContext.getExternalFilesDir(null).getAbsolutePath()+"/crash/";
                File dir = new File(path);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                FileOutputStream fos = new FileOutputStream(path + fileName);
                String newFileName = path + fileName;
                Log.i(TAG,"exc newFileName:"+newFileName);
                File file = new File(newFileName);
                file.mkdirs();
                if(file.exists()){
                    file.delete();
                }
                file.createNewFile();
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(sb.toString().getBytes());
                //发送给开发人员
                sendCrashLog2PM(path+fileName);
@@ -238,7 +217,7 @@
                s = reader.readLine();
                if(s == null) break;
                //由于目前尚未确定以何种方式发送,所以先打出log日志。
                //Log.i("info", s.toString());
                Log.i("info", s.toString());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
@@ -252,9 +231,5 @@
                e.printStackTrace();
            }
        }
    }
    public static  interface CrashListener{
        void onCrash();
    }
}
app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java
@@ -1,9 +1,11 @@
package com.runt.open.mvvm.retrofit.api;
import com.runt.open.mvvm.config.Configuration;
import com.runt.open.mvvm.data.HttpApiResult;
import com.runt.open.mvvm.data.PageResult;
import com.runt.open.mvvm.data.Results;
import com.runt.open.mvvm.ui.login.UserBean;
import io.reactivex.Observable;
import okhttp3.MultipartBody;
import retrofit2.http.*;
@@ -51,12 +53,23 @@
    Observable<Object> postPageData(@Url String url, @Field("page") int pageNum, @Field("size") int pageSize, @FieldMap Map<String,String> param);
    /**
     * 登录
     * @return
     */
    @POST("loginToken")
    Observable<HttpApiResult<UserBean>> getUserBean();
    @FormUrlEncoded
    @POST
    Observable<HttpApiResult<Results.SmsResult>> getVerifyCode(@Url String url, @Field(Configuration.KEY_PHONE) String phone, @Field(Configuration.KEY_CODE) String code, @Field("time") String time);
    /**
     * app更新
     * @return
     */
    @GET("getControlVersion")
    Observable<HttpApiResult<Results.ApkVersion>> getAppUpdate();
    @FormUrlEncoded
    @POST("updateName")
    Observable<Results.StringApiResult> updateName(@Field("username") String name);
@@ -71,11 +84,17 @@
    @GET("getMsgDetail")
    Observable<HttpApiResult<Results.Message>> getMsgDetail(@Query("id") String id);
    @FormUrlEncoded
    @POST("updateAlipay")
    Observable<Results.StringApiResult> updateAlipay(@Field("account") String account,@Field("paypass") String paypass);
    @FormUrlEncoded
    @POST("updateRealname")
    Observable<Results.StringApiResult> updateRealname(@Field("account") String account,@Field("paypass") String paypass);
    @FormUrlEncoded
    @POST("withDraw")
    Observable<Results.StringApiResult> withDraw(@Field("paypass") String paypass,@Field("count") int count);
    /**
     * 获取签到列表
@@ -88,4 +107,7 @@
    @POST("signIn")
    Observable<Results.StringApiResult> signIn();
    @FormUrlEncoded
    @POST("updatePaypass")
    Observable<Results.StringApiResult> updatePaypass(@Field("smsCode") String smsCode,@Field("paypass") String paypass);
}
app/src/main/java/com/runt/open/mvvm/retrofit/api/LoginApiCenter.java
@@ -3,13 +3,11 @@
import com.runt.open.mvvm.config.Configuration;
import com.runt.open.mvvm.data.HttpApiResult;
import com.runt.open.mvvm.data.Results;
import com.runt.open.mvvm.ui.login.UserBean;
import io.reactivex.Observable;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
import retrofit2.http.Url;
/**
 * Created by Administrator on 2021/11/15 0015.
@@ -34,11 +32,6 @@
    @FormUrlEncoded
    @POST("loginCode")
    Observable<HttpApiResult<UserBean>> loginByCode(@Field(Configuration.KEY_PHONE) String phone, @Field(Configuration.KEY_CODE) String code);
    @FormUrlEncoded
    @POST
    Observable<HttpApiResult<Results.SmsResult>> getVerifyCode(@Url String url, @Field(Configuration.KEY_PHONE) String phone, @Field(Configuration.KEY_CODE) String code, @Field("time") String time);
    /**
     * 重置密码
app/src/main/java/com/runt/open/mvvm/ui/adapter/CoinTransAdapter.java
@@ -63,6 +63,6 @@
            binding.txtCount.setText("+" + data.count);
            Glide.with(binding.getRoot().getContext()).load(BuildConfig.HOST_IP_ADDR + (data.fromUser == null ? "" : data.fromUser.getHead())).apply(options).into(binding.imgHead);
        }
        binding.txtTime.setText(HandleDate.getTimeStateNew(data.cTime));
        binding.txtTime.setText(HandleDate.getTimeStateNew(HandleDate.getDateTimeToLong(data.cTime)));
    }
}
app/src/main/java/com/runt/open/mvvm/ui/adapter/NumAdapter.java
New file
@@ -0,0 +1,23 @@
package com.runt.open.mvvm.ui.adapter;
import com.runt.open.mvvm.base.adapter.BaseAdapter;
import com.runt.open.mvvm.databinding.ItemNumBinding;
import java.util.List;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-10-30.
 */
public class NumAdapter extends BaseAdapter<String, ItemNumBinding> {
    public NumAdapter(List<String> list) {
        this.dataList = list;
    }
    @Override
    protected void onBindView(ItemNumBinding binding, int position, String s) {
        binding.text.setText(s);
        binding.getRoot().setTag(s);
    }
}
app/src/main/java/com/runt/open/mvvm/ui/coin/CoinSettingActivity.java
@@ -9,13 +9,14 @@
import com.runt.open.mvvm.listener.ResPonse;
import com.runt.open.mvvm.retrofit.observable.HttpObserver;
import com.runt.open.mvvm.ui.login.UserBean;
import com.runt.open.mvvm.ui.paypass.PaypassActivity;
/**
 * My father is Object, ites purpose of 金币交易设置
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-10-30.
 */
public class CoinSettingActivity extends BaseActivity<ActivitySettingCoinBinding,CoinSettingViewModel> {
public class CoinSettingActivity extends BaseActivity<ActivitySettingCoinBinding, CoinViewModel> {
    private int requestCode = 0;
@@ -64,7 +65,7 @@
                public void doSuccess(Object obj) {
                    mBinding.txtAlipay.setText(obj.toString());
                    requestCode = REQUEST_CODE_PAYPASS_FOR_ALIPAY;
                    //launcher.launch(new Intent(mContext, PaypassActivity.class));
                    launcher.launch(new Intent(mContext, PaypassActivity.class));
                }
            });
        });
@@ -74,12 +75,12 @@
                public void doSuccess(Object obj) {
                    requestCode = REQUEST_CODE_PAYPASS_FOR_REALNAME;
                    mBinding.txtRealname.setText(obj.toString());
                    //launcher.launch(new Intent(mContext, PaypassActivity.class));
                    launcher.launch(new Intent(mContext, PaypassActivity.class));
                }
            });
        });
        mBinding.linPass.setOnClickListener(v->{
            //startActivity(new Intent(mContext,PaypassActivity.class).putExtra("type",1));
            startActivity(new Intent(mContext,PaypassActivity.class).putExtra("type",1));
        });
    }
app/src/main/java/com/runt/open/mvvm/ui/coin/CoinSettingViewModel.java
File was deleted
app/src/main/java/com/runt/open/mvvm/ui/coin/CoinViewModel.java
New file
@@ -0,0 +1,53 @@
package com.runt.open.mvvm.ui.coin;
import android.content.Intent;
import com.runt.open.mvvm.base.model.BaseViewModel;
import com.runt.open.mvvm.data.HttpApiResult;
import com.runt.open.mvvm.listener.ResPonse;
import com.runt.open.mvvm.retrofit.observable.HttpObserver;
import com.runt.open.mvvm.ui.login.UserBean;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2019-3-26.
 */
public class CoinViewModel extends BaseViewModel {
    //更新真实姓名
    public void updateName(String name,String pass, HttpObserver<String> httpObserver){
        httpObserverOnLoading(commonApi.updateRealname(name,pass),httpObserver);
    }
    //更新支付宝
    public void updateAlipay(String alipay,String pass, HttpObserver<String> httpObserver){
        httpObserverOnLoading(commonApi.updateAlipay(alipay,pass),httpObserver);
    }
    //提现
    public void withDraw(String pass,int count){
        httpObserverOnLoading(commonApi.withDraw(pass, count), new HttpObserver<String>(mActivity) {
            @Override
            protected void onSuccess(String data) {
                UserBean.getUser().setCoin(UserBean.getUser().getCoin()-count);
                mActivity.showToast("申请成功");
                mActivity.setResult(mActivity.RESULT_OK);
                mActivity.finish();
            }
            @Override
            protected void onFailed(HttpApiResult error) {
                if(error.code == 40004){
                    mActivity.showDialog("申请失败", "未找到对应的支付宝账户", "设置", "取消", new ResPonse() {
                        @Override
                        public void doSuccess(Object obj) {
                            mActivity.startActivity(new Intent(mActivity, CoinSettingActivity.class) );//去设置密码
                        }
                    });
                }else if(error.code == 622){
                    mActivity.showDialog("申请失败",error.msg,null);
                }else{
                    super.onFailed(error);
                }
            }
        });
    }
}
app/src/main/java/com/runt/open/mvvm/ui/coin/WithDrawActivity.java
New file
@@ -0,0 +1,70 @@
package com.runt.open.mvvm.ui.coin;
import android.content.Intent;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.databinding.ActivityWithdrawBinding;
import com.runt.open.mvvm.listener.CustomClickListener;
import com.runt.open.mvvm.ui.login.UserBean;
import com.runt.open.mvvm.ui.paypass.PaypassActivity;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-10-30.
 */
public class WithDrawActivity extends BaseActivity<ActivityWithdrawBinding, CoinViewModel> {
    ActivityResultLauncher<Intent> launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
        if(result.getResultCode() == RESULT_OK){
            int count = Integer.parseInt(mBinding.edit.getText().toString())*1000;
            mViewModel.withDraw(result.getData().getStringExtra("paypass"),count);
        }
    });
    @Override
    public void initViews() {
        mBinding.txtBalance.setText(String.format("当前金币数量%s个", UserBean.getUser().getCoin()));
        mBinding.edit.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }
            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }
            @Override
            public void afterTextChanged(Editable editable) {
                mBinding.txtRmb.setText(String.format("提现¥%s元",mBinding.edit.getText()));
            }
        });
        mBinding.btnSubmit.setOnClickListener(new CustomClickListener() {
            @Override
            protected void onSingleClick(View view) {
                if(isNull(mBinding.edit.getText())){
                    showToast("请输入提现数量");
                }else{
                    int count = Integer.parseInt(mBinding.edit.getText().toString())*1000;
                    if(count>UserBean.getUser().getCoin()){
                        showToast("余额不足");
                    }else if( isNull(UserBean.getUser().getAlipay())){
                        showToast("还没有设置支付宝账号");
                    }else {
                        launcher.launch(new Intent(mContext, PaypassActivity.class));//开启支付密码
                    }
                }
            }
        });
    }
    @Override
    public void loadData() {
    }
}
app/src/main/java/com/runt/open/mvvm/ui/loadpage/PageActivitys.java
@@ -1,9 +1,15 @@
package com.runt.open.mvvm.ui.loadpage;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.view.View;
import com.runt.open.mvvm.R;
import com.runt.open.mvvm.base.activities.LoadPageActivity;
import com.runt.open.mvvm.data.Results;
import com.runt.open.mvvm.databinding.RefreshRecyclerBinding;
import com.runt.open.mvvm.databinding.ActivityRecyclerBinding;
import com.runt.open.mvvm.listener.CustomClickListener;
import com.runt.open.mvvm.ui.adapter.CoinTransAdapter;
import com.runt.open.mvvm.ui.coin.CoinSettingActivity;
import java.util.Map;
@@ -13,13 +19,27 @@
public class PageActivitys {
    //金币记录
    public class CoinRecordActivity extends LoadPageActivity<RefreshRecyclerBinding, PageViewModels.CoinRecordViewModel, CoinTransAdapter, Results.CustomCoin>{
    public static class CoinRecordActivity extends LoadPageActivity<ActivityRecyclerBinding, PageViewModels.CoinRecordViewModel, CoinTransAdapter, Results.CustomCoin>{
        @Override
        protected String initTitle() {
            return "金币记录";
        }
        @Override
        public void initViews() {
            super.initViews();
            Drawable drawable = getResources().getDrawable(R.mipmap.icon_white_setting);
            drawable.setTint(getResources().getColor(R.color.txt_color));
            setTitleRight(drawable);
            titleBarView.setRightClick(new CustomClickListener() {
                @Override
                protected void onSingleClick(View view) {
                    startActivity(new Intent(mContext, CoinSettingActivity.class));//打开设置
                }
            });
        }
        @Override
        protected Map requestParams() {
            Map map = super.requestParams();
            map.put("inOrOut",0);
app/src/main/java/com/runt/open/mvvm/ui/login/LoginViewModel.java
@@ -1,18 +1,17 @@
package com.runt.open.mvvm.ui.login;
import androidx.lifecycle.MutableLiveData;
import com.runt.open.mvvm.R;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.base.model.BaseViewModel;
import com.runt.open.mvvm.config.Configuration;
import com.runt.open.mvvm.data.HttpApiResult;
import com.runt.open.mvvm.data.Results;
import com.runt.open.mvvm.retrofit.api.LoginApiCenter;
import com.runt.open.mvvm.retrofit.observable.HttpObserver;
import com.runt.open.mvvm.retrofit.utils.RetrofitUtils;
import com.runt.open.mvvm.util.MyLog;
import io.reactivex.Observable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
 * Created by Administrator on 2021/11/15 0015.
@@ -25,8 +24,6 @@
        loginApi = RetrofitUtils.getInstance().getRetrofit(LoginApiCenter.class);
    }
    MutableLiveData<UserBean> loginResult = new MutableLiveData<>();
    MutableLiveData<Integer> verifyResult = new MutableLiveData<>();
    MutableLiveData<Results.StringApiResult> resetResult = new MutableLiveData<>();
    MutableLiveData<Results.StringApiResult> registerResult = new MutableLiveData<>();
    HttpObserver<UserBean> logginObserver;
@@ -37,18 +34,16 @@
        logginObserver = new HttpObserver<UserBean>(mActivity){
            @Override
            protected void onSuccess(UserBean data) {
                loginResult.setValue(data);
                UserBean.setUser(data);
                mActivity.putStringProjectPrefrence(Configuration.KEY_USERNAME, data.getUsername());
                MyLog.i("registerlogin",data.toString());
                mActivity.showToast(R.string.login_success);
                mActivity.setResult(mActivity.RESULT_CODE_SUCESS);
                mActivity.finish();
            }
        };
    }
    public MutableLiveData<UserBean> getLoginResult() {
        return loginResult;
    }
    public MutableLiveData<Integer> getVerifyResult() {
        return verifyResult;
    }
    /**
     * 密码登录
@@ -119,59 +114,4 @@
        getVerifyCode("getLoginSMS",phone);
    }
    /**
     * 获取验证码
     * @param url    验证码地址
     * @param phone 手机号
     */
    public void getVerifyCode(String url,String phone){
        String time = new Date().getTime()+"";
        httpObserverOnLoading(loginApi.getVerifyCode(url, phone, randomString(phone, time), time), new HttpObserver<Results.SmsResult>(){
            @Override
            protected void onSuccess(Results.SmsResult data) {
                verifyResult.setValue(0);
            }
            @Override
            protected void onFailed(HttpApiResult httpResult) {
                super.onFailed(httpResult);
                verifyResult.setValue(-1);
            }
        });
    }
    /**
     * 随机字符串
     * @param phone
     * @param time
     * @return
     */
    private String randomString(String phone,String time){
        int p =  (int) Math.round(phone.length()/6.0);
        int t = time.length()/6;
        List<String> list = new ArrayList<String>();
        for(int i = 0 ; i < 6 ; i ++){
            String str = "";
            if(i*p>phone.length()){
                str = phone.substring((i-1)*p);
            }else if((i+1)*p>phone.length()){
                str = phone.substring(i*p);
            }else{
                str = phone.substring(i*p,(i+1)*p);
            }
            String num = ((Integer.parseInt(str)*Long.parseLong(time))+"") ;
            list.add(num);
        }
        //return sb.toString();
        return plusSingle2(list);
    }
    private String plusSingle2(List<String> list){
        StringBuilder sb = new StringBuilder();
        for(int i = 0 ; i < list.size() ; i ++){
            sb.append(list.get(i).substring(list.get(i).length()-2<0?0:list.get(i).length()-2));
        }
        return sb.toString();
    }
}
app/src/main/java/com/runt/open/mvvm/ui/login/RegisterLoginActivity.java
@@ -3,8 +3,6 @@
import android.content.Intent;
import android.view.View;
import android.widget.EditText;
import com.google.gson.Gson;
import com.runt.open.mvvm.R;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.config.Configuration;
@@ -12,7 +10,6 @@
import com.runt.open.mvvm.listener.CustomClickListener;
import com.runt.open.mvvm.ui.web.WebViewActivity;
import com.runt.open.mvvm.util.AlgorithmUtils;
import com.runt.open.mvvm.util.MyLog;
import com.runt.open.mvvm.util.PhoneUtil;
import java.util.Date;
@@ -44,15 +41,6 @@
           }else{
           }
        });
        mViewModel.getLoginResult().observe(this, loggedInUser -> {
            putStringProjectPrefrence(Configuration.KEY_USERNAME, mBinding.editPhone.getText().toString());
            UserBean.setUser(loggedInUser);
            putStringProjectPrefrence(Configuration.KEY_USERINFO, new Gson().toJson(loggedInUser));
            MyLog.i("registerlogin",loggedInUser.toString());
            showToast(R.string.login_success);
            setResult(RESULT_CODE_SUCESS);
            finish();
        });
    }
app/src/main/java/com/runt/open/mvvm/ui/login/UserBean.java
@@ -4,6 +4,8 @@
 * Created by Administrator on 2021/11/15 0015.
 */
import androidx.lifecycle.MutableLiveData;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
@@ -19,6 +21,7 @@
    private static final long serialVersionUID = 1L;
    private static UserBean user;
    public static MutableLiveData<UserBean> onUpdate = new MutableLiveData<>();
    public static UserBean getUser() {
        return user;
@@ -26,6 +29,7 @@
    public static void setUser(UserBean user) {
        UserBean.user = user;
        onUpdate.postValue(user);
    }
    /** 主键 **/
app/src/main/java/com/runt/open/mvvm/ui/main/mine/MineFragment.java
@@ -9,6 +9,7 @@
import androidx.activity.result.contract.ActivityResultContracts;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.google.gson.Gson;
import com.luck.picture.lib.PictureSelector;
import com.luck.picture.lib.config.PictureConfig;
import com.luck.picture.lib.config.PictureMimeType;
@@ -16,9 +17,11 @@
import com.runt.open.mvvm.BuildConfig;
import com.runt.open.mvvm.R;
import com.runt.open.mvvm.base.fragments.BaseFragment;
import com.runt.open.mvvm.config.Configuration;
import com.runt.open.mvvm.databinding.FragmentMineBinding;
import com.runt.open.mvvm.listener.ResPonse;
import com.runt.open.mvvm.retrofit.observable.HttpObserver;
import com.runt.open.mvvm.ui.coin.WithDrawActivity;
import com.runt.open.mvvm.ui.coin.CoinSettingActivity;
import com.runt.open.mvvm.ui.loadpage.PageActivitys;
import com.runt.open.mvvm.ui.login.UserBean;
@@ -40,15 +43,25 @@
public class MineFragment extends BaseFragment<FragmentMineBinding,MineViewModel> implements View.OnClickListener {
    private final  String TAG = "MineFragment";
    ActivityResultLauncher<Intent> signLaunch = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
    ActivityResultLauncher<Intent> launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
        refreshUi();
    });
    @Override
    public void initViews() {
        mViewModel.getUserBean();
        UserBean.onUpdate.observe(this, userBean -> {
            mActivity.putStringProjectPrefrence(Configuration.KEY_USERINFO, new Gson().toJson(userBean));
            refreshUi();
        });
    }
    @Override
    public void loadData() {
        setOnClickListener(this,R.id.lin_sign,R.id.lin_coin,R.id.img,R.id.txt_name);
        refreshUi();
    }
    public void refreshUi(){
        if(UserBean.getUser() != null){
            RequestOptions options = new RequestOptions()
                    .placeholder(R.mipmap.default_head)//图片加载出来前,显示的图片
@@ -59,14 +72,13 @@
            mBinding.txtCoin.setText(UserBean.getUser().getCoin()+"");
            mBinding.txtSigns.setText(UserBean.getUser().getSign()+"");
            mBinding.linGroup.setVisibility(View.VISIBLE);
        }else{
            Glide.with(getContext()).load(R.mipmap.default_head).into(mBinding.img);
            mBinding.txtName.setText("未登录");
            mBinding.linGroup.setVisibility(View.GONE);
        }
        setOnClickListener(this,R.id.lin_sign,R.id.lin_coin,R.id.img,R.id.txt_name);
    }
    @Override
    public void onClick(View view) {
@@ -109,7 +121,7 @@
                                            }
                                        });
                                    }else{
                                        //startActivityForResult(new Intent(mActivity, WithDrawActivity.class),REQUEST_CODE_WITHDRAW );
                                        launcher.launch(new Intent(mActivity, WithDrawActivity.class) );
                                    }
                                }
                            }
@@ -117,7 +129,7 @@
                        .show();
                break;
            case R.id.lin_sign://签到
                signLaunch.launch(new Intent(getContext(), SignInActivity.class));
                launcher.launch(new Intent(getContext(), SignInActivity.class));
                break;
        }
    }
app/src/main/java/com/runt/open/mvvm/ui/paypass/PayPassViewModel.java
New file
@@ -0,0 +1,21 @@
package com.runt.open.mvvm.ui.paypass;
import com.runt.open.mvvm.base.model.BaseViewModel;
import com.runt.open.mvvm.retrofit.observable.HttpObserver;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-10-30.
 */
public class PayPassViewModel extends BaseViewModel {
    public void updatePass(String pass,String code){
        httpObserverOnLoading(commonApi.updatePaypass(code, pass), new HttpObserver(mActivity) {
            @Override
            protected void onSuccess(Object data) {
                mActivity.finish();
                mActivity.showToast("支付密码修改成功");
            }
        });
    }
}
app/src/main/java/com/runt/open/mvvm/ui/paypass/PaypassActivity.java
New file
@@ -0,0 +1,118 @@
package com.runt.open.mvvm.ui.paypass;
import android.app.Instrumentation;
import android.content.Intent;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import androidx.recyclerview.widget.GridLayoutManager;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.databinding.ActivityPaypassBinding;
import com.runt.open.mvvm.listener.ResPonse;
import com.runt.open.mvvm.ui.adapter.NumAdapter;
import com.runt.open.mvvm.ui.login.UserBean;
import com.runt.open.mvvm.widgets.PasswordInputView;
import java.util.ArrayList;
import java.util.List;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-10-30.
 */
public class PaypassActivity extends BaseActivity<ActivityPaypassBinding, PayPassViewModel> {
    @Override
    public void initViews() {
        int type = getIntent().getIntExtra("type",0);// 0 验证 , 1 修改
        if(type == 0) {
            setTitle("验证支付密码");
        }else{
            setTitle("修改支付密码");
        }
        PasswordInputView password = mBinding.paypassInclude.password;
        mBinding.paypassInclude.view.setOnClickListener(v->{});
        mBinding.paypassInclude.recyclerNum.setLayoutManager(new GridLayoutManager(this,3));
        mBinding.paypassInclude.password.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }
            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }
            @Override
            public void afterTextChanged(Editable editable) {
                if(editable.length() ==6){
                    if(type == 0 ){
                        setResult(RESULT_OK,new Intent().putExtra("paypass",editable.toString()));
                        finish();
                    }else if(type == 1){
                        if(password.getTag() == null){
                            password.setText("");
                            password.setTag(editable.toString());
                            mBinding.paypassInclude.txtTip.setText("请再次输入支付密码");
                        } else if(password.getTag().toString().equals(editable.toString())) {
                            mViewModel.getVerifyCode("getPayPassSMS", UserBean.getUser().getPhone());
                        } else if(!password.getTag().toString().equals(editable.toString())) {
                            password.setText("");
                            password.setTag(null);
                            showToast("两次输入不一致");
                            mBinding.paypassInclude.txtTip.setText("两次输入不一致");
                        }
                    }
                }
            }
        });
        mViewModel.getVerifyResult().observe(this, integer -> {
            if(integer == 0){
                showInputDialog("输入验证码", "", "请输入发送到" + UserBean.getUser().getPhone().substring(8)+"的验证码", new ResPonse() {
                    @Override
                    public void doSuccess(Object obj) {
                        mViewModel.updatePass(password.getText().toString(),obj.toString());
                    }
                });
            }
            password.setText("");
            password.setTag(null);
        });
    }
    @Override
    public void loadData() {
        List<String> list = new ArrayList<>();
        for(int i = 1 ; i < 10; i ++) {
            list.add(i+"");
        }
        list.add("×");
        list.add("0");
        list.add("←");
        NumAdapter numAdapter = new NumAdapter(list);
        numAdapter.setOnItemClickListener((position, s) -> {
            if(s.equals("←")){
                new Thread(() -> {
                    Instrumentation inst = new Instrumentation();
                    inst.sendKeyDownUpSync( KeyEvent.KEYCODE_DEL  );
                }).start();
            }else if(s.equals("×")){
                showDialog("取消操作", "确定取消当前操作?", new ResPonse() {
                    @Override
                    public void doSuccess(Object obj) {
                        finish();
                    }
                });
            } else {
                new Thread(() -> {
                    Instrumentation inst = new Instrumentation();
                    inst.sendKeyDownUpSync( Integer.parseInt(s)+7 );
                }).start();
            }
        });
        mBinding.paypassInclude.recyclerNum.setAdapter(numAdapter);
    }
}
app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java
@@ -293,25 +293,7 @@
        return 0;
    }
    /**
     * dp获取dip
     * @param dp
     * @return
     */
    public static int convertDpToPixel(float dp, Context context) {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        return (int) (dp * displayMetrics.density);
    }
    /***
     * px获取dip
     * @param pixel
     * @return
     */
    public static int convertPixelToDp(int pixel, Context context) {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        return (int) (pixel / displayMetrics.density);
    }
    public static String getSerialNumber(Context context) {
        String serial = "";
app/src/main/java/com/runt/open/mvvm/util/DimensionUtils.java
@@ -12,9 +12,9 @@
     * @param dp
     * @return
     */
    public int convertDpToPixel(float dp,Context context) {
    public static float convertDpToPixel(float dp, Context context) {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        return (int) (dp * displayMetrics.density);
        return (dp * displayMetrics.density);
    }
    /***
@@ -22,9 +22,9 @@
     * @param pixel
     * @return
     */
    public int convertPixelToDp(int pixel,Context context) {
    public static float convertPixelToDp(int pixel,Context context) {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        return (int) (pixel / displayMetrics.density);
        return (pixel / displayMetrics.density);
    }
    /**
     * 把pix值转换为sp
@@ -44,8 +44,8 @@
     *            (DisplayMetrics类中属性scaledDensity)
     * @return
     */
    public static int convertSpToPixel(Context context, float spValue) {
    public static float convertSpToPixel(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
        return (spValue * fontScale + 0.5f);
    }
}
app/src/main/java/com/runt/open/mvvm/widgets/PasswordInputView.java
New file
@@ -0,0 +1,245 @@
package com.runt.open.mvvm.widgets;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import com.runt.open.mvvm.R;
import com.runt.open.mvvm.util.DimensionUtils;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2019-3-26.
 */
public class PasswordInputView  extends androidx.appcompat.widget.AppCompatEditText {
    private int textLength;
    private int borderColor;
    private float borderWidth;
    private float borderRadius;
    private int passwordLength;
    private int passwordColor;
    private float passwordWidth;
    private float passwordRadius;
    private Paint passwordPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final int defaultSplitLineWidth = 1;
    public PasswordInputView(Context context, AttributeSet attrs) {
        super(context, attrs);
        final Resources res = getResources();
        final int defaultBorderColor = res.getColor(R.color.cut_off_line);
        final float defaultBorderWidth = res.getDimension(R.dimen.dimen_1px);
        final float defaultBorderRadius = res.getDimension(R.dimen.radios);
        final int defaultPasswordLength = 6;
        final int defaultPasswordColor = res.getColor(R.color.txt_normal);
        final float defaultPasswordWidth = res.getDimension(R.dimen.dimen_6);
        final float defaultPasswordRadius = res.getDimension(R.dimen.dimen_6);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PasswordInputView, 0, 0);
        try {
            borderColor = a.getColor(R.styleable.PasswordInputView_borderColor, defaultBorderColor);
            borderWidth = a.getDimension(R.styleable.PasswordInputView_borderWidth, defaultBorderWidth);
            borderRadius = a.getDimension(R.styleable.PasswordInputView_borderRadius, defaultBorderRadius);
            passwordLength = a.getInt(R.styleable.PasswordInputView_passwordLength, defaultPasswordLength);
            passwordColor = a.getColor(R.styleable.PasswordInputView_passwordColor, defaultPasswordColor);
            passwordWidth = a.getDimension(R.styleable.PasswordInputView_passwordWidth, defaultPasswordWidth);
            passwordRadius = a.getDimension(R.styleable.PasswordInputView_passwordRadius, defaultPasswordRadius);
        } finally {
            a.recycle();
        }
        borderPaint.setStrokeWidth(borderWidth);
        borderPaint.setStyle(Paint.Style.STROKE); //空心的
        borderPaint.setStrokeWidth(DimensionUtils.convertDpToPixel(1,getContext()));//线宽
        borderPaint.setColor(borderColor);
        passwordPaint.setStrokeWidth(passwordWidth);
        passwordPaint.setStyle(Paint.Style.FILL);
        passwordPaint.setColor(passwordColor);
        setBackground(null);
        setSingleLine(true);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        int width = getWidth();
        int border = (int) DimensionUtils.convertDpToPixel(1,getContext());
        final float cha = (borderWidth - passwordWidth)/2;
        final float xCha = (width/passwordLength-borderWidth)/2;
        for (int i = 0; i < passwordLength; i++) {
            float x = width * i / passwordLength + xCha;
            canvas.drawRoundRect(new RectF(x, border, x+borderWidth, border+borderWidth), 10, 10, borderPaint);
        }
        // 密码
        float cx, cy = cha+passwordWidth/2 + border;
        for(int i = 0; i < textLength; i++) {
            cx = width * i / passwordLength + cha+passwordWidth/2 + xCha;
            canvas.drawCircle(cx, cy, passwordWidth, passwordPaint);
        }
    }
    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        this.textLength = text.toString().length();
        invalidate();
    }
    public int getBorderColor() {
        return borderColor;
    }
    public void setBorderColor(int borderColor) {
        this.borderColor = borderColor;
        borderPaint.setColor(borderColor);
        invalidate();
    }
    public float getBorderWidth() {
        return borderWidth;
    }
    public void setBorderWidth(float borderWidth) {
        this.borderWidth = borderWidth;
        borderPaint.setStrokeWidth(borderWidth);
        invalidate();
    }
    public float getBorderRadius() {
        return borderRadius;
    }
    public void setBorderRadius(float borderRadius) {
        this.borderRadius = borderRadius;
        invalidate();
    }
    public int getPasswordLength() {
        return passwordLength;
    }
    public void setPasswordLength(int passwordLength) {
        this.passwordLength = passwordLength;
        invalidate();
    }
    public int getPasswordColor() {
        return passwordColor;
    }
    public void setPasswordColor(int passwordColor) {
        this.passwordColor = passwordColor;
        passwordPaint.setColor(passwordColor);
        invalidate();
    }
    public float getPasswordWidth() {
        return passwordWidth;
    }
    public void setPasswordWidth(float passwordWidth) {
        this.passwordWidth = passwordWidth;
        passwordPaint.setStrokeWidth(passwordWidth);
        invalidate();
    }
    public float getPasswordRadius() {
        return passwordRadius;
    }
    public void setPasswordRadius(float passwordRadius) {
        this.passwordRadius = passwordRadius;
        invalidate();
    }
}
app/src/main/res/layout/activity_paypass.xml
New file
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"  >
    <com.runt.open.mvvm.widgets.TitleBarView
        android:id="@+id/title_bar"
        style="@style/titlebar"
        android:background="@color/transparent"
        app:leftDrawable="@mipmap/icon_white_back"
        app:leftTint="@color/black"
        app:titleText="支付密码"
        tools:ignore="MissingConstraints"/>
    <include
        android:id="@+id/paypass_include"
        layout="@layout/dialog_paypass"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />
    <View
        app:layout_constraintTop_toBottomOf="@id/title_bar"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/gray_normal" />
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/layout/activity_recycler.xml
New file
@@ -0,0 +1,31 @@
<?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" xmlns:app="http://schemas.android.com/apk/res-auto">
    <com.runt.open.mvvm.widgets.TitleBarView
        android:id="@+id/title_bar"
        style="@style/titlebar"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:leftDrawable="@mipmap/icon_white_back"
        app:leftTint="@color/txt_normal"
        app:titleTextColor="@color/txt_normal"/>
    <include
        android:layout_width="match_parent"
        android:layout_height="0dp"
        layout="@layout/refresh_recycler"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/title_bar"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />
    <View
        app:layout_constraintTop_toBottomOf="@id/title_bar"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/cut_off_line"/>
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/layout/activity_withdraw.xml
New file
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:background="@color/white"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <com.runt.open.mvvm.widgets.TitleBarView
        android:id="@+id/title_bar"
        style="@style/titlebar"
        android:background="@color/transparent"
        app:leftDrawable="@mipmap/icon_white_back"
        app:leftTint="@color/black"
        app:titleText="提现"
        />
    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/color_gray8" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/default_margin_lr"
        android:layout_marginLeft="@dimen/default_margin_lr"
        android:layout_marginRight="@dimen/default_margin_lr"
        android:orientation="vertical">
        <TextView
            android:id="@+id/txt_balance"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="当前金币数量1000个" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/default_margin_td"
            android:gravity="center_vertical"
            android:orientation="horizontal">
            <EditText
                android:id="@+id/edit"
                android:layout_width="match_parent"
                android:layout_weight="1"
                android:layout_height="40dp"
                android:background="@null"
                android:inputType="number"
                android:textSize="23sp"
                android:gravity="right|center_vertical"
                android:textColor="@color/txt_normal"
                android:hint="输入提现数量" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:textSize="23sp"
                android:layout_marginLeft="5dp"
                android:gravity="center_vertical"
                android:text=",000 个" />
        </LinearLayout>
        <TextView
            android:id="@+id/txt_rmb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/default_margin_td"
            android:layout_gravity="right"
            android:textSize="16sp"
            android:text="提现¥0元" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:textColor="@color/txt_enable"
            android:layout_marginTop="@dimen/default_margin_td"
            android:text="输入金币数量必须为1000的倍数,1000兑换为1元人民币。提现申请提交后,系统会在一个工作日内将金额汇入您所设置的支付宝账户中,请留意查收"
            />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/lin_ad"
        android:layout_margin="15dp"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" ></LinearLayout>
    <Button
        android:id="@+id/btn_submit"
        style="@style/btn_normal"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="50dp"
        android:layout_marginLeft="@dimen/default_margin_lr"
        android:layout_marginRight="@dimen/default_margin_lr"
        android:text="提交申请"/>
</LinearLayout>
app/src/main/res/layout/dialog_paypass.xml
New file
@@ -0,0 +1,63 @@
<?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="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <TextView
        android:id="@+id/txt_tip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:gravity="center"
        android:textSize="14sp"
        android:layout_margin="@dimen/default_margin_lr"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />
    <com.runt.open.mvvm.widgets.PasswordInputView
        android:id="@+id/password"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:inputType="number"
        android:background="@null"
        android:layout_marginTop="36dp"
        android:layout_marginLeft="21dp"
        android:layout_marginRight="20dp"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:maxLength="6"
        app:passwordColor="@color/black"
        app:passwordWidth="10dp"
        app:passwordLength="6"
        app:borderColor="@color/gray"
        app:borderWidth="47dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/txt_tip" />
    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintLeft_toLeftOf="@id/password"
        app:layout_constraintRight_toRightOf="@id/password"
        app:layout_constraintTop_toTopOf="@id/password"
        app:layout_constraintBottom_toBottomOf="@id/password" />
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_num"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:overScrollMode="never"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/password"
        app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
        app:spanCount="3"
        tools:listitem="@layout/item_num"
        tools:itemCount="12" />
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/layout/item_num.xml
New file
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<com.runt.open.mvvm.widgets.QuadrateLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/text"
        android:text="←"
        style="@style/item_num" />
</com.runt.open.mvvm.widgets.QuadrateLinearLayout >
app/src/main/res/values/dimens.xml
@@ -45,4 +45,6 @@
    <dimen name="default_margin_lr">27dp</dimen>
    <dimen name="default_margin_td">12dp</dimen>
    <dimen name="dimen_1px">1px</dimen>
    <dimen name="dimen_6">6dp</dimen>
</resources>
app/src/main/res/values/styles.xml
@@ -150,4 +150,25 @@
        <item name="android:paddingLeft">@dimen/default_margin_lr</item>
        <item name="android:paddingRight">@dimen/default_margin_lr</item>
    </style>
    <declare-styleable name="PasswordInputView">
        <attr name="borderColor" format="color" />
        <attr name="borderWidth" format="dimension" />
        <attr name="borderRadius" format="dimension" />
        <attr name="passwordLength" format="integer" />
        <attr name="passwordColor" format="color" />
        <attr name="passwordWidth" format="dimension" />
        <attr name="passwordRadius" format="dimension" />
    </declare-styleable>
    <style name="item_num">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">match_parent</item>
        <item name="android:gravity">center</item>
        <item name="android:background">@drawable/bg_num</item>
        <item name="android:textColor">@color/black</item>
        <item name="android:textSize">44sp</item>
    </style>
</resources>