diff --git a/base/.gitattributes b/base/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/base/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/base/.gitignore b/base/.gitignore new file mode 100644 index 0000000..dcdc776 --- /dev/null +++ b/base/.gitignore @@ -0,0 +1,83 @@ +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ diff --git a/base/README.md b/base/README.md new file mode 100644 index 0000000..2e83279 --- /dev/null +++ b/base/README.md @@ -0,0 +1,184 @@ +[TOC] + + +# WebSocket + +``````kotlin +WebSocketHandler.getInstance("ws://192.168.3.20:8765") +`````` + +# 线程 + +## UI线程执行 + +````kotlin +runOnUiThread { "提示信息".showMessage() } +```` + +````kotlin +App.getInstance().runOnUiThread() {} +```` + +## 延时执行 + +```kotlin +App.getInstance().runOnUiThreadDelay({},1100) +``` + +# 常用工具 + +## Toast + +````kotlin +"连接完成".showMessage() +```` + +````kotlin +ToolsHelper.showMessage("") +```` + +## Log + +````kotlin +"".loge() +```` + +````kotlin +"".log() +```` + +````kotlin +LogHelper.d("") +```` + + + +# 常用方法 + +## 双击退出 + +```kotlin + + private var oldTime = 0L + override fun onBackPressed() { + val newTime = System.currentTimeMillis() + if (newTime - oldTime < 1500 && oldTime != 0L) + AppManager.getInstance().exit() + else { + oldTime = newTime + ToolsHelper.showMessage("双击退出") + } + } +``` + + + + + + +# 界面 + +> 所有界面继承`BaseFragment`,`BaseActivity`,`BaseListActivity`等 +> +> 页面`layout`跟节点必须为`layout` + +```xml + + +``` + +## 列表页面 + +### 纯列表 + +> `BaseListActivity` + +### 自定义布局列表 + +> `BaseListFormLayoutActivity` +> +> 布局列表部分必须使用下面的方法和id + +```xml + + + + + + + + + +``` + +## 界面控件使用 + +```kotlin +binding.btn1.setOnClickListener { + +} +``` + +## 导航栏 + +> 使用base自带导航栏的情况下,可以操控对应控件 + +```kotlin +baseBinding.baseToolbar.backBtn.setOnClickListener {} +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/build.gradle b/base/build.gradle new file mode 100644 index 0000000..e3a4d55 --- /dev/null +++ b/base/build.gradle @@ -0,0 +1,94 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' + +// 声明aar包的版本号 +def aarVersion = "0.0.1.101" + + +android { + compileSdkVersion versions.compileSdk + buildToolsVersion versions.buildTools + + defaultConfig { + minSdkVersion versions.minSdk + targetSdkVersion versions.targetSdk + flavorDimensions "versioncode" + buildConfigField("String", "APP_ID", "\"" + apps.applicationId + "\"") + } + + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + productFlavors productF + namespace 'com.xuqm.base' +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + api androidxDependencies + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + + + implementation 'org.jetbrains.anko:anko-commons:0.10.5' + + annotationProcessor compilerDependencies + commonDependencies.each { k, v -> + api(v) { + //去除第三方的重复support库 + exclude group: 'com.android.support' + } + } + //扫码二维码 + api 'com.huawei.hms:scanplus:1.1.1.301' +} +// 这个是把源码打入aar包中的任务 +task sourceJar(type: Jar) { + archiveClassifier.set('sources') + from android.sourceSets.main.java.srcDirs +} +afterEvaluate { + publishing { + publications { + // 这里的debug名字是自己起的 + release(MavenPublication) { + groupId = 'cn.org.bjca.trust.android' + artifactId = 'base' + version = aarVersion + // 这里除了有debug 还有release + from components.release + // 运行任务,把源码打进去 + artifact sourceJar + } + } + // 添加仓库地址 + repositories { + // 本地仓库 +// mavenLocal() + // 当上传到远端仓库 +// maven { +// allowInsecureProtocol true +// url("http://nexus.51trust.net/repository/android-hosted/") +// credentials { +// username = "deployment" +// password = "deployment123" +// } +// } + maven { + allowInsecureProtocol true + url("http://xuqinmin.com.cn:8081/repository/android-releases/") + credentials { + username = "admin" + password = "xuqinmin1022" + } + } + } + } +} \ No newline at end of file diff --git a/base/consumer-rules.pro b/base/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/base/proguard-rules.pro b/base/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/base/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 diff --git a/base/src/main/AndroidManifest.xml b/base/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f3743a2 --- /dev/null +++ b/base/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/PermissionsManager.kt b/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/PermissionsManager.kt new file mode 100644 index 0000000..8b802f9 --- /dev/null +++ b/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/PermissionsManager.kt @@ -0,0 +1,155 @@ +package com.livinglifetechway.quickpermissions_kotlin + +import android.content.Context +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import com.livinglifetechway.quickpermissions_kotlin.util.PermissionCheckerFragment +import com.livinglifetechway.quickpermissions_kotlin.util.PermissionsUtil +import com.livinglifetechway.quickpermissions_kotlin.util.QuickPermissionsRequest +import com.livinglifetechway.quickpermissions_kotlin.util.QuickPermissionsOptions + +private const val TAG = "runWithPermissions" + +/** + * Injects code to ask for permissions before executing any code that requires permissions + * defined in the annotation + */ +fun Context?.runWithPermissions( + vararg permissions: String, + options: QuickPermissionsOptions = QuickPermissionsOptions(), + callback: () -> Unit +): Any? { + return runWithPermissionsHandler(this, permissions, callback, options) +} + +/** + * Injects code to ask for permissions before executing any code that requires permissions + * defined in the annotation + */ +fun Fragment?.runWithPermissions( + vararg permissions: String, + options: QuickPermissionsOptions = QuickPermissionsOptions(), + callback: () -> Unit +): Any? { + return runWithPermissionsHandler(this, permissions, callback, options) +} + +private fun runWithPermissionsHandler(target: Any?, permissions: Array, callback: () -> Unit, options: QuickPermissionsOptions): Nothing? { + Log.d(TAG, "runWithPermissions: start") + + // get the permissions defined in annotation + Log.d(TAG, "runWithPermissions: permissions to check: $permissions") + + // get target + if (target is AppCompatActivity || target is Fragment) { + Log.d(TAG, "runWithPermissions: context found") + + val context = when (target) { + is Context -> target + is Fragment -> target.context + else -> null + } + + // check if we have the permissions + if (PermissionsUtil.hasSelfPermission(context, arrayOf(*permissions))) { + Log.d(TAG, "runWithPermissions: already has required permissions. Proceed with the execution.") + callback() + } else { + // we don't have required permissions + // begin the permission request flow + + Log.d(TAG, "runWithPermissions: doesn't have required permissions") + + // check if we have permission checker fragment already attached + + // support for AppCompatActivity and Activity + var permissionCheckerFragment = when (context) { + // for app compat activity + is AppCompatActivity -> context.supportFragmentManager?.findFragmentByTag(PermissionCheckerFragment::class.java.canonicalName) as PermissionCheckerFragment? + // for support fragment + is Fragment -> context.childFragmentManager.findFragmentByTag(PermissionCheckerFragment::class.java.canonicalName) as PermissionCheckerFragment? + // else return null + else -> null + } + + // check if permission check fragment is added or not + // if not, add that fragment + if (permissionCheckerFragment == null) { + Log.d(TAG, "runWithPermissions: adding headless fragment for asking permissions") + permissionCheckerFragment = PermissionCheckerFragment.newInstance() + when (context) { + is AppCompatActivity -> { + context.supportFragmentManager.beginTransaction().apply { + add(permissionCheckerFragment, PermissionCheckerFragment::class.java.canonicalName) + commit() + } + // make sure fragment is added before we do any context based operations + context.supportFragmentManager?.executePendingTransactions() + } + is Fragment -> { + // this does not work at the moment + context.childFragmentManager.beginTransaction().apply { + add(permissionCheckerFragment, PermissionCheckerFragment::class.java.canonicalName) + commit() + } + // make sure fragment is added before we do any context based operations + context.childFragmentManager.executePendingTransactions() + } + } + } + + // set callback to permission checker fragment + permissionCheckerFragment.setListener(object : PermissionCheckerFragment.QuickPermissionsCallback { + override fun onPermissionsGranted(quickPermissionsRequest: QuickPermissionsRequest?) { + Log.d(TAG, "runWithPermissions: got permissions") + try { + callback() + } catch (throwable: Throwable) { + throwable.printStackTrace() + } + } + + override fun onPermissionsDenied(quickPermissionsRequest: QuickPermissionsRequest?) { + quickPermissionsRequest?.permissionsDeniedMethod?.invoke(quickPermissionsRequest) + } + + override fun shouldShowRequestPermissionsRationale(quickPermissionsRequest: QuickPermissionsRequest?) { + quickPermissionsRequest?.rationaleMethod?.invoke(quickPermissionsRequest) + } + + override fun onPermissionsPermanentlyDenied(quickPermissionsRequest: QuickPermissionsRequest?) { + quickPermissionsRequest?.permanentDeniedMethod?.invoke(quickPermissionsRequest) + } + }) + + // create permission request instance + val permissionRequest = QuickPermissionsRequest(permissionCheckerFragment, arrayOf(*permissions)) + permissionRequest.handleRationale = options.handleRationale + permissionRequest.handlePermanentlyDenied = options.handlePermanentlyDenied + permissionRequest.rationaleMessage = if (options.rationaleMessage.isBlank()) + "These permissions are required to perform this feature. Please allow us to use this feature. " + else + options.rationaleMessage + permissionRequest.permanentlyDeniedMessage = if (options.permanentlyDeniedMessage.isBlank()) + "Some permissions are permanently denied which are required to perform this operation. Please open app settings to grant these permissions." + else + options.permanentlyDeniedMessage + permissionRequest.rationaleMethod = options.rationaleMethod + permissionRequest.permanentDeniedMethod = options.permanentDeniedMethod + permissionRequest.permissionsDeniedMethod = options.permissionsDeniedMethod + + // begin the flow by requesting permissions + permissionCheckerFragment.setRequestPermissionsRequest(permissionRequest) + + // start requesting permissions for the first time + permissionCheckerFragment.requestPermissionsFromUser() + } + } else { + // context is null + // cannot handle the permission checking from the any class other than AppCompatActivity/Fragment + // crash the app RIGHT NOW! + throw IllegalStateException("Found " + target!!::class.java.canonicalName + " : No support from any classes other than AppCompatActivity/Fragment") + } + return null +} \ No newline at end of file diff --git a/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/PermissionCheckerFragment.kt b/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/PermissionCheckerFragment.kt new file mode 100644 index 0000000..388a772 --- /dev/null +++ b/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/PermissionCheckerFragment.kt @@ -0,0 +1,237 @@ +package com.livinglifetechway.quickpermissions_kotlin.util + + +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri.fromParts +import android.os.Bundle +import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS +import android.util.Log +import androidx.core.app.ActivityCompat +import androidx.fragment.app.Fragment +import org.jetbrains.anko.alert + +/** + * This fragment holds the single permission request and holds it until the flow is completed + */ +class PermissionCheckerFragment : Fragment() { + + private var quickPermissionsRequest: QuickPermissionsRequest? = null + + interface QuickPermissionsCallback { + fun shouldShowRequestPermissionsRationale(quickPermissionsRequest: QuickPermissionsRequest?) + fun onPermissionsGranted(quickPermissionsRequest: QuickPermissionsRequest?) + fun onPermissionsPermanentlyDenied(quickPermissionsRequest: QuickPermissionsRequest?) + fun onPermissionsDenied(quickPermissionsRequest: QuickPermissionsRequest?) + } + + companion object { + private const val TAG = "QuickPermissionsKotlin" + private const val PERMISSIONS_REQUEST_CODE = 199 + fun newInstance(): PermissionCheckerFragment = PermissionCheckerFragment() + } + + private var mListener: QuickPermissionsCallback? = null + + fun setListener(listener: QuickPermissionsCallback) { + mListener = listener + Log.d(TAG, "onCreate: listeners set") + } + + private fun removeListener() { + mListener = null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + Log.d(TAG, "onCreate: permission fragment created") + } + + fun setRequestPermissionsRequest(quickPermissionsRequest: QuickPermissionsRequest?) { + this.quickPermissionsRequest = quickPermissionsRequest + } + + private fun removeRequestPermissionsRequest() { + quickPermissionsRequest = null + } + + fun clean() { + if (quickPermissionsRequest != null) { + // permission request flow is finishing + // let the caller receive callback about it + if (quickPermissionsRequest?.deniedPermissions?.size ?: 0 > 0) + mListener?.onPermissionsDenied(quickPermissionsRequest) + + removeRequestPermissionsRequest() + removeListener() + } else { + Log.w( + TAG, "clean: QuickPermissionsRequest has already completed its flow. " + + "No further callbacks will be called for the current flow." + ) + } + } + + fun requestPermissionsFromUser() { + if (quickPermissionsRequest != null) { + Log.d(TAG, "requestPermissionsFromUser: requesting permissions") + requestPermissions( + quickPermissionsRequest?.permissions.orEmpty(), + PERMISSIONS_REQUEST_CODE + ) + } else { + Log.w( + TAG, + "requestPermissionsFromUser: QuickPermissionsRequest has already completed its flow. " + + "Cannot request permissions again from the request received from the callback. " + + "You can start the new flow by calling runWithPermissions() { } again." + ) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + Log.d(TAG, "passing callback") + + // check if permissions granted + handlePermissionResult(permissions, grantResults) + } + + /** + * Checks and takes the action based on permission results retrieved from onRequestPermissionsResult + * and from the settings activity + * + * @param permissions List of Permissions + * @param grantResults A list of permission result Granted or Denied + */ + private fun handlePermissionResult(permissions: Array, grantResults: IntArray) { + // add a check with the permissions list + // if the permissions list is empty, that means system has told that permissions request + // is invalid somehow or discarded the previous request + // this can happen in case when the multiple permissions requests are sent + // simultaneously to the system + if (permissions.isEmpty()) { + Log.w( + TAG, + "handlePermissionResult: Permissions result discarded. You might have called multiple permissions request simultaneously" + ) + return + } + + if (PermissionsUtil.hasSelfPermission(context, permissions)) { + + // set the denied permissions to empty as all the permissions are granted + // this is required as clean will be called which can invoke on permissions denied + // if it finds some permissions in the denied list + quickPermissionsRequest?.deniedPermissions = emptyArray() + + // we are good to go! + mListener?.onPermissionsGranted(quickPermissionsRequest) + + // flow complete + clean() + } else { + // we are still missing permissions + val deniedPermissions = PermissionsUtil.getDeniedPermissions(permissions, grantResults) + quickPermissionsRequest?.deniedPermissions = deniedPermissions + + // check if rationale dialog should be shown or not + var shouldShowRationale = true + var isPermanentlyDenied = false + for (i in 0 until deniedPermissions.size) { + val deniedPermission = deniedPermissions[i] + val rationale = shouldShowRequestPermissionRationale(deniedPermission) + if (!rationale) { + shouldShowRationale = false + isPermanentlyDenied = true + break + } + } + + if (quickPermissionsRequest?.handlePermanentlyDenied == true && isPermanentlyDenied) { + + quickPermissionsRequest?.permanentDeniedMethod?.let { + // get list of permanently denied methods + quickPermissionsRequest?.permanentlyDeniedPermissions = + PermissionsUtil.getPermanentlyDeniedPermissions( + this, + permissions, + grantResults + ) + mListener?.onPermissionsPermanentlyDenied(quickPermissionsRequest) + return + } + + activity?.alert { + message = quickPermissionsRequest?.permanentlyDeniedMessage.orEmpty() + positiveButton("SETTINGS") { + openAppSettings() + } + negativeButton("CANCEL") { + clean() + } + }?.apply { isCancelable = false }?.show() + return + } + + // if should show rationale dialog + if (quickPermissionsRequest?.handleRationale == true && shouldShowRationale) { + + quickPermissionsRequest?.rationaleMethod?.let { + mListener?.shouldShowRequestPermissionsRationale(quickPermissionsRequest) + return + } + + activity?.alert { + message = quickPermissionsRequest?.rationaleMessage.orEmpty() + positiveButton("TRY AGAIN") { + requestPermissionsFromUser() + } + negativeButton("CANCEL") { + clean() + } + }?.apply { isCancelable = false }?.show() + return + } + + // if handlePermanentlyDenied = false and handleRationale = false + // This will call permissionsDenied method + clean() + } + } + + fun openAppSettings() { + if (quickPermissionsRequest != null) { + val intent = Intent( + ACTION_APPLICATION_DETAILS_SETTINGS, + fromParts("package", activity?.packageName, null) + ) + // intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityForResult(intent, PERMISSIONS_REQUEST_CODE) + } else { + Log.w( + TAG, + "openAppSettings: QuickPermissionsRequest has already completed its flow. Cannot open app settings" + ) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == PERMISSIONS_REQUEST_CODE) { + val permissions = quickPermissionsRequest?.permissions ?: emptyArray() + val grantResults = IntArray(permissions.size) + permissions.forEachIndexed { index, s -> + grantResults[index] = context?.let { ActivityCompat.checkSelfPermission(it, s) } + ?: PackageManager.PERMISSION_DENIED + } + + handlePermissionResult(permissions, grantResults) + } + } +} diff --git a/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/PermissionsUtil.kt b/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/PermissionsUtil.kt new file mode 100644 index 0000000..1f6cda4 --- /dev/null +++ b/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/PermissionsUtil.kt @@ -0,0 +1,54 @@ +package com.livinglifetechway.quickpermissions_kotlin.util + +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat +import androidx.fragment.app.Fragment + +/** + * Utility class that wraps access to the runtime permissions API in M and provides basic helper + * methods. + */ +object PermissionsUtil { + + fun getDeniedPermissions(permissions: Array, grantResults: IntArray): Array = + permissions.filterIndexed { index, s -> + grantResults[index] == PackageManager.PERMISSION_DENIED + }.toTypedArray() + + fun getPermanentlyDeniedPermissions( + fragment: Fragment, + permissions: Array, + grantResults: IntArray + ): Array = + permissions.filterIndexed { index, s -> + grantResults[index] == PackageManager.PERMISSION_DENIED && !fragment.shouldShowRequestPermissionRationale( + s + ) + }.toTypedArray() + + /** + * Returns true if the Activity has access to all given permissions. + * Always returns true on platforms below M. + * + * @see Activity.checkSelfPermission + */ + fun hasSelfPermission(activity: Context?, permissions: Array): Boolean { + // Verify that all required permissions have been granted + activity?.let { + for (permission in permissions) { + if (ActivityCompat.checkSelfPermission( + activity, + permission + ) != PackageManager.PERMISSION_GRANTED + ) { + return false + } + } + } + + return true + } + +} \ No newline at end of file diff --git a/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/QuickPermissionsOptions.kt b/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/QuickPermissionsOptions.kt new file mode 100644 index 0000000..36ed66c --- /dev/null +++ b/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/QuickPermissionsOptions.kt @@ -0,0 +1,11 @@ +package com.livinglifetechway.quickpermissions_kotlin.util + +data class QuickPermissionsOptions( + var handleRationale: Boolean = true, + var rationaleMessage: String = "", + var handlePermanentlyDenied: Boolean = true, + var permanentlyDeniedMessage: String = "", + var rationaleMethod: ((QuickPermissionsRequest) -> Unit)? = null, + var permanentDeniedMethod: ((QuickPermissionsRequest) -> Unit)? = null, + var permissionsDeniedMethod: ((QuickPermissionsRequest) -> Unit)? = null +) \ No newline at end of file diff --git a/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/QuickPermissionsRequest.kt b/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/QuickPermissionsRequest.kt new file mode 100644 index 0000000..84f9ea0 --- /dev/null +++ b/base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/QuickPermissionsRequest.kt @@ -0,0 +1,30 @@ +package com.livinglifetechway.quickpermissions_kotlin.util + +data class QuickPermissionsRequest( + private var target: PermissionCheckerFragment, + var permissions: Array = emptyArray(), + var handleRationale: Boolean = true, + var rationaleMessage: String = "", + var handlePermanentlyDenied: Boolean = true, + var permanentlyDeniedMessage: String = "", + internal var rationaleMethod: ((QuickPermissionsRequest) -> Unit)? = null, + internal var permanentDeniedMethod: ((QuickPermissionsRequest) -> Unit)? = null, + internal var permissionsDeniedMethod: ((QuickPermissionsRequest) -> Unit)? = null, + var deniedPermissions: Array = emptyArray(), + var permanentlyDeniedPermissions: Array = emptyArray() +) { + /** + * Proceed with requesting permissions again with user request + */ + fun proceed() = target.requestPermissionsFromUser() + + /** + * Cancels the current permissions request flow + */ + fun cancel() = target.clean() + + /** + * In case of permissions permanently denied, request user to enable from app settings + */ + fun openAppSettings() = target.openAppSettings() +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/App.java b/base/src/main/java/com/xuqm/base/App.java new file mode 100644 index 0000000..094aabb --- /dev/null +++ b/base/src/main/java/com/xuqm/base/App.java @@ -0,0 +1,111 @@ +package com.xuqm.base; + +import android.app.Application; +import android.content.Context; +import android.os.Handler; +import android.util.DisplayMetrics; +import android.view.WindowManager; + +import androidx.annotation.Nullable; + +import com.orhanobut.logger.AndroidLogAdapter; +import com.orhanobut.logger.FormatStrategy; +import com.orhanobut.logger.Logger; +import com.orhanobut.logger.PrettyFormatStrategy; +import com.xuqm.base.di.component.AppComponent; +import com.xuqm.base.di.manager.HttpManager; + +public class App extends Application { + + public AppComponent appComponent; + // 宽高 + private int width = 0, height = 0; + + + private static App instance; + + public static App getInstance() { + if (null == instance) { + synchronized (App.class) { + if (null == instance) + instance = new App(); + } + } + return instance; + } + + public App() { + instance = this; + handler = new Handler(); + appComponent = HttpManager.getAppComponent(""); + } + + //https://www.wanandroid.com/wxarticle/list/408/1/json + @Override + public void onCreate() { + super.onCreate(); + DisplayMetrics dm = new DisplayMetrics(); + WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE); + if (null != wm) { + wm.getDefaultDisplay().getMetrics(dm); + width = dm.widthPixels;// 屏幕宽度 + height = dm.heightPixels;// 屏幕高度 + } + + FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder() + .showThreadInfo(false) // (Optional) Whether to show thread info or not. Default true + .methodCount(0) // (Optional) How many method line to show. Default 2 + .methodOffset(2) // (Optional) Hides internal method calls up to offset. Default 5 + //.logStrategy(customLog) // (Optional) Changes the log strategy to print out. Default LogCat + .tag("LogHttpInfo") // (Optional) Global tag for every log. Default PRETTY_LOGGER + .build(); + + Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy) { + @Override + public boolean isLoggable(int priority, @Nullable String tag) { + return showLog(); + } + }); + + + } + + /** + * 是否打印日志 + * + * @return true-开启日志 + */ + public boolean showLog() { + return true; + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } + + private final Handler handler; + + /** + * 提交主线程处理 + * + * @param runnable runnable + */ + public void runOnUiThread(final Runnable runnable) { + handler.post(runnable); + } + + /** + * 提交主线程,延时后处理 + * + * @param runnable runnable + * @param delayMillis 延时时间 + */ + public void runOnUiThreadDelay(final Runnable runnable, long delayMillis) { + handler.postDelayed(runnable, delayMillis); + } + +} diff --git a/base/src/main/java/com/xuqm/base/CrashHandler.java b/base/src/main/java/com/xuqm/base/CrashHandler.java new file mode 100644 index 0000000..2d261e1 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/CrashHandler.java @@ -0,0 +1,222 @@ +package com.xuqm.base; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; +import android.os.Environment; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import com.xuqm.base.common.FileHelper; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.Field; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + + +/** + * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告. + * + * @author user + */ +public class CrashHandler implements UncaughtExceptionHandler { + + public static final String TAG = "CrashHandler"; + + // CrashHandler 实例 + private static CrashHandler INSTANCE = new CrashHandler(); + + // 程序的 Context 对象 + private Context mContext; + + // 系统默认的 UncaughtException 处理类 + private UncaughtExceptionHandler mDefaultHandler; + + // 用来存储设备信息和异常信息 + private Map infos = new HashMap(); + + // 用于格式化日期,作为日志文件名的一部分 + private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()); + + /** + * 保证只有一个 CrashHandler 实例 + */ + private CrashHandler() { + } + + /** + * 获取 CrashHandler 实例 ,单例模式 + */ + public static CrashHandler getInstance() { + return INSTANCE; + } + + /** + * 初始化 + * + * @param context + */ + public void init(Context context) { + mContext = context; + // 获取系统默认的 UncaughtException 处理器 + mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + // 设置该 CrashHandler 为程序的默认处理器 + Thread.setDefaultUncaughtExceptionHandler(this); + } + + /** + * 当 UncaughtException 发生时会转入该函数来处理 + */ + @Override + public void uncaughtException(Thread thread, Throwable ex) { + if (!handleException(ex) && mDefaultHandler != null) { + // 如果用户没有处理则让系统默认的异常处理器来处理 + mDefaultHandler.uncaughtException(thread, ex); + } else { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + Log.e(TAG, "error : ", e); + Thread.currentThread().interrupt(); + } + +// Intent mIntent = new Intent(mContext, WelcomeActivity.class); +// mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); +// mContext.startActivity(mIntent); + android.os.Process.killProcess(android.os.Process.myPid()); + } + } + + /** + * 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成 + * + * @param ex + * @return true:如果处理了该异常信息;否则返回 false + */ + private boolean handleException(Throwable ex) { + if (ex == null) { + return false; + } + + // 使用 Toast 来显示异常信息 + new Thread() { + @Override + public void run() { + Looper.prepare(); + Toast.makeText(mContext, "好像出了点问题~~~", Toast.LENGTH_LONG).show(); + Looper.loop(); + } + }.start(); + + // 收集设备参数信息 + collectDeviceInfo(mContext); + // 保存日志文件 + saveCrashInfo2File(ex); + return true; + } + + /** + * 收集设备参数信息 + * + * @param ctx + */ + public void collectDeviceInfo(Context ctx) { + try { + PackageManager pm = ctx.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES); + + if (pi != null) { + String versionName = pi.versionName == null ? "null" : pi.versionName; + String versionCode = pi.versionCode + ""; + infos.put("versionName", versionName); + infos.put("versionCode", versionCode); + } + } catch (NameNotFoundException e) { + Log.e(TAG, "an error occured when collect package info", e); + } + + Field[] fields = Build.class.getDeclaredFields(); + for (Field field : fields) { + try { + field.setAccessible(true); + infos.put(field.getName(), field.get(null).toString()); + Log.d(TAG, field.getName() + " : " + field.get(null)); + } catch (Exception e) { + Log.e(TAG, "an error occured when collect crash info", e); + } + } + } + + /** + * 保存错误信息到文件中 * + * + * @param ex + * @return 返回文件名称, 便于将文件传送到服务器 + */ + private String saveCrashInfo2File(Throwable ex) { + StringBuffer sb = new StringBuffer(); + for (Map.Entry entry : infos.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + sb.append(String.format("%s=%s\n", key, value)); + } + + Writer writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + ex.printStackTrace(printWriter); + Throwable cause = ex.getCause(); + while (cause != null) { + cause.printStackTrace(printWriter); + cause = cause.getCause(); + } + printWriter.close(); + + String result = writer.toString(); + sb.append(result); + + long timestamp = System.currentTimeMillis(); + String time = formatter.format(new Date()); + String fileName = "crash-" + time + "-" + timestamp + ".log"; + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + String path = String.format("%scrash/", FileHelper.getRootFilePath()); + File dir = new File(path); + if (!dir.exists()) { + dir.mkdirs(); + } + FileOutputStream fos = null; + try { + fos = new FileOutputStream(path + fileName); + fos.write(sb.toString().getBytes()); + } catch (Exception e) { + Log.e(TAG, "an error occured while writing file...", e); + } finally { + try { + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + return fileName; + + + } +} diff --git a/base/src/main/java/com/xuqm/base/adapter/BaseItem.java b/base/src/main/java/com/xuqm/base/adapter/BaseItem.java new file mode 100644 index 0000000..c21f8f8 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/BaseItem.java @@ -0,0 +1,17 @@ +package com.xuqm.base.adapter; + +/** + * 所有用到{@link com.xuqm.base.ui.BaseListActivity}来做的列表页 + * 数据model都要继承BaseItem + */ +public class BaseItem { + private int s_id; + + public int getS_id() { + return s_id; + } + + public void setS_id(int s_id) { + this.s_id = s_id; + } +} diff --git a/base/src/main/java/com/xuqm/base/adapter/BaseNormalAdapter.java b/base/src/main/java/com/xuqm/base/adapter/BaseNormalAdapter.java new file mode 100644 index 0000000..09b1110 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/BaseNormalAdapter.java @@ -0,0 +1,186 @@ +package com.xuqm.base.adapter; + +import android.content.Context; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.xuqm.base.adapter.callback.AdapterItemClickListener; +import com.xuqm.base.adapter.callback.AdapterItemLongClickListener; + +import java.util.ArrayList; +import java.util.List; + +/** + * 不用{@link BasePagedAdapter}的时候,可以用这个 + *

