nilupeng
2022-07-29 fe4fa5323fc075ca7fdf3d6c8cfd20cb0a7d45f8
部分框架优化,
首页登录部分添加
17 files added
2 files renamed
33 files modified
15 files deleted
3699 ■■■■■ changed files
app/build.gradle 15 ●●●●● patch | view | raw | blame | history
app/libs/alipaySdk-15.7.5.aar patch | view | raw | blame | history
app/src/main/AndroidManifest.xml 3 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/MainActivity.java 68 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/MyApplication.java 26 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java 89 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/activities/BaseLoadPageActivity.java 96 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/activities/BaseTabActivity.java 4 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/activities/BaseTitleBarActivity.java 60 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/activities/LoadPageActivity.java 96 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java 146 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/fragments/BaseFragment.java 44 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/fragments/BaseLoadPageFragment.java 106 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/fragments/BaseTabFragment.java 20 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/fragments/LoadPageFragment.java 96 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/model/BaseLoadPageViewModel.java 13 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java 23 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/model/ImpViewModel.java 7 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/base/model/LoadPageViewModel.java 47 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/data/ApkUpGradeResult.java 2 ●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/data/HttpApiResult.java 2 ●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/data/LoadingCmd.java 21 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/data/PageResult.java 2 ●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/data/Results.java 6 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java 143 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java 11 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonResponseBodyConverter.java 6 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkCost.java 27 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkListenear.java 28 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java 113 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/utils/HttpPrintUtils.java 61 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/retrofit/utils/RetrofitUtils.java 3 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/login/LoginViewModel.java 28 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/login/RegisterLoginActivity.java 138 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/MainActivity.java 153 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/dashboard/DashboardFragment.java 45 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/dashboard/DashboardViewModel.java 19 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/home/HomeFragment.java 30 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/home/HomeViewModel.java 19 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/home/Message.java 8 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/home/MsgAdapter.java 33 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/mine/MineFragment.java 177 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/mine/MineViewModel.java 36 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/notifications/NotificationsFragment.java 45 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/notifications/NotificationsViewModel.java 19 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/main/service/ServiceFragment.java 63 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/splash/SplashActivity.java 79 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/splash/SplashViewModel.java 51 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/ui/web/WebViewActivity.java 51 ●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java 57 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/util/HandleDate.java 216 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/widgets/CircleImageView.java 245 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/widgets/CornerImageView.java 163 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/runt/open/mvvm/widgets/TitleBarView.java 14 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/activity_main.xml 30 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/activity_setting.xml 79 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/activity_splash.xml 4 ●●●● patch | view | raw | blame | history
app/src/main/res/layout/activity_web.xml 7 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/fragment_dashboard.xml 23 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/fragment_home.xml 23 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/fragment_mine.xml 191 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/fragment_notifications.xml 23 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/fragment_service.xml 126 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/item_msg.xml 66 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/refresh_recycler.xml 2 ●●● patch | view | raw | blame | history
app/src/main/res/navigation/mobile_navigation.xml 25 ●●●●● patch | view | raw | blame | history
app/src/main/res/values/styles.xml 27 ●●●●● patch | view | raw | blame | history
app/build.gradle
@@ -18,16 +18,14 @@
    buildTypes {
        debug{
            minifyEnabled false
            buildConfigField 'String','HOST_IP_ADDR','"http://192.168.100.82:8080/"'
            buildConfigField 'String','ENVIRONMENT','"release"'
            resValue "string", "app_name", "MVVM开源项目测试"
            buildConfigField 'String','HOST_IP_ADDR','"http://192.168.110.116:8080/"'
            resValue "string", "app_name", "MVVM OPEN TEST"
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled false
            buildConfigField 'String','HOST_IP_ADDR','"http://192.168.100.82:8080/"'
            buildConfigField 'String','ENVIRONMENT','"release"'
            resValue "string", "app_name", "MVVM开源项目"
            buildConfigField 'String','HOST_IP_ADDR','"http://192.168.100.82:8080/'
            resValue "string", "app_name", "MVVM OPEN"
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
@@ -77,9 +75,8 @@
    implementation 'com.guolindev.permissionx:permissionx:1.6.0'    //权限依赖
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
    implementation 'com.pangle.cn:ads-sdk-pro:4.0.2.2'//字节跳动  穿山甲广告
    implementation 'com.tencent.bugly:crashreport:latest.release' //其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如2.1.9
    implementation 'com.tencent.bugly:nativecrashreport:latest.release' //其中latest.release指代最新Bugly NDK版本号,也可以指定明确的版本号,例如3.0
    implementation 'com.facebook.rebound:rebound:0.3.6'//Rebound  “弹簧”动画效果的第三方工具包,由FaceBook
    implementation 'com.github.zhaolei9527:BottomMenu:v1.0.1'//底部菜单弹框
    implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.2.3'//图片选择
    implementation 'com.github.wildma:PictureSelector:1.1.1'//图片选择裁切工具
}
app/libs/alipaySdk-15.7.5.aar
Binary files differ
app/src/main/AndroidManifest.xml
@@ -30,6 +30,7 @@
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:networkSecurityConfig="@xml/network_security_config"
        tools:replace="android:theme"
        android:theme="@style/Theme.OpemMvvm" >
        <activity
            android:name=".ui.splash.SplashActivity"
@@ -41,7 +42,7 @@
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity"
        <activity android:name=".ui.main.MainActivity"
            android:launchMode="singleTask"
            tools:ignore="WrongManifestParent"
            android:exported="true">
app/src/main/java/com/runt/open/mvvm/MainActivity.java
File was deleted
app/src/main/java/com/runt/open/mvvm/MyApplication.java
@@ -8,9 +8,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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;
@@ -21,7 +18,6 @@
import com.scwang.smart.refresh.layout.api.RefreshLayout;
import com.scwang.smart.refresh.layout.listener.DefaultRefreshFooterCreator;
import com.scwang.smart.refresh.layout.listener.DefaultRefreshHeaderCreator;
import com.tencent.bugly.crashreport.CrashReport;
import java.util.ArrayList;
import java.util.List;
@@ -115,29 +111,7 @@
                }
            }
        });
        CrashReport.initCrashReport(getApplicationContext(), "8d88679ae9", false);//注册bugly
        TTAdConfig.Builder builder = new TTAdConfig.Builder()
                .appId("5106813")
                .useTextureView(true) //使用TextureView控件播放视频,默认为SurfaceView,当有SurfaceView冲突的场景,可以使用TextureView
                .appName(getString(R.string.app_name))
                .titleBarTheme(TTAdConstant.TITLE_BAR_THEME_DARK)
                .allowShowNotify(true) //是否允许sdk展示通知栏提示
                .allowShowPageWhenScreenLock(true) //是否在锁屏场景支持展示广告落地页
                .directDownloadNetworkType(TTAdConstant.NETWORK_STATE_WIFI) //允许直接下载的网络状态集合
                .supportMultiProcess(true) //是否支持多进程,true支持
                .asyncInit(true) ;//异步初始化sdk,开启可减少初始化耗时
        //.httpStack(new MyOkStack3())//自定义网络库,demo中给出了okhttp3版本的样例,其余请自行开发或者咨询工作人员。
        TTAdSdk.init(this, builder.build(), new TTAdSdk.InitCallback() {
            @Override
            public void success() {
                MyLog.i(TAG,"TTAdSdk success");
            }
            @Override
            public void fail(int i, String s) {
                MyLog.e(TAG,"TTAdSdk fail");
            }
        });
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init(getApplicationContext(), new CrashHandler.CrashListener() {
            @Override
app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java
@@ -3,13 +3,17 @@
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
@@ -23,12 +27,14 @@
import androidx.lifecycle.ViewModelProvider;
import androidx.viewbinding.ViewBinding;
import com.permissionx.guolindev.PermissionX;
import com.runt.open.mvvm.MyApplication;
import com.runt.open.mvvm.R;
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 com.runt.open.mvvm.widgets.TitleBarView;
import java.io.File;
import java.lang.reflect.Method;
@@ -41,13 +47,12 @@
 * activity 封装
 * Created by Administrator on 2021/10/27 0027.
 */
public abstract class BaseActivity<B extends ViewBinding,VM extends BaseViewModel> extends AppCompatActivity {
public abstract class BaseActivity<VB extends ViewBinding,VM extends BaseViewModel> extends AppCompatActivity {
    protected  B binding;
    protected  VM viewModel;
    protected  VB mBinding;
    protected  VM mViewModel;
    protected String TAG ;
    public final String[] FILE_PERMISSIONS = new String []{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE};
    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};
@@ -83,6 +88,7 @@
            RESULT_CODE_SUCESS = 4046/*成功*/,
            RESULT_CODE_CANCEL = 4043/*取消*/;
    protected Context mContext;
    TitleBarView titleBarView;
    @Override
@@ -93,15 +99,17 @@
        setStatusBarTextColor(true);
        final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
        try {
            Class<B> entityClass = (Class<B>) type.getActualTypeArguments()[0];
            Class<VB> entityClass = (Class<VB>) type.getActualTypeArguments()[0];
            Method method = entityClass.getMethod("inflate", LayoutInflater.class);//get method from name "inflate";
            binding = (B) method.invoke(entityClass,getLayoutInflater());//execute method to create a objct of viewbind;
            mBinding = (VB) method.invoke(entityClass,getLayoutInflater());//execute method to create a objct of viewbind;
            titleBarView = (TitleBarView) mBinding.getClass().getDeclaredField("titleBar").get(mBinding);
            titleBarView.setLeftClick(v -> onBackPressed());
        } catch (Exception e) {
            e.printStackTrace();
        }
        Class<VM> vmClass = (Class<VM>) type.getActualTypeArguments()[1];
        viewModel = new ViewModelProvider(this,getViewModelFactory()).get(vmClass);
        setContentView(binding.getRoot());
        mViewModel = new ViewModelProvider(this,getViewModelFactory()).get(vmClass);
        setContentView(mBinding.getRoot());
        mContext = this;
        try {
            //设置坚屏 一定要放到try catch里面,否则会崩溃
@@ -110,13 +118,34 @@
        }
        TAG = this.getClass().getSimpleName();
        initViews();
        mViewModel.onCreate(this);
        loadData();
    }
    public abstract void initViews();
    public abstract void loadData();
    public boolean isNull(Object object){
        return object == null || object.toString().trim().equals("") || object.equals("null");
    }
    protected void setTitle(String text){
        titleBarView.setTitleText(text);
    }
    protected void onTitleLeftClick(){
        onBackKeyDown();
    }
    protected void setTitleRight(String text){
        titleBarView.setRightText(text);
        titleBarView.setRightDra(null);
    }
    protected void setTitleRight(Drawable drawable){
        titleBarView.setRightText(null);
        titleBarView.setRightDra(drawable);
    }
@@ -125,11 +154,29 @@
     * 显示弹框
     * @param title
     * @param msg
     * @param resPonse
     */
    public void showDialog(String title,String msg,ResPonse resPonse){
        showDialog(title,msg,"确认","取消",resPonse);
    }
    public void showInputDialog(String title,String msg,String hint,ResPonse resPonse){
        showInputDialog(title,msg,hint,"确认","取消",resPonse);
    }
    public void showInputDialog(String title, String msg, String hint,String btnOk,String btnCancel,final  ResPonse resPonse){
        showDialog(title,msg,hint,btnOk,btnCancel,resPonse,true);
    }
    /**
     * 显示弹框
     * @param title
     * @param msg
     * @param btnOk
     * @param btnCancel
     * @param resPonse
     */
    public void showDialog(String title, String msg, String btnOk,String btnCancel,final ResPonse resPonse){
    public void showDialog(String title, String msg, String btnOk,String btnCancel,final  ResPonse resPonse){
        showDialog(title,msg,null,btnOk,btnCancel,resPonse,false);
    }
@@ -215,6 +262,9 @@
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        setStatusBarTextColor(isBlack);
        final ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) titleBarView.getLayoutParams();
        layoutParams.topMargin = layoutParams.topMargin+getStatusBarHeight();
        titleBarView.setLayoutParams(layoutParams);
    }
    /**
@@ -343,7 +393,7 @@
        }
    }
    public void showToast(String message){
        Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
        runOnUiThread(() -> Toast.makeText(mContext,message,Toast.LENGTH_SHORT).show());
    }
    public void showToast(@StringRes int msg){
@@ -371,6 +421,25 @@
        return false;
    }
    /**
     * 拨打电话(直接拨打电话)
     * @param phoneNum 电话号码
     */
    public void callPhone(String phoneNum){
        PermissionX.init(this)
                .permissions(Manifest.permission.CALL_PHONE)
                .request((allGranted, grantedList, deniedList) -> {
                    if(allGranted){
                        Intent intent = new Intent(Intent.ACTION_CALL);
                        Uri data = Uri.parse("tel:" + phoneNum);
                        intent.setData(data);
                        startActivity(intent);
                    }else{
                        showToast("权限被拒绝");
                    }
                });
    }
    public boolean getBooleanUserPrefrence(String key){
        return PreferencesUtils.getBoolean(this,key,false,PreferencesUtils.USER);
app/src/main/java/com/runt/open/mvvm/base/activities/BaseLoadPageActivity.java
File was deleted
app/src/main/java/com/runt/open/mvvm/base/activities/BaseTabActivity.java
@@ -35,8 +35,8 @@
        //设置当前可见Item左右可见page数,次范围内不会被销毁
        //禁用预加载
        try {
            viewPager2 = (ViewPager2) binding.getClass().getDeclaredField("viewPager2").get(binding);
            tabLayout = (TabLayout) binding.getClass().getDeclaredField("tabLayout").get(binding);
            viewPager2 = (ViewPager2) mBinding.getClass().getDeclaredField("viewPager2").get(mBinding);
            tabLayout = (TabLayout) mBinding.getClass().getDeclaredField("tabLayout").get(mBinding);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
app/src/main/java/com/runt/open/mvvm/base/activities/BaseTitleBarActivity.java
File was deleted
app/src/main/java/com/runt/open/mvvm/base/activities/LoadPageActivity.java
New file
@@ -0,0 +1,96 @@
package com.runt.open.mvvm.base.activities;
import androidx.annotation.NonNull;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding;
import com.runt.open.mvvm.R;
import com.runt.open.mvvm.base.adapter.BaseAdapter;
import com.runt.open.mvvm.base.model.LoadPageViewModel;
import com.scwang.smart.refresh.footer.ClassicsFooter;
import com.scwang.smart.refresh.header.ClassicsHeader;
import com.scwang.smart.refresh.layout.SmartRefreshLayout;
import com.scwang.smart.refresh.layout.api.RefreshLayout;
import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;
/**
 * Created by Administrator on 2021/11/4 0004.
 */
public abstract class LoadPageActivity<VB extends ViewBinding,VM extends LoadPageViewModel,A extends BaseAdapter,RESULT>
        extends BaseActivity<VB,VM>  implements OnRefreshLoadMoreListener {
    protected int page;
    protected SmartRefreshLayout refresh;
    //适配器
    protected A adapter;
    @Override
    public void initViews() {
        try {
            Class<A> entityClass = (Class<A>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2];
            this.adapter = entityClass.newInstance();//实例化泛型
        } catch (Exception e) {
            e.printStackTrace();
        }
        refresh.setRefreshHeader(new ClassicsHeader(mContext));
        refresh.setRefreshFooter(new ClassicsFooter(mContext));
        refresh.setOnRefreshLoadMoreListener(this);
        RecyclerView recycler = mBinding.getRoot().findViewById(R.id.recycler);
        recycler.setLayoutManager(new LinearLayoutManager(mContext));
        recycler.setAdapter(adapter);
        refresh = mBinding.getRoot().findViewById(R.id.refresh);
        refresh.setOnRefreshLoadMoreListener(this);
        mViewModel.getLiveData().observe(this, (Observer<List<RESULT>>) list -> {
            adapter.showNull = true;
            if(page == 0){
                adapter.setData(list);
            }else{
                adapter.addData(list);
            }
            refresh.finishRefresh();
            //加载完毕
            if(list.size() < 10 || page > 0 && list.size() == 0){
                refresh.finishLoadMoreWithNoMoreData();
            }else{
                refresh.finishLoadMore();
            }
        });
        mViewModel.getLiveFailed().observe(this, o -> {
            refresh.finishRefresh();
            refresh.finishLoadMore();
            //校正page数值
            int size = adapter.getData().size();
            if(size/mViewModel.SIZE+1 < page){
                page--;
            }
        });
    }
    @Override
    public void loadData() {
        refresh.autoRefresh();
    }
    protected abstract Map requestParams();
    @Override
    public void onRefresh(@NonNull RefreshLayout refreshLayout) {
        page = 0;
        mViewModel.requestData(page,requestParams());
    }
    @Override
    public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
        page++;
        mViewModel.requestData(page,requestParams());
    }
    public A getAdapter() {
        return adapter;
    }
}
app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java
@@ -1,6 +1,6 @@
package com.runt.open.mvvm.base.adapter;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -8,7 +8,6 @@
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.databinding.LayoutNullBinding;
import com.runt.open.mvvm.util.DeviceUtil;
@@ -23,47 +22,73 @@
 *  T  数据类型
 *  V 适配器视图
 */
public abstract class BaseAdapter<B extends ViewBinding,T> extends RecyclerView.Adapter {
public abstract class BaseAdapter<DATA, VB extends ViewBinding> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    protected List<T> mData = new ArrayList<>();
    protected List<DATA> dataList = new ArrayList<>();
    protected OnItemClickListener<DATA> onItemClickListener;
    public boolean showNull;
    public float defaultMarginBottom,lastMarginBottom;
    protected Drawable nullDrawable;
    protected String nullTxt="暂无数据";
    protected String TAG = "BaseAdapter";
    protected BaseActivity activity;
    public BaseAdapter(){
    public interface  OnItemClickListener<DATA>{
        void onItemClick(int position,DATA data);
    }
    public BaseAdapter(@NonNull List<T> data){
        mData = data;
    public class ViewBindHolder extends RecyclerView.ViewHolder  {
        ViewBinding binding;
        public ViewBindHolder(ViewBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }
    public List<T> getData() {
        return mData;
    public void setOnItemClickListener(OnItemClickListener<DATA> onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }
    public void setData(@NonNull List<T> data){
        mData = data;
    public void setData(List<DATA> list){
        if(dataList != list) {
            dataList.clear();
            if (list != null) {
                dataList.addAll(list);
            }
        }
        notifyDataSetChanged();
    }
    public void addData(DATA data){
        if(data != null){
            dataList.add(data);
        }
        notifyDataSetChanged();
    }
    public void addData(List<DATA> list){
        if (list != null && list.size() > 0) {
            this.dataList.addAll(list);
        }
        notifyDataSetChanged();
    }
    public List<DATA> getData() {
        return dataList;
    }
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    public ViewBindHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if(viewType == 1 ){
            // get genericity "B"
            Class<B> entityClass = (Class<B>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
            try {
               /* for(Method method: entityClass.getMethods()){
                Class<VB> entityClass = (Class<VB>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
                /*for(Method method: entityClass.getMethods()){
                    StringBuilder sb = new StringBuilder();
                    for(Class type : method.getParameterTypes()){
                        sb.append(type.getSimpleName()+",");
                    }
                    Log.e(TAG,String.format("method:%s,return:%s,param:%s",method.getName(),method.getReturnType().getSimpleName(),sb.toString()));
                    Log.e("BaseAdapter",String.format("method:%s,return:%s,param:%s",method.getName(),method.getReturnType().getSimpleName(),sb.toString()));
                }*/
                Method method = entityClass.getMethod("inflate", LayoutInflater.class,ViewGroup.class,boolean.class);//get method from name "inflate";
                B vBind = (B) method.invoke(entityClass,LayoutInflater.from(parent.getContext()),parent,false);//execute method to create a objct of viewbind;
                VB vBind = (VB) method.invoke(entityClass,LayoutInflater.from(parent.getContext()),parent,false);//execute method to create a objct of viewbind;
                return new ViewBindHolder(vBind);
            } catch (SecurityException e) {
                e.printStackTrace();
@@ -80,19 +105,24 @@
                e.printStackTrace();
            }
        }
        return new NullViewHolder( LayoutNullBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false));
        return new ViewBindHolder( LayoutNullBinding.inflate( LayoutInflater.from(parent.getContext()), parent, false ) );
    }
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        //MyLog.i(TAG,"onBindViewHolder position:"+position+" "+mData.size()+" "+getItemViewType(position));
        if(activity == null){
            activity = (BaseActivity) holder.itemView.getContext();
        }
        if(getItemViewType(position)==0){
            bindView((NullViewHolder) holder);
        }else {
            bindView((ViewBindHolder) holder, mData.size() == 0 ? null : mData.get(position), position);
        ViewBindHolder bindHolder = (ViewBindHolder) holder;
        if(getItemViewType(position) == 0 || bindHolder.binding instanceof LayoutNullBinding){
            onBindEmptyView((LayoutNullBinding) bindHolder.binding);
        }else{
            if (onItemClickListener != null) {
                bindHolder.binding.getRoot().setOnClickListener(view -> {
                    if(onItemClickListener != null){
                        onItemClickListener.onItemClick(position,getItem(position));
                    }
                });
            }
            onBindView((VB) bindHolder.binding, position, getItem(position));
            setBottomMargin(bindHolder,position);
        }
    }
@@ -102,7 +132,7 @@
     * @param position
     */
    protected void setBottomMargin(ViewBindHolder holder, int position){
        setBottomMargin(holder,position,23);
        setBottomMargin(holder,position,lastMarginBottom);
    }
    /**
@@ -111,56 +141,54 @@
     * @param position  位置
     * @param dp        间距
     */
    protected void setBottomMargin(RecyclerView.ViewHolder holder, int position,int dp){
        setBottomMargin(holder,position,dp,0);
    protected void setBottomMargin(RecyclerView.ViewHolder holder, int position,float dp){
        setBottomMargin(holder,position,dp,defaultMarginBottom);
    }
    protected void setBottomMargin(RecyclerView.ViewHolder holder, int position, int dp, int defaultDp){
    protected void setBottomMargin(RecyclerView.ViewHolder holder, int position, float dp, float defaultDp){
        ViewGroup.MarginLayoutParams params1 = (ViewGroup.MarginLayoutParams) holder.itemView.getLayoutParams();
        if(position == mData.size() -1){
        if(position == dataList.size() -1){
            params1.setMargins(params1.leftMargin, params1.topMargin, params1.rightMargin, DeviceUtil.convertDpToPixel(dp,holder.itemView.getContext()));
        }else{
            params1.setMargins(params1.leftMargin, params1.topMargin, params1.rightMargin, DeviceUtil.convertDpToPixel(defaultDp,holder.itemView.getContext()));
        }
    }
    protected abstract void bindView(ViewBindHolder holder,T data,int position);
    protected void bindView(NullViewHolder holder){
    }
    /**
     * 空数据支持
     * @return
     */
    @Override
    public int getItemCount() {
        //默认显示空视图,若不显示空视图则重写该方法,返回mData.size()
        return mData == null || mData.size() == 0 ?1:mData.size();
        return (showNull && dataList.size() == 0 )? 1 : dataList.size();
    }
    /**
     * 当下标为0,数据集合为0 返回0(意味当前应显示空数据视图))
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        //当下标为0,数据集合为0 返回0(意味当前应显示空数据视图))
        //MyLog.i(TAG,"getItemViewType position:"+position+" mdata:"+mData.size()+" "+(position ==0 && mData.size()==0));
        return position == 0 && mData.size()==0?0:1;
        return  (showNull && position == 0 && dataList.size() == 0)? 0 : 1;
    }
    public class ViewBindHolder extends RecyclerView.ViewHolder{
        public B binding;
        public ViewBindHolder( B binding) {
            super(binding.getRoot());
            this.binding = binding;
    public DATA getItem(int position){
        if(position >= dataList.size()){
            return null;
        }else {
            return dataList.get(position);
        }
    }
    protected abstract void onBindView(VB binding, int position, DATA data);
    /**
     * 空数据显示
     * Created by Administrator on 2021/10/28 0028.
     */
    public class NullViewHolder extends RecyclerView.ViewHolder {
        LayoutNullBinding binding;
    protected void onBindEmptyView(LayoutNullBinding emptyBinding){
        Log.e("baseAdapter"," emptyBinding:"+emptyBinding);
        public NullViewHolder(LayoutNullBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }
}
app/src/main/java/com/runt/open/mvvm/base/fragments/BaseFragment.java
@@ -7,11 +7,11 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.viewbinding.ViewBinding;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.base.model.BaseViewModel;
import com.runt.open.mvvm.base.model.ViewModelFactory;
import java.lang.reflect.Method;
@@ -21,11 +21,22 @@
 * fragment 封装
 * Created by Administrator on 2021/10/28 0028.
 */
public abstract class BaseFragment<B extends ViewBinding,VM extends ViewModel> extends Fragment {
public abstract class BaseFragment<VB extends ViewBinding,VM extends BaseViewModel> extends Fragment {
    protected BaseActivity activity;
    protected  B binding;
    protected  VM viewModel;
    protected BaseActivity mActivity;
    protected  VB mBinding;
    protected  VM mViewModel;
    public static final int REQUEST_CODE_LOGOUT = 20009,/*退出*/
            REQUEST_CODE_ORDER = 10010,/*订单详情*/
            REQUEST_CODE_LOGIN = 20010,/*登录*/
            REQUEST_CODE_SCAN = 20011/*扫描请求*/,
            REQUEST_CODE_PIC = 20012,/*选择图片*/
            REQUEST_CODE_PERMISSION = 20013,/*请求权限*/
            REQUEST_CODE_SIGN = 20014,/*签到*/
            REQUEST_CODE_WITHDRAW = 22014,/*提现*/
            RESULT_CODE_FAILD = 4044/*失败*/,
            RESULT_CODE_SUCESS = 4046/*成功*/,
            RESULT_CODE_CANCEL = 4043/*取消*/;
    @Nullable
    @Override
@@ -33,17 +44,23 @@
        // get genericity "B"
        try {
            final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
            Class<B> entityClass = (Class<B>) type.getActualTypeArguments()[0];
            Class<VB> entityClass = (Class<VB>) type.getActualTypeArguments()[0];
            Method method = entityClass.getMethod("inflate", LayoutInflater.class,ViewGroup.class,boolean.class);//get method from name "inflate";
            binding = (B) method.invoke(entityClass,inflater,container,false);//execute method to create a objct of viewbind;
            mBinding = (VB) method.invoke(entityClass,inflater,container,false);//execute method to create a objct of viewbind;
            Class<VM> vmClass = (Class<VM>) type.getActualTypeArguments()[1];
            viewModel = new ViewModelProvider(this,getViewModelFactory()).get(vmClass);
            mViewModel = new ViewModelProvider(this,getViewModelFactory()).get(vmClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return binding.getRoot();
        initViews();
        return mBinding.getRoot();
    }
    public void setOnClickListener(View.OnClickListener click,int... ids){
        for (int id: ids){
            getActivity().findViewById(id).setOnClickListener(click);
        }
    }
    public ViewModelProvider.Factory getViewModelFactory(){
        return ViewModelFactory.getInstance();
    }
@@ -51,16 +68,19 @@
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        activity = (BaseActivity) getActivity();
        initViews();
        mActivity = (BaseActivity) getActivity();
        mViewModel.onCreate(mActivity);
        loadData();
    }
    public abstract void initViews();
    public abstract void loadData();
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
        mBinding = null;
    }
}
app/src/main/java/com/runt/open/mvvm/base/fragments/BaseLoadPageFragment.java
File was deleted
app/src/main/java/com/runt/open/mvvm/base/fragments/BaseTabFragment.java
@@ -1,12 +1,12 @@
package com.runt.open.mvvm.base.fragments;
import androidx.lifecycle.ViewModel;
import androidx.viewbinding.ViewBinding;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import com.runt.open.mvvm.base.adapter.FragmentAdapter;
import com.runt.open.mvvm.base.model.BaseViewModel;
import java.util.ArrayList;
import java.util.Arrays;
@@ -16,7 +16,7 @@
 * 带有tablayout fragment封装
 * Created by Administrator on 2021/11/3 0003.
 */
public abstract class BaseTabFragment<B extends ViewBinding,VM extends ViewModel> extends BaseFragment<B,VM> {
public abstract class BaseTabFragment<B extends ViewBinding,VM extends BaseViewModel> extends BaseFragment<B,VM> {
    TabLayout tabLayout;
    FragmentAdapter fragmentAdapter;
@@ -25,28 +25,30 @@
    @Override
    public void initViews() {
        fragmentAdapter = new FragmentAdapter(activity);
        fragmentAdapter.setFragments(initFragments());
        setTabTitles(initTabTitles());
        //设置当前可见Item左右可见page数,次范围内不会被销毁
        //禁用预加载
        try {
            viewPager2 = (ViewPager2) binding.getClass().getDeclaredField("viewPager2").get(binding);
            tabLayout = (TabLayout) binding.getClass().getDeclaredField("tabLayout").get(binding);
            viewPager2 = (ViewPager2) mBinding.getClass().getDeclaredField("viewPager2").get(mBinding);
            tabLayout = (TabLayout) mBinding.getClass().getDeclaredField("tabLayout").get(mBinding);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        ;
        viewPager2.setOffscreenPageLimit(ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT);
        viewPager2.setAdapter(fragmentAdapter);
        viewPager2.setCurrentItem(0);
        viewPager2.setUserInputEnabled(false); //true:滑动,false:禁止滑动
        new TabLayoutMediator(tabLayout, viewPager2, (tab, position) -> tab.setText(tabTitles.get(position))).attach();
    }
    @Override
    public void loadData() {
        fragmentAdapter = new FragmentAdapter(mActivity);
        fragmentAdapter.setFragments(initFragments());
        viewPager2.setAdapter(fragmentAdapter);
    }
    protected abstract List<String> initTabTitles();
    protected abstract List<BaseFragment> initFragments();
app/src/main/java/com/runt/open/mvvm/base/fragments/LoadPageFragment.java
New file
@@ -0,0 +1,96 @@
package com.runt.open.mvvm.base.fragments;
import androidx.annotation.NonNull;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding;
import com.runt.open.mvvm.R;
import com.runt.open.mvvm.base.adapter.BaseAdapter;
import com.runt.open.mvvm.base.model.LoadPageViewModel;
import com.scwang.smart.refresh.footer.ClassicsFooter;
import com.scwang.smart.refresh.header.ClassicsHeader;
import com.scwang.smart.refresh.layout.SmartRefreshLayout;
import com.scwang.smart.refresh.layout.api.RefreshLayout;
import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;
/**
 * 分页fragment 封装
 * Created by Administrator on 2021/11/3 0003.
 */
public abstract class LoadPageFragment<VB extends ViewBinding,VM extends LoadPageViewModel,A extends BaseAdapter,RESULT> extends BaseFragment<VB,VM>  implements OnRefreshLoadMoreListener {
    protected int page;
    protected SmartRefreshLayout refresh;
    //适配器
    protected A adapter;
    @Override
    public void initViews() {
        try {
            Class<A> entityClass = (Class<A>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2];
            this.adapter = entityClass.newInstance();//实例化泛型
        } catch (Exception e) {
            e.printStackTrace();
        }
        RecyclerView recycler = mBinding.getRoot().findViewById(R.id.recycler);
        recycler.setLayoutManager(new LinearLayoutManager(getContext()));
        recycler.setAdapter(adapter);
        refresh = mBinding.getRoot().findViewById(R.id.refresh);
        refresh.setRefreshHeader(new ClassicsHeader(getContext()));
        refresh.setRefreshFooter(new ClassicsFooter(getContext()));
        refresh.setOnRefreshLoadMoreListener(this);
        mViewModel.getLiveData().observe(this, (Observer<List<RESULT>>) list -> {
            adapter.showNull = true;
            if(page == 0){
                adapter.setData(list);
            }else{
                adapter.addData(list);
            }
            refresh.finishRefresh();
            //加载完毕
            if(list.size() < 10 || page > 0 && list.size() == 0){
                refresh.finishLoadMoreWithNoMoreData();
            }else{
                refresh.finishLoadMore();
            }
        });
        mViewModel.getLiveFailed().observe(this, o -> {
            refresh.finishRefresh();
            refresh.finishLoadMore();
            //校正page数值
            int size = adapter.getData().size();
            if(size/mViewModel.SIZE+1 < page){
                page--;
            }
        });
    }
    @Override
    public void loadData() {
        refresh.autoRefresh();
    }
    protected abstract Map requestParams();
    @Override
    public void onRefresh(@NonNull RefreshLayout refreshLayout) {
        page = 0;
        mViewModel.requestData(page,requestParams());
    }
    @Override
    public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
        page++;
        mViewModel.requestData(page,requestParams());
    }
    public A getAdapter() {
        return adapter;
    }
}
app/src/main/java/com/runt/open/mvvm/base/model/BaseLoadPageViewModel.java
File was deleted
app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java
@@ -1,13 +1,15 @@
package com.runt.open.mvvm.base.model;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.runt.open.mvvm.data.LoadingCmd;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.retrofit.AndroidScheduler;
import com.runt.open.mvvm.retrofit.api.CommonApiCenter;
import com.runt.open.mvvm.retrofit.observable.HttpObserver;
import com.runt.open.mvvm.retrofit.utils.RetrofitUtils;
import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
/**
@@ -15,10 +17,11 @@
 */
public class BaseViewModel extends ViewModel {
    MutableLiveData<LoadingCmd> loadLive = new MutableLiveData<>();
    protected BaseActivity mActivity;
    protected CommonApiCenter commonApi = RetrofitUtils.getInstance().getCommonApi();
    public MutableLiveData<LoadingCmd> getLoadLive() {
        return loadLive;
    public void onCreate(BaseActivity activity) {
        this.mActivity = activity;
    }
    /**
@@ -43,11 +46,17 @@
    public <T> void httpObserverOnLoading(Observable<T> observable, HttpObserver observer){
        observable.subscribeOn(Schedulers.io())//指定网络请求在io后台线程中进行
                .doOnSubscribe(disposable -> {
                             loadLive.setValue(new LoadingCmd(LoadingCmd.CMD.LOADING,"请求数据中..."));
                    mActivity.showLoadingDialog("");
                })
                .observeOn(AndroidScheduler.mainThread())
                .doOnError(new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                    }
                })
                .doOnComplete(() -> {
                    loadLive.postValue(new LoadingCmd(LoadingCmd.CMD.DISSMISS));
                    mActivity.dissmissLoadingDialog();
                })
                .subscribe(observer);
    }
app/src/main/java/com/runt/open/mvvm/base/model/ImpViewModel.java
New file
@@ -0,0 +1,7 @@
package com.runt.open.mvvm.base.model;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2022/7/27.
 */
public class ImpViewModel extends BaseViewModel{
}
app/src/main/java/com/runt/open/mvvm/base/model/LoadPageViewModel.java
New file
@@ -0,0 +1,47 @@
package com.runt.open.mvvm.base.model;
import androidx.lifecycle.MutableLiveData;
import com.runt.open.mvvm.data.HttpApiResult;
import com.runt.open.mvvm.data.PageResult;
import com.runt.open.mvvm.retrofit.observable.HttpObserver;
import java.util.List;
import java.util.Map;
/**
 * 分页
 * Created by Administrator on 2021/11/3 0003.
 */
public abstract class LoadPageViewModel<RESULT extends PageResult> extends BaseViewModel {
    public final int SIZE = 10;
    private MutableLiveData<List> liveData = new MutableLiveData<>();
    private MutableLiveData liveFailed = new MutableLiveData();
    protected abstract String requestUrl();
    public void requestData(int page,Map param){
        httpObserverOn( commonApi.getPageData(requestUrl(), page, SIZE, param), new HttpObserver<RESULT>() {
            @Override
            protected void onSuccess(RESULT data) {
                liveData.postValue(data.rows);
            }
            @Override
            protected void onFailed(HttpApiResult httpResult) {
                mActivity.showToast(httpResult.msg);
                liveFailed.postValue(1);
            }
        });
    }
    public MutableLiveData<List> getLiveData(){
        return liveData;
    }
    public MutableLiveData getLiveFailed() {
        return liveFailed;
    }
}
app/src/main/java/com/runt/open/mvvm/data/ApkUpGradeResult.java
@@ -3,7 +3,7 @@
/**
 * Created by Administrator on 2021/11/15 0015.
 */
public class ApkUpGradeResult extends BaseApiResult<ApkUpGradeResult.AppInfo>{
public class ApkUpGradeResult extends HttpApiResult<ApkUpGradeResult.AppInfo> {
    public class AppInfo {
        //以下为声明的参数
app/src/main/java/com/runt/open/mvvm/data/HttpApiResult.java
File was renamed from app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java
@@ -5,7 +5,7 @@
/**
 * Created by Administrator on 2021/10/28 0028.
 */
public class BaseApiResult<D extends Object> implements Serializable {
public class HttpApiResult<D extends Object> implements Serializable {
    public String msg;
    public int code = 200;
app/src/main/java/com/runt/open/mvvm/data/LoadingCmd.java
File was deleted
app/src/main/java/com/runt/open/mvvm/data/PageResult.java
File was renamed from app/src/main/java/com/runt/open/mvvm/data/BasePageResult.java
@@ -5,7 +5,7 @@
/**
 * Created by Administrator on 2021/10/28 0028.
 */
public class BasePageResult<T> extends BaseApiResult<String>{
public class PageResult<T> extends HttpApiResult<String> {
    public int pages;
    public int total;
    public int pageNum;
app/src/main/java/com/runt/open/mvvm/data/Results.java
@@ -1,6 +1,7 @@
package com.runt.open.mvvm.data;
import com.runt.open.mvvm.ui.login.UserBean;
import com.runt.open.mvvm.ui.main.home.Message;
/**
 * My father is Object, ites purpose of
@@ -9,8 +10,9 @@
 */
public class Results {
    public static class LoggedInUser extends BaseApiResult<UserBean> { }
    public static class LoggedInUser extends HttpApiResult<UserBean> { }
    public static class StringApiResult extends BaseApiResult<String>{ }
    public static class StringApiResult extends HttpApiResult<String> { }
    public class MessageResult extends PageResult<Message>{}
}
app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java
@@ -2,11 +2,14 @@
import android.util.Log;
import com.runt.open.mvvm.MyApplication;
import com.runt.open.mvvm.retrofit.net.NetWorkCost;
import com.runt.open.mvvm.retrofit.net.NetWorkListenear;
import com.runt.open.mvvm.retrofit.utils.HttpPrintUtils;
import com.runt.open.mvvm.util.GsonUtils;
import com.runt.open.mvvm.util.DeviceUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.EOFException;
@@ -14,6 +17,7 @@
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import okhttp3.FormBody;
@@ -38,47 +42,69 @@
    final String TAG = "HttpLogging";
    public HttpLoggingInterceptor() {
    }
    private boolean printLog ;
    public HttpLoggingInterceptor(){
        this(true);
    }
    public HttpLoggingInterceptor(boolean printLog) {
        this.printLog = printLog;
    }
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        int hashCode = request.hashCode();
        ArrayList<String> logArrays = getRequestLog(request);
        int position = logArrays.size() +2;
        Response response;
        Request requestTemp = chain.request();
        int hashCode = requestTemp.hashCode();
        if(printLog) {
            Log.d(TAG, "hashcode:" + hashCode);
        }
        Request.Builder requestBuild = requestTemp.newBuilder()
                .addHeader("appVersion", DeviceUtil.getAppVersionName(MyApplication.getApplication()))
                .addHeader("os", DeviceUtil.isHarmonyOS()? "harmony" : "android");
        Request request = requestBuild.build().newBuilder().build();
        ArrayList<String> logArrays = new ArrayList<>();
        Response response = null;
        try {
            request = encryptRequest(request);//加密
            logArrays.addAll(getRequestLog(request));
            int position = logArrays.size() +2;
            //request = encryptRequest(request);//加密
            response = chain.proceed(request);
            logArrays.addAll(getResponseLog(response));
            Log.d(TAG,"hashcode:"+hashCode);
            NetWorkCost netWorkCost = NetWorkListenear.workCostMap.get(hashCode);
            if(netWorkCost != null) {
                String cost = String.format("dns:%s,secure:%s,connect:%s,requestH:%s,requestB:%s,responseH:%s,responseB:%s", convertTimes(netWorkCost.dns), convertTimes(netWorkCost.secure), convertTimes(netWorkCost.connect), convertTimes(netWorkCost.requestHeader), convertTimes(netWorkCost.requestBody), convertTimes(netWorkCost.resposeHeader), convertTimes(netWorkCost.resposeBody));
                logArrays.add(position, "<-- costtimes : " + convertTimes(netWorkCost.total) + " (" + cost + ')');
                logArrays.add(position, "<-- costtimes : " + netWorkCost);
            }
            NetWorkListenear.workCostMap.remove(hashCode);
            new Thread(){
                @Override
                public void run() {
                    HttpPrintUtils.getInstance().printLog(logArrays, true);//线程安全方法,需在新线程执行,避免阻塞当前线程,导致程序无响应
                    if(printLog) {
                        HttpPrintUtils.getInstance().printLog(logArrays, true);//线程安全方法,需在新线程执行,避免阻塞当前线程,导致程序无响应
                    }
                }
            }.start();
        } catch (JSONException e) {
            if(response == null){
                response = chain.proceed(request);
            }
            e.printStackTrace();
        } catch (Exception e) {
            logArrays.add("<-- response url:" + URLDecoder.decode(request.url().toString(), "UTF-8"));
            NetWorkCost netWorkCost = NetWorkListenear.workCostMap.get(hashCode);
            String cost = String.format("dns:%s,secure:%s,connect:%s,requestH:%s,requestB:%s,responseH:%s,responseB:%s",  convertTimes(netWorkCost.dns) ,convertTimes(netWorkCost.secure) , convertTimes(netWorkCost.connect),convertTimes(netWorkCost.requestHeader),convertTimes(netWorkCost.requestBody) ,convertTimes(netWorkCost.resposeHeader),convertTimes(netWorkCost.resposeBody)    );
            logArrays.add("<-- costtimes : "+convertTimes(netWorkCost.total)+" (" +cost + ')');
            if (netWorkCost != null) {
                netWorkCost.total = new Date().getTime() - netWorkCost.total;
                logArrays.add("<-- costtimes : " + netWorkCost);
            }
            NetWorkListenear.workCostMap.remove(hashCode);
            logArrays.add("<-- response failed " + e.getLocalizedMessage());
            logArrays.add("<--                 " + e.toString());
            new Thread(){
                @Override
                public void run() {
                    HttpPrintUtils.getInstance().printLog(logArrays, false);//线程安全方法,需在新线程执行,避免阻塞当前线程,导致程序无响应
                    if(printLog) {
                        HttpPrintUtils.getInstance().printLog(logArrays, false);//线程安全方法,需在新线程执行,避免阻塞当前线程,导致程序无响应
                    }
                }
            }.start();
            throw e;//抛出异常,用于请求接收信息
@@ -86,7 +112,7 @@
        return response;
    }
    private ArrayList<String> getRequestLog(Request request) throws IOException {
    private ArrayList<String> getRequestLog(Request request) throws IOException, JSONException {
        RequestBody requestBody = request.body();
        ArrayList<String> logArrays = new ArrayList<>();
        String requestStartMessage = "--> " + request.method() + ' ' + URLDecoder.decode(request.url().toString() ,"UTF-8")+ ' ' ;
@@ -120,22 +146,28 @@
                    String str=buffer1.readString(charset).replaceAll("%(?![0-9a-fA-F]{2})","%25");
                    param.put(part.headers().get(part.headers().name(0)),URLDecoder.decode(str, "UTF-8"));
                }
                logArrays.add(GsonUtils.retractJson(new JSONObject(param).toString()));
                logArrays.add(new JSONObject(param).toString(4));
            }else if(requestBody instanceof FormBody){
                logArrays.add("---------->REQUEST BODY[FormBody]<----------");
                FormBody body = (FormBody) requestBody;
                for(int i = 0 ; i < body.size() ; i ++ ){
                    param.put(body.name(i),body.value(i));
                }
                logArrays.add(GsonUtils.retractJson(new JSONObject(param).toString()));
                logArrays.add(new JSONObject(param).toString(4));
            }else{
                Buffer buffer = new Buffer();
                requestBody.writeTo(buffer);
                logArrays.add("---------->REQUEST BODY<----------");
                String str = buffer.readString(charset);
                try{
                    logArrays.add(GsonUtils.retractJson(URLDecoder.decode(str, "UTF-8")));
                }catch (Exception e){
                    str = URLDecoder.decode(str, "UTF-8");
                }catch (Exception e){}
                if(str.indexOf("[") == 0){
                    logArrays.add(new JSONArray(str).toString(4));
                }else if(str.indexOf("{") == 0){
                    logArrays.add(new JSONObject(str).toString(4));
                }else{
                    logArrays.add(str);
                }
            }
@@ -147,7 +179,7 @@
    }
    private ArrayList<String> getResponseLog(Response response) throws IOException {
    private ArrayList<String> getResponseLog(Response response) throws IOException, JSONException {
        ArrayList<String> logArrays = new ArrayList<>();
        ResponseBody responseBody = response.body();
        long contentLength = responseBody.contentLength();
@@ -173,7 +205,7 @@
            if (isPlaintext(buffer)) {
                logArrays.add("---------->RESPONSE BODY<----------");
                if (contentLength != 0) {
                    logArrays.add(retractJson(buffer.clone().readString(charset)));
                    logArrays.add(new JSONObject(buffer.clone().readString((charset))).toString(4));
                }
                logArrays.add("<-- END HTTP (" + buffer.size() + "-byte body)");
@@ -183,57 +215,6 @@
        return logArrays;
    }
    /**
     * 字符串缩进
     * @param json
     * @return
     */
    private String retractJson(String json){
        int level = 0 ;
        StringBuffer jsonForMatStr = new StringBuffer();
        for(int index=0;index<json.length();index++)//将字符串中的字符逐个按行输出
        {
            //获取s中的每个字符
            char c = json.charAt(index);
            //          System.out.println(s.charAt(index));
            //level大于0并且jsonForMatStr中的最后一个字符为\n,jsonForMatStr加入\t
            if (level > 0 && '\n' == jsonForMatStr.charAt(jsonForMatStr.length() - 1)) {
                jsonForMatStr.append(getLevelStr(level));
                //                System.out.println("123"+jsonForMatStr);
            }
            //遇到"{"和"["要增加空格和换行,遇到"}"和"]"要减少空格,以对应,遇到","要换行
            switch (c) {
                case '{':
                case '[':
                    jsonForMatStr.append(c + "\n");
                    level++;
                    break;
                case ',':
                    jsonForMatStr.append(c + "\n");
                    break;
                case '}':
                case ']':
                    jsonForMatStr.append("\n");
                    level--;
                    jsonForMatStr.append(getLevelStr(level));
                    jsonForMatStr.append(c);
                    break;
                default:
                    jsonForMatStr.append(c);
                    break;
            }
        }
        return jsonForMatStr.toString();
    }
    private  String getLevelStr(int level) {
        StringBuffer levelStr = new StringBuffer();
        for (int levelI = 0; levelI < level; levelI++) {
            levelStr.append("\t");//\t或空格
        }
        return levelStr.toString();
    }
    /**
     * Returns true if the body in question probably contains human readable text. Uses a small sample
@@ -263,18 +244,4 @@
        String contentEncoding = headers.get("Content-Encoding");
        return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
    }
    private String convertTimes(long ms){
        String m = null,s=null;
        final int utilS = 1000;
        final int utilM = utilS*60;
        if(ms/utilM>0){
            m = ms/utilM+"m";
        }
        if(ms%utilM/utilS>0){
            s = ms%utilM/utilS+"s";
        }
        return (m!=null?m:"")+(s!=null?s:"")+ms%utilS+"ms";
    }
}
app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java
@@ -1,17 +1,21 @@
package com.runt.open.mvvm.retrofit.api;
import com.runt.open.mvvm.data.ApkUpGradeResult;
import java.util.Map;
import io.reactivex.Observable;
import okhttp3.MultipartBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
import retrofit2.http.Url;
@@ -62,5 +66,10 @@
    @GET("system/appupgrade/tourist/get/2")
    Observable<ApkUpGradeResult> getAppUpdate();
    @POST("updateName")
    Observable<ApkUpGradeResult> updateName(@Field("username") String name);
    @Multipart
    @POST("updatehead")
    Call<ResponseBody> updateHead(@Part MultipartBody.Part file);
}
app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonResponseBodyConverter.java
@@ -2,7 +2,7 @@
import android.util.Log;
import com.runt.open.mvvm.data.BaseApiResult;
import com.runt.open.mvvm.data.HttpApiResult;
import com.runt.open.mvvm.util.GsonUtils;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
@@ -51,13 +51,13 @@
            response = decryptJsonStr(val);//解密
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            e.printStackTrace();
            BaseApiResult apiResult = new BaseApiResult<>();
            HttpApiResult apiResult = new HttpApiResult<>();
            apiResult.code = 412;
            apiResult.msg = "解密数据出错"+e.getMessage();
            response = new Gson().toJson(apiResult);
        } catch (JSONException e) {
            e.printStackTrace();
            BaseApiResult apiResult = new BaseApiResult<>();
            HttpApiResult apiResult = new HttpApiResult<>();
            apiResult.code = 414;
            apiResult.msg = "非标准json";
            response = new Gson().toJson(apiResult);
app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkCost.java
@@ -11,4 +11,31 @@
    //网络消耗时间
    public long dns,connect,total,secure,requestHeader,requestBody,resposeHeader,resposeBody;
    @Override
    public String toString() {
        return "NetWorkCost{" +
                "total=" + convertTimes(total) +
                ", dns=" + convertTimes(dns) +
                ", connect=" + convertTimes(connect) +
                ", secure=" + convertTimes(secure) +
                ", requestHeader=" + convertTimes(requestHeader) +
                ", requestBody=" + convertTimes(requestBody) +
                ", resposeHeader=" + convertTimes(resposeHeader) +
                ", resposeBody=" + convertTimes(resposeBody) +
                '}';
    }
    private String convertTimes(long ms){
        String m = null,s=null;
        final int utilS = 1000;
        final int utilM = utilS*60;
        if(ms/utilM>0){
            m = ms/utilM+"m";
        }
        if(ms%utilM/utilS>0){
            s = ms%utilM/utilS+"s";
        }
        return (m!=null?m:"")+(s!=null?s:"")+ms%utilS+"ms";
    }
}
app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkListenear.java
@@ -27,13 +27,14 @@
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-9.
 */
public class NetWorkListenear extends EventListener {
    private static final String TAG = "NetworkEventListener";
    final Charset UTF8 = Charset.forName("UTF-8");
    public static Map<Integer, NetWorkCost> workCostMap = new HashMap<>();
    public static Factory get(){
    public static Factory get() {
        Factory factory = new Factory() {
            @NotNull
            @Override
@@ -47,11 +48,11 @@
    @Override
    public void callStart(@NotNull Call call) {
        super.callStart(call);
        //mRequestId = mNextRequestId.getAndIncrement() + "";
        //getAndAdd,在多线程下使用cas保证原子性
        //Log.d(TAG, "callStart hashcode:"+call.request().hashCode());
        NetWorkCost netWorkCost = new NetWorkCost();
        netWorkCost.total = new Date().getTime();
        workCostMap.put(call.request().hashCode(),netWorkCost);
        workCostMap.put(call.request().hashCode(), netWorkCost);
    }
    @Override
@@ -65,7 +66,7 @@
    public void dnsEnd(@NotNull Call call, @NotNull String domainName, @NotNull List<InetAddress> inetAddressList) {
        super.dnsEnd(call, domainName, inetAddressList);
        //Log.d(TAG, "dnsEnd");
        workCostMap.get(call.request().hashCode()).dns = new Date().getTime()  - workCostMap.get(call.request().hashCode()).dns;
        workCostMap.get(call.request().hashCode()).dns = new Date().getTime() - workCostMap.get(call.request().hashCode()).dns;
    }
    @Override
@@ -100,9 +101,10 @@
    @Override
    public void connectFailed(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress, @NotNull Proxy proxy, @Nullable Protocol protocol, @NotNull IOException ioe) {
        super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe);
        workCostMap.get(call.request().hashCode()).connect = new Date().getTime() - workCostMap.get(call.request().hashCode()).connect;
        workCostMap.get(call.request().hashCode()).total = new Date().getTime() - workCostMap.get(call.request().hashCode()).total;
        //Log.d(TAG, "connectFailed");
        NetWorkCost workCost = workCostMap.get(call.request().hashCode());
        workCost.connect = new Date().getTime() - workCost.connect;
        workCost.total = new Date().getTime() - workCost.total;
        //Log.d(TAG, "connectFailed hashcode:"+call.request().hashCode() +" "+workCost);
    }
    @Override
@@ -158,16 +160,20 @@
    public void responseBodyEnd(@NotNull Call call, long byteCount) {
        super.responseBodyEnd(call, byteCount);
        //Log.d(TAG, "responseBodyEnd");
        workCostMap.get(call.request().hashCode()).resposeBody = new Date().getTime() - workCostMap.get(call.request().hashCode()).resposeBody;
        workCostMap.get(call.request().hashCode()).total = new Date().getTime() - workCostMap.get(call.request().hashCode()).total;
        NetWorkCost workCost = workCostMap.get(call.request().hashCode());
        workCost.resposeBody = new Date().getTime() - workCost.resposeBody;
        workCost.total = new Date().getTime() - workCost.total;
    }
    @Override
    public void callFailed(@NotNull Call call, @NotNull IOException ioe) {
        super.callFailed(call, ioe);
        workCostMap.get(call.request().hashCode()).total = new Date().getTime() - workCostMap.get(call.request().hashCode()).total;
        //Log.d(TAG, "callFailed");
        NetWorkCost workCost = workCostMap.get(call.request().hashCode());
        if (workCost != null) {
            workCost.total = new Date().getTime() - workCost.total;
        }
        //Log.d(TAG, "callFailed hashcode:"+call.request().hashCode() +" "+workCost);
    }
}
app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java
@@ -1,96 +1,71 @@
package com.runt.open.mvvm.retrofit.observable;
import android.accounts.NetworkErrorException;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import com.google.gson.Gson;
import com.runt.open.mvvm.data.HttpApiResult;
import com.runt.open.mvvm.data.BaseApiResult;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.concurrent.TimeoutException;
import java.util.regex.Pattern;
import io.reactivex.SingleObserver;
import io.reactivex.observers.DisposableObserver;
import retrofit2.Response;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import retrofit2.adapter.rxjava2.HttpException;
/**
 * 网络请求观察
 * Created by Administrator on 2021/11/11 0011.
 */
public abstract class HttpObserver<M extends BaseApiResult> extends DisposableObserver<Response<M>> implements SingleObserver<Response<M>> {
public abstract class HttpObserver<RESULT> implements Observer<HttpApiResult<RESULT>> {
    final String TAG = "HttpObserver";
    MutableLiveData<M> resultLive;
    public HttpObserver(MutableLiveData<M> resultLive) {
        this.resultLive = resultLive;
    }
    @Override
    public void onNext(Response<M> response) {
        onExcuted(response);
    public void onSubscribe(Disposable d) {
        Log.d(TAG,"onSubscribe "+hashCode());
    }
    @Override
    public void onError(Throwable throwable) {
        Log.i("subscribe","onError");
        try {
            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  if( throwable instanceof NetworkErrorException){
                t.msg = "网络连接不畅,请检查您的网络设置";//设置错误信息
            }else{
                t.msg = throwable.getMessage();//设置错误信息
            }
            resultLive.setValue(t);
        } catch (ClassCastException e) {
            e.printStackTrace();
            M t = (M) new BaseApiResult<String>();
            t.code = 409;
            t.msg = "实例化对象未指定泛型实体类";
            resultLive.setValue(t);
        } catch (Exception e) {
            e.printStackTrace();
            M t = (M) new BaseApiResult<String>();
            t.code = 409;
            t.msg = e.getMessage();
            resultLive.setValue(t);
        }
    }
    @Override
    public void onSuccess(Response<M> response) {
        onExcuted(response);
    }
    private void onExcuted(@NonNull Response<M> response){
        if(response.body() != null){
            resultLive.setValue(response.body());
    public void onNext(HttpApiResult<RESULT> httpResult) {
        Log.d(TAG,"onNext "+httpResult);
        if (httpResult != null && httpResult.code == 0) {
            onSuccess(httpResult.data);
        }else{
            try {
                String error = response.errorBody().string();
                Log.i("subscribe","onExcuted "+error);
                onError(new Throwable(error));
            } catch (IOException e) {
                e.printStackTrace();
            }
            onFailed(httpResult);//接口返回错误
        }
    }
    @Override
    public void onError(Throwable e) {
        e.printStackTrace();
        Log.e(TAG,"onError "+e.getMessage()+" "+hashCode());
        int code = 600;
        String msg = "网络请求失败,请检查网络或稍后重试";
        if( e instanceof ConnectException || e instanceof TimeoutException
                || e instanceof SocketTimeoutException || e instanceof UnknownHostException){
            code = 601;
            msg = "网络请求失败,请检查网络或稍后重试";
        }else if( e instanceof HttpException){
            String regEx = "[^0-9]";
            Log.i(TAG,"code:"+ Pattern.compile(regEx).matcher(e.getMessage()).replaceAll(""));
            String error = Pattern.compile(regEx).matcher(e.getMessage()).replaceAll("");
            code = error.length()>0?Integer.parseInt(error):500;
            msg = error.length()>0?"服务器请求失败":"登录信息验证失败";
        }
        HttpApiResult httpResult = new Gson().fromJson("{'code':"+code+",'msg':'"+msg+"'}", HttpApiResult.class);
        onFailed(httpResult);
    }
    @Override
    public void onComplete() {
        Log.i("subscribe","onComplete");
        Log.i(TAG,"onComplete "+hashCode());
    }
    protected abstract void onSuccess(RESULT data);
    protected void onFailed(HttpApiResult httpResult){}
}
app/src/main/java/com/runt/open/mvvm/retrofit/utils/HttpPrintUtils.java
@@ -1,26 +1,20 @@
package com.runt.open.mvvm.retrofit.utils;
import com.runt.open.mvvm.BuildConfig;
import com.runt.open.mvvm.util.GsonUtils;
import com.runt.open.mvvm.util.MyLog;
import android.util.Log;
import java.io.EOFException;
import java.util.ArrayList;
import okio.Buffer;
/**
 * My father is Object, ites purpose of 单例模式  保证synchronized方法的线程安全性
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-5-13.
 */
public class HttpPrintUtils {
    String TAG = "HttpPrintUtils";
    static HttpPrintUtils instance;
    public static  HttpPrintUtils getInstance(){
        if(instance == null){
           instance = new HttpPrintUtils();
            instance = new HttpPrintUtils();
        }
        return instance;
    }
@@ -76,14 +70,12 @@
        logArrays.add("┗"+getEmptyStr((length-end.length())/2,"━")+end+getEmptyStr((length-end.length())/2,"━")+"┛\n");
        logArrays.add(" \n\n\n");
        //Logger.DEFAULT.log(sb.toString());//打印log,避免多个log语句,导致log输出时其他线程的log输出切入此输出阵列内
        if(BuildConfig.DEBUG) {
            for(int i = 0 ; i < logArrays.size() ; i ++ ){
                String str = logArrays.get(i);
                if (info) {
                    MyLog.i(TAG , str.replace("\n","")+" "+logArrays.size()+" "+i);
                } else {
                    MyLog.e(TAG , str.replace("\n","")+" "+logArrays.size()+" "+i);
                }
        for(int i = 0 ; i < logArrays.size() ; i ++ ){
            String str = logArrays.get(i);
            if (info) {
                Log.i(TAG , str.replace("\n","")+" "+logArrays.size()+" "+i);
            } else {
                Log.e(TAG , str.replace("\n","")+" "+logArrays.size()+" "+i);
            }
        }
    }
@@ -102,16 +94,12 @@
                s = s.substring(0,totalLength*3)+"...";
            }
            s = s.replace("\t","    ");//缩进替换空格
            if(s.indexOf("\":{\"")>-1 || s.indexOf("\":[{\"")>-1 || s.indexOf("\":[[")>-1){//内容非校正缩进,且为json字符规范
                splitStr(s.substring(0,s.indexOf("\":")+2)+ GsonUtils.retractJson(s.substring(s.indexOf("\":")+2)),totalLength,list);
            if(s.length()> totalLength){
                outOflength(s,totalLength,list);
            }else {
                if(s.length()> totalLength){
                    outOflength(s,totalLength,list);
                }else {
                    logStr = "┃      " + s + getEmptyStr((totalLength - 16 - s.length()), " ");
                    //处理中文间距,保证打印无偏差
                    list.add(logStr + getEmptyStr((totalLength - logStr.length() - 1 - hasCNchar(logStr)), " ") + "┃ "/*+logStr.length()+" "+logStr.getBytes().length+" "+(" ").getBytes().length +" "+hasCNchar(s)*/ + "\n");
                }
                logStr = "┃      " + s + getEmptyStr((totalLength - 16 - s.length()), " ");
                //处理中文间距,保证打印无偏差
                list.add(logStr + getEmptyStr((totalLength - logStr.length() - 1 - hasCNchar(logStr)), " ") + "┃ "/*+logStr.length()+" "+logStr.getBytes().length+" "+(" ").getBytes().length +" "+hasCNchar(s)*/ + "\n");
            }
        }
    }
@@ -182,29 +170,6 @@
            sb.append(space);
        }
        return sb.toString();
    }
    /**
     * Returns true if the body in question probably contains human readable text. Uses a small sample
     * of code points to detect unicode control characters commonly used in binary file signatures.
     */
    static boolean isPlaintext(Buffer buffer) {
        try {
            Buffer prefix = new Buffer();
            long byteCount = buffer.size() < 64 ? buffer.size() : 64;
            buffer.copyTo(prefix, 0, byteCount);
            for (int i = 0; i < 16; i++) {
                if (prefix.exhausted()) {
                    break;
                }
                int codePoint = prefix.readUtf8CodePoint();
                if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
                    return false;
                }
            }
            return true;
        } catch (EOFException e) {
            return false; // Truncated UTF-8 sequence.
        }
    }
}
app/src/main/java/com/runt/open/mvvm/retrofit/utils/RetrofitUtils.java
@@ -23,7 +23,6 @@
 */
public class RetrofitUtils {
    public static String HOST_IP_ADDR;
    static RetrofitUtils instance;
    Retrofit retrofit/*log输出,驼峰转换*/,unHumpRetrofit/*log输出,不强制驼峰转换*/,
            unLogRetrofit/*log不输出,驼峰转换*/,unLogHumpRetorfit/*log不输出,不强制驼峰转换*/;
@@ -108,7 +107,7 @@
                //设置OKHttpClient
                .client(client)
                //设置baseUrl,注意,baseUrl必须后缀"/"
                .baseUrl(BuildConfig.ENVIRONMENT.equals("develop")?HOST_IP_ADDR:BuildConfig.HOST_IP_ADDR)
                .baseUrl(BuildConfig.HOST_IP_ADDR+"api/v1/")
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }
app/src/main/java/com/runt/open/mvvm/ui/login/LoginViewModel.java
@@ -29,7 +29,12 @@
    MutableLiveData<Results.StringApiResult> verifyResult = new MutableLiveData<>();
    MutableLiveData<Results.StringApiResult> resetResult = new MutableLiveData<>();
    MutableLiveData<Results.StringApiResult> registerResult = new MutableLiveData<>();
    HttpObserver<Results.LoggedInUser> logginObserver = new HttpObserver<Results.LoggedInUser>(){
        @Override
        protected void onSuccess(Results.LoggedInUser data) {
            loginResult.setValue(data);
        }
    };
    public MutableLiveData<Results.LoggedInUser> getLoginResult() {
        return loginResult;
    }
@@ -46,7 +51,7 @@
    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){});
        httpObserverOnLoading(userObservable,logginObserver);
    }
    /**
@@ -55,8 +60,7 @@
     * @param code
     */
    public void loginByCode(String phone,String code){
        httpObserverOnLoading(loginApi.loginByCode(phone,code),
                new HttpObserver<Results.LoggedInUser>(loginResult){});
        httpObserverOnLoading(loginApi.loginByCode(phone,code),logginObserver);
    }
    /**
@@ -66,7 +70,7 @@
     * @param pass
     */
    public void resetPwd(String phone,String sms,String pass){
        httpObserverOnLoading(loginApi.resetLoginPwd(phone, sms, pass), new HttpObserver<Results.StringApiResult>(resetResult) {});
        httpObserverOnLoading(loginApi.resetLoginPwd(phone, sms, pass),logginObserver);
    }
    /**
@@ -76,7 +80,12 @@
     * @param pass
     */
    public void register(String phone,String sms,String pass){
        httpObserverOnLoading(loginApi.register(phone, sms, pass), new HttpObserver<Results.StringApiResult>(resetResult) {});
        httpObserverOnLoading(loginApi.register(phone, sms, pass), new HttpObserver<Results.StringApiResult>(){
            @Override
            protected void onSuccess(Results.StringApiResult data) {
                resetResult.setValue(data);
            }
        });
    }
    /**
@@ -110,7 +119,12 @@
     */
    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){});
        httpObserverOnLoading(loginApi.getVerifyCode(url, phone, randomString(phone, time), time), new HttpObserver<Results.StringApiResult>(){
            @Override
            protected void onSuccess(Results.StringApiResult data) {
                verifyResult.setValue(data);
            }
        });
    }
    /**
app/src/main/java/com/runt/open/mvvm/ui/login/RegisterLoginActivity.java
@@ -29,30 +29,23 @@
    @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 -> {
        mBinding.txtGetVerify.setOnClickListener(onclick);
        mBinding.txtForgot.setOnClickListener(onclick);
        mBinding.txtLogin.setOnClickListener(onclick);
        mBinding.txtRegister.setOnClickListener(onclick);
        mBinding.txtPrivacy.setOnClickListener(onclick);
        mBinding.editPhone.setText(getStringProjectPrefrence(Configuration.KEY_USERNAME));
        mViewModel.getVerifyResult().observe(this, stringApiResult -> {
           if(stringApiResult.code == 200){
           }else{
               showToast(stringApiResult.msg);
           }
        });
        viewModel.getLoginResult().observe(this,loggedInUser -> {
        mViewModel.getLoginResult().observe(this, loggedInUser -> {
            if(loggedInUser.code == 200){
                putBooleanProjectPrefrence(Configuration.IS_LOGIN,true);
                putStringProjectPrefrence(Configuration.KEY_USERNAME,binding.editPhone.getText().toString());
                putStringProjectPrefrence(Configuration.KEY_USERNAME, mBinding.editPhone.getText().toString());
                UserBean user = new Gson().fromJson(new Gson().toJson(loggedInUser.data) ,UserBean.class);
                UserBean.setUser(user);
@@ -67,6 +60,17 @@
        });
    }
    @Override
    public void loadData() {
        long getTime = getLongProjectPrefrence(VERIFY_CODE);
        long cha = new Date().getTime() - getTime;
        if(cha <1000*60){
            CodeTimer codeTimer = new CodeTimer(cha, 1000, mBinding.txtGetVerify);
            codeTimer.startUp();
        }
        changeView();
    }
    CustomClickListener onclick = new CustomClickListener() {
        @Override
        protected void onSingleClick(View view) {
@@ -76,16 +80,16 @@
                    break;
                case R.id.txt_get_verify:
                    String phone = binding.editPhone.getText().toString();
                    String phone = mBinding.editPhone.getText().toString();
                    if(!verifyPhone(phone)){//验证手机
                        return;
                    }
                    if(type==2){//获取注册验证码
                        viewModel.getRegisterSMS(phone);
                        mViewModel.getRegisterSMS(phone);
                    }else if(type ==1){
                        viewModel.getForgetSMS( phone);
                        mViewModel.getForgetSMS( phone);
                    }else if(type == -1){
                        viewModel.getLoginSMS( phone);
                        mViewModel.getLoginSMS( phone);
                    }
                    break;
                case R.id.txt_privacy:
@@ -116,53 +120,53 @@
     * 修改页面布局
     */
    private void changeView(){
        binding.button.setEnabled(true);
        binding.txtRegister.setVisibility(View.VISIBLE);
        binding.checkbox.setVisibility(View.GONE);
        binding.txtPrivacy.setVisibility(View.GONE);
        mBinding.button.setEnabled(true);
        mBinding.txtRegister.setVisibility(View.VISIBLE);
        mBinding.checkbox.setVisibility(View.GONE);
        mBinding.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);
                mBinding.editVerifyCode.setVisibility(View.VISIBLE);
                mBinding.txtGetVerify.setVisibility(View.VISIBLE);
                mBinding.editPass.setVisibility(View.GONE);
                mBinding.editPassRepeat.setVisibility(View.GONE);
                mBinding.txtForgot.setVisibility(View.VISIBLE);
                mBinding.txtLogin.setText(getResources().getString(R.string.login));
                mBinding.button.setText(getResources().getString(R.string.login));
                mBinding.checkbox.setVisibility(View.VISIBLE);
                mBinding.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));
                mBinding.editVerifyCode.setVisibility(View.GONE);
                mBinding.txtGetVerify.setVisibility(View.GONE);
                mBinding.editPass.setVisibility(View.VISIBLE);
                mBinding.editPassRepeat.setVisibility(View.GONE);
                mBinding.txtForgot.setVisibility(View.VISIBLE);
                mBinding.txtLogin.setText(getResources().getString(R.string.msg_login));
                mBinding.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));
                mBinding.txtForgot.setVisibility(View.INVISIBLE);
                mBinding.editVerifyCode.setVisibility(View.VISIBLE);
                mBinding.txtGetVerify.setVisibility(View.VISIBLE);
                mBinding.editPass.setVisibility(View.VISIBLE);
                mBinding.editPassRepeat.setVisibility(View.VISIBLE);
                mBinding.txtLogin.setText(getResources().getString(R.string.login));
                mBinding.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));
                mBinding.checkbox.setVisibility(View.VISIBLE);
                mBinding.txtPrivacy.setVisibility(View.VISIBLE);
                mBinding.txtRegister.setVisibility(View.INVISIBLE);
                mBinding.editVerifyCode.setVisibility(View.VISIBLE);
                mBinding.txtGetVerify.setVisibility(View.VISIBLE);
                mBinding.editPass.setVisibility(View.VISIBLE);
                mBinding.editPassRepeat.setVisibility(View.VISIBLE);
                mBinding.txtLogin.setText(getResources().getString(R.string.login));
                mBinding.button.setText(getResources().getString(R.string.register));
                break;
        }
        clearText(binding.editPassRepeat,binding.editPass,binding.editPhone,binding.editVerifyCode);
        clearText(mBinding.editPassRepeat, mBinding.editPass, mBinding.editPhone, mBinding.editVerifyCode);
    }
    private void clearText(EditText... editTextes){
@@ -178,10 +182,10 @@
     * 提交数据
     */
    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();
        String phone = mBinding.editPhone.getText().toString();
        String pass = mBinding.editPass .getText().toString();
        String veriCode = mBinding.editVerifyCode.getText().toString();
        String invite = mBinding.editPassRepeat.getText().toString();
        if(!verifyPhone(phone)){//验证手机
            return;
        }
@@ -191,17 +195,17 @@
                    showToast(R.string.input_verify_code);
                    return;
                }
                if(!binding.checkbox.isChecked()){
                if(!mBinding.checkbox.isChecked()){
                    showToast("请阅读并勾选《隐私条款》");
                    return;
                }
                viewModel.loginByCode(phone,veriCode);
                mViewModel.loginByCode(phone,veriCode);
                break;
            case 0:
                if(!verifyPassWord(pass)){//验证密码
                    return;
                }
                viewModel.login(phone,pass);
                mViewModel.login(phone,pass);
                break;
            case 1:
                if(!verifyPassWord(pass)){//验证密码
@@ -220,7 +224,7 @@
                    showToast(R.string.str_new_verify_failed);
                    return;
                }
                viewModel.resetPwd(phone,veriCode,pass);
                mViewModel.resetPwd(phone,veriCode,pass);
                break;
            case 2://注册
                if(!verifyPassWord(pass)){//验证密码
@@ -230,11 +234,11 @@
                    showToast(R.string.input_verify_code);
                    return;
                }
                if(!binding.checkbox.isChecked()){
                if(!mBinding.checkbox.isChecked()){
                    showToast("请阅读并勾选《隐私条款》");
                    return;
                }
                viewModel.register(phone,veriCode,pass);
                mViewModel.register(phone,veriCode,pass);
                break;
        }
    }
app/src/main/java/com/runt/open/mvvm/ui/main/MainActivity.java
New file
@@ -0,0 +1,153 @@
package com.runt.open.mvvm.ui.main;
import android.Manifest;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.view.KeyEvent;
import android.view.View;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.permissionx.guolindev.PermissionX;
import com.runt.open.mvvm.R;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.base.adapter.FragmentAdapter;
import com.runt.open.mvvm.base.fragments.BaseFragment;
import com.runt.open.mvvm.data.PhoneDevice;
import com.runt.open.mvvm.databinding.ActivityMainBinding;
import com.runt.open.mvvm.listener.CustomClickListener;
import com.runt.open.mvvm.listener.ResPonse;
import com.runt.open.mvvm.ui.login.RegisterLoginActivity;
import com.runt.open.mvvm.ui.login.UserBean;
import com.runt.open.mvvm.ui.main.home.HomeFragment;
import com.runt.open.mvvm.ui.main.mine.MineFragment;
import com.runt.open.mvvm.ui.main.service.ServiceFragment;
import java.util.Arrays;
public class MainActivity extends BaseActivity<ActivityMainBinding, MainViewModel> {
    private BaseFragment[] fragments = {new HomeFragment(),new ServiceFragment(),new MineFragment()} ;
    ActivityResultLauncher<Intent>  loginLaunch = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
        if(result.getResultCode() == RESULT_CODE_SUCESS){
            fragments[2].loadData();//登录后重新刷新
        }else if(result.getResultCode() != RESULT_CODE_SUCESS){
            mBinding.viewPager2.setCurrentItem(0);
        }
    });
    @Override
    public void initViews() {
        mBinding.titleBar.setRightDra(getResources().getDrawable(R.mipmap.icon_white_setting));
        mBinding.titleBar.setRightClick(new CustomClickListener() {
            @Override
            protected void onSingleClick(View view) {
                //startActivityForResult(new Intent(mContext,SettingActivity.class),REQUEST_CODE_LOGOUT);//打开设置
            }
        });
        final FragmentAdapter fragmentAdapter = new FragmentAdapter(this);
        fragmentAdapter.setFragments(Arrays.asList(fragments));
        //设置当前可见Item左右可见page数,次范围内不会被销毁
        //禁用预加载
        mBinding.viewPager2.setOffscreenPageLimit(ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT);
        mBinding.viewPager2.setAdapter(fragmentAdapter);
        mBinding.viewPager2.setCurrentItem(0);
        mBinding.viewPager2.setUserInputEnabled(false); //true:滑动,false:禁止滑动
        mBinding.viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                setTitleStr(position);
            }
            @Override
            public void onPageSelected(int position) {
                mBinding.navView.getMenu().getItem(position).setChecked(true);
                if(position == 2 && UserBean.getUser() == null){
                    loginLaunch.launch(new Intent(mContext, RegisterLoginActivity.class));
                }
            }
        });
        ColorStateList csl = getResources().getColorStateList(R.color.home_nav_color);
        mBinding.navView.setItemTextColor(csl);//设置文本颜色
        mBinding.navView.setItemIconTintList(csl);//图标着色
        mBinding.navView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);//监听
    }
    @Override
    public void loadData() {
        checkPermission();
    }
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
            backExit();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
    /**
     * 底部导航监听
     */
    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener = item -> {
        for(int i = 0 ; i < mBinding.navView.getMenu().size() ; i ++){
            if(item.getItemId() == mBinding.navView.getMenu().getItem(i).getItemId()){
                mBinding.viewPager2.setCurrentItem(i);
                return true;
            }
        }
        return false;
    };
    /**
     * 设置标题
     * @param position
     */
    private void setTitleStr(int position){
        switch (position){
            case 0:
                setTitle("资讯");
                break;
            case 1:
                setTitle("服务");
                break;
            case 2:
                setTitle("个人中心");
                break;
        }
    }
    private void showPermissionDialog(){
        showDialog("警告", "软件需要权限才能运行", "申请权限", "退出", new ResPonse() {
            @Override
            public void doSuccess(Object obj) {
                checkPermission();
            }
            @Override
            public void doError(Object obj) {
                finish();
                System.exit(0);
            }
        });
    }
    private void checkPermission(){
        PermissionX.init(MainActivity.this)
                .permissions(Manifest.permission.READ_PHONE_STATE)
                .request((allGranted, grantedList, deniedList) -> {
                    if(allGranted){
                        PhoneDevice.setDevice(mContext);
                    }else{
                        showPermissionDialog();
                    }
                });
    }
}
app/src/main/java/com/runt/open/mvvm/ui/main/dashboard/DashboardFragment.java
File was deleted
app/src/main/java/com/runt/open/mvvm/ui/main/dashboard/DashboardViewModel.java
File was deleted
app/src/main/java/com/runt/open/mvvm/ui/main/home/HomeFragment.java
@@ -1,33 +1,17 @@
package com.runt.open.mvvm.ui.main.home;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.runt.open.mvvm.base.fragments.LoadPageFragment;
import com.runt.open.mvvm.databinding.RefreshRecyclerBinding;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import java.util.HashMap;
import java.util.Map;
import com.runt.open.mvvm.base.fragments.BaseFragment;
import com.runt.open.mvvm.databinding.FragmentHomeBinding;
public class HomeFragment extends BaseFragment<FragmentHomeBinding,HomeViewModel> {
public class HomeFragment extends LoadPageFragment<RefreshRecyclerBinding,HomeViewModel,MsgAdapter,Message> {
    @Override
    public void initViews() {
        final TextView textView = binding.textHome;
        viewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                textView.setText(s);
            }
        });
    protected Map requestParams() {
        return new HashMap();
    }
}
app/src/main/java/com/runt/open/mvvm/ui/main/home/HomeViewModel.java
@@ -1,19 +1,12 @@
package com.runt.open.mvvm.ui.main.home;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.runt.open.mvvm.base.model.LoadPageViewModel;
import com.runt.open.mvvm.data.Results;
public class HomeViewModel extends ViewModel {
public class HomeViewModel extends LoadPageViewModel<Results.MessageResult> {
    private MutableLiveData<String> mText;
    public HomeViewModel() {
        mText = new MutableLiveData<>();
        mText.setValue("This is home fragment");
    }
    public LiveData<String> getText() {
        return mText;
    @Override
    protected String requestUrl() {
        return "getMsgList";
    }
}
app/src/main/java/com/runt/open/mvvm/ui/main/home/Message.java
New file
@@ -0,0 +1,8 @@
package com.runt.open.mvvm.ui.main.home;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2022/7/27.
 */
public class Message {
    public String content,cTime,title;
}
app/src/main/java/com/runt/open/mvvm/ui/main/home/MsgAdapter.java
New file
@@ -0,0 +1,33 @@
package com.runt.open.mvvm.ui.main.home;
import android.view.View;
import com.runt.open.mvvm.base.adapter.BaseAdapter;
import com.runt.open.mvvm.databinding.ItemMsgBinding;
import com.runt.open.mvvm.listener.CustomClickListener;
import com.runt.open.mvvm.util.HandleDate;
import java.util.Date;
/**
 * My father is Object, ites purpose of
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-8-21.
 */
public class MsgAdapter extends BaseAdapter<Message, ItemMsgBinding> {
    @Override
    protected void onBindView(ItemMsgBinding binding, int position, Message message) {
        binding.txtDetail.setText(message.content);
        Date date = new Date(message.cTime);
        binding.txtTime.setText(HandleDate.getTimeStateNew(date));
        binding.txtTitle.setText(message.title);
        binding.getRoot().setOnClickListener(new CustomClickListener() {
            @Override
            protected void onSingleClick(View view) {
                //context.startActivity(new Intent(context, MsgDetailActivity.class).putExtra("id", data.get("id").toString()));
            }
        });
    }
}
app/src/main/java/com/runt/open/mvvm/ui/main/mine/MineFragment.java
New file
@@ -0,0 +1,177 @@
package com.runt.open.mvvm.ui.main.mine;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.permissionx.guolindev.PermissionX;
import com.runt.open.mvvm.BuildConfig;
import com.runt.open.mvvm.R;
import com.runt.open.mvvm.base.fragments.BaseFragment;
import com.runt.open.mvvm.databinding.FragmentMineBinding;
import com.runt.open.mvvm.listener.ResPonse;
import com.runt.open.mvvm.retrofit.observable.HttpObserver;
import com.runt.open.mvvm.ui.login.UserBean;
import com.runt.open.mvvm.util.MyLog;
import com.wildma.pictureselector.PictureSelector;
import java.io.File;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
 * My father is Object, ites purpose of
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-9-15.
 */
public class MineFragment extends BaseFragment<FragmentMineBinding,MineViewModel> implements View.OnClickListener {
    private final  String TAG = "MineFragment";
    @Override
    public void initViews() {
    }
    @Override
    public void loadData() {
        if(UserBean.getUser() != null){
            RequestOptions options = new RequestOptions()
                    .placeholder(R.mipmap.default_head)//图片加载出来前,显示的图片
                    .fallback(R.mipmap.default_head) //url为空的时候,显示的图片
                    .error(R.mipmap.default_head);//图片加载失败后,显示的图片
            Glide.with(getContext()).load(BuildConfig.HOST_IP_ADDR +UserBean.getUser().getHead()).apply(options).into(mBinding.img);
            mBinding.txtName.setText(UserBean.getUser().getUsername());
            mBinding.txtCoin.setText(UserBean.getUser().getCoin()+"");
            mBinding.txtSigns.setText(UserBean.getUser().getSign()+"");
            mBinding.linGroup.setVisibility(View.VISIBLE);
        }else{
            Glide.with(getContext()).load(R.mipmap.default_head).into(mBinding.img);
            mBinding.txtName.setText("未登录");
            mBinding.linGroup.setVisibility(View.GONE);
        }
        setOnClickListener(this,R.id.lin_sign,R.id.lin_coin,R.id.img,R.id.txt_name);
    }
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.img:
                openAlthum();
                break;
            case R.id.txt_name://名称
                mViewModel.updateName(new HttpObserver() {
                    @Override
                    protected void onSuccess(Object data) {
                        UserBean.getUser().setUsername(data.toString());
                        mBinding.txtName.setText(data.toString());
                    }
                });
                break;
           /* case R.id.lin_coin://金币
                new BottomMenuFragment(getActivity())
                        .addMenuItems(new MenuItem("查看记录"))
                        .addMenuItems(new MenuItem("申请提现"))
                        .setOnItemClickListener(new BottomMenuFragment.OnItemClickListener() {
                            @Override
                            public void onItemClick(TextView menu_item, int position) {
                                if(position == 0){
                                    startActivity(new Intent(mActivity, CoinRecordActivity.class) );
                                }else {
                                    if(mActivity.isNull(UserBean.getUser().getAlipay())){
                                        mActivity.showDialog("设置支付宝", "您还没有设置支付宝账号", "设置", "取消", new ResPonse() {
                                            @Override
                                            public void doSuccess(Object obj) {
                                                startActivity(new Intent(mActivity, CoinSettingActivity.class) );
                                            }
                                        });
                                    }else if(mActivity.isNull(UserBean.getUser().getRealname())){
                                        mActivity.showDialog("设置真实姓名", "您还没有设置真实姓名", "设置", "取消", new ResPonse() {
                                            @Override
                                            public void doSuccess(Object obj) {
                                                startActivity(new Intent(mActivity, CoinSettingActivity.class) );
                                            }
                                        });
                                    }else{
                                        startActivityForResult(new Intent(mActivity, WithDrawActivity.class),REQUEST_CODE_WITHDRAW );
                                    }
                                }
                            }
                        })
                        .show();
                break;
            case R.id.lin_sign://签到
                startActivityForResult(new Intent(getContext(), SignInActivity.class),REQUEST_CODE_SIGN);
                break;*/
        }
    }
    /**
     * 打开相册
     */
    public void openAlthum(){
        PermissionX.init(this)
                .permissions(mActivity.CAMERA_PERMISSIONS)
                .request((allGranted, grantedList, deniedList) -> {
                    if(allGranted){
                        PictureSelector
                                .create(this, PictureSelector.SELECT_REQUEST_CODE)
                                .selectPicture(true, 300, 300, 20, 20);
                    }else{
                        mActivity.showDialog("警告", "软件需要权限才能运行", "申请权限", "取消", new ResPonse() {
                            @Override
                            public void doSuccess(Object obj) {
                                openAlthum();
                            }
                            @Override
                            public void doError(Object obj) {
                            }
                        });
                    }
                });
    }
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        /*结果回调*/
        if (requestCode == PictureSelector.SELECT_REQUEST_CODE) {
            if (data != null) {
                String picturePath = data.getStringExtra(PictureSelector.PICTURE_PATH);
                MyLog.i("mineActivity","picturePath:"+picturePath);
                final  File file = new File(picturePath);
                mViewModel.updateHead(file).enqueue(new Callback<ResponseBody>() {
                    @Override
                    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                        /*UserBean.getUser().setHead(obj.toString());
                        file.delete();
                        Glide.with(getContext()).load(BuildConfig.HOST_IP_ADDR+UserBean.getUser().getHead()) .into(mBinding.img); //获取选取的图片*/
                    }
                    @Override
                    public void onFailure(Call<ResponseBody> call, Throwable t) {
                        file.delete();
                    }
                });
            }
        }else if(requestCode == REQUEST_CODE_SIGN && resultCode == Activity.RESULT_OK){
            loadData();
        }else if(requestCode == REQUEST_CODE_WITHDRAW && resultCode == Activity.RESULT_OK){
            loadData();
        }
    }
}
app/src/main/java/com/runt/open/mvvm/ui/main/mine/MineViewModel.java
New file
@@ -0,0 +1,36 @@
package com.runt.open.mvvm.ui.main.mine;
import com.runt.open.mvvm.base.model.BaseViewModel;
import com.runt.open.mvvm.listener.ResPonse;
import com.runt.open.mvvm.retrofit.observable.HttpObserver;
import com.runt.open.mvvm.ui.login.UserBean;
import java.io.File;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2022/7/27.
 */
public class MineViewModel extends BaseViewModel {
    public void updateName(HttpObserver observer){
        if(UserBean.getUser().getPhone().equals(UserBean.getUser().getUsername())) {
            mActivity.showInputDialog("输入名称", UserBean.getUser().getUsername(), "名称只能修改一次", new ResPonse() {
                @Override
                public void doSuccess(Object obj) {
                    httpObserverOnLoading(commonApi.updateName(obj.toString()), observer);
                }
            });
        }
    }
    public Call<ResponseBody> updateHead(File file){
        return commonApi.updateHead(MultipartBody.Part.createFormData("head",file.getName(), RequestBody.create(MediaType.parse("text/plain"), file)));
    }
}
app/src/main/java/com/runt/open/mvvm/ui/main/notifications/NotificationsFragment.java
File was deleted
app/src/main/java/com/runt/open/mvvm/ui/main/notifications/NotificationsViewModel.java
File was deleted
app/src/main/java/com/runt/open/mvvm/ui/main/service/ServiceFragment.java
New file
@@ -0,0 +1,63 @@
package com.runt.open.mvvm.ui.main.service;
import android.view.View;
import com.runt.open.mvvm.R;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.base.fragments.BaseFragment;
import com.runt.open.mvvm.base.model.ImpViewModel;
import com.runt.open.mvvm.databinding.FragmentServiceBinding;
import com.runt.open.mvvm.listener.ResPonse;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2022/7/27.
 */
public class ServiceFragment extends BaseFragment<FragmentServiceBinding, ImpViewModel> implements View.OnClickListener {
    @Override
    public void initViews() {
    }
    @Override
    public void loadData() {
    }
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.lin_uav:
                mActivity.showDialog("联系服务商", "即将拨打服务商电话", "拨打","取消",new ResPonse() {
                    @Override
                    public void doSuccess(Object obj) {
                        mActivity.callPhone("15048325741");
                    }
                });
                break;
            case R.id.lin_bozhong:
                showDialog();
                break;
            case R.id.lin_yumi:
                showDialog();
                break;
            case R.id.lin_xiaomai:
                showDialog();
                break;
        }
    }
    public void showDialog(){
        ((BaseActivity)getActivity()).showDialog("暂无服务商", "如果您是相关服务商可致电入住", "入住","取消",new ResPonse() {
            @Override
            public void doSuccess(Object obj) {
                mActivity.showDialog("联系平台", "即将拨打平台电话", "拨打","取消",new ResPonse() {
                    @Override
                    public void doSuccess(Object obj) {
                        mActivity.callPhone("13000000000");
                    }
                });
            }
        });
    }
}
app/src/main/java/com/runt/open/mvvm/ui/splash/SplashActivity.java
@@ -2,18 +2,12 @@
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.lifecycle.Observer;
import com.bytedance.sdk.openadsdk.TTSplashAd;
import com.runt.open.mvvm.MainActivity;
import com.runt.open.mvvm.base.activities.BaseActivity;
import com.runt.open.mvvm.base.model.ImpViewModel;
import com.runt.open.mvvm.databinding.ActivitySplashBinding;
import com.runt.open.mvvm.ui.main.MainActivity;
/**
@@ -21,73 +15,26 @@
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-4-16.
 */
