nilupeng
2022-01-29 0c89bf11bcddd39b5193bb19e28399648c59a2b8
登录界面及接口
12 files added
12 files modified
2 files deleted
2394 ■■■■■ changed files
app/src/main/AndroidManifest.xml 2 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/MainActivity.java 16 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/MyApplication.java 11 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java 129 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/config/Configuration.java 16 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java 1 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/data/Results.java 16 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/listener/CrashHandler.java 260 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java 2 ●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/api/LoginApiCenter.java 49 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java 7 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/login/CodeTimer.java 40 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/login/LoggedInUser.java 10 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/login/LoginViewModel.java 132 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/login/RegisterLoginActivity.java 287 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/web/WebViewActivity.java 159 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/util/Configuration.java 48 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/util/MyAnimations.java 477 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/util/PhoneUtil.java 77 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/util/PreferencesUtils.java 352 ●●●●● patch | view | raw | blame | history
app/src/main/res/color/btn_txt_normal.xml 9 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/activity_login.xml 138 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/activity_web.xml 40 ●●●●● patch | view | raw | blame | history
app/src/main/res/values/strings.xml 67 ●●●●● patch | view | raw | blame | history
app/src/main/res/values/styles.xml 44 ●●●●● patch | view | raw | blame | history
app/src/main/res/values/themes.xml 5 ●●●● patch | view | raw | blame | history
app/src/main/AndroidManifest.xml
@@ -51,6 +51,8 @@
                <action android:name="com.zfwl.merchant.activities.MainActivity" />
            </intent-filter>
        </activity>
        <activity android:name=".ui.login.RegisterLoginActivity" />
        <activity android:name=".ui.web.WebViewActivity" />
    </application>
</manifest>
app/src/main/java/com/runt/open/mvvm/MainActivity.java
@@ -1,7 +1,12 @@
package com.runt.open.mvvm;
import android.Manifest;
import android.content.Intent;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.NavigationUI;
@@ -11,6 +16,7 @@
import com.runt.open.mvvm.data.PhoneDevice;
import com.runt.open.mvvm.databinding.ActivityMainBinding;
import com.runt.open.mvvm.listener.ResPonse;
import com.runt.open.mvvm.ui.login.RegisterLoginActivity;
import com.runt.open.mvvm.ui.main.MainViewModel;
public class MainActivity extends BaseActivity<ActivityMainBinding, MainViewModel> {
@@ -20,6 +26,16 @@
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
        NavigationUI.setupWithNavController(binding.navView, navController);
        checkPermission();
        ActivityResultLauncher<Intent>  launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                if(result.getResultCode() == RESULT_CODE_SUCESS){
                    showToast("登录成功");
                }
            }
        });
        Intent intent = new Intent(mContext, RegisterLoginActivity.class);
        launcher.launch(intent);
    }
    private void showPermissionDialog(){
app/src/main/java/com/runt/open/mvvm/MyApplication.java
@@ -11,6 +11,7 @@
import com.bytedance.sdk.openadsdk.TTAdConfig;
import com.bytedance.sdk.openadsdk.TTAdConstant;
import com.bytedance.sdk.openadsdk.TTAdSdk;
import com.runt.open.mvvm.listener.CrashHandler;
import com.runt.open.mvvm.util.MyLog;
import com.scwang.smart.refresh.footer.ClassicsFooter;
import com.scwang.smart.refresh.header.ClassicsHeader;
@@ -137,6 +138,16 @@
                MyLog.e(TAG,"TTAdSdk fail");
            }
        });
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init(getApplicationContext(), new CrashHandler.CrashListener() {
            @Override
            public void onCrash() {
                for(Activity activity : activities){
                    activity.finish();
                }
                System.exit(0);
            }
        });
    }
    /**
app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java
@@ -28,10 +28,12 @@
import com.runt.open.mvvm.base.model.BaseViewModel;
import com.runt.open.mvvm.base.model.ViewModelFactory;
import com.runt.open.mvvm.listener.ResPonse;
import com.runt.open.mvvm.util.PreferencesUtils;
import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Set;
import dmax.dialog.SpotsDialog;
@@ -48,6 +50,9 @@
    public final String[] LOCATION_PERMISSIONS = new String []{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION};
    public final String[] CAMERA_PERMISSIONS = new String[]{ FILE_PERMISSIONS[0],FILE_PERMISSIONS[1], Manifest.permission.CAMERA};
    public final String[] CAMERA_RECORD_PERMISSIONS = new String[]{ FILE_PERMISSIONS[0],FILE_PERMISSIONS[1], Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO};
    public static final String PARAMS_TITLE = "title";
    public static  final String PARAMS_URL = "url";
    public static final int RESULT_LOGIN = 100,RESULT_LOGIN_RECREATE = 103,RESULT_BIND = 101,RESULT_SENDEDFILES = 105,RESULT_DISSCONNECT = 104,
            RESULT_UPDATEUSER =  115,RESULT_LOGOUT = 113, REQUEST_CODE_ACTIVITY = 333;
@@ -366,4 +371,128 @@
        return false;
    }
    public boolean getBooleanUserPrefrence(String key){
        return PreferencesUtils.getBoolean(this,key,false,PreferencesUtils.USER);
    }
    public boolean getBooleanProjectPrefrence(String key){
        return PreferencesUtils.getBoolean(this,key,false,PreferencesUtils.PROJECT);
    }
    public String getStringUserPrefrence(String key){
        return PreferencesUtils.getString(this,key,"",PreferencesUtils.USER);
    }
    public String getStringProjectPrefrence(String key){
        return PreferencesUtils.getString(this,key,"",PreferencesUtils.PROJECT);
    }
    public Integer getIntProjectPrefrence(String key){
        return PreferencesUtils.getInt(this,key,0,PreferencesUtils.PROJECT);
    }
    public Long getLongProjectPrefrence(String key){
        return PreferencesUtils.getLong(this,key,0,PreferencesUtils.PROJECT);
    }
    public float getFloatProjectPrefrence(String key){
        return PreferencesUtils.getFloat(this,key,0,PreferencesUtils.PROJECT);
    }
    public Set getStringSetProjectPrefrence(String key){
        return PreferencesUtils.getStringSet(this,key,PreferencesUtils.PROJECT);
    }
    public Integer getIntUserPrefrence(String key){
        return PreferencesUtils.getInt(this,key,0,PreferencesUtils.USER);
    }
    public Long getLongUserPrefrence(String key){
        return PreferencesUtils.getLong(this,key,0,PreferencesUtils.USER);
    }
    public float getFloatUserPrefrence(String key){
        return PreferencesUtils.getFloat(this,key,0,PreferencesUtils.USER);
    }
    public Set getStringSetUserPrefrence(String key){
        return PreferencesUtils.getStringSet(this,key,PreferencesUtils.USER);
    }
    public void putBooleanUserPrefrence(String key ,Boolean value){
        PreferencesUtils.putBoolean(this,key,value,PreferencesUtils.USER);
    }
    public void putBooleanProjectPrefrence(String key,Boolean value){
        PreferencesUtils.putBoolean(this,key,value,PreferencesUtils.PROJECT);
    }
    public void putStringUserPrefrence(String key,String value){
        PreferencesUtils.putString(this,key,value,PreferencesUtils.USER);
    }
    public void putStringProjectPrefrence(String key,String value){
        PreferencesUtils.putString(this,key,value,PreferencesUtils.PROJECT);
    }
    public void putIntProjectPrefrence(String key,int value){
        PreferencesUtils.putInt(this,key,value,PreferencesUtils.PROJECT);
    }
    public void putLongProjectPrefrence(String key,long value){
        PreferencesUtils.putLong(this,key,value,PreferencesUtils.PROJECT);
    }
    public void putFloatProjectPrefrence(String key,float value){
        PreferencesUtils.putFloat(this,key,value,PreferencesUtils.PROJECT);
    }
    public void putStringSetProjectPrefrence(String key, Set value){
        PreferencesUtils.putStringSet(this,key,value,PreferencesUtils.PROJECT);
    }
    public void putIntUserPrefrence(String key,int value){
        PreferencesUtils.putInt(this,key,value,PreferencesUtils.USER);
    }
    public void putLongUserPrefrence(String key,long value){
        PreferencesUtils.putLong(this,key,value,PreferencesUtils.USER);
    }
    public void putFloatUserPrefrence(String key,float value){
        PreferencesUtils.putFloat(this,key,value,PreferencesUtils.USER);
    }
    public void putStringSetUserPrefrence(String key, Set value){
        PreferencesUtils.putStringSet(this,key,value,PreferencesUtils.USER);
    }
    public void removeUserKey(String key){
        PreferencesUtils.removeKey(this,key,PreferencesUtils.USER);
    }
    public void removeProjectKey(String key){
        PreferencesUtils.removeKey(this,key,PreferencesUtils.PROJECT);
    }
    public void removeUserValue(String Value){
        PreferencesUtils.removeValue(this,Value,PreferencesUtils.USER);
    }
    public void removeProjectValue(String Value){
        PreferencesUtils.removeValue(this,Value,PreferencesUtils.PROJECT);
    }
    public void clearProjectData(){
        PreferencesUtils.clearData(this,PreferencesUtils.PROJECT);
    }
    public void clearUserData(){
        PreferencesUtils.clearData(this,PreferencesUtils.USER);
    }
}
app/src/main/java/com/runt/open/mvvm/config/Configuration.java
New file
@@ -0,0 +1,16 @@
package com.runt.open.mvvm.config;
/**
 * My father is Object, ites purpose of
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2022/1/29.
 */
