From c56260d3be75e059c4525df802e4f17bcfb0b630 Mon Sep 17 00:00:00 2001 From: Administrator <123> Date: Mon, 15 Nov 2021 03:01:19 +0000 Subject: [PATCH] 工具类、网络请求框架、mvvm框架 --- app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java | 326 +++ app/src/main/java/com/runt/open/mvvm/MyApplication.java | 136 + app/src/main/res/menu/bottom_nav_menu.xml | 19 app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsFragment.java | 44 app/src/main/java/com/runt/open/mvvm/data/BasePageResult.java | 21 app/src/main/java/com/runt/open/mvvm/retrofit/utils/RetrofitUtils.java | 127 + app/src/main/java/com/runt/open/mvvm/base/activities/BaseTitleBarActivity.java | 60 app/src/main/java/com/runt/open/mvvm/base/model/ViewModelFactory.java | 33 app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java | 31 app/src/main/java/com/runt/open/mvvm/util/GsonUtils.java | 256 ++ app/src/main/res/drawable/ic_home_black_24dp.xml | 9 app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonResponseBodyConverter.java | 95 gradlew.bat | 89 app/src/main/java/com/runt/open/mvvm/widgets/ClearEditText.java | 194 + app/src/main/res/layout/activity_main.xml | 35 app/src/main/res/mipmap-xxhdpi/icon_delete.png | 0 app/build.gradle | 66 app/src/main/res/values/dimens.xml | 9 app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsViewModel.java | 19 gradlew | 185 + app/src/main/res/values/strings.xml | 5 app/src/main/java/com/runt/open/mvvm/ui/home/HomeFragment.java | 44 app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java | 374 +++ app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java | 166 + app/src/main/java/com/runt/open/mvvm/util/MyLog.java | 42 app/src/main/java/com/runt/open/mvvm/util/SpUtils.java | 476 ++++ app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml | 5 app/src/main/res/mipmap-xhdpi/ic_launcher.webp | 0 app/src/main/res/layout/fragment_notifications.xml | 23 app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkListenear.java | 173 + app/src/main/res/drawable/ic_notifications_black_24dp.xml | 9 app/src/main/java/com/runt/open/mvvm/util/NetWorkUtils.java | 212 ++ app/src/main/res/mipmap-hdpi/ic_launcher.webp | 0 app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkCost.java | 14 app/src/main/java/com/runt/open/mvvm/widgets/QuadrateLinearLayout.java | 40 app/src/main/res/layout/layout_null.xml | 18 app/src/main/res/mipmap-mdpi/ic_launcher.webp | 0 app/src/main/java/com/runt/open/mvvm/retrofit/AndroidScheduler.java | 39 app/proguard-rules.pro | 21 app/src/main/java/com/runt/open/mvvm/retrofit/utils/HttpPrintUtils.java | 210 ++ .gitignore | 41 app/src/main/res/values/styles.xml | 15 app/src/main/res/values/themes.xml | 16 gradle/wrapper/gradle-wrapper.jar | 0 app/src/main/java/com/runt/open/mvvm/base/fragments/BaseLoadPageFragment.java | 105 + app/src/main/java/com/runt/open/mvvm/widgets/TitleBarView.java | 256 ++ app/src/main/res/layout/refresh_recycler.xml | 14 app/src/main/res/drawable/ic_launcher_background.xml | 170 + app/src/main/res/xml/network_security_config.xml | 4 gradle/wrapper/gradle-wrapper.properties | 6 app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonConverterFactory.java | 58 app/src/main/java/com/runt/open/mvvm/base/fragments/BaseFragment.java | 66 app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java | 23 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp | 0 app/src/main/res/values/colors.xml | 19 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp | 0 app/src/main/java/com/runt/open/mvvm/retrofit/utils/RSAUtils.java | 178 + app/src/main/res/drawable/ic_dashboard_black_24dp.xml | 9 app/src/main/java/com/runt/open/mvvm/util/DimensionUtils.java | 51 app/src/main/res/layout/fragment_dashboard.xml | 23 build.gradle | 17 app/src/main/java/com/runt/open/mvvm/retrofit/converter/GsonRequestBodyConverter.java | 44 app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/EncryptInterceptor.java | 104 + app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java | 280 ++ app/src/main/res/drawable-v24/ic_launcher_foreground.xml | 30 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp | 0 app/.gitignore | 3 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp | 0 settings.gradle | 10 app/src/main/java/com/runt/open/mvvm/data/ApkUpGradeResult.java | 29 app/src/main/res/navigation/mobile_navigation.xml | 25 app/src/main/java/com/runt/open/mvvm/base/activities/BaseLoadPageActivity.java | 96 app/src/main/java/com/runt/open/mvvm/MainActivity.java | 30 app/src/main/AndroidManifest.xml | 24 app/src/main/java/com/runt/open/mvvm/base/adapter/FragmentAdapter.java | 51 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp | 0 app/src/main/java/com/runt/open/mvvm/base/model/ItemViewModel.java | 15 app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java | 69 app/src/main/res/layout/fragment_home.xml | 23 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml | 5 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp | 0 gradle.properties | 19 app/src/main/java/com/runt/open/mvvm/base/model/BaseLoadPageViewModel.java | 13 app/src/main/java/com/runt/open/mvvm/base/activities/BaseTabActivity.java | 78 app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardViewModel.java | 19 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp | 0 app/src/main/java/com/runt/open/mvvm/ui/home/HomeViewModel.java | 19 app/src/main/java/com/runt/open/mvvm/base/fragments/BaseTabFragment.java | 74 app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java | 66 app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardFragment.java | 44 90 files changed, 5,841 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 3a36c6e..5885f03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,17 @@ -# Built application files -*.apk -*.ap_ - -# Files for the Dalvik VM -*.dex - -# Java class files -*.class - -# Generated files -bin/ -gen/ - -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx local.properties - -# Proguard folder generated by Eclipse -proguard/ - -#Log Files -*.log +/.idea/ +/.idea/ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..423e3dc --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,3 @@ +/build +/src/androidTest/ +/src/test/ diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..1789858 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,66 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 31 + + defaultConfig { + applicationId "com.runt.open.mvvm" + minSdk 23 + targetSdk 31 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + debug{ + minifyEnabled false + buildConfigField 'String','HOST_IP_ADDR','"http://192.168.100.82:8080/"' + buildConfigField 'String','ENVIRONMENT','"release"' + resValue "string", "app_name", "MVVM开源项目测试" + 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开源项目" + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.3.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' + implementation 'androidx.navigation:navigation-fragment:2.3.5' + implementation 'androidx.navigation:navigation-ui:2.3.5' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + //// 注意:分包之后不会有默认的Header和Footer需要手动添加!还是原来的三种方法! + implementation 'com.scwang.smart:refresh-layout-kernel:2.0.1' //核心必须依赖 + implementation 'com.scwang.smart:refresh-header-classics:2.0.1' //经典刷新头 + implementation 'com.github.d-max:spots-dialog:1.1@aar'//loading view + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' //RXjava和retrofit结合 + implementation 'com.permissionx.guolindev:permissionx:1.2.2' //权限依赖让你推广你就发群里?没有别的群了? + implementation 'com.github.bumptech.glide:glide:4.12.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..58dc725 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.runt.open.mvvm" > + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.OpemMvvm" > + <activity + android:name=".MainActivity" + android:exported="true" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest> \ No newline at end of file diff --git a/app/src/main/java/com/runt/open/mvvm/MainActivity.java b/app/src/main/java/com/runt/open/mvvm/MainActivity.java new file mode 100644 index 0000000..b180224 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/MainActivity.java @@ -0,0 +1,30 @@ +package com.runt.open.mvvm; + +import android.os.Bundle; + +import com.google.android.material.bottomnavigation.BottomNavigationView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; +import androidx.navigation.ui.AppBarConfiguration; +import androidx.navigation.ui.NavigationUI; + +import com.runt.open.mvvm.databinding.ActivityMainBinding; + +public class MainActivity extends AppCompatActivity { + + private ActivityMainBinding binding; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main); + NavigationUI.setupWithNavController(binding.navView, navController); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/runt/open/mvvm/MyApplication.java b/app/src/main/java/com/runt/open/mvvm/MyApplication.java new file mode 100644 index 0000000..8ed880c --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/MyApplication.java @@ -0,0 +1,136 @@ +package com.runt.open.mvvm; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.runt.open.mvvm.util.MyLog; +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.RefreshFooter; +import com.scwang.smart.refresh.layout.api.RefreshHeader; +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 java.util.ArrayList; +import java.util.List; + +/** + * Created by Administrator on 2021/10/28 0028. + */ +public class MyApplication extends Application { + + final String TAG = "MyApplication"; + List<Activity> activities = new ArrayList<>(); + private Activity currentActivity;//当前activity + private boolean isInfront; //是否前台运行 + private static MyApplication application; + public static MyApplication getApplication() { + return application; + } + + public Activity getCurrentActivity() { + return currentActivity; + } + + @Override + public void onCreate() { + super.onCreate(); + application = this; + //CrashReport.initCrashReport(getApplicationContext(), "8d88679ae9", false);//注册bugly + //设置全局的Header构建器 + SmartRefreshLayout.setDefaultRefreshHeaderCreator(new DefaultRefreshHeaderCreator() { + @Override + public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) { + //layout.setPrimaryColorsId(R.color.colorPrimary, android.R.color.white);//全局设置主题颜色 + return new ClassicsHeader(context);//.setTimeFormat(new DynamicTimeFormat("更新于 %s"));//指定为经典Header,默认是 贝塞尔雷达Header + } + }); + //设置全局的Footer构建器 + SmartRefreshLayout.setDefaultRefreshFooterCreator(new DefaultRefreshFooterCreator() { + @Override + public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) { + //指定为经典Footer,默认是 BallPulseFooter + return new ClassicsFooter(context).setDrawableSize(20); + } + }); + registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { + MyLog.d(TAG,"onActivityCreated "+activity.getClass().getSimpleName()); + if(!activities.contains(activity)){ + activities.add(activity); + } + } + + @Override + public void onActivityStarted(@NonNull Activity activity) { + MyLog.d(TAG,"onActivityStarted "+activity.getClass().getSimpleName()); + currentActivity = activity; + } + + @Override + public void onActivityResumed(@NonNull Activity activity) { + MyLog.d(TAG,"onActivityResumed "+activity.getClass().getSimpleName()); + currentActivity = activity; + isInfront = true; + } + + @Override + public void onActivityPaused(@NonNull Activity activity) { + MyLog.d(TAG,"onActivityPaused "+activity.getClass().getSimpleName()); + + } + + @Override + public void onActivityStopped(@NonNull Activity activity) { + MyLog.d(TAG,"onActivityStopped "+activity.getClass().getSimpleName()); + + } + + @Override + public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) { + MyLog.d(TAG,"onActivitySaveInstanceState "+activity.getClass().getSimpleName()); + isInfront = false; + + } + + @Override + public void onActivityDestroyed(@NonNull Activity activity) { + MyLog.d(TAG,"onActivityDestroyed "+activity.getClass().getSimpleName()); + if(activities.contains(activity)){ + activities.remove(activity); + } + } + }); + } + + /** + * 退出程序 + */ + public void quitApp(){ + for(Activity activity:activities){ + activity.finish(); + } + System.exit(0); + } + + public void clearActivities(){ + for(Activity activity:activities){ + /*if(activity instanceof LoginActivity){ + continue; + }*/ + activity.finish(); + } + } + + public boolean isInfront(){ + return isInfront; + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java new file mode 100644 index 0000000..355a46d --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java @@ -0,0 +1,326 @@ +package com.runt.open.mvvm.base.activities; + +import android.Manifest; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.provider.Settings; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.annotation.ColorRes; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.FileProvider; +import androidx.lifecycle.ViewModelProvider; +import androidx.viewbinding.ViewBinding; + +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.data.ApkUpGradeResult; +import com.runt.open.mvvm.util.MyLog; +import com.runt.open.mvvm.util.SpUtils; +import com.permissionx.guolindev.PermissionX; +import com.permissionx.guolindev.callback.ExplainReasonCallbackWithBeforeParam; +import com.permissionx.guolindev.callback.ForwardToSettingsCallback; +import com.permissionx.guolindev.callback.RequestCallback; +import com.permissionx.guolindev.request.ExplainScope; +import com.permissionx.guolindev.request.ForwardScope; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.Date; +import java.util.List; + +import dmax.dialog.SpotsDialog; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +/** + * activity 封装 + * Created by Administrator on 2021/10/27 0027. + */ +public abstract class BaseActivity<B extends ViewBinding,VM extends BaseViewModel> extends AppCompatActivity { + + protected B binding; + protected VM viewModel; + 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}; + + public static final int RESULT_LOGIN = 100,RESULT_LOGIN_RECREATE = 103,RESULT_BIND = 101,RESULT_SENDEDFILES = 105,RESULT_DISSCONNECT = 104, + RESULT_UPDATEUSER = 115,RESULT_LOGOUT = 113, REQUEST_CODE_ACTIVITY = 333; + 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_PAYPASS = 20112,/*支付验证*/ + REQUEST_CODE_PAYPASS_FOR_ALIPAY = 20110,/*支付宝修改支付验证*/ + REQUEST_CODE_PAYPASS_FOR_REALNAME = 20111,/*支付宝修改支付验证*/ + REQUEST_CODE_PERMISSION = 20013,/*请求权限*/ + REQUEST_VERSION_PERMISSION = 20013,/*检查更新*/ + REQUEST_INSTALL_PERMISSION = 20014,/*请求安装权限*/ + REQUEST_CODE_MANAGE_GROUP = 20015,/*选择分组*/ + REQUEST_CODE_MANAGE_DEL = 20016,/*选择删除*/ + REQUEST_CODE_MANAGE_EDIT = 20017,/*选择编辑*/ + REQUEST_CODE_MANAGE_ADD = 20018,/*选择添加*/ + REQUEST_CODE_MANAGE_DRAFT = 20019,/*选择草稿编辑*/ + REQUEST_CODE_WEPAY = 20020,/*微信支付*/ + REQUEST_CODE_ALIPAY = 20021,/*支付宝*/ + REQUEST_CODE_PINKAGE = 20022,/*包邮设置*/ + REQUEST_INSTALL_APK = 20200,/*请求安装权限*/ + REQUEST_CODE_COMMENT_REFRESH = 20222,/*请求安装权限*/ + RESULT_CODE_DRAFT = 4041/*草稿*/, + RESULT_CODE_UPDATED = 4042/*更新*/, + RESULT_CODE_FAILD = 4044/*失败*/, + RESULT_CODE_SUCESS = 4046/*成功*/, + RESULT_CODE_CANCEL = 4043/*取消*/; + protected Context mContext; + + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // get genericity "B" + setStatusBarBgColor(R.color.white); + setStatusBarTextColor(true); + final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass(); + try { + Class<B> entityClass = (Class<B>) 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; + } catch (Exception e) { + e.printStackTrace(); + } + Class<VM> vmClass = (Class<VM>) type.getActualTypeArguments()[1]; + viewModel = new ViewModelProvider(this,getViewModelFactory()).get(vmClass); + setContentView(binding.getRoot()); + mContext = this; + try { + //设置坚屏 一定要放到try catch里面,否则会崩溃 + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } catch (Exception e) { + } + TAG = this.getClass().getSimpleName(); + initViews(); + } + + public abstract void initViews(); + + AlertDialog loadingDialog; + /** + * 显示加载弹框 + * @param msg + */ + public void showLoadingDialog(String msg){ + if(loadingDialog != null && loadingDialog.isShowing()){ + loadingDialog.dismiss(); + } + loadingDialog = new SpotsDialog.Builder().setContext(mContext).build(); + loadingDialog.setMessage(msg); + loadingDialog.setCancelable(false); + loadingDialog.show(); + } + + /** + * 清除弹框 + */ + public void dissmissLoadingDialog(){ + if(loadingDialog != null && loadingDialog.isShowing()){ + loadingDialog.dismiss(); + } + } + + public ViewModelProvider.Factory getViewModelFactory(){ + return ViewModelFactory.getInstance(); + } + + public void setStatusBarTransparent(boolean isBlack){ + //透明状态栏 + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + //透明导航栏 + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + } + + /** + * 修改状态栏颜色,支持4.4以上版本 + * @param colorId + */ + public void setStatusBarBgColor(@ColorRes int colorId) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + getWindow().setStatusBarColor(getResources().getColor(colorId)); + } + + /** + * 修改状态栏文本颜色 + * @param isBlack + */ + public void setStatusBarTextColor(boolean isBlack){ + View decor = getWindow().getDecorView(); + if (isBlack) { + decor.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } else { + decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + } + } + + /** + * 隐藏虚拟按键 + */ + public void hideBottomUIMenu() { + //隐藏虚拟按键 + if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api + View v = getWindow().getDecorView(); + v.setSystemUiVisibility(View.GONE); + } else if (Build.VERSION.SDK_INT >= 19) { + //for new api versions. + View decorView = getWindow().getDecorView(); + int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ; + decorView.setSystemUiVisibility(uiOptions); + } + + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { //把操作放在用户点击的时候 + View v = getCurrentFocus(); //得到当前页面的焦点,ps:有输入框的页面焦点一般会被输入框占据 + if (isShouldHideKeyboard(v, ev)) { //判断用户点击的是否是输入框以外的区域 + hideSoftKeyboard (); //收起键盘 + } + } + return super.dispatchTouchEvent(ev); + } + + /** + * 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘,因为当用户点击EditText时则不能隐藏 + * + * @param v + * @param event + * @return + */ + private boolean isShouldHideKeyboard(View v, MotionEvent event) { + if (v != null && (v instanceof EditText)) { //判断得到的焦点控件是否包含EditText + int[] l = {0, 0}; + v.getLocationInWindow(l); + int left = l[0], //得到输入框在屏幕中上下左右的位置 + top = l[1], + bottom = top + v.getHeight(), + right = left + v.getWidth(); + if (event.getX() > left && event.getX() < right + && event.getY() > top && event.getY() < bottom) { + // 点击位置如果是EditText的区域,忽略它,不收起键盘。 + return false; + } else { + return true; + } + } + // 如果焦点不是EditText则忽略 + return false; + } + /** + * 判断软键盘输入法是否弹出 + */ + public boolean isKeyboardVisbility(Context context, View v) { + InputMethodManager imm = (InputMethodManager) context.getSystemService(context.INPUT_METHOD_SERVICE); + if (imm.hideSoftInputFromWindow(v.getWindowToken(), 0)) { + imm.showSoftInput(v, 0); + return true;//键盘显示中 + } else { + return false;//键盘未显示 + } + } + protected void hideSoftKeyboard() { + if (getWindow().getAttributes().softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) { + if (getCurrentFocus() != null) + ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), + InputMethodManager.HIDE_NOT_ALWAYS); + } + } + /** + * 状态栏高度 + * @return + */ + public int getStatusBarHeight() { + int result = 0; + int resId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resId > 0) { + result = getResources().getDimensionPixelOffset(resId); + } + return result; + } + + + long mExitTime= 0 ; + /** + * 返回键退出程序 + */ + public void backExit() { + if ((System.currentTimeMillis() - mExitTime) > 2000) { + Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show(); + mExitTime = System.currentTimeMillis(); + } else { + //此方法导致app关闭后重启 + MyApplication.getApplication().quitApp(); + System.exit(0); + //quitApp(); + } + } + public void showToast(String message){ + Toast.makeText(this,message,Toast.LENGTH_SHORT).show(); + } + + public void showToast(@StringRes int msg){ + showToast(getString(msg)); + } + + /** + * 获取文件保存路径 sdcard根目录/download/文件名称 + * @param fileUrl + * @return + */ + public static String getSaveFilePath(String fileUrl){ + String fileName=fileUrl.substring(fileUrl.lastIndexOf("/")+1,fileUrl.length());//获取文件名称 + String storePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "Download"; + File appDir = new File(storePath); + if (!appDir.exists()) { + appDir.mkdirs(); + } + return storePath + File.separator +fileName; + } + + + protected boolean onBackKeyDown() { + onBackPressed(); + return false; + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/activities/BaseLoadPageActivity.java b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseLoadPageActivity.java new file mode 100644 index 0000000..9e2695a --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseLoadPageActivity.java @@ -0,0 +1,96 @@ +package com.runt.open.mvvm.base.activities; + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.runt.open.mvvm.base.adapter.BaseAdapter; +import com.runt.open.mvvm.base.model.BaseLoadPageViewModel; +import com.runt.open.mvvm.data.BasePageResult; +import com.runt.open.mvvm.databinding.RefreshRecyclerBinding; +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.listener.OnRefreshLoadMoreListener; + +import java.lang.reflect.ParameterizedType; +import java.util.HashMap; + +/** + * Created by Administrator on 2021/11/4 0004. + */ +public abstract class BaseLoadPageActivity<B extends ViewBinding,VM extends BaseLoadPageViewModel,A extends BaseAdapter,D extends BasePageResult> + extends BaseTitleBarActivity<B,VM> implements OnRefreshLoadMoreListener { + + protected SmartRefreshLayout smartRefresh; + protected RecyclerView recycler; + final String TAG = "RecyclerFragment"; + protected A adapter;//适配器 + protected String url;//请求地址 + protected HashMap param;//参数 + protected int page=1; + protected final int SIZE = 10; + protected boolean isRefresh = false;//是否正在刷新 + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + try { + Class<A> entityClass = (Class<A>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2]; + this.adapter = entityClass.newInstance();//实例化泛型 + String smartStr = "smartRefresh"; + smartRefresh = (SmartRefreshLayout) binding.getClass().getField(smartStr).get(binding); + recycler = (RecyclerView) binding.getClass().getDeclaredField("recycler").get(binding); + } catch (NoSuchFieldException e) { + try { + RefreshRecyclerBinding includeRefreshRecycler = (RefreshRecyclerBinding) binding.getClass().getDeclaredField ("includeRefreshRecycler").get(binding); + smartRefresh = includeRefreshRecycler.smartRefresh; + recycler = includeRefreshRecycler.recycler; + } catch (Exception e2) { + e2.printStackTrace(); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } + smartRefresh.setRefreshHeader(new ClassicsHeader(mContext)); + smartRefresh.setRefreshFooter(new ClassicsFooter(mContext)); + smartRefresh.setOnRefreshLoadMoreListener(this); + recycler.setLayoutManager(new LinearLayoutManager(mContext)); + recycler.setAdapter(adapter); + } + + private void finishFreshLoadmore(D result){ + if(result.code == 200){ + + smartRefresh.setEnableRefresh(true); + smartRefresh.finishRefresh(); + if(page == 1){ + adapter.getData().clear(); + adapter.setData(result.rows); + }else{ + adapter.getData().addAll(result.rows); + adapter.notifyDataSetChanged(); + } + if(result.total <= adapter.getData().size()// 总数是否已经加载完 + || result.rows.size() < SIZE // 最后一页数据的数量一般不满size + ){//判断是否没有数据了 + smartRefresh.finishLoadMoreWithNoMoreData(); + }else { + smartRefresh.finishLoadMore(); + } + }else{ + smartRefresh.setEnableRefresh(true); + smartRefresh.finishRefresh(); + smartRefresh.finishLoadMore(); + } + } + + public A getAdapter() { + return adapter; + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTabActivity.java b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTabActivity.java new file mode 100644 index 0000000..c14ee7b --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTabActivity.java @@ -0,0 +1,78 @@ +package com.runt.open.mvvm.base.activities; + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.viewbinding.ViewBinding; +import androidx.viewpager2.widget.ViewPager2; + +import com.runt.open.mvvm.base.adapter.FragmentAdapter; +import com.runt.open.mvvm.base.fragments.BaseFragment; +import com.runt.open.mvvm.base.model.BaseViewModel; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Created by Administrator on 2021/11/4 0004. + */ +public abstract class BaseTabActivity<B extends ViewBinding,VM extends BaseViewModel> extends BaseActivity<B,VM> { + + TabLayout tabLayout; + FragmentAdapter fragmentAdapter; + List<String> tabTitles = new ArrayList<>(); + ViewPager2 viewPager2; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + fragmentAdapter = new FragmentAdapter(this); + fragmentAdapter.setFragments(initFragments()); + setTabTitles(initTabTitles()); + //设置当前可见Item左右可见page数,次范围内不会被销毁 + //禁用预加载 + try { + viewPager2 = (ViewPager2) binding.getClass().getDeclaredField("viewPager2").get(binding); + tabLayout = (TabLayout) binding.getClass().getDeclaredField("tabLayout").get(binding); + } 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(); + } + + + protected abstract List<String> initTabTitles(); + + protected abstract List<BaseFragment> initFragments(); + + protected List<String> getTabTitles(){ + return tabTitles; + } + + public FragmentAdapter getFragmentAdapter() { + return fragmentAdapter; + } + + public void setTabTitles(List<String> tabTitles) { + if(tabTitles == null){ + this.tabTitles.clear(); + } + this.tabTitles = tabTitles; + } + public void setTabTitles(String[] tabTitles) { + if(tabTitles == null){ + this.tabTitles.clear(); + } + setTabTitles(new ArrayList<>(Arrays.asList(tabTitles))); + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTitleBarActivity.java b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTitleBarActivity.java new file mode 100644 index 0000000..414103e --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTitleBarActivity.java @@ -0,0 +1,60 @@ +package com.runt.open.mvvm.base.activities; + +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; +import androidx.viewbinding.ViewBinding; + +import com.runt.open.mvvm.base.model.BaseViewModel; +import com.runt.open.mvvm.widgets.TitleBarView; + +/** + * 带有标题栏的activity封装 + * Created by Administrator on 2021/11/2 0002. + */ +public abstract class BaseTitleBarActivity<B extends ViewBinding,VM extends BaseViewModel> extends BaseActivity<B,VM> { + TitleBarView titleBarView; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + titleBarView = (TitleBarView) binding.getClass().getDeclaredField("titleBar").get(binding); + titleBarView.setLeftClick(v -> onTitleLeftClick()); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + } + + 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); + } + + @Override + public void setStatusBarTransparent(boolean isBlack) { + super.setStatusBarTransparent(isBlack); + final ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) titleBarView.getLayoutParams(); + layoutParams.topMargin = layoutParams.topMargin+getStatusBarHeight(); + titleBarView.setLayoutParams(layoutParams); + + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java b/app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java new file mode 100644 index 0000000..d022444 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java @@ -0,0 +1,166 @@ +package com.runt.open.mvvm.base.adapter; + +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.runt.open.mvvm.base.activities.BaseActivity; +import com.runt.open.mvvm.databinding.LayoutNullBinding; +import com.runt.open.mvvm.util.DeviceUtil; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Administrator on 2021/10/27 0027. + * T 数据类型 + * V 适配器视图 + */ +public abstract class BaseAdapter<B extends ViewBinding,T> extends RecyclerView.Adapter { + + protected List<T> mData = new ArrayList<>(); + + protected Drawable nullDrawable; + protected String nullTxt="暂无数据"; + protected String TAG = "BaseAdapter"; + protected BaseActivity activity; + + public BaseAdapter(){ + } + + public BaseAdapter(@NonNull List<T> data){ + mData = data; + } + + public List<T> getData() { + return mData; + } + + public void setData(@NonNull List<T> data){ + mData = data; + notifyDataSetChanged(); + } + + @NonNull + @Override + public RecyclerView.ViewHolder 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()){ + 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())); + }*/ + 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; + return new ViewBindHolder(vBind); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException();// 获取目标异常 + t.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return new NullViewHolder( 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); + } + } + + /** + * 设置最后一个底部间隔 + * @param holder + * @param position + */ + protected void setBottomMargin(ViewBindHolder holder, int position){ + setBottomMargin(holder,position,23); + } + + /** + * 设置最后一个底部间隔 + * @param holder + * @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, int dp, int defaultDp){ + ViewGroup.MarginLayoutParams params1 = (ViewGroup.MarginLayoutParams) holder.itemView.getLayoutParams(); + if(position == mData.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){ + + } + + @Override + public int getItemCount() { + //默认显示空视图,若不显示空视图则重写该方法,返回mData.size() + return mData == null || mData.size() == 0 ?1:mData.size(); + } + + + @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; + } + + public class ViewBindHolder extends RecyclerView.ViewHolder{ + public B binding; + public ViewBindHolder( B binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + /** + * 空数据显示 + * Created by Administrator on 2021/10/28 0028. + */ + public class NullViewHolder extends RecyclerView.ViewHolder { + LayoutNullBinding binding; + + public NullViewHolder(LayoutNullBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/adapter/FragmentAdapter.java b/app/src/main/java/com/runt/open/mvvm/base/adapter/FragmentAdapter.java new file mode 100644 index 0000000..589ec63 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/adapter/FragmentAdapter.java @@ -0,0 +1,51 @@ +package com.runt.open.mvvm.base.adapter; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import com.runt.open.mvvm.base.fragments.BaseFragment; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Administrator on 2021/11/3 0003. + */ +public class FragmentAdapter extends FragmentStateAdapter { + + private List<BaseFragment> fragments = new ArrayList<>(); + + public FragmentAdapter(@NonNull FragmentActivity fragmentActivity) { + super(fragmentActivity); + } + + public void setFragments(List<BaseFragment> fragments) { + this.fragments = fragments; + } + + public void removeFragment(BaseFragment fragment){ + this.fragments.remove(fragment); + } + + public void removeFragment(int index){ + this.fragments.remove(index); + } + + public void addFragment(BaseFragment fragment){ + fragments.add(fragment); + } + + + @NonNull + @Override + public Fragment createFragment(int position) { + return fragments.get(position); + } + + @Override + public int getItemCount() { + return fragments.size(); + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseFragment.java b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseFragment.java new file mode 100644 index 0000000..04fab76 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseFragment.java @@ -0,0 +1,66 @@ +package com.runt.open.mvvm.base.fragments; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +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.ViewModelFactory; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; + +/** + * fragment 封装 + * Created by Administrator on 2021/10/28 0028. + */ +public abstract class BaseFragment<B extends ViewBinding,VM extends ViewModel> extends Fragment { + + protected BaseActivity activity; + protected B binding; + protected VM viewModel; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + // get genericity "B" + try { + final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass(); + Class<B> entityClass = (Class<B>) 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; + Class<VM> vmClass = (Class<VM>) type.getActualTypeArguments()[1]; + viewModel = new ViewModelProvider(this,getViewModelFactory()).get(vmClass); + } catch (Exception e) { + e.printStackTrace(); + } + return binding.getRoot(); + } + + public ViewModelProvider.Factory getViewModelFactory(){ + return ViewModelFactory.getInstance(); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + activity = (BaseActivity) getActivity(); + initViews(); + } + + public abstract void initViews(); + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseLoadPageFragment.java b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseLoadPageFragment.java new file mode 100644 index 0000000..4d691b0 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseLoadPageFragment.java @@ -0,0 +1,105 @@ +package com.runt.open.mvvm.base.fragments; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.runt.open.mvvm.base.adapter.BaseAdapter; +import com.runt.open.mvvm.base.model.BaseLoadPageViewModel; +import com.runt.open.mvvm.data.BasePageResult; +import com.runt.open.mvvm.databinding.RefreshRecyclerBinding; +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.HashMap; + +/** + * 分页fragment 封装 + * Created by Administrator on 2021/11/3 0003. + */ +public abstract class BaseLoadPageFragment<B extends ViewBinding,VM extends BaseLoadPageViewModel,A extends BaseAdapter,D extends BasePageResult> extends BaseFragment<B,VM> + implements OnRefreshLoadMoreListener { + protected SmartRefreshLayout smartRefresh; + protected RecyclerView recycler; + final String TAG = "RecyclerFragment"; + protected A adapter;//适配器 + protected String url;//请求地址 + protected HashMap param;//参数 + protected int page=1; + protected final int SIZE = 10; + protected boolean isRefresh = false;//是否正在刷新 + + @Override + public void initViews() { + try { + Class<A> entityClass = (Class<A>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2]; + this.adapter = entityClass.newInstance();//实例化泛型 + String smartStr = "smartRefresh"; + smartRefresh = (SmartRefreshLayout) binding.getClass().getField(smartStr).get(binding); + recycler = (RecyclerView) binding.getClass().getDeclaredField("recycler").get(binding); + } catch (NoSuchFieldException e) { + try { + RefreshRecyclerBinding includeRefreshRecycler = (RefreshRecyclerBinding) binding.getClass().getDeclaredField ("includeRefreshRecycler").get(binding); + smartRefresh = includeRefreshRecycler.smartRefresh; + recycler = includeRefreshRecycler.recycler; + } catch (Exception e2) { + e2.printStackTrace(); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (java.lang.InstantiationException e) { + e.printStackTrace(); + } + smartRefresh.setRefreshHeader(new ClassicsHeader(getContext())); + smartRefresh.setRefreshFooter(new ClassicsFooter(getContext())); + smartRefresh.setOnRefreshLoadMoreListener(this); + recycler.setLayoutManager(new LinearLayoutManager(getContext())); + recycler.setAdapter(adapter); + } + + @Override + public void onLoadMore(@NonNull RefreshLayout refreshLayout) { + page++; + viewModel.onLoadMore(); + } + + @Override + public void onRefresh(@NonNull RefreshLayout refreshLayout) { + page = 1; + viewModel.onRefresh(); + } + + private void finishFreshLoadmore(D result){ + if(result.code == 200){ + + smartRefresh.setEnableRefresh(true); + smartRefresh.finishRefresh(); + if(page == 1){ + adapter.getData().clear(); + adapter.setData(result.rows); + }else{ + adapter.getData().addAll(result.rows); + adapter.notifyDataSetChanged(); + } + if(result.total <= adapter.getData().size()// 总数是否已经加载完 + || result.rows.size() < SIZE // 最后一页数据的数量一般不满size + ){//判断是否没有数据了 + smartRefresh.finishLoadMoreWithNoMoreData(); + }else { + smartRefresh.finishLoadMore(); + } + }else{ + smartRefresh.setEnableRefresh(true); + smartRefresh.finishRefresh(); + smartRefresh.finishLoadMore(); + } + } + public A getAdapter() { + return adapter; + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseTabFragment.java b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseTabFragment.java new file mode 100644 index 0000000..f482d6b --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseTabFragment.java @@ -0,0 +1,74 @@ +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 java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 带有tablayout fragment封装 + * Created by Administrator on 2021/11/3 0003. + */ +public abstract class BaseTabFragment<B extends ViewBinding,VM extends ViewModel> extends BaseFragment<B,VM> { + + TabLayout tabLayout; + FragmentAdapter fragmentAdapter; + List<String> tabTitles = new ArrayList<>(); + ViewPager2 viewPager2; + + @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); + } 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(); + } + + protected abstract List<String> initTabTitles(); + + protected abstract List<BaseFragment> initFragments(); + + protected List<String> getTabTitles(){ + return tabTitles; + } + + public FragmentAdapter getFragmentAdapter() { + return fragmentAdapter; + } + + public void setTabTitles(List<String> tabTitles) { + if(tabTitles == null){ + this.tabTitles.clear(); + } + this.tabTitles = tabTitles; + } + public void setTabTitles(String[] tabTitles) { + if(tabTitles == null){ + this.tabTitles.clear(); + } + setTabTitles(new ArrayList<>(Arrays.asList(tabTitles))); + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/model/BaseLoadPageViewModel.java b/app/src/main/java/com/runt/open/mvvm/base/model/BaseLoadPageViewModel.java new file mode 100644 index 0000000..150ef54 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/model/BaseLoadPageViewModel.java @@ -0,0 +1,13 @@ +package com.runt.open.mvvm.base.model; + +/** + * 分页 + * Created by Administrator on 2021/11/3 0003. + */ +public abstract class BaseLoadPageViewModel extends BaseViewModel { + + public abstract void onRefresh(); + + public abstract void onLoadMore(); + +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java b/app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java new file mode 100644 index 0000000..a50ff27 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java @@ -0,0 +1,31 @@ +package com.runt.open.mvvm.base.model; + +import androidx.lifecycle.ViewModel; + +import com.runt.open.mvvm.retrofit.observable.HttpObserver; +import com.runt.open.mvvm.retrofit.AndroidScheduler; + +import io.reactivex.Observable; +import io.reactivex.schedulers.Schedulers; + +/** + * Created by Administrator on 2021/11/11 0011. + */ +public class BaseViewModel extends ViewModel { + + + /** + * 网络请求观察 + * @param observable + * @param <T> + * @return + */ + public <T> void httpObserverOn(Observable<T> observable, HttpObserver observer){ + observable.subscribeOn(Schedulers.io())//指定网络请求在io后台线程中进行 + .observeOn(AndroidScheduler.mainThread()) + .subscribe(observer); + } + + + +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/model/ItemViewModel.java b/app/src/main/java/com/runt/open/mvvm/base/model/ItemViewModel.java new file mode 100644 index 0000000..ef2d908 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/model/ItemViewModel.java @@ -0,0 +1,15 @@ +package com.runt.open.mvvm.base.model; + +import androidx.lifecycle.MutableLiveData; + +/** + * Created by Administrator on 2021/11/5 0005. + */ +public class ItemViewModel<T> extends BaseViewModel { + + MutableLiveData<T> liveData = new MutableLiveData<>(); + + public MutableLiveData<T> getLiveData() { + return liveData; + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/base/model/ViewModelFactory.java b/app/src/main/java/com/runt/open/mvvm/base/model/ViewModelFactory.java new file mode 100644 index 0000000..7e861c5 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/base/model/ViewModelFactory.java @@ -0,0 +1,33 @@ +package com.runt.open.mvvm.base.model; + +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +/** + * Created by Administrator on 2021/11/4 0004. + */ +public class ViewModelFactory implements ViewModelProvider.Factory { + + static ViewModelFactory sInstance; + + public static ViewModelFactory getInstance() { + if (sInstance == null) { + sInstance = new ViewModelFactory(); + } + return sInstance; + } + + @Override + public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { + //noinspection TryWithIdenticalCatches + try { + return modelClass.newInstance(); + } catch (InstantiationException e) { + throw new RuntimeException("(创建实例失败,没有对应的构造方法,请重写一个实现类)Cannot create an instance of " + modelClass, e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot create an instance of " + modelClass, e); + } + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/data/ApkUpGradeResult.java b/app/src/main/java/com/runt/open/mvvm/data/ApkUpGradeResult.java new file mode 100644 index 0000000..6b9d44a --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/data/ApkUpGradeResult.java @@ -0,0 +1,29 @@ +package com.runt.open.mvvm.data; + +/** + * Created by Administrator on 2021/11/15 0015. + */ +public class ApkUpGradeResult extends BaseApiResult<ApkUpGradeResult.AppInfo>{ + + public class AppInfo { + //以下为声明的参数 + public int type, isEnable, isImportant, id, code; + public String url,version, content, udate, remark; + + + @Override + public String toString() { + return "ApkUpGrade{" + + "type=" + type + "," + + "isEnable=" + isEnable + "," + + "isImportant=" + isImportant + "," + + "id=" + id + "," + + "url=" + url + "," + + "version=" + version + "," + + "content=" + content + "," + + "udate=" + udate + "," + + '}'; + } + + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java b/app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java new file mode 100644 index 0000000..9b758bf --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java @@ -0,0 +1,23 @@ +package com.runt.open.mvvm.data; + +import java.io.Serializable; + +/** + * Created by Administrator on 2021/10/28 0028. + */ +public class BaseApiResult<D extends Object> implements Serializable { + public String msg; + public int code = 200; + public D data; + + + @Override + public String toString() { + return "BaseApiData{" + + "msg='" + msg + '\'' + + ", code=" + code + + ", data=" + data + + '}'; + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/data/BasePageResult.java b/app/src/main/java/com/runt/open/mvvm/data/BasePageResult.java new file mode 100644 index 0000000..6eba4d3 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/data/BasePageResult.java @@ -0,0 +1,21 @@ +package com.runt.open.mvvm.data; + +import java.util.ArrayList; + +/** + * Created by Administrator on 2021/10/28 0028. + */ +public class BasePageResult<T> extends BaseApiResult<String>{ + public int pages; + public int total; + public int pageNum; + public ArrayList<T> rows; + + @Override + public String toString() { + return "PageBean{" + + ", total=" + total + + ", rows=" + rows + + '}'; + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/AndroidScheduler.java b/app/src/main/java/com/runt/open/mvvm/retrofit/AndroidScheduler.java new file mode 100644 index 0000000..3315c0f --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/AndroidScheduler.java @@ -0,0 +1,39 @@ +package com.runt.open.mvvm.retrofit; + +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; + +import java.util.concurrent.Executor; + +import io.reactivex.Scheduler; +import io.reactivex.schedulers.Schedulers; + +/** + * Created by Administrator on 2021/11/8 0008. + */ +public class AndroidScheduler implements Executor { + private static AndroidScheduler instance; + + private final Scheduler mMainScheduler; + private final Handler mHandler; + + private AndroidScheduler() { + mHandler = new Handler(Looper.myLooper()); + mMainScheduler = Schedulers.from(this); + } + + public static synchronized Scheduler mainThread() { + if (instance == null) { + instance = new AndroidScheduler(); + } + return instance.mMainScheduler; + } + + @Override + public void execute(@NonNull Runnable command) { + mHandler.post(command); + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/EncryptInterceptor.java b/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/EncryptInterceptor.java new file mode 100644 index 0000000..6db29f3 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/EncryptInterceptor.java @@ -0,0 +1,104 @@ +package com.runt.open.mvvm.retrofit.Interceptor; + + +import com.runt.open.mvvm.retrofit.utils.RSAUtils; + +import org.json.JSONObject; + +import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okio.Buffer; + +/** + * My father is Object, ites purpose of 加密拦截器 + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-10-8. + */ + +public class EncryptInterceptor implements Interceptor { + + protected static final Charset UTF8 = Charset.forName("UTF-8"); + private final String ENCRYPT = "encrypt"; + + @Override + public Response intercept(Chain chain) throws IOException { + return chain.proceed(encryptRequest(chain.request())); + } + + + //加密 + protected Request encryptRequest(Request request) throws IOException { + Headers headers = request.headers(); + RequestBody requestBody = request.body(); + Request.Builder builder = request.newBuilder(); + for(int i = 0 ; i < headers.size() ; i ++){ + builder.addHeader(headers.name(i),headers.value(i)); + } + if(requestBody != null){ + Charset charset = UTF8; + MediaType contentType = requestBody.contentType(); + if (contentType != null) { + charset = contentType.charset(UTF8); + } + HashMap param = new HashMap(); + if(requestBody instanceof MultipartBody){ + MultipartBody body = (MultipartBody) requestBody; + for(MultipartBody.Part part:body.parts()){ + Buffer buffer1 = new Buffer(); + part.body().writeTo(buffer1); + 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")); + } + MultipartBody.Builder mbuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); + mbuilder.addFormDataPart(ENCRYPT,encryptParam(param)); + builder.post(mbuilder.build()); + }else if(requestBody instanceof FormBody){ + FormBody body = (FormBody) requestBody; + for(int i = 0 ; i < body.size() ; i ++ ){ + param.put(body.name(i),body.value(i)); + } + FormBody.Builder formBuild = new FormBody.Builder(); + formBuild.add(ENCRYPT,encryptParam(param)); + builder.post(formBuild.build()); + }else{ + Buffer buffer = new Buffer(); + requestBody.writeTo(buffer); + String str = buffer.readString(charset); + String encrypt = encryptJson(str); + param.put(ENCRYPT,encrypt); + builder.post(RequestBody.create(MediaType.parse("application/json;charset=utf-8"), new JSONObject(param).toString())); + } + } + return builder.build(); + } + + + /** + * 加密传递的参数 + * @param params + * @return + */ + public static String encryptParam(Map<String, Object> params){ + return encryptJson(new JSONObject(params).toString()); + } + public static String encryptJson(String json){ + try { + return RSAUtils.encrypt(json,RSAUtils.getPublicKey(RSAUtils.PUBLIC_KEY)); + }catch (Exception e){ + e.printStackTrace(); + return e.getMessage(); + } + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java b/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java new file mode 100644 index 0000000..221f070 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java @@ -0,0 +1,280 @@ +package com.runt.open.mvvm.retrofit.Interceptor; + +import android.util.Log; + +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 org.json.JSONObject; + +import java.io.EOFException; +import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; + +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.internal.http.HttpHeaders; +import okio.Buffer; +import okio.BufferedSource; + +/** + * My father is Object, ites purpose of log打印 + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-10-21. + */ + +public class HttpLoggingInterceptor extends EncryptInterceptor { + + final String TAG = "HttpLogging"; + + public HttpLoggingInterceptor() { + } + + + @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; + try { + //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 + ')'); + } + NetWorkListenear.workCostMap.remove(hashCode); + new Thread(){ + @Override + public void run() { + HttpPrintUtils.getInstance().printLog(logArrays, true);//线程安全方法,需在新线程执行,避免阻塞当前线程,导致程序无响应 + } + }.start(); + } 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 + ')'); + 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);//线程安全方法,需在新线程执行,避免阻塞当前线程,导致程序无响应 + } + }.start(); + throw e;//抛出异常,用于请求接收信息 + } + return response; + } + + private ArrayList<String> getRequestLog(Request request) throws IOException { + RequestBody requestBody = request.body(); + ArrayList<String> logArrays = new ArrayList<>(); + String requestStartMessage = "--> " + request.method() + ' ' + URLDecoder.decode(request.url().toString() ,"UTF-8")+ ' ' ; + if ( requestBody != null) { + requestStartMessage += " (" + requestBody.contentLength() + "-byte body)"; + } + logArrays.add(requestStartMessage); + Headers headers = request.headers(); + logArrays.add("---------->REQUEST HEADER<----------"); + for (int i = 0, count = headers.size(); i < count; i++) { + logArrays.add(headers.name(i) + ": " + headers.value(i)); + } + if (requestBody == null) { + logArrays.add("--> END " + request.method()); + } else if (bodyEncoded(request.headers())) { + logArrays.add("--> END " + request.method() + " (encoded body omitted)"); + } else { + + Charset charset = UTF8; + MediaType contentType = requestBody.contentType(); + if (contentType != null) { + charset = contentType.charset(UTF8); + } + HashMap param = new HashMap(); + if(requestBody instanceof MultipartBody){ + logArrays.add("---------->REQUEST BODY[MultipartBody]<----------"); + MultipartBody body = (MultipartBody) requestBody; + for(MultipartBody.Part part:body.parts()){ + Buffer buffer1 = new Buffer(); + part.body().writeTo(buffer1); + 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())); + }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())); + }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){ + logArrays.add(str); + } + } + logArrays.add("--> END " + request.method() + " " + contentType + " ( " + + requestBody.contentLength() + "-byte body )"); + } + return logArrays; + + } + + + private ArrayList<String> getResponseLog(Response response) throws IOException { + ArrayList<String> logArrays = new ArrayList<>(); + ResponseBody responseBody = response.body(); + long contentLength = responseBody.contentLength(); + String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length"; + logArrays.add("<-- response code:" + response.code() + " message:" + response.message()+" contentlength:"+bodySize ); + logArrays.add("<-- response url:"+URLDecoder.decode(response.request().url().toString(),"UTF-8") ); + + if ( !HttpHeaders.hasBody(response)) { + logArrays.add("<-- END HTTP"); + } else if (bodyEncoded(response.headers())) { + logArrays.add("<-- END HTTP (encoded body omitted)"); + } else { + BufferedSource source = responseBody.source(); + source.request(Long.MAX_VALUE); // Buffer the entire body. + Buffer buffer = source.buffer(); + + Charset charset = UTF8; + MediaType contentType = responseBody.contentType(); + if (contentType != null) { + charset = contentType.charset(UTF8); + } + + if (isPlaintext(buffer)) { + logArrays.add("---------->RESPONSE BODY<----------"); + if (contentLength != 0) { + logArrays.add(retractJson(buffer.clone().readString(charset))); + } + + logArrays.add("<-- END HTTP (" + buffer.size() + "-byte body)"); + } + + } + 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 + * 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. + } + } + + private boolean bodyEncoded(Headers headers) { + 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"; + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java b/app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java new file mode 100644 index 0000000..fef1d1e --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java @@ -0,0 +1,66 @@ +package com.runt.open.mvvm.retrofit.api; + + + +import com.runt.open.mvvm.data.ApkUpGradeResult; + +import java.util.Map; + +import io.reactivex.Observable; +import retrofit2.http.Field; +import retrofit2.http.FieldMap; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Query; +import retrofit2.http.QueryMap; +import retrofit2.http.Url; + +/** + * My father is Object, ites purpose of 常用接口 + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-21. + */ + +public interface CommonApiCenter { + + @GET + Observable<Object> getData(@Url String url, @QueryMap Map<String,String> param); + + @FormUrlEncoded + @POST + Observable<Object> postData(@Url String url, @FieldMap Map<String,String> param); + + + /** + * 分页数据 + * @param url 请求地址 + * @param pageNum 页数 + * @param pageSize 每页数量 + * @param param 其他参数 + * @return + */ + @GET + Observable<Object> getPageData(@Url String url, @Query("pageNum") int pageNum, @Query("pageSize") int pageSize, @QueryMap Map<String,String> param); + + /** + * 分页数据 + * @param url 请求地址 + * @param pageNum 页数 + * @param pageSize 每页数量 + * @param param 其他参数 + * @return + */ + @FormUrlEncoded + @POST + Observable<Object> postPageData(@Url String url, @Field("pageNum") int pageNum, @Field("pageSize") int pageSize, @FieldMap Map<String,String> param); + + /** + * app更新 + * @return + */ + @GET("system/appupgrade/tourist/get/2") + Observable<ApkUpGradeResult> getAppUpdate(); + + +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonConverterFactory.java b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonConverterFactory.java new file mode 100644 index 0000000..519ea76 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonConverterFactory.java @@ -0,0 +1,58 @@ +package com.runt.open.mvvm.retrofit.converter; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Converter; +import retrofit2.Retrofit; + +/** + * My father is Object, ites purpose of 解密gson转换 + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-22. + */ +public class DecryptGsonConverterFactory extends Converter.Factory { + + public static DecryptGsonConverterFactory create() { + return create(new Gson(),false); + } + + public static DecryptGsonConverterFactory create(boolean transHump) { + return create(new Gson(),transHump); + } + + + public static DecryptGsonConverterFactory create(Gson gson,boolean transHump) { + return new DecryptGsonConverterFactory(gson,transHump); + } + + private final Gson gson; + + private final boolean transHump; + + public DecryptGsonConverterFactory(Gson gson, boolean transHump) { + if (gson == null) throw new NullPointerException("gson == null"); + this.gson = gson; + this.transHump = transHump; + } + + @Override + public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, + Retrofit retrofit) { + TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); + return new DecryptGsonResponseBodyConverter<>(gson, adapter,transHump); + } + + @Override + public Converter<?, RequestBody> requestBodyConverter(Type type, + Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { + TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); + return new GsonRequestBodyConverter<>(gson, adapter); + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonResponseBodyConverter.java b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonResponseBodyConverter.java new file mode 100644 index 0000000..4213ddc --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonResponseBodyConverter.java @@ -0,0 +1,95 @@ +package com.runt.open.mvvm.retrofit.converter; + +import android.util.Log; + +import com.runt.open.mvvm.data.BaseApiResult; +import com.runt.open.mvvm.util.GsonUtils; +import com.google.gson.Gson; +import com.google.gson.JsonIOException; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import okhttp3.ResponseBody; +import retrofit2.Converter; + +/** + * My father is Object, ites purpose of 解密gson转换器 + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-22. + */ + +public class DecryptGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> { + private final Gson gson; + private final TypeAdapter<T> adapter; + private final Charset UTF_8 = Charset.forName("UTF-8"); + private final boolean transHump;//驼峰转换 + private final String ENCRYPT = "encrypt"; + + public DecryptGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter, boolean transHump) { + this.gson = gson; + this.adapter = adapter; + this.transHump = transHump; + } + + @Override + public T convert(ResponseBody value) throws IOException { + String response = null; + try { + String val = new String(value.bytes(),UTF_8); + response = decryptJsonStr(val);//解密 + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + e.printStackTrace(); + BaseApiResult apiResult = new BaseApiResult<>(); + apiResult.code = 412; + apiResult.msg = "解密数据出错"+e.getMessage(); + response = new Gson().toJson(apiResult); + } catch (JSONException e) { + e.printStackTrace(); + BaseApiResult apiResult = new BaseApiResult<>(); + apiResult.code = 414; + apiResult.msg = "非标准json"; + response = new Gson().toJson(apiResult); + }catch (Exception e){ + JsonReader jsonReader = gson.newJsonReader(value.charStream()); + return adapter.read(jsonReader); + } finally { + InputStream inputStream = new ByteArrayInputStream(response.getBytes()); + JsonReader jsonReader = gson.newJsonReader(new InputStreamReader(inputStream, UTF_8)); + T result = adapter.read(jsonReader); + if (jsonReader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonIOException("JSON document was not fully consumed."); + } + value.close(); + return result; + } + } + + /** + * 解密json + * @param body + * @return + * @throws Exception + */ + protected String decryptJsonStr(String body) throws Exception { + Log.e("Converter","decryptJsonStr body:"+body); + if(body.indexOf("{") == 0) { + JSONObject json = new JSONObject(body); + body = json.toString(); + //body = RSAUtils.decrypt(json.getString(ENCRYPT), RSAUtils.getPublicKey(RSAUtils.PUBLIC_KEY));// + } + return transHump? GsonUtils.toHumpJson(body):body; + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/converter/GsonRequestBodyConverter.java b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/GsonRequestBodyConverter.java new file mode 100644 index 0000000..7df807d --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/GsonRequestBodyConverter.java @@ -0,0 +1,44 @@ +package com.runt.open.mvvm.retrofit.converter; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; + +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okio.Buffer; +import retrofit2.Converter; + +/** + * My father is Object, ites purpose of + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-15. + */ + +final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> { + private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private final Gson gson; + private final TypeAdapter<T> adapter; + + GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) { + this.gson = gson; + this.adapter = adapter; + } + + @Override + public RequestBody convert(T value) throws IOException { + Buffer buffer = new Buffer(); + Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8); + JsonWriter jsonWriter = gson.newJsonWriter(writer); + adapter.write(jsonWriter, value); + jsonWriter.close(); + return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkCost.java b/app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkCost.java new file mode 100644 index 0000000..afc660a --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkCost.java @@ -0,0 +1,14 @@ +package com.runt.open.mvvm.retrofit.net; + + +/** + * My father is Object, ites purpose of 网络消耗 + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-3-12. + */ + +public class NetWorkCost { + + //网络消耗时间 + public long dns,connect,total,secure,requestHeader,requestBody,resposeHeader,resposeBody; +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkListenear.java b/app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkListenear.java new file mode 100644 index 0000000..6f49237 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkListenear.java @@ -0,0 +1,173 @@ +package com.runt.open.mvvm.retrofit.net; + +import androidx.annotation.Nullable; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.nio.charset.Charset; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import okhttp3.Call; +import okhttp3.EventListener; +import okhttp3.Handshake; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; + +/** + * My father is Object, ites purpose of 接口请求耗时监听 + * + * @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(){ + Factory factory = new Factory() { + @NotNull + @Override + public EventListener create(@NotNull Call call) { + return new NetWorkListenear(); + } + }; + return factory; + } + + @Override + public void callStart(@NotNull Call call) { + super.callStart(call); + //mRequestId = mNextRequestId.getAndIncrement() + ""; + //getAndAdd,在多线程下使用cas保证原子性 + NetWorkCost netWorkCost = new NetWorkCost(); + netWorkCost.total = new Date().getTime(); + workCostMap.put(call.request().hashCode(),netWorkCost); + } + + @Override + public void dnsStart(@NotNull Call call, @NotNull String domainName) { + super.dnsStart(call, domainName); + //Log.d(TAG, "dnsStart"); + workCostMap.get(call.request().hashCode()).dns = new Date().getTime(); + } + + @Override + 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; + } + + @Override + public void connectStart(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress, @NotNull Proxy proxy) { + super.connectStart(call, inetSocketAddress, proxy); + //Log.d(TAG, "connectStart"); + workCostMap.get(call.request().hashCode()).connect = new Date().getTime(); + } + + @Override + public void secureConnectStart(@NotNull Call call) { + super.secureConnectStart(call); + //Log.d(TAG, "secureConnectStart"); + workCostMap.get(call.request().hashCode()).secure = new Date().getTime(); + } + + @Override + public void secureConnectEnd(@NotNull Call call, @Nullable Handshake handshake) { + super.secureConnectEnd(call, handshake); + //Log.d(TAG, "secureConnectEnd"); + workCostMap.get(call.request().hashCode()).secure = new Date().getTime() - workCostMap.get(call.request().hashCode()).secure; + } + + @Override + public void connectEnd(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress, + @NotNull Proxy proxy, @Nullable Protocol protocol) { + super.connectEnd(call, inetSocketAddress, proxy, protocol); + //Log.d(TAG, "connectEnd"); + workCostMap.get(call.request().hashCode()).connect = new Date().getTime() - workCostMap.get(call.request().hashCode()).connect; + } + + @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"); + } + + @Override + public void requestHeadersStart(@NotNull Call call) { + super.requestHeadersStart(call); + //Log.d(TAG, "requestHeadersStart"); + workCostMap.get(call.request().hashCode()).requestHeader = new Date().getTime(); + } + + @Override + public void requestHeadersEnd(@NotNull Call call, @NotNull Request request) { + super.requestHeadersEnd(call, request); + //Log.d(TAG, "requestHeadersEnd"); + workCostMap.get(call.request().hashCode()).requestHeader = new Date().getTime() - workCostMap.get(call.request().hashCode()).requestHeader; + } + + @Override + public void requestBodyStart(@NotNull Call call) { + super.requestBodyStart(call); + //Log.d(TAG, "requestBodyStart"); + workCostMap.get(call.request().hashCode()).requestBody = new Date().getTime(); + } + + @Override + public void requestBodyEnd(@NotNull Call call, long byteCount) { + super.requestBodyEnd(call, byteCount); + //Log.d(TAG, "requestBodyEnd"); + workCostMap.get(call.request().hashCode()).requestBody = new Date().getTime() - workCostMap.get(call.request().hashCode()).requestBody; + } + + @Override + public void responseHeadersStart(@NotNull Call call) { + super.responseHeadersStart(call); + //Log.d(TAG, "responseHeadersStart"); + workCostMap.get(call.request().hashCode()).resposeHeader = new Date().getTime(); + } + + @Override + public void responseHeadersEnd(@NotNull Call call, @NotNull Response response) { + super.responseHeadersEnd(call, response); + //Log.d(TAG, "responseHeadersEnd"); + workCostMap.get(call.request().hashCode()).resposeHeader = new Date().getTime() - workCostMap.get(call.request().hashCode()).resposeHeader; + } + + @Override + public void responseBodyStart(@NotNull Call call) { + super.responseBodyStart(call); + //Log.d(TAG, "responseBodyStart"); + workCostMap.get(call.request().hashCode()).resposeBody = new Date().getTime(); + } + + @Override + 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; + } + + + @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"); + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java b/app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java new file mode 100644 index 0000000..b02e526 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java @@ -0,0 +1,69 @@ +package com.runt.open.mvvm.retrofit.observable; + +import android.util.Log; + +import androidx.lifecycle.MutableLiveData; + + +import com.runt.open.mvvm.data.BaseApiResult; + +import java.lang.reflect.ParameterizedType; +import java.net.SocketTimeoutException; + +import io.reactivex.observers.DisposableObserver; + +/** + * 网络请求观察 + * Created by Administrator on 2021/11/11 0011. + */ +public abstract class HttpObserver<T extends BaseApiResult> extends DisposableObserver<T>{ + + final String TAG = "HttpObserver"; + + MutableLiveData<T> resultLive; + + public HttpObserver(MutableLiveData<T> resultLive) { + this.resultLive = resultLive; + } + + + @Override + public void onNext(T value) { + resultLive.setValue(value); + } + + @Override + public void onError(Throwable throwable) { + Log.i("subscribe","onError"); + + try { + Log.e(TAG,this.getClass().getSimpleName()+" "+throwable.getMessage()); + Class<T> entityClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + T t = entityClass.newInstance();//实例化一个泛型 + t.code = 410; + if( throwable instanceof SocketTimeoutException){ + t.msg = "服务请求超时,请稍候再试";//设置错误信息 + }else{ + t.msg = "网络连接不畅,请检查您的网络设置";//设置错误信息 + } + resultLive.setValue(t); + } catch (ClassCastException e) { + e.printStackTrace(); + T t = (T) new BaseApiResult<String>(); + t.code = 409; + t.msg = "实例化对象未指定泛型实体类"; + resultLive.setValue(t); + } catch (Exception e) { + e.printStackTrace(); + T t = (T) new BaseApiResult<String>(); + t.code = 409; + t.msg = e.getMessage(); + resultLive.setValue(t); + } + } + + @Override + public void onComplete() { + Log.i("subscribe","onComplete"); + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/utils/HttpPrintUtils.java b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/HttpPrintUtils.java new file mode 100644 index 0000000..53dd3b0 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/HttpPrintUtils.java @@ -0,0 +1,210 @@ +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 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(); + } + return instance; + } + + /** + * 打印log + * @param list + */ + public synchronized void printLog(ArrayList<String> list, boolean info){ + int length = 0 ;//计算每行最长的长度 + ArrayList<String> logArrays = new ArrayList<>(); + for(String str : list){ + if(str.indexOf("\n")>-1){//有换行的拆分处理 + String[] split = str.split("\n"); + for(String s : split){ + s = s.replace("\t"," ");//缩进替换空格 + if(length<s.length()){ + length = s.length(); + } + } + }else{ + if(length<str.length()){ + length = str.length(); + } + } + } + length+=14;//左右间距 + if(length>300){ + length = 300; + } + String head = "HTTP REQUEST START"; + logArrays.add(" \n\n\n"+"\n"); + //打印头部 + String logHead = "┏"+getEmptyStr((length-head.length())/2,"━")+head+getEmptyStr((length-head.length())/2,"━")+"┓"; + logArrays.add(logHead+"\n"); + //打印内容 + for(String str : list){ + String logStr = ""; + if(str.indexOf("\n")>-1){//内部换行替换 + splitStr(str,logHead.length(),logArrays); + }else{ + if(str.length()> logHead.length()){ + outOflength(str,logHead.length(),logArrays); + }else { + logStr = "┃ " + str + getEmptyStr((length - 14 - str.length()), " "); + //处理中文间距,保证打印无偏差 + logArrays.add(logStr + getEmptyStr((logHead.length() - logStr.length() - 1 - hasCNchar(logStr)), " ") + "┃ \n"); + } + } + } + String end = "HTTP REQUEST END"; + //打印结尾 + 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); + } + } + } + } + + /** + * 拆分 + * @param str + * @param totalLength + * @param list + */ + private void splitStr(String str,int totalLength,ArrayList<String> list){ + String logStr = ""; + String[] split = str.split("\n"); + for(String s : split){ + if(s.length()/totalLength>3){ + 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); + }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"); + } + } + } + } + + + /** + * 超长字符拆分 + * @param str + * @param total + * @param list + */ + private void outOflength(String str,int total,ArrayList<String> list){ + String logStr = ""; + //缩进空间 + String space = getEmptyStr(str.length() - str.trim().length()+4," "); + //要拆分的实际长度 + int length = (str.length()-space.length()); + //每行数量 + int count = total-16-space.length();//总长度-间距-缩进空间是每行的数量 + //最终拆分数量 + int lines = (length/count) + (length%(count)>0?1:0); + + for(int i = 0 ; i < lines ; i ++){ + int start = space.length() + (count * (i+1));//起始位 + int end = start+count;//结束位 + String s = ""; + if(start > str.length() && i > 0){ + break; + } else if(end > str.length() && i > 0 || i == lines-1){ + s = str.substring(start); + } else if(i == 0 ){ + s = str.substring(0, start); + } else { + s = str.substring(start, end); + } + if(i>0) { + s = space + s; + } + logStr = "┃ " + s + getEmptyStr((total - 16 - s.length()), " "); + list.add(logStr + getEmptyStr((total - logStr.length() - 1 - hasCNchar(logStr)), " ") + "┃ \n"); + } + } + + + //返回包含中文数量, + private int hasCNchar(String str){ + str = str.replace("┃",""); + int size = 0 ; + for(int i = 0 ; i < str.length() ; i ++){ + char c = str.charAt(i); + + if((c >= 0x0391 && c <= 0xFFE5)) { //中文字符 + size++; + } + } + return size>0?(int)(size/3.0*2):0;//+1为修正在log中与英文字符短一位 + } + + /** + * 占位符填充 + * @param length 占位数量 + * @param space 占位符 + * @return + */ + private String getEmptyStr(int length,String space){ + StringBuilder sb = new StringBuilder(); + for(int i = 0 ; i < length ; i ++){ + 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. + } + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RSAUtils.java b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RSAUtils.java new file mode 100644 index 0000000..fd5a98c --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RSAUtils.java @@ -0,0 +1,178 @@ +package com.runt.open.mvvm.retrofit.utils; + +import android.util.Base64; + +import java.io.ByteArrayOutputStream; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import javax.crypto.Cipher; + + +public class RSAUtils { + + public static final String PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALBGei0scHoOjTLImPHvASaGqYNrdLie0ckWp74Nkqv7FVeXPOvWEG8_jRJCVjJ1grr8SGd9sVY2sxn5XIz7fUEBfx7Vm8m0DaCNBWJpFLGw9xiaVZ2AUKoNyTD4NgZobbwZbt6ZNB6_fggPrGF18pq6GPyCndX1JW8ZiZKj33VBAgMBAAECgYB0q-EX3y7_CnyYXT8l-mxHhJ_T9R6HR89QimcyGqe2nvRMSjSvX7r29xg3OqL0uORzQKHnpcDncELw8SQ5yAbpENeIsD0dvdFlkoyFYU4ljeUbJ46binwwg20TNARjTbpNos9zbhTPh8qixdblxppXA1WC18HtXhixgca5bNG9lQJBAPQfNdpNdDL9l8Tw4hYVuDMszcFuZYbHbm0S4xcwqj-dXNWBztNf5W_K92-N5GIoHbOypkGzjlBjSZi_oKA0HusCQQC42irhw682CG44mKdP6YRDxy6OaauVX4yE9WnsbO8JFSSc9ZCKMMD0F3NGtytDrVMAJxG1iPWXa4ptEdtgwCmDAkAUW1npR1YuPllekdu4jb0bf1v1ClirAYxiyhVnxKYdweiQ4U827yM5zEoP4lwuFzxK1NXqWqe-alkjxK8HTPFbAkAviQLf_adP2MknSrIzzZQSreTeAHR8PA7xnf54KucpScOZjVh3AOSNoH4nYDEC_U5LysA2E5s8Lg5xz9a_QYsrAkEAwV6gNED7_SYDsYyEWimQ6znUb_QSY-sSChnSCY-ILG1wpynBHw_t1Oi3ljl6gL_cYKG1O3uwOtvZtb-Vr1bNkQ"; + + public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwRnotLHB6Do0yyJjx7wEmhqmDa3S4ntHJFqe-DZKr-xVXlzzr1hBvP40SQlYydYK6_EhnfbFWNrMZ-VyM-31BAX8e1ZvJtA2gjQViaRSxsPcYmlWdgFCqDckw-DYGaG28GW7emTQev34ID6xhdfKauhj8gp3V9SVvGYmSo991QQIDAQAB"; + + + /** + * RSA最大加密明文大小 + */ + private static final int MAX_ENCRYPT_BLOCK = 117; + + /** + * RSA最大解密密文大小 + */ + private static final int MAX_DECRYPT_BLOCK = 128; + + static final String KEY_RSA = "RSA"; //android标准 “RSA/ECB/PKCS1Padding” 服务端标准 “RSA” + + /** + * 获取密钥对 + * + * @return 密钥对 + */ + public static KeyPair getKeyPair() throws Exception { + KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_RSA); + generator.initialize(1024); + return generator.generateKeyPair(); + } + + /** + * 获取私钥 + * + * @param privateKey 私钥字符串 + * @return + */ + public static PrivateKey getPrivateKey(String privateKey) throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); + byte[] decodedKey = Base64.decode(privateKey.getBytes(), Base64.URL_SAFE); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); + return keyFactory.generatePrivate(keySpec); + } + + /** + * 获取公钥 + * + * @param publicKey 公钥字符串 + * @return + */ + public static PublicKey getPublicKey(String publicKey) throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); + byte[] decodedKey = Base64.decode(publicKey.getBytes(), Base64.URL_SAFE); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey); + return keyFactory.generatePublic(keySpec); + } + + /** + * RSA加密 + * + * @param data 待加密数据 + * @param key 密钥 + * @return + */ + public static String encrypt(String data, Key key) throws Exception { + Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + int inputLen = data.getBytes().length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offset = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offset > 0) { + if (inputLen - offset > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset); + } + out.write(cache, 0, cache.length); + i++; + offset = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + // 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串 + // 加密后的字符串 + return Base64.encodeToString(encryptedData,Base64.URL_SAFE | Base64.NO_WRAP); + } + + /** + * RSA解密 + * + * @param data 待解密数据 + * @param key 密钥 + * @return + */ + public static String decrypt(String data, Key key) throws Exception { + Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] dataBytes = Base64.decode(data.getBytes(), Base64.URL_SAFE); + int inputLen = dataBytes.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offset = 0; + byte[] cache; + int i = 0; + // 对数据分段解密 + while (inputLen - offset > 0) { + if (inputLen - offset > MAX_DECRYPT_BLOCK) { + cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK); + } else { + cache = cipher.doFinal(dataBytes, offset, inputLen - offset); + } + out.write(cache, 0, cache.length); + i++; + offset = i * MAX_DECRYPT_BLOCK; + } + byte[] decryptedData = out.toByteArray(); + out.close(); + // 解密后的内容 + return new String(decryptedData, "UTF-8"); + } + + /** + * 签名 + * + * @param data 待签名数据 + * @param privateKey 私钥 + * @return 签名 + */ + public static String sign(String data, PrivateKey privateKey) throws Exception { + byte[] keyBytes = privateKey.getEncoded(); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); + PrivateKey key = keyFactory.generatePrivate(keySpec); + Signature signature = Signature.getInstance("MD5withRSA"); + signature.initSign(key); + signature.update(data.getBytes()); + return Base64.encodeToString(signature.sign(),Base64.DEFAULT); + } + + /** + * 验签 + * + * @param srcData 原始字符串 + * @param publicKey 公钥 + * @param sign 签名 + * @return 是否验签通过 + */ + public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception { + byte[] keyBytes = publicKey.getEncoded(); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); + PublicKey key = keyFactory.generatePublic(keySpec); + Signature signature = Signature.getInstance("MD5withRSA"); + signature.initVerify(key); + signature.update(srcData.getBytes()); + return signature.verify(Base64.decode(sign.getBytes(),Base64.DEFAULT)); + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RetrofitUtils.java b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RetrofitUtils.java new file mode 100644 index 0000000..df2ae38 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RetrofitUtils.java @@ -0,0 +1,127 @@ +package com.runt.open.mvvm.retrofit.utils; + + +import com.runt.open.mvvm.BuildConfig; +import com.runt.open.mvvm.retrofit.Interceptor.EncryptInterceptor; +import com.runt.open.mvvm.retrofit.Interceptor.HttpLoggingInterceptor; +import com.runt.open.mvvm.retrofit.api.CommonApiCenter; +import com.runt.open.mvvm.retrofit.converter.DecryptGsonConverterFactory; +import com.runt.open.mvvm.retrofit.net.NetWorkListenear; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; + +/** + * My father is Object, ites purpose of + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-9. + */ + +public class RetrofitUtils { + public static String HOST_IP_ADDR; + static RetrofitUtils instance; + Retrofit retrofit/*log输出,驼峰转换*/,unHumpRetrofit/*log输出,不强制驼峰转换*/, + unLogRetrofit/*log不输出,驼峰转换*/,unLogHumpRetorfit/*log不输出,不强制驼峰转换*/; + CommonApiCenter commonApi;//常用接口 + + OkHttpClient.Builder builder = new OkHttpClient.Builder() + .addInterceptor(new EncryptInterceptor()); + OkHttpClient.Builder logBuilder = new OkHttpClient.Builder() + .addInterceptor(new HttpLoggingInterceptor());//log打印拦截器 + + public static RetrofitUtils getInstance() { + if(instance == null){ + instance = new RetrofitUtils(); + } + return instance; + } + + /** + * log输出,gson驼峰转换 + * @return + */ + public <T> T getRetrofit(Class<T> clas) { + if(retrofit == null){ + retrofit = getRetrofit(getOkHttpClient(logBuilder), + new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create(true))) ; + } + if(!BuildConfig.DEBUG){//正式版 不打印log + return getUnLogRetrofit(clas); + } + return retrofit.create(clas); + } + + /** + * log输出,gson不转换驼峰 + * @return + */ + public <T> T getUnHumpRetrofit(Class<T> clas) { + if(unHumpRetrofit == null){ + unHumpRetrofit = getRetrofit(getOkHttpClient(logBuilder), + new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create())) ; + } + if(!BuildConfig.DEBUG){//正式版 不打印log + return getUnLogHumpRetorfit(clas); + } + return unHumpRetrofit.create(clas); + } + + /** + * log不输出,gson驼峰转换 + * @return + */ + public <T> T getUnLogRetrofit(Class<T> clas) { + if(unLogRetrofit == null){ + unLogRetrofit = getRetrofit(getOkHttpClient(builder), + new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create(true))) ; + } + return unLogRetrofit.create(clas); + } + + /** + * log不输出,gson不转换驼峰 + * @return + */ + public <T> T getUnLogHumpRetorfit(Class<T> clas) { + if(unLogHumpRetorfit == null){ + unLogHumpRetorfit = getRetrofit(getOkHttpClient(builder), + new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create())) ; + } + return unLogHumpRetorfit.create(clas); + } + + private OkHttpClient getOkHttpClient(OkHttpClient.Builder builder){ + return builder.connectTimeout(10, TimeUnit.SECONDS)//设置连接超时时间 + .readTimeout(30, TimeUnit.SECONDS)//设置读取超时时间 + .protocols(Collections.singletonList(Protocol.HTTP_1_1)) + .eventListenerFactory(NetWorkListenear.get()) + .build(); + } + + private Retrofit getRetrofit(OkHttpClient client,Retrofit.Builder builder){ + return builder + //设置OKHttpClient + .client(client) + //设置baseUrl,注意,baseUrl必须后缀"/" + .baseUrl(BuildConfig.ENVIRONMENT.equals("develop")?HOST_IP_ADDR:BuildConfig.HOST_IP_ADDR) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); + } + + + /** + * 常用接口 + * @return + */ + public CommonApiCenter getCommonApi(){ + if(commonApi == null){ + commonApi = getRetrofit(CommonApiCenter.class); + } + return commonApi; + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardFragment.java b/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardFragment.java new file mode 100644 index 0000000..7266305 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardFragment.java @@ -0,0 +1,44 @@ +package com.runt.open.mvvm.ui.dashboard; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import com.runt.open.mvvm.R; +import com.runt.open.mvvm.databinding.FragmentDashboardBinding; + +public class DashboardFragment extends Fragment { + + private DashboardViewModel dashboardViewModel; +private FragmentDashboardBinding binding; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + dashboardViewModel = + new ViewModelProvider(this).get(DashboardViewModel.class); + + binding = FragmentDashboardBinding.inflate(inflater, container, false); + View root = binding.getRoot(); + + final TextView textView = binding.textDashboard; + dashboardViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() { + @Override + public void onChanged(@Nullable String s) { + textView.setText(s); + } + }); + return root; + } + +@Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardViewModel.java b/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardViewModel.java new file mode 100644 index 0000000..d7234f7 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardViewModel.java @@ -0,0 +1,19 @@ +package com.runt.open.mvvm.ui.dashboard; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +public class DashboardViewModel extends ViewModel { + + private MutableLiveData<String> mText; + + public DashboardViewModel() { + mText = new MutableLiveData<>(); + mText.setValue("This is dashboard fragment"); + } + + public LiveData<String> getText() { + return mText; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runt/open/mvvm/ui/home/HomeFragment.java b/app/src/main/java/com/runt/open/mvvm/ui/home/HomeFragment.java new file mode 100644 index 0000000..bade84b --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/ui/home/HomeFragment.java @@ -0,0 +1,44 @@ +package com.runt.open.mvvm.ui.home; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import com.runt.open.mvvm.R; +import com.runt.open.mvvm.databinding.FragmentHomeBinding; + +public class HomeFragment extends Fragment { + + private HomeViewModel homeViewModel; +private FragmentHomeBinding binding; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + homeViewModel = + new ViewModelProvider(this).get(HomeViewModel.class); + + binding = FragmentHomeBinding.inflate(inflater, container, false); + View root = binding.getRoot(); + + final TextView textView = binding.textHome; + homeViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() { + @Override + public void onChanged(@Nullable String s) { + textView.setText(s); + } + }); + return root; + } + +@Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runt/open/mvvm/ui/home/HomeViewModel.java b/app/src/main/java/com/runt/open/mvvm/ui/home/HomeViewModel.java new file mode 100644 index 0000000..a728c8a --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/ui/home/HomeViewModel.java @@ -0,0 +1,19 @@ +package com.runt.open.mvvm.ui.home; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +public class HomeViewModel extends ViewModel { + + private MutableLiveData<String> mText; + + public HomeViewModel() { + mText = new MutableLiveData<>(); + mText.setValue("This is home fragment"); + } + + public LiveData<String> getText() { + return mText; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsFragment.java b/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsFragment.java new file mode 100644 index 0000000..fc02849 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsFragment.java @@ -0,0 +1,44 @@ +package com.runt.open.mvvm.ui.notifications; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import com.runt.open.mvvm.R; +import com.runt.open.mvvm.databinding.FragmentNotificationsBinding; + +public class NotificationsFragment extends Fragment { + + private NotificationsViewModel notificationsViewModel; +private FragmentNotificationsBinding binding; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + notificationsViewModel = + new ViewModelProvider(this).get(NotificationsViewModel.class); + + binding = FragmentNotificationsBinding.inflate(inflater, container, false); + View root = binding.getRoot(); + + final TextView textView = binding.textNotifications; + notificationsViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() { + @Override + public void onChanged(@Nullable String s) { + textView.setText(s); + } + }); + return root; + } + +@Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsViewModel.java b/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsViewModel.java new file mode 100644 index 0000000..3e3c5d7 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsViewModel.java @@ -0,0 +1,19 @@ +package com.runt.open.mvvm.ui.notifications; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +public class NotificationsViewModel extends ViewModel { + + private MutableLiveData<String> mText; + + public NotificationsViewModel() { + mText = new MutableLiveData<>(); + mText.setValue("This is notifications fragment"); + } + + public LiveData<String> getText() { + return mText; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java b/app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java new file mode 100644 index 0000000..58761bb --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java @@ -0,0 +1,374 @@ +package com.runt.open.mvvm.util; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Point; +import android.os.Build; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.WindowManager; + +import androidx.core.app.ActivityCompat; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigDecimal; + + +/** + * Created by EDZ on 2018/1/30. + */ +public class DeviceUtil { + public static final String TAG = "DeviceUtil"; + + /** + * 设备信息 + * + * @param context + */ + public static void getDisplay(Context context) { + StringBuilder sb = new StringBuilder(); + sb.append("Version code is \n"); + sb.append("设备的Android版本号:");//设备的Android版本号 + sb.append(getSDKInt() + " " + getSDKVersion() + "\t");//设备的Android版本号 + sb.append("设备型号:");//设备型号 + sb.append(getDeviceModel() + "\t");//设备型号 + sb.append("设备厂商:");//设备型号 + sb.append(getDeviceBrand() + "\t");//设备型号 + sb.append("程序版本号:" + getAppVersionCode(context) + " " + getAppVersionName(context) + "\t");//程序版本号 + sb.append("设备唯一标识符:" + getSerialNumber(context)); + sb.append("\n设备imei:" + getIMEI(context)); + String str = sb.toString() + " \n"; + str += getDisplayInfomation(context) + " \n"; + str += getDensity(context) + " \n"; + str += "屏幕大小:" + getScreenInch(context) + "英寸 \n"; + str += getAndroiodScreenProperty(context) + "\n"; + Log.i(TAG, str); + } + + /** + * Double类型保留指定位数的小数,返回double类型(四舍五入) + * newScale 为指定的位数 + */ + private static double formatDouble(double d, int newScale) { + BigDecimal bd = new BigDecimal(d); + return bd.setScale(newScale, BigDecimal.ROUND_HALF_UP).doubleValue(); + } + + /** + * 设备型号 + * @return + */ + public static String getDeviceModel() { + return Build.MODEL; + } + + /** + * 设备厂商 + * @return + */ + public static String getDeviceBrand() { + return Build.BRAND; + } + + /** + * 设备的Android版本号 + * @return + */ + public static int getSDKInt() { + return Build.VERSION.SDK_INT; + } + + /** + * 设备的Android版本号 + * @return + */ + public static String getSDKVersion() { + return Build.VERSION.RELEASE; + } + + /** + * 程序版本号 + * @param context + * @return + */ + public static int getAppVersionCode(Context context) { + try { + return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return 1; + } + + /** + * 程序版本号 + * + * @param context + * @return + */ + public static String getAppVersionName(Context context) { + try { + return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return ""; + } + + /** + * 屏幕像素px + */ + @SuppressLint("NewApi") + public static String getDisplayInfomation(Context context) { + Point point = new Point(); + ((Activity) context).getWindowManager().getDefaultDisplay().getSize(point); + Log.d(TAG, "the screen size is " + point.toString()); + ((Activity) context).getWindowManager().getDefaultDisplay().getRealSize(point); + Log.d(TAG, "the screen real size is " + point.toString()); + return point.toString(); + } + + /** + * 屏幕信息 + * @param context + * @return + */ + public static String getAndroiodScreenProperty(Context context) { + int width = getScreenPixel(context).widthPixels; // 屏幕宽度(像素) + int height = getScreenPixel(context).heightPixels; // 屏幕高度(像素) + float density = getDensity(getScreenPixel(context)); // 屏幕密度(0.75 / 1.0 / 1.5) + int densityDpi = getDpi(context); // 屏幕密度dpi(120 / 160 / 240) + // 屏幕宽度算法:屏幕宽度(像素)/屏幕密度 + int screenWidth = getDp(context)[0]; // 屏幕宽度(dp) + int screenHeight = getDp(context)[1];// 屏幕高度(dp) + + + Log.d("h_bl", "屏幕宽度(像素):" + width); + Log.d("h_bl", "屏幕高度(像素):" + height); + Log.d("h_bl", "屏幕密度(0.75 / 1.0 / 1.5):" + density); + Log.d("h_bl", "屏幕密度dpi(120 / 160 / 240):" + densityDpi); + Log.d("h_bl", "屏幕宽度(dp):" + screenWidth); // 屏幕适配文件夹(例:layout-sw300dp),是以该属性为准则 + Log.d("h_bl", "屏幕高度(dp):" + screenHeight); + + StringBuilder sb = new StringBuilder(); + sb.append("屏幕宽度(像素):" + width + "\n"); + sb.append("屏幕高度(像素):" + height + "\n"); + sb.append("屏幕密度(0.75 / 1.0 / 1.5):" + density + "\n"); + sb.append("屏幕密度dpi(120 / 160 / 240):" + densityDpi + "\n"); + sb.append("屏幕宽度(dp):" + screenWidth + "\n"); + sb.append("屏幕高度(dp):" + screenHeight + "\n"); + return sb.toString(); + + } + + /** + * 通知栏高度 + * @param context + * @return + */ + public static int getStatusBarHeight(Context context) { + Class<?> c = null; + Object obj = null; + Field field = null; + int x = 0, statusBarHeight = 0; + try { + c = Class.forName("com.android.internal.R$dimen"); + obj = c.newInstance(); + field = c.getField("status_bar_height"); + x = Integer.parseInt(field.get(obj).toString()); + statusBarHeight = context.getResources().getDimensionPixelSize(x); + } catch (Exception e1) { + e1.printStackTrace(); + } + return statusBarHeight; + } + + /** + * 屏幕密度dpi + */ + public static int getDpi(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics dm = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(dm); + return dm.densityDpi; + } + + + /** + * 屏幕像素px + */ + @SuppressLint("NewApi") + public static Point getDisplayPixel(Context context) { + Point point = new Point(); + ((Activity) context).getWindowManager().getDefaultDisplay().getSize(point); + Log.d(TAG, "the screen size is " + point.toString()); + ((Activity) context).getWindowManager().getDefaultDisplay().getRealSize(point); + Log.d(TAG, "the screen real size is " + point.toString()); + return point; + } + + /** + * 屏幕像素px + */ + public static DisplayMetrics getScreenPixel(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics dm = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(dm); + return dm; + } + + /** + * 屏幕dp + */ + public static int[] getDp(Context context) { + int width = getScreenPixel(context).widthPixels; // 屏幕宽度(像素) + int height = getScreenPixel(context).heightPixels; // 屏幕高度(像素) + float density = getDensity(getScreenPixel(context)); // 屏幕密度(0.75 / 1.0 / 1.5) + // 屏幕宽度算法:屏幕宽度(像素)/屏幕密度 + int screenWidth = (int) (width / density); // 屏幕宽度(dp) + int screenHeight = (int) (height / density);// 屏幕高度(dp) + return new int[]{screenWidth, screenHeight}; + } + + /** + * 屏幕密度 + */ + public static float getDensity(DisplayMetrics dm) { + return dm.density; // 屏幕密度(0.75 / 1.0 / 1.5); + } + + /** + * 屏幕密度dpi + */ + public static String getDensity(Context context) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + Log.d(TAG, "Density is " + displayMetrics.density + " densityDpi is " + displayMetrics.densityDpi + " height: " + displayMetrics.heightPixels + + " width: " + displayMetrics.widthPixels); + return "Density is " + displayMetrics.density + " densityDpi is " + displayMetrics.densityDpi + " height: " + displayMetrics.heightPixels + + " width: " + displayMetrics.widthPixels; + } + + + /** + * 屏幕尺寸inch + */ + public static double getScreenInch(Context context) { + + try { + int realWidth = 0, realHeight = 0; + Display display = ((Activity) context).getWindowManager().getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + if (Build.VERSION.SDK_INT >= 17) { + Point size = new Point(); + display.getRealSize(size); + realWidth = size.x; + realHeight = size.y; + } else if (Build.VERSION.SDK_INT < 17 + && Build.VERSION.SDK_INT >= 14) { + Method mGetRawH = Display.class.getMethod("getRawHeight"); + Method mGetRawW = Display.class.getMethod("getRawWidth"); + realWidth = (Integer) mGetRawW.invoke(display); + realHeight = (Integer) mGetRawH.invoke(display); + } else { + realWidth = metrics.widthPixels; + realHeight = metrics.heightPixels; + } + + return formatDouble(Math.sqrt((realWidth / metrics.xdpi) * (realWidth / metrics.xdpi) + (realHeight / metrics.ydpi) * (realHeight / metrics.ydpi)), 1); + + + } catch (Exception e) { + e.printStackTrace(); + } + + return 0; + } + + /** + * dp获取dip + * @param dp + * @return + */ + public static int convertDpToPixel(float dp, Context context) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + return (int) (dp * displayMetrics.density); + } + + /*** + * px获取dip + * @param pixel + * @return + */ + public static int convertPixelToDp(int pixel, Context context) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + return (int) (pixel / displayMetrics.density); + } + + public static String getSerialNumber(Context context) { + String serial = ""; + try { + if (Build.VERSION.SDK_INT >= 28) {//9.0+ + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { + Log.i(TAG, "getMEID meid: READ_PHONE_STATE" ); + ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.READ_PHONE_STATE}, 1567); + } else { + serial = Build.getSerial(); + } + } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+ + serial = Build.SERIAL; + } else {//8.0- + Class<?> c = Class.forName("android.os.SystemProperties"); + Method get = c.getMethod("get", String.class); + serial = (String) get.invoke(c, "ro.serialno"); + } + } catch (Exception e) { + Log.e("e", "读取设备序列号异常:" + e.toString()); + } + return serial; + } + + public static String getIMEI(Context context) { + String deviceId = ""; + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + if (null != tm) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { + Log.i(TAG, "getMEID meid: READ_PHONE_STATE" ); + ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.READ_PHONE_STATE}, 1567); + } else { + if (tm.getDeviceId() != null) { + deviceId = tm.getDeviceId(); + } else { + deviceId = Settings.Secure.getString(context.getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID); + } + } + Log.d("deviceId--->", deviceId); + } + return ""; + } + + public static String getMEID() { + try { + Class clazz = Class.forName("android.os.SystemProperties"); + Method method = clazz.getMethod("get", String.class, String.class); + + String meid = (String) method.invoke(null, "ril.cdma.meid", ""); + if (!TextUtils.isEmpty(meid)) { + Log.d(TAG, "getMEID meid: " + meid); + return meid; + } + } catch (Exception e) { + e.printStackTrace(); + Log.w(TAG, "getMEID error : " + e.getMessage()); + } + return ""; + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/util/DimensionUtils.java b/app/src/main/java/com/runt/open/mvvm/util/DimensionUtils.java new file mode 100644 index 0000000..73494a0 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/util/DimensionUtils.java @@ -0,0 +1,51 @@ +package com.runt.open.mvvm.util; + +import android.content.Context; +import android.util.DisplayMetrics; + +/** + * Created by Administrator on 2021/11/2 0002. + */ +public class DimensionUtils { + /** + * dp获取dip + * @param dp + * @return + */ + public int convertDpToPixel(float dp,Context context) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + return (int) (dp * displayMetrics.density); + } + + /*** + * px获取dip + * @param pixel + * @return + */ + public int convertPixelToDp(int pixel,Context context) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + return (int) (pixel / displayMetrics.density); + } + /** + * 把pix值转换为sp + * + * @return + */ + public static float convertPixelToSp( float pixValue,Context context) { + final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; + return pixValue / fontScale + 0.5f; + } + + /** + * 将sp值转换为px值,保证文字大小不变 + * + * @param spValue + * @param context + * (DisplayMetrics类中属性scaledDensity) + * @return + */ + public static int convertSpToPixel(Context context, float spValue) { + final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; + return (int) (spValue * fontScale + 0.5f); + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/util/GsonUtils.java b/app/src/main/java/com/runt/open/mvvm/util/GsonUtils.java new file mode 100644 index 0000000..080cdfe --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/util/GsonUtils.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. + */ +package com.runt.open.mvvm.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Json工具类. + */ +public class GsonUtils { + private static Gson gson = new GsonBuilder().create(); + + public static String toJson(Object value) { + return gson.toJson(value); + } + + public static <T> T fromJson(String json, Class<T> classOfT) throws JsonParseException { + return gson.fromJson(json, classOfT); + } + + public static <T> T fromJson(String json, Type typeOfT) throws JsonParseException { + return (T) gson.fromJson(json, typeOfT); + } + + /** + * 将对象转换为驼峰命名的json + * @param value + * @return + */ + public static String toHumpJson(Object value) { + try { + if(value instanceof Collection){ + return convertToHumpJsonArray(new JSONArray(gson.toJson(value)) ).toString(); + }else { + return convertToHumpJsonObj(new JSONObject(gson.toJson(value)) ).toString(); + } + } catch (JSONException e) { + e.printStackTrace(); + return gson.toJson(value); + } + } + + /** + * + * 将json转换为驼峰命名的json + * @param json + * @return + */ + public static String toHumpJson(String json) throws JSONException { + if(json.indexOf("[") == 0){ + return convertToHumpJsonArray(new JSONArray(json) ).toString(); + }else { + return convertToHumpJsonObj(new JSONObject(json) ).toString(); + } + } + + /** + * 驼峰命名转换 + * @param json + * @param classOfT + * @param <T> + * @return + * @throws JsonParseException + */ + public static <T> T fromJsonToHump(String json, Class<T> classOfT) throws JsonParseException, JSONException { + return gson.fromJson(toHumpJson(json), classOfT); + } + + /** + * 驼峰命名转换 + * @param json + * @param typeOfT + * @param <T> + * @return + * @throws JsonParseException + */ + public static <T> T fromJsonToHump(String json, Type typeOfT) throws JsonParseException, JSONException { + return (T) gson.fromJson(toHumpJson(json), typeOfT); + } + + /** + * 转换驼峰命名 + * @param jsonObject + * @return + */ + public static JSONObject convertToHumpJsonObj(JSONObject jsonObject){ + JSONObject temp = new JSONObject(); + Iterator<String> it = jsonObject.keys(); + while ( it.hasNext()){ + String key = it.next(); + String humpKey = humpName(key); + try { + if(jsonObject.get(key) instanceof JSONObject){ + temp.put(humpKey,convertToHumpJsonObj(jsonObject.getJSONObject(key))); + }else if(jsonObject.get(key) instanceof JSONArray){ + temp.put(humpKey,convertToHumpJsonArray(jsonObject.getJSONArray(key))); + }else { + temp.put(humpKey,jsonObject.get(key)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + return temp; + + } + + public static JSONArray convertToHumpJsonArray(JSONArray array){ + JSONArray jsons = new JSONArray(); + for(int i = 0 ; i < array.length() ; i ++){ + try { + if(array.get(i) instanceof JSONObject){ + jsons.put(convertToHumpJsonObj(array.getJSONObject(i))); + }else if(array.get(i) instanceof JSONArray){ + jsons.put(convertToHumpJsonArray(array.getJSONArray(i))); + }else { + jsons.put(array.get(i)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + return jsons; + + } + /** + * 将key转换为驼峰 + * @param param + * @return + */ + public static Map convertToHumpMap(Map<String, Object> param){ + Map temp = new TreeMap(); + for(String key: param.keySet()){ + String humpKey = humpName(key); + if(param.get(key) instanceof Map){ + temp.put(humpKey,convertToHumpMap((Map<String, Object>) param.get(key))); + }else if(param.get(key) instanceof List){ + temp.put(humpKey,convertToHumpList((List)param.get(key))); + }else { + temp.put(humpKey,param.get(key)); + } + } + return temp; + } + + + public static List convertToHumpList(List list){ + List ars = new ArrayList(); + for(Object object : list){ + if(object instanceof Map){ + ars.add(convertToHumpMap((Map)object)); + }else if(object instanceof List){ + ars.add(convertToHumpList((List)object)); + }else { + ars.add(object); + } + } + return ars; + } + /** + * 驼峰命名 + * @param name + * @return + */ + public static String humpName(String name){ + String[] strings = name.split("_"); + StringBuilder sb = new StringBuilder(); + sb.append(strings[0]); + for(int i = 1 ; i < strings.length ; i ++){ + sb.append(toUperFirst(strings[i])); + } + if(sb.toString().equals("new")){//关键字 转成大写 + return "NEW"; + } + return sb.toString(); + } + + /** + * 首字母大写 + * @param name + * @return + */ + public static String toUperFirst(String name){ + return name.substring(0,1).toUpperCase()+name.substring(1); + } + + /** + * json字符串缩进 + * @param json + * @return + */ + public static 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 ',': + if(index>0 && index < json.length()-2 && (json.charAt(index-1) != '\n') && json.charAt(index+1) == '"'){ + jsonForMatStr.append(c + "\n"); + }else{ + jsonForMatStr.append(c); + } + break; + case '}': + case ']': + jsonForMatStr.append("\n"); + level--; + jsonForMatStr.append(getLevelStr(level)); + jsonForMatStr.append(c); + break; + default: + jsonForMatStr.append(c); + break; + } + } + return jsonForMatStr.toString(); + } + private static String getLevelStr(int level) { + StringBuffer levelStr = new StringBuffer(); + for (int levelI = 0; levelI < level; levelI++) { + levelStr.append("\t");//\t或空格 + } + return levelStr.toString(); + } +} diff --git a/app/src/main/java/com/runt/open/mvvm/util/MyLog.java b/app/src/main/java/com/runt/open/mvvm/util/MyLog.java new file mode 100644 index 0000000..8a3b994 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/util/MyLog.java @@ -0,0 +1,42 @@ +package com.runt.open.mvvm.util; + +import android.util.Log; + +import com.runt.open.mvvm.BuildConfig; + + +/** + * My father is Object, ites purpose of + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-4-5. + */ +public class MyLog { + + + public static void i(String tag,String msg){ + if(BuildConfig.DEBUG) { + Log.i(tag, BuildConfig.BUILD_TYPE +" "+ msg); + } + } + + public static void e(String tag,String msg){ + if(BuildConfig.DEBUG) { + Log.e(tag, msg); + } + } + + + public static void d(String tag,String msg){ + if(BuildConfig.DEBUG) { + Log.d(tag, msg); + } + } + + + public static void v(String tag,String msg){ + if(BuildConfig.DEBUG) { + Log.v(tag, msg); + } + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/util/NetWorkUtils.java b/app/src/main/java/com/runt/open/mvvm/util/NetWorkUtils.java new file mode 100644 index 0000000..dc1d86a --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/util/NetWorkUtils.java @@ -0,0 +1,212 @@ +package com.runt.open.mvvm.util; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * My father is Object, ites purpose of + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2019-3-19. + */ + +public class NetWorkUtils { + + + /** + * 获取外网IP地址 + * @return + */ + public static String getNetIp() { + final Map<String,String> param = new HashMap<>(); + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + String line = ""; + URL infoUrl = null; + InputStream inStream = null; + try { + infoUrl = new URL("http://pv.sohu.com/cityjson?ie=utf-8"); + URLConnection connection = infoUrl.openConnection(); + HttpURLConnection httpConnection = (HttpURLConnection) connection; + int responseCode = httpConnection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + inStream = httpConnection.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8")); + StringBuilder strber = new StringBuilder(); + while ((line = reader.readLine()) != null) + strber.append(line + "\n"); + inStream.close(); + // 从反馈的结果中提取出IP地址 + int start = strber.indexOf("{"); + int end = strber.indexOf("}"); + String json = strber.substring(start, end + 1); + if (json != null) { + try { + JSONObject jsonObject = new JSONObject(json); + line = jsonObject.optString("cip"); + } catch (JSONException e) { + e.printStackTrace(); + } + } + param.put("ip",line); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + try { + thread.start(); + thread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return param.get("ip"); + } + + /*** + * 获取局域网ip + * @param context + * @return + */ + @SuppressLint("MissingPermission") + public static String getLocalIpAddress(Context context) { + + ConnectivityManager connect = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + //检查网络连接 + NetworkInfo info = connect.getActiveNetworkInfo(); + + if (info == null) { + return ""; + } + int netType = info.getType(); + int netSubtype = info.getSubtype(); + + if (netType == ConnectivityManager.TYPE_WIFI) { //WIFI + return getWifiIpAddress(context); + } else /*if (netType == ConnectivityManager.TYPE_MOBILE) { //MOBILE + return getGPRSIpAddress(); + } else */if (netType == ConnectivityManager.TYPE_ETHERNET) { //MOBILE + return getEthernetIp(); + } else { + return ""; + } + } + + /** + * 使用wifi + * @param context + * @return + */ + @SuppressLint("MissingPermission") + public static String getWifiIpAddress(Context context) { + + //获取wifi服务 + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + //判断wifi是否开启 + // if (!wifiManager.isWifiEnabled()) { + // <span style="white-space:pre"> </span> wifiManager.setWifiEnabled(true); + // } + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + int ipAddress = wifiInfo.getIpAddress(); + String ip = intToIp(ipAddress); + return ip; + } + + private static String intToIp(int i) { + return (i & 0xFF) + "." + + ((i >> 8) & 0xFF) + "." + + ((i >> 16) & 0xFF) + "." + + (i >> 24 & 0xFF); + } + /** + * 使用GPRS + * @return + */ + public static String getGPRSIpAddress() { + try { + for (Enumeration<NetworkInterface> en = NetworkInterface + .getNetworkInterfaces(); en.hasMoreElements(); ) { + NetworkInterface intf = en.nextElement(); + for (Enumeration<InetAddress> enumIpAddr = intf + .getInetAddresses(); enumIpAddr.hasMoreElements(); ) { + InetAddress inetAddress = enumIpAddr.nextElement(); + if (!inetAddress.isLoopbackAddress()) { + return inetAddress.getHostAddress().toString(); + } + } + } + } catch (SocketException ex) { + MyLog.e("Exception", ex.toString()); + } + return "127.0.0.1"; + } + + public static String getEthernetIp() { + String hostIp = null; + try { + Enumeration nis = NetworkInterface.getNetworkInterfaces(); + InetAddress ia = null; + while (nis.hasMoreElements()) { + NetworkInterface ni = (NetworkInterface) nis.nextElement(); + Enumeration<InetAddress> ias = ni.getInetAddresses(); + while (ias.hasMoreElements()) { + ia = ias.nextElement(); + if (ia instanceof Inet6Address) { + continue;// skip ipv6 + } + String ip = ia.getHostAddress(); + if (!"127.0.0.1".equals(ip)) { + hostIp = ia.getHostAddress(); + break; + } + } + } + } catch (SocketException e) { + Log.i("yao", "SocketException"); + e.printStackTrace(); + } + return hostIp; + } + + + /** + * 将得到的int类型的IP转换为String类型 + * + * @param ip + * @return + */ + public static String intIP2StringIP(int ip) { + return (ip & 0xFF) + "." + + ((ip >> 8) & 0xFF) + "." + + ((ip >> 16) & 0xFF) + "." + + (ip >> 24 & 0xFF); + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/util/SpUtils.java b/app/src/main/java/com/runt/open/mvvm/util/SpUtils.java new file mode 100644 index 0000000..5233129 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/util/SpUtils.java @@ -0,0 +1,476 @@ +package com.runt.open.mvvm.util; + +import android.content.Context; +import android.content.SharedPreferences; + +import androidx.collection.ArraySet; + +import java.util.Set; + +/** + * Created by Administrator on 2021/10/28 0028. + */ +public class SpUtils { + + static SpUtils instance; + + /** + * 获取SP实例 + * + * @return {@link SpUtils} + */ + public static SpUtils getInstance() { + if(instance == null){ + instance = new SpUtils(); + } + return instance; + } + + + public final String PROJECT = "project"; + public final String USER = "user"; + + + public boolean clearData(Context context, String keyShared){ + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + return settings.edit().clear().commit(); + } + + + public boolean clearData(Context context, String key, String keyShared){ + putString(context,key,null,keyShared); + return true; + } + + + + /** + * remove key preferences + * + * @param context + * @param key The name of the preference to modify + * @return True if the new values were successfully written to persistent storage. + */ + public void removeKey(Context context, String key, String keyShared){ + + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.remove(key); + } + + /** + * remove value preferences + * + * @param context + * @param value The name of the preference to modify + * @return True if the new values were successfully written to persistent storage. + */ + public void removeValue(Context context, String value, String keyShared){ + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.remove(value); + } + + + /** + * put string preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference + * @return True if the new values were successfully written to persistent storage. + */ + public boolean putString(Context context, String key, String value, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putString(key, value); + return editor.commit(); + } + + /** + * get string preferences + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or null. Throws ClassCastException if there is a preference with this + * name that is not a string + * @see #getString(Context, String, String) + */ + public String getString(Context context, String key, String keyShared) { + return getString(context, key, null,keyShared); + } + + /** + * get string preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a string + */ + public String getString(Context context, String key, String defaultValue, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + return settings.getString(key, defaultValue); + } + + /** + * put int preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference + * @return True if the new values were successfully written to persistent storage. + */ + public boolean putInt(Context context, String key, int value, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(key, value); + return editor.commit(); + } + + /** + * get int preferences + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this + * name that is not a int + */ + public int getInt(Context context, String key, String keyShared) { + return getInt(context, key, -1,keyShared); + } + + /** + * get int preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a int + */ + public int getInt(Context context, String key, int defaultValue, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + try { + return settings.getInt(key, defaultValue); + }catch (ClassCastException e){ + try { + return Integer.parseInt(settings.getString(key,defaultValue+"")); + }catch (NumberFormatException en){ + return defaultValue; + } + } + } + + /** + * put long preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference + * @return True if the new values were successfully written to persistent storage. + */ + public boolean putLong(Context context, String key, long value, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putLong(key, value); + return editor.commit(); + } + + /** + * get long preferences + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this + * name that is not a long + */ + public long getLong(Context context, String key, String keyShared) { + return getLong(context, key, -1,keyShared); + } + + /** + * get long preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a long + */ + public long getLong(Context context, String key, long defaultValue, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + try { + return settings.getLong(key, defaultValue); + }catch (ClassCastException e){ + try { + return Long.parseLong(settings.getString(key,defaultValue+"")); + }catch (NumberFormatException en){ + return defaultValue; + } + } + } + + /** + * put float preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference + * @return True if the new values were successfully written to persistent storage. + */ + public boolean putFloat(Context context, String key, float value, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putFloat(key, value); + return editor.commit(); + } + + /** + * get float preferences + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this + * name that is not a float + */ + public float getFloat(Context context, String key, String keyShared) { + return getFloat(context, key, -1,keyShared); + } + + /** + * get float preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a float + */ + public float getFloat(Context context, String key, float defaultValue, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + try { + return settings.getFloat(key, defaultValue); + }catch (ClassCastException e){ + try { + return Float.parseFloat(settings.getString(key,defaultValue+"")); + }catch (NumberFormatException en){ + return defaultValue; + } + } + } + + /** + * put boolean preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference + * @return True if the new values were successfully written to persistent storage. + */ + public boolean putBoolean(Context context, String key, boolean value, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putBoolean(key, value); + return editor.commit(); + } + + /** + * get boolean preferences, default is false + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or false. Throws ClassCastException if there is a preference with this + * name that is not a boolean + */ + public boolean getBoolean(Context context, String key, String keyShared) { + return getBoolean(context, key, false,keyShared); + } + + /** + * get boolean preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a boolean + */ + public boolean getBoolean(Context context, String key, boolean defaultValue, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + try { + return settings.getBoolean(key, defaultValue); + }catch (ClassCastException e){ + try { + return Boolean.parseBoolean(settings.getString(key,defaultValue+"")); + }catch (NumberFormatException en){ + return defaultValue; + } + } + } + + /** + * put boolean preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference , the value of set ,canot be the other class out of java collection + * @return True if the new values were successfully written to persistent storage. + */ + public boolean putStringSet(Context context, String key, Set value, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putStringSet(key, value); + return editor.commit(); + } + + /** + * get boolean preferences, default is false + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or false. Throws ClassCastException if there is a preference with this + * name that is not a boolean 获取出来的值最终被转换为hashset类型 + */ + public Set getStringSet(Context context, String key, String keyShared) { + return getStringSet(context, key,new ArraySet(),keyShared); + } + + /** + * get boolean preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a boolean 获取出来的值最终被转换为hashset类型 + */ + public Set getStringSet(Context context, String key, Set defaultValue, String keyShared) { + SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); + return settings.getStringSet(key, defaultValue); + } + + public boolean getBooleanValOfUser(Context context, String key){ + return getBoolean(context,key,false,USER); + } + + public boolean getBooleanValOfProject(Context context, String key){ + return getBoolean(context,key,false,PROJECT); + } + + public String getStringValOfUser(Context context, String key){ + return getString(context,key,"",USER); + } + + public String getStringValOfProject(Context context, String key){ + return getString(context,key,"",PROJECT); + } + + public int getIntValOfProject(Context context, String key){ + return getInt(context,key,0,PROJECT); + } + + public Long getLongValOfProject(Context context, String key){ + return getLong(context,key,0,PROJECT); + } + + public float getFloatValOfProject(Context context, String key){ + return getFloat(context,key,0,PROJECT); + } + + public Set getStringSetValOfProject(Context context, String key){ + return getStringSet(context,key,PROJECT); + } + + public int getIntValOfUser(Context context, String key){ + return getInt(context,key,0,USER); + } + + public Long getLongValOfUser(Context context, String key){ + return getLong(context,key,0,USER); + } + + public float getFloatValOfUser(Context context, String key){ + return getFloat(context,key,0,USER); + } + + public Set getStringSetValOfUser(Context context, String key){ + return getStringSet(context,key,USER); + } + + + public void putBooleanValOfUser(Context context, String key ,Boolean value){ + putBoolean(context,key,value,USER); + } + + public void putBooleanValOfProject(Context context, String key,Boolean value){ + putBoolean(context,key,value,PROJECT); + } + + public void putStringValOfUser(Context context,String key,String value){ + putString(context,key,value,USER); + } + + public void putStringValOfProject(Context context,String key,String value){ + putString(context,key,value,PROJECT); + } + + public void putIntValOfProject(Context context,String key,int value){ + putInt(context,key,value,PROJECT); + } + + public void putLongValOfProject(Context context,String key,long value){ + putLong(context,key,value,PROJECT); + } + + public void putFloatValOfProject(Context context,String key,float value){ + putFloat(context,key,value,PROJECT); + } + + public void putStringSetValOfProject(Context context,String key,Set value){ + putStringSet(context,key,value,PROJECT); + } + + public void putIntValOfUser(Context context,String key,int value){ + putInt(context,key,value,USER); + } + + public void putLongValOfUser(Context context,String key,long value){ + putLong(context,key,value,USER); + } + + public void putFloatValOfUser(Context context,String key,float value){ + putFloat(context,key,value,USER); + } + + public void putStringSetValOfUser(Context context,String key, Set value){ + putStringSet(context,key,value,USER); + } + + + public void removeUserKey(Context context, String key){ + removeKey(context,key,USER); + } + + public void removeProjectKey(Context context, String key){ + removeKey(context,key,PROJECT); + } + + public void removeUserValue(Context context, String Value){ + removeValue(context,Value,USER); + } + + public void removeProjectValue(Context context, String Value){ + removeValue(context,Value,PROJECT); + } + + + public void clearProjectData(Context context){ + clearData(context,PROJECT); + } + public void clearUserData(Context context){ + clearData(context,USER); + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/widgets/ClearEditText.java b/app/src/main/java/com/runt/open/mvvm/widgets/ClearEditText.java new file mode 100644 index 0000000..f75dc39 --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/widgets/ClearEditText.java @@ -0,0 +1,194 @@ +package com.runt.open.mvvm.widgets; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.CycleInterpolator; +import android.view.animation.TranslateAnimation; +import android.widget.EditText; + +import com.runt.open.mvvm.R; + + +/** + * My father is Object, ites purpose of + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-2-25. + */ +@SuppressLint("AppCompatCustomView") +public class ClearEditText extends EditText implements View.OnFocusChangeListener, + TextWatcher { + /** + * 删除按钮的引用 + */ + private Drawable mClearDrawable; + /** + * 控件是否有焦点 + */ + private boolean hasFoucs; + + /** + * 是否可清除内容 + */ + private boolean isClearable = true; + + /** + * @param context + * @Description TODO + */ + public ClearEditText(Context context) { + this(context, null); + } + + /** + * @param context + * @param attrs + * @Description TODO + */ + public ClearEditText(Context context, AttributeSet attrs) { + // 这里构造方法也很重要,不加这个很多属性不能再XML里面定义 + this(context, attrs, android.R.attr.editTextStyle); + } + + /** + * @param context + * @param attrs + * @param defStyle + * @Description TODO + */ + public ClearEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + // 获取EditText的DrawableRight,假如没有设置我们就使用默认的图片 + mClearDrawable = getCompoundDrawables()[2]; + if (mClearDrawable == null) { + // throw new + // NullPointerException("You can add drawableRight attribute in XML"); + mClearDrawable = getResources().getDrawable(R.mipmap.icon_delete); + } + + mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(), + mClearDrawable.getIntrinsicHeight()); + + // 默认设置隐藏图标 + setClearIconVisible(false); + // 设置焦点改变的监听 + setOnFocusChangeListener(this); + // 设置输入框里面内容发生改变的监听 + addTextChangedListener(this); + } + + /** + * 因为我们不能直接给EditText设置点击事件,所以我们用记住我们按下的位置来模拟点击事件 当我们按下的位置 在 EditText的宽度 - + * 图标到控件右边的间距 - 图标的宽度 和 EditText的宽度 - 图标到控件右边的间距之间我们就算点击了图标,竖直方向就没有考虑 + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + if (getCompoundDrawables()[2] != null) { + + boolean touchable = event.getX() > (getWidth() - getTotalPaddingRight()) + && (event.getX() < ((getWidth() - getPaddingRight()))); + + if (touchable && isClearable) { + this.setText(""); + } + } + } + + return super.onTouchEvent(event); + } + + /** + * 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置清除图标的显示与隐藏 + */ + @Override + public void onFocusChange(View v, boolean hasFocus) { + this.hasFoucs = hasFocus; + if (hasFocus) { + setClearIconVisible(getText().toString().length() > 0); + } else { + setClearIconVisible(false); + } + } + + /** + * 设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去 + * + * @param visible + */ + public void setClearIconVisible(boolean visible) { + Drawable right = visible ? mClearDrawable : null; + setCompoundDrawables(getCompoundDrawables()[0], + getCompoundDrawables()[1], right, getCompoundDrawables()[3]); + } + + /** + * 当输入框里面内容发生变化的时候回调的方法 + */ + @Override + public void onTextChanged(CharSequence s, int start, int count, int after) { + if (hasFoucs) { + setClearIconVisible(s.toString().length() > 0); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + + } + + @Override + public void afterTextChanged(Editable s) { + + } + + /** + * 设置晃动动画 + */ + public void startShakeAnimation() { + this.startAnimation(shakeAnimation(5)); + } + + /** + * 晃动动画 + * + * @param counts 1秒钟晃动多少下 + * @return + */ + public static Animation shakeAnimation(int counts) { + Animation translateAnimation = new TranslateAnimation(0, 10, 0, 0); + translateAnimation.setInterpolator(new CycleInterpolator(counts)); + translateAnimation.setDuration(1000); + return translateAnimation; + } + + /** + * 设置是否可清除 + * + * @param clearable + */ + public void setClearable(boolean clearable) { + isClearable = clearable; + } + + /** + * 获取是否可清除 + * + * @return + */ + public boolean getClearable() { + return isClearable; + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/widgets/QuadrateLinearLayout.java b/app/src/main/java/com/runt/open/mvvm/widgets/QuadrateLinearLayout.java new file mode 100644 index 0000000..90feadf --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/widgets/QuadrateLinearLayout.java @@ -0,0 +1,40 @@ +package com.runt.open.mvvm.widgets; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +/** + * My father is Object, ites purpose of 正方形控件 + * + * @purpose Created by Runt (qingingrunt2010@qq.com) on 2019-3-24. + */ + +public class QuadrateLinearLayout extends LinearLayout { + + public QuadrateLinearLayout(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + + public QuadrateLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public QuadrateLinearLayout(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), + getDefaultSize(0, heightMeasureSpec)); + + int childWidthSize = getMeasuredWidth(); + // 高度和宽度一样 + heightMeasureSpec = widthMeasureSpec = MeasureSpec.makeMeasureSpec( + childWidthSize, MeasureSpec.EXACTLY); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + +} diff --git a/app/src/main/java/com/runt/open/mvvm/widgets/TitleBarView.java b/app/src/main/java/com/runt/open/mvvm/widgets/TitleBarView.java new file mode 100644 index 0000000..746b72a --- /dev/null +++ b/app/src/main/java/com/runt/open/mvvm/widgets/TitleBarView.java @@ -0,0 +1,256 @@ +package com.runt.open.mvvm.widgets; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; + +import com.runt.open.mvvm.R; +import com.runt.open.mvvm.util.DimensionUtils; + + +/** + * Created by Administrator on 2021/11/1 0001. + */ +public class TitleBarView extends View { + Drawable leftDra,rightDra; + private Rect mRect; // 绘制区域 + private RectF leftClickRect,rightClickRect;//点击事件响应区域 + String titleText,rightText; + @ColorInt int titleColor,rightTextColor; + float titleSize,rightTextSize,rightPadding; + Paint textPaint,rightTextPaint,drawPaint; + int viewWidth,viewHeight; + OnClickListener leftClick,rightClick; + int touchStartX,touchStartY; + + + public TitleBarView(Context context) { + this(context,null); + } + + public TitleBarView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs,0); + } + + public TitleBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initWidget(context.obtainStyledAttributes(attrs, R.styleable.TitleBarView)); + } + + public TitleBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initWidget(context.obtainStyledAttributes(attrs,R.styleable.TitleBarView)); + } + + public void initWidget(TypedArray array){ + leftDra = array.getDrawable(R.styleable.TitleBarView_leftDrawable); + rightDra = array.getDrawable(R.styleable.TitleBarView_rightDrawable); + titleText = array.getString(R.styleable.TitleBarView_titleText); + titleColor = array.getColor(R.styleable.TitleBarView_titleTextColor, Color.BLACK); + titleSize = array.getDimension(R.styleable.TitleBarView_titleTextSize, DimensionUtils.convertSpToPixel(getContext(),16)); + rightText = array.getString(R.styleable.TitleBarView_rightText); + rightTextColor = array.getColor(R.styleable.TitleBarView_rightTextColor,Color.BLACK); + rightTextSize = array.getDimension(R.styleable.TitleBarView_rightTextSize, DimensionUtils.convertSpToPixel(getContext(),14)); + rightPadding = array.getDimension(R.styleable.TitleBarView_rightDrawablePadding,10); + + textPaint = new Paint(); + textPaint.setAntiAlias(true); // 是否抗锯齿 + //mTextPaint.setAlpha(50); // 设置alpha不透明度,范围为0~255 + textPaint.setColor(titleColor); + textPaint.setTextSize(titleSize); + // 设置画笔属性 + textPaint.setStyle(Paint.Style.FILL);//画笔属性是实心圆 + // paint.setStyle(Paint.Style.STROKE);//画笔属性是空心圆 + textPaint.setStrokeWidth(4);//设置画笔粗细 + + + rightTextPaint = new Paint(); + rightTextPaint.setAntiAlias(true); // 是否抗锯齿 + //mTextPaint.setAlpha(50); // 设置alpha不透明度,范围为0~255 + rightTextPaint.setColor(rightTextColor); + rightTextPaint.setTextSize(rightTextSize); + // 设置画笔属性 + rightTextPaint.setStyle(Paint.Style.FILL);//画笔属性是实心圆 + // paint.setStyle(Paint.Style.STROKE);//画笔属性是空心圆 + rightTextPaint.setStrokeWidth(4);//设置画笔粗细 + + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + viewWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); + viewHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); + mRect = new Rect(getPaddingLeft(),getPaddingTop(),getMeasuredWidth() - getPaddingRight(),getMeasuredHeight() - getPaddingBottom()); + Log.e("TitleBarView","onMeasure mRect:"+mRect); + + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + Log.e("TitleBarView","onDraw mRect:"+mRect); + if(leftDra != null){ + final Bitmap bitmap = ((BitmapDrawable) leftDra).getBitmap(); + float top = mRect.top+(viewHeight-bitmap.getHeight()*2f)/2; + float left = mRect.left-bitmap.getWidth()*0.5f; + leftClickRect = new RectF(left,top,left + (bitmap.getWidth()*2f),top + bitmap.getHeight()*2f); + canvas.drawBitmap(bitmap,mRect.left,mRect.top+(viewHeight-bitmap.getHeight())/2,null); + + } + if(rightDra != null){ + final Bitmap bitmap = ((BitmapDrawable) rightDra).getBitmap(); + float chaTop = (viewHeight-bitmap.getHeight())/2; + float top = mRect.top+chaTop; + float left = mRect.right-bitmap.getWidth(); + rightClickRect = new RectF(left-bitmap.getWidth()/2,top-bitmap.getHeight()/2,left+bitmap.getWidth()*2f,top+bitmap.getHeight()*1.5f); + canvas.drawBitmap(bitmap,left,top,null); + } + if(titleText != null){ + final int textWidth = getTextWidth(textPaint, titleText); + final int textHeight = getTextHeight(textPaint); + final Paint.FontMetrics fontMetrics = textPaint.getFontMetrics(); + canvas.drawText(titleText,mRect.left+((viewWidth-textWidth)/2),mRect.top+((viewHeight-textHeight)/2)+(0-fontMetrics.ascent),textPaint); + } + if(rightText != null){ + final int textWidth = getTextWidth(rightTextPaint, rightText); + final int textHeight = getTextHeight(rightTextPaint); + final Paint.FontMetrics fontMetrics = rightTextPaint.getFontMetrics(); + float left = mRect.right-textWidth; + float top = mRect.top+((viewHeight-textHeight)/2)+(0-fontMetrics.ascent); + rightClickRect = new RectF(left-textWidth/2,top-textHeight,left+textWidth*2f,top+textHeight/2); + canvas.drawText(rightText,left,top,rightTextPaint); + } + } + @Override + public boolean onTouchEvent(MotionEvent event) { + int x = (int) event.getX(); + int y = (int) event.getY(); + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + touchStartX = x; + touchStartY = y; + break; + case MotionEvent.ACTION_MOVE: + break; + case MotionEvent.ACTION_UP: + if(isTouched(x,y,leftClickRect) && leftClick != null){ + leftClick.onClick(this); + }else if(isTouched(x,y,rightClickRect) && rightClick != null){ + rightClick.onClick(this); + } + + break; + } + return true; + } + + /** + * 是否为点击事件 + * @param x + * @param y + * @param rectF + * @return + */ + private boolean isTouched(int x, int y, RectF rectF){ + if(rectF == null){ + return false; + } + if(x < rectF.right && x > rectF.left && + touchStartX < rectF.right && touchStartX > rectF.left && + y < rectF.bottom && y > rectF.top && + touchStartY < rectF.bottom && touchStartY > rectF.top ){ + return true; + } + return false; + } + public void setLeftDra(Drawable leftDra) { + this.leftDra = leftDra; + invalidate(); + } + + public void setRightDra(Drawable rightDra) { + this.rightDra = rightDra; + invalidate(); + } + + public void setTitleText(String titleText) { + this.titleText = titleText; + invalidate(); + } + + public void setRightText(String rightText) { + this.rightText = rightText; + invalidate(); + } + + public void setTitleColor(@ColorInt int titleColor) { + textPaint.setColor(titleColor); + invalidate(); + } + + public void setRightTextColor(@ColorInt int rightTextColor) { + rightTextPaint.setColor(rightTextColor); + invalidate(); + } + + public void setTitleSize(int titleSize) { + textPaint.setTextSize(DimensionUtils.convertSpToPixel(getContext(),titleSize)); + invalidate(); + } + + public void setRightTextSize(float rightTextSize) { + rightTextPaint.setTextSize(DimensionUtils.convertSpToPixel(getContext(),rightTextSize)); + invalidate(); + } + + public void setRightPadding(float rightPadding) { + this.rightPadding = rightPadding; + invalidate(); + } + + public void setLeftClick(OnClickListener leftClick) { + this.leftClick = leftClick; + } + + public void setRightClick(OnClickListener rightClick) { + this.rightClick = rightClick; + } + + //第二个参数是一个数组.传进去个长度跟字符串长度相同的float数组,方法调用后,里边塞的是每个字符的长度. + public int getTextWidth(Paint paint, String str) { + int iRet = 0; + if (str != null && str.length() > 0) { + int len = str.length(); + float[] widths = new float[len]; + paint.getTextWidths(str, widths); + for (int j = 0; j < len; j++) { + iRet += (int) Math.ceil(widths[j]); + } + } + return iRet; + } + + //第二个参数是一个数组.传进去个长度跟字符串长度相同的float数组,方法调用后,里边塞的是每个字符的长度. + public int getTextHeight(Paint paint) { + Paint.FontMetrics fm = paint.getFontMetrics(); + float height1 = fm.descent - fm.ascent;//文字的高度 + float height2 = fm.bottom - fm.top + fm.leading;//行高 + return (int) height2; + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeWidth="1" + android:strokeColor="#00000000" /> +</vector> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_dashboard_black_24dp.xml b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml new file mode 100644 index 0000000..663d572 --- /dev/null +++ b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_home_black_24dp.xml b/app/src/main/res/drawable/ic_home_black_24dp.xml new file mode 100644 index 0000000..70fb291 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:fillColor="#3DDC84" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> +</vector> diff --git a/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/app/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 0000000..7009a67 --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/> +</vector> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..a57477b --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,35 @@ +<?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" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="?attr/actionBarSize" > + + <com.google.android.material.bottomnavigation.BottomNavigationView + android:id="@+id/nav_view" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="0dp" + android:layout_marginEnd="0dp" + android:background="?android:attr/windowBackground" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + 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> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml new file mode 100644 index 0000000..0ef53b9 --- /dev/null +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ui.dashboard.DashboardFragment" > + + <TextView + android:id="@+id/text_dashboard" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:textAlignment="center" + android:textSize="20sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 0000000..7ecfe18 --- /dev/null +++ b/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ui.home.HomeFragment" > + + <TextView + android:id="@+id/text_home" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:textAlignment="center" + android:textSize="20sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml new file mode 100644 index 0000000..01a3222 --- /dev/null +++ b/app/src/main/res/layout/fragment_notifications.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ui.notifications.NotificationsFragment" > + + <TextView + android:id="@+id/text_notifications" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:textAlignment="center" + android:textSize="20sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/layout_null.xml b/app/src/main/res/layout/layout_null.xml new file mode 100644 index 0000000..9d96af7 --- /dev/null +++ b/app/src/main/res/layout/layout_null.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:minHeight="400dp" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="暂无数据" + android:layout_centerHorizontal="true" + android:layout_alignParentBottom="true" + android:layout_gravity="center_horizontal" /> +</RelativeLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/refresh_recycler.xml b/app/src/main/res/layout/refresh_recycler.xml new file mode 100644 index 0000000..fceabe9 --- /dev/null +++ b/app/src/main/res/layout/refresh_recycler.xml @@ -0,0 +1,14 @@ +<?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:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recycler" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:itemCount="1" + tools:listitem="@layout/layout_null"/> +</com.scwang.smart.refresh.layout.SmartRefreshLayout> \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml new file mode 100644 index 0000000..52321c6 --- /dev/null +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/navigation_home" + android:icon="@drawable/ic_home_black_24dp" + android:title="@string/title_home"/> + + <item + android:id="@+id/navigation_dashboard" + android:icon="@drawable/ic_dashboard_black_24dp" + android:title="@string/title_dashboard"/> + + <item + android:id="@+id/navigation_notifications" + android:icon="@drawable/ic_notifications_black_24dp" + android:title="@string/title_notifications"/> + +</menu> \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher.webp Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher.webp Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_delete.png b/app/src/main/res/mipmap-xxhdpi/icon_delete.png new file mode 100644 index 0000000..a2fdf07 --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/icon_delete.png Binary files differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp Binary files differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp Binary files differ diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml new file mode 100644 index 0000000..1887f2e --- /dev/null +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<navigation 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/mobile_navigation" + app:startDestination="@+id/navigation_home"> + + <fragment + android:id="@+id/navigation_home" + android:name="com.runt.open.mvvm.ui.home.HomeFragment" + android:label="@string/title_home" + tools:layout="@layout/fragment_home" /> + + <fragment + android:id="@+id/navigation_dashboard" + android:name="com.runt.open.mvvm.ui.dashboard.DashboardFragment" + android:label="@string/title_dashboard" + tools:layout="@layout/fragment_dashboard" /> + + <fragment + android:id="@+id/navigation_notifications" + android:name="com.runt.open.mvvm.ui.notifications.NotificationsFragment" + android:label="@string/title_notifications" + tools:layout="@layout/fragment_notifications" /> +</navigation> \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..b15bd48 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="teal_700">#FF018786</color> + <color name="black">#FF000000</color> + <color name="black_4">#373737</color> + <color name="white">#FFFFFFFF</color> + <color name="white_2">#F3F3F3</color> + <color name="red">#FF1414</color> + <color name="trans_red">#4DFF1414</color> + <color name="light_gray">#E3E3E3</color> + <color name="gray">#CDCDCD</color> + <color name="deep_gray">#808080</color> + <color name="trans_gray">#4DFFFFFF</color> + <color name="enable">#ECECEC</color> + <color name="enable_sky">#889DB6</color> + <color name="deep_sky">#4184D6</color> + <color name="sky">#509CFA</color> + <color name="gold">#FAD550</color> +</resources> \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..60f9c7e --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,9 @@ +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> + <dimen name="small_radios">3dp</dimen> + <dimen name="circle">1000dp</dimen> + <dimen name="radios">10dp</dimen> + <dimen name="frame_margin_lr">10dp</dimen> +</resources> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..1c9793b --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ +<resources> + <string name="title_home">Home</string> + <string name="title_dashboard">Dashboard</string> + <string name="title_notifications">Notifications</string> +</resources> \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..f26a5c1 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <declare-styleable name="TitleBarView"> + <attr name="leftDrawable" format="reference" /> + <attr name="rightDrawable" format="reference" /> + <attr name="rightDrawablePadding" format="dimension" /> + <attr name="titleText" format="string" /> + <attr name="titleTextSize" format="dimension" /> + <attr name="titleTextColor" format="color" /> + <attr name="rightText" format="string" /> + <attr name="rightTextSize" format="dimension" /> + <attr name="rightTextColor" format="color" /> + </declare-styleable> +</resources> \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..21b5366 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ +<resources xmlns:tools="http://schemas.android.com/tools"> + <!-- Base application theme. --> + <style name="Theme.OpemMvvm" parent="Theme.MaterialComponents.Light.NoActionBar"> + <!-- Primary brand color. --> + <item name="colorPrimary">@color/sky</item> + <item name="colorPrimaryVariant">@color/red</item> + <item name="colorOnPrimary">@color/black</item> + <!-- Secondary brand color. --> + <item name="colorSecondary">@color/black_4</item> + <item name="colorSecondaryVariant">@color/black_4</item> + <item name="colorOnSecondary">@color/black</item> + <!-- Status bar color. --> + <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> + <!-- Customize your theme here. --> + </style> +</resources> \ No newline at end of file diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..dca93c0 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config cleartextTrafficPermitted="true" /> +</network-security-config> \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..7cbb664 --- /dev/null +++ b/build.gradle @@ -0,0 +1,17 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:7.0.3" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..52f5917 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar Binary files differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1b585ee --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 15 09:23:49 CST 2021 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9563052 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + jcenter() // Warning: this repository is going to shut down soon + } +} +rootProject.name = "OpemMvvm" +include ':app' -- Gitblit v1.9.1