public class SplashActivity extends BaseActivity<ActivitySplashBinding,SplashViewModel> {
public class SplashActivity extends BaseActivity<ActivitySplashBinding, ImpViewModel> {
    final String TAG = "WelcomeActivity";
    Handler handler = new Handler(){
        boolean started = false;
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if(!started) {//确保该语句只执行一次
                started = true;
                Intent intent = new Intent(mContext, MainActivity.class);
                startActivity(intent);
                finish();
            }
        }
    };
    @Override
    public void initViews() {
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏
        hideBottomUIMenu();
        viewModel.getSplashAd().observe(this, new Observer<TTSplashAd>() {
            @Override
            public void onChanged(TTSplashAd ttSplashAd) {
                binding.splashAdContainer.addView(ttSplashAd.getSplashView());
                //设置SplashView的交互监听器
                ttSplashAd.setSplashInteractionListener(new TTSplashAd.AdInteractionListener() {
                    @Override
                    public void onAdClicked(View view, int type) {
                        Log.d(TAG, "onAdClicked");
                    }
                    @Override
                    public void onAdShow(View view, int type) {
                        Log.d(TAG, "onAdShow");
                    }
                    @Override
                    public void onAdSkip() {
                        Log.d(TAG, "onAdSkip");
                        handler.sendMessage(new Message());
                    }
                    @Override
                    public void onAdTimeOver() {
                        Log.d(TAG, "onAdTimeOver");
                        handler.sendMessage(new Message());
                    }
                });
            }
        });
        viewModel.getTimeOut().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                handler.sendMessage(new Message());
            }
        });
        viewModel.applyTdAd(mContext);;//请求广告
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        binding.splashAdContainer.removeAllViews();
    public void loadData() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Intent intent = new Intent(mContext, MainActivity.class);
                startActivity(intent);
                finish();
            }
        },2000);
    }
}
app/src/main/java/com/runt/open/mvvm/ui/splash/SplashViewModel.java
@@ -1,15 +1,9 @@
package com.runt.open.mvvm.ui.splash;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import androidx.lifecycle.MutableLiveData;
import com.bytedance.sdk.openadsdk.AdSlot;
import com.bytedance.sdk.openadsdk.TTAdNative;
import com.bytedance.sdk.openadsdk.TTAdSdk;
import com.bytedance.sdk.openadsdk.TTSplashAd;
import com.runt.open.mvvm.base.model.BaseViewModel;
import java.util.Date;
@@ -22,12 +16,8 @@
    final String TAG = "SplashViewModel";
    long cTime = new Date().getTime(),limitTime = 2000;
    private MutableLiveData<TTSplashAd> splashAd = new MutableLiveData<>();
    private MutableLiveData<Integer> timeOut = new MutableLiveData<>();
    public MutableLiveData<TTSplashAd> getSplashAd() {
        return splashAd;
    }
    public MutableLiveData<Integer> getTimeOut() {
        return timeOut;
@@ -42,46 +32,5 @@
        },limitTime);
    }
    /**
     * 请求广告
     * @param context
     */
    public void applyTdAd(Context context){
        countdown();
        TTAdNative mTTAdNative = TTAdSdk.getAdManager().createAdNative(context);
        AdSlot adSlot = new AdSlot.Builder()
                .setCodeId("887382769")//广告id
                .setSupportDeepLink(true)
                .setImageAcceptedSize(1080,1920)
                //模板广告需要设置期望个性化模板广告的大小,单位dp,代码位是否属于个性化模板广告,请在穿山甲平台查看
                //.setExpressViewAcceptedSize(expressViewWidth, expressViewHeight)
                .build();
        mTTAdNative.loadSplashAd(adSlot,new TTAdNative.SplashAdListener() {
            @Override
            public void onError(int i, String s) {
                Log.i(TAG,"code:"+i+" message:"+s);
            }
            @Override
            public void onTimeout() {
                Log.i(TAG,"超时");
            }
            @Override
            public void onSplashAdLoad(TTSplashAd ttSplashAd) {
                Log.d(TAG, "开屏广告请求成功");
                long waitTime = limitTime - (new Date().getTime() - cTime);
                if(waitTime > 0){//是否超过限定时间  没有超时则继续
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            splashAd.setValue(ttSplashAd);
                        }
                    }, waitTime > 0 ? waitTime : 0);
                }
            }
        });
    }
}
app/src/main/java/com/runt/open/mvvm/ui/web/WebViewActivity.java
@@ -33,59 +33,64 @@
        initCompent();
    }
    @Override
    public void loadData() {
    }
    int count = 100;
    int index = 100;
    private void initCompent(){
        binding.browser.getSettings().setJavaScriptEnabled(true);
        binding.browser.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
        mBinding.browser.getSettings().setJavaScriptEnabled(true);
        mBinding.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(){
        mBinding.browser.loadUrl(url);
        mBinding.browser.setWebViewClient(new myWebViewClient());
        mBinding.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());
                MyLog.i("onProgressChanged","--binding.viewProgressbar:--"+ mBinding.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);
                    MyAnimations.hideAnimaInSitu(mBinding.linProgressbar);
                    MyAnimations.makeViewMove(mBinding.viewProgressbar.getTranslationX(),0,0,0, mBinding.viewProgressbar);
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            binding.linProgressbar.setVisibility(View.GONE);
                            mBinding.linProgressbar.setVisibility(View.GONE);
                        }
                    },MyAnimations.ANIMA_TIME);
                } else {
                    if (View.VISIBLE != binding.linProgressbar.getVisibility()) {
                        MyAnimations.showAnimaInSitu(binding.linProgressbar);
                    if (View.VISIBLE != mBinding.linProgressbar.getVisibility()) {
                        MyAnimations.showAnimaInSitu(mBinding.linProgressbar);
                        if(linProgressWidth==0){
                            final ViewTreeObserver vto = binding.linProgressbar.getViewTreeObserver();
                            final ViewTreeObserver vto = mBinding.linProgressbar.getViewTreeObserver();
                            vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                                public boolean onPreDraw() {
                                    linProgressWidth = binding.linProgressbar.getMeasuredWidth();
                                    binding.viewProgressbar.setTranslationX(0-linProgressWidth);
                                    binding.linProgressbar.getViewTreeObserver().removeOnPreDrawListener(this);
                                    linProgressWidth = mBinding.linProgressbar.getMeasuredWidth();
                                    mBinding.viewProgressbar.setTranslationX(0-linProgressWidth);
                                    mBinding.linProgressbar.getViewTreeObserver().removeOnPreDrawListener(this);
                                    return true;
                                }
                            });
                        }else{
                            binding.viewProgressbar.setTranslationX(0-linProgressWidth);
                            mBinding.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);
                        MyAnimations.makeViewMove(mBinding.viewProgressbar.getTranslationX(),0-linProgressWidth+linProgressWidth/100*newProgress,0,0, mBinding.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.makeViewMove(mBinding.viewProgressbar.getTranslationX(), mBinding.viewProgressbar.getTranslationX()+300,0,0, mBinding.viewProgressbar,MyAnimations.ANIMA_TIME*10);
                            }
                        },MyAnimations.ANIMA_TIME*3);
                    }