public class Configuration {
    public final static String KEY_CODE= "code";
    public static final String KEY_TOKEN = "token";
    public static final String KEY_USERNAME = "username";
    public static final String KEY_PHONE = "phone";
    public static final String KEY_USERPASS = "userpass";
    public final static String IS_LOGIN= "is_login";
}
app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java
@@ -6,6 +6,7 @@
 * Created by Administrator on 2021/10/28 0028.
 */
public class BaseApiResult<D extends Object> implements Serializable {
    public String msg;
    public int code = 200;
    public D data;
app/src/main/java/com/runt/open/mvvm/data/Results.java
New file
@@ -0,0 +1,16 @@
package com.runt.open.mvvm.data;
import com.runt.open.mvvm.ui.login.UserBean;
/**
 * My father is Object, ites purpose of
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2022/1/29.
 */
public class Results {
    public static class LoggedInUser extends BaseApiResult<UserBean> { }
    public static class StringApiResult extends BaseApiResult<String>{ }
}
app/src/main/java/com/runt/open/mvvm/listener/CrashHandler.java
New file
@@ -0,0 +1,260 @@
package com.runt.open.mvvm.listener;
import android.content.Context;
import android.content.pm.PackageInfo;
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.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
 * My father is Object, ites purpose of   崩溃监听
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-4-13.
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    public static final String TAG = "CrashHandler";
    //系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    CrashListener crashListener;
    //CrashHandler实例
    private static CrashHandler instance;
    //程序的Context对象
    private Context mContext;
    //用来存储设备信息和异常信息
    private Map<String, String> infos = new HashMap<String, String>();
    //用于格式化日期,作为日志文件名的一部分
    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /** 保证只有一个CrashHandler实例 */
    private CrashHandler() {}
    /** 获取CrashHandler实例 ,单例模式 */
    public static CrashHandler getInstance() {
        Log.i(TAG, "getInstance");
        if(instance == null)
            instance = new CrashHandler();
        return instance;
    }
    /**
     * 初始化
     */
    public void init(Context context) {
        Log.i(TAG, "init context:"+context);
        mContext = context;
        //获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    /**
     * 初始化
     */
    public void init(Context context,CrashListener crashListener) {
        Log.i(TAG, "init context:"+context);
        mContext = context;
        this.crashListener = crashListener;
        //设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        Log.i(TAG, "uncaughtException Throwable:"+ex);
        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);
        }
    }
    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private boolean handleException(Throwable ex) {
        Log.i(TAG, "handleException Throwable:"+ex);
        if (ex == null) {
            return false;
        }
        //收集设备参数信息
        collectDeviceInfo(mContext);
        //使用Toast来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }.start();
        //保存日志文件
        saveCatchInfo2File(ex);
        return true;
    }
    /**
     * 收集设备参数信息
     * @param ctx
     */
    public void collectDeviceInfo(Context ctx) {
        Log.i(TAG, "collectDeviceInfo Context:"+ctx);
        try {
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (pi != null) {
                String versionName = pi.versionName == null ? "null" : pi.versionName;
                String versionCode = pi.versionCode + "";
                infos.put("versionName", versionName);
                infos.put("versionCode", versionCode);
            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "an error occured when collect package info", e);
        }
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
                Log.d(TAG, field.getName() + " : " + field.get(null));
            } catch (Exception e) {
                Log.e(TAG, "an error occured when collect crash info", e);
            }
        }
    }
    /**
     * 保存错误信息到文件中
     *
     * @param ex
     * @return  返回文件名称,便于将文件传送到服务器
     */
    private String saveCatchInfo2File(Throwable ex) {
        ex.printStackTrace();
        Log.i(TAG, "saveCatchInfo2File Throwable:"+ex);
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();
        String result = writer.toString();
        sb.append(result);
        try {
            long timestamp = System.currentTimeMillis();
            String time = formatter.format(new Date());
            String fileName = "crash-" + time + "-" + timestamp + ".log";
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String path = "/mnt/sdcard/crash/";
                File dir = new File(path);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                FileOutputStream fos = new FileOutputStream(path + fileName);
                fos.write(sb.toString().getBytes());
                //发送给开发人员
                sendCrashLog2PM(path+fileName);
                fos.close();
            }
            return fileName;
        } catch (Exception e) {
            Log.e(TAG, "an error occured while writing file...", e);
        }
        return null;
    }
    /**
     * 将捕获的导致崩溃的错误信息发送给开发人员
     *
     * 目前只将log日志保存在sdcard 和输出到LogCat中,并未发送给后台。
     */
    private void sendCrashLog2PM(String fileName){
        Log.i(TAG, "asendCrashLog2PM fileName:"+fileName);
        if(!new File(fileName).exists()){
            Toast.makeText(mContext, "日志文件不存在!", Toast.LENGTH_SHORT).show();
            return;
        }
        FileInputStream fis = null;
        BufferedReader reader = null;
        String s = null;
        try {
            fis = new FileInputStream(fileName);
            reader = new BufferedReader(new InputStreamReader(fis, "GBK"));
            while(true){
                s = reader.readLine();
                if(s == null) break;
                //由于目前尚未确定以何种方式发送,所以先打出log日志。
                //Log.i("info", s.toString());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{   // 关闭流
            try {
                reader.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static  interface CrashListener{
        void onCrash();
    }
}
app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java
@@ -51,7 +51,7 @@
        int position = logArrays.size() +2;
        Response response;
        try {
            //request = encryptRequest(request);//加密
            request = encryptRequest(request);//加密
            response = chain.proceed(request);
            logArrays.addAll(getResponseLog(response));
            Log.d(TAG,"hashcode:"+hashCode);
app/src/main/java/com/runt/open/mvvm/retrofit/api/LoginApiCenter.java
@@ -1,24 +1,65 @@
package com.runt.open.mvvm.retrofit.api;
import com.runt.open.mvvm.ui.login.LoggedInUser;
import com.runt.open.mvvm.config.Configuration;
import com.runt.open.mvvm.data.Results;
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.
 */
public interface LoginApiCenter {
    /**
     * 密码登录
     * @param phone
     * @param pass
     * @return
     */
    @FormUrlEncoded
    @POST("login")
    Observable<LoggedInUser> login(@Field("phone") String phone,@Field("pass") String pass);
    Observable<Results.LoggedInUser> login(@Field(Configuration.KEY_PHONE) String phone, @Field("pass") String pass);
    /**
     * 验证码登录
     * @param phone
     * @param code
     * @return
     */
    @FormUrlEncoded
    @POST("loginCode")
    Observable<Results.LoggedInUser> loginByCode(@Field(Configuration.KEY_PHONE) String phone, @Field(Configuration.KEY_CODE) String code);
    @FormUrlEncoded
    @POST("login")
    Observable<LoggedInUser> loginByCode(@Field("phone") String phone,@Field("code") String code);
    @POST
    Observable<Results.StringApiResult> getVerifyCode(@Url String url, @Field(Configuration.KEY_PHONE) String phone, @Field(Configuration.KEY_CODE) String code, @Field("time") String time);
    /**
     * 重置密码
     * @param phone
     * @param sms
     * @param newPass
     * @return
     */
    @FormUrlEncoded
    @POST("verifySMSReSetLoginPwd")
    Observable<Results.StringApiResult> resetLoginPwd(@Field(Configuration.KEY_PHONE) String phone,@Field("sms") String sms, @Field("pass") String newPass);
    /**
     * 注册
     * @param phone
     * @param sms
     * @param pass
     * @return
     */
    @FormUrlEncoded
    @POST("registerCustomer")
    Observable<Results.StringApiResult> register(@Field(Configuration.KEY_USERNAME) String phone,@Field("sms") String sms, @Field("pass") String pass);
}
app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java
@@ -1,5 +1,6 @@
package com.runt.open.mvvm.retrofit.observable;
import android.accounts.NetworkErrorException;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -41,14 +42,16 @@
        Log.i("subscribe","onError");
        try {
            Log.e(TAG,this.getClass().getSimpleName()+" "+throwable.getMessage());
            Log.e(TAG,this.getClass().getSimpleName()+" mes:"+throwable.getMessage());
            Class<M> entityClass = (Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
            M t = entityClass.newInstance();//实例化一个泛型
            t.code = 410;
            if( throwable instanceof SocketTimeoutException){
                t.msg = "服务请求超时,请稍候再试";//设置错误信息
            }else{
            }else  if( throwable instanceof NetworkErrorException){
                t.msg = "网络连接不畅,请检查您的网络设置";//设置错误信息
            }else{
                t.msg = throwable.getMessage();//设置错误信息
            }
            resultLive.setValue(t);
        } catch (ClassCastException e) {
app/src/main/java/com/runt/open/mvvm/ui/login/CodeTimer.java
New file
@@ -0,0 +1,40 @@
package com.runt.open.mvvm.ui.login;
import android.os.CountDownTimer;
import android.widget.TextView;
import com.runt.open.mvvm.R;
/**
 * My father is Object, ites purpose of
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-2-23.
 */
public class CodeTimer extends CountDownTimer {
    TextView txtGetCode;
    public CodeTimer(long millisInFuture, long countDownInterval, TextView txtGetCode) {
        super(millisInFuture, countDownInterval);
        this.txtGetCode = txtGetCode;
    }
    public void startUp(){
        txtGetCode.setEnabled(false);
        txtGetCode.setTextColor(txtGetCode.getContext().getResources().getColor(R.color.txt_enable));
        start();
    }
    @Override
    public void onTick(long l) {
        txtGetCode.setText(String.format("(%s)", l/1000));
    }
    @Override
    public void onFinish() {
        txtGetCode.setEnabled(true);
        txtGetCode.setTextColor(txtGetCode.getContext().getResources().getColor(R.color.link));
        txtGetCode.setText(txtGetCode.getContext().getResources().getString(R.string.get_verify_code));
    }
}
app/src/main/java/com/runt/open/mvvm/ui/login/LoggedInUser.java
File was deleted
app/src/main/java/com/runt/open/mvvm/ui/login/LoginViewModel.java
@@ -3,9 +3,14 @@
import androidx.lifecycle.MutableLiveData;
import com.runt.open.mvvm.base.model.BaseViewModel;
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 java.util.ArrayList;
import java.util.Date;
import java.util.List;
import io.reactivex.Observable;
@@ -14,17 +19,132 @@
 */
public class LoginViewModel extends BaseViewModel {
    MutableLiveData<LoggedInUser> loginResult = new MutableLiveData<>();
    LoginApiCenter loginApi;
    public MutableLiveData<LoggedInUser> getLoginResult() {
    public LoginViewModel() {
        loginApi = RetrofitUtils.getInstance().getRetrofit(LoginApiCenter.class);
    }
    MutableLiveData<Results.LoggedInUser> loginResult = new MutableLiveData<>();
    MutableLiveData<Results.StringApiResult> verifyResult = new MutableLiveData<>();
    MutableLiveData<Results.StringApiResult> resetResult = new MutableLiveData<>();
    MutableLiveData<Results.StringApiResult> registerResult = new MutableLiveData<>();
    public MutableLiveData<Results.LoggedInUser> getLoginResult() {
        return loginResult;
    }
    public void login(String username, String password) {
        // can be launched in a separate asynchronous job
        final Observable<LoggedInUser> userObservable = RetrofitUtils.getInstance().getRetrofit(LoginApiCenter.class).login(username, password);
        httpObserverOn(userObservable,new HttpObserver<LoggedInUser>(loginResult){});
    public MutableLiveData<Results.StringApiResult> getVerifyResult() {
        return verifyResult;
    }
    /**
     * 密码登录
     * @param username
     * @param password
     */
    public void login(String username, String password) {
        // can be launched in a separate asynchronous job
        final Observable<Results.LoggedInUser> userObservable = loginApi.login(username, password);
        httpObserverOnLoading(userObservable,new HttpObserver<Results.LoggedInUser>(loginResult){});
    }
    /**
     * 验证码登录
     * @param phone
     * @param code
     */
    public void loginByCode(String phone,String code){
        httpObserverOnLoading(loginApi.loginByCode(phone,code),
                new HttpObserver<Results.LoggedInUser>(loginResult){});
    }
    /**
     * 重置密码
     * @param phone
     * @param sms
     * @param pass
     */
    public void resetPwd(String phone,String sms,String pass){
        httpObserverOnLoading(loginApi.resetLoginPwd(phone, sms, pass), new HttpObserver<Results.StringApiResult>(resetResult) {});
    }
    /**
     * 注册
     * @param phone
     * @param sms
     * @param pass
     */
    public void register(String phone,String sms,String pass){
        httpObserverOnLoading(loginApi.register(phone, sms, pass), new HttpObserver<Results.StringApiResult>(resetResult) {});
    }
    /**
     * 注册密码
     * @param phone
     */
    public void getRegisterSMS(String phone){
        getVerifyCode("getRegisterSMS",phone);
    }
    /**
     * 忘记密码
     * @param phone
     */
    public void getForgetSMS(String phone){
        getVerifyCode("getForgetSMS",phone);
    }
    /**
     * 登录验证码
     * @param phone
     */
    public void getLoginSMS(String phone){
        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.StringApiResult>(verifyResult){});
    }
    /**
     * 随机字符串
     * @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
New file
@@ -0,0 +1,287 @@
package com.runt.open.mvvm.ui.login;
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;
import com.runt.open.mvvm.databinding.ActivityLoginBinding;
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;
/**
 * My father is Object, ites purpose of
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2022/1/29.
 */
public class RegisterLoginActivity extends BaseActivity<ActivityLoginBinding,LoginViewModel> {
    final String VERIFY_CODE = "verify_code";
    final String TAG = "RegisterLoginActivity";
    int type = 0;//0 登录,1忘记密码,2注册,-1短信登录
    @Override
    public void initViews() {
        binding.txtGetVerify.setOnClickListener(onclick);
        binding.txtForgot.setOnClickListener(onclick);
        binding.txtLogin.setOnClickListener(onclick);
        binding.txtRegister.setOnClickListener(onclick);
        binding.txtPrivacy.setOnClickListener(onclick);
        long getTime = getLongProjectPrefrence(VERIFY_CODE);
        long cha = new Date().getTime() - getTime;
        if(cha <1000*60){
            CodeTimer codeTimer = new CodeTimer(cha, 1000, binding.txtGetVerify);
            codeTimer.startUp();
        }
        changeView();
        binding.editPhone.setText(getStringProjectPrefrence(Configuration.KEY_USERNAME));
        viewModel.getVerifyResult().observe(this, stringApiResult -> {
           if(stringApiResult.code == 200){
           }else{
               showToast(stringApiResult.msg);
           }
        });
        viewModel.getLoginResult().observe(this,loggedInUser -> {
            if(loggedInUser.code == 200){
                putBooleanProjectPrefrence(Configuration.IS_LOGIN,true);
                putStringProjectPrefrence(Configuration.KEY_USERNAME,binding.editPhone.getText().toString());
                UserBean user = new Gson().fromJson(new Gson().toJson(loggedInUser.data) ,UserBean.class);
                UserBean.setUser(user);
                putStringProjectPrefrence(Configuration.KEY_TOKEN, user.getToken());
                MyLog.i("registerlogin",user.toString());
                showToast(R.string.login_success);
                setResult(RESULT_CODE_SUCESS);
                finish();
            }else{
                showToast(loggedInUser.msg);
            }
        });
    }
    CustomClickListener onclick = new CustomClickListener() {
        @Override
        protected void onSingleClick(View view) {
            switch (view.getId()){
                case R.id.button:
                    submit();
                    break;
                case R.id.txt_get_verify:
                    String phone = binding.editPhone.getText().toString();
                    if(!verifyPhone(phone)){//验证手机
                        return;
                    }
                    if(type==2){//获取注册验证码
                        viewModel.getRegisterSMS(phone);
                    }else if(type ==1){
                        viewModel.getForgetSMS( phone);
                    }else if(type == -1){
                        viewModel.getLoginSMS( phone);
                    }
                    break;
                case R.id.txt_privacy:
                    startActivity(new Intent(mContext, WebViewActivity.class).putExtra(PARAMS_URL,"http://www.hefan.space/privacyPolicy.html").putExtra(PARAMS_TITLE,"隐私政策"));
                    break;
                case R.id.txt_register:
                    type = 2;
                    changeView();
                    break;
                case R.id.txt_forgot:
                    type = 1;
                    changeView();
                    break;
                case R.id.txt_login:
                    if(type != 0 ){
                        type = 0;
                    }else  {
                        type = -1;
                    }
                    changeView();
                    break;
            }
        }
    };
    /**
     * 修改页面布局
     */
    private void changeView(){
        binding.button.setEnabled(true);
        binding.txtRegister.setVisibility(View.VISIBLE);
        binding.checkbox.setVisibility(View.GONE);
        binding.txtPrivacy.setVisibility(View.GONE);
        switch (type){
            case -1://短信登录
                binding.editVerifyCode.setVisibility(View.VISIBLE);
                binding.txtGetVerify.setVisibility(View.VISIBLE);
                binding.editPass.setVisibility(View.GONE);
                binding.editPassRepeat.setVisibility(View.GONE);
                binding.txtForgot.setVisibility(View.VISIBLE);
                binding.txtLogin.setText(getResources().getString(R.string.login));
                binding.button.setText(getResources().getString(R.string.login));
                binding.checkbox.setVisibility(View.VISIBLE);
                binding.txtPrivacy.setVisibility(View.VISIBLE);
                break;
            case 0://登录
                binding.editVerifyCode.setVisibility(View.GONE);
                binding.txtGetVerify.setVisibility(View.GONE);
                binding.editPass.setVisibility(View.VISIBLE);
                binding.editPassRepeat.setVisibility(View.GONE);
                binding.txtForgot.setVisibility(View.VISIBLE);
                binding.txtLogin.setText(getResources().getString(R.string.msg_login));
                binding.button.setText(getResources().getString(R.string.login));
                break;
            case 1://忘记密码
                binding.txtForgot.setVisibility(View.INVISIBLE);
                binding.editVerifyCode.setVisibility(View.VISIBLE);
                binding.txtGetVerify.setVisibility(View.VISIBLE);
                binding.editPass.setVisibility(View.VISIBLE);
                binding.editPassRepeat.setVisibility(View.VISIBLE);
                binding.txtLogin.setText(getResources().getString(R.string.login));
                binding.button.setText(getResources().getString(R.string.str_confirm));
                break;
            case 2://注册
                binding.checkbox.setVisibility(View.VISIBLE);
                binding.txtPrivacy.setVisibility(View.VISIBLE);
                binding.txtRegister.setVisibility(View.INVISIBLE);
                binding.editVerifyCode.setVisibility(View.VISIBLE);
                binding.txtGetVerify.setVisibility(View.VISIBLE);
                binding.editPass.setVisibility(View.VISIBLE);
                binding.editPassRepeat.setVisibility(View.VISIBLE);
                binding.txtLogin.setText(getResources().getString(R.string.login));
                binding.button.setText(getResources().getString(R.string.register));
                break;
        }
        clearText(binding.editPassRepeat,binding.editPass,binding.editPhone,binding.editVerifyCode);
    }
    private void clearText(EditText... editTextes){
        for(EditText editText :editTextes) {
            editText.setText("");
        }
    }
    /**
     * 提交数据
     */
    public void submit(){
        String phone = binding.editPhone.getText().toString();
        String pass = binding.editPass .getText().toString();
        String veriCode = binding.editVerifyCode.getText().toString();
        String invite = binding.editPassRepeat.getText().toString();
        if(!verifyPhone(phone)){//验证手机
            return;
        }
        switch (type){
            case -1://短信登录
                if(veriCode.length() == 0){//验证码
                    showToast(R.string.input_verify_code);
                    return;
                }
                if(!binding.checkbox.isChecked()){
                    showToast("请阅读并勾选《隐私条款》");
                    return;
                }
                viewModel.loginByCode(phone,veriCode);
                break;
            case 0:
                if(!verifyPassWord(pass)){//验证密码
                    return;
                }
                viewModel.login(phone,pass);
                break;
            case 1:
                if(!verifyPassWord(pass)){//验证密码
                    return;
                }
                if(veriCode.length() == 0){//验证码
                    showToast(R.string.input_verify_code);
                    return;
                }
                //新密码
                if(invite.length() == 0){
                    showToast(R.string.input_pass);
                    return;
                } else if(!invite.equals(pass)){
                    showToast(R.string.str_new_verify_failed);
                    return;
                }
                viewModel.resetPwd(phone,veriCode,pass);
                break;
            case 2://注册
                if(!verifyPassWord(pass)){//验证密码
                    return;
                }
                if(veriCode.length() == 0){//验证码
                    showToast(R.string.input_verify_code);
                    return;
                }
                if(!binding.checkbox.isChecked()){
                    showToast("请阅读并勾选《隐私条款》");
                    return;
                }
                viewModel.register(phone,veriCode,pass);
                break;
        }
    }
    /**
     * 验证密码
     * @param pass
     * @return
     */
    public boolean verifyPassWord(String pass){
        if(pass.length() == 0){
            showToast(R.string.input_pass);
            return false;
        } else if(!verifyPass(pass)){
            showToast(R.string.str_pass_format_failed);
            return false;
        }
        return  true;
    }
    /**
     * 验证手机号
     * @param phone
     * @return
     */
    public boolean verifyPhone(String phone ){
        if(phone.length() == 0){
            showToast(R.string.input_phone);
            return false;
        }else if(phone.length()<5 || !PhoneUtil.isMobileNO(phone)){
            showToast(R.string.str_phone_format_failed);
            return false;
        }
        return true;
    }
    /**
     * 验证密码
     * @param pass
     * @return
     */
    public boolean verifyPass(String pass){
        return AlgorithmUtils.pwdLevel(pass)>1 && pass.length()>=6 && pass.length() <=12;
    }
}
app/src/main/java/com/runt/open/mvvm/ui/web/WebViewActivity.java
New file
@@ -0,0 +1,159 @@
package com.runt.open.mvvm.ui.web;
import android.os.Handler;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.LayoutAnimationController;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.base.model.BaseViewModel;
import com.runt.open.mvvm.databinding.ActivityWebBinding;
import com.runt.open.mvvm.util.MyAnimations;
import com.runt.open.mvvm.util.MyLog;
/**
 * My father is Object, ites purpose of
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-9-16.
 */
public class WebViewActivity extends BaseActivity<ActivityWebBinding, BaseViewModel> {
    private String url;
    private  int linProgressWidth;
    @Override
    public void initViews() {
        url = getIntent().getSerializableExtra(PARAMS_URL)+"";
        setTitle(getIntent().getSerializableExtra(PARAMS_TITLE)+"");
        initCompent();
    }
    int count = 100;
    int index = 100;
    private void initCompent(){
        binding.browser.getSettings().setJavaScriptEnabled(true);
        binding.browser.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
        //跳转至拼接好的地址
        //mBaseHandler.sendMessage(msg);//http://192.168.5.156:8080/MyFinance/gd16/1.html
        binding.browser.loadUrl(url);
        binding.browser.setWebViewClient(new myWebViewClient());
        binding.browser.setWebChromeClient(new WebChromeClient(){
            @Override
            public void onProgressChanged(WebView view,final int newProgress) {
                MyLog.i("onProgressChanged","--newProgress:--"+newProgress);
                MyLog.i("onProgressChanged","--binding.viewProgressbar:--"+binding.viewProgressbar.getWidth());
                final LayoutAnimationController.AnimationParameters animation= new LayoutAnimationController.AnimationParameters();   //得到一个LayoutAnimationController对象;
                animation.index =index++ ;
                animation.count = count++ ;
                if (newProgress == 100) {
                    MyAnimations.hideAnimaInSitu(binding.linProgressbar);
                    MyAnimations.makeViewMove(binding.viewProgressbar.getTranslationX(),0,0,0,binding.viewProgressbar);
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            binding.linProgressbar.setVisibility(View.GONE);
                        }
                    },MyAnimations.ANIMA_TIME);
                } else {
                    if (View.VISIBLE != binding.linProgressbar.getVisibility()) {
                        MyAnimations.showAnimaInSitu(binding.linProgressbar);
                        if(linProgressWidth==0){
                            final ViewTreeObserver vto = binding.linProgressbar.getViewTreeObserver();
                            vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                                public boolean onPreDraw() {
                                    linProgressWidth = binding.linProgressbar.getMeasuredWidth();
                                    binding.viewProgressbar.setTranslationX(0-linProgressWidth);
                                    binding.linProgressbar.getViewTreeObserver().removeOnPreDrawListener(this);
                                    return true;
                                }
                            });
                        }else{
                            binding.viewProgressbar.setTranslationX(0-linProgressWidth);
                        }
                    }
                    if(linProgressWidth!=0){
                        MyAnimations.makeViewMove(binding.viewProgressbar.getTranslationX(),0-linProgressWidth+linProgressWidth/100*newProgress,0,0,binding.viewProgressbar,MyAnimations.ANIMA_TIME*3);
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                MyAnimations.makeViewMove(binding.viewProgressbar.getTranslationX(),binding.viewProgressbar.getTranslationX()+300,0,0,binding.viewProgressbar,MyAnimations.ANIMA_TIME*10);
                            }
                        },MyAnimations.ANIMA_TIME*3);
                    }
                }
                super.onProgressChanged(view, newProgress);
            }
        });
        binding.browser.getSettings().setSavePassword(false);
        //Toast.makeText(mContext,"进入浏览器",Toast.LENGTH_SHORT).show();
        String Scale = String.valueOf(binding.browser.getScale());
        MyLog.i("Runt","--Scale:--"+Scale);
        int screenDensity=getResources().getDisplayMetrics().densityDpi;
        MyLog.i("Runt", "--screenDensity:--"+String.valueOf(screenDensity));  //60-160-240
    }
    private class myWebViewClient extends WebViewClient {
        /**
         * 每加载一张图片资源执行一次
         */
        @Override
        public void onLoadResource(WebView view, String url) {
            // TODO Auto-generated method stub
            super.onLoadResource(view, url);
            //MyLog.i("WebView", "onLoadResource "+url);
        }
        @Override
        public void onPageFinished(WebView view, String url) {
            hideProgressBar();
            //MyLog.i("WebView", "onPageFinished "+url);
        }
        /**
         * 获取页面跳转的链接
         */
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            // TODO Auto-generated method stub
            //MyLog.i("UrlLoading", "UrlLoading 正在跳转页面"+url);
            view.loadUrl(url);
            return true;
        }
        @Override
        public void onReceivedError(WebView view, int errorCode,
                                    String description, String failingUrl) {
            // TODO Auto-generated method stub
            super.onReceivedError(view, errorCode, description, failingUrl);
            Toast.makeText(mContext, "加载失败,请稍候再试", Toast.LENGTH_SHORT).show();
            hideProgressBar();
        }
    }
    private void hideProgressBar(){
        MyAnimations.hideAnimaInSitu(binding.linProgressbar);
        MyAnimations.makeViewMove(binding.viewProgressbar.getTranslationX(),0,0,0,binding.viewProgressbar);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                binding.linProgressbar.setVisibility(View.GONE);
            }
        },MyAnimations.ANIMA_TIME*2);
    }
}
app/src/main/java/com/runt/open/mvvm/util/Configuration.java
File was deleted
app/src/main/java/com/runt/open/mvvm/util/MyAnimations.java
New file
@@ -0,0 +1,477 @@
package com.runt.open.mvvm.util;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.RelativeLayout;
import com.facebook.rebound.SimpleSpringListener;
import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringConfig;
import com.facebook.rebound.SpringSystem;
import com.runt.open.mvvm.R;
/**
 * Created by Administrator on 2017/11/30.
 */