+ * + * @param 数据各式 + */ +public abstract class BaseNormalAdapter extends RecyclerView.Adapter { + private Context context; + private AdapterItemClickListener itemClickListener;//item的点击事件 + private AdapterItemLongClickListener itemLongClickListener;//item的长按事件 + private ItemViewDelegateManager mItemViewDelegateManager;//ItemViewDelegate的管理类 + + private List list; + + private AdapterItemClickListener listener; + + public BaseNormalAdapter() { + this.list = new ArrayList<>(); + mItemViewDelegateManager = new ItemViewDelegateManager<>(); + } + + public BaseNormalAdapter(List list) { + this.list = null == list ? new ArrayList<>() : list; + mItemViewDelegateManager = new ItemViewDelegateManager<>(); + } + + public void setmDatas(List mDatas) { + this.list.clear(); + this.addmDatas(null == mDatas ? new ArrayList<>() : mDatas); + } + + public List getDatas() { + return this.list; + } + + public void addmDatas(List mDatas) { + this.list.addAll(mDatas); + notifyDataSetChanged(); + } + + public void addItem(T item) { + this.list.add(item); + notifyDataSetChanged(); + } + + public void removeItem(T item) { + this.list.remove(item); + notifyDataSetChanged(); + } + + + @Override + public int getItemViewType(int position) { + if (!useItemViewDelegateManager()) return super.getItemViewType(position); + return mItemViewDelegateManager.getItemViewType(list.get(position), position); + } + + + /** + * 判断是否有多种ItemViewType + * 根据mItemViewDelegateManager 里面存储的数量决定 + * + * @return true 有多种ItemViewType + */ + private boolean useItemViewDelegateManager() { + return mItemViewDelegateManager.getItemViewDelegateCount() > 0; + } + + /** + * 添加不同的item样式 + * + * @param itemViewDelegate 自定义的item + * @return this + */ + public BaseNormalAdapter addItemViewDelegate(ItemViewDelegate itemViewDelegate) { + mItemViewDelegateManager.addDelegate(itemViewDelegate); + return this; + } + + /** + * 添加不同的item样式 + * + * @param viewType 自定义的item type 不能重复 + * @param itemViewDelegate 自定义的item + * @return this + */ + public BaseNormalAdapter addItemViewDelegate(int viewType, ItemViewDelegate itemViewDelegate) { + mItemViewDelegateManager.addDelegate(viewType, itemViewDelegate); + return this; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + ItemViewDelegate itemViewDelegate = mItemViewDelegateManager.getItemViewDelegate(viewType); + int layoutId = itemViewDelegate.getItemViewLayoutId();//这里拿到自定义的layoutId + context = parent.getContext();//context没用传递过来,这里自己获取到 + return new ViewHolder(context, parent, layoutId); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + if (null != itemClickListener) { + holder.itemView.setOnClickListener(v -> itemClickListener.onClick(holder.itemView, list.get(position), position)); + } + if (null != itemLongClickListener) { + holder.itemView.setOnLongClickListener(v -> itemLongClickListener.onClick(holder.itemView, list.get(position), position)); + } + convert(holder, list.get(position)); + } + + @Override + public int getItemCount() { + return list.size(); + } + + /** + * 设置item点击监听 + * + * @param itemClickListener item的点击事件 + */ + public void setItemClickListener(AdapterItemClickListener itemClickListener) { + this.itemClickListener = itemClickListener; + } + + /** + * 设置item长按监听 + * + * @param itemLongClickListener item的长按事件 + */ + public void setItemLongClickListener(AdapterItemLongClickListener itemLongClickListener) { + this.itemLongClickListener = itemLongClickListener; + } + + /** + * 部分情况可以需要用到这个,比如item里面元素想要和item使用同一个回调处理 + * + * @return + */ + protected AdapterItemClickListener getItemClickListener() { + return itemClickListener; + } + + /** + * ui绘制的事件,分发给ItemViewDelegate自己处理 + * 比如settext() setOnClickListener()这些 + * + * @param holder holder + * @param item item + */ + public void convert(ViewHolder holder, T item) { + mItemViewDelegateManager.convert(holder, item, holder.getAdapterPosition()); + } + + /** + * 刷新知道item + * + * @param position position + */ + public void changeItem(int position) { + if (0 <= position && position < getItemCount()) { + notifyItemChanged(position); + } + } + + public void changeItem(int position, Object payload) { + if (0 <= position && position < getItemCount()) { + notifyItemChanged(position, payload); + } + } + +} diff --git a/base/src/main/java/com/xuqm/base/adapter/BasePagedAdapter.java b/base/src/main/java/com/xuqm/base/adapter/BasePagedAdapter.java new file mode 100644 index 0000000..c9743a1 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/BasePagedAdapter.java @@ -0,0 +1,157 @@ +package com.xuqm.base.adapter; + +import android.content.Context; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.paging.PagedListAdapter; + +import com.xuqm.base.adapter.callback.AdapterItemClickListener; +import com.xuqm.base.adapter.callback.AdapterItemLongClickListener; + +import java.util.List; + +/** + * 如果用到了{@link com.xuqm.base.ui.BaseListActivity}来展示列表页面的话,需要adapter继承这个 + *

+ * 如果item只有一种类型,可以使用{@link CommonPagedAdapter}来展示 + *

+ * 如果不用{@link CommonPagedAdapter}的话,继承后需要使用{@link #addItemViewDelegate(ItemViewDelegate)} + * 来设置展示的页面 + * + * @param + */ +public class BasePagedAdapter extends PagedListAdapter { + private Context context; + private AdapterItemClickListener itemClickListener;//item的点击事件 + private AdapterItemLongClickListener itemLongClickListener;//item的长按事件 + private ItemViewDelegateManager mItemViewDelegateManager;//ItemViewDelegate的管理类 + + public BasePagedAdapter() { + super(new Diff<>()); + mItemViewDelegateManager = new ItemViewDelegateManager<>(); + } + + + @Override + public int getItemViewType(int position) { + if (!useItemViewDelegateManager()) return super.getItemViewType(position); + return mItemViewDelegateManager.getItemViewType(getItem(position), position); + } + + + /** + * 判断是否有多种ItemViewType + * 根据mItemViewDelegateManager 里面存储的数量决定 + * + * @return true 有多种ItemViewType + */ + private boolean useItemViewDelegateManager() { + return mItemViewDelegateManager.getItemViewDelegateCount() > 0; + } + + /** + * 添加不同的item样式 + * + * @param itemViewDelegate 自定义的item + * @return this + */ + public BasePagedAdapter addItemViewDelegate(ItemViewDelegate itemViewDelegate) { + mItemViewDelegateManager.addDelegate(itemViewDelegate); + return this; + } + + /** + * 添加不同的item样式 + * + * @param viewType 自定义的item type 不能重复 + * @param itemViewDelegate 自定义的item + * @return this + */ + public BasePagedAdapter addItemViewDelegate(int viewType, ItemViewDelegate itemViewDelegate) { + mItemViewDelegateManager.addDelegate(viewType, itemViewDelegate); + return this; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + ItemViewDelegate itemViewDelegate = mItemViewDelegateManager.getItemViewDelegate(viewType); + int layoutId = itemViewDelegate.getItemViewLayoutId();//这里拿到自定义的layoutId + context = parent.getContext();//context没用传递过来,这里自己获取到 + return new ViewHolder(context, parent, layoutId); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + convert(holder, getItem(position)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List payloads) { + if (null != itemClickListener) { + holder.itemView.setOnClickListener(v -> itemClickListener.onClick(holder.itemView, getItem(position), position)); + } + if (null != itemLongClickListener) { + holder.itemView.setOnLongClickListener(v -> itemLongClickListener.onClick(holder.itemView, getItem(position), position)); + } + bindViewHolder(holder, getItem(position), position, payloads); + } + + private void bindViewHolder(ViewHolder holder, T item, int position, List payloads) { + convert(holder, item); + } + + /** + * 设置item点击监听 + * + * @param itemClickListener item的点击事件 + */ + public void setItemClickListener(AdapterItemClickListener itemClickListener) { + this.itemClickListener = itemClickListener; + } + + /** + * 设置item长按监听 + * + * @param itemLongClickListener item的长按事件 + */ + public void setItemLongClickListener(AdapterItemLongClickListener itemLongClickListener) { + this.itemLongClickListener = itemLongClickListener; + } + + /** + * 部分情况可以需要用到这个,比如item里面元素想要和item使用同一个回调处理 + * @return + */ + protected AdapterItemClickListener getItemClickListener() { + return itemClickListener; + } + + /** + * ui绘制的事件,分发给ItemViewDelegate自己处理 + * 比如settext() setOnClickListener()这些 + * @param holder holder + * @param item item + */ + public void convert(ViewHolder holder, T item) { + mItemViewDelegateManager.convert(holder, item, holder.getAdapterPosition()); + } + + /** + * 刷新知道item + * @param position position + */ + public void changeItem(int position) { + if (0 <= position && position < getItemCount()) { + notifyItemChanged(position); + } + } + + public void changeItem(int position, Object payload) { + if (0 <= position && position < getItemCount()) { + notifyItemChanged(position, payload); + } + } + +} diff --git a/base/src/main/java/com/xuqm/base/adapter/CommonAdapter.java b/base/src/main/java/com/xuqm/base/adapter/CommonAdapter.java new file mode 100644 index 0000000..bd06b69 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/CommonAdapter.java @@ -0,0 +1,59 @@ +package com.xuqm.base.adapter; + +import java.util.List; + +/** + * 这个adapter主要是用来简化通用adapter + * 如果item只有一种样式,或者说不需要用到itemViewType,可以直接使用这个 + *

+ * 构造函数直接传入对应的layoutId,然后重写convert方法就可以了 + * list不传的话,后面使用{@link #setmDatas(List)} 添加就可以了 + * + * @param item用到的数据类型 + */ +public abstract class CommonAdapter extends BaseNormalAdapter { + + + protected CommonAdapter(final int layoutId) { + super(); + addItemViewDelegate(new ItemViewDelegate() { + @Override + public int getItemViewLayoutId() { + return layoutId; + } + + @Override + public boolean isForViewType(T item, int position) { + return true; + } + + @Override + public void convert(ViewHolder holder, T t, int position) { + CommonAdapter.this.convert(holder, t, position); + } + }); + } + + protected CommonAdapter(final int layoutId, List list) { + super(list); + addItemViewDelegate(new ItemViewDelegate() { + @Override + public int getItemViewLayoutId() { + return layoutId; + } + + @Override + public boolean isForViewType(T item, int position) { + return true; + } + + @Override + public void convert(ViewHolder holder, T t, int position) { + CommonAdapter.this.convert(holder, t, position); + } + }); + } + + protected abstract void convert(ViewHolder holder, T item, int position); + +} diff --git a/base/src/main/java/com/xuqm/base/adapter/CommonPagedAdapter.java b/base/src/main/java/com/xuqm/base/adapter/CommonPagedAdapter.java new file mode 100644 index 0000000..a3acafe --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/CommonPagedAdapter.java @@ -0,0 +1,36 @@ +package com.xuqm.base.adapter; + +/** + * 这个adapter主要是用来简化通用列表页的绘制 + * 如果item只有一种样式,或者说不需要用到itemViewType,可以直接使用这个 + *

+ * 构造函数直接传入对应的layoutId,然后重写convert方法就可以了 + * + * @param item用到的数据类型 + */ +public abstract class CommonPagedAdapter extends BasePagedAdapter { + + + protected CommonPagedAdapter(final int layoutId) { + super(); + addItemViewDelegate(new ItemViewDelegate() { + @Override + public int getItemViewLayoutId() { + return layoutId; + } + + @Override + public boolean isForViewType(T item, int position) { + return true; + } + + @Override + public void convert(ViewHolder holder, T t, int position) { + CommonPagedAdapter.this.convert(holder, t, position); + } + }); + } + + protected abstract void convert(ViewHolder holder, T item, int position); + +} diff --git a/base/src/main/java/com/xuqm/base/adapter/Diff.java b/base/src/main/java/com/xuqm/base/adapter/Diff.java new file mode 100644 index 0000000..0b442d2 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/Diff.java @@ -0,0 +1,16 @@ +package com.xuqm.base.adapter; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; + +public class Diff extends DiffUtil.ItemCallback { + @Override + public boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem) { + return oldItem.getS_id() == newItem.getS_id(); + } + + @Override + public boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem) { + return oldItem.getS_id() == newItem.getS_id(); + } +} diff --git a/base/src/main/java/com/xuqm/base/adapter/ElasticHorizontalScrollView.java b/base/src/main/java/com/xuqm/base/adapter/ElasticHorizontalScrollView.java new file mode 100644 index 0000000..b616639 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/ElasticHorizontalScrollView.java @@ -0,0 +1,72 @@ +package com.xuqm.base.adapter; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.widget.HorizontalScrollView; + +public class ElasticHorizontalScrollView extends HorizontalScrollView { + private float x; + private DisplayMetrics metrics; + private int threshold = 0; + + public ElasticHorizontalScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + + metrics = getResources().getDisplayMetrics(); + } + + public ElasticHorizontalScrollView(Context context) { + this(context, null); + } + + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (ev == null) { + return super.onTouchEvent(ev); + } else { + return commOnTouchEvent(ev); + } + } + + public void reset() { + scrollTo(0, 0); + } + + private boolean commOnTouchEvent(MotionEvent ev) { + int action = ev.getAction(); + int length = threshold; + switch (action) { + case MotionEvent.ACTION_DOWN: + x = ev.getX(); + break; + case MotionEvent.ACTION_UP: + //复原位置 + if ((ev.getX() - x) > 0) { + if (getScrollX() > length / 2) { + smoothScrollTo(length, 0); + } else { + smoothScrollTo(0, 0); + } + } else { + if (getScrollX() > length / 2) { + smoothScrollTo(length, 0); + } else { + smoothScrollTo(0, 0); + } + } + return true; + case MotionEvent.ACTION_MOVE: + return super.onTouchEvent(ev); + default: + return true; + + } + return true; + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/adapter/FragmentAdapter.java b/base/src/main/java/com/xuqm/base/adapter/FragmentAdapter.java new file mode 100644 index 0000000..9137150 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/FragmentAdapter.java @@ -0,0 +1,30 @@ +package com.xuqm.base.adapter; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.Lifecycle; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import java.util.List; + +public class FragmentAdapter extends FragmentStateAdapter { + + private List fragments; + + public FragmentAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, List fragments) { + super(fragmentManager, lifecycle); + this.fragments = fragments; + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return fragments.get(position); + } + + @Override + public int getItemCount() { + return fragments.size(); + } +} diff --git a/base/src/main/java/com/xuqm/base/adapter/ItemViewDelegate.java b/base/src/main/java/com/xuqm/base/adapter/ItemViewDelegate.java new file mode 100644 index 0000000..90c9f19 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/ItemViewDelegate.java @@ -0,0 +1,15 @@ +package com.xuqm.base.adapter; + + +/** + * Created by zhy on 16/6/22. + */ +public interface ItemViewDelegate { + + int getItemViewLayoutId();//这个 ItemViewDelegate 将要展示的页面 + + boolean isForViewType(T item, int position); //条件判断,用来判断什么时候展示这个ItemViewDelegate + + void convert(ViewHolder holder, T item, int position);//UI绘制与事件添加 + +} diff --git a/base/src/main/java/com/xuqm/base/adapter/ItemViewDelegateManager.java b/base/src/main/java/com/xuqm/base/adapter/ItemViewDelegateManager.java new file mode 100644 index 0000000..9408856 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/ItemViewDelegateManager.java @@ -0,0 +1,95 @@ +package com.xuqm.base.adapter; + + +import androidx.collection.SparseArrayCompat; + +/** + * Created by zhy on 16/6/22. + */ +public class ItemViewDelegateManager { + private SparseArrayCompat> delegates = new SparseArrayCompat<>(); + + public int getItemViewDelegateCount() { + return delegates.size(); + } + + public ItemViewDelegateManager addDelegate(ItemViewDelegate delegate) { + int viewType = delegates.size(); + if (delegate != null) { + delegates.put(viewType, delegate); + } + return this; + } + + public ItemViewDelegateManager addDelegate(int viewType, ItemViewDelegate delegate) { + if (delegates.get(viewType) != null) { + throw new IllegalArgumentException( + "An ItemViewDelegate is already registered for the viewType = " + + viewType + + ". Already registered ItemViewDelegate is " + + delegates.get(viewType)); + } + delegates.put(viewType, delegate); + return this; + } + + public ItemViewDelegateManager removeDelegate(ItemViewDelegate delegate) { + if (delegate == null) { + throw new NullPointerException("ItemViewDelegate is null"); + } + int indexToRemove = delegates.indexOfValue(delegate); + + if (indexToRemove >= 0) { + delegates.removeAt(indexToRemove); + } + return this; + } + + public ItemViewDelegateManager removeDelegate(int itemType) { + int indexToRemove = delegates.indexOfKey(itemType); + + if (indexToRemove >= 0) { + delegates.removeAt(indexToRemove); + } + return this; + } + + int getItemViewType(T item, int position) { + int delegatesCount = delegates.size(); + for (int i = 0; i < delegatesCount; i++) { + ItemViewDelegate delegate = delegates.valueAt(i); + if (delegate.isForViewType(item, position)) { + return delegates.keyAt(i); + } + } + throw new IllegalArgumentException( + "No ItemViewDelegate added that matches position=" + position + " in data source"); + } + + void convert(ViewHolder holder, T item, int position) { + int delegatesCount = delegates.size(); + for (int i = 0; i < delegatesCount; i++) { + ItemViewDelegate delegate = delegates.valueAt(i); + + if (delegate.isForViewType(item, position)) { + delegate.convert(holder, item, position); + return; + } + } + throw new IllegalArgumentException( + "No ItemViewDelegateManager added that matches position=" + position + " in data source"); + } + + + public ItemViewDelegate getItemViewDelegate(int viewType) { + return delegates.get(viewType); + } + + public int getItemViewLayoutId(int viewType) { + return getItemViewDelegate(viewType).getItemViewLayoutId(); + } + + public int getItemViewType(ItemViewDelegate itemViewDelegate) { + return delegates.indexOfValue(itemViewDelegate); + } +} diff --git a/base/src/main/java/com/xuqm/base/adapter/SlipReAdapter.java b/base/src/main/java/com/xuqm/base/adapter/SlipReAdapter.java new file mode 100644 index 0000000..c43244a --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/SlipReAdapter.java @@ -0,0 +1,178 @@ +package com.xuqm.base.adapter; + +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; + +import androidx.recyclerview.widget.RecyclerView; + +import com.xuqm.base.R; + + +/** + * @author jose.han + * @date 2019/7/19 0019 + * @description 包装器,再原有的adpter 基础上,分装测滑功能,基于 + */ + +public class SlipReAdapter extends RecyclerView.Adapter { + + private RecyclerView.Adapter mAdapter; + private ISlipClickAction mISlipClickAction; + private int mSlipViewId; + public final static int MODE_DELETE = 0; + public final static int MODE_CLICK = 0; + private int mMode = MODE_DELETE; + private int mSlipWidth = 0; + + public static class Builder { + + private RecyclerView.Adapter mAdapter; + private ISlipClickAction mISlipClickAction; + private int mSlipViewId; + private int mMode; + private int mSlipWidth; + + public Builder setAdapter(RecyclerView.Adapter adapter) { + mAdapter = adapter; + return this; + } + + public Builder setISlipClickAction( + ISlipClickAction ISlipClickAction) { + mISlipClickAction = ISlipClickAction; + return this; + } + + public Builder setSlipViewId(int slipViewId) { + mSlipViewId = slipViewId; + return this; + } + + public Builder setMode(int mode) { + mMode = mode; + return this; + } + + public Builder setSlipWidth(float slipWidth) { + mSlipWidth = (int) slipWidth; + return this; + } + + public SlipReAdapter build() { + SlipReAdapter slipReAdapter = new SlipReAdapter(); + slipReAdapter.setAdapter(mAdapter); + slipReAdapter.setISlipClickAction(mISlipClickAction); + slipReAdapter.setMode(mMode); + slipReAdapter.setSlipViewId(mSlipViewId); + slipReAdapter.setSlipWidth(mSlipWidth); + return slipReAdapter; + } + } + + public SlipReAdapter() { + + } + + public void setAdapter(RecyclerView.Adapter adapter) { + mAdapter = adapter; + } + + public void setISlipClickAction( + ISlipClickAction ISlipClickAction) { + mISlipClickAction = ISlipClickAction; + } + + public void setSlipViewId(int slipViewId) { + mSlipViewId = slipViewId; + } + + public void setMode(int mode) { + mMode = mode; + } + + public void setSlipWidth(int slipWidth) { + mSlipWidth = slipWidth; + } + + @Override + public RViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_slip, parent, false); + LinearLayout contentLL = view.findViewById(R.id.content_ll); + LinearLayout deleteLl = view.findViewById(R.id.delete_ll); + View delete = LayoutInflater.from(parent.getContext()).inflate(mSlipViewId, null, false); + deleteLl.addView(delete); + + LayoutParams layoutParams = new LayoutParams( + parent.getResources().getDisplayMetrics().widthPixels, + ViewGroup.LayoutParams.WRAP_CONTENT); + RecyclerView.ViewHolder viewHolder = mAdapter.onCreateViewHolder(parent, viewType); + viewHolder.itemView.setLayoutParams(layoutParams); + contentLL.addView(viewHolder.itemView); + + return new RViewHolder(view, viewHolder, mSlipWidth); + } + + @Override + public void onBindViewHolder(final RViewHolder holder, final int position) { + mAdapter.onBindViewHolder(holder.mViewHolder, position); + holder.deleteLl.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mISlipClickAction.onAction(holder.getAdapterPosition()); + holder.mElasticHorizontalScrollView.reset(); + Log.i("SlipReAdapter", "slip action and the pos is:" + holder.getAdapterPosition()); + if (mMode == MODE_DELETE) { + notifyItemRemoved(holder.getAdapterPosition()); + } else if (mMode == MODE_CLICK) { + notifyItemChanged(holder.getAdapterPosition()); + } + } + }); + } + + @Override + public int getItemCount() { + return mAdapter != null ? mAdapter.getItemCount() : 0; + } + + public static class RViewHolder extends RecyclerView.ViewHolder { + + private View deleteLl; + private ElasticHorizontalScrollView mElasticHorizontalScrollView; + private RecyclerView.ViewHolder mViewHolder; + + public RViewHolder(View itemView, RecyclerView.ViewHolder viewHolder, int threshold) { + super(itemView); + mViewHolder = viewHolder; + deleteLl = itemView.findViewById(R.id.delete_ll); + mElasticHorizontalScrollView = itemView.findViewById(R.id.ElasticHorizontalScrollView); + if (threshold != 0) { + LayoutParams layoutParams = new LayoutParams(threshold, + ViewGroup.LayoutParams.WRAP_CONTENT); + deleteLl.setLayoutParams(layoutParams); + mElasticHorizontalScrollView.setThreshold(threshold); + } else { + deleteLl.post(new Runnable() { + @Override + public void run() { + int width = deleteLl.getWidth(); + mElasticHorizontalScrollView.setThreshold(width); + } + }); + } + } + } + + public interface ISlipClickAction { + + public void onAction(int position); + } + + +} diff --git a/base/src/main/java/com/xuqm/base/adapter/ViewHolder.java b/base/src/main/java/com/xuqm/base/adapter/ViewHolder.java new file mode 100644 index 0000000..7848848 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/ViewHolder.java @@ -0,0 +1,171 @@ +package com.xuqm.base.adapter; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Typeface; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.IdRes; +import androidx.annotation.LayoutRes; +import androidx.annotation.StringRes; +import androidx.recyclerview.widget.RecyclerView; + +import com.xuqm.base.adapter.callback.AdapterClickListener; +import com.xuqm.base.common.ImageHelper; + +import java.util.List; + +public class ViewHolder extends RecyclerView.ViewHolder { + + private Context context; + private ViewGroup parent; + private int layoutId; + + private SparseArray views = new SparseArray<>(); + + public ViewHolder(Context context, ViewGroup parent, @LayoutRes int layoutId) { + super(LayoutInflater.from(context).inflate(layoutId, parent, false)); + this.context = context; + this.parent = parent; + this.layoutId = layoutId; + } + + public T getView(int viewId) { + View view = views.get(viewId); + if (null == view) { + view = itemView.findViewById(viewId); + if (null == view) throw new IllegalArgumentException("not found id"); + views.put(viewId, view); + } + return (T) view; + } + + public ViewHolder setText(@IdRes int viewId, CharSequence text) { + TextView textView = getView(viewId); + textView.setText(text); + return this; + } + + public ViewHolder setTypeface(@IdRes int viewId, Typeface typeface) { + TextView textView = getView(viewId); + textView.setTypeface(typeface); + return this; + } + + public ViewHolder setEnabled(@IdRes int viewId, boolean enabled) { + View view = getView(viewId); + view.setEnabled(enabled); + return this; + } + + public ViewHolder setBackgroundResource(@IdRes int viewId, @DrawableRes int resId) { + View textView = getView(viewId); + textView.setBackgroundResource(resId); + return this; + } + + public ViewHolder setBackgroundColor(@IdRes int viewId, @ColorInt int color) { + View textView = getView(viewId); + textView.setBackgroundColor(color); + return this; + } + + public ViewHolder setTextColor(@IdRes int viewId, @ColorInt int color) { + TextView textView = getView(viewId); + textView.setTextColor(color); + return this; + } + + public ViewHolder setText(@IdRes int viewId, @StringRes int resId) { + TextView textView = getView(viewId); + textView.setText(context.getString(resId)); + return this; + } + + public ViewHolder setImageResource(@IdRes int viewId, @DrawableRes int resId) { + ImageView imageView = getView(viewId); + imageView.setImageResource(resId); + return this; + } + + public ViewHolder setImage(@IdRes int viewId, String url) { + ImageView imageView = getView(viewId); + ImageHelper.load(imageView, url); + return this; + } + + public ViewHolder setImage(@IdRes int viewId, @DrawableRes int resourceId, String url) { + ImageView imageView = getView(viewId); + ImageHelper.load(imageView, resourceId, url); + return this; + } + + public ViewHolder setImage(@IdRes int viewId, @DrawableRes int placeholder, @DrawableRes int error, String url) { + ImageView imageView = getView(viewId); + ImageHelper.load(imageView, placeholder, error, url); + return this; + } + + public ViewHolder setImage(@IdRes int viewId, Bitmap bitmap) { + ImageView imageView = getView(viewId); + ImageHelper.load(imageView, bitmap); + return this; + } + + public ViewHolder gone(@IdRes int viewId) { + View view = getView(viewId); + view.setVisibility(View.GONE); + return this; + } + + public ViewHolder invisible(@IdRes int viewId) { + View view = getView(viewId); + view.setVisibility(View.INVISIBLE); + return this; + } + + public ViewHolder gone(View view) { + view.setVisibility(View.GONE); + return this; + } + + public ViewHolder visible(@IdRes int viewId) { + View view = getView(viewId); + view.setVisibility(View.VISIBLE); + return this; + } + + public ViewHolder setVisibility(@IdRes int viewId, boolean isVisible) { + View view = getView(viewId); + if (isVisible) view.setVisibility(View.VISIBLE); + else view.setVisibility(View.GONE); + return this; + } + + public ViewHolder visible(View view) { + view.setVisibility(View.VISIBLE); + return this; + } + + public ViewHolder setClickListener(@IdRes int viewId, AdapterClickListener adapterClickListener) { + View view = getView(viewId); + if (null != view) view.setOnClickListener(adapterClickListener::onClick); + return this; + } + + public ViewHolder setClickListener(List viewIds, AdapterClickListener adapterClickListener) { + for (Integer viewId : viewIds) { + View view = getView(viewId); + if (null != view) view.setOnClickListener(adapterClickListener::onClick); + } + return this; + } + +} diff --git a/base/src/main/java/com/xuqm/base/adapter/callback/AdapterClickListener.java b/base/src/main/java/com/xuqm/base/adapter/callback/AdapterClickListener.java new file mode 100644 index 0000000..e98938d --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/callback/AdapterClickListener.java @@ -0,0 +1,10 @@ +package com.xuqm.base.adapter.callback; + +import android.view.View; + +/** + * adapter中,为item元素设置点击时间时候用到的监听 + */ +public interface AdapterClickListener { + void onClick(View view); +} diff --git a/base/src/main/java/com/xuqm/base/adapter/callback/AdapterItemClickListener.java b/base/src/main/java/com/xuqm/base/adapter/callback/AdapterItemClickListener.java new file mode 100644 index 0000000..84b42bc --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/callback/AdapterItemClickListener.java @@ -0,0 +1,11 @@ +package com.xuqm.base.adapter.callback; + +import android.view.View; + +/** + * item设置点击事件的监听 + * @param + */ +public interface AdapterItemClickListener { + void onClick(View view, T item, int position); +} diff --git a/base/src/main/java/com/xuqm/base/adapter/callback/AdapterItemLongClickListener.java b/base/src/main/java/com/xuqm/base/adapter/callback/AdapterItemLongClickListener.java new file mode 100644 index 0000000..c7a07bb --- /dev/null +++ b/base/src/main/java/com/xuqm/base/adapter/callback/AdapterItemLongClickListener.java @@ -0,0 +1,11 @@ +package com.xuqm.base.adapter.callback; + +import android.view.View; + +/** + * item设置长按事件的监听 + * @param + */ +public interface AdapterItemLongClickListener { + boolean onClick(View view, T item, int position); +} diff --git a/base/src/main/java/com/xuqm/base/common/AESCBCUtil.java b/base/src/main/java/com/xuqm/base/common/AESCBCUtil.java new file mode 100644 index 0000000..39e1ad4 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/AESCBCUtil.java @@ -0,0 +1,143 @@ +package com.xuqm.base.common; + +import android.text.TextUtils; +import android.util.Base64; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Locale; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class AESCBCUtil { //密码 + private static final String key = "12345678901234567890123456789012"; + //iv偏移量 + private static final String iv = "1234567890123456"; + + /** + * 加密:对字符串进行加密,并返回十六进制字符串(hex) + * + * @param encryptStr 需要加密的字符串 + * @return 加密后的十六进制字符串(hex) + */ + public static String encrypt(String encryptStr, String key, String iv) { + try { + IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8)); + SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING"); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec); + + byte[] encrypted = cipher.doFinal(encryptStr.getBytes()); + + + byte[] encode = Base64.encode(encrypted, Base64.DEFAULT); + + return new String(encode).replace("\n",""); + // return byte2HexStr(encrypted); + } catch (Exception ex) { + ex.printStackTrace(); + } + + return null; + } + + /** + * 解密:对加密后的十六进制字符串(hex)进行解密,并返回字符串 + * + * @param encryptedStr 需要解密的,加密后的十六进制字符串 + * @return 解密后的字符串 + */ + public static String decrypt(String encryptedStr, String key, String iv) { + try { + IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8)); + SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); + + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING"); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec); + + + byte[] decode = Base64.decode(encryptedStr, Base64.DEFAULT); + + // byte[] bytes = hexStr2Bytes(encryptedStr); + byte[] original = cipher.doFinal(decode); + + return new String(original); + } catch (Exception ex) { + ex.printStackTrace(); + } + + return null; + } + + /** + * 十六进制字符串转换为byte[] + * + * @param hexStr 需要转换为byte[]的字符串 + * @return 转换后的byte[] + */ + public static byte[] hexStr2Bytes(String hexStr) { + + + /*对输入值进行规范化整理*/ + hexStr = hexStr.trim().replace(" ", "").toUpperCase(Locale.US); + //处理值初始化 + int m = 0, n = 0; + int iLen = hexStr.length() / 2; //计算长度 + byte[] ret = new byte[iLen]; //分配存储空间 + + for (int i = 0; i < iLen; i++) { + m = i * 2 + 1; + n = m + 1; + ret[i] = (byte) (Integer.decode("0x" + hexStr.substring(i * 2, m) + hexStr.substring(m, n)) & 0xFF); + } + return ret; + } + + + /** + * byte[]转换为十六进制字符串 + * + * @param bytes 需要转换为字符串的byte[] + * @return 转换后的十六进制字符串 + */ + public static String byte2HexStr(byte[] bytes) { + String hs = ""; + String stmp = ""; + for (int n = 0; n < bytes.length; n++) { + stmp = (Integer.toHexString(bytes[n] & 0XFF)); + if (stmp.length() == 1) + hs = hs + "0" + stmp; + else + hs = hs + stmp; + } + return hs; + } + + public static String md5(String string) { + if (TextUtils.isEmpty(string)) { + return ""; + } + MessageDigest md5 = null; + try { + md5 = MessageDigest.getInstance("MD5"); + byte[] bytes = md5.digest(string.getBytes()); + String result = ""; + for (byte b : bytes) { + String temp = Integer.toHexString(b & 0xff); + if (temp.length() == 1) { + temp = "0" + temp; + } + result += temp; + } + return result.toUpperCase(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return ""; + } +} diff --git a/base/src/main/java/com/xuqm/base/common/AppManager.java b/base/src/main/java/com/xuqm/base/common/AppManager.java new file mode 100644 index 0000000..5ee6bf1 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/AppManager.java @@ -0,0 +1,106 @@ +package com.xuqm.base.common; + +import android.app.Activity; + +import java.util.Stack; + +/** + * activity的管理栈 + */ +public class AppManager { + + + public static AppManager getInstance() { + return APPHolder.INSTANCE; + } + + private static class APPHolder { + private static final AppManager INSTANCE = new AppManager(); + } + + private AppManager() { + activityStack = new Stack<>(); + } + + private final Stack activityStack; + + //添加一个新的act + public void pushActivity(Activity activity) { + activityStack.add(activity); + } + + /** + * 推出一个activity 其实toolbar的返回按钮,可以直接使用这个方法 + * + * @param activity 需要退出的activity + */ + public void popActivity(Activity activity) { + if (activityStack != null && activityStack.size() > 0) { + if (activity != null) { + activity.finish(); + activityStack.remove(activity); + } + + } + } + + /** + * 获取当前最上面的那个activity + * + * @return + */ + public Activity getActivity() { + return activityStack.lastElement(); + } + + /** + * finish最后一个 + * + * @return + */ + public void finish() { + this.popActivity(this.getActivity()); + } + + + /** + * finish最后一个之外的所有页面 + * + * @return + */ + public void logout() { + if (activityStack.size() < 1) + return; + for (int i = 0; i < activityStack.size() - 1; i++) { + Activity activity = activityStack.firstElement(); + if (activity == null) break; + popActivity(activity); + } + } + + /** + * 退出app + */ + public void exit() { + if (activityStack != null) { + while (activityStack.size() > 0) { + Activity activity = getActivity(); + if (activity == null) break; + popActivity(activity); + } + } + android.os.Process.killProcess(android.os.Process.myPid()); + } + + /** + * 退出app + */ + public void exitWithOutLast() { + if (activityStack != null) { + int size = activityStack.size(); + for (int i = 0; i < size - 1; i++) { + popActivity(activityStack.get(0)); + } + } + } +} diff --git a/base/src/main/java/com/xuqm/base/common/DeviceUuidFactory.java b/base/src/main/java/com/xuqm/base/common/DeviceUuidFactory.java new file mode 100644 index 0000000..cfa47cf --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/DeviceUuidFactory.java @@ -0,0 +1,87 @@ +package com.xuqm.base.common; + +import android.content.Context; +import android.content.SharedPreferences; +import android.provider.Settings.Secure; +import android.telephony.TelephonyManager; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +public class DeviceUuidFactory { + protected static final String PREFS_FILE = "device_id.xml"; + protected static final String PREFS_DEVICE_ID = "device_id"; + + protected static volatile UUID uuid; + + + + public DeviceUuidFactory(Context context) { + + if( uuid ==null ) { + synchronized (DeviceUuidFactory.class) { + if( uuid == null) { + final SharedPreferences prefs = context.getSharedPreferences( PREFS_FILE, 0); + final String id = prefs.getString(PREFS_DEVICE_ID, null ); + + if (id != null) { + // Use the ids previously computed and stored in the prefs file + uuid = UUID.fromString(id); + + } else { + + final String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID); + + // Use the Android ID unless it's broken, in which case fallback on deviceId, + // unless it's not available, then fallback on a random number which we store + // to a prefs file + try { + if (!"9774d56d682e549c".equals(androidId)) { + uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8")); + } else { + final String deviceId = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE )).getDeviceId(); + uuid = deviceId!=null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")) : UUID.randomUUID(); + } + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + + // Write the value out to the prefs file + prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString() ).commit(); + + } + + } + } + } + + } + + + /** + * Returns a unique UUID for the current android device. As with all UUIDs, this unique ID is "very highly likely" + * to be unique across all Android devices. Much more so than ANDROID_ID is. + * + * The UUID is generated by using ANDROID_ID as the base key if appropriate, falling back on + * TelephonyManager.getDeviceID() if ANDROID_ID is known to be incorrect, and finally falling back + * on a random UUID that's persisted to SharedPreferences if getDeviceID() does not return a + * usable value. + * + * In some rare circumstances, this ID may change. In particular, if the device is factory reset a new device ID + * may be generated. In addition, if a user upgrades their phone from certain buggy implementations of Android 2.2 + * to a newer, non-buggy version of Android, the device ID may change. Or, if a user uninstalls your app on + * a device that has neither a proper Android ID nor a Device ID, this ID may change on reinstallation. + * + * Note that if the code falls back on using TelephonyManager.getDeviceId(), the resulting ID will NOT + * change after a factory reset. Something to be aware of. + * + * Works around a bug in Android 2.2 for many devices when using ANDROID_ID directly. + * + * @see http://code.google.com/p/android/issues/detail?id=10603 + * + * @return a UUID that may be used to uniquely identify your device for most purposes. + */ + public UUID getDeviceUuid() { + return uuid; + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/common/DimensHelper.java b/base/src/main/java/com/xuqm/base/common/DimensHelper.java new file mode 100644 index 0000000..e90216a --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/DimensHelper.java @@ -0,0 +1,10 @@ +package com.xuqm.base.common; + +import android.content.Context; +import android.util.TypedValue; + +public class DimensHelper { + public static int pxToDp(Context context, int px) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, px, context.getResources().getDisplayMetrics()); + } +} diff --git a/base/src/main/java/com/xuqm/base/common/FileHelper.java b/base/src/main/java/com/xuqm/base/common/FileHelper.java new file mode 100644 index 0000000..4fd64c4 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/FileHelper.java @@ -0,0 +1,218 @@ +package com.xuqm.base.common; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; + +import androidx.annotation.NonNull; +import androidx.core.content.FileProvider; + +import com.xuqm.base.App; +import com.xuqm.base.BuildConfig; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +public class FileHelper { + + + public static String getRootFilePath() { + return App.getInstance().getExternalFilesDir(null).getPath() + + File.separator + + BuildConfig.APP_ID + + File.separator; + } + + /** + * 根据路径创建文件夹 + * + * @param filePath + * @return + */ + public static boolean createDirectory(String filePath) { + if (ToolsHelper.isNull(filePath)) { + return false; + } + + File file = new File(filePath); + + if (file.exists() && file.isDirectory()) { + return true; + } + + return file.mkdirs(); + + } + + public static void delete(String path) { + LogHelper.e(String.format("开始删除文件::%s", path)); + File file = new File(path); + if (!file.exists() + || !file.isFile()) + return; + file.delete(); + + } + public static void delete(File file) { + LogHelper.e(String.format("开始删除文件::%s", file.getAbsoluteFile())); + if (!file.exists() + || !file.isFile()) + return; + file.delete(); + + } + + public static String getVoicePath() { + String path = getRootFilePath() + + "voice" + + File.separator; + + return createDirectory(path) ? path : ""; + } + public static String getImagePath() { + String path = getRootFilePath() + + "image" + + File.separator; + + return createDirectory(path) ? path : ""; + } + + public static String getAppPath() { + String path = getRootFilePath() + + "apps" + + File.separator; + LogHelper.e(path); + + return createDirectory(path) ? path : ""; + } + public static String getDownloadPath() { + String path = getRootFilePath() + + "download" + + File.separator; + LogHelper.e(path); + + return createDirectory(path) ? path : ""; + } + + /** + * 读取assets文件夹的文件 + * + * @param strFileName 文件名,包含assets后面的路径 + * @return 文件内容 + */ + public static String readJSON(String strFileName) { + String strResult = ""; + try (InputStream is = App.getInstance().getAssets().open(strFileName)) { + int size = is.available(); + byte[] buffer = new byte[size]; + is.read(buffer); + strResult = new String(buffer, StandardCharsets.UTF_8); + } catch (Exception ex) { + LogHelper.e("readJson", ex); + } + return strResult; + } + + public static String getBitmapFilePath(String path, String suffix) { + + String pathStr = getRootFilePath() + "images" + File.separator + path + File.separator; + createDirectory(pathStr); + + return pathStr + UUID.randomUUID() + "." + suffix; + } + + /** + * 保存图片到本地 + * + * @param bitmap 图片 + * @param filePath 保存到的文件 png + * @return 状态 + */ + public static String saveBitmap(Bitmap bitmap, String filePath) { + if (bitmap == null) + return ""; + FileOutputStream fos = null; + try { + fos = new FileOutputStream(new File(filePath)); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.flush(); + return filePath; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return ""; + } + + public void installAPK(String filePath) { + Intent intent = new Intent(); + intent.setAction("android.intent.action.VIEW"); + intent.addCategory("android.intent.category.DEFAULT"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 广播里面操作需要加上这句,存在于一个独立的栈里 + intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive"); + App.getInstance().startActivity(intent); + } + + public static void openFile(Context activity, File file) { + Intent intent = new Intent(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 设置intent的Action属性 + intent.setAction(Intent.ACTION_VIEW); + // 获取文件file的MIME类型 + String type = getMIMEType(file); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + // 设置intent的data和Type属性。 + intent.setDataAndType(/* uri */FileHelper.getFileUri(file), type); + activity.startActivity(intent); + } + + public static Uri getFileUri(@NonNull File file) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return FileProvider.getUriForFile(AppManager.getInstance().getActivity(), BuildConfig.APP_ID + ".fileprovider", file); + } else { + return Uri.fromFile(file); + } + } + + private static String[][] MIME_MapTable = new String[][]{{".3gp", "video/3gpp"}, {".apk", "application/vnd.android.package-archive"}, {".asf", "video/x-ms-asf"}, {".avi", "video/x-msvideo"}, {".bin", "application/octet-stream"}, {".bmp", "image/bmp"}, {".c", "text/plain"}, {".class", "application/octet-stream"}, {".conf", "text/plain"}, {".cpp", "text/plain"}, {".doc", "application/msword"}, {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, {".xls", "application/vnd.ms-excel"}, {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, {".exe", "application/octet-stream"}, {".gif", "image/gif"}, {".gtar", "application/x-gtar"}, {".gz", "application/x-gzip"}, {".h", "text/plain"}, {".htm", "text/html"}, {".html", "text/html"}, {".jar", "application/java-archive"}, {".java", "text/plain"}, {".jpeg", "image/jpeg"}, {".jpg", "image/jpeg"}, {".eucppic", "image/jpeg"}, {".js", "application/x-javascript"}, {".log", "text/plain"}, {".m3u", "audio/x-mpegurl"}, {".m4a", "audio/mp4a-latm"}, {".m4b", "audio/mp4a-latm"}, {".m4p", "audio/mp4a-latm"}, {".m4u", "video/vnd.mpegurl"}, {".m4v", "video/x-m4v"}, {".mov", "video/quicktime"}, {".mp2", "audio/x-mpeg"}, {".mp3", "audio/x-mpeg"}, {".mp4", "video/mp4"}, {".mpc", "application/vnd.mpohun.certificate"}, {".mpe", "video/mpeg"}, {".mpeg", "video/mpeg"}, {".mpg", "video/mpeg"}, {".mpg4", "video/mp4"}, {".mpga", "audio/mpeg"}, {".msg", "application/vnd.ms-outlook"}, {".ogg", "audio/ogg"}, {".pdf", "application/pdf"}, {".png", "image/png"}, {".pps", "application/vnd.ms-powerpoint"}, {".ppt", "application/vnd.ms-powerpoint"}, {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, {".prop", "text/plain"}, {".rc", "text/plain"}, {".rmvb", "audio/x-pn-realaudio"}, {".rtf", "application/rtf"}, {".sh", "text/plain"}, {".tar", "application/x-tar"}, {".tgz", "application/x-compressed"}, {".txt", "text/plain"}, {".wav", "audio/x-wav"}, {".wma", "audio/x-ms-wma"}, {".wmv", "audio/x-ms-wmv"}, {".wps", "application/vnd.ms-works"}, {".xml", "text/plain"}, {".z", "application/x-compress"}, {".zip", "application/x-zip-compressed"}, {"", "*/*"}}; + + public static String getMIMEType(File file) { + String type = "*/*"; + String fName = file.getName(); + int dotIndex = fName.lastIndexOf("."); + if (dotIndex < 0) { + return type; + } else { + String end = fName.substring(dotIndex, fName.length()).toLowerCase(); + if (end == "") { + return type; + } else { + for (int i = 0; i < MIME_MapTable.length; ++i) { + if (end.equals(MIME_MapTable[i][0])) { + type = MIME_MapTable[i][1]; + } + } + + return type; + } + } + } + +} diff --git a/base/src/main/java/com/xuqm/base/common/GlideEngine.java b/base/src/main/java/com/xuqm/base/common/GlideEngine.java new file mode 100644 index 0000000..627aab8 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/GlideEngine.java @@ -0,0 +1,112 @@ +package com.xuqm.base.common; + +import android.content.Context; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.luck.picture.lib.engine.ImageEngine; +import com.luck.picture.lib.utils.ActivityCompatHelper; +import com.xuqm.base.R; + +public class GlideEngine implements ImageEngine { + + /** + * 加载图片 + * + * @param context 上下文 + * @param url 资源url + * @param imageView 图片承载控件 + */ + @Override + public void loadImage(Context context, String url, ImageView imageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return; + } + Glide.with(context) + .load(url) + .into(imageView); + } + + @Override + public void loadImage(Context context, ImageView imageView, String url, int maxWidth, int maxHeight) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return; + } + Glide.with(context) + .load(url) + .override(maxWidth, maxHeight) + .into(imageView); + } + + /** + * 加载相册目录封面 + * + * @param context 上下文 + * @param url 图片路径 + * @param imageView 承载图片ImageView + */ + @Override + public void loadAlbumCover(Context context, String url, ImageView imageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return; + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(new CenterCrop(), new RoundedCorners(8)) + .placeholder(R.drawable.ps_image_placeholder) + .into(imageView); + } + + + /** + * 加载图片列表图片 + * + * @param context 上下文 + * @param url 图片路径 + * @param imageView 承载图片ImageView + */ + @Override + public void loadGridImage(Context context, String url, ImageView imageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return; + } + Glide.with(context) + .load(url) + .override(200, 200) + .centerCrop() + .placeholder(R.drawable.ps_image_placeholder) + .into(imageView); + } + + @Override + public void pauseRequests(Context context) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return; + } + Glide.with(context).pauseRequests(); + } + + @Override + public void resumeRequests(Context context) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return; + } + Glide.with(context).resumeRequests(); + } + + private GlideEngine() { + } + + private static final class InstanceHolder { + static final GlideEngine instance = new GlideEngine(); + } + + public static GlideEngine createGlideEngine() { + return InstanceHolder.instance; + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/common/GsonImplHelp.java b/base/src/main/java/com/xuqm/base/common/GsonImplHelp.java new file mode 100644 index 0000000..fb1ad62 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/GsonImplHelp.java @@ -0,0 +1,61 @@ +package com.xuqm.base.common; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Created by xuqm on 2016/6/3. + */ +public class GsonImplHelp extends Json { + private Gson gson = new Gson(); + + @Override + public String toJson(Object src) { + return gson.toJson(src); + + } + + @Override + public T toObject(String json, Class claxx) { + return gson.fromJson(json, claxx); + + } + + @Override + public T toObject(String json, Type typeOfT) { + return gson.fromJson(json, typeOfT); + + } + + @Override + public T toObject(byte[] bytes, Class claxx) { + return gson.fromJson(new String(bytes), claxx); + + } + + public List toList(String json, Class clazz) { + JsonArray jsonArray = new JsonParser().parse(json).getAsJsonArray(); + + List list = new ArrayList<>(); + for (JsonElement jsonElement : jsonArray) { + list.add(gson.fromJson(jsonElement, clazz)); //cls + } + + return list; + + + } + + public static List stringToArray(String s, Class cls) { + T[] array = new Gson().fromJson(s, cls); + return Arrays.asList(array); + } + +} diff --git a/base/src/main/java/com/xuqm/base/common/ImageHelp.java b/base/src/main/java/com/xuqm/base/common/ImageHelp.java new file mode 100644 index 0000000..135b306 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/ImageHelp.java @@ -0,0 +1,53 @@ +package com.xuqm.base.common; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Base64; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class ImageHelp { + /* + * bitmap转base64 + * */ + public static String bitmapToBase64(Bitmap bitmap) { + String result = null; + ByteArrayOutputStream baos = null; + try { + if (bitmap != null) { + baos = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); + + baos.flush(); + baos.close(); + + byte[] bitmapBytes = baos.toByteArray(); + result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (baos != null) { + baos.flush(); + baos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return result; + } + + /** + * base64转为bitmap + * + * @param base64Data + * @return + */ + public static Bitmap base64ToBitmap(String base64Data) { + byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT); + return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + } +} diff --git a/base/src/main/java/com/xuqm/base/common/ImageHelper.java b/base/src/main/java/com/xuqm/base/common/ImageHelper.java new file mode 100644 index 0000000..374d795 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/ImageHelper.java @@ -0,0 +1,34 @@ +package com.xuqm.base.common; + +import android.widget.ImageView; + +import androidx.annotation.DrawableRes; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.request.RequestOptions; + +/** + * 一个image相关的工具类 + * + * @author xuqm + */ +public class ImageHelper { + /** + * 给imageView添加图片的方法 + * + * @param imageView 需要添加图片的控件 + * @param url url地址,可以是path draw等 + */ + public static void load(ImageView imageView, Object url) { + Glide.with(imageView).load(url).into(imageView); + } + + public static void load(ImageView imageView, @DrawableRes int resourceId, Object url) { + Glide.with(imageView).applyDefaultRequestOptions(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.AUTOMATIC)).load(url).placeholder(resourceId).error(resourceId).into(imageView); + } + + public static void load(ImageView imageView, @DrawableRes int placeholder, @DrawableRes int error, Object url) { + Glide.with(imageView).applyDefaultRequestOptions(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.AUTOMATIC)).load(url).placeholder(placeholder).error(error).into(imageView); + } +} diff --git a/base/src/main/java/com/xuqm/base/common/Json.java b/base/src/main/java/com/xuqm/base/common/Json.java new file mode 100644 index 0000000..4c57223 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/Json.java @@ -0,0 +1,31 @@ +package com.xuqm.base.common; + +import java.lang.reflect.Type; +import java.util.List; + +/** + * Created by xuqm on 2016/6/3. + */ +public abstract class Json { + private static Json json; + + Json() { + } + + public static Json get() { + if (json == null) { + json = new GsonImplHelp(); + } + return json; + } + + public abstract String toJson(Object src); + + public abstract T toObject(String json, Class claxx); + public abstract T toObject(String json, Type typeOfT); + + public abstract T toObject(byte[] bytes, Class claxx); + + public abstract List toList(String json, Class claxx); + +} diff --git a/base/src/main/java/com/xuqm/base/common/LogHelper.java b/base/src/main/java/com/xuqm/base/common/LogHelper.java new file mode 100644 index 0000000..8521ecd --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/LogHelper.java @@ -0,0 +1,75 @@ +package com.xuqm.base.common; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.orhanobut.logger.Logger; + +import java.util.Locale; + +/** + * 日志库用的是 KLog + * 平常自己调试时候不想用那么多,就随便写了个类 + */ +public class LogHelper { + + + public static void d(String tag, Object object) { + Logger.t(tag).d(object); + } + + public static void d(Object object) { + StackTraceElement caller = getCallerStackTraceElement(); + String tag = generateTag(caller); + Logger.t(tag).d(object); + } + + public static void d(@NonNull String message, @Nullable Object... args) { + Logger.t(message).d(args); + } + + public static void e(String tag, Object object) { + Logger.t(tag).e(object.toString()); + } + + public static void e(Object object) { + if (null == object){ + return; + } + StackTraceElement caller = getCallerStackTraceElement(); + String tag = generateTag(caller); + Logger.t(tag).e("=====>" + object.toString()); + } + + public static void e(String msg, Throwable tr) { + StackTraceElement caller = getCallerStackTraceElement(); + String tag = generateTag(caller); + Logger.t(tag).e(tr, msg); + } + + public static void e(String tag, String msg, Throwable tr) { + Logger.t(tag).e(tr, msg); + } + + public static void json(String msg) { + StackTraceElement caller = getCallerStackTraceElement(); + String tag = generateTag(caller); + Logger.t(tag).json(msg); + } + + public static void json(String tag, String msg) { + Logger.t(tag).json(msg); + } + + + private static String generateTag(StackTraceElement caller) { + String tag = "%s.%s(L:%d)"; + String callerClazzName = caller.getClassName(); + callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1); + return String.format(Locale.getDefault(), tag, callerClazzName, caller.getMethodName(), caller.getLineNumber()); + } + + private static StackTraceElement getCallerStackTraceElement() { + return Thread.currentThread().getStackTrace()[4]; + } +} diff --git a/base/src/main/java/com/xuqm/base/common/RefreshResult.java b/base/src/main/java/com/xuqm/base/common/RefreshResult.java new file mode 100644 index 0000000..932b1aa --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/RefreshResult.java @@ -0,0 +1,8 @@ +package com.xuqm.base.common; + +/** + * 下拉刷新的状态码表 + */ +public enum RefreshResult { + SUCCEED, FAILED, NO_DATA, NO_MORE +} diff --git a/base/src/main/java/com/xuqm/base/common/ScreenUtils.java b/base/src/main/java/com/xuqm/base/common/ScreenUtils.java new file mode 100644 index 0000000..46772c6 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/ScreenUtils.java @@ -0,0 +1,18 @@ +package com.xuqm.base.common; + +import android.content.Context; + +public class ScreenUtils { + /** + * 获取屏幕高度(px) + */ + public static int getScreenHeight(Context context) { + return context.getResources().getDisplayMetrics().heightPixels; + } + /** + * 获取屏幕宽度(px) + */ + public static int getScreenWidth(Context context) { + return context.getResources().getDisplayMetrics().widthPixels; + } +} diff --git a/base/src/main/java/com/xuqm/base/common/SharedPreferencesConfigs.kt b/base/src/main/java/com/xuqm/base/common/SharedPreferencesConfigs.kt new file mode 100644 index 0000000..965e51b --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/SharedPreferencesConfigs.kt @@ -0,0 +1,13 @@ +package com.xuqm.base.common + + +const val SHARE_UESR_NAME = "share_user_name" +const val SHARE_UESR_PASSWORD = "share_user_password" +const val SHARE_LOGIN_OBJ = "SHARE_LOGIN_OBJ" +const val SHARE_UESR_TOKEN = "share_user_token" +const val SHARE_UESR_ID = "share_user_id" +const val SHARE_UESR_TOKEN_TIME = "share_user_token_time" + +const val MSG_COUNT = "msg_count" + +const val SHARE_COUNTRY = "share_user_country" diff --git a/base/src/main/java/com/xuqm/base/common/TimeHelper.java b/base/src/main/java/com/xuqm/base/common/TimeHelper.java new file mode 100644 index 0000000..06e241f --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/TimeHelper.java @@ -0,0 +1,94 @@ +package com.xuqm.base.common; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +public class TimeHelper { + + /** + * 获取当前时间戳 + * + * @return 时间戳 + */ + public static long getTimeMillis() { + return System.currentTimeMillis(); + } + + /** + * 获取当前时间,指定返回样式 + * + * @param formats 指定样式 + * @return 时间字符串 + */ + public static String getTimeString(String formats) { + return getStringFormMillis(System.currentTimeMillis(), formats); + } + + /** + * 根据给定时间戳和样式,返回字符串 + * + * @param millis 时间戳 + * @param formats 指定字符串格式 + * @return 时间字符串 + */ + public static String getStringFormMillis(long millis, String formats) { + Date date = new Date(millis); + return new SimpleDateFormat(formats, Locale.CHINESE).format(date); + } + + public static String getStringFormMillisForGMT(long millis, String formats) { + Date date = new Date(millis); + + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(formats);//格式 + + simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); // 设置时区为GMT + + + return simpleDateFormat.format(date); + } + + + /** + * 根据Date 返回指定格式的时字符串 + * + * @param date 数据 + * @param formats 格式 + * @return 指定格式的字符串 + */ + public static String getStringFromDate(Date date, String formats) { + SimpleDateFormat formatter = new SimpleDateFormat(formats, Locale.getDefault()); + return formatter.format(date); + } + + /** + * 根据给定字符串和格式,获取时间戳 + * + * @param dateString 时间字符串 + * @param formats 时间格式 + * @return 时间戳 + */ + public static long getTimeMillisForType(String dateString, String formats) { + SimpleDateFormat format = new SimpleDateFormat(formats, Locale.getDefault()); + Date date = null; + try { + date = format.parse(dateString); + } catch (ParseException e) { + e.printStackTrace(); + } + + return date != null ? date.getTime() : 0; + + } + + /** + * 获取以秒为单位的时间戳 + * + * @return 秒 + */ + public static long getTimeFromSecond() { + return getTimeMillis() / 1000; + } +} diff --git a/base/src/main/java/com/xuqm/base/common/ToolsHelper.java b/base/src/main/java/com/xuqm/base/common/ToolsHelper.java new file mode 100644 index 0000000..48c5fe9 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/common/ToolsHelper.java @@ -0,0 +1,274 @@ +package com.xuqm.base.common; + +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.widget.EditText; +import android.widget.Toast; + +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputLayout; +import com.google.gson.internal.$Gson$Types; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +public class ToolsHelper { + + public static boolean isNull(Object obj) { + if (null == obj) return true; + String str = obj.toString(); + if (str.isEmpty()) return true; + return str.equalsIgnoreCase("null"); + } + + public static Long toLong(Object obj) { + if (isNull(obj)) return 0L; + try { + return Long.parseLong(obj.toString()); + } catch (Exception e) { + return 0L; + } + } + + public static int toInt(Object obj) { + if (isNull(obj)) return 0; + try { + return (int)(ToolsHelper.toDouble(obj.toString())); + } catch (Exception e) { + return 0; + } + } + + public static double toDouble(Object obj) { + if (isNull(obj)) return 0.0; + try { + return Double.parseDouble(obj.toString()); + } catch (Exception e) { + return 0D; + } + } + + public static String toString(Object obj) { + if (isNull(obj)) return ""; + return obj.toString(); + } + + + /** + * 格式化json字符串 + * + * @param jsonStr 需要格式化的json串 + * @return 格式化后的json串 + */ + public static String formatJson(String jsonStr) { + if (null == jsonStr || "".equals(jsonStr)) return ""; + StringBuilder sb = new StringBuilder(); + char last = '\0'; + char current = '\0'; + int indent = 0; + for (int i = 0; i < jsonStr.length(); i++) { + last = current; + current = jsonStr.charAt(i); + //遇到{ [换行,且下一行缩进 + switch (current) { + case '{': + case '[': + sb.append(current); + sb.append('\n'); + indent++; + addIndentBlank(sb, indent); + break; + //遇到} ]换行,当前行缩进 + case '}': + case ']': + sb.append('\n'); + indent--; + addIndentBlank(sb, indent); + sb.append(current); + break; + //遇到,换行 + case ',': + sb.append(current); + if (last != '\\') { + sb.append('\n'); + addIndentBlank(sb, indent); + } + break; + default: + sb.append(current); + } + } + return sb.toString(); + } + + /** + * 添加space + */ + private static void addIndentBlank(StringBuilder sb, int indent) { + for (int i = 0; i < indent; i++) { + sb.append('\t'); + } + } + + /** + * http 请求数据返回 json 中中文字符为 unicode 编码转汉字转码 + * + * @param theString + * @return 转化后的结果. + */ + public static String decodeUnicode(String theString) { + char aChar; + int len = theString.length(); + StringBuilder outBuffer = new StringBuilder(len); + for (int x = 0; x < len; ) { + aChar = theString.charAt(x++); + if (aChar == '\\') { + aChar = theString.charAt(x++); + if (aChar == 'u') { + int value = 0; + for (int i = 0; i < 4; i++) { + aChar = theString.charAt(x++); + switch (aChar) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + value = (value << 4) + aChar - '0'; + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + value = (value << 4) + 10 + aChar - 'a'; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + value = (value << 4) + 10 + aChar - 'A'; + break; + default: + throw new IllegalArgumentException("Malformed \\uxxxx encoding."); + } + + } + outBuffer.append((char) value); + } else { + if (aChar == 't') { + aChar = '\t'; + } else if (aChar != 'r') { + if (aChar == 'n') { + aChar = '\n'; + } else if (aChar == 'f') { + aChar = '\f'; + } + } else { + aChar = '\r'; + } + outBuffer.append(aChar); + } + } else { + outBuffer.append(aChar); + } + } + return outBuffer.toString(); + } + + /** + * 弹出提示信息 感觉比Toast好看点 不过Toast不需要依赖view + * + * @param view 绑定一个view才能展示 + * @param content 需要展示的内容 + */ + public static void snack(View view, CharSequence content) { + Snackbar.make(view, content, Snackbar.LENGTH_SHORT).show(); + } + + public static void showMessage(CharSequence content) { + Toast.makeText(AppManager.getInstance().getActivity(), content, Toast.LENGTH_SHORT).show(); + } + + /** + * EditText绑定TextInputLayout,处理一下 + * + * @param editText editText + * @param textInputLayout textInputLayout + */ + public static void addTextChangedListener(EditText editText, TextInputLayout textInputLayout) { + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + if (!TextUtils.isEmpty(textInputLayout.getError())) {//输入的时候不提示错误信息 + textInputLayout.setErrorEnabled(true); + textInputLayout.setError(""); + textInputLayout.setErrorEnabled(false); + } + } + }); + } + + /** + * 使用 TextInputLayout 提示错误信息 + * + * @param textInputLayout TextInputLayout + * @param msg 错判的内容 + */ + public static void showError(TextInputLayout textInputLayout, String msg) { + textInputLayout.setErrorEnabled(true); + textInputLayout.setError(msg); + } + + /** + * 将Object对象里面的属性和值转化成Map对象 + * + * @param obj + * @return + * @throws IllegalAccessException + */ + public static Map objectToMap(Object obj) throws IllegalAccessException { + Map map = new HashMap<>(); + Class clazz = obj.getClass(); + for (Field field : clazz.getDeclaredFields()) { + field.setAccessible(true); + String fieldName = field.getName(); + T value = (T) field.get(obj); + if (null != value) map.put(fieldName, value); + } + return map; + } + + public static Type getSuperclassTypeParameter(Class subclass) { + Type superClass = subclass.getGenericSuperclass(); +// if (superClass instanceof Class) { +// System.out.println("superClass=" + superClass); +// throw new RuntimeException("Missing type parameter."); +// } + ParameterizedType parameterized = (ParameterizedType) superClass; + return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]); + } +} diff --git a/base/src/main/java/com/xuqm/base/datasource/DataSourceFactory.java b/base/src/main/java/com/xuqm/base/datasource/DataSourceFactory.java new file mode 100644 index 0000000..815ba7b --- /dev/null +++ b/base/src/main/java/com/xuqm/base/datasource/DataSourceFactory.java @@ -0,0 +1,23 @@ +package com.xuqm.base.datasource; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; +import androidx.paging.DataSource; + +public class DataSourceFactory extends DataSource.Factory { + private PagedDataLoader dataLoader; + + public DataSourceFactory(PagedDataLoader dataLoader) { + this.dataLoader = dataLoader; + } + + public MutableLiveData> sourceLiveData = new MutableLiveData<>(); + + @NonNull + @Override + public DataSource create() { + PagedDataSource dataSource = new PagedDataSource<>(dataLoader); + sourceLiveData.postValue(dataSource); + return dataSource; + } +} diff --git a/base/src/main/java/com/xuqm/base/datasource/PagedDataLoader.java b/base/src/main/java/com/xuqm/base/datasource/PagedDataLoader.java new file mode 100644 index 0000000..fdabc1a --- /dev/null +++ b/base/src/main/java/com/xuqm/base/datasource/PagedDataLoader.java @@ -0,0 +1,13 @@ +package com.xuqm.base.datasource; + +import androidx.paging.PageKeyedDataSource; + +public interface PagedDataLoader { + void loadInitial(PageKeyedDataSource.LoadInitialParams params, PageKeyedDataSource.LoadInitialCallback callback); + + void loadAfter(PageKeyedDataSource.LoadParams params, PageKeyedDataSource.LoadCallback callback); + + void refresh(); + + void loadMore(); +} diff --git a/base/src/main/java/com/xuqm/base/datasource/PagedDataSource.java b/base/src/main/java/com/xuqm/base/datasource/PagedDataSource.java new file mode 100644 index 0000000..d0143a5 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/datasource/PagedDataSource.java @@ -0,0 +1,26 @@ +package com.xuqm.base.datasource; + +import androidx.annotation.NonNull; +import androidx.paging.PageKeyedDataSource; + +public class PagedDataSource extends PageKeyedDataSource { + private PagedDataLoader dataLoader; + + public PagedDataSource(PagedDataLoader dataLoader) { + this.dataLoader = dataLoader; + } + + @Override + public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback callback) { + this.dataLoader.loadInitial(params, callback); + } + + @Override + public void loadBefore(@NonNull LoadParams params, @NonNull LoadCallback callback) { + } + + @Override + public void loadAfter(@NonNull LoadParams params, @NonNull LoadCallback callback) { + this.dataLoader.loadAfter(params, callback); + } +} diff --git a/base/src/main/java/com/xuqm/base/di/component/AppComponent.java b/base/src/main/java/com/xuqm/base/di/component/AppComponent.java new file mode 100644 index 0000000..9446392 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/di/component/AppComponent.java @@ -0,0 +1,28 @@ +package com.xuqm.base.di.component; + +import com.franmontiel.persistentcookiejar.PersistentCookieJar; +import com.xuqm.base.di.module.NetworkModule; + +import java.util.List; + +import javax.inject.Singleton; + +import dagger.Component; +import okhttp3.Cookie; +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; + +/** + * 可以获取到Retrofit、OkHttpClient、PersistentCookieJar、cookies + */ +@Singleton +@Component(modules = NetworkModule.class) +public interface AppComponent { + Retrofit retrofit(); + + OkHttpClient okHttpClient(); + + PersistentCookieJar persistentCookieJar(); + + List cookies(); +} diff --git a/base/src/main/java/com/xuqm/base/di/interceptor/HttpLogger.java b/base/src/main/java/com/xuqm/base/di/interceptor/HttpLogger.java new file mode 100644 index 0000000..8a7a109 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/di/interceptor/HttpLogger.java @@ -0,0 +1,28 @@ +package com.xuqm.base.di.interceptor; + +import com.xuqm.base.common.LogHelper; +import com.xuqm.base.common.ToolsHelper; + +import okhttp3.logging.HttpLoggingInterceptor; + +public class HttpLogger implements HttpLoggingInterceptor.Logger { + private StringBuilder mMessage = new StringBuilder(); + + @Override + public void log(String message) { + // 请求或者响应开始 + if (message.startsWith("--> POST")) { + mMessage.setLength(0); + } + // 以{}或者[]形式的说明是响应结果的json数据,需要进行格式化 + if ((message.startsWith("{") && message.endsWith("}")) + || (message.startsWith("[") && message.endsWith("]"))) { + message = ToolsHelper.formatJson(ToolsHelper.decodeUnicode(message)); + } + mMessage.append(message.concat("\n")); + // 响应结束,打印整条日志 + if (message.startsWith("<-- END HTTP")) { + LogHelper.d("___http",mMessage.toString()); + } + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/di/interceptor/LoggingInterceptor.java b/base/src/main/java/com/xuqm/base/di/interceptor/LoggingInterceptor.java new file mode 100644 index 0000000..f515d9b --- /dev/null +++ b/base/src/main/java/com/xuqm/base/di/interceptor/LoggingInterceptor.java @@ -0,0 +1,57 @@ +package com.xuqm.base.di.interceptor; + + +import com.xuqm.base.common.LogHelper; + +import java.io.IOException; +import java.nio.charset.Charset; + +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; + +import static okhttp3.internal.Util.UTF_8; + +public class LoggingInterceptor implements Interceptor { + String TAG = "_____Http"; + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + long startTime = System.currentTimeMillis(); + Response response = chain.proceed(chain.request()); + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + ResponseBody responseBody = response.body(); + if (responseBody == null) { + return response; + } + okhttp3.MediaType mediaType = responseBody.contentType(); + String content = response.body().string(); + LogHelper.e(TAG, request.toString()); + LogHelper.e(TAG, response.code() + " : " + response.message()); + String method = request.method(); + if ("POST".equals(method)) { + Buffer buffer = new Buffer(); + try { + request.body().writeTo(buffer); + Charset charset = Charset.forName("UTF-8"); + MediaType contentType = request.body().contentType(); + if (contentType != null) { + charset = contentType.charset(UTF_8); + } + String params = buffer.readString(charset); + LogHelper.e(TAG, params); + } catch (IOException e) { + e.printStackTrace(); + } + } + LogHelper.json(TAG, content); + LogHelper.e(TAG, "耗时: " + duration + "毫秒"); + return response.newBuilder().body(okhttp3.ResponseBody.create(mediaType, content)).build(); + + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/di/manager/HttpManager.java b/base/src/main/java/com/xuqm/base/di/manager/HttpManager.java new file mode 100644 index 0000000..9f18a29 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/di/manager/HttpManager.java @@ -0,0 +1,82 @@ +package com.xuqm.base.di.manager; + +import com.xuqm.base.App; +import com.xuqm.base.di.component.AppComponent; +import com.xuqm.base.di.component.DaggerAppComponent; +import com.xuqm.base.di.module.NetworkModule; + +import java.util.HashMap; +import java.util.Map; + +import okhttp3.Interceptor; + +/** + * 网络访问的管理类, + */ +public class HttpManager { + + private static Map apis = new HashMap<>(); + private static Map appComponentMap = new HashMap<>(); + + /** + * 使用默认的appComponent和给定的service获取一个service实例 + * appComponent 在{@link App #appComponent}定义 + * service 可以参照retrofit的使用方法 + *

+ * appComponent {@link #getAppComponent(String)} + * + * @param service service + * @param service实例class类型 + * @return service实例 + */ + public static T getApi(final Class service) { + return getApi(App.getInstance().appComponent, service); + } + + /** + * 根据给定的appComponent和service获取一个service实例 + * appComponent可以使用{@link #getAppComponent(String)} 方法获得 + * service 可以参照retrofit的使用方法 + * + * @param appComponent {@link #getAppComponent(String)} + * @param service service + * @param service实例class类型 + * @return service实例 + */ + public static T getApi(AppComponent appComponent, final Class service) { + String key = appComponent.hashCode() + service.getCanonicalName(); + if (!apis.containsKey(key)) + synchronized (HttpManager.class) { + if (!apis.containsKey(key)) + apis.put(key, appComponent.retrofit().create(service)); + } + + return (T) apis.get(key); + } + + /** + * 根据指定的baseUrl 获取一个{@link AppComponent} 用来做后续事件 + * + * @param baseUrl 换地地址 例如 + * @return AppComponent + */ +// public static AppComponent getAppComponent(String baseUrl) { +// return getAppComponent(baseUrl, null); +// } + + /** + * 根据指定的baseUrl 获取一个{@link AppComponent} 用来做后续事件 + * + * @param baseUrl 换地地址 + * @param interceptor 自定义拦截器 + * @return AppComponent + */ + public static AppComponent getAppComponent(String baseUrl, Interceptor... interceptor) { + if (!appComponentMap.containsKey(baseUrl)) + synchronized (HttpManager.class) { + if (!appComponentMap.containsKey(baseUrl)) + appComponentMap.put(baseUrl, DaggerAppComponent.builder().networkModule(new NetworkModule(baseUrl, interceptor)).build()); + } + return appComponentMap.get(baseUrl); + } +} diff --git a/base/src/main/java/com/xuqm/base/di/module/NetworkModule.java b/base/src/main/java/com/xuqm/base/di/module/NetworkModule.java new file mode 100644 index 0000000..543bdb5 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/di/module/NetworkModule.java @@ -0,0 +1,97 @@ +package com.xuqm.base.di.module; + +import com.franmontiel.persistentcookiejar.PersistentCookieJar; +import com.franmontiel.persistentcookiejar.cache.SetCookieCache; +import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; +import com.xuqm.base.App; +import com.xuqm.base.di.interceptor.HttpLogger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import okhttp3.Cookie; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; + +@Module +public class NetworkModule { + private String BaseUrl = "https://xuqinmin.com/"; + private final List interceptor = new ArrayList<>(); + + public NetworkModule() { + } + + public NetworkModule(String baseUrl, Interceptor... interceptor) { + BaseUrl = baseUrl; + this.interceptor.clear(); + this.interceptor.addAll(Arrays.asList(interceptor)); + } + + @Provides + @Singleton + Retrofit provideRetrofit(OkHttpClient okHttpClient) { + + return new Retrofit.Builder() + .baseUrl(BaseUrl) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); + } + + @Provides + @Singleton + OkHttpClient provideOkHttpClient(HttpLoggingInterceptor httpLoggingInterceptor, PersistentCookieJar persistentCookieJar) { + + OkHttpClient.Builder builder = new OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .pingInterval(5, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS); +// builder.addNetworkInterceptor(httpLoggingInterceptor); + if (0 != interceptor.size()) { + for (Interceptor interceptor1 : this.interceptor) { + builder.addInterceptor(interceptor1); + } + } + + return builder.cookieJar(persistentCookieJar) + .build(); + } + + @Provides + @Singleton + HttpLoggingInterceptor provideHttpLoggingInterceptor() { + return new HttpLoggingInterceptor(new HttpLogger()).setLevel(HttpLoggingInterceptor.Level.BASIC); + + } + + @Provides + @Singleton + PersistentCookieJar providePersistentCookieJar(SharedPrefsCookiePersistor sharedPrefsCookiePersistor) { + return new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor); + } + + @Provides + @Singleton + SharedPrefsCookiePersistor provideSharedPrefsCookiePersistor() { + return new SharedPrefsCookiePersistor(App.getInstance()); + } + + @Provides + @Singleton + List provideCookies(SharedPrefsCookiePersistor sharedPrefsCookiePersistor) { + return sharedPrefsCookiePersistor.loadAll(); + } + +} diff --git a/base/src/main/java/com/xuqm/base/dialog/AlertDialogFragment.kt b/base/src/main/java/com/xuqm/base/dialog/AlertDialogFragment.kt new file mode 100644 index 0000000..5aaba1b --- /dev/null +++ b/base/src/main/java/com/xuqm/base/dialog/AlertDialogFragment.kt @@ -0,0 +1,83 @@ +package com.xuqm.base.dialog + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment + +class AlertDialogFragment : DialogFragment() { + companion object { + fun newInstance( + title: String, + message: String, + confirm: String, + cancel: String + ): AlertDialogFragment { + val args = Bundle() + args.putString("title", title) + args.putString("message", message) + args.putString("confirm", confirm) + args.putString("cancel", cancel) + val fragment = AlertDialogFragment() + fragment.arguments = args + return fragment + } + } + + private var title: String? = null + private var message: String? = null + private var confirm: String? = null + private var cancel: String? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + title = arguments?.getString("title") + message = arguments?.getString("message") + confirm = arguments?.getString("confirm") + cancel = arguments?.getString("cancel") + } + + + private lateinit var listener: NoticeDialogListener + + interface NoticeDialogListener { + fun onDialogPositiveClick(dialog: DialogFragment) + fun onDialogNegativeClick(dialog: DialogFragment) + } + + override fun onAttach(context: Context) { + super.onAttach(context) + try { + listener = context as NoticeDialogListener + } catch (e: ClassCastException) { + throw ClassCastException( + (context.toString() + + " must implement NoticeDialogListener") + ) + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return activity?.let { + val builder = AlertDialog.Builder(it) + title?.let { it1 -> builder.setTitle(it1) } + message?.let { it1 -> builder.setMessage(it1) } + confirm?.let { it1 -> + builder.setPositiveButton( + it1 + ) { _, _ -> + listener.onDialogPositiveClick(this) + } + } + cancel?.let { it1 -> + builder.setNegativeButton( + it1 + ) { _, _ -> + listener.onDialogNegativeClick(this) + } + } + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } + +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/dialog/loading/ColorfulRingProgressView.java b/base/src/main/java/com/xuqm/base/dialog/loading/ColorfulRingProgressView.java new file mode 100644 index 0000000..e03189b --- /dev/null +++ b/base/src/main/java/com/xuqm/base/dialog/loading/ColorfulRingProgressView.java @@ -0,0 +1,164 @@ +package com.xuqm.base.dialog.loading; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.util.AttributeSet; +import android.view.View; + +import com.xuqm.base.R; + + +/** + * Created by qiqi on 15/11/3. + */ +public class ColorfulRingProgressView extends View { + + + private float mPercent = 75; + private float mStrokeWidth; + private int mBgColor = 0xffe1e1e1; + private float mStartAngle = 0; + private int mFgColorStart = 0xffffe400; + private int mFgColorEnd = 0xffff4800; + + private LinearGradient mShader; + private Context mContext; + private RectF mOval; + private Paint mPaint; + + + public ColorfulRingProgressView(Context context, AttributeSet attrs) { + super(context, attrs); + this.mContext = context; + + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, + R.styleable.ColorfulRingProgressView, + 0, 0); + + try { + mBgColor = a.getColor(R.styleable.ColorfulRingProgressView_bgColor, 0xffe1e1e1); + mFgColorEnd = a.getColor(R.styleable.ColorfulRingProgressView_fgColorEnd, 0xffff4800); + + mFgColorStart = a.getColor(R.styleable.ColorfulRingProgressView_fgColorStart, 0xffffe400); + mPercent = a.getFloat(R.styleable.ColorfulRingProgressView_percent, 75); + mStartAngle = a.getFloat(R.styleable.ColorfulRingProgressView_startAngle, 0) + 270; + mStrokeWidth = a.getDimensionPixelSize(R.styleable.ColorfulRingProgressView_strokeWidths, dp2px(21)); + System.out.println("**** m" + mStrokeWidth); + } finally { + a.recycle(); + } + + init(); + } + + private void init() { + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(mStrokeWidth); + mPaint.setStrokeCap(Paint.Cap.ROUND); + } + + private int dp2px(float dp) { + return (int) (mContext.getResources().getDisplayMetrics().density * dp + 0.5f); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + mPaint.setShader(null); + mPaint.setColor(0xffFFE93C); + canvas.drawArc(mOval, 0, 360, false, mPaint); + + mPaint.setShader(mShader); + canvas.drawArc(mOval, mStartAngle, mPercent * 3.6f, false, mPaint); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + updateOval(); + + mShader = new LinearGradient(mOval.left, mOval.top, + mOval.left, mOval.bottom, mFgColorStart, mFgColorEnd, Shader.TileMode.MIRROR); + } + + public float getPercent() { + return mPercent; + } + + public void setPercent(float mPercent) { + this.mPercent = mPercent; + refreshTheLayout(); + } + + public float getStrokeWidth() { + return mStrokeWidth; + } + + public void setStrokeWidth(float mStrokeWidth) { + this.mStrokeWidth = mStrokeWidth; + mPaint.setStrokeWidth(mStrokeWidth); + updateOval(); + refreshTheLayout(); + } + + private void updateOval() { + int xp = getPaddingLeft() + getPaddingRight(); + int yp = getPaddingBottom() + getPaddingTop(); + mOval = new RectF(getPaddingLeft() + mStrokeWidth, getPaddingTop() + mStrokeWidth, + getPaddingLeft() + (getWidth() - xp) - mStrokeWidth, + getPaddingTop() + (getHeight() - yp) - mStrokeWidth); + } + + public void setStrokeWidthDp(float dp) { + this.mStrokeWidth = dp2px(dp); + mPaint.setStrokeWidth(mStrokeWidth); + updateOval(); + refreshTheLayout(); + } + + public void refreshTheLayout() { + invalidate(); + requestLayout(); + } + + public int getFgColorStart() { + return mFgColorStart; + } + + public void setFgColorStart(int mFgColorStart) { + this.mFgColorStart = mFgColorStart; + mShader = new LinearGradient(mOval.left, mOval.top, + mOval.left, mOval.bottom, mFgColorStart, mFgColorEnd, Shader.TileMode.MIRROR); + refreshTheLayout(); + } + + public int getFgColorEnd() { + return mFgColorEnd; + } + + public void setFgColorEnd(int mFgColorEnd) { + this.mFgColorEnd = mFgColorEnd; + mShader = new LinearGradient(mOval.left, mOval.top, + mOval.left, mOval.bottom, mFgColorStart, mFgColorEnd, Shader.TileMode.MIRROR); + refreshTheLayout(); + } + + + public float getStartAngle() { + return mStartAngle; + } + + public void setStartAngle(float mStartAngle) { + this.mStartAngle = mStartAngle + 270; + refreshTheLayout(); + } +} diff --git a/base/src/main/java/com/xuqm/base/dialog/loading/LoadingDialog.java b/base/src/main/java/com/xuqm/base/dialog/loading/LoadingDialog.java new file mode 100644 index 0000000..8ee7c1f --- /dev/null +++ b/base/src/main/java/com/xuqm/base/dialog/loading/LoadingDialog.java @@ -0,0 +1,238 @@ +/** + * com.leadingsoft.leaderaide.activity + * + * @ClassName: LoadingDialog + * @Description: LoadingDialog等待页面 + * @author: macq macq@leadingsoft.cn + * @date: 2014-06-16 + */ +package com.xuqm.base.dialog.loading; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnKeyListener; +import android.os.Handler; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.xuqm.base.R; +import com.xuqm.base.common.AppManager; +import com.xuqm.base.common.LogHelper; +import com.xuqm.base.common.ToolsHelper; + + +public class LoadingDialog { + private static Dialog mDialog; + + public static void showDialog(String msg) { + showDialog(AppManager.getInstance().getActivity(), msg); + } + + public static void showDialog(Context context, String msg) { + if (isShowing()) { + return; + } + try { + + OnKeyListener keyListener = (dialog, keyCode, event) -> { + if (keyCode == KeyEvent.KEYCODE_BACK + && event.getAction() == KeyEvent.ACTION_DOWN) { + mDialog.dismiss(); + } + return false; + + }; + mDialog = new Dialog(context, R.style.dialog); + mDialog.setOnKeyListener(keyListener); + mDialog.setCancelable(false); + mDialog.show(); + mDialog.setContentView(R.layout.loading_process_dialog_icon); + TextView TvLoading = (TextView) mDialog + .findViewById(R.id.loading_process_dialog_text); + TvLoading.setText(msg); + if (ToolsHelper.isNull(msg)) { + TvLoading.setVisibility(View.GONE); + } + Window window = mDialog.getWindow(); + WindowManager.LayoutParams lp = window.getAttributes(); + lp.gravity = Gravity.CENTER; + lp.width = WindowManager.LayoutParams.WRAP_CONTENT;//宽高可设置具体大小 + lp.height = WindowManager.LayoutParams.WRAP_CONTENT; + mDialog.getWindow().setAttributes(lp); + } catch (Exception e) { + LogHelper.e(LoadingDialog.class.getSimpleName(), e); + } + } + + public static void showDialog(Context context, String msg, boolean isShowBackGround) { + OnKeyListener keyListener = new OnKeyListener() { + @Override + public boolean onKey(DialogInterface dialog, int keyCode, + KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK + && event.getAction() == KeyEvent.ACTION_DOWN) { + mDialog.dismiss(); + } + return false; + + } + }; + mDialog = new Dialog(context, R.style.dialog); + mDialog.setOnKeyListener(keyListener); + mDialog.setCancelable(false); + mDialog.show(); + mDialog.setContentView(R.layout.loading_process_dialog_icon); + LinearLayout ll_loading_process_dialog = (LinearLayout) mDialog.findViewById(R.id.ll_loading_process_dialog); + // 清空背景 + if (!isShowBackGround) { + ll_loading_process_dialog.setBackgroundResource(0); + } + TextView TvLoading = (TextView) mDialog.findViewById(R.id.loading_process_dialog_text); + TvLoading.setText(msg); + if (ToolsHelper.isNull(msg)) { + TvLoading.setVisibility(View.GONE); + } + Window window = mDialog.getWindow(); + WindowManager.LayoutParams lp = window.getAttributes(); + lp.gravity = Gravity.CENTER; + lp.width = WindowManager.LayoutParams.WRAP_CONTENT;//宽高可设置具体大小 + lp.height = WindowManager.LayoutParams.WRAP_CONTENT; + mDialog.getWindow().setAttributes(lp); + } + + public static void updataTV(String text) { + if (null == mDialog) + return; + TextView TvLoading = (TextView) mDialog.findViewById(R.id.loading_process_dialog_text); + TvLoading.setText(text); + } + + public static void dismissDialog() { + if (mDialog != null) { + if (mDialog.isShowing()) { + try { + mDialog.dismiss(); + } catch (Exception e) { + // TODO: handle exception + } + } + } + } + + public static boolean isShowing() { + if (mDialog != null) { + return mDialog.isShowing(); + } + return false; + } + + + /** + * 获取进度条的Dialog + */ + public static Dialog getCircleProgressDialog(Context context) { + return new Dialog(context, R.style.dialog); + } + + /** + * 获取进度条的VIEW + */ + public static View getCircleProgressView(Context context) { + LayoutInflater inflater = LayoutInflater.from(context); + return inflater.inflate(R.layout.loading_circle_process_dialog_icon, null); + } + + /** + * 显示带进度条的dialog + */ + public static void showCircleProgressDialog(Context context, Dialog dialog, View view) { + OnKeyListener keyListener = new OnKeyListener() { + @Override + public boolean onKey(DialogInterface dialog, int keyCode, + KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK + && event.getAction() == KeyEvent.ACTION_DOWN) { + dialog.dismiss(); + } + return false; + + } + }; + try { + //dialog = new AlertDialog.Builder(context).create(); + dialog.setOnKeyListener(keyListener); + dialog.setCancelable(false); + dialog.show(); + dialog.setContentView(view); + } catch (Exception ex) { + LogHelper.e("创建圆形进度条", ex); + } + } + + /** + * 显示带进度条的dialog + */ + public static void updateCircleProgressDialog(int percent) { + if (null== mView)return; + TextView tvPercent = (TextView) mView.findViewById(R.id.tvPercent); + ColorfulRingProgressView isDownload = (ColorfulRingProgressView) mView.findViewById(R.id.crpv); + isDownload.setPercent(percent); + tvPercent.setText("" + percent); + } + + /********************************* APK下载圆形进度条 ***********************************************/ + + /** + * 获取进度条的VIEW + */ + public static View getProgressViewByTextView(Context context) { + LayoutInflater inflater = LayoutInflater.from(context); + View view = inflater.inflate(R.layout.loading_circle_process_dialog_icon_by_textview, null); + return view; + } + + private static View mView; + /** + * 显示带进度条的dialog + */ + public static void showCircleProgressDialog(final Context context, final Handler handler, String msg) { + + mDialog=getCircleProgressDialog(context); + mView=getProgressViewByTextView(context); + + OnKeyListener keyListener = (dialog, keyCode, event) -> { + if (keyCode == KeyEvent.KEYCODE_BACK + && event.getAction() == KeyEvent.ACTION_DOWN) { + handler.obtainMessage(9999).sendToTarget(); + } + return false; + }; + try { + //dialog = new AlertDialog.Builder(context).create(); + mDialog.setOnKeyListener(keyListener); + mDialog.setCancelable(false); + mDialog.show(); + + TextView textView = (TextView) mView.findViewById(R.id.tip_msg); + textView.setText(msg); + TextView tvPercent = (TextView) mView.findViewById(R.id.tvPercent); + tvPercent.setText("0"); + mDialog.setContentView(mView); + Window window = mDialog.getWindow(); + WindowManager.LayoutParams lp = window.getAttributes(); + lp.gravity = Gravity.CENTER; + lp.width = WindowManager.LayoutParams.WRAP_CONTENT;//宽高可设置具体大小 + lp.height = WindowManager.LayoutParams.WRAP_CONTENT; + mDialog.getWindow().setAttributes(lp); + } catch (Exception ex) { + LogHelper.e("创建圆形进度条", ex); + } + } +} diff --git a/base/src/main/java/com/xuqm/base/extensions/CommonExt.kt b/base/src/main/java/com/xuqm/base/extensions/CommonExt.kt new file mode 100644 index 0000000..f265b0a --- /dev/null +++ b/base/src/main/java/com/xuqm/base/extensions/CommonExt.kt @@ -0,0 +1,232 @@ +package com.xuqm.base.extensions + +import android.app.Activity +import android.content.Context +import android.view.inputmethod.InputMethodManager +import androidx.annotation.NonNull +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment +import com.livinglifetechway.quickpermissions_kotlin.runWithPermissions +import com.livinglifetechway.quickpermissions_kotlin.util.QuickPermissionsOptions +import com.xuqm.base.BuildConfig +import com.xuqm.base.common.LogHelper +import com.xuqm.base.common.ToolsHelper +import kotlin.experimental.and +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + + +private class NotNullSingleValue : ReadWriteProperty { + + private var value: T? = null + + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + return value ?: throw IllegalStateException("not initialized") + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + if (this.value == null) { + this.value = value + } else { + throw IllegalStateException("already initialized") + } + } + +} + +/** + * 不为空且唯一 + */ +fun notNullSingleValue(): ReadWriteProperty = NotNullSingleValue() + + +/** + * 打印log + */ +//fun Any.log(message: CharSequence) { +// KLog.d(this.javaClass.simpleName, message) +//} + +fun Any.log(message: Any) { + LogHelper.d(this.javaClass.simpleName, message) +} + +fun Any.loge(message: Any) { + LogHelper.e("=====>" + this.javaClass.simpleName, message) +} + +fun Any.log() { + LogHelper.d("=====>" + this.javaClass.simpleName, this) +} + +fun Any.loge() { + LogHelper.e("=====>" + this.javaClass.simpleName, this) +} + +fun Any.showMessage(content: CharSequence) { + ToolsHelper.showMessage(content) +} + +fun Any.showMessage() { + ToolsHelper.showMessage(this.toString()) +} + +fun ByteArray.toHexString(): String { + val sb = StringBuffer() + for (i in this.indices) { + val hex = Integer.toHexString((this[i] and 0xFF.toByte()).toInt()) + if (hex.length < 2) { + sb.append(0) + } + sb.append(hex) + } + return sb.toString() +} + + +/** + * 弹出窗口 + * @param title 标题 + * @param message 提示信息 + * @param confirm 点击确认按钮 + * @param cancel 点击取消按钮的事件 + */ +fun Activity.showDialog(title: String, message: String, confirm: () -> Unit, cancel: () -> Unit) { + AlertDialog.Builder(this).setTitle(title) + .setMessage(message) + .setPositiveButton( + "Confirm" + ) { _, _ -> + confirm() + } + .setNegativeButton( + "Cancel" + ) { _, _ -> + cancel() + }.create().show() +} + +/** + * 弹出窗口---只有一个确认按钮 + * @param title 标题 + * @param message 提示信息 + * @param confirm 点击确认按钮 + */ +fun Activity.showDialog(title: String, message: String, confirm: () -> Unit) { + AlertDialog.Builder(this).setTitle(title) + .setMessage(message) + .setPositiveButton( + "Confirm" + ) { _, _ -> + confirm() + }.create().show() +} + +/** + * 权限检测--申请 + * @param rationaleMethod 权限被拒绝时候的回调,提供重新申请,打开设置界面等方法 + * @param permanentDeniedMethod 拒绝权限并且选中了不在提示选项的回调,可以指导用户打开设置页面授予权限 + */ +fun Context?.runWithPermission( + vararg permissions: String, + callback: () -> Unit +): Any? = this.runWithPermissions( + *permissions +) { callback() } + + +fun Context?.runWithPermission( + vararg permissions: String, + rationaleMethod: ((com.livinglifetechway.quickpermissions_kotlin.util.QuickPermissionsRequest) -> Unit), + permanentDeniedMethod: ((com.livinglifetechway.quickpermissions_kotlin.util.QuickPermissionsRequest) -> Unit), + callback: () -> Unit +): Any? { + + val quickPermissionsOption = QuickPermissionsOptions( + rationaleMethod = rationaleMethod, + permanentDeniedMethod = permanentDeniedMethod + ) + + return this.runWithPermissions( + *permissions, options = quickPermissionsOption + ) { callback() } +} + +/* +隐藏软键盘 + */ +fun Activity.hideSoftInput() { + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(window.decorView.windowToken, 0) +} + +fun Fragment.hideSoftInput() { + activity?.hideSoftInput() +} + +fun Context.putInt(key: String, value: Int) { + val sharedPref = + getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) ?: return + with(sharedPref.edit()) { + putInt(key, value) + apply() + } +} + +fun Context.getIntForPreferences(key: String): Int { + val sharedPref = + getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) + return sharedPref?.getInt(key, -1) ?: -1 +} + +fun Context.getIntForPreferences(key: String, @NonNull defValue: Int): Int { + val sharedPref = + getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) + return sharedPref?.getInt(key, defValue) ?: defValue +} + +fun Context.putLong(key: String, value: Long) { + val sharedPref = + getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) ?: return + with(sharedPref.edit()) { + putLong(key, value) + apply() + } +} + +fun Context.getLongForPreferences(key: String): Long { + val sharedPref = + getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) + return sharedPref?.getLong(key, -1) ?: -1 +} + +fun Context.getLongForPreferences(key: String, @NonNull defValue: Long): Long { + val sharedPref = + getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) + return sharedPref?.getLong(key, defValue) ?: defValue +} + +fun Context.putString(key: String, value: String) { + val sharedPref = + getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) ?: return + with(sharedPref.edit()) { + putString(key, value) + apply() + } +} + +fun Context.getStringForPreferences(key: String): String { + val sharedPref = + getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) + return sharedPref?.getString(key, "") ?: "" +} + +fun Context.getStringForPreferences(key: String, @NonNull defValue: String): String { + val sharedPref = + getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) + return sharedPref?.getString(key, defValue) ?: defValue +} + +fun Context.clearForPreferences() { + getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE).edit().clear().apply() +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/extensions/FontExt.kt b/base/src/main/java/com/xuqm/base/extensions/FontExt.kt new file mode 100644 index 0000000..a1df6de --- /dev/null +++ b/base/src/main/java/com/xuqm/base/extensions/FontExt.kt @@ -0,0 +1,10 @@ +package com.xuqm.base.extensions + +import android.content.Context +import android.graphics.Typeface +import android.widget.TextView + + +fun TextView.setFont(context: Context, font: String) { + this.typeface = Typeface.createFromAsset(context.assets, "fonts/${font}.ttf") +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/extensions/Fonts.kt b/base/src/main/java/com/xuqm/base/extensions/Fonts.kt new file mode 100644 index 0000000..ef0b2ad --- /dev/null +++ b/base/src/main/java/com/xuqm/base/extensions/Fonts.kt @@ -0,0 +1,25 @@ +package com.xuqm.base.extensions + +class Fonts { + companion object { + const val Black = "Montserrat-Black" + const val BlackItalic = "Montserrat-BlackItalic" + const val Bold = "Montserrat-Bold" + const val BoldItalic = "Montserrat-BoldItalic" + const val ExtraBold = "Montserrat-ExtraBold" + const val ExtraBoldItalic = "Montserrat-ExtraBoldItalic" + const val ExtraLight = "Montserrat-ExtraLight" + const val ExtraLightItalic = "Montserrat-ExtraLightItalic" + const val Italic = "Montserrat-Italic" + const val Light = "Montserrat-Light" + const val LightItalic = "Montserrat-LightItalic" + const val Medium = "Montserrat-Medium" + const val MediumItalic = "Montserrat-MediumItalic" + const val Regular = "Montserrat-Regular" + const val SemiBold = "Montserrat-SemiBold" + const val SemiBoldItalic = "Montserrat-SemiBoldItalic" + const val Thin = "Montserrat-Thin" + const val ThinItalic = "Montserrat-ThinItalic" + } + +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/file/DownloadInterceptor.java b/base/src/main/java/com/xuqm/base/file/DownloadInterceptor.java new file mode 100644 index 0000000..f093519 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/file/DownloadInterceptor.java @@ -0,0 +1,22 @@ +package com.xuqm.base.file; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Response; + +public class DownloadInterceptor implements Interceptor { + + private final DownloadListener downloadListener; + + public DownloadInterceptor(DownloadListener downloadListener) { + this.downloadListener = downloadListener; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + return response.newBuilder().body( + new DownloadResponseBody(response.body(), downloadListener)).build(); + } +} diff --git a/base/src/main/java/com/xuqm/base/file/DownloadListener.java b/base/src/main/java/com/xuqm/base/file/DownloadListener.java new file mode 100644 index 0000000..abce364 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/file/DownloadListener.java @@ -0,0 +1,11 @@ +package com.xuqm.base.file; + +public interface DownloadListener { + void onStartDownload(); + + void onProgress(int progress); + + void onFinishDownload(); + + void onFail(String errorInfo); +} diff --git a/base/src/main/java/com/xuqm/base/file/DownloadResponseBody.java b/base/src/main/java/com/xuqm/base/file/DownloadResponseBody.java new file mode 100644 index 0000000..39e7bfb --- /dev/null +++ b/base/src/main/java/com/xuqm/base/file/DownloadResponseBody.java @@ -0,0 +1,70 @@ +package com.xuqm.base.file; + +import android.util.Log; + +import com.xuqm.base.common.LogHelper; + +import java.io.IOException; + +import okhttp3.MediaType; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; +import okio.ForwardingSource; +import okio.Okio; +import okio.Source; + +public class DownloadResponseBody extends ResponseBody { + + private final ResponseBody responseBody; + + private final DownloadListener downloadListener; + + // BufferedSource 是okio库中的输入流,这里就当作inputStream来使用。 + private BufferedSource bufferedSource; + + public DownloadResponseBody(ResponseBody responseBody, DownloadListener downloadListener) { + this.responseBody = responseBody; + this.downloadListener = downloadListener; + } + + @Override + public MediaType contentType() { + return responseBody.contentType(); + } + + @Override + public long contentLength() { + return responseBody.contentLength(); + } + + @Override + public BufferedSource source() { + if (bufferedSource == null) { + bufferedSource = Okio.buffer(source(responseBody.source())); + } + return bufferedSource; + } + + private Source source(Source source) { + return new ForwardingSource(source) { + long totalBytesRead = 0L; + + @Override + public long read(Buffer sink, long byteCount) throws IOException { + long bytesRead = super.read(sink, byteCount); + // read() returns the number of bytes read, or -1 if this source is exhausted. + totalBytesRead += bytesRead != -1 ? bytesRead : 0; + Log.e("download", "read: "+ (int) (totalBytesRead * 100 / responseBody.contentLength())); + if (null != downloadListener) { + if (bytesRead != -1) { + downloadListener.onProgress((int) (totalBytesRead * 100 / responseBody.contentLength())); + } + + } + return bytesRead; + } + }; + + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/file/FileDownLoadObserver.java b/base/src/main/java/com/xuqm/base/file/FileDownLoadObserver.java new file mode 100644 index 0000000..0dcc460 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/file/FileDownLoadObserver.java @@ -0,0 +1,80 @@ +package com.xuqm.base.file; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import io.reactivex.observers.DefaultObserver; +import okhttp3.ResponseBody; + +public abstract class FileDownLoadObserver extends DefaultObserver { + + @Override + public void onNext(T t) { + onDownLoadSuccess(t); + } + @Override + public void onError(Throwable e) { + onDownLoadFail(e); + } + //可以重写,具体可由子类实现 + @Override + public void onComplete() { + } + //下载成功的回调 + public abstract void onDownLoadSuccess(T t); + //下载失败回调 + public abstract void onDownLoadFail(Throwable throwable); + //下载进度监听 + public abstract void onProgress(int progress,long total); + + /** + * 将文件写入本地 + * @param responseBody 请求结果全体 + * @param destFileDir 目标文件夹 + * @param destFileName 目标文件名 + * @return 写入完成的文件 + * @throws IOException IO异常 + */ + public File saveFile(ResponseBody responseBody, String destFileDir, String destFileName) throws IOException { + InputStream is = null; + byte[] buf = new byte[2048]; + int len = 0; + FileOutputStream fos = null; + try { + is = responseBody.byteStream(); + final long total = responseBody.contentLength(); + long sum = 0; + + File dir = new File(destFileDir); + if (!dir.exists()) { + dir.mkdirs(); + } + File file = new File(dir, destFileName); + fos = new FileOutputStream(file); + while ((len = is.read(buf)) != -1) { + sum += len; + fos.write(buf, 0, len); + final long finalSum = sum; + //这里就是对进度的监听回调 + onProgress((int) (finalSum * 100 / total),total); + } + fos.flush(); + + return file; + + } finally { + try { + if (is != null) is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + if (fos != null) fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/file/FileDownloadAndUploadManager.java b/base/src/main/java/com/xuqm/base/file/FileDownloadAndUploadManager.java new file mode 100644 index 0000000..867e037 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/file/FileDownloadAndUploadManager.java @@ -0,0 +1,178 @@ +package com.xuqm.base.file; + +import com.xuqm.base.common.LogHelper; +import com.xuqm.base.di.component.AppComponent; +import com.xuqm.base.di.manager.HttpManager; +import com.xuqm.base.repository.CommonService; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; + +public class FileDownloadAndUploadManager { + private static FileDownloadAndUploadManager instance; + + public static FileDownloadAndUploadManager getInstance() { + if (null == instance) { + synchronized (FileDownloadAndUploadManager.class) { + if (null == instance) { + instance = new FileDownloadAndUploadManager(); + } + } + } + return instance; + } + + private final AppComponent appComponent; + protected CompositeDisposable compositeDisposable; + + private FileDownloadAndUploadManager() { + appComponent = HttpManager.getAppComponent("https://xuqinmin.com/"); + compositeDisposable = new CompositeDisposable(); + } + + public void download(@NonNull String url, final String filePath) { + LogHelper.e(url); + thisListener.onStartDownload(); + Disposable d = HttpManager.getApi(appComponent, CommonService.class) + .download(url) + .subscribeOn(Schedulers.io()) + .unsubscribeOn(Schedulers.io()) + .map(ResponseBody::byteStream) + .observeOn(Schedulers.computation()) + .doOnNext(inputStream -> writeFile(inputStream, filePath)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(body -> { + thisListener.onFinishDownload(); + }, err -> { + LogHelper.e("=============>", err); + thisListener.onFail(err.getMessage()); + }); + compositeDisposable.add(d); + } + + public void cancel() { + compositeDisposable.dispose(); + compositeDisposable.clear(); + } + + public void download(@NonNull String url, final String destDir, final String fileName, final FileDownLoadObserver fileDownLoadObserver) { + HttpManager.getApi(appComponent, CommonService.class) + .download(url) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .observeOn(Schedulers.computation()) + .map(responseBody -> fileDownLoadObserver.saveFile(responseBody, destDir, fileName)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(fileDownLoadObserver); + } + +// public void upload(@NonNull String url, @NonNull String filePath, final Map formDataPart) { +// upload(url, new File(filePath), formDataPart); +// } + + public void upload(@NonNull String url, @NonNull File file, final String contentType) { + + MediaType mediaType = MediaType.parse(contentType); + RequestBody body = + RequestBody.create(mediaType, file); + + Disposable d = HttpManager.getApi(appComponent, CommonService.class) + .upload(url, body) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(b -> { + LogHelper.e("=============>上传完成:::" + b); + if (null != listener) + listener.onFinishDownload(); + }, err -> { + LogHelper.e("=============>", err); + if (null != listener) + listener.onFail(err.getMessage()); + }); + compositeDisposable.add(d); + } + + /** + * 将输入流写入文件 + * + * @param inputString 文件流 + * @param filePath 文件地址 + */ + private void writeFile(InputStream inputString, String filePath) { + + File file = new File(filePath); + if (file.exists()) { + file.delete(); + } + + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + + byte[] b = new byte[1024]; + + int len; + while ((len = inputString.read(b)) != -1) { + fos.write(b, 0, len); + } + inputString.close(); + fos.close(); + + } catch (FileNotFoundException e) { + thisListener.onFail("FileNotFoundException"); + } catch (IOException e) { + thisListener.onFail("IOException"); + } + + } + + private DownloadListener listener; + + private final DownloadListener thisListener = new DownloadListener() { + @Override + public void onStartDownload() { + if (null != listener) + listener.onStartDownload(); + LogHelper.e("=========开始下载"); + } + + @Override + public void onProgress(int progress) { + if (null != listener) + listener.onProgress(progress); + LogHelper.e("=========下载进度" + progress); + } + + @Override + public void onFinishDownload() { + + if (null != listener) + listener.onFinishDownload(); + LogHelper.e("=========下载完成"); + } + + @Override + public void onFail(String errorInfo) { + if (null != listener) + listener.onFail(errorInfo); + LogHelper.e("=========下载失败" + errorInfo); + } + }; + + public void setListener(DownloadListener listener) { + this.listener = listener; + } +} diff --git a/base/src/main/java/com/xuqm/base/model/HttpResult.java b/base/src/main/java/com/xuqm/base/model/HttpResult.java new file mode 100644 index 0000000..beca784 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/model/HttpResult.java @@ -0,0 +1,48 @@ +package com.xuqm.base.model; + +import androidx.annotation.NonNull; + +/** + * 通用的HttpResult封装,app应该根据接口情况定制化配置 + * + * @param + */ +public class HttpResult { + private String status; + private String maxNumber; + private T data; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getMaxNumber() { + return maxNumber; + } + + public void setMaxNumber(String maxNumber) { + this.maxNumber = maxNumber; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + @Override + @NonNull + public String toString() { + return "HttpResult{" + + "status='" + status + '\'' + + ", maxNumber='" + maxNumber + '\'' + + ", data=" + data + + '}'; + } +} diff --git a/base/src/main/java/com/xuqm/base/model/KeyValueData.java b/base/src/main/java/com/xuqm/base/model/KeyValueData.java new file mode 100644 index 0000000..3168f8c --- /dev/null +++ b/base/src/main/java/com/xuqm/base/model/KeyValueData.java @@ -0,0 +1,38 @@ +package com.xuqm.base.model; + +public class KeyValueData { + private K key; + private V value; + + public KeyValueData() { + } + + public KeyValueData(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public void setKey(K key) { + this.key = key; + } + + public V getValue() { + return value; + } + + public void setValue(V value) { + this.value = value; + } + + @Override + public String toString() { + return "KeyValueData{" + + "key=" + key + + ", value=" + value + + '}'; + } +} diff --git a/base/src/main/java/com/xuqm/base/repository/CommonService.java b/base/src/main/java/com/xuqm/base/repository/CommonService.java new file mode 100644 index 0000000..f0858a9 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/repository/CommonService.java @@ -0,0 +1,21 @@ +package com.xuqm.base.repository; + +import io.reactivex.Observable; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Streaming; +import retrofit2.http.Url; + +public interface CommonService { + @Streaming + @GET + Observable download(@Url String url); + + @PUT + @Streaming + Observable upload(@Url String url, @Body RequestBody body); +} diff --git a/base/src/main/java/com/xuqm/base/ui/BaseActivity.java b/base/src/main/java/com/xuqm/base/ui/BaseActivity.java new file mode 100644 index 0000000..9addcc1 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/ui/BaseActivity.java @@ -0,0 +1,382 @@ +package com.xuqm.base.ui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Typeface; +import android.os.Bundle; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AppCompatActivity; +import androidx.databinding.DataBindingUtil; +import androidx.databinding.ViewDataBinding; + +import com.gyf.immersionbar.BarHide; +import com.gyf.immersionbar.ImmersionBar; +import com.xuqm.base.R; +import com.xuqm.base.common.AppManager; +import com.xuqm.base.databinding.ActivityBaseBinding; +import com.xuqm.base.ui.callback.UiCallback; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public abstract class BaseActivity extends AppCompatActivity implements UiCallback { + + protected String TAG = this.getClass().getSimpleName(); + protected Activity mContext; + private V binding; + + private ActivityBaseBinding baseBinding; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + AppManager.getInstance().pushActivity(this); + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + mContext = this; + editText.clear(); + views.clear(); + if (getLayoutId() == 0) {//没有layout的时候,act自己写布局 + ImmersionBar.with(this) + .init(); + setContentView(); + } else if (showStatus() != -1) {//没有toolbar但是有状态栏的情况 + bindUi(getLayoutId()); + ImmersionBar.with(this) + .titleBar(binding.getRoot()) //指定标题栏view + .statusBarColor(showStatus()) + .autoStatusBarDarkModeEnable(true) + .init(); + } else if (transparentStatusBar()) {//透明状态栏,但是显示状态栏的内容 + bindUi(getLayoutId()); + ImmersionBar.with(this).transparentBar() + .titleBar(binding.getRoot().findViewWithTag("top_status")) + .statusBarDarkFont(true) +// .hideBar(BarHide.FLAG_HIDE_NAVIGATION_BAR) + .init(); + } else if (fullscreen()) {//全屏,什么都不显示 + bindUi(getLayoutId()); + ImmersionBar.with(this) + .fullScreen(true) + .statusBarDarkFont(true) +// .hideBar(BarHide.FLAG_HIDE_NAVIGATION_BAR) + .init(); + } else {//使用base提供的toolbar + baseBinding = DataBindingUtil.setContentView(mContext, R.layout.activity_base); + ImmersionBar.with(this) +// .hideBar(BarHide.FLAG_HIDE_NAVIGATION_BAR) + .statusBarColor(R.color.white) + .statusBarDarkFont(true) + .titleBar(baseBinding.baseToolbar) //指定标题栏view + .init(); + binding = DataBindingUtil.inflate(getLayoutInflater(), getLayoutId(), baseBinding.activityRootView, true); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + if (null != baseBinding) { + baseBinding.baseToolbar.backBtnPressed(this::backBtnPressed); + baseBinding.baseToolbar.getTitleView().setTypeface(Typeface.createFromAsset(this.getAssets(), "fonts/Montserrat-Bold.ttf")); + } + initView(savedInstanceState); + initData(); + lateInitView(); + } + + @NonNull + public V getBinding() { + return binding; + } + + public ActivityBaseBinding getBaseBinding() { + return baseBinding; + } + + /** + * 导航栏展示的内容 + * + * @param titleId 标题 + */ + public void setTitleText(@StringRes int titleId) { + setTitleText(getText(titleId)); + } + + /** + * 导航栏展示的内容 + * + * @param title 标题 + */ + public void setTitleText(CharSequence title) { + if (null == baseBinding) { + return; + } + baseBinding.baseToolbar.setTitle(title); + } + + /** + * 导航栏右上角按钮 + * + * @param title 标题 + */ + public void setConfirmText(CharSequence title, View.OnClickListener listener) { + if (null == baseBinding) { + return; + } + baseBinding.baseToolbar.getConfirmBtn().setVisibility(View.VISIBLE); + baseBinding.baseToolbar.getConfirmBtn().setText(title); + baseBinding.baseToolbar.getConfirmBtn().setOnClickListener(listener); + } + + /** + * 设置标题颜色 + * + * @param color 标题颜色 + */ + public void setTextColor(int color) { + if (null == baseBinding) { + return; + } + baseBinding.baseToolbar.setTextColor(color); + } + + /** + * 设置返回图标颜色 + * + * @param iconTintColor 返回图标颜色 + */ + public void setIconTintColor(int iconTintColor) { + if (null == baseBinding) { + return; + } + baseBinding.baseToolbar.setIconTintColor(iconTintColor); + } + + public void setIconDraw(@DrawableRes int resId) { + if (null == baseBinding) { + return; + } + baseBinding.baseToolbar.getBackBtn().setImageResource(resId); + } + + /** + * 是否展示返回按钮 + * + * @param showBack 是否展示返回按钮 + */ + public void showBack(boolean showBack) { + if (null == baseBinding) { + return; + } + baseBinding.baseToolbar.setShowBack(showBack); + } + + /** + * 是否显示导航栏下面的线 + * + * @param showLine 是否显示导航栏下面的线 + */ + public void showLine(boolean showLine) { + if (null == baseBinding) { + return; + } + baseBinding.baseToolbar.setShowLine(showLine); + } + + + public void backBtnPressed() { + finish(); + } + + + protected void bindUi(@LayoutRes int layoutResID) { + binding = DataBindingUtil.setContentView(mContext, layoutResID); + } + + /** + * 如果不提供layoutId{@link #getLayoutId()} 则可以重写这个方法自己布局 + */ + @Override + public void setContentView() { + + } + + @Override + public void initView(Bundle savedInstanceState) { + + } + + @Override + public void initData() { + + } + + /** + * 这个init方法,在initData之后执行,有一些view相关的设置,需要数据信息 + */ + public void lateInitView() { + + } + + /** + * 是否需要展示statusBar,默认为false,使用默认布局展示toolbar + * false的话,可以自定义toolbar + * + * @return statusBar 的颜色 + */ + @Override + @ColorRes + public int showStatus() { + return -1; + } + + /** + * 如果返回true,则状态栏变成透明状态,但是状态栏的文字还显示 + * 使用透明状态栏 布局必须指定一个 android:tag="top_status" + * 最好是第一个子元素指定,可以自动设置padding + * + * @return 是否需要设置状态栏为透明状态 + */ + @Override + public boolean transparentStatusBar() { + return false; + } + + /** + * 是不是黑色主题 + * + * @return 全屏展示 + */ + @Override + public boolean fullscreen() { + return false; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + AppManager.getInstance().popActivity(this); + } + + + /** + * 点击空白处软键盘是否消失 + */ + protected boolean canTouch = true; + /** + * 指定控件,点击软键盘不消失,需要addView相关控件 + */ + protected boolean viewCanTouch = true; + /** + * editText注册-不在这里注册一下,软件盘消失时光标还会闪烁 + */ + private final List editText = new ArrayList<>(); + /** + * 指定控件,点击软键盘不消失 + */ + private final List views = new ArrayList<>(); + + /** + * EditText与软键盘的控制 + * 如果点击空白处(非EditText),关闭软键盘,隐藏光标 + * + * @param editText + */ + public void addEditText(EditText... editText) { + this.editText.addAll(Arrays.asList(editText)); + } + + /** + * 部分页面存在点击按钮,软键盘不取消的需求,把对应view添加到这里就好了 + * + * @param view + */ + public void addView(View view) { + this.views.add(view); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + + for (View v : views) { + int[] leftTop = {0, 0}; + //获取输入框当前的location位置 + v.getLocationInWindow(leftTop); + int left = leftTop[0]; + int top = leftTop[1]; + int bottom = top + v.getHeight(); + int right = left + v.getWidth(); + if ((ev.getX() > left && ev.getX() < right + && ev.getY() > top && ev.getY() < bottom + && v.getVisibility() == View.VISIBLE)) { + viewCanTouch = false; + } + } + + if (!canTouch || !viewCanTouch) { + viewCanTouch = true; + return super.dispatchTouchEvent(ev); + } + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + View v = getCurrentFocus(); + if (isShouldHideInput(v, ev)) { + hideKeyboard(v); + } + return super.dispatchTouchEvent(ev); + } + // 必不可少,否则所有的组件都不会有TouchEvent了 + if (getWindow().superDispatchTouchEvent(ev)) { + return true; + } + return onTouchEvent(ev); + } + + /** + * 强制隐藏键盘 + * + * @param v + */ + public void hideKeyboard(View v) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + if (null == v || null == v.getWindowToken()) { + } else { + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + this.onHideKeyboard(); + for (EditText et : editText) { + et.clearFocus(); + } + } + } + + public void onHideKeyboard() { + } + + public boolean isShouldHideInput(View v, MotionEvent event) { + if (v != null && (v instanceof EditText)) { + int[] leftTop = {0, 0}; + //获取输入框当前的location位置 + v.getLocationInWindow(leftTop); + int left = leftTop[0]; + int top = leftTop[1]; + int bottom = top + v.getHeight(); + int right = left + v.getWidth(); + if ((event.getX() > left && event.getX() < right + && event.getY() > top && event.getY() < bottom)) { + // 点击的是输入框区域,保留点击EditText的事件 + return false; + } else { + return true; + } + } + return false; + } + +} diff --git a/base/src/main/java/com/xuqm/base/ui/BaseFragment.java b/base/src/main/java/com/xuqm/base/ui/BaseFragment.java new file mode 100644 index 0000000..3e43e7a --- /dev/null +++ b/base/src/main/java/com/xuqm/base/ui/BaseFragment.java @@ -0,0 +1,61 @@ +package com.xuqm.base.ui; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.DataBindingUtil; +import androidx.databinding.ViewDataBinding; +import androidx.fragment.app.Fragment; + +import com.xuqm.base.common.ToolsHelper; + +public abstract class BaseFragment extends Fragment { + + private V binding; + protected Context mContext; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = DataBindingUtil.inflate(getLayoutInflater(), getLayoutId(), container, false); + mContext = getActivity(); + return binding.getRoot(); + } + + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + initView(); + initData(); + } + + @NonNull + protected V getBinding() { + return binding; + } + + @LayoutRes + protected abstract int getLayoutId(); + + protected void initView() { + } + + protected void initData() { + } + + /** + * 收起键盘 + */ + protected void hideSoftInput() { + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getActivity().getWindow().getDecorView().getWindowToken(), 0); + } +} diff --git a/base/src/main/java/com/xuqm/base/ui/BaseListActivity.java b/base/src/main/java/com/xuqm/base/ui/BaseListActivity.java new file mode 100644 index 0000000..cd95f7b --- /dev/null +++ b/base/src/main/java/com/xuqm/base/ui/BaseListActivity.java @@ -0,0 +1,161 @@ +package com.xuqm.base.ui; + +import android.os.Bundle; +import android.view.View; + +import androidx.lifecycle.ViewModelProvider; +import androidx.paging.PagedList; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.xuqm.base.R; +import com.xuqm.base.adapter.BaseItem; +import com.xuqm.base.adapter.BasePagedAdapter; +import com.xuqm.base.common.RefreshResult; +import com.xuqm.base.common.ToolsHelper; +import com.xuqm.base.databinding.ActivityBaseListBinding; +import com.xuqm.base.view.enu.Status; +import com.xuqm.base.viewmodel.BaseListViewModel; +import com.xuqm.base.viewmodel.callback.AdapterObserverCallback; +import com.xuqm.base.viewmodel.callback.DataObserverCallback; + +import java.lang.reflect.ParameterizedType; + +/** + * 列表页面的activity继承这个,只需要指定一个adapter就可以展示数据了 + * 例子参考: + * MainActivity extends BaseListActivity + * + * User需要继承{@link BaseItem} + * MainViewModel 需要继承{@link BaseListViewModel} + * + * @param + * @param + */ +public abstract class BaseListActivity> + extends BaseActivity { + + private ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass(); + private Class cal = (Class) parameterizedType.getActualTypeArguments()[1]; + private VM viewModel; + + private BasePagedAdapter adapter; + + @Override + public int getLayoutId() { + return R.layout.activity_base_list; + } + + /** + * 获取到viewModel,可以做其它事情,比如item的增删改查等 + * + * @return viewModel + */ + public VM getViewModel() { + return viewModel; + } + + @Override + public void initView(Bundle savedInstanceState) { + getBaseBinding().activityRootView.setBackgroundResource(R.color.bg_window); + viewModel = new ViewModelProvider(this).get(cal); + adapter = adapter(); + adapter.setItemClickListener(this::itemClicked); + adapter.setItemLongClickListener(this::itemLongClicked); + getBinding().baseRecyclerView.setAdapter(adapter); + getBinding().baseRecyclerView.setLayoutManager(new LinearLayoutManager(mContext)); + getBinding().baseRefreshLayout.setOnRefreshListener(() -> viewModel.invalidate()); + } + + @Override + public void initData() { + + /* + 数据更新更新事件的观察者 + */ + viewModel.observeDataObserver(this, new DataObserverCallback() { + @Override + public void data(PagedList data) { + adapter.submitList(data);//数据加载 + } + + @Override + public void refreshResult(RefreshResult refreshResult) { + refreshFinished(refreshResult);//刷新状态处理 + } + + @Override + public void loadMoreResult(RefreshResult refreshResult) { + loadMoreFinished(refreshResult);//加载更多的处理 + } + }); + //数据更新处理观察者 + viewModel.observeAdapterObserver(this, new AdapterObserverCallback() { + @Override + public void notifyItem(int position, Object payload) { + adapter.notifyItemChanged(position, payload); + } + + @Override + public void removeItem(int position) { + adapter.notifyItemRemoved(position); + } + }); + + } + + /** + * 如果需要对item的点击事件做处理,直接重写这个方法就可以了 + * + * @param view view + * @param item item + * @param position position + */ + public void itemClicked(View view, T item, int position) { + + } + + /** + * 如果需要对item的长按事件做处理,直接重写这个方法就可以了 + * + * @param view view + * @param item item + * @param position position + * @return true + */ + public boolean itemLongClicked(View view, T item, int position) { + return false; + } + + private void refreshFinished(RefreshResult result) { + getBinding().baseRefreshLayout.setRefreshing(false); + + if (result == RefreshResult.SUCCEED) + getBinding().baseEmptyView.setStatus(Status.DISMISS); + else if (result == RefreshResult.FAILED) + getBinding().baseEmptyView.setStatus(Status.LOAD_FAILED); + else if (result == RefreshResult.NO_DATA) + getBinding().baseEmptyView.setStatus(Status.NO_DATA); + else if (result == RefreshResult.NO_MORE) { + getBinding().baseEmptyView.setStatus(Status.DISMISS); + ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed"); + } + } + + private void loadMoreFinished(RefreshResult result) { +// if (result == RefreshResult.SUCCEED) { +// } else if (result == RefreshResult.FAILED) { +// } else + if (result == RefreshResult.NO_MORE) { + ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed"); + } + + } + + /** + * 需要指定一个adapter,item只有一种类型的使用{@link com.xuqm.base.adapter.CommonPagedAdapter} + * 需要指定一个adapter,item有多种类型的使用{@link com.xuqm.base.adapter.BasePagedAdapter} + * + * @return 自定义的adapter + */ + public abstract BasePagedAdapter adapter(); +} diff --git a/base/src/main/java/com/xuqm/base/ui/BaseListAppBarFragment.java b/base/src/main/java/com/xuqm/base/ui/BaseListAppBarFragment.java new file mode 100644 index 0000000..c2fb177 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/ui/BaseListAppBarFragment.java @@ -0,0 +1,174 @@ +package com.xuqm.base.ui; + +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.LayoutRes; +import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModelProvider; +import androidx.paging.PagedList; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.google.android.material.appbar.AppBarLayout; +import com.xuqm.base.R; +import com.xuqm.base.adapter.BaseItem; +import com.xuqm.base.adapter.BasePagedAdapter; +import com.xuqm.base.common.RefreshResult; +import com.xuqm.base.common.ToolsHelper; +import com.xuqm.base.databinding.ActivityBaseListAppBarBinding; +import com.xuqm.base.view.enu.Status; +import com.xuqm.base.viewmodel.BaseListViewModel; +import com.xuqm.base.viewmodel.callback.AdapterObserverCallback; +import com.xuqm.base.viewmodel.callback.DataObserverCallback; + +import java.lang.reflect.ParameterizedType; + +public abstract class BaseListAppBarFragment> extends BaseFragment { + + private ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass(); + private Class cal = (Class) parameterizedType.getActualTypeArguments()[1]; + private VM viewModel; + + private BasePagedAdapter adapter; + + @Override + public int getLayoutId() { + return R.layout.activity_base_list_app_bar; + } + + @LayoutRes + protected int getAppBarView() { + return 0; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + if (0 != getAppBarView()) { + getLayoutInflater().inflate(getAppBarView(), getBinding().appBarLayout,true); + } + super.onActivityCreated(savedInstanceState); + } + + /** + * 获取到viewModel,可以做其它事情,比如item的增删改查等 + * + * @return viewModel + */ + public VM getViewModel() { + return viewModel; + } + + @Override + protected void initView() { + + viewModel = new ViewModelProvider(this).get(cal); + adapter = adapter(); + adapter.setItemClickListener(this::itemClicked); + adapter.setItemLongClickListener(this::itemLongClicked); + getBinding().baseRecyclerView.setAdapter(adapter); + getBinding().baseRecyclerView.setLayoutManager(new LinearLayoutManager(mContext)); + getBinding().baseRefreshLayout.setOnRefreshListener(() -> viewModel.invalidate()); + + getBinding().appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> { + if (verticalOffset >= 0) { + getBinding().baseRefreshLayout.setEnabled(true); + } else { + getBinding().baseRefreshLayout.setEnabled(false); + } + }); + } + + @Override + protected void initData() { + + /* + 数据更新更新事件的观察者 + */ + viewModel.observeDataObserver(this, new DataObserverCallback() { + @Override + public void data(PagedList data) { + adapter.submitList(data);//数据加载 + } + + @Override + public void refreshResult(RefreshResult refreshResult) { + refreshFinished(refreshResult);//刷新状态处理 + } + + @Override + public void loadMoreResult(RefreshResult refreshResult) { + loadMoreFinished(refreshResult);//加载更多的处理 + } + }); + //数据更新处理观察者 + viewModel.observeAdapterObserver(this, new AdapterObserverCallback() { + @Override + public void notifyItem(int position, Object payload) { + adapter.notifyItemChanged(position, payload); + } + + @Override + public void removeItem(int position) { + adapter.notifyItemRemoved(position); + } + }); + + } + + + /** + * 如果需要对item的点击事件做处理,直接重写这个方法就可以了 + * + * @param view view + * @param item item + * @param position position + */ + public void itemClicked(View view, T item, int position) { + + } + + /** + * 如果需要对item的长按事件做处理,直接重写这个方法就可以了 + * + * @param view view + * @param item item + * @param position position + * @return true + */ + public boolean itemLongClicked(View view, T item, int position) { + return false; + } + + public void refreshFinished(RefreshResult result) { + getBinding().baseRefreshLayout.setRefreshing(false); + + if (result == RefreshResult.SUCCEED) + getBinding().baseEmptyView.setStatus(Status.DISMISS); + else if (result == RefreshResult.FAILED) + getBinding().baseEmptyView.setStatus(Status.LOAD_FAILED); + else if (result == RefreshResult.NO_DATA) + getBinding().baseEmptyView.setStatus(Status.NO_DATA); + else if (result == RefreshResult.NO_MORE) { + getBinding().baseEmptyView.setStatus(Status.DISMISS); + ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed"); + } + } + + private void loadMoreFinished(RefreshResult result) { +// if (result == RefreshResult.SUCCEED) { +// } else if (result == RefreshResult.FAILED) { +// } else + if (result == RefreshResult.NO_MORE) { + ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed"); + } + + } + + /** + * 需要指定一个adapter,item只有一种类型的使用{@link com.xuqm.base.adapter.CommonPagedAdapter} + * 需要指定一个adapter,item有多种类型的使用{@link BasePagedAdapter} + * + * @return 自定义的adapter + */ + public abstract BasePagedAdapter adapter(); +} diff --git a/base/src/main/java/com/xuqm/base/ui/BaseListFormLayoutActivity.java b/base/src/main/java/com/xuqm/base/ui/BaseListFormLayoutActivity.java new file mode 100644 index 0000000..78d7a59 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/ui/BaseListFormLayoutActivity.java @@ -0,0 +1,174 @@ +package com.xuqm.base.ui; + +import android.os.Bundle; +import android.view.View; + +import androidx.databinding.ViewDataBinding; +import androidx.lifecycle.ViewModelProvider; +import androidx.paging.PagedList; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.xuqm.base.R; +import com.xuqm.base.adapter.BaseItem; +import com.xuqm.base.adapter.BasePagedAdapter; +import com.xuqm.base.common.RefreshResult; +import com.xuqm.base.common.ToolsHelper; +import com.xuqm.base.view.EmptyView; +import com.xuqm.base.view.enu.Status; +import com.xuqm.base.viewmodel.BaseListViewModel; +import com.xuqm.base.viewmodel.callback.AdapterObserverCallback; +import com.xuqm.base.viewmodel.callback.DataObserverCallback; + +import java.lang.reflect.ParameterizedType; + +/** + * 列表页面的activity继承这个,只需要指定一个adapter就可以展示数据了 + * 例子参考: + * MainActivity extends BaseListActivity + *

+ * User需要继承{@link BaseItem} + * MainViewModel 需要继承{@link BaseListViewModel} + * + * @param + * @param + */ +public abstract class BaseListFormLayoutActivity, V extends ViewDataBinding> + extends BaseActivity { + + private ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass(); + private Class cal = (Class) parameterizedType.getActualTypeArguments()[1]; + private VM viewModel; + + private BasePagedAdapter adapter; + + @Override + public int getLayoutId() { + return R.layout.activity_base_list; + } + + /** + * 获取到viewModel,可以做其它事情,比如item的增删改查等 + * + * @return viewModel + */ + public VM getViewModel() { + return viewModel; + } + public BasePagedAdapter getAdapter() { + return adapter; + } + + public RecyclerView recyclerView; + private SwipeRefreshLayout swipeRefreshLayout; + private EmptyView baseEmptyView; + + @Override + public void initView(Bundle savedInstanceState) { + viewModel = new ViewModelProvider(this).get(cal); + adapter = adapter(); + adapter.setItemClickListener(this::itemClicked); + adapter.setItemLongClickListener(this::itemLongClicked); + recyclerView = findViewById(R.id.baseRecyclerView); + swipeRefreshLayout = findViewById(R.id.baseRefreshLayout); + baseEmptyView = findViewById(R.id.baseEmptyView); + recyclerView.setAdapter(adapter); + recyclerView.setLayoutManager(new LinearLayoutManager(mContext)); + swipeRefreshLayout.setOnRefreshListener(() -> viewModel.invalidate()); + } + + @Override + public void initData() { + + /* + 数据更新更新事件的观察者 + */ + viewModel.observeDataObserver(this, new DataObserverCallback() { + @Override + public void data(PagedList data) { + adapter.submitList(data);//数据加载 + } + + @Override + public void refreshResult(RefreshResult refreshResult) { + refreshFinished(refreshResult);//刷新状态处理 + } + + @Override + public void loadMoreResult(RefreshResult refreshResult) { + loadMoreFinished(refreshResult);//加载更多的处理 + } + }); + //数据更新处理观察者 + viewModel.observeAdapterObserver(this, new AdapterObserverCallback() { + @Override + public void notifyItem(int position, Object payload) { + adapter.notifyItemChanged(position, payload); + } + + @Override + public void removeItem(int position) { +// adapter.notifyItemRemoved(position); + adapter.notifyItemRangeRemoved(position,adapter.getItemCount()-1); + } + }); + + } + + /** + * 如果需要对item的点击事件做处理,直接重写这个方法就可以了 + * + * @param view view + * @param item item + * @param position position + */ + public void itemClicked(View view, T item, int position) { + + } + + /** + * 如果需要对item的长按事件做处理,直接重写这个方法就可以了 + * + * @param view view + * @param item item + * @param position position + * @return true + */ + public boolean itemLongClicked(View view, T item, int position) { + return false; + } + + public void refreshFinished(RefreshResult result) { + swipeRefreshLayout.setRefreshing(false); + + if (result == RefreshResult.SUCCEED) + baseEmptyView.setStatus(Status.DISMISS); + else if (result == RefreshResult.FAILED) + baseEmptyView.setStatus(Status.LOAD_FAILED); + else if (result == RefreshResult.NO_DATA) + baseEmptyView.setStatus(Status.NO_DATA); + else if (result == RefreshResult.NO_MORE) { + baseEmptyView.setStatus(Status.DISMISS); + ToolsHelper.snack(baseEmptyView, "All loads completed"); + } + } + + public void loadMoreFinished(RefreshResult result) { +// if (result == RefreshResult.SUCCEED) { +// } else if (result == RefreshResult.FAILED) { +// } else + if (result == RefreshResult.NO_MORE) { + ToolsHelper.snack(baseEmptyView, "All loads completed"); + } + + } + + /** + * 需要指定一个adapter,item只有一种类型的使用{@link com.xuqm.base.adapter.CommonPagedAdapter} + * 需要指定一个adapter,item有多种类型的使用{@link BasePagedAdapter} + * + * @return 自定义的adapter + */ + public abstract BasePagedAdapter adapter(); +} diff --git a/base/src/main/java/com/xuqm/base/ui/BaseListFragment.java b/base/src/main/java/com/xuqm/base/ui/BaseListFragment.java new file mode 100644 index 0000000..d3dc401 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/ui/BaseListFragment.java @@ -0,0 +1,163 @@ +package com.xuqm.base.ui; + +import android.view.View; + +import androidx.lifecycle.ViewModelProvider; +import androidx.paging.PagedList; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.xuqm.base.R; +import com.xuqm.base.adapter.BaseItem; +import com.xuqm.base.adapter.BasePagedAdapter; +import com.xuqm.base.common.RefreshResult; +import com.xuqm.base.common.ToolsHelper; +import com.xuqm.base.databinding.ActivityBaseListBinding; +import com.xuqm.base.view.enu.Status; +import com.xuqm.base.viewmodel.BaseListViewModel; +import com.xuqm.base.viewmodel.callback.AdapterObserverCallback; +import com.xuqm.base.viewmodel.callback.DataObserverCallback; + +import java.lang.reflect.ParameterizedType; + +public abstract class BaseListFragment> extends BaseFragment { + + private ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass(); + private Class cal = (Class) parameterizedType.getActualTypeArguments()[1]; + protected VM viewModel; + + private BasePagedAdapter adapter; + + @Override + public int getLayoutId() { + return R.layout.activity_base_list; + } + + /** + * 获取到viewModel,可以做其它事情,比如item的增删改查等 + * + * @return viewModel + */ + public VM getViewModel() { + return viewModel; + } + + @Override + protected void initView() { + + if (getFactory() == null) + viewModel = new ViewModelProvider(this).get(cal); + else + viewModel = new ViewModelProvider(this, getFactory()).get(cal); + adapter = adapter(); + adapter.setItemClickListener(this::itemClicked); + adapter.setItemLongClickListener(this::itemLongClicked); + getBinding().baseRecyclerView.setAdapter(adapter); + getBinding().baseRecyclerView.setLayoutManager(layoutManager()); + getBinding().baseRefreshLayout.setOnRefreshListener(() -> viewModel.invalidate()); + } + + protected ViewModelProvider.Factory getFactory() { + return null; + } + + @Override + protected void initData() { + + /* + 数据更新更新事件的观察者 + */ + viewModel.observeDataObserver(this, new DataObserverCallback() { + @Override + public void data(PagedList data) { + adapter.submitList(data);//数据加载 + } + + @Override + public void refreshResult(RefreshResult refreshResult) { + refreshFinished(refreshResult);//刷新状态处理 + } + + @Override + public void loadMoreResult(RefreshResult refreshResult) { + loadMoreFinished(refreshResult);//加载更多的处理 + } + }); + //数据更新处理观察者 + viewModel.observeAdapterObserver(this, new AdapterObserverCallback() { + @Override + public void notifyItem(int position, Object payload) { + adapter.notifyItemChanged(position, payload); + } + + @Override + public void removeItem(int position) { + adapter.notifyItemRemoved(position); + } + }); + + } + + + /** + * 如果需要对item的点击事件做处理,直接重写这个方法就可以了 + * + * @param view view + * @param item item + * @param position position + */ + public void itemClicked(View view, T item, int position) { + + } + + /** + * 如果需要对item的长按事件做处理,直接重写这个方法就可以了 + * + * @param view view + * @param item item + * @param position position + * @return true + */ + public boolean itemLongClicked(View view, T item, int position) { + return false; + } + + public void refreshFinished(RefreshResult result) { + getBinding().baseRefreshLayout.setRefreshing(false); + + if (result == RefreshResult.SUCCEED) + getBinding().baseEmptyView.setStatus(Status.DISMISS); + else if (result == RefreshResult.FAILED) + getBinding().baseEmptyView.setStatus(Status.LOAD_FAILED); + else if (result == RefreshResult.NO_DATA) + getBinding().baseEmptyView.setStatus(Status.NO_DATA); + else if (result == RefreshResult.NO_MORE) { + getBinding().baseEmptyView.setStatus(Status.DISMISS); +// ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed"); + } + } + + private void loadMoreFinished(RefreshResult result) { +// if (result == RefreshResult.SUCCEED) { +// } else if (result == RefreshResult.FAILED) { +// } else + if (result == RefreshResult.NO_MORE) { +// ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed"); + } + + } + + /** + * 需要指定一个adapter,item只有一种类型的使用{@link com.xuqm.base.adapter.CommonPagedAdapter} + * 需要指定一个adapter,item有多种类型的使用{@link com.xuqm.base.adapter.BasePagedAdapter} + * + * @return 自定义的adapter + */ + public abstract BasePagedAdapter adapter(); + + public RecyclerView.LayoutManager layoutManager() { + return new LinearLayoutManager(mContext); + } + + ; +} diff --git a/base/src/main/java/com/xuqm/base/ui/BaseListFromLayoutFragment.java b/base/src/main/java/com/xuqm/base/ui/BaseListFromLayoutFragment.java new file mode 100644 index 0000000..150ca64 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/ui/BaseListFromLayoutFragment.java @@ -0,0 +1,185 @@ +package com.xuqm.base.ui; + +import android.view.View; + +import androidx.databinding.ViewDataBinding; +import androidx.lifecycle.ViewModelProvider; +import androidx.paging.PagedList; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.xuqm.base.R; +import com.xuqm.base.adapter.BaseItem; +import com.xuqm.base.adapter.BasePagedAdapter; +import com.xuqm.base.common.RefreshResult; +import com.xuqm.base.common.ToolsHelper; +import com.xuqm.base.view.EmptyView; +import com.xuqm.base.view.enu.Status; +import com.xuqm.base.viewmodel.BaseListViewModel; +import com.xuqm.base.viewmodel.callback.AdapterObserverCallback; +import com.xuqm.base.viewmodel.callback.DataObserverCallback; + +import java.lang.reflect.ParameterizedType; + +public abstract class BaseListFromLayoutFragment, V extends ViewDataBinding> extends BaseFragment { + + private ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass(); + private Class cal = (Class) parameterizedType.getActualTypeArguments()[1]; + protected VM viewModel; + + private BasePagedAdapter adapter; + + @Override + public int getLayoutId() { + return R.layout.activity_base_list; + } + + /** + * 获取到viewModel,可以做其它事情,比如item的增删改查等 + * + * @return viewModel + */ + public VM getViewModel() { + return viewModel; + } + + public RecyclerView recyclerView; + private SwipeRefreshLayout swipeRefreshLayout; + private EmptyView baseEmptyView; + + @Override + protected void initView() { + + if (getFactory() == null) + viewModel = new ViewModelProvider(this).get(cal); + else + viewModel = new ViewModelProvider(this, getFactory()).get(cal); + adapter = adapter(); + recyclerView = recyclerView(); + swipeRefreshLayout = swipeRefreshLayout(); + baseEmptyView = baseEmptyView(); + adapter.setItemClickListener(this::itemClicked); + adapter.setItemLongClickListener(this::itemLongClicked); + recyclerView.setLayoutManager(layoutManager()); + recyclerView.setAdapter(adapter); + swipeRefreshLayout.setOnRefreshListener(() -> viewModel.invalidate()); + } + + protected ViewModelProvider.Factory getFactory() { + return null; + } + + @Override + protected void initData() { + + /* + 数据更新更新事件的观察者 + */ + viewModel.observeDataObserver(this, new DataObserverCallback() { + @Override + public void data(PagedList data) { + adapter.submitList(data);//数据加载 + } + + @Override + public void refreshResult(RefreshResult refreshResult) { + refreshFinished(refreshResult);//刷新状态处理 + } + + @Override + public void loadMoreResult(RefreshResult refreshResult) { + loadMoreFinished(refreshResult);//加载更多的处理 + } + }); + //数据更新处理观察者 + viewModel.observeAdapterObserver(this, new AdapterObserverCallback() { + @Override + public void notifyItem(int position, Object payload) { + adapter.notifyItemChanged(position, payload); + } + + @Override + public void removeItem(int position) { + adapter.notifyItemRemoved(position); + } + }); + + } + + + /** + * 如果需要对item的点击事件做处理,直接重写这个方法就可以了 + * + * @param view view + * @param item item + * @param position position + */ + public void itemClicked(View view, T item, int position) { + + } + + /** + * 如果需要对item的长按事件做处理,直接重写这个方法就可以了 + * + * @param view view + * @param item item + * @param position position + * @return true + */ + public boolean itemLongClicked(View view, T item, int position) { + return false; + } + + public void refreshFinished(RefreshResult result) { + swipeRefreshLayout.setRefreshing(false); + + if (result == RefreshResult.SUCCEED) + baseEmptyView.setStatus(Status.DISMISS); + else if (result == RefreshResult.FAILED) + baseEmptyView.setStatus(Status.LOAD_FAILED); + else if (result == RefreshResult.NO_DATA) + baseEmptyView.setStatus(Status.NO_DATA); + else if (result == RefreshResult.NO_MORE) { + baseEmptyView.setStatus(Status.DISMISS); + ToolsHelper.snack(baseEmptyView, "All loads completed"); + } + } + + private void loadMoreFinished(RefreshResult result) { +// if (result == RefreshResult.SUCCEED) { +// } else if (result == RefreshResult.FAILED) { +// } else + if (result == RefreshResult.NO_MORE) { +// ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed"); + } + + } + + /** + * 需要指定一个adapter,item只有一种类型的使用{@link com.xuqm.base.adapter.CommonPagedAdapter} + * 需要指定一个adapter,item有多种类型的使用{@link BasePagedAdapter} + * + * @return 自定义的adapter + */ + public abstract BasePagedAdapter adapter(); + + public RecyclerView recyclerView() { + return getActivity().findViewById(R.id.baseRecyclerView); + } + + public SwipeRefreshLayout swipeRefreshLayout() { + return getActivity().findViewById(R.id.baseRefreshLayout); + } + + public EmptyView baseEmptyView() { + return getActivity().findViewById(R.id.baseEmptyView); + } + + + public RecyclerView.LayoutManager layoutManager() { + return new LinearLayoutManager(mContext); + } + + ; +} diff --git a/base/src/main/java/com/xuqm/base/ui/adapter/ImageViewAdapter.java b/base/src/main/java/com/xuqm/base/ui/adapter/ImageViewAdapter.java new file mode 100644 index 0000000..d82a84c --- /dev/null +++ b/base/src/main/java/com/xuqm/base/ui/adapter/ImageViewAdapter.java @@ -0,0 +1,44 @@ +package com.xuqm.base.ui.adapter; + +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.widget.ImageView; + +import androidx.databinding.BindingAdapter; + +import com.bumptech.glide.Glide; +import com.xuqm.base.R; +import com.xuqm.base.common.ImageHelper; + +public class ImageViewAdapter { + @BindingAdapter("android:src") + public static void setSrc(ImageView view, Bitmap bitmap) { + view.setImageBitmap(bitmap); + } + + @BindingAdapter("android:src") + public static void setSrc(ImageView view, int resId) { + view.setImageResource(resId); + } + + + @BindingAdapter("imageUrl") + public static void setSrc(ImageView imageView, String url) { + Glide.with(imageView.getContext()).load(url) + .placeholder(R.drawable.ic_loading) + .into(imageView); +// ImageHelper.load(imageView,url); + } + + + @BindingAdapter({"imageUrl", "placeHolder", "error"}) + public static void loadImage(ImageView imageView, String url, Drawable holderDrawable, Drawable errorDrawable) { + Glide.with(imageView.getContext()) + .load(url) + .placeholder(holderDrawable) + .error(errorDrawable) + .into(imageView); + } + + +} diff --git a/base/src/main/java/com/xuqm/base/ui/callback/ToolBarListener.java b/base/src/main/java/com/xuqm/base/ui/callback/ToolBarListener.java new file mode 100644 index 0000000..f603cdf --- /dev/null +++ b/base/src/main/java/com/xuqm/base/ui/callback/ToolBarListener.java @@ -0,0 +1,5 @@ +package com.xuqm.base.ui.callback; + +public interface ToolBarListener { + void back(); +} diff --git a/base/src/main/java/com/xuqm/base/ui/callback/UiCallback.java b/base/src/main/java/com/xuqm/base/ui/callback/UiCallback.java new file mode 100644 index 0000000..c0882e4 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/ui/callback/UiCallback.java @@ -0,0 +1,23 @@ +package com.xuqm.base.ui.callback; + +import android.os.Bundle; + +import androidx.annotation.LayoutRes; + +public interface UiCallback { + @LayoutRes + int getLayoutId(); + + void setContentView(); + + void initView(Bundle savedInstanceState); + + void initData(); + + int showStatus(); + + boolean transparentStatusBar(); + + boolean fullscreen(); + +} diff --git a/base/src/main/java/com/xuqm/base/view/EmptyMsgView.java b/base/src/main/java/com/xuqm/base/view/EmptyMsgView.java new file mode 100644 index 0000000..0c65390 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/view/EmptyMsgView.java @@ -0,0 +1,100 @@ +package com.xuqm.base.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.xuqm.base.R; +import com.xuqm.base.view.enu.Status; + +/** + * 自定义的view + * 展示不同的数据加载状态 + */ +public class EmptyMsgView extends FrameLayout { + + private View contentView; + private Status status = Status.DISMISS; + + + public EmptyMsgView(@NonNull Context context) { + super(context); + init(); + } + + public EmptyMsgView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + View.inflate(getContext(), R.layout.empty_msg_view, this); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + if (getChildCount() > 5) { + throw new IllegalStateException("EmptyView can only have one child view"); + } + if (getChildCount() == 5) { + contentView = getChildAt(4); + } + emptyViewLoading = findViewById(R.id.emptyViewLoading); + emptyViewNoData = findViewById(R.id.emptyViewNoData); + emptyViewLoadFailed = findViewById(R.id.emptyViewLoadFailed); + emptyViewNetworkUnavailable = findViewById(R.id.emptyViewNetworkUnavailable); + + setStatus(Status.DISMISS); + } + + private LinearLayout emptyViewLoading; + private LinearLayout emptyViewNoData; + private LinearLayout emptyViewLoadFailed; + private LinearLayout emptyViewNetworkUnavailable; + + + public void setStatus(Status status) { +// LogHelper.e(contentView.getVisibility()); + if (status != this.status) { + + if (status == Status.DISMISS) { + if (null != contentView) contentView.setVisibility(VISIBLE); + emptyViewLoading.setVisibility(GONE); + emptyViewNoData.setVisibility(GONE); + emptyViewLoadFailed.setVisibility(GONE); + emptyViewNetworkUnavailable.setVisibility(GONE); + } else if (status == Status.LOADING) { + if (null != contentView) contentView.setVisibility(GONE); + emptyViewLoading.setVisibility(VISIBLE); + emptyViewNoData.setVisibility(GONE); + emptyViewLoadFailed.setVisibility(GONE); + emptyViewNetworkUnavailable.setVisibility(GONE); + } else if (status == Status.NO_DATA) { + if (null != contentView) contentView.setVisibility(GONE); + emptyViewLoading.setVisibility(GONE); + emptyViewNoData.setVisibility(VISIBLE); + emptyViewLoadFailed.setVisibility(GONE); + emptyViewNetworkUnavailable.setVisibility(GONE); + } else if (status == Status.LOAD_FAILED) { + if (null != contentView) contentView.setVisibility(GONE); + emptyViewLoading.setVisibility(GONE); + emptyViewNoData.setVisibility(GONE); + emptyViewLoadFailed.setVisibility(VISIBLE); + emptyViewNetworkUnavailable.setVisibility(GONE); + } else if (status == Status.NETWORK_UNAVAILABLE) { + if (null != contentView) contentView.setVisibility(GONE); + emptyViewLoading.setVisibility(GONE); + emptyViewNoData.setVisibility(GONE); + emptyViewLoadFailed.setVisibility(GONE); + emptyViewNetworkUnavailable.setVisibility(VISIBLE); + } + this.status = status; + } + } +} diff --git a/base/src/main/java/com/xuqm/base/view/EmptyView.java b/base/src/main/java/com/xuqm/base/view/EmptyView.java new file mode 100644 index 0000000..6399fc1 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/view/EmptyView.java @@ -0,0 +1,112 @@ +package com.xuqm.base.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.xuqm.base.R; +import com.xuqm.base.common.ImageHelper; +import com.xuqm.base.view.enu.Status; + +/** + * 自定义的view + * 展示不同的数据加载状态 + */ +public class EmptyView extends FrameLayout { + + private View contentView; + private Status status = Status.DISMISS; + + + public EmptyView(@NonNull Context context) { + super(context); + init(); + } + + public EmptyView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + View.inflate(getContext(), R.layout.empty_view, this); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + if (getChildCount() > 5) { + throw new IllegalStateException("EmptyView can only have one child view"); + } + if (getChildCount() == 5) { + contentView = getChildAt(4); + } + emptyViewLoading = findViewById(R.id.emptyViewLoading); + emptyViewNoData = findViewById(R.id.emptyViewNoData); + emptyViewLoadFailed = findViewById(R.id.emptyViewLoadFailed); + emptyViewNetworkUnavailable = findViewById(R.id.emptyViewNetworkUnavailable); + emptyIv = findViewById(R.id.empty_iv); + emptyTv = findViewById(R.id.empty_tv); + + setStatus(Status.DISMISS); + } + + private LinearLayout emptyViewLoading; + private LinearLayout emptyViewNoData; + private LinearLayout emptyViewLoadFailed; + private LinearLayout emptyViewNetworkUnavailable; + private ImageView emptyIv; + private TextView emptyTv; + + + public void setEmptyView(String content, Object url) { + emptyTv.setText(content); + ImageHelper.load(emptyIv, url); + } + + public void setStatus(Status status) { +// LogHelper.e(contentView.getVisibility()); + if (status != this.status) { + + if (status == Status.DISMISS) { + if (null != contentView) contentView.setVisibility(VISIBLE); + emptyViewLoading.setVisibility(GONE); + emptyViewNoData.setVisibility(GONE); + emptyViewLoadFailed.setVisibility(GONE); + emptyViewNetworkUnavailable.setVisibility(GONE); + } else if (status == Status.LOADING) { + if (null != contentView) contentView.setVisibility(GONE); + emptyViewLoading.setVisibility(VISIBLE); + emptyViewNoData.setVisibility(GONE); + emptyViewLoadFailed.setVisibility(GONE); + emptyViewNetworkUnavailable.setVisibility(GONE); + } else if (status == Status.NO_DATA) { + if (null != contentView) contentView.setVisibility(GONE); + emptyViewLoading.setVisibility(GONE); + emptyViewNoData.setVisibility(VISIBLE); + emptyViewLoadFailed.setVisibility(GONE); + emptyViewNetworkUnavailable.setVisibility(GONE); + } else if (status == Status.LOAD_FAILED) { + if (null != contentView) contentView.setVisibility(GONE); + emptyViewLoading.setVisibility(GONE); + emptyViewNoData.setVisibility(GONE); + emptyViewLoadFailed.setVisibility(VISIBLE); + emptyViewNetworkUnavailable.setVisibility(GONE); + } else if (status == Status.NETWORK_UNAVAILABLE) { + if (null != contentView) contentView.setVisibility(GONE); + emptyViewLoading.setVisibility(GONE); + emptyViewNoData.setVisibility(GONE); + emptyViewLoadFailed.setVisibility(GONE); + emptyViewNetworkUnavailable.setVisibility(VISIBLE); + } + this.status = status; + } + } +} diff --git a/base/src/main/java/com/xuqm/base/view/FlowRadioGroup.java b/base/src/main/java/com/xuqm/base/view/FlowRadioGroup.java new file mode 100644 index 0000000..6d86836 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/view/FlowRadioGroup.java @@ -0,0 +1,120 @@ +package com.xuqm.base.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.RadioGroup; + +public class FlowRadioGroup extends RadioGroup { + + public FlowRadioGroup(Context context) { + super(context); + } + + public FlowRadioGroup(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + //调用ViewGroup的方法,测量子view + measureChildren(widthMeasureSpec, heightMeasureSpec); + + //最大的宽 + int maxWidth = 0; + //累计的高 + int totalHeight = 0; + + //当前这一行的累计行宽 + int lineWidth = 0; + //当前这行的最大行高 + int maxLineHeight = 0; + //用于记录换行前的行宽和行高 + int oldHeight; + int oldWidth; + + int count = getChildCount(); + //假设 widthMode和heightMode都是AT_MOST + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); + oldHeight = maxLineHeight; + //当前最大宽度 + oldWidth = maxWidth; + + int deltaX = child.getMeasuredWidth() + params.leftMargin + params.rightMargin; + if (lineWidth + deltaX + getPaddingLeft() + getPaddingRight() > widthSize) {//如果折行,height增加 + //和目前最大的宽度比较,得到最宽。不能加上当前的child的宽,所以用的是oldWidth + maxWidth = Math.max(lineWidth, oldWidth); + //重置宽度 + lineWidth = deltaX; + //累加高度 + totalHeight += oldHeight; + //重置行高,当前这个View,属于下一行,因此当前最大行高为这个child的高度加上margin + maxLineHeight = child.getMeasuredHeight() + params.topMargin + params.bottomMargin; + + } else { + //不换行,累加宽度 + lineWidth += deltaX; + //不换行,计算行最高 + int deltaY = child.getMeasuredHeight() + params.topMargin + params.bottomMargin; + maxLineHeight = Math.max(maxLineHeight, deltaY); + } + if (i == count - 1) { + //前面没有加上下一行的搞,如果是最后一行,还要再叠加上最后一行的最高的值 + totalHeight += maxLineHeight; + //计算最后一行和前面的最宽的一行比较 + maxWidth = Math.max(lineWidth, oldWidth); + } + } + + //加上当前容器的padding值 + maxWidth += getPaddingLeft() + getPaddingRight(); + totalHeight += getPaddingTop() + getPaddingBottom(); + setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : maxWidth, + heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight); + + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int count = getChildCount(); + //pre为前面所有的child的相加后的位置 + int preLeft = getPaddingLeft(); + int preTop = getPaddingTop(); + //记录每一行的最高值 + int maxHeight = 0; + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); + //r-l为当前容器的宽度。如果子view的累积宽度大于容器宽度,就换行。 + if (preLeft + params.leftMargin + child.getMeasuredWidth() + params.rightMargin + getPaddingRight() > (r - l)) { + //重置 + preLeft = getPaddingLeft(); + //要选择child的height最大的作为设置 + preTop = preTop + maxHeight; + maxHeight = getChildAt(i).getMeasuredHeight() + params.topMargin + params.bottomMargin; + } else { //不换行,计算最大高度 + maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + params.topMargin + params.bottomMargin); + } + //left坐标 + int left = preLeft + params.leftMargin; + //top坐标 + int top = preTop + params.topMargin; + int right = left + child.getMeasuredWidth(); + int bottom = top + child.getMeasuredHeight(); + //为子view布局 + child.layout(left, top, right, bottom); + //计算布局结束后,preLeft的值 + preLeft += params.leftMargin + child.getMeasuredWidth() + params.rightMargin; + + } + + } + +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/view/GalleryTransformer.kt b/base/src/main/java/com/xuqm/base/view/GalleryTransformer.kt new file mode 100644 index 0000000..b4dba34 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/view/GalleryTransformer.kt @@ -0,0 +1,35 @@ +package com.xuqm.base.view + +import android.view.View +import androidx.viewpager2.widget.ViewPager2 + +class GalleryTransformer : ViewPager2.PageTransformer { + companion object { + private const val TARGET_ALPHA = 0.5f + private const val TARGET_SCALE = 0.9f + } + + override fun transformPage(page: View, position: Float) { + if (position < -1 || position > 1) { + //当前页面左侧以及右侧的页面效果 + page.alpha = TARGET_ALPHA + page.scaleX = TARGET_SCALE + page.scaleY = TARGET_SCALE + } else { + //从不可见变为可见效果 + + //透明度效果 + if (position <= 0) { + page.alpha = + TARGET_ALPHA + TARGET_ALPHA * (1 + position) + } else { + page.alpha = + TARGET_ALPHA + TARGET_ALPHA * (1 - position) + } + //缩放效果 + val scale = Math.max(TARGET_SCALE, 1 - Math.abs(position)) + page.scaleX = scale + page.scaleY = scale + } + } +} diff --git a/base/src/main/java/com/xuqm/base/view/MaxLimitRecyclerView.java b/base/src/main/java/com/xuqm/base/view/MaxLimitRecyclerView.java new file mode 100644 index 0000000..6c158da --- /dev/null +++ b/base/src/main/java/com/xuqm/base/view/MaxLimitRecyclerView.java @@ -0,0 +1,69 @@ +package com.xuqm.base.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import com.xuqm.base.R; + +/** + * max limit-able RecyclerView + */ +public class MaxLimitRecyclerView extends RecyclerView { + private int mMaxHeight; + private int mMaxWidth; + + public MaxLimitRecyclerView(Context context) { + this(context, null); + } + + public MaxLimitRecyclerView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public MaxLimitRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + inti(attrs); + } + + private void inti(AttributeSet attrs) { + if (getContext() != null && attrs != null) { + TypedArray typedArray = null; + try { + typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MaxLimitRecyclerView); + if (typedArray.hasValue(R.styleable.MaxLimitRecyclerView_limit_maxHeight)) { + mMaxHeight = typedArray.getDimensionPixelOffset(R.styleable.MaxLimitRecyclerView_limit_maxHeight, -1); + } + if (typedArray.hasValue(R.styleable.MaxLimitRecyclerView_limit_maxWidth)) { + mMaxWidth = typedArray.getDimensionPixelOffset(R.styleable.MaxLimitRecyclerView_limit_maxWidth, -1); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (typedArray != null) { + typedArray.recycle(); + } + } + } + } + + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + super.onMeasure(widthSpec, heightSpec); + boolean needLimit = mMaxHeight >= 0 || mMaxWidth >= 0; + if (needLimit) { + int limitHeight = getMeasuredHeight(); + int limitWith = getMeasuredWidth(); + if (getMeasuredHeight() > mMaxHeight) { + limitHeight = mMaxHeight; + } +// if (getMeasuredWidth() > mMaxWidth) { +// limitWith = mMaxWidth; +// } + setMeasuredDimension(limitWith, limitHeight); + } + } +} diff --git a/base/src/main/java/com/xuqm/base/view/SelectableRoundedImageView.java b/base/src/main/java/com/xuqm/base/view/SelectableRoundedImageView.java new file mode 100644 index 0000000..175ddaf --- /dev/null +++ b/base/src/main/java/com/xuqm/base/view/SelectableRoundedImageView.java @@ -0,0 +1,657 @@ +package com.xuqm.base.view; + + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.net.Uri; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.xuqm.base.R; + +/** + * Created by JiYaRuo on 2019/10/11. + * + * @BeUsedFor 自定义ImageView 随心所欲的改变ImageView的四个圆角角度 + */ +@SuppressLint("AppCompatCustomView") +public class SelectableRoundedImageView extends ImageView { + public static final String TAG = "SelectableRoundedImageView"; + + private int mResource = 0; + + private static final ScaleType[] sScaleTypeArray = { + ScaleType.MATRIX, + ScaleType.FIT_XY, + ScaleType.FIT_START, + ScaleType.FIT_CENTER, + ScaleType.FIT_END, + ScaleType.CENTER, + ScaleType.CENTER_CROP, + ScaleType.CENTER_INSIDE + }; + + // Set default scale type to FIT_CENTER, which is default scale type of + // original ImageView. + private ScaleType mScaleType = ScaleType.FIT_CENTER; + + private float mLeftTopCornerRadius = 0.0f; + private float mRightTopCornerRadius = 0.0f; + private float mLeftBottomCornerRadius = 0.0f; + private float mRightBottomCornerRadius = 0.0f; + + private float mBorderWidth = 0.0f; + private static final int DEFAULT_BORDER_COLOR = Color.BLACK; + private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR); + + private boolean isOval = false; + + private Drawable mDrawable; + + private float[] mRadii = new float[]{0, 0, 0, 0, 0, 0, 0, 0}; + + public SelectableRoundedImageView(Context context) { + super(context); + } + + public SelectableRoundedImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SelectableRoundedImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SelectableRoundedImageView, defStyle, 0); + + final int index = a.getInt(R.styleable.SelectableRoundedImageView_android_scaleType, -1); + if (index >= 0) { + setScaleType(sScaleTypeArray[index]); + } + + mLeftTopCornerRadius = a.getDimensionPixelSize( + R.styleable.SelectableRoundedImageView_sriv_left_top_corner_radius, 0); + mRightTopCornerRadius = a.getDimensionPixelSize( + R.styleable.SelectableRoundedImageView_sriv_right_top_corner_radius, 0); + mLeftBottomCornerRadius = a.getDimensionPixelSize( + R.styleable.SelectableRoundedImageView_sriv_left_bottom_corner_radius, 0); + mRightBottomCornerRadius = a.getDimensionPixelSize( + R.styleable.SelectableRoundedImageView_sriv_right_bottom_corner_radius, 0); + + if (mLeftTopCornerRadius < 0.0f || mRightTopCornerRadius < 0.0f + || mLeftBottomCornerRadius < 0.0f || mRightBottomCornerRadius < 0.0f) { + throw new IllegalArgumentException("radius values cannot be negative."); + } + + mRadii = new float[]{ + mLeftTopCornerRadius, mLeftTopCornerRadius, + mRightTopCornerRadius, mRightTopCornerRadius, + mRightBottomCornerRadius, mRightBottomCornerRadius, + mLeftBottomCornerRadius, mLeftBottomCornerRadius + }; + + mBorderWidth = a.getDimensionPixelSize(R.styleable.SelectableRoundedImageView_sriv_border_width, 0); + if (mBorderWidth < 0) { + throw new IllegalArgumentException("border width cannot be negative."); + } + + mBorderColor = a.getColorStateList(R.styleable.SelectableRoundedImageView_sriv_border_color); + if (mBorderColor == null) { + mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR); + } + + isOval = a.getBoolean(R.styleable.SelectableRoundedImageView_sriv_oval, false); + a.recycle(); + + updateDrawable(); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + invalidate(); + } + + @Override + public ScaleType getScaleType() { + return mScaleType; + } + + @Override + public void setScaleType(ScaleType scaleType) { + super.setScaleType(scaleType); + mScaleType = scaleType; + updateDrawable(); + } + + @Override + public void setImageDrawable(Drawable drawable) { + mResource = 0; + mDrawable = SelectableRoundedCornerDrawable.fromDrawable(drawable, getResources()); + super.setImageDrawable(mDrawable); + updateDrawable(); + } + + @Override + public void setImageBitmap(Bitmap bm) { + mResource = 0; + mDrawable = SelectableRoundedCornerDrawable.fromBitmap(bm, getResources()); + super.setImageDrawable(mDrawable); + updateDrawable(); + } + + @Override + public void setImageResource(int resId) { + if (mResource != resId) { + mResource = resId; + mDrawable = resolveResource(); + super.setImageDrawable(mDrawable); + updateDrawable(); + } + } + + @Override + public void setImageURI(Uri uri) { + super.setImageURI(uri); + setImageDrawable(getDrawable()); + } + + private Drawable resolveResource() { + Resources rsrc = getResources(); + if (rsrc == null) { + return null; + } + + Drawable d = null; + + if (mResource != 0) { + try { + d = rsrc.getDrawable(mResource); + } catch (Exception e) { +// Log.w(TAG, "Unable to find resource: " + mResource, e); + // Don't try again. + mResource = 0; + } + } + return SelectableRoundedCornerDrawable.fromDrawable(d, getResources()); + } + + private void updateDrawable() { + if (mDrawable == null) { + return; + } + + ((SelectableRoundedCornerDrawable) mDrawable).setScaleType(mScaleType); + ((SelectableRoundedCornerDrawable) mDrawable).setCornerRadii(mRadii); + ((SelectableRoundedCornerDrawable) mDrawable).setBorderWidth(mBorderWidth); + ((SelectableRoundedCornerDrawable) mDrawable).setBorderColor(mBorderColor); + ((SelectableRoundedCornerDrawable) mDrawable).setOval(isOval); + } + + public float getCornerRadius() { + return mLeftTopCornerRadius; + } + + /** + * Set radii for each corner. + * + * @param leftTop The desired radius for left-top corner in dip. + * @param rightTop The desired desired radius for right-top corner in dip. + * @param leftBottom The desired radius for left-bottom corner in dip. + * @param rightBottom The desired radius for right-bottom corner in dip. + */ + public void setCornerRadiiDP(float leftTop, float rightTop, float leftBottom, float rightBottom) { + final float density = getResources().getDisplayMetrics().density; + + final float lt = leftTop * density; + final float rt = rightTop * density; + final float lb = leftBottom * density; + final float rb = rightBottom * density; + + mRadii = new float[]{lt, lt, rt, rt, rb, rb, lb, lb}; + updateDrawable(); + } + + public float getBorderWidth() { + return mBorderWidth; + } + + /** + * Set border width. + * + * @param width The desired width in dip. + */ + public void setBorderWidthDP(float width) { + float scaledWidth = getResources().getDisplayMetrics().density * width; + if (mBorderWidth == scaledWidth) { + return; + } + + mBorderWidth = scaledWidth; + updateDrawable(); + invalidate(); + } + + public int getBorderColor() { + return mBorderColor.getDefaultColor(); + } + + public void setBorderColor(int color) { + setBorderColor(ColorStateList.valueOf(color)); + } + + public ColorStateList getBorderColors() { + return mBorderColor; + } + + public void setBorderColor(ColorStateList colors) { + if (mBorderColor.equals(colors)) { + return; + } + + mBorderColor = (colors != null) ? colors : ColorStateList + .valueOf(DEFAULT_BORDER_COLOR); + updateDrawable(); + if (mBorderWidth > 0) { + invalidate(); + } + } + + public boolean isOval() { + return isOval; + } + + public void setOval(boolean oval) { + isOval = oval; + updateDrawable(); + invalidate(); + } + + static class SelectableRoundedCornerDrawable extends Drawable { + + private static final String TAG = "SelectableRoundedCornerDrawable"; + private static final int DEFAULT_BORDER_COLOR = Color.BLACK; + + private RectF mBounds = new RectF(); + private RectF mBorderBounds = new RectF(); + + private final RectF mBitmapRect = new RectF(); + private final int mBitmapWidth; + private final int mBitmapHeight; + + private final Paint mBitmapPaint; + private final Paint mBorderPaint; + + private BitmapShader mBitmapShader; + + private float[] mRadii = new float[]{0, 0, 0, 0, 0, 0, 0, 0}; + private float[] mBorderRadii = new float[]{0, 0, 0, 0, 0, 0, 0, 0}; + + private boolean mOval = false; + + private float mBorderWidth = 0; + private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR); + // Set default scale type to FIT_CENTER, which is default scale type of + // original ImageView. + private ScaleType mScaleType = ScaleType.FIT_CENTER; + + private Path mPath = new Path(); + private Bitmap mBitmap; + private boolean mBoundsConfigured = false; + + public SelectableRoundedCornerDrawable(Bitmap bitmap, Resources r) { + mBitmap = bitmap; + mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + + if (bitmap != null) { + mBitmapWidth = bitmap.getScaledWidth(r.getDisplayMetrics()); + mBitmapHeight = bitmap.getScaledHeight(r.getDisplayMetrics()); + } else { + mBitmapWidth = mBitmapHeight = -1; + } + + mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight); + + mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mBitmapPaint.setStyle(Paint.Style.FILL); + mBitmapPaint.setShader(mBitmapShader); + + mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mBorderPaint.setStyle(Paint.Style.STROKE); + mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR)); + mBorderPaint.setStrokeWidth(mBorderWidth); + } + + public static SelectableRoundedCornerDrawable fromBitmap(Bitmap bitmap, Resources r) { + if (bitmap != null) { + return new SelectableRoundedCornerDrawable(bitmap, r); + } else { + return null; + } + } + + public static Drawable fromDrawable(Drawable drawable, Resources r) { + if (drawable != null) { + if (drawable instanceof SelectableRoundedCornerDrawable) { + return drawable; + } else if (drawable instanceof LayerDrawable) { + LayerDrawable ld = (LayerDrawable) drawable; + final int num = ld.getNumberOfLayers(); + for (int i = 0; i < num; i++) { + Drawable d = ld.getDrawable(i); + ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d, r)); + } + return ld; + } + + Bitmap bm = drawableToBitmap(drawable); + if (bm != null) { + return new SelectableRoundedCornerDrawable(bm, r); + } else { +// Log.w(TAG, "Failed to create bitmap from drawable!"); + } + } + return drawable; + } + + public static Bitmap drawableToBitmap(Drawable drawable) { + if (drawable == null) { + return null; + } + + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } + + Bitmap bitmap; + int width = Math.max(drawable.getIntrinsicWidth(), 2); + int height = Math.max(drawable.getIntrinsicHeight(), 2); + try { + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + bitmap = null; + } + return bitmap; + } + + @Override + public boolean isStateful() { + return mBorderColor.isStateful(); + } + + @Override + protected boolean onStateChange(int[] state) { + int newColor = mBorderColor.getColorForState(state, 0); + if (mBorderPaint.getColor() != newColor) { + mBorderPaint.setColor(newColor); + return true; + } else { + return super.onStateChange(state); + } + } + + private void configureBounds(Canvas canvas) { + // I have discovered a truly marvelous explanation of this, + // which this comment space is too narrow to contain. :) + // If you want to understand what's going on here, + // See http://www.joooooooooonhokim.com/?p=289 + Rect clipBounds = canvas.getClipBounds(); + Matrix canvasMatrix = canvas.getMatrix(); + + if (ScaleType.CENTER == mScaleType) { + mBounds.set(clipBounds); + } else if (ScaleType.CENTER_CROP == mScaleType) { + applyScaleToRadii(canvasMatrix); + mBounds.set(clipBounds); + } else if (ScaleType.FIT_XY == mScaleType) { + Matrix m = new Matrix(); + m.setRectToRect(mBitmapRect, new RectF(clipBounds), Matrix.ScaleToFit.FILL); + mBitmapShader.setLocalMatrix(m); + mBounds.set(clipBounds); + } else if (ScaleType.FIT_START == mScaleType || ScaleType.FIT_END == mScaleType + || ScaleType.FIT_CENTER == mScaleType || ScaleType.CENTER_INSIDE == mScaleType) { + applyScaleToRadii(canvasMatrix); + mBounds.set(mBitmapRect); + } else if (ScaleType.MATRIX == mScaleType) { + applyScaleToRadii(canvasMatrix); + mBounds.set(mBitmapRect); + } + } + + private void applyScaleToRadii(Matrix m) { + float[] values = new float[9]; + m.getValues(values); + for (int i = 0; i < mRadii.length; i++) { + mRadii[i] = mRadii[i] / values[0]; + } + } + + private void adjustCanvasForBorder(Canvas canvas) { + Matrix canvasMatrix = canvas.getMatrix(); + final float[] values = new float[9]; + canvasMatrix.getValues(values); + + final float scaleFactorX = values[0]; + final float scaleFactorY = values[4]; + final float translateX = values[2]; + final float translateY = values[5]; + + final float newScaleX = mBounds.width() + / (mBounds.width() + mBorderWidth + mBorderWidth); + final float newScaleY = mBounds.height() + / (mBounds.height() + mBorderWidth + mBorderWidth); + + canvas.scale(newScaleX, newScaleY); + if (ScaleType.FIT_START == mScaleType || ScaleType.FIT_END == mScaleType + || ScaleType.FIT_XY == mScaleType || ScaleType.FIT_CENTER == mScaleType + || ScaleType.CENTER_INSIDE == mScaleType || ScaleType.MATRIX == mScaleType) { + canvas.translate(mBorderWidth, mBorderWidth); + } else if (ScaleType.CENTER == mScaleType || ScaleType.CENTER_CROP == mScaleType) { + // First, make translate values to 0 + canvas.translate( + -translateX / (newScaleX * scaleFactorX), + -translateY / (newScaleY * scaleFactorY)); + // Then, set the final translate values. + canvas.translate(-(mBounds.left - mBorderWidth), -(mBounds.top - mBorderWidth)); + } + } + + private void adjustBorderWidthAndBorderBounds(Canvas canvas) { + Matrix canvasMatrix = canvas.getMatrix(); + final float[] values = new float[9]; + canvasMatrix.getValues(values); + + final float scaleFactor = values[0]; + + float viewWidth = mBounds.width() * scaleFactor; + mBorderWidth = (mBorderWidth * mBounds.width()) / (viewWidth - (2 * mBorderWidth)); + mBorderPaint.setStrokeWidth(mBorderWidth); + + mBorderBounds.set(mBounds); + mBorderBounds.inset(-mBorderWidth / 2, -mBorderWidth / 2); + } + + private void setBorderRadii() { + for (int i = 0; i < mRadii.length; i++) { + if (mRadii[i] > 0) { + mBorderRadii[i] = mRadii[i]; + mRadii[i] = mRadii[i] - mBorderWidth; + } + } + } + + @Override + public void draw(Canvas canvas) { + canvas.save(); + if (!mBoundsConfigured) { + configureBounds(canvas); + if (mBorderWidth > 0) { + adjustBorderWidthAndBorderBounds(canvas); + setBorderRadii(); + } + mBoundsConfigured = true; + } + + if (mOval) { + if (mBorderWidth > 0) { + adjustCanvasForBorder(canvas); + mPath.addOval(mBounds, Path.Direction.CW); + canvas.drawPath(mPath, mBitmapPaint); + mPath.reset(); + mPath.addOval(mBorderBounds, Path.Direction.CW); + canvas.drawPath(mPath, mBorderPaint); + } else { + mPath.addOval(mBounds, Path.Direction.CW); + canvas.drawPath(mPath, mBitmapPaint); + } + } else { + if (mBorderWidth > 0) { + adjustCanvasForBorder(canvas); + mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW); + canvas.drawPath(mPath, mBitmapPaint); + mPath.reset(); + mPath.addRoundRect(mBorderBounds, mBorderRadii, Path.Direction.CW); + canvas.drawPath(mPath, mBorderPaint); + } else { + mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW); + canvas.drawPath(mPath, mBitmapPaint); + } + } + canvas.restore(); + } + + public void setCornerRadii(float[] radii) { + if (radii == null) + return; + + if (radii.length != 8) { + throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); + } + + for (int i = 0; i < radii.length; i++) { + mRadii[i] = radii[i]; + } + } + + @Override + public int getOpacity() { + return (mBitmap == null || mBitmap.hasAlpha() || mBitmapPaint.getAlpha() < 255) ? PixelFormat.TRANSLUCENT + : PixelFormat.OPAQUE; + } + + @Override + public void setAlpha(int alpha) { + mBitmapPaint.setAlpha(alpha); + invalidateSelf(); + } + + @Override + public void setColorFilter(ColorFilter cf) { + mBitmapPaint.setColorFilter(cf); + invalidateSelf(); + } + + @Override + public void setDither(boolean dither) { + mBitmapPaint.setDither(dither); + invalidateSelf(); + } + + @Override + public void setFilterBitmap(boolean filter) { + mBitmapPaint.setFilterBitmap(filter); + invalidateSelf(); + } + + @Override + public int getIntrinsicWidth() { + return mBitmapWidth; + } + + @Override + public int getIntrinsicHeight() { + return mBitmapHeight; + } + + public float getBorderWidth() { + return mBorderWidth; + } + + public void setBorderWidth(float width) { + mBorderWidth = width; + mBorderPaint.setStrokeWidth(width); + } + + public int getBorderColor() { + return mBorderColor.getDefaultColor(); + } + + public void setBorderColor(int color) { + setBorderColor(ColorStateList.valueOf(color)); + } + + public ColorStateList getBorderColors() { + return mBorderColor; + } + + /** + * Controls border color of this ImageView. + * + * @param colors The desired border color. If it's null, no border will be + * drawn. + */ + public void setBorderColor(ColorStateList colors) { + if (colors == null) { + mBorderWidth = 0; + mBorderColor = ColorStateList.valueOf(Color.TRANSPARENT); + mBorderPaint.setColor(Color.TRANSPARENT); + } else { + mBorderColor = colors; + mBorderPaint.setColor(mBorderColor.getColorForState(getState(), + DEFAULT_BORDER_COLOR)); + } + } + + public boolean isOval() { + return mOval; + } + + public void setOval(boolean oval) { + mOval = oval; + } + + public ScaleType getScaleType() { + return mScaleType; + } + + public void setScaleType(ScaleType scaleType) { + if (scaleType == null) { + return; + } + mScaleType = scaleType; + } + } + +} diff --git a/base/src/main/java/com/xuqm/base/view/StrokeTextView.java b/base/src/main/java/com/xuqm/base/view/StrokeTextView.java new file mode 100644 index 0000000..26908cc --- /dev/null +++ b/base/src/main/java/com/xuqm/base/view/StrokeTextView.java @@ -0,0 +1,83 @@ +package com.xuqm.base.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.TextView; + +public class StrokeTextView extends androidx.appcompat.widget.AppCompatTextView { + private final TextView outlineTextView; + private TextPaint strokePaint; + + public StrokeTextView(Context context) { + super(context); + + outlineTextView = new TextView(context); + } + + public StrokeTextView(Context context, AttributeSet attrs) { + super(context, attrs); + + outlineTextView = new TextView(context, attrs); + } + + public StrokeTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + outlineTextView = new TextView(context, attrs, defStyle); + } + + @Override + public void setLayoutParams(ViewGroup.LayoutParams params) { + super.setLayoutParams(params); + outlineTextView.setLayoutParams(params); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + //设置轮廓文字 + CharSequence outlineText = outlineTextView.getText(); + if (outlineText == null || !outlineText.equals(this.getText())) { + outlineTextView.setText(getText()); + postInvalidate(); + } + outlineTextView.measure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + outlineTextView.layout(left, top, right, bottom); + } + + @Override + protected void onDraw(Canvas canvas) { + + if (strokePaint == null) { + strokePaint = new TextPaint(); + } + //复制原来TextViewg画笔中的一些参数 + TextPaint paint = getPaint(); + strokePaint.setTextSize(paint.getTextSize()+3); + strokePaint.setFlags(paint.getFlags()); + strokePaint.setAlpha(paint.getAlpha()); + + //自定义描边效果 + strokePaint.setStyle(Paint.Style.STROKE); + strokePaint.setColor(Color.parseColor("#66000000")); + strokePaint.setStrokeWidth(6); + + String text = getText().toString(); + + //在文本底层画出带描边的文本 + canvas.drawText(text, (getWidth() - strokePaint.measureText(text)) / 2, + getBaseline(), strokePaint); + super.onDraw(canvas); + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/view/SuperViewPager.kt b/base/src/main/java/com/xuqm/base/view/SuperViewPager.kt new file mode 100644 index 0000000..145a686 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/view/SuperViewPager.kt @@ -0,0 +1,98 @@ +package com.xuqm.base.view + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.RelativeLayout +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 +import com.xuqm.base.R + +class SuperViewPager : RelativeLayout { + + val mViewPager: ViewPager2 by lazy { + findViewById(R.id.mViewPager) + .apply { + //设置关闭过度滑动的效果 + getChildAt(0).overScrollMode = View.OVER_SCROLL_NEVER + } + } + + //自己定义了一个比率,来调整画廊效果最左侧和最右侧占用的宽度 + var edgeRatio = 0.25 + set(value) { + field = value + refreshPageSize() + } + + //为了保证画廊效果,可见的Page处理为单数 + var visibleItem: Int = 1 + set(value) { + field = if (value.rem(2) == 0) { + value - 1 + } else { + value + } + refreshPageSize() + } + + + //刷新页面大小 + private fun refreshPageSize() { + //使用post为了保证获取根布局width的时候结果不为0 + mViewPager.post { + mViewPager.offscreenPageLimit = visibleItem + //根据想要显示的页面个数,动态给ViewPager2计算一个大小 + val mPageWidth = if (visibleItem == 1) { + width + } else { + width.toDouble().div(visibleItem.minus(2).plus(edgeRatio)).toInt() + } + mViewPager.layoutParams = LayoutParams( + LayoutParams( + mPageWidth, + ViewGroup.LayoutParams.MATCH_PARENT + ).apply { gravity = Gravity.CENTER }) + } + } + + /** + * 将根布局的触摸事件直接传递给ViewPager + */ + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent?): Boolean { + return mViewPager.getChildAt(0).onTouchEvent(event) + } + + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) + + init { + clipChildren = false + LayoutInflater.from(context).inflate(R.layout.super_viewpager_layout, this, true) + } + + /** + * 为ViewPager2设置一个适配器,ViewPager2的适配器不再是PagerAdapter,而是RecyclerView.Adapter类型 + */ + fun setAdapter(adapter: RecyclerView.Adapter<*>) { + mViewPager.adapter = adapter + } + + /** + * 设置页面切换的效果 + */ + fun setPageTransformer(pageTransformer: ViewPager2.PageTransformer) { + mViewPager.setPageTransformer(pageTransformer) + } +} diff --git a/base/src/main/java/com/xuqm/base/view/ToolbarLayout.java b/base/src/main/java/com/xuqm/base/view/ToolbarLayout.java new file mode 100644 index 0000000..e29865c --- /dev/null +++ b/base/src/main/java/com/xuqm/base/view/ToolbarLayout.java @@ -0,0 +1,150 @@ +package com.xuqm.base.view; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; + +import com.xuqm.base.R; +import com.xuqm.base.ui.callback.ToolBarListener; + +/** + * 自定义的toolbar + */ +public class ToolbarLayout extends FrameLayout { + + private CharSequence title = ""; + private int textColor = 0xFFFFFF; + private int iconTintColor = 0xFFFFFF; + private boolean showBack = true; + private boolean showLine = false; + + public void setTitle(CharSequence title) { + this.title = title; + getTitleView().setText(title); + } + + public void setTextColor(int textColor) { + this.textColor = textColor; + getTitleView().setTextColor(textColor); + } + + public void setIconTintColor(int iconTintColor) { + this.iconTintColor = iconTintColor; + tintIcon(iconTintColor); + } + + public void setShowBack(boolean showBack) { + this.showBack = showBack; + getBackBtn().setVisibility(showBack ? VISIBLE : GONE); + } + + public void setShowLine(boolean showLine) { + this.showLine = showLine; + findViewById(R.id.toolbarLine).setVisibility(showLine ? VISIBLE : GONE); + } + + private ToolBarListener backListener; + + public void backBtnPressed(ToolBarListener listener) { + backListener = listener; + } + + public ToolbarLayout(@NonNull Context context) { + super(context); + init(null); + } + + public ToolbarLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(attrs); + } + + private void init(AttributeSet attrs) { + Context mContext = getContext(); + if (null == attrs) return; + View.inflate(mContext, R.layout.toolbar, this); + TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.ToolbarLayout); + title = typedArray.getString(R.styleable.ToolbarLayout_title); + showBack = typedArray.getBoolean(R.styleable.ToolbarLayout_showBack, true); + showLine = typedArray.getBoolean(R.styleable.ToolbarLayout_showLine, false); + textColor = typedArray.getColor(R.styleable.ToolbarLayout_textColor, ContextCompat.getColor(mContext, R.color.title)); +// iconTintColor = typedArray.getColor(R.styleable.ToolbarLayout_iconTint, ContextCompat.getColor(mContext, R.color.white)); + typedArray.recycle(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + if (null != getBackBtn()) { + getBackBtn().setVisibility(showBack ? VISIBLE : GONE); + if (showBack) { +// tintIcon(iconTintColor); + getBackBtn().setOnClickListener(v -> backListener.back()); + + } + } + if (null != getTitleView()) { + getTitleView().setText(title); + getTitleView().setTextColor(textColor); + } + if (null != getConfirmBtn()) { + getTitleView().setText(title); + getConfirmBtn().setTextColor(textColor); + } + findViewById(R.id.toolbarLine).setVisibility(showLine ? VISIBLE : GONE); + } + + private void tintIcon(int colors) { + Drawable wrappedDrawable = DrawableCompat.wrap(getBackBtn().getDrawable()); + DrawableCompat.setTintList(wrappedDrawable, ColorStateList.valueOf(colors)); + getBackBtn().setImageDrawable(wrappedDrawable); + } + + private ImageView backBtn; + + public ImageView getBackBtn() { + if (null == backBtn) + backBtn = findViewById(R.id.toolbarBack); + return backBtn; + } + + private TextView titleView; + + public TextView getTitleView() { + if (null == titleView) + titleView = findViewById(R.id.toolbarTitle); + return titleView; + } + private TextView confirmBtn; + + public TextView getConfirmBtn() { + if (null == confirmBtn) + confirmBtn = findViewById(R.id.toolbarConfirm); + return confirmBtn; + } + private ImageView closeBtn; + + public ImageView getCloseBtn() { + if (null == closeBtn) + closeBtn = findViewById(R.id.toolbarClose); + return closeBtn; + } + private ImageView toolbarMenu; + + public ImageView getToolbarMenu() { + if (null == toolbarMenu) + toolbarMenu = findViewById(R.id.toolbarMenu); + return toolbarMenu; + } +} diff --git a/base/src/main/java/com/xuqm/base/view/enu/Status.java b/base/src/main/java/com/xuqm/base/view/enu/Status.java new file mode 100644 index 0000000..28a13c9 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/view/enu/Status.java @@ -0,0 +1,5 @@ +package com.xuqm.base.view.enu; + +public enum Status { + DISMISS, LOADING, NO_DATA, LOAD_FAILED, NETWORK_UNAVAILABLE +} diff --git a/base/src/main/java/com/xuqm/base/viewmodel/BaseListViewModel.java b/base/src/main/java/com/xuqm/base/viewmodel/BaseListViewModel.java new file mode 100644 index 0000000..2440cbb --- /dev/null +++ b/base/src/main/java/com/xuqm/base/viewmodel/BaseListViewModel.java @@ -0,0 +1,132 @@ +package com.xuqm.base.viewmodel; + +import androidx.annotation.NonNull; +import androidx.core.util.Pair; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.paging.LivePagedListBuilder; +import androidx.paging.PageKeyedDataSource; +import androidx.paging.PagedList; + +import com.xuqm.base.common.RefreshResult; +import com.xuqm.base.datasource.DataSourceFactory; +import com.xuqm.base.datasource.PagedDataLoader; +import com.xuqm.base.viewmodel.callback.AdapterObserverCallback; +import com.xuqm.base.viewmodel.callback.DataObserverCallback; +import com.xuqm.base.viewmodel.callback.Response; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @param + */ +public abstract class BaseListViewModel extends BaseViewModel implements PagedDataLoader { + + public int pageSize() { + return 10; + } + + private final ArrayList data = new ArrayList<>(); + + private final DataSourceFactory dataSourceFactory = new DataSourceFactory<>(this); + private final LiveData> loadLiveData = new LivePagedListBuilder<>(dataSourceFactory, this.pageSize()).build(); + private final MutableLiveData refreshLiveData = new MutableLiveData<>(); + private final MutableLiveData loadMoreLiveData = new MutableLiveData<>(); + private final MutableLiveData> notifyItemLiveData = new MutableLiveData<>(); + private final MutableLiveData removeItemLiveData = new MutableLiveData<>(); + + public void invalidate() { + Objects.requireNonNull(dataSourceFactory.sourceLiveData.getValue()).invalidate(); + } + + public void observeDataObserver(@NonNull LifecycleOwner owner, DataObserverCallback dataObserverCallback) { + loadLiveData.observe(owner, dataObserverCallback::data); + refreshLiveData.observe(owner, dataObserverCallback::refreshResult); + loadMoreLiveData.observe(owner, dataObserverCallback::loadMoreResult); + } + + public void observeAdapterObserver(@NonNull LifecycleOwner owner, AdapterObserverCallback observerCallback) { + notifyItemLiveData.observe(owner, integerObjectPair -> observerCallback.notifyItem(integerObjectPair.first, integerObjectPair.second)); + removeItemLiveData.observe(owner, observerCallback::removeItem); + } + + @Override + public void loadInitial(PageKeyedDataSource.LoadInitialParams params, PageKeyedDataSource.LoadInitialCallback callback) { + refresh(); + data.clear(); + loadData(1, onResponse -> { + if (null == onResponse) { + refreshLiveData.postValue(RefreshResult.FAILED); + } else if (onResponse.isEmpty()) { + refreshLiveData.postValue(RefreshResult.NO_DATA); + } else if (onResponse.size() < pageSize()) { + data.addAll(onResponse); + callback.onResult(onResponse, null, null); + refreshLiveData.postValue(RefreshResult.NO_MORE); + } else { + data.addAll(onResponse); + callback.onResult(onResponse, null, 2); + refreshLiveData.postValue(RefreshResult.SUCCEED); + } + + }); + } + + @Override + public void loadAfter(PageKeyedDataSource.LoadParams params, PageKeyedDataSource.LoadCallback callback) { + loadMore(); + loadData(params.key, onResponse -> { + if (null == onResponse) loadMoreLiveData.postValue(RefreshResult.FAILED); + else if (onResponse.size() < pageSize()) { + data.addAll(onResponse); + callback.onResult(onResponse, null); + loadMoreLiveData.postValue(RefreshResult.NO_MORE); + } else { + data.addAll(onResponse); + callback.onResult(onResponse, params.key + 1); + loadMoreLiveData.postValue(RefreshResult.SUCCEED); + } + + }); + } + + public void setData(List data) { + this.data.clear(); + this.data.addAll(data); + refreshLiveData.postValue(RefreshResult.SUCCEED); + } + + public ArrayList getData() { + return data; + } + + public void notifyItem(int position) { + notifyItemLiveData.postValue(new Pair<>(position, null)); + } + + public void notifyItem(int position, Object payload) { + notifyItemLiveData.postValue(new Pair<>(position, payload)); + } + + public void removeItem(int position) { + removeItemLiveData.postValue(position); + } + + @Override + public void refresh() { + + } + + @Override + public void loadMore() { + + } + + public abstract void loadData(int page, Response onResponse); + +} + + diff --git a/base/src/main/java/com/xuqm/base/viewmodel/BaseViewModel.java b/base/src/main/java/com/xuqm/base/viewmodel/BaseViewModel.java new file mode 100644 index 0000000..ef3a0a9 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/viewmodel/BaseViewModel.java @@ -0,0 +1,67 @@ +package com.xuqm.base.viewmodel; + +import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ViewModel; + +import com.xuqm.base.common.LogHelper; + +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; + +/** + * ViewModel 基类 + */ +public abstract class BaseViewModel extends ViewModel implements DefaultLifecycleObserver { + private final String TAG = getClass().getSimpleName(); + protected CompositeDisposable compositeDisposable = new CompositeDisposable(); + + /** + * Disposable 的管理,自动处理网络请求 + * + * @param d Disposable + */ + protected void add(Disposable d) { + compositeDisposable.add(d); + } + + @Override + public void onCreate(@NonNull LifecycleOwner owner) { + LogHelper.d(TAG, "onCreate"); + } + + @Override + public void onStart(@NonNull LifecycleOwner owner) { + LogHelper.d(TAG, "onStart"); + } + + @Override + public void onResume(@NonNull LifecycleOwner owner) { + LogHelper.d(TAG, "onResume"); + } + + @Override + public void onPause(@NonNull LifecycleOwner owner) { + LogHelper.d(TAG, "onPause"); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + LogHelper.d(TAG, "onStop"); + } + + @Override + public void onDestroy(@NonNull LifecycleOwner owner) { + LogHelper.d(TAG, "onDestroy"); + } + + @Override + protected void onCleared() { + super.onCleared(); + LogHelper.d(TAG, "onCleared"); + if (!compositeDisposable.isDisposed()) { + compositeDisposable.dispose(); + } + } +} diff --git a/base/src/main/java/com/xuqm/base/viewmodel/callback/AdapterObserverCallback.java b/base/src/main/java/com/xuqm/base/viewmodel/callback/AdapterObserverCallback.java new file mode 100644 index 0000000..4b51f4b --- /dev/null +++ b/base/src/main/java/com/xuqm/base/viewmodel/callback/AdapterObserverCallback.java @@ -0,0 +1,7 @@ +package com.xuqm.base.viewmodel.callback; + +public interface AdapterObserverCallback { + void notifyItem(int position, Object payload); + + void removeItem(int position); +} diff --git a/base/src/main/java/com/xuqm/base/viewmodel/callback/DataObserverCallback.java b/base/src/main/java/com/xuqm/base/viewmodel/callback/DataObserverCallback.java new file mode 100644 index 0000000..bc28aae --- /dev/null +++ b/base/src/main/java/com/xuqm/base/viewmodel/callback/DataObserverCallback.java @@ -0,0 +1,13 @@ +package com.xuqm.base.viewmodel.callback; + +import androidx.paging.PagedList; + +import com.xuqm.base.common.RefreshResult; + +public interface DataObserverCallback { + void data(PagedList data); + + void refreshResult(RefreshResult refreshResult); + + void loadMoreResult(RefreshResult refreshResult); +} diff --git a/base/src/main/java/com/xuqm/base/viewmodel/callback/Response.java b/base/src/main/java/com/xuqm/base/viewmodel/callback/Response.java new file mode 100644 index 0000000..1956357 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/viewmodel/callback/Response.java @@ -0,0 +1,7 @@ +package com.xuqm.base.viewmodel.callback; + +import java.util.ArrayList; + +public interface Response { + void onResponse(ArrayList onResponse); +} diff --git a/base/src/main/java/com/xuqm/base/web/XAndroidInterface.kt b/base/src/main/java/com/xuqm/base/web/XAndroidInterface.kt new file mode 100644 index 0000000..1d19ea2 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/web/XAndroidInterface.kt @@ -0,0 +1,41 @@ +package com.xuqm.base.web + +import android.content.Context +import android.webkit.JavascriptInterface +import com.xuqm.base.common.AppManager +import com.xuqm.base.common.ToolsHelper +import com.xuqm.base.extensions.log + +class XAndroidInterface constructor(val mContext: Context, val viewModel: XWebViewModel) { + @JavascriptInterface + fun showToast(callBackId: String, toast: String) { + ToolsHelper.showMessage(toast) + viewModel.callBackSuccess(callBackId) + } + + @JavascriptInterface + fun setTitle(callBackId: String, title: String) { + viewModel.setTitle(title) + viewModel.callBackSuccess(callBackId) + } + + @JavascriptInterface + fun scan(callBackId: String) { + viewModel.scan(callBackId) + } + + @JavascriptInterface + fun closed() { + AppManager.getInstance().finish() + } + + @JavascriptInterface + fun setTopMenu(callBackId: String, date: String) { + date.split(",").log() + viewModel.setTopMenu(callBackId,date.split(",")) + } + @JavascriptInterface + fun getUserInfo(callBackId: String) { + viewModel.getUserInfo(callBackId) + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/web/XWebCallBack.kt b/base/src/main/java/com/xuqm/base/web/XWebCallBack.kt new file mode 100644 index 0000000..5a26258 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/web/XWebCallBack.kt @@ -0,0 +1,25 @@ +package com.xuqm.base.web + +import android.webkit.WebView +import com.xuqm.base.App +import com.xuqm.base.common.GsonImplHelp +import com.xuqm.base.extensions.log + +class XWebCallBack(val webView: WebView) { + fun error(callBackId: String, msg: String) { + sendCallBack(XWebJsCallModel(callBackId, -1, msg, null)) + } + + fun success(callBackId: String, data: Any?) { + sendCallBack(XWebJsCallModel(callBackId, 0, null, data)) + } + + private fun sendCallBack(model: XWebJsCallModel) { + App.getInstance().runOnUiThread { + GsonImplHelp.get().toJson(model).log() + webView.loadUrl( + "javascript:__callbackMessageFromNative("+GsonImplHelp.get().toJson(model)+")" + ) + } + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/web/XWebChromeClient.kt b/base/src/main/java/com/xuqm/base/web/XWebChromeClient.kt new file mode 100644 index 0000000..f1c19d0 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/web/XWebChromeClient.kt @@ -0,0 +1,115 @@ +package com.xuqm.base.web + +import android.Manifest +import android.net.Uri +import android.webkit.* +import com.bigkoo.alertview.AlertView +import com.xuqm.base.App +import com.xuqm.base.extensions.log +import com.xuqm.base.extensions.loge +import com.xuqm.base.extensions.runWithPermission + + +class XWebChromeClient(val mContext: XWebViewActivity, val viewModel: XWebViewModel) : + WebChromeClient() { + + override fun onProgressChanged(view: WebView?, newProgress: Int) { + super.onProgressChanged(view, newProgress) + viewModel.setProgress(newProgress) + } + + override fun onJsAlert( + view: WebView, + url: String, + message: String, + result: JsResult + ): Boolean { + App.getInstance().runOnUiThread { + + AlertView( + "", message, null, arrayOf("Confirm"), null, mContext, + AlertView.Style.Alert + ) { _, _ -> + result.confirm() + }.show() + } + return true + } + + override fun onJsConfirm( + view: WebView, + url: String, + message: String, + result: JsResult + ): Boolean { + AlertView( + "", message, null, arrayOf("Confirm", "Cancel"), null, mContext, + AlertView.Style.Alert + ) { _, position -> + when (position) { + 0 -> result.confirm() + 1 -> result.cancel() + } + }.show() + return true + } + + override fun onShowFileChooser( + webView: WebView, + filePathCallback: ValueCallback>, + fileChooserParams: FileChooserParams + ): Boolean { + var types = "*/*" + if (fileChooserParams.acceptTypes.isNotEmpty()) + types = if (fileChooserParams.acceptTypes[0].contains("image")) + "image/*" + else "*/*" + mContext.selectedFile(types, filePathCallback) + return true + } + + override fun onGeolocationPermissionsShowPrompt( + origin: String?, + callback: GeolocationPermissions.Callback + ) { + + mContext.runWithPermission( + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + ) { + + AlertView( + "定位权限", + "当前页面需要获取定位权限", + null, + arrayOf("Confirm", "Cancel"), + null, + mContext, + AlertView.Style.Alert + ) { _, position -> + when (position) { + 0 -> callback.invoke(origin, true, true) + 1 -> callback.invoke(origin, false, true) + } + super.onGeolocationPermissionsShowPrompt(origin, callback) + }.show() + + } + } + + override fun onGeolocationPermissionsHidePrompt() { + "onGeolocationPermissionsHidePrompt".loge() + super.onGeolocationPermissionsHidePrompt() + } + + override fun onPermissionRequest(request: PermissionRequest) { + "onPermissionRequest".loge() + request.grant(request.resources) + } + + override fun onPermissionRequestCanceled(request: PermissionRequest?) { + "onPermissionRequestCanceled".loge() + request?.loge() + super.onPermissionRequestCanceled(request) + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/web/XWebJsCallModel.kt b/base/src/main/java/com/xuqm/base/web/XWebJsCallModel.kt new file mode 100644 index 0000000..307d80d --- /dev/null +++ b/base/src/main/java/com/xuqm/base/web/XWebJsCallModel.kt @@ -0,0 +1,8 @@ +package com.xuqm.base.web + +data class XWebJsCallModel( + val callBackId: String, + val status: Int, + val errMsg: String?, + val data: Any? +) diff --git a/base/src/main/java/com/xuqm/base/web/XWebViewActivity.kt b/base/src/main/java/com/xuqm/base/web/XWebViewActivity.kt new file mode 100644 index 0000000..6e7f347 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/web/XWebViewActivity.kt @@ -0,0 +1,237 @@ +package com.xuqm.base.web + +import android.Manifest +import android.annotation.SuppressLint +import android.app.DownloadManager +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.View +import android.webkit.URLUtil +import android.webkit.ValueCallback +import androidx.lifecycle.ViewModelProvider +import com.bigkoo.alertview.AlertView +import com.huawei.hms.hmsscankit.ScanUtil +import com.huawei.hms.ml.scan.HmsScan +import com.luck.picture.lib.basic.PictureSelector +import com.luck.picture.lib.config.SelectMimeType +import com.luck.picture.lib.config.SelectModeConfig +import com.luck.picture.lib.entity.LocalMedia +import com.luck.picture.lib.interfaces.OnResultCallbackListener +import com.xuqm.base.R +import com.xuqm.base.common.FileHelper +import com.xuqm.base.common.GlideEngine +import com.xuqm.base.common.LogHelper +import com.xuqm.base.databinding.ActivityXWebviewBinding +import com.xuqm.base.extensions.loge +import com.xuqm.base.extensions.runWithPermission +import com.xuqm.base.extensions.showMessage +import com.xuqm.base.ui.BaseActivity +import java.io.File + + +class XWebViewActivity : BaseActivity() { + private lateinit var title: String + private lateinit var url: String + private var hasTopBar: Boolean = true + + + private lateinit var viewModel: XWebViewModel + + override fun getLayoutId(): Int = R.layout.activity_x_webview + + @SuppressLint("SetJavaScriptEnabled") + override fun initView(savedInstanceState: Bundle?) { + super.initView(savedInstanceState) + LogHelper.e("initView") + showBack(false) + intent?.apply { + hasTopBar = getBooleanExtra("hasTopBar", true) + } + if (hasTopBar) { + showBack(true) + } + + viewModel = ViewModelProvider(this)[XWebViewModel::class.java] + + viewModel.activity = this + viewModel.callBack = XWebCallBack(binding.XWebView) + + binding.XWebView.webChromeClient = XWebChromeClient(this, viewModel) + binding.XWebView.webViewClient = XWebViewClient() + binding.XWebView.clearCache(true) + binding.XWebView.settings.javaScriptEnabled = true + binding.XWebView.settings.setGeolocationEnabled(true) + binding.XWebView.settings.domStorageEnabled = true + binding.XWebView.settings.mediaPlaybackRequiresUserGesture = false; + + binding.XWebView.addJavascriptInterface( + XAndroidInterface(mContext, viewModel), + "ZHHBAndroid" + ) + binding.XWebView.setDownloadListener { url, _, contentDisposition, mimeType, _ -> + + downloadBySystem(url, contentDisposition, mimeType) + } + } + + override fun fullscreen(): Boolean { + return false + } + + override fun initData() { + super.initData() + LogHelper.e("initData") + intent?.apply { + title = getStringExtra("title") ?: "" + url = getStringExtra("url") ?: "" + } + + viewModel.title.observe(this) { +// if(ToolsHelper.isNull(it)){ +// baseBinding.baseToolbar.visibility=View.GONE +// }else{ +// baseBinding.baseToolbar.visibility=View.VISIBLE + setTitleText(it) +// } + } + viewModel.progress.observe(this) { + binding.XProgressBar.progress = it + if (it > 90) { + binding.XProgressBar.visibility = View.GONE + } else { + binding.XProgressBar.visibility = View.VISIBLE + } + } + + } + + override fun lateInitView() { + super.lateInitView() + LogHelper.e("lateInitView") + LogHelper.e(url) + setTitleText(title) + binding.XWebView.loadUrl(url) + + } + + override fun backBtnPressed() { + if (binding.XWebView.canGoBack()) { + binding.XWebView.goBack() + } else finish() + } + + override fun onBackPressed() { + if (binding.XWebView.canGoBack()) { + binding.XWebView.goBack() + } else finish() + } + + private lateinit var filePathCallback: ValueCallback> + + fun selectedFile(types: String, back: ValueCallback>) { + filePathCallback = back + + if (types.contains("image")) { + runWithPermission( + Manifest.permission.CAMERA, + rationaleMethod = { + "无法获取相关权限".showMessage() + back.onReceiveValue(null) + }, + permanentDeniedMethod = { + "请在设置界面开放对应权限".showMessage() + back.onReceiveValue(null) + }, + callback = { + PictureSelector.create(this) + .openGallery(SelectMimeType.ofImage()) + .setImageEngine(GlideEngine.createGlideEngine()) + .setSelectionMode(SelectModeConfig.SINGLE) + .forResult(object : OnResultCallbackListener { + override fun onResult(result: ArrayList) { + val results = + result.map { FileHelper.getFileUri(File(it.realPath)) } + back.onReceiveValue(results.toTypedArray()) + } + + override fun onCancel() { + back.onReceiveValue(null) + } + }) + + }) + } else { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = types + } + mContext.startActivityForResult(intent, 10012) + } + + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == 10012) { + if (resultCode == RESULT_OK) { + data?.data?.also { uri -> + filePathCallback.onReceiveValue(arrayOf(uri)) + } + } else { + filePathCallback.onReceiveValue(emptyArray()) + } + } else if (requestCode == 1111) { + + + if (resultCode == RESULT_OK) { + val obj: HmsScan? = data?.getParcelableExtra(ScanUtil.RESULT) + if (obj != null) { + viewModel.backScan(obj.originalValue) + } else { + viewModel.backScanError() + } + } else { + viewModel.backScan("Cancel") + } + + } + + } + + private fun downloadBySystem(url: String, contentDisposition: String, mimeType: String) { + val request = DownloadManager.Request(Uri.parse(url)) + + val fileName: String = URLUtil.guessFileName(url, contentDisposition, mimeType) + fileName.loge() + +// request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) + // 设置通知栏的标题,如果不设置,默认使用文件名 +// request.setTitle(BuildConfig.APP_Name); + // 设置通知栏的描述 + request.setDescription("正在下载文件:${fileName}"); + // 允许在计费流量下下载 + request.setAllowedOverMetered(false) + // 允许该记录在下载管理界面可见 + request.setVisibleInDownloadsUi(true) + // 允许漫游时下载 + request.setAllowedOverRoaming(true) + // 允许下载的网路类型 + request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) + // 设置下载文件保存的路径和文件名 + val file = File(FileHelper.getDownloadPath(), fileName) + request.setDestinationUri(Uri.fromFile(file)) + val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager + // 添加一个下载任务 + val downloadId = downloadManager.enqueue(request) + downloadId.loge() + + AlertView( + "", "当前文件已经开始下载,请查看通知栏下载进度", null, arrayOf("Confirm"), null, mContext, + AlertView.Style.Alert + ) { _, _ -> + }.show() + } + +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/web/XWebViewClient.kt b/base/src/main/java/com/xuqm/base/web/XWebViewClient.kt new file mode 100644 index 0000000..8c45381 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/web/XWebViewClient.kt @@ -0,0 +1,19 @@ +package com.xuqm.base.web + +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import com.xuqm.base.common.LogHelper + +class XWebViewClient : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + LogHelper.e(url) + } + + override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { + LogHelper.e(request?.toString()) + return false + } + +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/web/XWebViewHelper.kt b/base/src/main/java/com/xuqm/base/web/XWebViewHelper.kt new file mode 100644 index 0000000..f21737d --- /dev/null +++ b/base/src/main/java/com/xuqm/base/web/XWebViewHelper.kt @@ -0,0 +1,22 @@ +package com.xuqm.base.web + +import android.content.Context +import android.content.Intent + +object XWebViewHelper { + fun startWebWithTopBar(context: Context, url: String, title: String?) { + val intent: Intent = Intent(context, XWebViewActivity::class.java).apply { + title?.let { putExtra("title", title) } + putExtra("url", url) + putExtra("hasTopBar", true) + } + context.startActivity(intent) + } + fun startWebNoTopBar(context: Context, url: String) { + val intent: Intent = Intent(context, XWebViewActivity::class.java).apply { + putExtra("url", url) + putExtra("hasTopBar", false) + } + context.startActivity(intent) + } +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/web/XWebViewModel.kt b/base/src/main/java/com/xuqm/base/web/XWebViewModel.kt new file mode 100644 index 0000000..ba06a27 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/web/XWebViewModel.kt @@ -0,0 +1,128 @@ +package com.xuqm.base.web + +import android.Manifest +import android.annotation.SuppressLint +import android.view.View +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.bigkoo.alertview.AlertView +import com.huawei.hms.hmsscankit.ScanUtil +import com.huawei.hms.ml.scan.HmsScan +import com.huawei.hms.ml.scan.HmsScanAnalyzerOptions +import com.xuqm.base.App +import com.xuqm.base.extensions.getStringForPreferences +import com.xuqm.base.extensions.log +import com.xuqm.base.extensions.runWithPermission +import com.xuqm.base.extensions.showMessage +import com.xuqm.base.viewmodel.BaseViewModel +import com.xuqm.base.common.SHARE_UESR_NAME + +class XWebViewModel : BaseViewModel() { + lateinit var callBack: XWebCallBack + + @SuppressLint("StaticFieldLeak") + lateinit var activity: XWebViewActivity + + private val _title = MutableLiveData() + val title: LiveData = _title + + fun setTitle(title: String) { + _title.postValue(title) + } + + private lateinit var callId: String + fun scan(callBackId: String) { + callId = callBackId + + App.getInstance().runOnUiThread { + activity.runWithPermission( + Manifest.permission.CAMERA, + rationaleMethod = { + "无法获取相关权限".showMessage() + callBackError(callId, "没有相关权限") + }, + permanentDeniedMethod = { + "请在设置界面开放对应权限".showMessage() + callBackError(callId, "没有相关权限") + }, + callback = { + ScanUtil.startScan( + activity, 1111, HmsScanAnalyzerOptions.Creator().setHmsScanTypes( + HmsScan.QRCODE_SCAN_TYPE + ).create() + ); + }) + } + + } + + fun backScan(msg: String) { + callBackSuccess(callId, msg) + } + + fun backScanError() { + callBackError(callId, "扫码失败") + } + + private val _progress = MutableLiveData().also { it.postValue(0) } + val progress: LiveData = _progress + + fun setProgress(progress: Int) { + _progress.postValue(progress) + } + + fun callBackSuccess(id: String) { + if (this::callBack.isInitialized) { + callBack.success(id, null) + } + } + + fun callBackSuccess(id: String, data: Any) { + if (this::callBack.isInitialized) { + callBack.success(id, data) + } + } + + fun callBackError(id: String, data: String) { + if (this::callBack.isInitialized) { + callBack.error(id, data) + } + } + + + fun getUserInfo(callBackId: String) { + if (this::callBack.isInitialized) { + callBack.success(callBackId, activity.getStringForPreferences(SHARE_UESR_NAME)) + } + } + + lateinit var v: AlertView + + fun setTopMenu(id: String, date: List) { + "================>".log() + App.getInstance().runOnUiThread { + if (date.isNotEmpty()) { + activity.baseBinding.baseToolbar.toolbarMenu.visibility = View.VISIBLE + activity.baseBinding.baseToolbar.toolbarMenu.setOnClickListener { + v = AlertView( + "请选择需要操作的内容", "", null, date.toTypedArray(), null, activity, + AlertView.Style.ActionSheet + ) { _, position -> + selected(id, position) + } + v.show() + } + } else { + activity.baseBinding.baseToolbar.toolbarMenu.visibility = View.GONE + } + } + } + + private fun selected(callBackId: String, data: Int) { + v.dismissImmediately() + if (this::callBack.isInitialized) { + callBack.success(callBackId, data) + } + } + +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/webSocket/ConnectStatus.java b/base/src/main/java/com/xuqm/base/webSocket/ConnectStatus.java new file mode 100644 index 0000000..58c3830 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/webSocket/ConnectStatus.java @@ -0,0 +1,9 @@ +package com.xuqm.base.webSocket; + +public enum ConnectStatus { + Connecting, // the initial state of each web socket. + Open, // the web socket has been accepted by the remote peer + Closing, // one of the peers on the web socket has initiated a graceful shutdown + Closed, // the web socket has transmitted all of its messages and has received all messages from the peer + Canceled // the web socket connection failed +} \ No newline at end of file diff --git a/base/src/main/java/com/xuqm/base/webSocket/WebSocketCallBack.java b/base/src/main/java/com/xuqm/base/webSocket/WebSocketCallBack.java new file mode 100644 index 0000000..7d14f3c --- /dev/null +++ b/base/src/main/java/com/xuqm/base/webSocket/WebSocketCallBack.java @@ -0,0 +1,11 @@ +package com.xuqm.base.webSocket; + +public interface WebSocketCallBack { + void onClose(); + + void onConnectError(Throwable t); + + void onMessage(String message); + + void onOpen(); +} diff --git a/base/src/main/java/com/xuqm/base/webSocket/WebSocketHandler.java b/base/src/main/java/com/xuqm/base/webSocket/WebSocketHandler.java new file mode 100644 index 0000000..63be659 --- /dev/null +++ b/base/src/main/java/com/xuqm/base/webSocket/WebSocketHandler.java @@ -0,0 +1,171 @@ +package com.xuqm.base.webSocket; + +import androidx.annotation.Nullable; + +import com.xuqm.base.common.LogHelper; + +import java.util.HashMap; +import java.util.Map; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import okio.ByteString; + +public class WebSocketHandler extends WebSocketListener { + private static final String TAG = "WebSocketHandler "; + + private boolean log = false; + + private String wsUrl; + + private WebSocket webSocket; + + private ConnectStatus status; + + private OkHttpClient client = new OkHttpClient.Builder() + .build(); + + private WebSocketHandler(String wsUrl) { + this.wsUrl = wsUrl; + } + + private static WebSocketHandler INST; + + public static WebSocketHandler getInstance(String url) { + if (INST == null) { + synchronized (WebSocketHandler.class) { + INST = new WebSocketHandler(url); + } + } + + return INST; + } + + public void setLog(boolean log) { + this.log = log; + } + + public ConnectStatus getStatus() { + return status; + } + + private Map headers = new HashMap<>(); + + public void addHeader(String name, String value) { + headers.put(name, value); + } + + public void connect() { + //构造request对象 + Request.Builder builder = new Request.Builder() + .url(wsUrl); + for (String key : headers.keySet()) { + builder.addHeader(key, headers.get(key)); + } + Request request = builder.build(); + webSocket = client.newWebSocket(request, this); + status = ConnectStatus.Connecting; + } + + public void reConnect() { + if (webSocket != null) { + webSocket = client.newWebSocket(webSocket.request(), this); + } + } + + public void send(String text) { + if (webSocket != null) { + if (this.log) + LogHelper.d("send: " + text); + webSocket.send(text); + if (mSocketIOCallBack != null) { + mSocketIOCallBack.onMessage(text); + } + } + } + + public void cancel() { + if (webSocket != null) { + webSocket.cancel(); + } + } + + public void close() { + if (webSocket != null) { + webSocket.close(1000, null); + } + } + + @Override + public void onOpen(WebSocket webSocket, Response response) { + super.onOpen(webSocket, response); + if (this.log) + LogHelper.d("onOpen"); + this.status = ConnectStatus.Open; + if (mSocketIOCallBack != null) { + mSocketIOCallBack.onOpen(); + } + } + + @Override + public void onMessage(WebSocket webSocket, String text) { + super.onMessage(webSocket, text); + if (this.log) + LogHelper.d("onMessage: " + text); + if (mSocketIOCallBack != null) { + mSocketIOCallBack.onMessage(text); + } + } + + @Override + public void onMessage(WebSocket webSocket, ByteString bytes) { + super.onMessage(webSocket, bytes); + } + + @Override + public void onClosing(WebSocket webSocket, int code, String reason) { + super.onClosing(webSocket, code, reason); + this.status = ConnectStatus.Closing; + if (this.log) + LogHelper.d("onClosing"); + } + + @Override + public void onClosed(WebSocket webSocket, int code, String reason) { + super.onClosed(webSocket, code, reason); + if (this.log) + LogHelper.d("onClosed"); + this.status = ConnectStatus.Closed; + if (mSocketIOCallBack != null) { + mSocketIOCallBack.onClose(); + } + } + + @Override + public void onFailure(WebSocket webSocket, Throwable t, @Nullable Response response) { + super.onFailure(webSocket, t, response); + if (this.log) + LogHelper.d("onFailure: " + t.toString()); + t.printStackTrace(); + this.status = ConnectStatus.Canceled; + if (mSocketIOCallBack != null) { + mSocketIOCallBack.onConnectError(t); + } + } + + + private WebSocketCallBack mSocketIOCallBack; + + public WebSocketHandler setSocketIOCallBack(WebSocketCallBack callBack) { + mSocketIOCallBack = callBack; + return this; + } + + public void removeSocketIOCallBack() { + mSocketIOCallBack = null; + } +} + diff --git a/base/src/main/res/drawable-hdpi/ic_arrow.png b/base/src/main/res/drawable-hdpi/ic_arrow.png new file mode 100644 index 0000000..f7f8fe5 Binary files /dev/null and b/base/src/main/res/drawable-hdpi/ic_arrow.png differ diff --git a/base/src/main/res/drawable-mdpi/ic_arrow.png b/base/src/main/res/drawable-mdpi/ic_arrow.png new file mode 100644 index 0000000..d4425e1 Binary files /dev/null and b/base/src/main/res/drawable-mdpi/ic_arrow.png differ diff --git a/base/src/main/res/drawable-xhdpi/ic_arrow.png b/base/src/main/res/drawable-xhdpi/ic_arrow.png new file mode 100644 index 0000000..5f14920 Binary files /dev/null and b/base/src/main/res/drawable-xhdpi/ic_arrow.png differ diff --git a/base/src/main/res/drawable-xhdpi/new_pro_bg.9.png b/base/src/main/res/drawable-xhdpi/new_pro_bg.9.png new file mode 100644 index 0000000..f084631 Binary files /dev/null and b/base/src/main/res/drawable-xhdpi/new_pro_bg.9.png differ diff --git a/base/src/main/res/drawable-xxhdpi/ic_arrow.png b/base/src/main/res/drawable-xxhdpi/ic_arrow.png new file mode 100644 index 0000000..d024d52 Binary files /dev/null and b/base/src/main/res/drawable-xxhdpi/ic_arrow.png differ diff --git a/base/src/main/res/drawable-xxhdpi/ic_back_arrow.png b/base/src/main/res/drawable-xxhdpi/ic_back_arrow.png new file mode 100644 index 0000000..639cc47 Binary files /dev/null and b/base/src/main/res/drawable-xxhdpi/ic_back_arrow.png differ diff --git a/base/src/main/res/drawable-xxhdpi/ic_orange_arrow_down.png b/base/src/main/res/drawable-xxhdpi/ic_orange_arrow_down.png new file mode 100644 index 0000000..dfaae6a Binary files /dev/null and b/base/src/main/res/drawable-xxhdpi/ic_orange_arrow_down.png differ diff --git a/base/src/main/res/drawable-xxhdpi/ic_orange_arrow_up.png b/base/src/main/res/drawable-xxhdpi/ic_orange_arrow_up.png new file mode 100644 index 0000000..f7c5811 Binary files /dev/null and b/base/src/main/res/drawable-xxhdpi/ic_orange_arrow_up.png differ diff --git a/base/src/main/res/drawable-xxxhdpi/ic_arrow.png b/base/src/main/res/drawable-xxxhdpi/ic_arrow.png new file mode 100644 index 0000000..935be9d Binary files /dev/null and b/base/src/main/res/drawable-xxxhdpi/ic_arrow.png differ diff --git a/base/src/main/res/drawable/dialog_style_xml_icon.xml b/base/src/main/res/drawable/dialog_style_xml_icon.xml new file mode 100644 index 0000000..ebd7e89 --- /dev/null +++ b/base/src/main/res/drawable/dialog_style_xml_icon.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/drawable/ic_baseline_close_24.xml b/base/src/main/res/drawable/ic_baseline_close_24.xml new file mode 100644 index 0000000..70db409 --- /dev/null +++ b/base/src/main/res/drawable/ic_baseline_close_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/base/src/main/res/drawable/ic_load_failed.xml b/base/src/main/res/drawable/ic_load_failed.xml new file mode 100644 index 0000000..582475a --- /dev/null +++ b/base/src/main/res/drawable/ic_load_failed.xml @@ -0,0 +1,9 @@ + + + diff --git a/base/src/main/res/drawable/ic_loading.xml b/base/src/main/res/drawable/ic_loading.xml new file mode 100644 index 0000000..d83dd10 --- /dev/null +++ b/base/src/main/res/drawable/ic_loading.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + diff --git a/base/src/main/res/drawable/ic_network_unavailable.xml b/base/src/main/res/drawable/ic_network_unavailable.xml new file mode 100644 index 0000000..54771e0 --- /dev/null +++ b/base/src/main/res/drawable/ic_network_unavailable.xml @@ -0,0 +1,9 @@ + + + diff --git a/base/src/main/res/drawable/ic_no_data.xml b/base/src/main/res/drawable/ic_no_data.xml new file mode 100644 index 0000000..ddf33f6 --- /dev/null +++ b/base/src/main/res/drawable/ic_no_data.xml @@ -0,0 +1,9 @@ + + + diff --git a/base/src/main/res/drawable/ic_top_menu.xml b/base/src/main/res/drawable/ic_top_menu.xml new file mode 100644 index 0000000..8bb3fc3 --- /dev/null +++ b/base/src/main/res/drawable/ic_top_menu.xml @@ -0,0 +1,5 @@ + + + diff --git a/base/src/main/res/drawable/icon_loading.png b/base/src/main/res/drawable/icon_loading.png new file mode 100644 index 0000000..382b936 Binary files /dev/null and b/base/src/main/res/drawable/icon_loading.png differ diff --git a/base/src/main/res/drawable/load_progress.xml b/base/src/main/res/drawable/load_progress.xml new file mode 100644 index 0000000..06f6e66 --- /dev/null +++ b/base/src/main/res/drawable/load_progress.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/base/src/main/res/drawable/picture_new_item_select_bg.xml b/base/src/main/res/drawable/picture_new_item_select_bg.xml new file mode 100644 index 0000000..5df58cc --- /dev/null +++ b/base/src/main/res/drawable/picture_new_item_select_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/layout/activity_base.xml b/base/src/main/res/layout/activity_base.xml new file mode 100644 index 0000000..8df83d4 --- /dev/null +++ b/base/src/main/res/layout/activity_base.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/layout/activity_base_list.xml b/base/src/main/res/layout/activity_base_list.xml new file mode 100644 index 0000000..d3514a4 --- /dev/null +++ b/base/src/main/res/layout/activity_base_list.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/layout/activity_base_list_app_bar.xml b/base/src/main/res/layout/activity_base_list_app_bar.xml new file mode 100644 index 0000000..25e06a0 --- /dev/null +++ b/base/src/main/res/layout/activity_base_list_app_bar.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/layout/activity_x_webview.xml b/base/src/main/res/layout/activity_x_webview.xml new file mode 100644 index 0000000..6d37225 --- /dev/null +++ b/base/src/main/res/layout/activity_x_webview.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/layout/empty_msg_view.xml b/base/src/main/res/layout/empty_msg_view.xml new file mode 100644 index 0000000..2ac56da --- /dev/null +++ b/base/src/main/res/layout/empty_msg_view.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/src/main/res/layout/empty_view.xml b/base/src/main/res/layout/empty_view.xml new file mode 100644 index 0000000..24e37f9 --- /dev/null +++ b/base/src/main/res/layout/empty_view.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/src/main/res/layout/item_slip.xml b/base/src/main/res/layout/item_slip.xml new file mode 100644 index 0000000..13fb8be --- /dev/null +++ b/base/src/main/res/layout/item_slip.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/layout/loading_circle_process_dialog_icon.xml b/base/src/main/res/layout/loading_circle_process_dialog_icon.xml new file mode 100644 index 0000000..d9ac540 --- /dev/null +++ b/base/src/main/res/layout/loading_circle_process_dialog_icon.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/layout/loading_circle_process_dialog_icon_by_textview.xml b/base/src/main/res/layout/loading_circle_process_dialog_icon_by_textview.xml new file mode 100644 index 0000000..38e7909 --- /dev/null +++ b/base/src/main/res/layout/loading_circle_process_dialog_icon_by_textview.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/layout/loading_process_dialog_icon.xml b/base/src/main/res/layout/loading_process_dialog_icon.xml new file mode 100644 index 0000000..e0f6330 --- /dev/null +++ b/base/src/main/res/layout/loading_process_dialog_icon.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/layout/super_viewpager_layout.xml b/base/src/main/res/layout/super_viewpager_layout.xml new file mode 100644 index 0000000..778c474 --- /dev/null +++ b/base/src/main/res/layout/super_viewpager_layout.xml @@ -0,0 +1,8 @@ + + + diff --git a/base/src/main/res/layout/toolbar.xml b/base/src/main/res/layout/toolbar.xml new file mode 100644 index 0000000..be4dfed --- /dev/null +++ b/base/src/main/res/layout/toolbar.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + diff --git a/base/src/main/res/mipmap-hdpi/btn_back.png b/base/src/main/res/mipmap-hdpi/btn_back.png new file mode 100644 index 0000000..a67482f Binary files /dev/null and b/base/src/main/res/mipmap-hdpi/btn_back.png differ diff --git a/base/src/main/res/mipmap-hdpi/empty.png b/base/src/main/res/mipmap-hdpi/empty.png new file mode 100644 index 0000000..184b01f Binary files /dev/null and b/base/src/main/res/mipmap-hdpi/empty.png differ diff --git a/base/src/main/res/mipmap-hdpi/home_follow.png b/base/src/main/res/mipmap-hdpi/home_follow.png new file mode 100644 index 0000000..fcb91d9 Binary files /dev/null and b/base/src/main/res/mipmap-hdpi/home_follow.png differ diff --git a/base/src/main/res/mipmap-hdpi/honem_chat.png b/base/src/main/res/mipmap-hdpi/honem_chat.png new file mode 100644 index 0000000..6064a0b Binary files /dev/null and b/base/src/main/res/mipmap-hdpi/honem_chat.png differ diff --git a/base/src/main/res/mipmap-hdpi/network.png b/base/src/main/res/mipmap-hdpi/network.png new file mode 100644 index 0000000..b25a641 Binary files /dev/null and b/base/src/main/res/mipmap-hdpi/network.png differ diff --git a/base/src/main/res/mipmap-hdpi/nomessage.png b/base/src/main/res/mipmap-hdpi/nomessage.png new file mode 100644 index 0000000..ad491c7 Binary files /dev/null and b/base/src/main/res/mipmap-hdpi/nomessage.png differ diff --git a/base/src/main/res/mipmap-hdpi/video_lock.png b/base/src/main/res/mipmap-hdpi/video_lock.png new file mode 100644 index 0000000..326ccb4 Binary files /dev/null and b/base/src/main/res/mipmap-hdpi/video_lock.png differ diff --git a/base/src/main/res/mipmap-hdpi/video_play.png b/base/src/main/res/mipmap-hdpi/video_play.png new file mode 100644 index 0000000..6be3775 Binary files /dev/null and b/base/src/main/res/mipmap-hdpi/video_play.png differ diff --git a/base/src/main/res/mipmap-mdpi/btn_back.png b/base/src/main/res/mipmap-mdpi/btn_back.png new file mode 100644 index 0000000..9d4e67a Binary files /dev/null and b/base/src/main/res/mipmap-mdpi/btn_back.png differ diff --git a/base/src/main/res/mipmap-mdpi/empty.png b/base/src/main/res/mipmap-mdpi/empty.png new file mode 100644 index 0000000..829ec25 Binary files /dev/null and b/base/src/main/res/mipmap-mdpi/empty.png differ diff --git a/base/src/main/res/mipmap-mdpi/home_follow.png b/base/src/main/res/mipmap-mdpi/home_follow.png new file mode 100644 index 0000000..c1262a5 Binary files /dev/null and b/base/src/main/res/mipmap-mdpi/home_follow.png differ diff --git a/base/src/main/res/mipmap-mdpi/honem_chat.png b/base/src/main/res/mipmap-mdpi/honem_chat.png new file mode 100644 index 0000000..6fb8f68 Binary files /dev/null and b/base/src/main/res/mipmap-mdpi/honem_chat.png differ diff --git a/base/src/main/res/mipmap-mdpi/network.png b/base/src/main/res/mipmap-mdpi/network.png new file mode 100644 index 0000000..5ae55b0 Binary files /dev/null and b/base/src/main/res/mipmap-mdpi/network.png differ diff --git a/base/src/main/res/mipmap-mdpi/nomessage.png b/base/src/main/res/mipmap-mdpi/nomessage.png new file mode 100644 index 0000000..13f4fd2 Binary files /dev/null and b/base/src/main/res/mipmap-mdpi/nomessage.png differ diff --git a/base/src/main/res/mipmap-mdpi/video_lock.png b/base/src/main/res/mipmap-mdpi/video_lock.png new file mode 100644 index 0000000..c8acbb8 Binary files /dev/null and b/base/src/main/res/mipmap-mdpi/video_lock.png differ diff --git a/base/src/main/res/mipmap-mdpi/video_play.png b/base/src/main/res/mipmap-mdpi/video_play.png new file mode 100644 index 0000000..92a08b6 Binary files /dev/null and b/base/src/main/res/mipmap-mdpi/video_play.png differ diff --git a/base/src/main/res/mipmap-xhdpi/btn_back.png b/base/src/main/res/mipmap-xhdpi/btn_back.png new file mode 100644 index 0000000..26eec78 Binary files /dev/null and b/base/src/main/res/mipmap-xhdpi/btn_back.png differ diff --git a/base/src/main/res/mipmap-xhdpi/empty.png b/base/src/main/res/mipmap-xhdpi/empty.png new file mode 100644 index 0000000..0ff586d Binary files /dev/null and b/base/src/main/res/mipmap-xhdpi/empty.png differ diff --git a/base/src/main/res/mipmap-xhdpi/home_follow.png b/base/src/main/res/mipmap-xhdpi/home_follow.png new file mode 100644 index 0000000..ab14586 Binary files /dev/null and b/base/src/main/res/mipmap-xhdpi/home_follow.png differ diff --git a/base/src/main/res/mipmap-xhdpi/honem_chat.png b/base/src/main/res/mipmap-xhdpi/honem_chat.png new file mode 100644 index 0000000..73f5067 Binary files /dev/null and b/base/src/main/res/mipmap-xhdpi/honem_chat.png differ diff --git a/base/src/main/res/mipmap-xhdpi/network.png b/base/src/main/res/mipmap-xhdpi/network.png new file mode 100644 index 0000000..1c80494 Binary files /dev/null and b/base/src/main/res/mipmap-xhdpi/network.png differ diff --git a/base/src/main/res/mipmap-xhdpi/nomessage.png b/base/src/main/res/mipmap-xhdpi/nomessage.png new file mode 100644 index 0000000..af18473 Binary files /dev/null and b/base/src/main/res/mipmap-xhdpi/nomessage.png differ diff --git a/base/src/main/res/mipmap-xhdpi/video_lock.png b/base/src/main/res/mipmap-xhdpi/video_lock.png new file mode 100644 index 0000000..9ab56ff Binary files /dev/null and b/base/src/main/res/mipmap-xhdpi/video_lock.png differ diff --git a/base/src/main/res/mipmap-xhdpi/video_play.png b/base/src/main/res/mipmap-xhdpi/video_play.png new file mode 100644 index 0000000..227e195 Binary files /dev/null and b/base/src/main/res/mipmap-xhdpi/video_play.png differ diff --git a/base/src/main/res/mipmap-xxhdpi/btn_back.png b/base/src/main/res/mipmap-xxhdpi/btn_back.png new file mode 100644 index 0000000..0cdd361 Binary files /dev/null and b/base/src/main/res/mipmap-xxhdpi/btn_back.png differ diff --git a/base/src/main/res/mipmap-xxhdpi/empty.png b/base/src/main/res/mipmap-xxhdpi/empty.png new file mode 100644 index 0000000..63b4ca0 Binary files /dev/null and b/base/src/main/res/mipmap-xxhdpi/empty.png differ diff --git a/base/src/main/res/mipmap-xxhdpi/home_follow.png b/base/src/main/res/mipmap-xxhdpi/home_follow.png new file mode 100644 index 0000000..ef059ae Binary files /dev/null and b/base/src/main/res/mipmap-xxhdpi/home_follow.png differ diff --git a/base/src/main/res/mipmap-xxhdpi/honem_chat.png b/base/src/main/res/mipmap-xxhdpi/honem_chat.png new file mode 100644 index 0000000..c3341f4 Binary files /dev/null and b/base/src/main/res/mipmap-xxhdpi/honem_chat.png differ diff --git a/base/src/main/res/mipmap-xxhdpi/network.png b/base/src/main/res/mipmap-xxhdpi/network.png new file mode 100644 index 0000000..61c0c6d Binary files /dev/null and b/base/src/main/res/mipmap-xxhdpi/network.png differ diff --git a/base/src/main/res/mipmap-xxhdpi/nomessage.png b/base/src/main/res/mipmap-xxhdpi/nomessage.png new file mode 100644 index 0000000..26bef1c Binary files /dev/null and b/base/src/main/res/mipmap-xxhdpi/nomessage.png differ diff --git a/base/src/main/res/mipmap-xxhdpi/video_lock.png b/base/src/main/res/mipmap-xxhdpi/video_lock.png new file mode 100644 index 0000000..30735f0 Binary files /dev/null and b/base/src/main/res/mipmap-xxhdpi/video_lock.png differ diff --git a/base/src/main/res/mipmap-xxhdpi/video_play.png b/base/src/main/res/mipmap-xxhdpi/video_play.png new file mode 100644 index 0000000..d880184 Binary files /dev/null and b/base/src/main/res/mipmap-xxhdpi/video_play.png differ diff --git a/base/src/main/res/mipmap-xxxhdpi/btn_back.png b/base/src/main/res/mipmap-xxxhdpi/btn_back.png new file mode 100644 index 0000000..65b0a9a Binary files /dev/null and b/base/src/main/res/mipmap-xxxhdpi/btn_back.png differ diff --git a/base/src/main/res/mipmap-xxxhdpi/empty.png b/base/src/main/res/mipmap-xxxhdpi/empty.png new file mode 100644 index 0000000..e02924b Binary files /dev/null and b/base/src/main/res/mipmap-xxxhdpi/empty.png differ diff --git a/base/src/main/res/mipmap-xxxhdpi/home_follow.png b/base/src/main/res/mipmap-xxxhdpi/home_follow.png new file mode 100644 index 0000000..387074f Binary files /dev/null and b/base/src/main/res/mipmap-xxxhdpi/home_follow.png differ diff --git a/base/src/main/res/mipmap-xxxhdpi/honem_chat.png b/base/src/main/res/mipmap-xxxhdpi/honem_chat.png new file mode 100644 index 0000000..86e80df Binary files /dev/null and b/base/src/main/res/mipmap-xxxhdpi/honem_chat.png differ diff --git a/base/src/main/res/mipmap-xxxhdpi/network.png b/base/src/main/res/mipmap-xxxhdpi/network.png new file mode 100644 index 0000000..ce8e713 Binary files /dev/null and b/base/src/main/res/mipmap-xxxhdpi/network.png differ diff --git a/base/src/main/res/mipmap-xxxhdpi/nomessage.png b/base/src/main/res/mipmap-xxxhdpi/nomessage.png new file mode 100644 index 0000000..cd10a2d Binary files /dev/null and b/base/src/main/res/mipmap-xxxhdpi/nomessage.png differ diff --git a/base/src/main/res/mipmap-xxxhdpi/video_lock.png b/base/src/main/res/mipmap-xxxhdpi/video_lock.png new file mode 100644 index 0000000..f481ea0 Binary files /dev/null and b/base/src/main/res/mipmap-xxxhdpi/video_lock.png differ diff --git a/base/src/main/res/mipmap-xxxhdpi/video_play.png b/base/src/main/res/mipmap-xxxhdpi/video_play.png new file mode 100644 index 0000000..84aee8c Binary files /dev/null and b/base/src/main/res/mipmap-xxxhdpi/video_play.png differ diff --git a/base/src/main/res/values/attr.xml b/base/src/main/res/values/attr.xml new file mode 100644 index 0000000..eac8e39 --- /dev/null +++ b/base/src/main/res/values/attr.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/base/src/main/res/values/attrs.xml b/base/src/main/res/values/attrs.xml new file mode 100644 index 0000000..7b5ee6d --- /dev/null +++ b/base/src/main/res/values/attrs.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/values/colors.xml b/base/src/main/res/values/colors.xml new file mode 100644 index 0000000..8f320d3 --- /dev/null +++ b/base/src/main/res/values/colors.xml @@ -0,0 +1,41 @@ + + + #008577 + + + @color/bg_window + @color/colorPrimary + #FAFAFA + #00000000 + + #F7F9FC + #222222 + #FFFFFF + #000000 + #409CFA + #FF4444 + #586C94 + + #56ffff + #ff4667 + #2084fb + #9056ffff + #902084fb + #90ff4667 + + #333333 + #00000000 + + @color/light_gray + + #CDCDCD + #686868 + + #000000 + #FFFFFF + #E0DBDBDB + #53575e + + #5ea978 + + diff --git a/base/src/main/res/values/dimens.xml b/base/src/main/res/values/dimens.xml new file mode 100644 index 0000000..80bccfc --- /dev/null +++ b/base/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 44dp + \ No newline at end of file diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml new file mode 100644 index 0000000..0a2de60 --- /dev/null +++ b/base/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + 返回按钮 + 右上角按钮 + \ No newline at end of file diff --git a/base/src/main/res/values/styles.xml b/base/src/main/res/values/styles.xml new file mode 100644 index 0000000..c443476 --- /dev/null +++ b/base/src/main/res/values/styles.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/base/src/main/res/xml/file_paths.xml b/base/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..a3b9b54 --- /dev/null +++ b/base/src/main/res/xml/file_paths.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/config.gradle b/config.gradle index 3ed995d..7b93f0d 100644 --- a/config.gradle +++ b/config.gradle @@ -14,7 +14,7 @@ ext { paging : '2.1.1', room : '2.2.3', - lifecycle : '2.5.1', + lifecycle : '2.2.0', retrofit : '2.4.0', glide : '4.10.0', @@ -25,9 +25,9 @@ ext { } androidx = [ - appcompat : 'androidx.appcompat:appcompat:1.3.0', + appcompat : 'androidx.appcompat:appcompat:1.1.0', annotation : 'androidx.annotation:annotation:1.1.0', - constraintlayout : 'androidx.constraintlayout:constraintlayout:2.0.4', + constraintlayout : 'androidx.constraintlayout:constraintlayout:1.1.3', recyclerview : 'androidx.recyclerview:recyclerview:1.1.0', viewpager2 : 'androidx.viewpager2:viewpager2:1.0.0', material : 'com.google.android.material:material:1.1.0', diff --git a/core/build.gradle b/core/build.gradle index f8a0029..4ceeb83 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -31,8 +31,8 @@ android { dependencies { api fileTree(dir: "libs", include: ["*.jar"]) -// api project(path: ':base') - api 'com.xuqm.android:base:0.0.5.102' + api project(path: ':base') +// api 'com.xuqm.android:base:0.0.5.102' api 'androidx.navigation:navigation-fragment-ktx:2.3.5' api 'androidx.navigation:navigation-ui-ktx:2.3.5' diff --git a/settings.gradle b/settings.gradle index add3c0a..9642a8d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ +include ':base' include ':app' include ':core' -include ':base' rootProject.name = "AndroidPlatform" \ No newline at end of file