@@ -96,9 +101,9 @@
        });
        binding.browser.getSettings().setSavePassword(false);
        mBinding.browser.getSettings().setSavePassword(false);
        //Toast.makeText(mContext,"进入浏览器",Toast.LENGTH_SHORT).show();
        String Scale = String.valueOf(binding.browser.getScale());
        String Scale = String.valueOf(mBinding.browser.getScale());
        MyLog.i("Runt","--Scale:--"+Scale);
        int screenDensity=getResources().getDisplayMetrics().densityDpi;
        MyLog.i("Runt", "--screenDensity:--"+String.valueOf(screenDensity));  //60-160-240
@@ -147,12 +152,12 @@
    }
    private void hideProgressBar(){
        MyAnimations.hideAnimaInSitu(binding.linProgressbar);
        MyAnimations.makeViewMove(binding.viewProgressbar.getTranslationX(),0,0,0,binding.viewProgressbar);
        MyAnimations.hideAnimaInSitu(mBinding.linProgressbar);
        MyAnimations.makeViewMove(mBinding.viewProgressbar.getTranslationX(),0,0,0, mBinding.viewProgressbar);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                binding.linProgressbar.setVisibility(View.GONE);
                mBinding.linProgressbar.setVisibility(View.GONE);
            }
        },MyAnimations.ANIMA_TIME*2);
    }