public class MyAnimations {
    public static final int ANIMA_TIME = 300;
    public static final float MOVE_SPACE = 1;
    public static final float SITU = 0;
    /**
     *  移动控件
     * @param x                x初始位置
     * @param distanceX     x移动的距离
     * @param y             y初始位置
     * @param distanceY     y移动的距离
     * @param view
     */
    public static void makeViewMove(float x ,float  distanceX , float y ,float  distanceY, View view){
        setAnimator("translationY", y, distanceY,view,ANIMA_TIME);
        setAnimator("translationX", x, distanceX,view,ANIMA_TIME);
    }
    /**
     *  移动控件
     * @param x                x初始位置
     * @param distanceX     x移动的距离
     * @param y             y初始位置
     * @param distanceY     y移动的距离
     * @param view
     */
    public static void makeViewMove(float x , float  distanceX , float y , float  distanceY, View view, int animTime){
        setAnimator("translationY", y, distanceY,view,animTime);
        setAnimator("translationX", x, distanceX,view,animTime);
    }
    private static  void setAnimator(String attribute, float from, float to, View view, int animTime){
        ObjectAnimator.ofFloat(view, attribute, from, to).setDuration(animTime).start();
    }
    public static void setLayoutMargin(View view, int left, int top, int right, int bottom)
    {
        //克隆view的width、height、margin的值生成margin对象
        ViewGroup.MarginLayoutParams margin=new ViewGroup.MarginLayoutParams(view.getLayoutParams());
        //设置新的边距
        margin.setMargins(left, top, right, bottom);
        //把新的边距生成layoutParams对象
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(margin);
        //设制view的新的位置
        view.setLayoutParams(layoutParams);
    }
    /**
     * 原地不动
     * @return
     */
    private static TranslateAnimation makeInSitu(){
        TranslateAnimation mAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF,SITU,//大于0 则从右向当前位置移动,反之则从左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从当前位置向右移动,反之则向左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从下方向当前位置,反之则从上方
                Animation.RELATIVE_TO_SELF,SITU);//大于0从当前位置向下移动,反之则向上方
        mAction.setDuration(ANIMA_TIME);
        return mAction;
    }
    /**
     * 从顶部显示
     * @return
     */
    private static TranslateAnimation makeInFromTop(){
        TranslateAnimation mAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF,SITU,//大于0 则从右向当前位置移动,反之则从左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从当前位置向右移动,反之则向左
                Animation.RELATIVE_TO_SELF,0-MOVE_SPACE,//大于0 则从下方向当前位置,反之则从上方
                Animation.RELATIVE_TO_SELF,SITU);//大于0从当前位置向下移动,反之则向上方
        mAction.setDuration(ANIMA_TIME);
        return mAction;
    }
    /**
     * 从底部显示
     * @return
     */
    private static TranslateAnimation makeInFromBottom(){
        TranslateAnimation mAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF,SITU,//大于0 则从右向当前位置移动,反之则从左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从当前位置向右移动,反之则向左
                Animation.RELATIVE_TO_SELF,MOVE_SPACE,//大于0 则从下方向当前位置,反之则从上方
                Animation.RELATIVE_TO_SELF,SITU);//大于0从当前位置向下移动,反之则向上方
        mAction.setDuration(ANIMA_TIME);
        return mAction;
    }
    /**
     * 从左侧显示
     * @return
     */
    private static TranslateAnimation makeInFromLeft(){
        TranslateAnimation mAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0-MOVE_SPACE,//大于0 则从右向当前位置移动,反之则从左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从当前位置向右移动,反之则向左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从下方向当前位置,反之则从上方
                Animation.RELATIVE_TO_SELF,SITU);//大于0从当前位置向下移动,反之则向上方
        mAction.setDuration(ANIMA_TIME);
        return mAction;
    }
    /**
     * 从右侧显示
     * @return
     */
    private static TranslateAnimation makeInFromRight(){
        TranslateAnimation mAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF,MOVE_SPACE,//大于0 则从右向当前位置移动,反之则从左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从当前位置向右移动,反之则向左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从下方向当前位置,反之则从上方
                Animation.RELATIVE_TO_SELF,SITU);//大于0从当前位置向下移动,反之则向上方
        mAction.setDuration(ANIMA_TIME);
        return mAction;
    }
    /**
     * 向上隐藏
     * @return
     */
    private static TranslateAnimation makeOutToTop(){
        TranslateAnimation mAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF,SITU,//大于0 则从右向当前位置移动,反之则从左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从当前位置向右移动,反之则向左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从下方向当前位置,反之则从上方
                Animation.RELATIVE_TO_SELF,0-MOVE_SPACE);//大于0从当前位置向下移动,反之则向上方
        mAction.setDuration(ANIMA_TIME);
        return mAction;
    }
    /**
     * 向左隐藏
     * @return
     */
    private static TranslateAnimation makeOutToLeft(){
        TranslateAnimation mAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF,SITU,//大于0 则从右向当前位置移动,反之则从左
                Animation.RELATIVE_TO_SELF,0-MOVE_SPACE,//大于0 则从当前位置向右移动,反之则向左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从下方向当前位置,反之则从上方
                Animation.RELATIVE_TO_SELF,SITU);//大于0从当前位置向下移动,反之则向上方
        mAction.setDuration(ANIMA_TIME);
        return mAction;
    }
    /**
     * 向右隐藏
     * @return
     */
    private static TranslateAnimation makeOutToRight(){
        TranslateAnimation mAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF,SITU,//大于0 则从右向当前位置移动,反之则从左
                Animation.RELATIVE_TO_SELF,MOVE_SPACE,//大于0 则从当前位置向右移动,反之则向左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从下方向当前位置,反之则从上方
                Animation.RELATIVE_TO_SELF,SITU);//大于0从当前位置向下移动,反之则向上方
        mAction.setDuration(ANIMA_TIME);
        return mAction;
    }
    /**
     * 向下隐藏
     * @return
     */
    private static TranslateAnimation makeOutToButtom(){
        TranslateAnimation mAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF,SITU,//大于0 则从右向当前位置移动,反之则从左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从当前位置向右移动,反之则向左
                Animation.RELATIVE_TO_SELF,SITU,//大于0 则从下方向当前位置,反之则从上方
                Animation.RELATIVE_TO_SELF,MOVE_SPACE);//大于0从当前位置向下移动,反之则向上方
        mAction.setDuration(ANIMA_TIME);
        return mAction;
    }
    /***
     * 动画显示 从右向左左显示
     *
     * @param view
     */
    public static void showAnimaRightToLeft(View view) {
        view.setVisibility(View.VISIBLE);
        Animation mAni;
        mAni =  makeInFromRight();
        mAni.setDuration(ANIMA_TIME);
        view.setAnimation(mAni);
    }
    /***
     * 动画显示 从右向左左显示
     *
     * @param view
     */
    public static void showAnimaLeftToRight(View view) {
        view.setVisibility(View.VISIBLE);
        Animation mAni;
        mAni =  makeInFromLeft();
        mAni.setDuration(ANIMA_TIME);
        view.setAnimation(mAni);
    }
    /**
     * 动画隐藏 从下往上
     *
     * @param view
     */
    public static void hideAnimaBottomToTop(View view) {
        view.setVisibility(View.INVISIBLE);
        Animation mAni;
        mAni = makeOutToTop();
        mAni.setDuration(ANIMA_TIME);
        view.setAnimation(mAni);
    }
    /**
     * 动画隐藏 从左往右
     *
     * @param view
     */
    public static void hideAnimaLeftToRight(View view) {
        view.setVisibility(View.INVISIBLE);
        Animation mAni;
        mAni = makeOutToRight();
        mAni.setDuration(ANIMA_TIME);
        view.setAnimation(mAni);
    }
    /**
     * 动画隐藏 从右往左
     *
     * @param view
     */
    public static void hideAnimaRightToLeft(View view) {
        view.setVisibility(View.INVISIBLE);
        Animation mAni;
        mAni = makeOutToLeft();
        mAni.setDuration(ANIMA_TIME);
        view.setAnimation(mAni);
    }
    /**
     * 动画隐藏 原地
     *
     * @param view
     */
    public static void hideAnimaInSitu(View view) {
        view.setVisibility(View.INVISIBLE);
        Animation mAni;
        mAni = makeInSitu();
        mAni.setDuration(ANIMA_TIME);
        view.setAnimation(mAni);
    }
    /**
     * 动画显示 原地
     *
     * @param view
     */
    public static void showAnimaInSitu(View view) {
        view.setVisibility(View.VISIBLE);
        Animation mAni;
        mAni = makeInSitu();
        mAni.setDuration(ANIMA_TIME);
        view.setAnimation(mAni);
    }
    /**
     * 动画显示 从上往下走
     *
     * @param view
     */
    public static void showAnimaTopToBottom(View view) {
        view.setVisibility(View.VISIBLE);
        Animation mAni;
        mAni = makeInFromTop();
        mAni.setDuration(ANIMA_TIME);
        view.setAnimation(mAni);
    }
    /**
     *  冒泡式显示控件
     * @param view
     */
    public static void showReBound(final View view){
        Log.i("","showReBound view:"+view);
        view.setVisibility(View.GONE);
        showAnimaInSitu(view);
        SpringSystem springSystem = SpringSystem.create();
        final Spring spring = springSystem.createSpring();
        spring.setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(100,7));//qcTension拉力和qcFriction摩擦力参数
        spring.addListener(new SimpleSpringListener() {
            @Override
            public void onSpringUpdate(Spring spring) {
                float value = (float) spring.getCurrentValue();
                float scale = value;
                view.setScaleX(scale);
                view.setScaleY(scale);
            }
        });
        spring.setEndValue(1);//控件拉伸收缩的倍率
    }
    /**
     * 冒泡式放大控件
     * @param view
     */
    public static void showReBoundBig(final View view){
        Log.i("","showReBound view:"+view);
        view.setVisibility(View.GONE);
        showAnimaInSitu(view);
        SpringSystem springSystem = SpringSystem.create();
        final Spring spring = springSystem.createSpring();
        spring.setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(100,7));//qcTension拉力和qcFriction摩擦力参数
        spring.addListener(new SimpleSpringListener() {
            @Override
            public void onSpringUpdate(Spring spring) {
                float value = (float) spring.getCurrentValue();
                float scale = value;
                view.setScaleX(scale);
                view.setScaleY(scale);
            }
        });
        spring.setEndValue(3);//控件拉伸收缩的倍率
    }
    /**
     * 收缩式 隐藏
     * @param view
     * @param context
     */
    public static void hideReBound(final View view, final Context context){
        /*showReBound(view);
        @SuppressLint("ResourceType") Animator animator = AnimatorInflater.loadAnimator(context, R.anim.anima_make_none);
        animator.setTarget(view);
        animator.start();*/
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                float value = (float) msg.obj;
                if(value>2){
                    @SuppressLint("ResourceType") Animator animator = AnimatorInflater.loadAnimator(context, R.anim.anima_make_none);
                    animator.setTarget(view);
                    animator.start();
                    animator.addListener(new Animator.AnimatorListener() {
                        @Override
                        public void onAnimationEnd(Animator animation, boolean isReverse) {
                            MyLog.i("hideReBound","onAnimationEnd "+animation+" "+isReverse);
                            view.setVisibility(View.GONE);
                        }
                        @Override
                        public void onAnimationStart(Animator animation, boolean isReverse) {}
                        @Override
                        public void onAnimationStart(Animator animator) {
                        }
                        @Override
                        public void onAnimationEnd(Animator animator) {
                            MyLog.i("hideReBound","onAnimationEnd "+animator);
                            view.setVisibility(View.GONE);
                        }
                        @Override
                        public void onAnimationCancel(Animator animator) {}
                        @Override
                        public void onAnimationRepeat(Animator animator) { }
                    });
                    hideAnimaInSitu(view);
                }else {
                    value = 1f + (value);
                    view.setScaleX(value);
                    view.setScaleY(value);
                }
            }
        };
        new Thread(){
            @Override
            public void run() {
                try {
                    int sleep = 10;
                    for(int i =0 ; i < 100 ; i+=sleep){
                        sleep(sleep);
                        Message msg = new Message();
                        msg.obj = (float)i/100/3;
                        handler.sendMessage(msg);
                    }
                    Message msg = new Message();
                    msg.obj = 3f;
                    handler.sendMessage(msg);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
    static int time = 0;
    public static void animaScale(final Context context, final View view, final float x, final float y){
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                float value = (float) msg.obj;
                value = 1f + (value);
                view.setScaleX(value);
                view.setScaleY(value);
            }
        };
        new Thread(){
            @Override
            public void run() {
                try {
                    int sleep = 10;
                    for(int i =0 ; i < 100 ; i+=sleep){
                        sleep(sleep);
                        Message msg = new Message();
                        msg.obj = (float)i/100/3;
                        handler.sendMessage(msg);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
    /**
     *  冒泡式拉伸控件
     * @param view
     */
    public static void scaleReBoundX(final View view, final int size){
        Log.i("","scaleReBound view:"+view);
        SpringSystem springSystem = SpringSystem.create();
        final Spring spring = springSystem.createSpring();
        spring.setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(100,7));//qcTension拉力和qcFriction摩擦力参数
        spring.addListener(new SimpleSpringListener() {
            @Override
            public void onSpringUpdate(Spring spring) {
                float value = (float) spring.getCurrentValue();
                float scale = value;
                view.setScaleX(scale);
                view.setScaleY(scale);
            }
        });
        spring.setEndValue(size);//控件拉伸收缩的倍率
    }
    public static void scalXAnima(final View view, float from, float to){
        setAnimator("scaleX",from,to,view,ANIMA_TIME);
    }
}
app/src/main/java/com/runt/open/mvvm/util/PhoneUtil.java
New file
@@ -0,0 +1,77 @@
package com.runt.open.mvvm.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * My father is Object, ites purpose of
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-4-14.
 */
public class PhoneUtil {
    /**
     * 验证中国大陆手机号是否合法
     * @return
     */
    public static boolean isCNMobileNO(String mobile){
        if (mobile.length() != 11)
        {
            return false;
        }else{
            /**
             * 移动号段正则表达式
             */
            String pat1 = "^((13[4-9])|(147)|(15[0-2,7-9])|(178)|(18[2-4,7-8]))\\d{8}|(1705)\\d{7}$";
            /**
             * 联通号段正则表达式
             */
            String pat2  = "^((13[0-2])|(145)|(15[5-6])|(176)|(18[5,6]))\\d{8}|(1709)\\d{7}$";
            /**
             * 电信号段正则表达式
             */
            String pat3  = "^((133)|(153)|(177)|(18[0,1,9])|(149))\\d{8}$";
            /**
             * 虚拟运营商正则表达式
             */
            String pat4 = "^((170))\\d{8}|(1718)|(1719)\\d{7}$";
            Pattern pattern1 = Pattern.compile(pat1);
            Matcher match1 = pattern1.matcher(mobile);
            boolean isMatch1 = match1.matches();
            if(isMatch1){
                return true;
            }
            Pattern pattern2 = Pattern.compile(pat2);
            Matcher match2 = pattern2.matcher(mobile);
            boolean isMatch2 = match2.matches();
            if(isMatch2){
                return true;
            }
            Pattern pattern3 = Pattern.compile(pat3);
            Matcher match3 = pattern3.matcher(mobile);
            boolean isMatch3 = match3.matches();
            if(isMatch3){
                return true;
            }
            Pattern pattern4 = Pattern.compile(pat4);
            Matcher match4 = pattern4.matcher(mobile);
            boolean isMatch4 = match4.matches();
            if(isMatch4){
                return true;
            }
            return false;
        }
    }
    public static boolean isMobileNO( String phone) {
        //china phone
        String regex = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$";
        if (phone.length() != 11) {
            return false;
        } else {
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(phone);
            return m.matches();
        }
    }
}
app/src/main/java/com/runt/open/mvvm/util/PreferencesUtils.java
New file
@@ -0,0 +1,352 @@
package com.runt.open.mvvm.util;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.collection.ArraySet;
import java.util.Set;
/**
 * PreferencesUtils, easy to get or put data
 * <ul>
 * <strong>Preference Name</strong>
 * <li>you can change preference name by {@link #PREFERENCE_NAME}</li>
 * </ul>
 * <ul>
 * <strong>Put Value</strong>
 * </ul>
 *
 * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-3-6
 */
public class PreferencesUtils {
    public static final String PREFERENCE_NAME="zipper";
    public static final String PROJECT = "project";
    public static final String USER = "user";
    public static final String VISITOR = "visitor";
    private PreferencesUtils() {
        throw new AssertionError();
    }
    public static boolean clearData(Context context, String keyShared){
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        return settings.edit().clear().commit();
    }
    public static boolean clearData(Context context, String key, String keyShared){
        putString(context,key,null,keyShared);
        return true;
    }
    /**
     * remove key preferences
     *
     * @param context
     * @param key The name of the preference to modify
     * @return True if the new values were successfully written to persistent storage.
     */
    public static void  removeKey(Context context, String key, String keyShared){
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.remove(key);
    }
    /**
     * remove value preferences
     *
     * @param context
     * @param value The name of the preference to modify
     * @return True if the new values were successfully written to persistent storage.
     */
    public static void  removeValue(Context context, String value, String keyShared){
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.remove(value);
    }
    /**
     * put string preferences
     *
     * @param context
     * @param key The name of the preference to modify
     * @param value The new value for the preference
     * @return True if the new values were successfully written to persistent storage.
     */
    public static boolean putString(Context context, String key, String value, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putString(key, value);
        return editor.commit();
    }
    /**
     * get string preferences
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @return The preference value if it exists, or null. Throws ClassCastException if there is a preference with this
     *         name that is not a string
     * @see #getString(Context, String, String)
     */
    public static String getString(Context context, String key, String keyShared) {
        return getString(context, key, null,keyShared);
    }
    /**
     * get string preferences
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @param defaultValue Value to return if this preference does not exist
     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
     *         this name that is not a string
     */
    public static String getString(Context context, String key, String defaultValue, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        return settings.getString(key, defaultValue);
    }
    /**
     * put int preferences
     *
     * @param context
     * @param key The name of the preference to modify
     * @param value The new value for the preference
     * @return True if the new values were successfully written to persistent storage.
     */
    public static boolean putInt(Context context, String key, int value, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putInt(key, value);
        return editor.commit();
    }
    /**
     * get int preferences
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this
     *         name that is not a int
     */
    public static int getInt(Context context, String key, String keyShared) {
        return getInt(context, key, -1,keyShared);
    }
    /**
     * get int preferences
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @param defaultValue Value to return if this preference does not exist
     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
     *         this name that is not a int
     */
    public static int getInt(Context context, String key, int defaultValue, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        try {
            return settings.getInt(key, defaultValue);
        }catch (ClassCastException e){
            try {
                return Integer.parseInt(settings.getString(key,defaultValue+""));
            }catch (NumberFormatException en){
                return defaultValue;
            }
        }
    }
    /**
     * put long preferences
     *
     * @param context
     * @param key The name of the preference to modify
     * @param value The new value for the preference
     * @return True if the new values were successfully written to persistent storage.
     */
    public static boolean putLong(Context context, String key, long value, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putLong(key, value);
        return editor.commit();
    }
    /**
     * get long preferences
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this
     *         name that is not a long
     */
    public static long getLong(Context context, String key, String keyShared) {
        return getLong(context, key, -1,keyShared);
    }
    /**
     * get long preferences
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @param defaultValue Value to return if this preference does not exist
     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
     *         this name that is not a long
     */
    public static long getLong(Context context, String key, long defaultValue, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        try {
            return settings.getLong(key, defaultValue);
        }catch (ClassCastException e){
            try {
                return Long.parseLong(settings.getString(key,defaultValue+""));
            }catch (NumberFormatException en){
                return defaultValue;
            }
        }
    }
    /**
     * put float preferences
     *
     * @param context
     * @param key The name of the preference to modify
     * @param value The new value for the preference
     * @return True if the new values were successfully written to persistent storage.
     */
    public static boolean putFloat(Context context, String key, float value, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putFloat(key, value);
        return editor.commit();
    }
    /**
     * get float preferences
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this
     *         name that is not a float
     */
    public static float getFloat(Context context, String key, String keyShared) {
        return getFloat(context, key, -1,keyShared);
    }
    /**
     * get float preferences
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @param defaultValue Value to return if this preference does not exist
     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
     *         this name that is not a float
     */
    public static float getFloat(Context context, String key, float defaultValue, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        try {
            return settings.getFloat(key, defaultValue);
        }catch (ClassCastException e){
            try {
                return Float.parseFloat(settings.getString(key,defaultValue+""));
            }catch (NumberFormatException en){
                return defaultValue;
            }
        }
    }
    /**
     * put boolean preferences
     *
     * @param context
     * @param key The name of the preference to modify
     * @param value The new value for the preference
     * @return True if the new values were successfully written to persistent storage.
     */
    public static boolean putBoolean(Context context, String key, boolean value, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putBoolean(key, value);
        return editor.commit();
    }
    /**
     * get boolean preferences, default is false
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @return The preference value if it exists, or false. Throws ClassCastException if there is a preference with this
     *         name that is not a boolean
     */
    public static boolean getBoolean(Context context, String key, String keyShared) {
        return getBoolean(context, key, false,keyShared);
    }
    /**
     * get boolean preferences
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @param defaultValue Value to return if this preference does not exist
     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
     *         this name that is not a boolean
     */
    public static boolean getBoolean(Context context, String key, boolean defaultValue, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        try {
            return settings.getBoolean(key, defaultValue);
        }catch (ClassCastException e){
            try {
                return Boolean.parseBoolean(settings.getString(key,defaultValue+""));
            }catch (NumberFormatException en){
                return defaultValue;
            }
        }
    }
    /**
     * put boolean preferences
     *
     * @param context
     * @param key The name of the preference to modify
     * @param value The new value for the preference ,  the value of set ,canot be the other class out of java collection
     * @return True if the new values were successfully written to persistent storage.
     */
    public static boolean putStringSet(Context context, String key, Set value, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putStringSet(key, value);
        return editor.commit();
    }
    /**
     * get boolean preferences, default is false
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @return The preference value if it exists, or false. Throws ClassCastException if there is a preference with this
     *         name that is not a boolean 获取出来的值最终被转换为hashset类型
     */
    public static Set getStringSet(Context context, String key, String keyShared) {
        return getStringSet(context, key,new ArraySet(),keyShared);
    }
    /**
     * get boolean preferences
     *
     * @param context
     * @param key The name of the preference to retrieve
     * @param defaultValue Value to return if this preference does not exist
     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
     *         this name that is not a boolean 获取出来的值最终被转换为hashset类型
     */
    public static Set getStringSet(Context context, String key, Set defaultValue, String keyShared) {
        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
        return settings.getStringSet(key, defaultValue);
    }
}
app/src/main/res/color/btn_txt_normal.xml
New file
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    tools:ignore="MissingDefaultResource">
     <item android:state_enabled="false" android:color="@color/txt_enable"/> <!-- pressed -->
     <item android:state_pressed="true" android:color="@color/txt_cusor_color"/> <!-- pressed -->
     <item android:state_focused="true" android:color="@color/txt_hint"/> <!-- focused -->
     <item android:color="@color/white"/> <!-- default -->
</selector>
app/src/main/res/layout/activity_login.xml
New file
@@ -0,0 +1,138 @@
<?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"
        app:leftDrawable="@mipmap/ic_arrow_back_black_24dp"
        app:titleText="登录"
        tools:ignore="MissingConstraints" />
    <ImageView
        android:id="@+id/img_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="50dp"
        android:src="@mipmap/app_icon"
        android:adjustViewBounds="true"
        app:layout_constraintTop_toBottomOf="@id/title_bar"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        />
    <com.runt.open.mvvm.widgets.ClearEditText
        android:id="@+id/edit_phone"
        style="@style/login_edit"
        android:hint="@string/input_phone"
        android:inputType="phone"
        app:layout_constraintTop_toBottomOf="@id/img_icon"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        />
    <com.runt.open.mvvm.widgets.ClearEditText
        android:id="@+id/edit_verify_code"
        style="@style/login_edit"
        android:hint="@string/input_verify_code"
        android:inputType="number"
        app:layout_constraintTop_toBottomOf="@id/edit_phone"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>
    <TextView
        android:id="@+id/txt_get_verify"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/link"
        android:layout_marginRight="@dimen/default_margin_lr"
        android:text="@string/get_verify_code"
        android:gravity="center"
        app:layout_constraintTop_toTopOf="@id/edit_verify_code"
        app:layout_constraintRight_toRightOf="@id/edit_verify_code"
        app:layout_constraintBottom_toBottomOf="@id/edit_verify_code"/>
    <com.runt.open.mvvm.widgets.ClearEditText
        android:id="@+id/edit_pass"
        style="@style/login_edit"
        android:inputType="textPassword"
        android:hint="@string/input_pass"
        app:layout_constraintTop_toBottomOf="@id/edit_verify_code"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />
    <com.runt.open.mvvm.widgets.ClearEditText
        android:id="@+id/edit_pass_repeat"
        style="@style/login_edit"
        android:hint="@string/str_input_repeat"
        android:inputType="textPassword"
        app:layout_constraintTop_toBottomOf="@id/edit_pass"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>
    <TextView
        android:id="@+id/txt_forgot"
        style="@style/login_link"
        android:text="@string/forgot_pass"
        app:layout_constraintTop_toBottomOf="@id/edit_pass_repeat"
        app:layout_constraintLeft_toLeftOf="parent" />
    <TextView
        android:id="@+id/txt_register"
        style="@style/login_link"
        android:text="@string/register"
        app:layout_constraintTop_toBottomOf="@id/edit_pass_repeat"
        app:layout_constraintLeft_toRightOf="@id/txt_forgot"
        app:layout_constraintRight_toLeftOf="@id/txt_login" />
    <TextView
        android:id="@+id/txt_login"
        style="@style/login_link"
        android:text="@string/login"
        app:layout_constraintTop_toBottomOf="@id/edit_pass_repeat"
        app:layout_constraintRight_toRightOf="parent"  />
    <androidx.appcompat.widget.AppCompatCheckBox
        android:id="@+id/checkbox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/default_margin_lr"
        android:layout_marginBottom="@dimen/default_margin_lr"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintBottom_toTopOf="@id/button"/>
    <TextView
        android:id="@+id/txt_privacy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/default_margin_td"
        android:paddingBottom="@dimen/default_margin_td"
        android:paddingRight="@dimen/default_margin_lr"
        android:background="@color/white"
        android:textColor="@color/link"
        android:text=" 我已阅读并同意《隐私条款》"
        app:layout_constraintLeft_toRightOf="@id/checkbox"
        app:layout_constraintTop_toTopOf="@id/checkbox"
        app:layout_constraintBottom_toBottomOf="@id/checkbox"/>
    <Button
        android:id="@+id/button"
        style="@style/btn_normal"
        android:layout_marginBottom="@dimen/default_margin_lr"
        android:text="@string/login"
        android:layout_marginLeft="@dimen/default_margin_lr"
        android:layout_marginRight="@dimen/default_margin_lr"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/layout/activity_web.xml
New file
@@ -0,0 +1,40 @@
<?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"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        style="@style/titlebar" />
    <LinearLayout
        android:id="@+id/lin_progressbar"
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:background="@drawable/bg_gradient_gray"
        app:layout_constraintTop_toBottomOf="@id/title_bar"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:visibility="gone">
        <View
            android:id="@+id/view_progressbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:animateLayoutChanges="true"
            android:background="@drawable/bg_gradient_blue"/>
    </LinearLayout>
    <WebView
        android:id="@+id/browser"
        app:layout_constraintTop_toBottomOf="@id/title_bar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_width="match_parent"
        android:layout_height="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/values/strings.xml
@@ -2,4 +2,71 @@
    <string name="title_home">Home</string>
    <string name="title_dashboard">Dashboard</string>
    <string name="title_notifications">Notifications</string>
    <string name="get_verify_code">获取验证码</string>
    <string name="select_region">选择国家/地区</string>
    <string name="input_phone">请输入手机号</string>
    <string name="input_pass">请输入密码</string>
    <string name="input_verify_code">请输入验证码</string>
    <string name="input_invite_code">请输入邀请码</string>
    <string name="forgot_pass">忘记密码?</string>
    <string name="register">注册</string>
    <string name="login">登录</string>
    <string name="msg_login">短信登录</string>
    <string name="str_input_old">请输入旧密码</string>
    <string name="str_input_new">请输入新密码</string>
    <string name="str_input_repeat">请再次输入新密码</string>
    <string name="str_pass_format_failed">密码要求6-12位数(数字/英文大小写/符号混合)格式</string>
    <string name="str_cannot_eques_old">新密码不能与旧密码相同</string>
    <string name="str_new_verify_failed">新密码两次输入不相同</string>
    <string name="str_edit_pass">修改密码</string>
    <string name="str_confirm">确认</string>
    <string name="str_invite">推荐码</string>
    <string name="str_mine">个人中心</string>
    <string name="str_setting">设置</string>
    <string name="str_msg">消息中心</string>
    <string name="str_version">版本号</string>
    <string name="str_msg_detail">资讯详情</string>
    <string name="str_copied">复制成功</string>
    <string name="str_get_data_failed">获取数据失败</string>
    <string name="str_phone_format_failed">输入手机格式不正确</string>
    <string name="login_pass_failed">账号或者密码错误,请重新输入</string>
    <string name="login_code_failed">验证码有误,请重新输入</string>
    <string name="comming_soon">敬请期待</string>
    <string name="notice">通知</string>
    <string name="save_img">保存图片</string>
    <string name="copy_url">复制链接</string>
    <string name="toast_login">请先登录后再操作</string>
    <string name="registed">账号已经注册了</string>
    <string name="failed">操作失败</string>
    <string name="none_account">账号不存在</string>
    <string name="loading">加载中</string>
    <string name="register_success">账号注册成功请登录</string>
    <string name="verify_failed">验证码失效</string>
    <string name="invite_error">错误的邀请码</string>
    <string name="update_pass_sucess">密码修改成功</string>
    <string name="update_pass_failed_old">旧密码错误</string>
    <string name="update_pass_failed">密码修改失败</string>
    <string name="login_success">登录成功</string>
    <string name="login_failed">登录失败</string>
    <string name="login_failed_pass">密码错误</string>
    <string name="new_version">新版本更新</string>
    <string name="str_cancel">取消</string>
    <string name="logouted">登录失效,请重新登录</string>
    <string name="forgot_update_pwd">点击忘记密码去修改</string>
    <string name="download">下载</string>
    <string name="created_at">更新</string>
    <string name="msg_deleted">消息已被删除</string>
    <string name="logout">退出登录</string>
    <string name="internet_failed">网络连接失败</string>
    <string name="str_miner_running">正在挖矿</string>
    <string name="str_apply">申请</string>
    <string name="str_permission">需要获取文件存储权限才能查看</string>
    <string name="str_wait">敬请期待</string>
    <string name="str_node_l">您当前为三级节点</string>
    <string name="str_email">官方邮箱:</string>
    <string name="str_website">官方网站:</string>
    <string name="str_restart">重启APP</string>
    <string name="str_success">成功</string>
    <string name="str_no_up">暂无更新</string>
</resources>
app/src/main/res/values/styles.xml
@@ -63,4 +63,48 @@
    <style name="TransparentDialog" parent="Theme.AppCompat.Dialog">
        <item name="android:windowBackground">@color/transparent</item>
    </style>
    <style name="titlebar">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">?attr/actionBarSize</item>
        <item name="android:background">@color/white</item>
        <item name="titleTextColor">@color/txt_normal</item>
        <item name="layout_constraintTop_toTopOf">parent</item>
        <item name="layout_constraintLeft_toLeftOf">parent</item>
        <item name="layout_constraintRight_toRightOf">parent</item>
        <item name="android:paddingLeft">@dimen/default_margin_lr</item>
        <item name="android:paddingRight">@dimen/default_margin_lr</item>
    </style>
    <style name="btn_normal">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:background">@drawable/btn_circle_blue</item>
        <item name="android:textColor">@color/btn_txt_normal</item>
        <item name="android:textSize">@dimen/title_size</item>
    </style>
    <style name="login_edit">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">@dimen/title_height</item>
        <item name="android:background">@drawable/bg_circle_default</item>
        <item name="android:layout_marginTop">@dimen/default_margin_td</item>
        <item name="android:paddingRight">@dimen/default_margin_lr</item>
        <item name="android:paddingLeft">@dimen/default_margin_lr</item>
        <item name="android:singleLine">true</item>
        <item name="android:gravity">center_vertical</item>
        <item name="android:layout_marginLeft">@dimen/default_margin_lr</item>
        <item name="android:layout_marginRight">@dimen/default_margin_lr</item>
    </style>
    <style name="login_link">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:paddingTop">@dimen/default_margin_td</item>
        <item name="android:paddingBottom">@dimen/default_margin_td</item>
        <item name="android:paddingRight">30dp</item>
        <item name="android:paddingLeft">30dp</item>
        <item name="android:textColor">@color/link</item>
    </style>
</resources>
app/src/main/res/values/themes.xml
@@ -3,7 +3,7 @@
    <style name="Theme.OpemMvvm" parent="Theme.MaterialComponents.Light.NoActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/sky</item>
        <item name="colorPrimaryVariant">@color/red</item>
        <item name="colorPrimaryVariant">@color/blue_sky</item>
        <item name="colorOnPrimary">@color/black</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/black_4</item>
@@ -12,6 +12,9 @@
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
        <!--全局设置文本颜色和大小-->
        <item name="android:textColor">@color/txt_normal</item>
        <item name="android:textSize">@dimen/txt_size</item>
    </style>