app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java
@@ -371,4 +371,61 @@
        }
        return "";
    }
    /**
     * check the system is harmony os
     *
     * @return true if it is harmony os
     */
    public static boolean isHarmonyOS() {
        try {
            Class clz = Class.forName("com.huawei.system.BuildEx");
            Method method = clz.getMethod("getOsBrand");
            return "harmony".equals(method.invoke(clz));
        } catch (ClassNotFoundException e) {
            Log.e(TAG, "occured ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e(TAG, "occured NoSuchMethodException");
        } catch (Exception e) {
            Log.e(TAG, "occur other problem");
        }
        return false;
    }
    /**
     * 获取鸿蒙系统版本号
     */
    public static String getHarmonyOsVersion() {
        if (isHarmonyOS()) {
            try {
                Class cls = Class.forName("android.os.SystemProperties");
                Method method = cls.getMethod("get", String.class);
                return method.invoke(cls, "ro.huawei.build.display.id").toString();
                //android.os.Build.DISPLAY
            } catch ( Exception e) {
            }
        }
        return "-1";
    }
    /**
     * 获取属性
     * @param property
     * @param defaultValue
     * @return
     */
    private static String getProp(String property, String defaultValue) {
        try {
            Class spClz = Class.forName("android.os.SystemProperties");
            Method method = spClz.getDeclaredMethod("get", String.class);
            String value = (String) method.invoke(spClz, property);
            if (TextUtils.isEmpty(value)) {
                return defaultValue;
            }
            return value;
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return defaultValue;
    }
}
app/src/main/java/com/runt/open/mvvm/util/HandleDate.java
New file
@@ -0,0 +1,216 @@
package com.runt.open.mvvm.util;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2022/7/27.
 */
public class HandleDate {
    private static SimpleDateFormat secondsdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static SimpleDateFormat timesdf = new SimpleDateFormat("HH:mm:ss");
    private static SimpleDateFormat datesdf = new SimpleDateFormat("yyyy-MM-dd");
    private static SimpleDateFormat hoursdf = new SimpleDateFormat("yyyy-MM-dd HH");
    private static SimpleDateFormat minutesdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    /**
     * 获取当前时间的String类型
     */
    public static long getDateToLong() {
        return new Date().getTime();
    }
    /**
     * 获取指定时间的long类型
     */
    public static long getDateToLong(Date date) {
        return date.getTime();
    }
    /**
     * 获取指定时间的long类型
     *
     * @throws ParseException
     */
    public static long getDateToLong(String datestr) throws ParseException {
        return datesdf.parse(datestr).getTime();
    }
    /**
     * 将long类型的时间转换成只有日期的int类型
     */
    public static int getDateToInt(long datetime) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Date date = getLongToDate(datetime);
        String format = sdf.format(date);
        return Integer.parseInt(format);
    }
    /**
     * 将指定long类型的日期转换为date
     */
    public static Date getLongToDate(Long datetime) {
        return new Date(datetime);
    }
    /**
     * 将指定long类型的日期转换为string时间精确到秒
     */
    public static String getLongToSecond(Long datetime) {
        Date date = new Date(datetime);
        return secondsdf.format(date);
    }
    /**
     * 将指定long类型的日期转换为string时间 只显示 时分秒
     */
    public static String getLongToTime(Long datetime) {
        String datestr = "";
        Date date = null;
        try {
            date = new Date(datetime);
            datestr = timesdf.format(date);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return datestr;
    }
    /**
     * 将指定long类型的日期转换为string时间只显示日期
     */
    public static String getLongToDatestr(Long datetime) {
        String datestr = "";
        Date date = null;
        try {
            date = new Date(datetime);
            datestr = datesdf.format(date);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return datestr;
    }
    /**
     * 将指定long类型的日期转换为string时间精确到小时
     */
    public static String getLongToHour(Long datetime) {
        String datestr = "";
        Date date = null;
        try {
            date = new Date(datetime);
            datestr = hoursdf.format(date);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return datestr;
    }
    /**
     * 将指定long类型的日期转换为string日期
     */
    public static String getLongToSimpleDate(Long datetime) {
        String datestr = "";
        Date date = null;
        try {
            date = new Date(datetime);
            datestr = datesdf.format(date);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return datestr;
    }
    /**
     * 根据毫秒时间戳来格式化字符串 今天显示今天、昨天显示昨天、前天显示前天. 早于前天的显示具体年-月-日,如2017-06-12;
     *
     * @param timeStamp 毫秒值
     * @return 今天 昨天 前天 或者 yyyy-MM-dd HH:mm:ss类型字符串
     */
    public static String format(long timeStamp) {
        long curTimeMillis = System.currentTimeMillis();
        Date curDate = new Date(curTimeMillis);
        int todayHoursSeconds = curDate.getHours() * 60 * 60;
        int todayMinutesSeconds = curDate.getMinutes() * 60;
        int todaySeconds = curDate.getSeconds();
        int todayMillis = (todayHoursSeconds + todayMinutesSeconds + todaySeconds) * 1000;
        long todayStartMillis = curTimeMillis - todayMillis;
        if (timeStamp >= todayStartMillis) {
            return "今天";
        }
        int oneDayMillis = 24 * 60 * 60 * 1000;
        long yesterdayStartMilis = todayStartMillis - oneDayMillis;
        if (timeStamp >= yesterdayStartMilis) {
            return "昨天";
        }
        long yesterdayBeforeStartMilis = yesterdayStartMilis - oneDayMillis;
        if (timeStamp >= yesterdayBeforeStartMilis) {
            return "前天";
        }
        //            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(new Date(timeStamp));
    }
    /**
     * 根据时间戳来判断当前的时间是几天前,几分钟,刚刚
     *
     * @param long_time
     * @return
     */
    public static String getTimeStateNew(String long_time) {
        String long_by_13 = "1000000000000";
        String long_by_10 = "1000000000";
        if (Long.valueOf(long_time) / Long.valueOf(long_by_13) < 1) {
            if (Long.valueOf(long_time) / Long.valueOf(long_by_10) >= 1) {
                long_time = long_time + "000";
            }
        }
        return getTimeStateNew(long_time);
    }
    public static String getTimeStateNew(Date long_time) {
        return getTimeStateNew(long_time.getTime() );
    }
    public static String getTimeStateNew(Long long_time) {
        Timestamp time = new Timestamp(Long.valueOf(long_time));
        Timestamp now = new Timestamp(System.currentTimeMillis());
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        //         System.out.println("传递过来的时间:"+format.format(time));
        //         System.out.println("现在的时间:"+format.format(now));
        long day_conver = 1000 * 60 * 60 * 24;
        long hour_conver = 1000 * 60 * 60;
        long min_conver = 1000 * 60;
        long time_conver = now.getTime() - time.getTime();
        long temp_conver;
        //         System.out.println("天数:"+time_conver/day_conver);
        if ((time_conver / day_conver) < 3) {
            temp_conver = time_conver / day_conver;
            if (temp_conver <= 2 && temp_conver >= 1) {
                return temp_conver + "天前";
            } else {
                temp_conver = (time_conver / hour_conver);
                if (temp_conver >= 1) {
                    return temp_conver + "小时前";
                } else {
                    temp_conver = (time_conver / min_conver);
                    if (temp_conver >= 1) {
                        return temp_conver + "分钟前";
                    } else {
                        return "刚刚";
                    }
                }
            }
        } else {
            return format.format(time);
        }
    }
}
app/src/main/java/com/runt/open/mvvm/widgets/CircleImageView.java
New file
@@ -0,0 +1,245 @@
package com.runt.open.mvvm.widgets;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
import com.runt.open.mvvm.R;
/**
 * My father is Object, ites purpose of 圆形图片带阴影
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-2-23.
 */
@SuppressLint("AppCompatCustomView")
public class CircleImageView extends ImageView {
    private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
    private static final int COLORDRAWABLE_DIMENSION = 1;
    private static final int DEFAULT_BORDER_WIDTH = 0;
    private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
    private final RectF mDrawableRect = new RectF();
    private final RectF mBorderRect = new RectF();
    private final Matrix mShaderMatrix = new Matrix();
    private final Paint mBitmapPaint = new Paint();
    private final Paint mBorderPaint = new Paint();
    private int mBorderColor = DEFAULT_BORDER_COLOR;
    private int mBorderWidth = DEFAULT_BORDER_WIDTH;
    private Bitmap mBitmap;
    private BitmapShader mBitmapShader;
    private int mBitmapWidth;
    private int mBitmapHeight;
    private float mDrawableRadius;
    private float mBorderRadius;
    private boolean mReady;
    private boolean mSetupPending;
    public CircleImageView(Context context) {
        super(context);
    }
    public CircleImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        super.setScaleType(SCALE_TYPE);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
        mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
        mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
        a.recycle();
        mReady = true;
        if (mSetupPending) {
            setup();
            mSetupPending = false;
        }
    }
    @Override
    public ScaleType getScaleType() {
        return SCALE_TYPE;
    }
    @Override
    public void setScaleType(ScaleType scaleType) {
        if (scaleType != SCALE_TYPE) {
            throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
        }
    }
    @Override
    protected void onDraw(Canvas canvas) {
        if (getDrawable() == null) {
            return;
        }
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
        if(getBorderWidth()>0) {
            canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
        }
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        setup();
    }
    public int getBorderColor() {
        return mBorderColor;
    }
    public void setBorderColor(int borderColor) {
        if (borderColor == mBorderColor) {
            return;
        }
        mBorderColor = borderColor;
        mBorderPaint.setColor(mBorderColor);
        invalidate();
    }
    public int getBorderWidth() {
        return mBorderWidth;
    }
    public void setBorderWidth(int borderWidth) {
        if (borderWidth == mBorderWidth) {
            return;
        }
        mBorderWidth = borderWidth;
        setup();
    }
    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        mBitmap = bm;
        setup();
    }
    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        mBitmap = getBitmapFromDrawable(drawable);
        setup();
    }
    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        mBitmap = getBitmapFromDrawable(getDrawable());
        setup();
    }
    private Bitmap getBitmapFromDrawable(Drawable drawable) {
        if (drawable == null) {
            return null;
        }
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }
        try {
            Bitmap bitmap;
            if (drawable instanceof ColorDrawable) {
                bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
            } else {
                bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
            }
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return bitmap;
        } catch (OutOfMemoryError e) {
            return null;
        }
    }
    private void setup() {
        if (!mReady) {
            mSetupPending = true;
            return;
        }
        if (mBitmap == null) {
            return;
        }
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setShader(mBitmapShader);
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth);
        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();
        mBorderRect.set(0, 0, getWidth(), getHeight());
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
        mDrawableRect.set(mBorderWidth, mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth);
        mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
        updateShaderMatrix();
        invalidate();
    }
    private void updateShaderMatrix() {
        float scale;
        float dx = 0;
        float dy = 0;
        mShaderMatrix.set(null);
        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
            scale = mDrawableRect.height() / (float) mBitmapHeight;
            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
        } else {
            scale = mDrawableRect.width() / (float) mBitmapWidth;
            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
        }
        mShaderMatrix.setScale(scale, scale);
        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth, (int) (dy + 0.5f) + mBorderWidth);
        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }
}
app/src/main/java/com/runt/open/mvvm/widgets/CornerImageView.java
New file
@@ -0,0 +1,163 @@
package com.runt.open.mvvm.widgets;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
/**
 * My father is Object, ites purpose of  圆角
 *
 * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-6-18.
 */
public class CornerImageView extends AppCompatImageView {
    private int cornerSize = 30;
    private Paint paint;
    public CornerImageView(Context context) {
        super(context);
        paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setAntiAlias(true);//消除锯齿
    }
    public CornerImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setAntiAlias(true);//消除锯齿
    }
    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        drawLeftTop(canvas);
        drawRightTop(canvas);
        drawLeftBottom(canvas);
        drawRightBottom(canvas);
    }
    private void drawLeftTop(Canvas canvas) {
        Path path = new Path();
        path.moveTo(0, cornerSize);
        path.lineTo(0, 0);
        path.lineTo(cornerSize, 0);
        path.arcTo(new RectF(0, 0, cornerSize * 2, cornerSize * 2), -90, -90);
        path.close();
        canvas.drawPath(path, paint);
    }
    private void drawLeftBottom(Canvas canvas) {
        Path path = new Path();
        path.moveTo(0, getHeight() - cornerSize);
        path.lineTo(0, getHeight());
        path.lineTo(cornerSize, getHeight());
        path.arcTo(new RectF(0, // x
                getHeight() - cornerSize * 2,// y
                cornerSize * 2,// x
                getHeight()// getWidth()// y
        ), 90, 90);
        path.close();
        canvas.drawPath(path, paint);
    }
    private void drawRightBottom(Canvas canvas) {
        Path path = new Path();
        path.moveTo(getWidth() - cornerSize, getHeight());
        path.lineTo(getWidth(), getHeight());
        path.lineTo(getWidth(), getHeight() - cornerSize);
        RectF oval = new RectF(getWidth() - cornerSize * 2, getHeight()
                - cornerSize * 2, getWidth(), getHeight());
        path.arcTo(oval, 0, 90);
        path.close();
        canvas.drawPath(path, paint);
    }
    private void drawRightTop(Canvas canvas) {
        Path path = new Path();
        path.moveTo(getWidth(), cornerSize);
        path.lineTo(getWidth(), 0);
        path.lineTo(getWidth() - cornerSize, 0);
        path.arcTo(new RectF(getWidth() - cornerSize * 2, 0, getWidth(),
                0 + cornerSize * 2), -90, 90);
        path.close();
        canvas.drawPath(path, paint);
    }
    public int getCornerSize() {
        return cornerSize;
    }
    public void setCornerSize(int cornerSize) {
        this.cornerSize = cornerSize;
    }
}
app/src/main/java/com/runt/open/mvvm/widgets/TitleBarView.java
@@ -67,6 +67,14 @@
        rightTextSize = array.getDimension(R.styleable.TitleBarView_rightTextSize, DimensionUtils.convertSpToPixel(getContext(),14));
        rightPadding = array.getDimension(R.styleable.TitleBarView_rightDrawablePadding,10);
        int leftTint = array.getColor(R.styleable.TitleBarView_leftTint,-1);
        if(leftTint != -1) {
            setTint(leftDra,leftTint);
        }
        int rightTint = array.getColor(R.styleable.TitleBarView_rightTint,-1);
        if(rightTint != -1) {
            setTint(rightDra,rightTint);
        }
        textPaint = new Paint();
        textPaint.setAntiAlias(true); // 是否抗锯齿
        //mTextPaint.setAlpha(50); // 设置alpha不透明度,范围为0~255
@@ -253,4 +261,10 @@
        float height2 = fm.bottom - fm.top + fm.leading;//行高
        return (int) height2;
    }
    private void setTint(Drawable drawable, @ColorInt int color){
        if(drawable!= null){
            drawable.setTint(color);
        }
    }
}
app/src/main/res/layout/activity_main.xml
@@ -2,19 +2,25 @@
<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:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <com.runt.open.mvvm.widgets.TitleBarView
        android:id="@+id/title_bar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/white"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />
        style="@style/titlebar"
        tools:ignore="MissingConstraints"
        app:titleText="标题" />
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager_2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/title_bar"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toTopOf="@id/nav_view" />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
@@ -27,17 +33,5 @@
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu"/>
    <fragment
        android:id="@+id/nav_host_fragment_activity_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation"
    />
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/layout/activity_setting.xml
New file
@@ -0,0 +1,79 @@
<?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:background="@color/white"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <com.runt.open.mvvm.widgets.TitleBarView
        android:id="@+id/title_bar"
        style="@style/titlebar"
        tools:ignore="MissingConstraints"
        app:titleText="设置"
        app:leftDrawable="@mipmap/icon_white_back"
        app:leftTint="@color/black"/>
    <TextView
        android:id="@+id/version"
        app:layout_constraintTop_toBottomOf="@id/title_bar"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:background="@drawable/bg_white"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="left"
        android:text="@string/str_version"
        android:textColor="@color/txt_color"
        android:textSize="@dimen/title_size"
        android:padding="15dp" />
    <TextView
        android:id="@+id/about"
        app:layout_constraintTop_toBottomOf="@id/version"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:background="@drawable/bg_white"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawableRight="@mipmap/arrow_right"
        android:gravity="left"
        android:text="关于我们"
        android:textColor="@color/txt_color"
        android:textSize="@dimen/title_size"
        android:padding="15dp" />
    <View
        app:layout_constraintTop_toBottomOf="@id/title_bar"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/color_gray8" />
    <View
        app:layout_constraintTop_toBottomOf="@id/version"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/color_gray8" />
    <View
        app:layout_constraintTop_toBottomOf="@id/about"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/color_gray8" />
    <View
        app:layout_constraintBottom_toTopOf="@id/logout"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/color_gray8" />
    <TextView
        android:id="@+id/logout"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:background="@drawable/bg_white"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/logout"
        android:textColor="@color/txt_color"
        android:textSize="@dimen/title_size"
        android:padding="15dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/layout/activity_splash.xml
@@ -13,9 +13,5 @@
        android:scaleType="centerCrop"
        />
    <FrameLayout
        android:id="@+id/splash_ad_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"   />
</RelativeLayout>
app/src/main/res/layout/activity_web.xml
@@ -1,15 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    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" />
        style="@style/titlebar"
        tools:ignore="MissingConstraints" />
    <LinearLayout
        android:id="@+id/lin_progressbar"
app/src/main/res/layout/fragment_dashboard.xml
File was deleted
app/src/main/res/layout/fragment_home.xml
File was deleted
app/src/main/res/layout/fragment_mine.xml
New file
@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@color/white"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:ignore="MissingDefaultResource">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <View
                android:layout_width="match_parent"
                android:layout_height="0.5dp"
                android:background="@color/color_gray8"  />
            <LinearLayout
                android:id="@+id/lin_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:background="@drawable/bg_white" >
                <ImageView
                    android:id="@+id/img_top_bg"
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:scaleType="centerCrop"
                    android:src="@mipmap/user_back"/>
                <com.runt.open.mvvm.widgets.CircleImageView
                    android:id="@+id/img"
                    android:layout_width="100dp"
                    android:layout_height="100dp"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginTop="-50dp"
                    android:scaleType="centerCrop"
                    android:src="@mipmap/default_head"  />
                <TextView
                    android:id="@+id/txt_name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginTop="10dp"
                    android:text="名称"
                    android:textSize="20sp"
                    android:textColor="@color/txt_normal"/>
                <TextView
                    android:id="@+id/txt_user_sign"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginTop="10dp"
                    android:paddingLeft="15dp"
                    android:paddingRight="15dp"
                    android:gravity="center"
                    android:text="签名"
                    android:textSize="16sp"
                    android:visibility="gone"
                    android:drawablePadding="20dp"
                    android:textColor="@color/txt_enable"/>
                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="15dp"
                    android:visibility="gone"
                    android:orientation="horizontal">
                    <TextView
                        style="@style/user_txt_left"
                        android:drawableLeft="@mipmap/location_e"
                        android:text="所在地:"/>
                    <TextView
                        android:id="@+id/txt_address"
                        style="@style/user_txt_value"
                        android:text="北京"/>
                </LinearLayout>
                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="15dp"
                    android:visibility="gone"
                    android:orientation="horizontal">
                    <TextView
                        style="@style/user_txt_left"
                        android:drawableLeft="@mipmap/birth"
                        android:text="年龄:"/>
                    <TextView
                        android:id="@+id/txt_age"
                        style="@style/user_txt_value"
                        android:text="11"/>
                </LinearLayout>
                <LinearLayout
                    android:id="@+id/lin_group"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@drawable/white_corner_border"
                    android:layout_margin="@dimen/default_margin_lr"
                    android:elevation="5dp">
                    <LinearLayout
                        android:id="@+id/lin_coin"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="vertical"
                        android:paddingTop="20dp"
                        android:paddingBottom="20dp"
                        android:gravity="center"
                        android:layout_weight="1">
                        <TextView
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:text="我的金币"
                            android:textSize="18sp"
                            android:drawableLeft="@mipmap/icon_coin"
                            android:drawablePadding="10dp"
                            android:textColor="@color/txt_normal"/>
                        <TextView
                            android:id="@+id/txt_coin"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_marginTop="10dp"
                            android:textSize="25sp"
                            android:text="0"
                            android:textColor="@color/txt_normal"/>
                    </LinearLayout>
                    <LinearLayout
                        android:id="@+id/lin_sign"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="vertical"
                        android:paddingTop="20dp"
                        android:paddingBottom="20dp"
                        android:gravity="center"
                        android:layout_weight="1">
                        <TextView
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:text="连续签到"
                            android:textSize="18sp"
                            android:drawableLeft="@mipmap/sign"
                            android:drawablePadding="10dp"
                            android:textColor="@color/txt_normal"/>
                        <TextView
                            android:id="@+id/txt_signs"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_marginTop="10dp"
                            android:textSize="25sp"
                            android:text="0"
                            android:textColor="@color/txt_normal"/>
                    </LinearLayout>
                </LinearLayout>
            </LinearLayout>
            <View
                android:layout_width="match_parent"
                android:layout_height="0.5dp"
                android:background="@color/color_gray8" />
        </LinearLayout>
    </ScrollView>
</LinearLayout>
app/src/main/res/layout/fragment_notifications.xml
File was deleted
app/src/main/res/layout/fragment_service.xml
New file
@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@color/white"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:ignore="MissingDefaultResource">
    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/cut_off_line" />
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingTop="20dp"
            android:orientation="vertical">
            <RelativeLayout
                android:id="@+id/lin_uav"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="1dp"
                android:layout_margin="10dp"
                android:gravity="center_vertical">
                <com.runt.open.mvvm.widgets.CornerImageView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:adjustViewBounds="true"
                    android:src="@mipmap/bg_uav" />
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/white"
                    android:textSize="20sp"
                    android:textStyle="bold"
                    android:layout_marginTop="15dp"
                    android:layout_marginLeft="15dp"
                    android:text="无人机喷洒农药/化肥" />
            </RelativeLayout>
            <RelativeLayout
                android:id="@+id/lin_yumi"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="1dp"
                android:layout_margin="10dp"
                android:gravity="center_vertical">
                <com.runt.open.mvvm.widgets.CornerImageView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:adjustViewBounds="true"
                    android:src="@mipmap/bg_yumi" />
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/white"
                    android:textSize="20sp"
                    android:textStyle="bold"
                    android:layout_marginTop="15dp"
                    android:layout_marginLeft="15dp"
                    android:text="玉米收割" />
            </RelativeLayout>
            <RelativeLayout
                android:id="@+id/lin_bozhong"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="1dp"
                android:layout_margin="10dp"
                android:gravity="center_vertical">
                <com.runt.open.mvvm.widgets.CornerImageView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:adjustViewBounds="true"
                    android:src="@mipmap/bg_bozhong" />
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/white"
                    android:textSize="20sp"
                    android:textStyle="bold"
                    android:layout_marginTop="15dp"
                    android:layout_marginLeft="15dp"
                    android:text="播种/施肥" />
            </RelativeLayout>
            <RelativeLayout
                android:id="@+id/lin_xiaomai"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="1dp"
                android:layout_margin="10dp"
                android:gravity="center_vertical">
                <com.runt.open.mvvm.widgets.CornerImageView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:adjustViewBounds="true"
                    android:src="@mipmap/bg_xiaomai" />
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/white"
                    android:textSize="20sp"
                    android:textStyle="bold"
                    android:layout_marginTop="15dp"
                    android:layout_marginLeft="15dp"
                    android:text="小麦收割" />
            </RelativeLayout>
        </LinearLayout>
    </ScrollView>
</LinearLayout>
app/src/main/res/layout/item_msg.xml
New file
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="vertical" >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="20dp"
        android:paddingBottom="20dp"
        android:paddingLeft="@dimen/default_margin_lr"
        android:paddingRight="@dimen/default_margin_lr"
        android:background="@drawable/bg_white"
        android:gravity="center"
        android:orientation="vertical" >
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="bottom" >
            <TextView
                android:id="@+id/txt_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:textStyle="bold"
                android:textSize="@dimen/title_size"
                android:text="挖鼻通知" />
            <TextView
                android:id="@+id/txt_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:alpha="0.6"
                android:text="02-11" />
        </LinearLayout>
        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/txt_detail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:alpha="0.6"
            android:text="l;ajs;ldjf;ljas;ldfjasdfasdfasdfasdf很垃圾乐山大佛了计算的;了时代峻峰拉三等奖了可接受的来精神的分了就l;asjdflkjl;asjdf;lkajsdf"
            android:singleLine="true"/>
    </LinearLayout>
    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/cut_off_line" />
    <LinearLayout
        android:id="@+id/lin_ad"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" ></LinearLayout>
</LinearLayout>
app/src/main/res/layout/refresh_recycler.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<com.scwang.smart.refresh.layout.SmartRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/smart_refresh"
    android:id="@+id/refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
app/src/main/res/navigation/mobile_navigation.xml
File was deleted
app/src/main/res/values/styles.xml
@@ -11,6 +11,8 @@
        <attr name="rightText" format="string" />
        <attr name="rightTextSize" format="dimension" />
        <attr name="rightTextColor" format="color" />
        <attr name="leftTint" format="color" />
        <attr name="rightTint" format="color" />
    </declare-styleable>
@@ -77,6 +79,31 @@
    </style>
    <style name="user_txt_left">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:textSize">18sp</item>
        <item name="android:textColor">@color/txt_enable</item>
        <item name="android:drawablePadding">10dp</item>
        <item name="android:layout_marginRight">10dp</item>
    </style>
    <style name="tip_txt_style">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">@dimen/title_height</item>
        <item name="android:onClick">onTypeClick</item>
        <item name="android:textSize">16sp</item>
        <item name="android:textColor">@color/txt_normal</item>
        <item name="android:drawableRight">@drawable/check_selector</item>
        <item name="android:gravity">center_vertical</item>
    </style>
    <style name="user_txt_value">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:textSize">18sp</item>
        <item name="android:textColor">@color/txt_normal</item>
    </style>
    <style name="btn_normal">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">wrap_content</item>