a
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,88 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'com.google.dagger.hilt.android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.android.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
versionCode rootProject.ext.android.versionCode
|
||||
versionName rootProject.ext.android.versionName
|
||||
multiDexEnabled true
|
||||
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += [
|
||||
"room.schemaLocation":"$projectDir/schemas".toString(),
|
||||
"room.incremental":"true",
|
||||
"room.expandProjection":"true",
|
||||
AROUTER_MODULE_NAME: project.getName()
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
arg("AROUTER_MODULE_NAME", project.getName())
|
||||
}
|
||||
generateStubs = true
|
||||
useBuildCache = true
|
||||
javacOptions {
|
||||
option("-Xmaxerrs", 500)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
|
||||
api rootProject.ext.ktxLibs
|
||||
|
||||
// surpport
|
||||
api rootProject.ext.commonUILibs
|
||||
|
||||
// Android Jetpack 组件
|
||||
api rootProject.ext.jetpackLibs
|
||||
|
||||
// net
|
||||
api rootProject.ext.netLibs
|
||||
|
||||
// glide
|
||||
api rootProject.ext.glideLibs
|
||||
kapt rootProject.ext.compiler["glideCompiler"]
|
||||
|
||||
// arouter
|
||||
api rootProject.ext.arouterLibs
|
||||
kapt rootProject.ext.compiler["arouterCompiler"]
|
||||
|
||||
kapt rootProject.ext.compiler["hiltAndroidCompiler"]
|
||||
|
||||
api rootProject.ext.otherLibs
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
# 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
|
||||
|
||||
###########################通用配置start#########################################
|
||||
#1.基本指令区
|
||||
# 代码混淆压缩比,在0~7之间,默认为5,一般不做修改
|
||||
-optimizationpasses 5
|
||||
|
||||
# 混合时不使用大小写混合,混合后的类名为小写
|
||||
-dontusemixedcaseclassnames
|
||||
|
||||
# 指定不去忽略非公共库的类
|
||||
-dontskipnonpubliclibraryclasses
|
||||
|
||||
# 指定不去忽略非公共库的类成员
|
||||
-dontskipnonpubliclibraryclassmembers
|
||||
|
||||
# 不进行优化,建议使用此选项,
|
||||
-dontoptimize
|
||||
|
||||
# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify 能够加快混淆速度。
|
||||
-dontpreverify
|
||||
|
||||
# 使我们的项目混淆后产生映射文件包含有类名->混淆后类名的映射关系
|
||||
-verbose
|
||||
|
||||
# 使用printmapping指定映射文件的名称
|
||||
-printmapping proguardMapping.txt
|
||||
|
||||
# 屏蔽警告
|
||||
-ignorewarnings
|
||||
|
||||
# 指定混淆是采用的算法,后面的参数是一个过滤器这个过滤器是谷歌推荐的算法,一般不做更改
|
||||
-optimizations !code/simplification/cast,!field/*,!class/merging/*
|
||||
|
||||
#添加支持的jar(引入libs下的所有jar包)
|
||||
#-libraryjars libs(*.jar)
|
||||
|
||||
#将文件来源重命名为“SourceFile”字符串
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
||||
# 保留Annotation不混淆
|
||||
-keepattributes *Annotation*
|
||||
-keep class * extends java.lang.annotation.Annotation {*;}
|
||||
|
||||
# 避免混淆泛型
|
||||
-keepattributes Signature
|
||||
|
||||
#保持反射不被混淆
|
||||
-keepattributes EnclosingMethod
|
||||
|
||||
#保持异常不被混淆
|
||||
-keepattributes Exceptions
|
||||
#保持内部类不被混淆
|
||||
-keepattributes Exceptions,InnerClasses
|
||||
|
||||
# 抛出异常时保留代码行号
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
#2.默认保留区
|
||||
# 保留我们使用的四大组件,自定义的Application等等这些类不被混淆
|
||||
# 因为这些子类都有可能被外部调用
|
||||
-keep public class * extends android.app.Activity
|
||||
-keep public class * extends android.app.Application
|
||||
-keep public class * extends android.app.Service
|
||||
-keep public class * extends android.content.BroadcastReceiver
|
||||
-keep public class * extends android.content.ContentProvider
|
||||
-keep public class * extends android.app.backup.BackupAgentHelper
|
||||
-keep public class * extends android.preference.Preference
|
||||
-keep public class * extends android.view.View
|
||||
-keep public class com.android.vending.licensing.ILicensingService
|
||||
-keep public class com.google.vending.licensing.ILicensingService
|
||||
# 保留support下的所有类及其内部类
|
||||
-keep class android.support.** {*;}
|
||||
# 保留继承的
|
||||
-keep public class * extends android.support.v4.**
|
||||
-keep public class * extends android.support.v7.**
|
||||
-keep public class * extends android.support.annotation.**
|
||||
-keep public class * extends androidx.appcompat.**
|
||||
-keep class com.google.android.material.** {*;}
|
||||
-keep class androidx.** {*;}
|
||||
-keep public class * extends androidx.**
|
||||
-keep interface androidx.** {*;}
|
||||
-dontwarn com.google.android.material.**
|
||||
-dontnote com.google.android.material.**
|
||||
-dontwarn androidx.**
|
||||
|
||||
|
||||
# 保留本地native方法不被混淆
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# 保留在Activity中的方法参数是view的方法,这样一来我们在layout中写的onClick就不会被影响
|
||||
-keepclassmembers class * extends android.app.Activity{
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
# 保留枚举类不被混淆
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
# 保留我们自定义控件(继承自View)不被混淆
|
||||
-keep public class * extends android.view.View{
|
||||
*** get*();
|
||||
void set*(***);
|
||||
public <init>(android.content.Context);
|
||||
public <init>(android.content.Context, android.util.AttributeSet);
|
||||
public <init>(android.content.Context, android.util.AttributeSet, int);
|
||||
}
|
||||
-keepclasseswithmembers class * {
|
||||
public <init>(android.content.Context, android.util.AttributeSet);
|
||||
public <init>(android.content.Context, android.util.AttributeSet, int);
|
||||
}
|
||||
|
||||
# 保留Parcelable序列化类不被混淆
|
||||
-keep class * implements android.os.Parcelable {
|
||||
public static final android.os.Parcelable$Creator *;
|
||||
}
|
||||
|
||||
# 保留Serializable序列化的类不被混淆
|
||||
-keepclassmembers class * implements java.io.Serializable {
|
||||
static final long serialVersionUID;
|
||||
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||
private void writeObject(java.io.ObjectOutputStream);
|
||||
private void readObject(java.io.ObjectInputStream);
|
||||
java.lang.Object writeReplace();
|
||||
java.lang.Object readResolve();
|
||||
}
|
||||
|
||||
# 保留R下面的资源
|
||||
-keep class **.R$* {
|
||||
*;
|
||||
}
|
||||
|
||||
# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
|
||||
-keepclassmembers class * {
|
||||
void *(**On*Event);
|
||||
void *(**On*Listener);
|
||||
}
|
||||
|
||||
# 避免layout中onclick方法(android:onclick="onClick")混淆
|
||||
-keepclassmembers class * extends android.app.Activity{
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
# 对WebView的处理
|
||||
-keepclassmembers class * extends android.webkit.webViewClient {
|
||||
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
|
||||
public boolean *(android.webkit.WebView, java.lang.String);
|
||||
}
|
||||
-keepclassmembers class * extends android.webkit.webViewClient {
|
||||
public void *(android.webkit.webView, java.lang.String);
|
||||
}
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
|
||||
# AndroidX混淆
|
||||
-keep class com.google.android.material.** {*;}
|
||||
-keep class androidx.** {*;}
|
||||
-keep public class * extends androidx.**
|
||||
-keep interface androidx.** {*;}
|
||||
-dontwarn com.google.android.material.**
|
||||
-dontnote com.google.android.material.**
|
||||
-dontwarn androidx.**
|
||||
###########################通用配置end#########################################
|
||||
|
||||
###########################第三方库start#########################################
|
||||
|
||||
#Architecture
|
||||
-dontwarn android.arch.**
|
||||
-keep public class android.arch.**{*;}
|
||||
|
||||
#bugly
|
||||
-dontwarn com.tencent.bugly.**
|
||||
-keep public class com.tencent.bugly.**{*;}
|
||||
|
||||
#okhttp
|
||||
-dontwarn okhttp3.**
|
||||
-dontwarn okio.**
|
||||
-dontwarn javax.annotation.**
|
||||
# A resource is loaded with a relative path so the package of this class must be preserved.
|
||||
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
|
||||
|
||||
|
||||
#retrofit
|
||||
-dontwarn retrofit2.**
|
||||
-keep class retrofit2.** { *; }
|
||||
-keepattributes Exceptions
|
||||
|
||||
#glide
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
-dontwarn com.bumptech.glide.load.model.stream.**
|
||||
|
||||
# EventBus
|
||||
-keepclassmembers class * {
|
||||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
# Only required if you use AsyncExecutor
|
||||
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
|
||||
<init>(java.lang.Throwable);
|
||||
}
|
||||
|
||||
# Arouter
|
||||
-keep public class com.alibaba.android.arouter.routes.**{*;}
|
||||
-keep public class com.alibaba.android.arouter.facade.**{*;}
|
||||
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
|
||||
|
||||
###########################第三方库end#########################################
|
||||
|
||||
-keep class com.bbgo.common_base.bean.** {*;}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.bbgo.common_base">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
</manifest>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.bbgo.common_base
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.alibaba.android.arouter.launcher.ARouter
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
open class BaseApplication : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
context = this
|
||||
MMKV.initialize(this)
|
||||
if (BuildConfig.DEBUG) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
|
||||
ARouter.openLog() // 打印日志
|
||||
ARouter.openDebug() // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
|
||||
}
|
||||
ARouter.init(this)
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private var context: BaseApplication? = null
|
||||
@JvmStatic
|
||||
fun getContext(): Context {
|
||||
return context!!
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.bbgo.common_base.base
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.bbgo.common_base.util.ActivityCollector
|
||||
import com.bbgo.library_statusbar.NotchScreenManager
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 2021/5/20 10:03 上午
|
||||
* description: todo
|
||||
*/
|
||||
|
||||
abstract class BaseActivity<VB: ViewBinding> : AppCompatActivity() {
|
||||
|
||||
/**
|
||||
* 日志输出标志
|
||||
*/
|
||||
protected val TAG: String = this.javaClass.simpleName
|
||||
|
||||
protected lateinit var binding: VB
|
||||
|
||||
/**
|
||||
* 当前Activity的实例。
|
||||
*/
|
||||
private var activity: Activity? = null
|
||||
|
||||
/** 当前Activity的弱引用,防止内存泄露 */
|
||||
private var activityWR: WeakReference<Activity>? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = inflateViewBinding()
|
||||
setContentView(binding.root)
|
||||
NotchScreenManager.getInstance().setDisplayInNotch(this)
|
||||
activity = this
|
||||
activityWR = WeakReference(activity!!)
|
||||
ActivityCollector.pushTask(activityWR)
|
||||
}
|
||||
|
||||
abstract fun inflateViewBinding(): VB
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
finish()
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
activity = null
|
||||
ActivityCollector.removeTask(activityWR)
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.bbgo.common_base.base
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewbinding.ViewBinding
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 2021/5/20 3:01 下午
|
||||
* description: todo
|
||||
*/
|
||||
abstract class BaseFragment<VB: ViewBinding> : Fragment() {
|
||||
|
||||
/**
|
||||
* 日志输出标志
|
||||
*/
|
||||
protected val TAG: String = this.javaClass.simpleName
|
||||
|
||||
private var _binding: VB? = null
|
||||
|
||||
protected val binding get() = _binding!!
|
||||
|
||||
private var isLoaded = false
|
||||
|
||||
/**
|
||||
* 防止回退到上一级页面时还会init view的问题
|
||||
*/
|
||||
private var isInitializedRootView = false
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = inflateViewBinding(inflater, container)
|
||||
return _binding?.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
if (isInitializedRootView) return
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initView()
|
||||
observe()
|
||||
isInitializedRootView = true
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (!isLoaded && !isHidden) {
|
||||
lazyLoad()
|
||||
isLoaded = true
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun inflateViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB
|
||||
|
||||
/**
|
||||
* 初始化 View
|
||||
*/
|
||||
abstract fun initView()
|
||||
/**
|
||||
* 懒加载
|
||||
*/
|
||||
abstract fun lazyLoad()
|
||||
|
||||
abstract fun observe()
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.bbgo.common_base.base
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class SingleLiveData<T> : MutableLiveData<T>() {
|
||||
|
||||
private val TAG = "SingleLiveData"
|
||||
|
||||
private val pending = AtomicBoolean(false)
|
||||
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||
if (hasActiveObservers()) {
|
||||
Log.d(TAG, "multi observers registered but only one will be notified of changes")
|
||||
}
|
||||
super.observe(owner) {
|
||||
if (pending.compareAndSet(true, false)) {
|
||||
observer.onChanged(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setValue(value: T) {
|
||||
pending.set(true)
|
||||
super.setValue(value)
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.bbgo.common_base.bean
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/8/13 11:40 上午
|
||||
*/
|
||||
@Keep
|
||||
open class BaseBean {
|
||||
var errorCode: Int = 0
|
||||
var errorMsg: String = ""
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.bbgo.common_base.bean
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/8/13 11:41 上午
|
||||
*/
|
||||
@Keep
|
||||
class HttpResult<T>(val data: T) : BaseBean()
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.bbgo.common_base.bus
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/8/17 10:17 上午
|
||||
*/
|
||||
class BusKey {
|
||||
companion object {
|
||||
const val COLLECT = "COLLECT"
|
||||
const val SCROLL_TOP = "SCROLL_TOP"
|
||||
const val LOGIN_SUCCESS = "LOGIN_SUCCESS"
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package com.bbgo.common_base.bus
|
||||
|
||||
import androidx.lifecycle.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* @Author:Wangyuebin
|
||||
* @Date:2022-09-19
|
||||
* @Description:
|
||||
*/
|
||||
object EventFlowBus {
|
||||
private val busMap = mutableMapOf<String, EventBus<*>>()
|
||||
private val busStickMap = mutableMapOf<String, StickEventBus<*>>()
|
||||
|
||||
@Synchronized
|
||||
fun <T> with(key: String): EventBus<T> {
|
||||
var eventBus = busMap[key]
|
||||
if (eventBus == null) {
|
||||
eventBus = EventBus<T>(key)
|
||||
busMap[key] = eventBus
|
||||
}
|
||||
return eventBus as EventBus<T>
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun <T> withStick(key: String): StickEventBus<T> {
|
||||
var eventBus = busStickMap[key]
|
||||
if (eventBus == null) {
|
||||
eventBus = StickEventBus<T>(key)
|
||||
busStickMap[key] = eventBus
|
||||
}
|
||||
return eventBus as StickEventBus<T>
|
||||
}
|
||||
|
||||
//真正实现类
|
||||
open class EventBus<T>(private val key: String) : LifecycleEventObserver {
|
||||
|
||||
//私有对象用于发送消息
|
||||
private val _events: MutableSharedFlow<T> by lazy {
|
||||
obtainEvent()
|
||||
}
|
||||
|
||||
//暴露的公有对象用于接收消息
|
||||
val events = _events.asSharedFlow()
|
||||
|
||||
open fun obtainEvent(): MutableSharedFlow<T> = MutableSharedFlow(0, 1, BufferOverflow.DROP_OLDEST)
|
||||
|
||||
//主线程接收数据
|
||||
fun register(lifecycleOwner: LifecycleOwner, action: (t: T) -> Unit) {
|
||||
lifecycleOwner.lifecycle.addObserver(this)
|
||||
lifecycleOwner.lifecycleScope.launch {
|
||||
events.collect {
|
||||
try {
|
||||
action(it)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//协程中发送数据
|
||||
suspend fun post(event: T) {
|
||||
_events.emit(event)
|
||||
}
|
||||
|
||||
//主线程发送数据
|
||||
fun post(scope: CoroutineScope, event: T) {
|
||||
scope.launch {
|
||||
_events.emit(event)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
//自动销毁
|
||||
if (event == Lifecycle.Event.ON_DESTROY) {
|
||||
val subscriptCount = _events.subscriptionCount.value
|
||||
if (subscriptCount <= 0)
|
||||
busMap.remove(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StickEventBus<T>(key: String) : EventBus<T>(key) {
|
||||
override fun obtainEvent(): MutableSharedFlow<T> = MutableSharedFlow(1, 1, BufferOverflow.DROP_OLDEST)
|
||||
}
|
||||
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package com.bbgo.common_base.bus
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import java.lang.Exception
|
||||
import java.lang.NullPointerException
|
||||
import java.util.HashMap
|
||||
|
||||
class LiveDataBus private constructor() {
|
||||
|
||||
private val bus: MutableMap<String, BusMutableLiveData<Any>>
|
||||
|
||||
init {
|
||||
bus = HashMap()
|
||||
}
|
||||
|
||||
private object SingletonHolder {
|
||||
val DEFAULT_BUS = LiveDataBus()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun get(): LiveDataBus {
|
||||
return SingletonHolder.DEFAULT_BUS
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> with(key: String, type: Class<T>?): MutableLiveData<T> {
|
||||
if (!bus.containsKey(key)) {
|
||||
val mutableLiveData = BusMutableLiveData<Any>()
|
||||
bus[key] = mutableLiveData
|
||||
return mutableLiveData as MutableLiveData<T>
|
||||
}
|
||||
return bus[key] as MutableLiveData<T>
|
||||
}
|
||||
|
||||
fun with(key: String): MutableLiveData<Any> {
|
||||
return with(key, Any::class.java)
|
||||
}
|
||||
|
||||
private class ObserverWrapper<T>(val observer: Observer<in T>) : Observer<T> {
|
||||
override fun onChanged(t: T) {
|
||||
if (isCallOnObserve) {
|
||||
return
|
||||
}
|
||||
observer.onChanged(t)
|
||||
}
|
||||
|
||||
private val isCallOnObserve: Boolean
|
||||
get() {
|
||||
val stackTrace = Thread.currentThread().stackTrace
|
||||
if (stackTrace.isNotEmpty()) {
|
||||
for (element in stackTrace) {
|
||||
if ("androidx.lifecycle.LiveData" == element.className && "observeForever" == element.methodName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class BusMutableLiveData<T> : MutableLiveData<T>() {
|
||||
private val observerMap: MutableMap<Observer<in T>, Observer<in T>> = HashMap()
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||
super.observe(owner, observer)
|
||||
try {
|
||||
hook(observer)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
override fun observeForever(observer: Observer<in T>) {
|
||||
if (!observerMap.containsKey(observer)) {
|
||||
observerMap[observer] = ObserverWrapper(observer)
|
||||
}
|
||||
observerMap[observer]?.let { super.observeForever(it) }
|
||||
}
|
||||
|
||||
override fun removeObserver(observer: Observer<in T>) {
|
||||
val realObserver = if (observerMap.containsKey(observer)) {
|
||||
observerMap.remove(observer) as Observer<in T>
|
||||
} else {
|
||||
observer
|
||||
}
|
||||
super.removeObserver(realObserver)
|
||||
}
|
||||
|
||||
private fun hook(observer: Observer<in T>) {
|
||||
kotlin.runCatching {
|
||||
//get wrapper's version
|
||||
val classLiveData = LiveData::class.java
|
||||
val fieldObservers = classLiveData.getDeclaredField("mObservers")
|
||||
fieldObservers.isAccessible = true
|
||||
val objectObservers = fieldObservers[this]
|
||||
val classObservers: Class<*> = objectObservers.javaClass
|
||||
val methodGet = classObservers.getDeclaredMethod("get", Any::class.java)
|
||||
methodGet.isAccessible = true
|
||||
val objectWrapperEntry = methodGet.invoke(objectObservers, observer)
|
||||
var objectWrapper: Any? = null
|
||||
if (objectWrapperEntry is Map.Entry<*, *>) {
|
||||
objectWrapper = objectWrapperEntry.value
|
||||
}
|
||||
if (objectWrapper == null) {
|
||||
throw NullPointerException("Wrapper can not be bull!")
|
||||
}
|
||||
val classObserverWrapper = objectWrapper.javaClass.superclass
|
||||
val fieldLastVersion = classObserverWrapper.getDeclaredField("mLastVersion")
|
||||
fieldLastVersion.isAccessible = true
|
||||
//get livedata's version
|
||||
val fieldVersion = classLiveData.getDeclaredField("mVersion")
|
||||
fieldVersion.isAccessible = true
|
||||
val objectVersion = fieldVersion[this]
|
||||
//set wrapper's version
|
||||
fieldLastVersion[objectWrapper] = objectVersion
|
||||
}.onFailure {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.bbgo.common_base.constants
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 2021/5/21 4:38 下午
|
||||
* description: todo
|
||||
*/
|
||||
object Constants {
|
||||
const val DB_INSTANCE = "DB_INSTANCE"
|
||||
const val TOKEN = "token"
|
||||
const val USER_ID = "userid"
|
||||
const val USER_NAME = "username"
|
||||
const val PASSWORD = "password"
|
||||
const val APP_TAG = "WanAndroid"
|
||||
|
||||
/**
|
||||
* url key
|
||||
*/
|
||||
const val CONTENT_URL_KEY = "url"
|
||||
/**
|
||||
* title key
|
||||
*/
|
||||
const val CONTENT_TITLE_KEY = "title"
|
||||
/**
|
||||
* id key
|
||||
*/
|
||||
const val CONTENT_ID_KEY = "id"
|
||||
/**
|
||||
* id key
|
||||
*/
|
||||
const val CONTENT_CID_KEY = "cid"
|
||||
/**
|
||||
* share key
|
||||
*/
|
||||
const val CONTENT_SHARE_TYPE = "text/plain"
|
||||
|
||||
const val POSITION = "position"
|
||||
|
||||
const val COLLECT = "isCollect"
|
||||
|
||||
const val ROUTER_PATH = "routerPath"
|
||||
|
||||
|
||||
object CollectType {
|
||||
const val COLLECT = "COLLECT"
|
||||
const val UNCOLLECT = "UNCOLLECT"
|
||||
const val UNKNOWN = "UNKNOWN"
|
||||
}
|
||||
|
||||
object FragmentIndex {
|
||||
const val HOME_INDEX = 0
|
||||
const val WECHAT_INDEX = 1
|
||||
const val SYS_INDEX = 2
|
||||
const val SQUARE_INDEX = 3
|
||||
const val PROJECT_INDEX = 4
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package com.bbgo.common_base.constants
|
||||
|
||||
/**
|
||||
* @Description: Arouter路由表
|
||||
* 不同的module,一级目录必须不能相同
|
||||
*
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/9/10 4:00 下午
|
||||
*/
|
||||
class RouterPath {
|
||||
|
||||
/**
|
||||
* 登录注册 组件
|
||||
*/
|
||||
object LoginRegister {
|
||||
private const val LOGIN_REGISTER = "module_login"
|
||||
const val PAGE_LOGIN = "/$LOGIN_REGISTER/login"
|
||||
const val PAGE_REGISTER = "/$LOGIN_REGISTER/register"
|
||||
const val SERVICE_LOGOUT = "/$LOGIN_REGISTER/logout"
|
||||
}
|
||||
|
||||
/**
|
||||
* 主页
|
||||
*/
|
||||
object Main {
|
||||
private const val MAIN = "app_main"
|
||||
const val PAGE_MAIN = "/$MAIN/main"
|
||||
}
|
||||
|
||||
object Collect {
|
||||
private const val COLLECT = "module_collect"
|
||||
const val SERVICE_COLLECT = "/$COLLECT/collect"
|
||||
}
|
||||
|
||||
/**
|
||||
* 内容 web 组件
|
||||
*/
|
||||
object Content {
|
||||
private const val CONTENT = "module_content"
|
||||
const val PAGE_CONTENT = "/$CONTENT/content"
|
||||
}
|
||||
|
||||
/**
|
||||
* 首页 组件
|
||||
*/
|
||||
object Home {
|
||||
private const val HOME = "module_home"
|
||||
const val PAGE_HOME = "/$HOME/home"
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信公众号 组件
|
||||
*/
|
||||
object WeChat {
|
||||
private const val WECHAT = "module_wechat"
|
||||
const val PAGE_WECHAT = "/$WECHAT/wechat"
|
||||
}
|
||||
|
||||
/**
|
||||
* 体系 组件
|
||||
*/
|
||||
object Sys {
|
||||
private const val SYS = "module_sys"
|
||||
const val PAGE_SYS = "/$SYS/sys"
|
||||
}
|
||||
|
||||
/**
|
||||
* 体系 组件
|
||||
*/
|
||||
object Square {
|
||||
private const val SQUARE = "module_square"
|
||||
const val PAGE_SQUARE = "/$SQUARE/square"
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目 组件
|
||||
*/
|
||||
object Project {
|
||||
private const val PROJECT = "module_project"
|
||||
const val PAGE_PROJECT = "/$PROJECT/project"
|
||||
}
|
||||
|
||||
/**
|
||||
* compose 组件
|
||||
*/
|
||||
object Compose {
|
||||
private const val COMPOSE = "module_compose"
|
||||
const val PAGE_COMPOSE = "/$COMPOSE/compose"
|
||||
}
|
||||
|
||||
object Media {
|
||||
private const val MEDIA = "module_media"
|
||||
const val PAGE_VIDEO = "/$MEDIA/video"
|
||||
}
|
||||
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
//package com.bbgo.common_base.di
|
||||
//
|
||||
//import com.bbgo.common_base.BaseApplication
|
||||
//import com.bbgo.common_base.constants.HttpConstant
|
||||
//import com.bbgo.common_base.net.GsonTypeAdapterFactory
|
||||
//import com.bbgo.common_base.net.ServiceCreators
|
||||
//import com.bbgo.common_base.net.interceptor.*
|
||||
//import com.google.gson.GsonBuilder
|
||||
//import dagger.Module
|
||||
//import dagger.Provides
|
||||
//import dagger.hilt.InstallIn
|
||||
//import dagger.hilt.components.SingletonComponent
|
||||
//import okhttp3.Cache
|
||||
//import okhttp3.OkHttpClient
|
||||
//import retrofit2.Retrofit
|
||||
//import retrofit2.converter.gson.GsonConverterFactory
|
||||
//import java.io.File
|
||||
//import javax.inject.Singleton
|
||||
//
|
||||
///**
|
||||
// * @Description:
|
||||
// * @Author: wangyuebin
|
||||
// * @Date: 2021/8/30 5:05 下午
|
||||
// */
|
||||
//
|
||||
//@Module
|
||||
//@InstallIn(SingletonComponent::class)
|
||||
//object NetWorkModule {
|
||||
//
|
||||
// private val BASE_URL = "https://wanandroid.com/"
|
||||
//
|
||||
// //设置 请求的缓存的大小跟位置
|
||||
// private val cacheFile = File(BaseApplication.getContext().cacheDir, "cache")
|
||||
// private val cache = Cache(cacheFile, HttpConstant.MAX_CACHE_SIZE)
|
||||
//
|
||||
// @Provides
|
||||
// @Singleton
|
||||
// fun provideOkHttpClient(): OkHttpClient {
|
||||
// return OkHttpClient.Builder()
|
||||
// .addInterceptor(MultiBaseUrlInterceptor())
|
||||
// .addInterceptor(LoggingInterceptor())
|
||||
// .addInterceptor(HeaderInterceptor())
|
||||
// .addInterceptor(SaveCookieInterceptor())
|
||||
// .addInterceptor(CacheInterceptor())
|
||||
// .cache(cache)
|
||||
// .addInterceptor(BasicParamsInterceptor())
|
||||
// .build()
|
||||
// }
|
||||
//
|
||||
// @Provides
|
||||
// @Singleton
|
||||
// fun provideRetrofit(): Retrofit {
|
||||
// return Retrofit.Builder()
|
||||
// .baseUrl(BASE_URL)
|
||||
// .client(ServiceCreators.httpClient)
|
||||
// .addConverterFactory(
|
||||
// GsonConverterFactory.create(
|
||||
// GsonBuilder().registerTypeAdapterFactory(
|
||||
// GsonTypeAdapterFactory()
|
||||
// ).create())).build()
|
||||
// }
|
||||
//}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.bbgo.common_base.event
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 2021/5/27 8:12 下午
|
||||
* description: todo
|
||||
*/
|
||||
@Keep
|
||||
data class MessageEvent(val indexPage: Int, val type: String, val position: Int, val pageId: Int)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.bbgo.common_base.event
|
||||
|
||||
data class ScrollEvent(val index: Int)
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) guolin, Suzhou Quxiang Inc. Open source codes for study only.
|
||||
* Do not use for commercial purpose.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bbgo.common_base.ext
|
||||
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
|
||||
|
||||
/**
|
||||
* 根据手机的分辨率将dp转成为px。
|
||||
*/
|
||||
fun dp2px(dp: Float): Int {
|
||||
val scale = BaseApplication.getContext().resources.displayMetrics.density
|
||||
return (dp * scale + 0.5f).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手机的分辨率将px转成dp。
|
||||
*/
|
||||
fun px2dp(px: Float): Int {
|
||||
val scale = BaseApplication.getContext().resources.displayMetrics.density
|
||||
return (px / scale + 0.5f).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取屏幕宽值。
|
||||
*/
|
||||
val screenWidth
|
||||
get() = BaseApplication.getContext().resources.displayMetrics.widthPixels
|
||||
|
||||
/**
|
||||
* 获取屏幕高值。
|
||||
*/
|
||||
val screenHeight
|
||||
get() = BaseApplication.getContext().resources.displayMetrics.heightPixels
|
||||
|
||||
/**
|
||||
* 获取屏幕像素:对获取的宽高进行拼接。例:1080X2340。
|
||||
*/
|
||||
fun screenPixel(): String {
|
||||
BaseApplication.getContext().resources.displayMetrics.run {
|
||||
return "${widthPixels}X${heightPixels}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.bbgo.common_base.ext
|
||||
|
||||
/**
|
||||
* Created by AhmedEltaher
|
||||
*/
|
||||
|
||||
class Error(val code: Int, val description: String) {
|
||||
constructor(exception: Exception) : this(code = DEFAULT_ERROR, description = exception.message
|
||||
?: "")
|
||||
}
|
||||
|
||||
const val HTTP_REQUEST_ERROR = -1
|
||||
const val NO_INTERNET_CONNECTION = -1
|
||||
const val NETWORK_ERROR = -2
|
||||
const val DEFAULT_ERROR = -3
|
||||
const val PASS_WORD_ERROR = -101
|
||||
const val USER_NAME_ERROR = -102
|
||||
const val CHECK_YOUR_FIELDS = -103
|
||||
const val SEARCH_ERROR = -104
|
||||
const val USER_REGISTERED_ERROR = -105
|
||||
const val USER_NOT_LOGIN = -1001
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.bbgo.common_base.ext
|
||||
|
||||
import androidx.lifecycle.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
||||
liveData.observe(this, { it?.let { t -> action(t) } })
|
||||
}
|
||||
|
||||
inline fun LifecycleOwner.collectWithLifeCycle(viewLifecycleOwner: LifecycleOwner,
|
||||
minActiveSate: Lifecycle.State = Lifecycle.State.STARTED,
|
||||
crossinline block: suspend CoroutineScope.() -> Unit
|
||||
) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(minActiveSate) {
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.bbgo.common_base.ext
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
object Prefs {
|
||||
|
||||
private val mmkv = MMKV.defaultMMKV()!!
|
||||
|
||||
fun putInt(key: String, value: Int) {
|
||||
mmkv.encode(key, value)
|
||||
}
|
||||
|
||||
fun getInt(key: String, defaultValue: Int = -1): Int {
|
||||
return mmkv.decodeInt(key, defaultValue)
|
||||
}
|
||||
|
||||
fun putIntAsync(key: String, value: Int) {
|
||||
mmkv.putInt(key, value).apply()
|
||||
}
|
||||
|
||||
fun putString(key: String, value: String) {
|
||||
mmkv.encode(key, value)
|
||||
}
|
||||
|
||||
fun putStringAsync(key: String, value: String) {
|
||||
mmkv.putString(key, value).apply()
|
||||
}
|
||||
|
||||
fun getString(key: String, defaultValue: String = ""): String {
|
||||
return mmkv.decodeString(key, defaultValue) ?: defaultValue
|
||||
}
|
||||
|
||||
fun putDouble(key: String, value: Double) {
|
||||
mmkv.encode(key, value)
|
||||
}
|
||||
|
||||
fun putDoubleAsync(key: String, value: Float) {
|
||||
mmkv.putFloat(key, value).apply()
|
||||
}
|
||||
|
||||
fun getDouble(key: String, defaultValue: Double = 0.00): Double {
|
||||
return mmkv.decodeDouble(key, defaultValue)
|
||||
}
|
||||
|
||||
fun putLong(key: String, value: Long) {
|
||||
mmkv.encode(key, value)
|
||||
}
|
||||
|
||||
fun putLongAsync(key: String, value: Long) {
|
||||
mmkv.putLong(key, value).apply()
|
||||
}
|
||||
|
||||
fun getLong(key: String, defaultValue: Long = 0L): Long {
|
||||
return mmkv.decodeLong(key, defaultValue)
|
||||
}
|
||||
|
||||
fun putBoolean(key: String, value: Boolean) {
|
||||
mmkv.encode(key, value)
|
||||
}
|
||||
|
||||
fun putBooleanAsync(key: String, value: Boolean) {
|
||||
mmkv.putBoolean(key, value).apply()
|
||||
}
|
||||
|
||||
fun getBoolean(key: String, defaultValue: Boolean = false): Boolean {
|
||||
return mmkv.decodeBool(key, defaultValue)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
mmkv.clear().apply()
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/**
|
||||
* dataStore 存入数据默认就是异步,没有同步方法
|
||||
* 取数据异步通过Flow实现
|
||||
*/
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||
|
||||
fun put(key: String, value: Any) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.bbgo.common_base.ext
|
||||
|
||||
// A generic class that contains data and status about loading this data.
|
||||
sealed class Resource<out T: Any> {
|
||||
data class Success<out T: Any>(val data: T) : Resource<T>()
|
||||
data class Loading(val nothing: Nothing? = null) : Resource<Nothing>()
|
||||
data class Error(val exception: Throwable) : Resource<Nothing>()
|
||||
|
||||
override fun toString(): String {
|
||||
return when (this) {
|
||||
is Success -> "Success[data=$data]"
|
||||
is Error -> "Error[exception=${exception}]"
|
||||
is Loading -> "Loading"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.bbgo.common_base.ext
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
/**
|
||||
* Created by xuhao on 2017/11/14.
|
||||
*/
|
||||
|
||||
fun Fragment.showToast(content: String): Toast {
|
||||
val toast = Toast.makeText(this.activity?.applicationContext, content, Toast.LENGTH_SHORT)
|
||||
toast.show()
|
||||
return toast
|
||||
}
|
||||
|
||||
fun Context.showToast(content: String): Toast {
|
||||
val toast = Toast.makeText(this.applicationContext, content, Toast.LENGTH_SHORT)
|
||||
toast.show()
|
||||
return toast
|
||||
}
|
||||
|
||||
|
||||
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
package com.bbgo.common_base.ext
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.RestrictTo
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.*
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Property Delegate of ViewBinding
|
||||
* <p>
|
||||
* Created by pengxr on 2/5/2021
|
||||
*/
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// ViewBindingProperty for Activity
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
@JvmName("viewBindingActivity")
|
||||
inline fun <V : ViewBinding> ComponentActivity.viewBinding(
|
||||
crossinline viewBinder: (View) -> V,
|
||||
crossinline viewProvider: (ComponentActivity) -> View = ::findRootView
|
||||
): ViewBindingProperty<ComponentActivity, V> = ActivityViewBindingProperty { activity: ComponentActivity ->
|
||||
viewBinder(viewProvider(activity))
|
||||
}
|
||||
|
||||
@JvmName("viewBindingActivity")
|
||||
inline fun <V : ViewBinding> ComponentActivity.viewBinding(
|
||||
crossinline viewBinder: (View) -> V,
|
||||
@IdRes viewBindingRootId: Int
|
||||
): ViewBindingProperty<ComponentActivity, V> = ActivityViewBindingProperty { activity: ComponentActivity ->
|
||||
viewBinder(activity.requireViewByIdCompat(viewBindingRootId))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// ViewBindingProperty for Fragment / DialogFragment
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@JvmName("viewBindingFragment")
|
||||
inline fun <F : Fragment, V : ViewBinding> Fragment.viewBinding(
|
||||
crossinline viewBinder: (View) -> V,
|
||||
crossinline viewProvider: (F) -> View = Fragment::requireView
|
||||
): ViewBindingProperty<F, V> = when (this) {
|
||||
is DialogFragment -> DialogFragmentViewBindingProperty { fragment: F ->
|
||||
viewBinder(viewProvider(fragment))
|
||||
} as ViewBindingProperty<F, V>
|
||||
else -> FragmentViewBindingProperty { fragment: F ->
|
||||
viewBinder(viewProvider(fragment))
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@JvmName("viewBindingFragment")
|
||||
inline fun <F : Fragment, V : ViewBinding> Fragment.viewBinding(
|
||||
crossinline viewBinder: (View) -> V,
|
||||
@IdRes viewBindingRootId: Int
|
||||
): ViewBindingProperty<F, V> = when (this) {
|
||||
is DialogFragment -> viewBinding(viewBinder) { fragment: DialogFragment ->
|
||||
fragment.getRootView(viewBindingRootId)
|
||||
} as ViewBindingProperty<F, V>
|
||||
else -> viewBinding(viewBinder) { fragment: F ->
|
||||
fragment.requireView().requireViewByIdCompat(viewBindingRootId)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// ViewBindingProperty for ViewGroup
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
@JvmName("viewBindingViewGroup")
|
||||
inline fun <V : ViewBinding> ViewGroup.viewBinding(
|
||||
crossinline viewBinder: (View) -> V,
|
||||
crossinline viewProvider: (ViewGroup) -> View = { this }
|
||||
): ViewBindingProperty<ViewGroup, V> = LazyViewBindingProperty { viewGroup: ViewGroup ->
|
||||
viewBinder(viewProvider(viewGroup))
|
||||
}
|
||||
|
||||
@JvmName("viewBindingViewGroup")
|
||||
inline fun <V : ViewBinding> ViewGroup.viewBinding(
|
||||
crossinline viewBinder: (View) -> V,
|
||||
@IdRes viewBindingRootId: Int
|
||||
): ViewBindingProperty<ViewGroup, V> = LazyViewBindingProperty { viewGroup: ViewGroup ->
|
||||
viewBinder(viewGroup.requireViewByIdCompat(viewBindingRootId))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// ViewBindingProperty for RecyclerView#ViewHolder
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
@JvmName("viewBindingViewHolder")
|
||||
inline fun <V : ViewBinding> RecyclerView.ViewHolder.viewBinding(
|
||||
crossinline viewBinder: (View) -> V,
|
||||
crossinline viewProvider: (RecyclerView.ViewHolder) -> View = RecyclerView.ViewHolder::itemView
|
||||
): ViewBindingProperty<RecyclerView.ViewHolder, V> = LazyViewBindingProperty { holder: RecyclerView.ViewHolder ->
|
||||
viewBinder(viewProvider(holder))
|
||||
}
|
||||
|
||||
@JvmName("viewBindingViewHolder")
|
||||
inline fun <V : ViewBinding> RecyclerView.ViewHolder.viewBinding(
|
||||
crossinline viewBinder: (View) -> V,
|
||||
@IdRes viewBindingRootId: Int
|
||||
): ViewBindingProperty<RecyclerView.ViewHolder, V> = LazyViewBindingProperty { holder: RecyclerView.ViewHolder ->
|
||||
viewBinder(holder.itemView.requireViewByIdCompat(viewBindingRootId))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// ViewBindingProperty
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
private const val TAG = "ViewBindingProperty"
|
||||
|
||||
interface ViewBindingProperty<in R : Any, out V : ViewBinding> : ReadOnlyProperty<R, V> {
|
||||
@MainThread
|
||||
fun clear()
|
||||
}
|
||||
|
||||
class LazyViewBindingProperty<in R : Any, out V : ViewBinding>(
|
||||
private val viewBinder: (R) -> V
|
||||
) : ViewBindingProperty<R, V> {
|
||||
|
||||
private var viewBinding: V? = null
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@MainThread
|
||||
override fun getValue(thisRef: R, property: KProperty<*>): V {
|
||||
// Already bound
|
||||
viewBinding?.let { return it }
|
||||
|
||||
return viewBinder(thisRef).also {
|
||||
this.viewBinding = it
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun clear() {
|
||||
viewBinding = null
|
||||
}
|
||||
}
|
||||
|
||||
abstract class LifecycleViewBindingProperty<in R : Any, out V : ViewBinding>(
|
||||
private val viewBinder: (R) -> V
|
||||
) : ViewBindingProperty<R, V> {
|
||||
|
||||
private var viewBinding: V? = null
|
||||
|
||||
protected abstract fun getLifecycleOwner(thisRef: R): LifecycleOwner
|
||||
|
||||
@SuppressLint("LogNotTimber")
|
||||
@MainThread
|
||||
override fun getValue(thisRef: R, property: KProperty<*>): V {
|
||||
// Already bound
|
||||
viewBinding?.let { return it }
|
||||
|
||||
val lifecycle = getLifecycleOwner(thisRef).lifecycle
|
||||
val viewBinding = viewBinder(thisRef)
|
||||
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
|
||||
Log.w(
|
||||
TAG, "Access to viewBinding after Lifecycle is destroyed or hasn't created yet. " +
|
||||
"The instance of viewBinding will be not cached."
|
||||
)
|
||||
// We can access to ViewBinding after Fragment.onDestroyView(), but don't save it to prevent memory leak
|
||||
} else {
|
||||
lifecycle.addObserver(ClearOnDestroyLifecycleObserver(this))
|
||||
this.viewBinding = viewBinding
|
||||
}
|
||||
return viewBinding
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun clear() {
|
||||
viewBinding = null
|
||||
}
|
||||
|
||||
private class ClearOnDestroyLifecycleObserver(
|
||||
private val property: LifecycleViewBindingProperty<*, *>
|
||||
) : DefaultLifecycleObserver {
|
||||
|
||||
private companion object {
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
mainHandler.post { property.clear() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FragmentViewBindingProperty<in F : Fragment, out V : ViewBinding>(
|
||||
viewBinder: (F) -> V
|
||||
) : LifecycleViewBindingProperty<F, V>(viewBinder) {
|
||||
|
||||
override fun getLifecycleOwner(thisRef: F): LifecycleOwner {
|
||||
try {
|
||||
return thisRef.viewLifecycleOwner
|
||||
} catch (ignored: IllegalStateException) {
|
||||
error("Fragment doesn't have view associated with it or the view has been destroyed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DialogFragmentViewBindingProperty<in F : DialogFragment, out V : ViewBinding>(
|
||||
viewBinder: (F) -> V
|
||||
) : LifecycleViewBindingProperty<F, V>(viewBinder) {
|
||||
|
||||
override fun getLifecycleOwner(thisRef: F): LifecycleOwner {
|
||||
return if (thisRef.showsDialog) {
|
||||
thisRef
|
||||
} else {
|
||||
try {
|
||||
thisRef.viewLifecycleOwner
|
||||
} catch (ignored: IllegalStateException) {
|
||||
error("Fragment doesn't have view associated with it or the view has been destroyed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// Utils
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||||
class ActivityViewBindingProperty<in A : ComponentActivity, out V : ViewBinding>(
|
||||
viewBinder: (A) -> V
|
||||
) : LifecycleViewBindingProperty<A, V>(viewBinder) {
|
||||
|
||||
override fun getLifecycleOwner(thisRef: A): LifecycleOwner {
|
||||
return thisRef
|
||||
}
|
||||
}
|
||||
|
||||
fun <V : View> View.requireViewByIdCompat(@IdRes id: Int): V {
|
||||
return ViewCompat.requireViewById(this, id)
|
||||
}
|
||||
|
||||
fun <V : View> Activity.requireViewByIdCompat(@IdRes id: Int): V {
|
||||
return ActivityCompat.requireViewById(this, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to find root view for ViewBinding in Activity
|
||||
*/
|
||||
fun findRootView(activity: Activity): View {
|
||||
val contentView = activity.findViewById<ViewGroup>(android.R.id.content)
|
||||
checkNotNull(contentView) { "Activity has no content view" }
|
||||
return when (contentView.childCount) {
|
||||
1 -> contentView.getChildAt(0)
|
||||
0 -> error("Content view has no children. Provide root view explicitly")
|
||||
else -> error("More than one child view found in Activity content view")
|
||||
}
|
||||
}
|
||||
|
||||
fun DialogFragment.getRootView(viewBindingRootId: Int): View {
|
||||
val dialog = checkNotNull(dialog) {
|
||||
"DialogFragment doesn't have dialog. Use viewBinding delegate after onCreateDialog"
|
||||
}
|
||||
val window = checkNotNull(dialog.window) { "Fragment's Dialog has no window" }
|
||||
return with(window.decorView) {
|
||||
if (viewBindingRootId != 0) requireViewByIdCompat(
|
||||
viewBindingRootId
|
||||
) else this
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.bbgo.common_base.glide
|
||||
|
||||
import android.content.Context
|
||||
import com.bbgo.common_base.glide.okhttp.OkHttpUrlLoader
|
||||
import com.bbgo.common_base.net.ServiceCreators
|
||||
import com.bbgo.common_base.util.FileUtils
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.GlideBuilder
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool
|
||||
import com.bumptech.glide.load.engine.cache.ExternalPreferredCacheDiskCacheFactory
|
||||
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
|
||||
import com.bumptech.glide.load.engine.cache.LruResourceCache
|
||||
import com.bumptech.glide.load.engine.cache.MemorySizeCalculator
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
@GlideModule
|
||||
class ImageAppModule : AppGlideModule() {
|
||||
|
||||
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
||||
//设置缓存大小为20mb
|
||||
// val memoryCacheSizeBytes = 1024 * 1024 * 20 // 20mb
|
||||
// //设置内存缓存大小
|
||||
// builder.setMemoryCache(LruResourceCache(memoryCacheSizeBytes.toLong()))
|
||||
val calculator = MemorySizeCalculator.Builder(context)
|
||||
.setMemoryCacheScreens(5.toFloat())
|
||||
.setBitmapPoolScreens(5.toFloat())
|
||||
.build()
|
||||
builder.setMemoryCache(LruResourceCache(calculator.memoryCacheSize.toLong()))
|
||||
builder.setBitmapPool(LruBitmapPool(calculator.bitmapPoolSize.toLong()))
|
||||
|
||||
if (FileUtils.isExistExternalStore()) {
|
||||
builder.setDiskCache(
|
||||
ExternalPreferredCacheDiskCacheFactory(context)
|
||||
)
|
||||
} else {
|
||||
builder.setDiskCache(
|
||||
InternalCacheDiskCacheFactory(context)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||
registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(
|
||||
ServiceCreators.httpClient))
|
||||
}
|
||||
override fun isManifestParsingEnabled(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.bbgo.common_base.glide;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class ProgressInterceptor implements Interceptor {
|
||||
|
||||
public static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();
|
||||
|
||||
//入注册下载监听
|
||||
public static void addListener(String url, ProgressListener listener) {
|
||||
LISTENER_MAP.put(url, listener);
|
||||
}
|
||||
|
||||
//取消注册下载监听
|
||||
public static void removeListener(String url) {
|
||||
LISTENER_MAP.remove(url);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request request = chain.request();
|
||||
Response response = chain.proceed(request);
|
||||
String url = request.url().toString();
|
||||
ResponseBody body = response.body();
|
||||
Response newResponse = response.newBuilder().body(new ProgressResponseBody(url, body)).build();
|
||||
return newResponse;
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.bbgo.common_base.glide;
|
||||
|
||||
public interface ProgressListener {
|
||||
void onProgress(int progress);
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package com.bbgo.common_base.glide;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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 ProgressResponseBody extends ResponseBody {
|
||||
|
||||
|
||||
private static final String TAG = ProgressResponseBody.class.getName();
|
||||
|
||||
private BufferedSource bufferedSource;
|
||||
|
||||
private ResponseBody responseBody;
|
||||
|
||||
private ProgressListener listener;
|
||||
|
||||
public ProgressResponseBody(String url, ResponseBody responseBody) {
|
||||
this.responseBody = responseBody;
|
||||
listener = ProgressInterceptor.LISTENER_MAP.get(url);
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return responseBody.contentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() {
|
||||
return responseBody.contentLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedSource source() {
|
||||
if (bufferedSource == null) {
|
||||
bufferedSource = Okio.buffer(new ProgressSource(responseBody.source()));
|
||||
}
|
||||
return bufferedSource;
|
||||
}
|
||||
|
||||
private class ProgressSource extends ForwardingSource {
|
||||
|
||||
long totalBytesRead = 0;
|
||||
|
||||
int currentProgress;
|
||||
|
||||
ProgressSource(Source source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long read(Buffer sink, long byteCount) throws IOException {
|
||||
long bytesRead = super.read(sink, byteCount);
|
||||
long fullLength = responseBody.contentLength();
|
||||
if (bytesRead == -1) {
|
||||
totalBytesRead = fullLength;
|
||||
} else {
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
int progress = (int) (100f * totalBytesRead / fullLength);
|
||||
Log.d(TAG, "download progress is " + progress);
|
||||
if (listener != null && progress != currentProgress) {
|
||||
listener.onProgress(progress);
|
||||
}
|
||||
if (listener != null && totalBytesRead == fullLength) {
|
||||
listener = null;
|
||||
}
|
||||
|
||||
currentProgress = progress;
|
||||
return bytesRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package com.bbgo.common_base.glide.okhttp;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.HttpException;
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
import com.bumptech.glide.util.ContentLengthInputStream;
|
||||
import com.bumptech.glide.util.Preconditions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/** Fetches an {@link InputStream} using the okhttp library. */
|
||||
public class OkHttpStreamFetcher implements DataFetcher<InputStream>, okhttp3.Callback {
|
||||
private static final String TAG = "OkHttpFetcher";
|
||||
private final Call.Factory client;
|
||||
private final GlideUrl url;
|
||||
private InputStream stream;
|
||||
private ResponseBody responseBody;
|
||||
private DataCallback<? super InputStream> callback;
|
||||
// call may be accessed on the main thread while the object is in use on other threads. All other
|
||||
// accesses to variables may occur on different threads, but only one at a time.
|
||||
private volatile Call call;
|
||||
|
||||
// Public API.
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public OkHttpStreamFetcher(Call.Factory client, GlideUrl url) {
|
||||
this.client = client;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadData(
|
||||
@NonNull Priority priority, @NonNull final DataCallback<? super InputStream> callback) {
|
||||
Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
|
||||
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
|
||||
String key = headerEntry.getKey();
|
||||
requestBuilder.addHeader(key, headerEntry.getValue());
|
||||
}
|
||||
Request request = requestBuilder.build();
|
||||
this.callback = callback;
|
||||
|
||||
call = client.newCall(request);
|
||||
call.enqueue(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "OkHttp failed to obtain result", e);
|
||||
}
|
||||
|
||||
callback.onLoadFailed(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
responseBody = response.body();
|
||||
if (response.isSuccessful()) {
|
||||
long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
|
||||
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
|
||||
callback.onDataReady(stream);
|
||||
} else {
|
||||
callback.onLoadFailed(new HttpException(response.message(), response.code()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
try {
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignored
|
||||
}
|
||||
if (responseBody != null) {
|
||||
responseBody.close();
|
||||
}
|
||||
callback = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
Call local = call;
|
||||
if (local != null) {
|
||||
local.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Class<InputStream> getDataClass() {
|
||||
return InputStream.class;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DataSource getDataSource() {
|
||||
return DataSource.REMOTE;
|
||||
}
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.bbgo.common_base.glide.okhttp;
|
||||
|
||||
//
|
||||
// Source code recreated from a .class file by IntelliJ IDEA
|
||||
// (powered by FernFlower decompiler)
|
||||
//
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.load.Options;
|
||||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
import com.bumptech.glide.load.model.ModelLoader;
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory;
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
|
||||
private final okhttp3.Call.Factory client;
|
||||
|
||||
public OkHttpUrlLoader(@NonNull okhttp3.Call.Factory client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public boolean handles(@NonNull GlideUrl url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height, @NonNull Options options) {
|
||||
return new LoadData(model, new OkHttpStreamFetcher(this.client, model));
|
||||
}
|
||||
|
||||
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
|
||||
private final okhttp3.Call.Factory client;
|
||||
|
||||
public Factory(@NonNull okhttp3.Call.Factory client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
|
||||
return new OkHttpUrlLoader(this.client);
|
||||
}
|
||||
|
||||
public void teardown() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.bbgo.common_base.interceptor
|
||||
|
||||
import android.content.Context
|
||||
import com.alibaba.android.arouter.facade.Postcard
|
||||
import com.alibaba.android.arouter.facade.annotation.Interceptor
|
||||
import com.alibaba.android.arouter.facade.callback.InterceptorCallback
|
||||
import com.alibaba.android.arouter.facade.template.IInterceptor
|
||||
import com.bbgo.common_base.constants.RouterPath
|
||||
import com.bbgo.common_base.util.AppUtil
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* 该拦截器的作用是:统一页面跳转时,判断用户是否已经登录,
|
||||
* 将业务层判断用户是否登录的逻辑统一到这里,业务层就不需要做if else判断了
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/9/17 2:25 下午
|
||||
*/
|
||||
@Interceptor(name = "login", priority = 1)
|
||||
class LoginInterceptor : IInterceptor {
|
||||
|
||||
/**
|
||||
* 该集合保存的是需要登录成功之后才跳转的页面,也就是有@RequireLogin注解的页面
|
||||
*/
|
||||
private var pageList = mutableListOf<String>()
|
||||
|
||||
|
||||
override fun init(context: Context?) {
|
||||
pageList.add("com.bbgo.module_compose.activity.ComposeActivity")
|
||||
}
|
||||
|
||||
|
||||
override fun process(postcard: Postcard, callback: InterceptorCallback) {
|
||||
// 判断哪些页面需要登录 (在整个应用中,有些页面需要登录,有些是不需要登录的)
|
||||
if (pageList.contains(postcard.destination.canonicalName)) {
|
||||
if (AppUtil.isLogin) { // 如果已经登录了,则默认不做任何处理
|
||||
callback.onContinue(postcard)
|
||||
} else { // 未登录,拦截
|
||||
callback.onInterrupt(null)
|
||||
}
|
||||
} else {
|
||||
callback.onContinue(postcard)
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.bbgo.common_base.interceptor
|
||||
|
||||
import com.alibaba.android.arouter.utils.ClassUtils
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import com.bbgo.common_base.util.AppUtil
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/9/18 9:37 上午
|
||||
*/
|
||||
class RefletionUtils {
|
||||
|
||||
companion object {
|
||||
|
||||
private val classList = mutableListOf<String>()
|
||||
|
||||
fun getRequireLoginPages(): List<String> {
|
||||
kotlin.runCatching {
|
||||
val clzList = ClassUtils.getFileNameByPackageName(BaseApplication.getContext(), "com.android.processor.apt")
|
||||
for(name in clzList) {
|
||||
if (!name.endsWith("RequireLogin")) {
|
||||
continue
|
||||
}
|
||||
val clz = Class.forName(name)
|
||||
val method = clz.getDeclaredMethod("getRequireLoginList")
|
||||
method.isAccessible = true
|
||||
val list = method.invoke(null) as List<String>
|
||||
if (list.isEmpty()) continue
|
||||
classList.addAll(list)
|
||||
}
|
||||
return classList
|
||||
}.onFailure {
|
||||
return emptyList()
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
fun getLoginStatus(): Boolean {
|
||||
kotlin.runCatching {
|
||||
val clzList = ClassUtils.getFileNameByPackageName(BaseApplication.getContext(), "com.android.processor.apt")
|
||||
for(name in clzList) {
|
||||
if (!name.endsWith("InjectLogin")) {
|
||||
continue
|
||||
}
|
||||
val clz = Class.forName(name)
|
||||
val method = clz.getDeclaredMethod("getInjectLoginClass")
|
||||
method.isAccessible = true
|
||||
val targetClzString = method.invoke(null) as String
|
||||
val targetClz = Class.forName(targetClzString)
|
||||
val field = targetClz.getDeclaredField("isLogin")
|
||||
field.isAccessible = true
|
||||
return field.getBoolean(targetClz)
|
||||
}
|
||||
return false
|
||||
}.onFailure {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.bbgo.common_base.net
|
||||
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/9/9 3:43 下午
|
||||
*/
|
||||
class ResponseCallAdapter<T>(
|
||||
private val responseType: Type
|
||||
) : CallAdapter<T, Flow<Response<T>>> {
|
||||
override fun adapt(call: Call<T>): Flow<Response<T>> {
|
||||
return flow {
|
||||
emit(
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
call.enqueue(object : Callback<T> {
|
||||
override fun onFailure(call: Call<T>, t: Throwable) {
|
||||
continuation.resumeWithException(t)
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<T>, response: Response<T>) {
|
||||
continuation.resume(response)
|
||||
}
|
||||
})
|
||||
continuation.invokeOnCancellation { call.cancel() }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun responseType() = responseType
|
||||
}
|
||||
|
||||
|
||||
class BodyCallAdapter<T>(private val responseType: Type) : CallAdapter<T, Flow<T>> {
|
||||
override fun adapt(call: Call<T>): Flow<T> {
|
||||
return flow {
|
||||
emit(
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
call.enqueue(object : Callback<T> {
|
||||
override fun onFailure(call: Call<T>, t: Throwable) {
|
||||
continuation.resumeWithException(t)
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<T>, response: Response<T>) {
|
||||
try {
|
||||
continuation.resume(response.body()!!)
|
||||
} catch (e: Exception) {
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
continuation.invokeOnCancellation { call.cancel() }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun responseType() = responseType
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.bbgo.common_base.net
|
||||
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import retrofit2.CallAdapter
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/9/9 3:44 下午
|
||||
*/
|
||||
class FlowCallAdapterFactory private constructor() : CallAdapter.Factory() {
|
||||
override fun get(
|
||||
returnType: Type,
|
||||
annotations: Array<Annotation>,
|
||||
retrofit: Retrofit
|
||||
): CallAdapter<*, *>? {
|
||||
if (getRawType(returnType) != Flow::class.java) {
|
||||
return null
|
||||
}
|
||||
check(returnType is ParameterizedType) { "Flow return type must be parameterized as Flow<Foo> or Flow<out Foo>" }
|
||||
val responseType = getParameterUpperBound(0, returnType)
|
||||
val rawFlowType = getRawType(responseType)
|
||||
return if (rawFlowType == Response::class.java) {
|
||||
check(responseType is ParameterizedType) { "Response must be parameterized as Response<Foo> or Response<out Foo>" }
|
||||
ResponseCallAdapter<Any>(
|
||||
getParameterUpperBound(
|
||||
0,
|
||||
responseType
|
||||
)
|
||||
)
|
||||
} else {
|
||||
BodyCallAdapter<Any>(responseType)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun create() = FlowCallAdapterFactory()
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package com.bbgo.common_base.net
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.TypeAdapterFactory
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 3/29/21 5:57 PM
|
||||
* description: 处理gson解析时类型不匹配或者空值问题 https://www.jianshu.com/p/d8dcc656a06e
|
||||
*/
|
||||
class GsonTypeAdapterFactory : TypeAdapterFactory {
|
||||
|
||||
override fun <T : Any?> create(gson: Gson?, type: TypeToken<T>?): TypeAdapter<T> {
|
||||
val adapter = gson?.getDelegateAdapter(this, type)
|
||||
return object : TypeAdapter<T>() {
|
||||
@Throws(IOException::class)
|
||||
override fun write(out: JsonWriter?, value: T) {
|
||||
adapter?.write(out, value)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun read(jr: JsonReader): T? {
|
||||
//gson 库会通过JsonReader对json对象的每个字段进项读取,当发现类型不匹配时抛出异常
|
||||
return try {
|
||||
adapter?.read(jr)
|
||||
} catch (e: Throwable) {
|
||||
//那么我们就在它抛出异常的时候进行处理,让它继续不中断接着往下读取其他的字段就好了
|
||||
consumeAll(jr)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun consumeAll(jr: JsonReader) {
|
||||
if (jr.hasNext()) {
|
||||
val peek: JsonToken = jr.peek()
|
||||
when {
|
||||
peek === JsonToken.STRING -> {
|
||||
jr.nextString()
|
||||
}
|
||||
peek === JsonToken.BEGIN_ARRAY -> {
|
||||
jr.beginArray()
|
||||
consumeAll(jr)
|
||||
jr.endArray()
|
||||
}
|
||||
peek === JsonToken.BEGIN_OBJECT -> {
|
||||
jr.beginObject()
|
||||
consumeAll(jr)
|
||||
jr.endObject()
|
||||
}
|
||||
peek === JsonToken.END_ARRAY -> {
|
||||
jr.endArray()
|
||||
}
|
||||
peek === JsonToken.END_OBJECT -> {
|
||||
jr.endObject()
|
||||
}
|
||||
peek === JsonToken.NUMBER -> {
|
||||
jr.nextString()
|
||||
}
|
||||
peek === JsonToken.BOOLEAN -> {
|
||||
jr.nextBoolean()
|
||||
}
|
||||
peek === JsonToken.NAME -> {
|
||||
jr.nextName()
|
||||
consumeAll(jr)
|
||||
}
|
||||
peek === JsonToken.NULL -> {
|
||||
jr.nextNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.bbgo.common_base.net
|
||||
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import com.bbgo.common_base.net.interceptor.BasicParamsInterceptor
|
||||
import com.bbgo.common_base.net.interceptor.CacheInterceptor
|
||||
import com.bbgo.common_base.net.interceptor.LoggingInterceptor
|
||||
import com.bbgo.common_base.net.interceptor.MultiBaseUrlInterceptor
|
||||
import com.franmontiel.persistentcookiejar.PersistentCookieJar
|
||||
import com.franmontiel.persistentcookiejar.cache.SetCookieCache
|
||||
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 3/29/21 5:46 PM
|
||||
* description: 服务创建类
|
||||
*/
|
||||
object ServiceCreators {
|
||||
|
||||
private const val BASE_URL = "https://wanandroid.com/"
|
||||
|
||||
private const val MAX_CACHE_SIZE: Long = 1024 * 1024 * 50 // 50M 的缓存大小
|
||||
//设置 请求的缓存的大小跟位置
|
||||
private val cacheFile = File(BaseApplication.getContext().cacheDir, "httpCache")
|
||||
private val cache = Cache(cacheFile, MAX_CACHE_SIZE)
|
||||
private val cookieJar = PersistentCookieJar(
|
||||
SetCookieCache(),
|
||||
SharedPrefsCookiePersistor(BaseApplication.getContext())
|
||||
)
|
||||
|
||||
val httpClient = OkHttpClient.Builder()
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.readTimeout(10, TimeUnit.SECONDS)
|
||||
.writeTimeout(20, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true)
|
||||
.addInterceptor(MultiBaseUrlInterceptor())
|
||||
.addInterceptor(LoggingInterceptor())
|
||||
.addInterceptor(CacheInterceptor())
|
||||
.cookieJar(cookieJar)
|
||||
.cache(cache)
|
||||
.addInterceptor(BasicParamsInterceptor())
|
||||
.build()
|
||||
|
||||
private val builder = Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.client(httpClient)
|
||||
.addCallAdapterFactory(FlowCallAdapterFactory.create())
|
||||
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().registerTypeAdapterFactory(
|
||||
GsonTypeAdapterFactory()
|
||||
).create()))
|
||||
|
||||
private val retrofit = builder.build()
|
||||
|
||||
// val service: HttpApiService by lazy { retrofit.create(HttpApiService::class.java) }
|
||||
|
||||
fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)
|
||||
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.bbgo.common_base.net.download
|
||||
|
||||
interface DownloadListener {
|
||||
fun onStart()
|
||||
fun onProgress(progress: Int, total: Float)
|
||||
fun onFinish(path: String, url: String)
|
||||
fun onError(msg: String?)
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package com.bbgo.common_base.net.download
|
||||
|
||||
import com.bbgo.common_base.net.ServiceCreators
|
||||
import com.bbgo.common_base.net.download.api.DownloadApiService
|
||||
import com.bbgo.common_base.util.log.Logs
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
object FileDownloader {
|
||||
|
||||
private val service = ServiceCreators.create(DownloadApiService::class.java)
|
||||
|
||||
private var url: String = ""
|
||||
private var path: String = ""
|
||||
private var listener: DownloadListener? = null
|
||||
private var downloadLength: Long = 0
|
||||
private var contentLength: Long = 0
|
||||
|
||||
fun create(url: String) : FileDownloader {
|
||||
FileDownloader.url = url
|
||||
return this
|
||||
}
|
||||
|
||||
fun setPath(path: String): FileDownloader {
|
||||
FileDownloader.path = path
|
||||
return this
|
||||
}
|
||||
|
||||
fun setListener(listener: DownloadListener): FileDownloader {
|
||||
FileDownloader.listener = listener
|
||||
return this
|
||||
}
|
||||
|
||||
suspend fun start() = withContext(Dispatchers.IO) {
|
||||
kotlin.runCatching {
|
||||
listener?.onStart()
|
||||
val responseBody = service.downloadFile(url)
|
||||
contentLength = responseBody.contentLength()
|
||||
val file = File(path)
|
||||
write(responseBody.byteStream(), file)
|
||||
listener?.onFinish(path, url)
|
||||
}.onFailure {
|
||||
listener?.onError(it.message)
|
||||
Logs.e(it, "download file fail, it's reason is ${it.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun write(inputStream: InputStream, file: File) {
|
||||
var fos: FileOutputStream? = null
|
||||
try {
|
||||
fos = FileOutputStream(file, true)
|
||||
val buffer = ByteArray(4096)
|
||||
var len: Int
|
||||
while (inputStream.read(buffer).also { len = it } != -1) {
|
||||
fos.write(buffer, 0, len)
|
||||
downloadLength += len
|
||||
notifyProgress()
|
||||
}
|
||||
fos.flush()
|
||||
} catch (e: IOException) {
|
||||
throw IOException(e.message)
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyProgress() {
|
||||
val progress = ((downloadLength.toFloat() / contentLength.toFloat()) * 100).toInt()
|
||||
listener?.onProgress(progress, contentLength.toFloat())
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.bbgo.common_base.net.download.api
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Streaming
|
||||
import retrofit2.http.Url
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 4/7/21 9:24 PM
|
||||
* description: http api
|
||||
*/
|
||||
interface DownloadApiService {
|
||||
|
||||
|
||||
@Streaming //添加这个注解用来下载大文件
|
||||
@GET
|
||||
suspend fun downloadFile(@Url fileUrl: String): ResponseBody
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.bbgo.common_base.net.interceptor
|
||||
|
||||
import android.os.Build
|
||||
import com.bbgo.common_base.ext.screenPixel
|
||||
import com.bbgo.common_base.util.AppUtil
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 2021/5/28 10:20 上午
|
||||
* description: todo
|
||||
*/
|
||||
class BasicParamsInterceptor : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
val originalHttpUrl = originalRequest.url
|
||||
val url = originalHttpUrl.newBuilder().apply {
|
||||
addQueryParameter("udid", AppUtil.getDeviceSerial())
|
||||
addQueryParameter("vc", AppUtil.eyepetizerVersionCode.toString())
|
||||
addQueryParameter("vn", AppUtil.eyepetizerVersionName)
|
||||
addQueryParameter("size", screenPixel())
|
||||
addQueryParameter("deviceModel", AppUtil.deviceModel)
|
||||
addQueryParameter("first_channel", AppUtil.deviceBrand)
|
||||
addQueryParameter("last_channel", AppUtil.deviceBrand)
|
||||
addQueryParameter("system_version_code", "${Build.VERSION.SDK_INT}")
|
||||
}.build()
|
||||
val request = originalRequest.newBuilder().url(url).method(originalRequest.method,
|
||||
originalRequest.body
|
||||
).build()
|
||||
return chain.proceed(request)
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.bbgo.common_base.net.interceptor
|
||||
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import com.bbgo.common_base.util.NetWorkUtil
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
|
||||
class CacheInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
var request = chain.request()
|
||||
if (!NetWorkUtil.isNetworkAvailable(BaseApplication.getContext())) {
|
||||
request = request.newBuilder()
|
||||
.cacheControl(CacheControl.FORCE_CACHE)
|
||||
.build()
|
||||
}
|
||||
val response = chain.proceed(request)
|
||||
if (NetWorkUtil.isNetworkAvailable(BaseApplication.getContext())) {
|
||||
val maxAge = 60 * 3
|
||||
// 有网络时 设置缓存超时时间0个小时 ,意思就是不读取缓存数据,只对get有用,post没有缓冲
|
||||
response.newBuilder()
|
||||
.header("Cache-Control", "public, max-age=$maxAge")
|
||||
.removeHeader("Retrofit")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
|
||||
.build()
|
||||
} else {
|
||||
// 无网络时,设置超时为4周 只对get有用,post没有缓冲
|
||||
val maxStale = 60 * 60 * 24 * 28
|
||||
response.newBuilder()
|
||||
.header("Cache-Control", "public, only-if-cached, max-stale=$maxStale")
|
||||
.removeHeader("nyn")
|
||||
.build()
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.bbgo.common_base.net.interceptor
|
||||
|
||||
import com.bbgo.common_base.util.log.Logs
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 2021/5/28 10:19 上午
|
||||
* description: todo
|
||||
*/
|
||||
class LoggingInterceptor : Interceptor {
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val t1 = System.nanoTime()
|
||||
Logs.d("Sending request: ${request.url} \n ${request.headers}")
|
||||
|
||||
val response = chain.proceed(request)
|
||||
|
||||
val t2 = System.nanoTime()
|
||||
Logs.d("Received response for ${response.request.url} in ${(t2 - t1) / 1e6} ms\n${response.headers}")
|
||||
return response
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "LoggingInterceptor"
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.bbgo.common_base.net.interceptor
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.bbgo.common_base.util.log.Logs
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
class MultiBaseUrlInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
//获取原始的request
|
||||
val originalRequest = chain.request()
|
||||
//获取原始的url
|
||||
val originalUrl = originalRequest.url
|
||||
//获取原始的request的创建者builder
|
||||
val builder = originalRequest.newBuilder()
|
||||
//获取原始的headers
|
||||
val headers = originalRequest.headers
|
||||
val baseUrl = headers["baseUrl"]//获取请求头里面设置的baseurl
|
||||
Logs.d("originalUrl=$originalUrl")
|
||||
Logs.d("baseUrl=$baseUrl")
|
||||
return if (!TextUtils.isEmpty(baseUrl)) {
|
||||
val baseHttpUrl = baseUrl?.toHttpUrlOrNull()//将其解析成httpurl
|
||||
//重建新的HttpUrl,需要重新设置的url部分
|
||||
val newHttpUrl = originalUrl.newBuilder()
|
||||
.scheme(baseHttpUrl?.scheme!!)
|
||||
.host(baseHttpUrl.host)
|
||||
.port(baseHttpUrl.port)
|
||||
.build()
|
||||
//获取处理后的新newRequest
|
||||
val newRequest = builder.url(newHttpUrl).build()
|
||||
Logs.d("newHttpUrl=${newRequest.url}")
|
||||
chain.proceed(newRequest)
|
||||
} else {
|
||||
chain.proceed(originalRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.bbgo.common_base.pool
|
||||
|
||||
import androidx.annotation.NonNull
|
||||
import com.bbgo.common_base.util.log.Logs
|
||||
import java.util.concurrent.ThreadFactory
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/9/24 4:13 下午
|
||||
*/
|
||||
class AppThreadFactory : ThreadFactory {
|
||||
private val TAG = "AppThreadFactory"
|
||||
private val threadNumber = AtomicInteger(1)
|
||||
private val group: ThreadGroup
|
||||
private val namePrefix: String
|
||||
override fun newThread(@NonNull runnable: Runnable?): Thread {
|
||||
val threadName = namePrefix + threadNumber.getAndIncrement()
|
||||
Logs.i("Thread production, name is [$threadName]")
|
||||
val thread = Thread(group, runnable, threadName, 0)
|
||||
if (thread.isDaemon) { //设为非后台线程
|
||||
thread.isDaemon = false
|
||||
}
|
||||
if (thread.priority != Thread.NORM_PRIORITY) { //优先级为normal
|
||||
thread.priority = Thread.NORM_PRIORITY
|
||||
}
|
||||
|
||||
// 捕获多线程处理中的异常
|
||||
thread.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { thread, ex ->
|
||||
Logs.i("Running task appeared exception! Thread [" + thread.name + "], because [" + ex.message + "]")
|
||||
}
|
||||
return thread
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val poolNumber = AtomicInteger(1)
|
||||
}
|
||||
|
||||
init {
|
||||
val s = System.getSecurityManager()
|
||||
group = if (s != null) s.threadGroup else Thread.currentThread().threadGroup
|
||||
namePrefix = "App task pool No." + poolNumber.getAndIncrement() + ", thread No."
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package com.bbgo.common_base.pool
|
||||
|
||||
import com.bbgo.common_base.util.log.Logs
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/9/24 4:10 下午
|
||||
*/
|
||||
class ThreadPoolUtils private constructor(
|
||||
corePoolSize: Int,
|
||||
maximumPoolSize: Int,
|
||||
keepAliveTime: Long,
|
||||
unit: TimeUnit,
|
||||
workQueue: BlockingQueue<Runnable>,
|
||||
threadFactory: ThreadFactory
|
||||
) :
|
||||
ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory,
|
||||
RejectedExecutionHandler { r, executor ->
|
||||
Logs.e("Task rejected, too many task!")
|
||||
}) {
|
||||
/*
|
||||
* 线程执行结束,顺便看一下有么有什么乱七八糟的异常
|
||||
*
|
||||
* @param r the runnable that has completed
|
||||
* @param t the exception that caused termination, or null if
|
||||
*/
|
||||
override fun afterExecute(r: Runnable?, t: Throwable?) {
|
||||
var throwable = t
|
||||
super.afterExecute(r, t)
|
||||
if (t == null && r is Future<*>) {
|
||||
try {
|
||||
(r as Future<*>).get()
|
||||
} catch (ce: CancellationException) {
|
||||
throwable = ce
|
||||
} catch (ee: ExecutionException) {
|
||||
throwable = ee.cause
|
||||
} catch (ie: InterruptedException) {
|
||||
Thread.currentThread().interrupt() // ignore/reset
|
||||
}
|
||||
}
|
||||
if (throwable != null) {
|
||||
Logs.w(
|
||||
throwable,
|
||||
"Running task appeared exception! Thread [${Thread.currentThread().name}]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "AppPoolExecutor"
|
||||
|
||||
// Thread args
|
||||
private val CPU_COUNT = Runtime.getRuntime().availableProcessors()
|
||||
private val INIT_THREAD_COUNT = CPU_COUNT + 1
|
||||
private val MAX_THREAD_COUNT = INIT_THREAD_COUNT
|
||||
private const val SURPLUS_THREAD_LIFE = 30L
|
||||
|
||||
@Volatile
|
||||
var instance: ThreadPoolUtils? = null
|
||||
get() {
|
||||
if (null == field) {
|
||||
synchronized(ThreadPoolUtils::class.java) {
|
||||
if (null == field) {
|
||||
field = ThreadPoolUtils(
|
||||
INIT_THREAD_COUNT,
|
||||
MAX_THREAD_COUNT,
|
||||
SURPLUS_THREAD_LIFE,
|
||||
TimeUnit.SECONDS,
|
||||
ArrayBlockingQueue(64),
|
||||
AppThreadFactory()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return field
|
||||
}
|
||||
private set
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2020. vipyinzhiwei <vipyinzhiwei@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.app.Activity
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* @Description: 管理应用程序中所有Activity。
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/11/09 7:22 下午
|
||||
*/
|
||||
object ActivityCollector {
|
||||
|
||||
private val activitys = Stack<WeakReference<Activity>>()
|
||||
|
||||
/**
|
||||
* 将Activity压入Application栈
|
||||
*
|
||||
* @param task 将要压入栈的Activity对象
|
||||
*/
|
||||
fun pushTask(task: WeakReference<Activity>?) {
|
||||
activitys.push(task)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将传入的Activity对象从栈中移除
|
||||
*
|
||||
* @param task
|
||||
*/
|
||||
fun removeTask(task: WeakReference<Activity>?) {
|
||||
activitys.remove(task)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定位置从栈中移除Activity
|
||||
*
|
||||
* @param taskIndex Activity栈索引
|
||||
*/
|
||||
fun removeTask(taskIndex: Int) {
|
||||
if (activitys.size > taskIndex) activitys.removeAt(taskIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将栈中Activity移除至栈顶
|
||||
*/
|
||||
fun removeToTop() {
|
||||
val end = activitys.size
|
||||
val start = 1
|
||||
for (i in end - 1 downTo start) {
|
||||
val mActivity = activitys[i].get()
|
||||
if (null != mActivity && !mActivity.isFinishing) {
|
||||
mActivity.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除全部(用于整个应用退出)
|
||||
*/
|
||||
fun removeAll() {
|
||||
for (task in activitys) {
|
||||
val mActivity = task.get()
|
||||
if (null != mActivity && !mActivity.isFinishing) {
|
||||
mActivity.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getTopActivity(): WeakReference<Activity> {
|
||||
return activitys[activitys.size - 1]
|
||||
}
|
||||
|
||||
}
|
||||
+494
@@ -0,0 +1,494 @@
|
||||
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import android.text.TextUtils
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.FileProvider
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import com.bbgo.common_base.ext.Prefs
|
||||
import com.bbgo.common_base.util.log.Logs
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* 应用程序全局的通用工具类,功能比较单一,经常被复用的功能,应该封装到此工具类当中,从而给全局代码提供方面的操作。
|
||||
*
|
||||
* @author wangyb
|
||||
* @since 17/2/18
|
||||
*/
|
||||
object AppUtil {
|
||||
|
||||
private var TAG = "AppUtil"
|
||||
|
||||
/**
|
||||
* 获取当前应用程序的包名。
|
||||
*
|
||||
* @return 当前应用程序的包名。
|
||||
*/
|
||||
val appPackage: String
|
||||
get() = BaseApplication.getContext().packageName
|
||||
|
||||
/**
|
||||
* 获取当前应用程序的名称。
|
||||
* @return 当前应用程序的名称。
|
||||
*/
|
||||
val appName: String
|
||||
get() = BaseApplication.getContext().resources.getString(BaseApplication.getContext().applicationInfo.labelRes)
|
||||
|
||||
/**
|
||||
* 获取当前应用程序的版本名。
|
||||
* @return 当前应用程序的版本名。
|
||||
*/
|
||||
val appVersionName: String
|
||||
get() = BaseApplication.getContext().packageManager.getPackageInfo(appPackage, 0).versionName
|
||||
|
||||
/**
|
||||
* 获取当前应用程序的版本号。
|
||||
* @return 当前应用程序的版本号。
|
||||
*/
|
||||
val appVersionCode: Long
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
BaseApplication.getContext().packageManager.getPackageInfo(appPackage, 0).longVersionCode
|
||||
} else {
|
||||
BaseApplication.getContext().packageManager.getPackageInfo(appPackage, 0).versionCode.toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取开眼应用程序的版本名。
|
||||
* @return 开眼当前应用程序的版本名。
|
||||
*/
|
||||
val eyepetizerVersionName: String
|
||||
get() = "6.3.01"
|
||||
|
||||
/**
|
||||
* 获取开眼应用程序的版本号。
|
||||
* @return 开眼当前应用程序的版本号。
|
||||
*/
|
||||
val eyepetizerVersionCode: Long
|
||||
get() = 6030012
|
||||
|
||||
/**
|
||||
* 获取设备的的型号,如果无法获取到,则返回Unknown。
|
||||
* @return 设备型号。
|
||||
*/
|
||||
val deviceModel: String
|
||||
get() {
|
||||
var deviceModel = Build.MODEL
|
||||
if (TextUtils.isEmpty(deviceModel)) {
|
||||
deviceModel = "unknown"
|
||||
}
|
||||
return deviceModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备的品牌,如果无法获取到,则返回Unknown。
|
||||
* @return 设备品牌,全部转换为小写格式。
|
||||
*/
|
||||
val deviceBrand: String
|
||||
get() {
|
||||
var deviceBrand = Build.BRAND
|
||||
if (TextUtils.isEmpty(deviceBrand)) {
|
||||
deviceBrand = "unknown"
|
||||
}
|
||||
return deviceBrand.toLowerCase(Locale.getDefault())
|
||||
}
|
||||
|
||||
private var deviceSerial: String? = null
|
||||
|
||||
/**
|
||||
* 获取设备的序列号。如果无法获取到设备的序列号,则会生成一个随机的UUID来作为设备的序列号,UUID生成之后会存入缓存,
|
||||
* 下次获取设备序列号的时候会优先从缓存中读取。
|
||||
* @return 设备的序列号。
|
||||
*/
|
||||
@SuppressLint("HardwareIds")
|
||||
fun getDeviceSerial(): String {
|
||||
if (deviceSerial == null) {
|
||||
var deviceId: String? = null
|
||||
// val appChannel = getApplicationMetaData("APP_CHANNEL")
|
||||
kotlin.runCatching {
|
||||
deviceId = Settings.Secure.getString(
|
||||
BaseApplication.getContext().contentResolver,
|
||||
Settings.Secure.ANDROID_ID
|
||||
)
|
||||
deviceId?.let {
|
||||
if (it.length < 255) {
|
||||
deviceSerial = deviceId
|
||||
return deviceSerial.toString()
|
||||
}
|
||||
}
|
||||
|
||||
}.onFailure {
|
||||
Logs.e(TAG, "get android_id with error", it)
|
||||
}
|
||||
|
||||
var uuid = Prefs.getString("uuid", "")
|
||||
if (uuid.isNotEmpty()) {
|
||||
deviceSerial = uuid
|
||||
return deviceSerial.toString()
|
||||
}
|
||||
uuid = UUID.randomUUID().toString().replace("-", "").uppercase(Locale.getDefault())
|
||||
Prefs.putString("uuid", uuid)
|
||||
deviceSerial = uuid
|
||||
return deviceSerial.toString()
|
||||
} else {
|
||||
return deviceSerial.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源文件中定义的字符串。
|
||||
*
|
||||
* @param resId
|
||||
* 字符串资源id
|
||||
* @return 字符串资源id对应的字符串内容。
|
||||
*/
|
||||
fun getString(resId: Int): String {
|
||||
return BaseApplication.getContext().resources.getString(resId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源文件中定义的字符串。
|
||||
*
|
||||
* @param resId
|
||||
* 字符串资源id
|
||||
* @return 字符串资源id对应的字符串内容。
|
||||
*/
|
||||
fun getDimension(resId: Int): Int {
|
||||
return BaseApplication.getContext().resources.getDimensionPixelOffset(resId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定资源名的资源id。
|
||||
*
|
||||
* @param name
|
||||
* 资源名
|
||||
* @param type
|
||||
* 资源类型
|
||||
* @return 指定资源名的资源id。
|
||||
*/
|
||||
fun getResourceId(name: String, type: String): Int {
|
||||
return BaseApplication.getContext().resources.getIdentifier(name, type, appPackage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AndroidManifest.xml文件中,<application>标签下的meta-data值。
|
||||
*
|
||||
* @param key
|
||||
* <application>标签下的meta-data健
|
||||
*/
|
||||
fun getApplicationMetaData(key: String): String? {
|
||||
var applicationInfo: ApplicationInfo? = null
|
||||
try {
|
||||
applicationInfo = BaseApplication.getContext().packageManager.getApplicationInfo(
|
||||
appPackage, PackageManager.GET_META_DATA)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Logs.e(e, e.message)
|
||||
}
|
||||
if (applicationInfo == null) return ""
|
||||
return applicationInfo.metaData.getString(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断某个应用是否安装。
|
||||
* @param packageName
|
||||
* 要检查是否安装的应用包名
|
||||
* @return 安装返回true,否则返回false。
|
||||
*/
|
||||
fun isInstalled(packageName: String): Boolean {
|
||||
val packageInfo: PackageInfo? = try {
|
||||
BaseApplication.getContext().packageManager.getPackageInfo(packageName, 0)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
}
|
||||
return packageInfo != null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前应用程序的图标。
|
||||
*/
|
||||
fun getAppIcon(): Drawable {
|
||||
val packageManager = BaseApplication.getContext().packageManager
|
||||
val applicationInfo = packageManager.getApplicationInfo(appPackage, 0)
|
||||
return packageManager.getApplicationIcon(applicationInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断手机是否安装了QQ。
|
||||
*/
|
||||
fun isQQInstalled() = isInstalled("com.tencent.mobileqq")
|
||||
|
||||
/**
|
||||
* 判断手机是否安装了微信。
|
||||
*/
|
||||
fun isWechatInstalled() = isInstalled("com.tencent.mm")
|
||||
|
||||
/**
|
||||
* 判断手机是否安装了微博。
|
||||
* */
|
||||
fun isWeiboInstalled() = isInstalled("com.sina.weibo")
|
||||
|
||||
var isLogin = false
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun isIgnoringBatteryOptimizations(context: Context): Boolean {
|
||||
var isIgnoring = false
|
||||
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
if (powerManager != null) {
|
||||
isIgnoring = powerManager.isIgnoringBatteryOptimizations(appPackage)
|
||||
}
|
||||
return isIgnoring
|
||||
}
|
||||
|
||||
/**
|
||||
* 将应用加入后台白名单,提高应用存活率
|
||||
*/
|
||||
@SuppressLint("BatteryLife")
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
fun requestIgnoreBatteryOptimizations(context: Context) {
|
||||
if (isIgnoringBatteryOptimizations(context)) {
|
||||
return
|
||||
}
|
||||
kotlin.runCatching {
|
||||
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
|
||||
intent.data = Uri.parse("package:$appPackage")
|
||||
context.startActivity(intent)
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun goToSetting(context: Context) {
|
||||
if (Build.BRAND == null) {
|
||||
return
|
||||
}
|
||||
when(Build.BRAND.lowercase(Locale.getDefault())) {
|
||||
"realme" -> goOPPOSetting(context)
|
||||
"oppo" -> goOPPOSetting(context)
|
||||
"huawei" -> goHuaWeiSetting(context)
|
||||
"honor" -> goHuaWeiSetting(context)
|
||||
"xiaomi" -> goXiaoMiSetting(context)
|
||||
"vivo" -> goVivoSetting(context)
|
||||
"meizu" -> goFlyeMeSetting(context)
|
||||
"samsung" -> goSamSungSetting(context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun goOPPOSetting(context: Context) {
|
||||
var intent = Intent()
|
||||
try {
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.putExtra("packageName", appPackage)
|
||||
val comp = ComponentName(
|
||||
"com.color.safecenter",
|
||||
"com.color.safecenter.permission.PermissionManagerActivity"
|
||||
)
|
||||
intent.component = comp
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.putExtra("pkg_name", context.packageName)
|
||||
intent.putExtra("class_name", "com.welab.notificationdemo.MainActivity")
|
||||
val comp = ComponentName(
|
||||
"com.coloros.notificationmanager",
|
||||
"com.coloros.notificationmanager.AppDetailPreferenceActivity"
|
||||
)
|
||||
intent.component = comp
|
||||
context.startActivity(intent)
|
||||
} catch (e1: Exception) {
|
||||
// 否则跳转到应用详情
|
||||
intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
val uri = Uri.fromParts("package", context.packageName, null)
|
||||
intent.data = uri
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun goVivoSetting(context: Context) {
|
||||
var localIntent: Intent
|
||||
try {
|
||||
if (Build.MODEL.contains("Y85") && !Build.MODEL.contains("Y85A") || Build.MODEL.contains(
|
||||
"vivo Y53L"
|
||||
)
|
||||
) {
|
||||
localIntent = Intent()
|
||||
localIntent.setClassName(
|
||||
"com.vivo.permissionmanager",
|
||||
"com.vivo.permissionmanager.activity.PurviewTabActivity"
|
||||
)
|
||||
localIntent.putExtra("packagename", context.packageName)
|
||||
localIntent.putExtra("tabId", "1")
|
||||
context.startActivity(localIntent)
|
||||
} else {
|
||||
localIntent = Intent()
|
||||
localIntent.setClassName(
|
||||
"com.vivo.permissionmanager",
|
||||
"com.vivo.permissionmanager.activity.SoftPermissionDetailActivity"
|
||||
)
|
||||
localIntent.action = "secure.intent.action.softPermissionDetail"
|
||||
localIntent.putExtra("packagename", context.packageName)
|
||||
context.startActivity(localIntent)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// 否则跳转到应用详情
|
||||
localIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
val uri = Uri.fromParts("package", context.packageName, null)
|
||||
localIntent.data = uri
|
||||
context.startActivity(localIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun goHuaWeiSetting(context: Context) {
|
||||
var componentName: ComponentName? = null
|
||||
val sdkVersion = Build.VERSION.SDK_INT
|
||||
try {
|
||||
val intent = Intent()
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
//跳自启动管理
|
||||
if (sdkVersion >= 28) { //9:已测试
|
||||
componentName =
|
||||
ComponentName.unflattenFromString("com.huawei.systemmanager/.startupmgr.ui.StartupNormalAppListActivity") //跳自启动管理
|
||||
} else if (sdkVersion >= 26) { //8:已测试
|
||||
componentName =
|
||||
ComponentName.unflattenFromString("com.huawei.systemmanager/.appcontrol.activity.StartupAppControlActivity")
|
||||
} else if (sdkVersion >= 23) { //7.6:已测试
|
||||
componentName =
|
||||
ComponentName.unflattenFromString("com.huawei.systemmanager/.startupmgr.ui.StartupNormalAppListActivity")
|
||||
} else if (sdkVersion >= 21) { //5
|
||||
componentName =
|
||||
ComponentName.unflattenFromString("com.huawei.systemmanager/com.huawei.permissionmanager.ui.MainActivity")
|
||||
}
|
||||
//componentName = new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity");//锁屏清理
|
||||
intent.component = componentName
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
//跳转失败
|
||||
// 否则跳转到应用详情
|
||||
val intentDetail = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
val uri = Uri.fromParts("package", context.packageName, null)
|
||||
intentDetail.data = uri
|
||||
context.startActivity(intentDetail)
|
||||
}
|
||||
}
|
||||
|
||||
private fun goXiaoMiSetting(context: Context) {
|
||||
try {
|
||||
// MIUI 8
|
||||
val localIntent = Intent("miui.intent.action.APP_PERM_EDITOR")
|
||||
localIntent.setClassName(
|
||||
"com.miui.securitycenter",
|
||||
"com.miui.permcenter.permissions.PermissionsEditorActivity"
|
||||
)
|
||||
localIntent.putExtra("extra_pkgname", context.packageName)
|
||||
context.startActivity(localIntent)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
// MIUI 5/6/7
|
||||
val localIntent = Intent("miui.intent.action.APP_PERM_EDITOR")
|
||||
localIntent.setClassName(
|
||||
"com.miui.securitycenter",
|
||||
"com.miui.permcenter.permissions.AppPermissionsEditorActivity"
|
||||
)
|
||||
localIntent.putExtra("extra_pkgname", context.packageName)
|
||||
context.startActivity(localIntent)
|
||||
} catch (e1: Exception) {
|
||||
// 否则跳转到应用详情
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
val uri = Uri.fromParts("package", context.packageName, null)
|
||||
intent.data = uri
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun goSamSungSetting(context: Context) {
|
||||
val intent = Intent()
|
||||
var componentName: ComponentName? = null
|
||||
try {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
//跳自启动管理
|
||||
componentName =
|
||||
ComponentName.unflattenFromString("com.samsung.android.sm/.app.dashboard.SmartManagerDashBoardActivity")
|
||||
intent.component = componentName
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
componentName = ComponentName(
|
||||
"com.samsung.android.sm_cn",
|
||||
"com.samsung.android.sm.ui.ram.AutoRunActivity"
|
||||
)
|
||||
intent.component = componentName
|
||||
context.startActivity(intent)
|
||||
} catch (e1: Exception) {
|
||||
//跳转失败
|
||||
// 否则跳转到应用详情
|
||||
val intentDetail = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
val uri = Uri.fromParts("package", context.packageName, null)
|
||||
intentDetail.data = uri
|
||||
context.startActivity(intentDetail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun goFlyeMeSetting(context: Context) {
|
||||
val intent = Intent()
|
||||
var componentName: ComponentName? = null
|
||||
try {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
//跳自启动管理
|
||||
componentName =
|
||||
ComponentName.unflattenFromString("com.meizu.safe/.permission.SmartBGActivity") //跳转到后台管理页面
|
||||
intent.component = componentName
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
componentName =
|
||||
ComponentName.unflattenFromString("com.meizu.safe/.permission.PermissionMainActivity") //跳转到手机管家
|
||||
intent.component = componentName
|
||||
context.startActivity(intent)
|
||||
} catch (e1: Exception) {
|
||||
//跳转失败
|
||||
// 否则跳转到应用详情
|
||||
val intentDetail = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
val uri = Uri.fromParts("package", context.packageName, null)
|
||||
intentDetail.data = uri
|
||||
context.startActivity(intentDetail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun installApk(context: Context, downloadApk: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
val file = File(downloadApk)
|
||||
Logs.d("安装路径==$downloadApk")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val apkUri: Uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
"$appPackage.fileprovider",
|
||||
file
|
||||
)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||
} else {
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
val uri = Uri.fromFile(file)
|
||||
intent.setDataAndType(uri, "application/vnd.android.package-archive")
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.annotation.IntRange
|
||||
|
||||
/**
|
||||
* @author chenxz
|
||||
* @date 2019/11/24
|
||||
* @desc ColorUtil
|
||||
*/
|
||||
object ColorUtil {
|
||||
|
||||
/**
|
||||
* 计算颜色
|
||||
*
|
||||
* @param color color值
|
||||
* @param alpha alpha值
|
||||
* @return 最终的状态栏颜色
|
||||
*/
|
||||
fun alphaColor(@ColorInt color: Int, @IntRange(from = 0, to = 255) alpha: Int): Int {
|
||||
val red = Color.red(color)
|
||||
val green = Color.green(color)
|
||||
val blue = Color.blue(color)
|
||||
return Color.argb(alpha, red, green, blue)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算颜色
|
||||
*
|
||||
* @param color color值
|
||||
* @param alpha alpha值[0-1]
|
||||
* @return 最终的状态栏颜色
|
||||
*/
|
||||
fun alphaColor(@ColorInt color: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float): Int {
|
||||
return alphaColor(color, (alpha * 255).toInt())
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据fraction值来计算当前的颜色
|
||||
*
|
||||
* @param colorFrom 起始颜色
|
||||
* @param colorTo 结束颜色
|
||||
* @param fraction 变量
|
||||
* @return 当前颜色
|
||||
*/
|
||||
fun changingColor(@ColorInt colorFrom: Int, @ColorInt colorTo: Int, @FloatRange(from = 0.0, to = 1.0) fraction: Float): Int {
|
||||
val redStart = Color.red(colorFrom)
|
||||
val blueStart = Color.blue(colorFrom)
|
||||
val greenStart = Color.green(colorFrom)
|
||||
val alphaStart = Color.alpha(colorFrom)
|
||||
|
||||
val redEnd = Color.red(colorTo)
|
||||
val blueEnd = Color.blue(colorTo)
|
||||
val greenEnd = Color.green(colorTo)
|
||||
val alphaEnd = Color.alpha(colorTo)
|
||||
|
||||
val redDifference = redEnd - redStart
|
||||
val blueDifference = blueEnd - blueStart
|
||||
val greenDifference = greenEnd - greenStart
|
||||
val alphaDifference = alphaEnd - alphaStart
|
||||
|
||||
val redCurrent = (redStart + fraction * redDifference).toInt()
|
||||
val blueCurrent = (blueStart + fraction * blueDifference).toInt()
|
||||
val greenCurrent = (greenStart + fraction * greenDifference).toInt()
|
||||
val alphaCurrent = (alphaStart + fraction * alphaDifference).toInt()
|
||||
|
||||
return Color.argb(alphaCurrent, redCurrent, greenCurrent, blueCurrent)
|
||||
}
|
||||
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.*
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
object DataStoreUtils {
|
||||
|
||||
/**
|
||||
* dataStore 存入数据默认就是异步,没有同步方法
|
||||
* 取数据异步通过Flow实现
|
||||
*/
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||
|
||||
/**
|
||||
* 写入数据默认就是异步
|
||||
*/
|
||||
suspend fun put(key: String, value: Any) {
|
||||
when(value) {
|
||||
is Int -> putInt(key, value)
|
||||
is Long -> putLong(key, value)
|
||||
is Float -> putFloat(key, value)
|
||||
is String -> putString(key, value)
|
||||
is Boolean -> putBoolean(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun putInt(key: String, value: Int) {
|
||||
BaseApplication.getContext().dataStore.edit { settings ->
|
||||
settings[intPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun putLong(key: String, value: Long) {
|
||||
BaseApplication.getContext().dataStore.edit { settings ->
|
||||
settings[longPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun putFloat(key: String, value: Float) {
|
||||
BaseApplication.getContext().dataStore.edit { settings ->
|
||||
settings[floatPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun putString(key: String, value: String) {
|
||||
BaseApplication.getContext().dataStore.edit { settings ->
|
||||
settings[stringPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun putBoolean(key: String, value: Boolean) {
|
||||
BaseApplication.getContext().dataStore.edit { settings ->
|
||||
settings[booleanPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
/**
|
||||
* 获取数据是同步还是异步由启动协程决定
|
||||
*/
|
||||
|
||||
fun getInt(key: String, defaultValue: Int) : Flow<Int> {
|
||||
return BaseApplication.getContext().dataStore.data
|
||||
.map { preferences ->
|
||||
preferences[intPreferencesKey(key)] ?: defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
fun getLong(key: String, defaultValue: Long) : Flow<Long> {
|
||||
return BaseApplication.getContext().dataStore.data
|
||||
.map { preferences ->
|
||||
preferences[longPreferencesKey(key)] ?: defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
fun getFloat(key: String, defaultValue: Float) : Flow<Float> {
|
||||
return BaseApplication.getContext().dataStore.data
|
||||
.map { preferences ->
|
||||
preferences[floatPreferencesKey(key)] ?: defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
fun getString(key: String, defaultValue: String) : Flow<String> {
|
||||
return BaseApplication.getContext().dataStore.data
|
||||
.map { preferences ->
|
||||
preferences[stringPreferencesKey(key)] ?: defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
fun getBoolean(key: String, defaultValue: Boolean) : Flow<Boolean> {
|
||||
return BaseApplication.getContext().dataStore.data
|
||||
.map { preferences ->
|
||||
preferences[booleanPreferencesKey(key)] ?: defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.text.TextUtils
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
||||
|
||||
/**
|
||||
* Created by chenxz on 2018/6/9.
|
||||
* 对话框辅助类,需要自己调用show方法
|
||||
*/
|
||||
object DialogUtil {
|
||||
|
||||
/**
|
||||
* 获取一个Dialog
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
fun getDialog(context: Context): AlertDialog.Builder {
|
||||
return AlertDialog.Builder(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个耗时的对话框 ProgressDialog
|
||||
*
|
||||
* @param context
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
fun getWaitDialog(context: Context, message: String): ProgressDialog {
|
||||
val waitDialog = ProgressDialog(context)
|
||||
if (!TextUtils.isEmpty(message)) {
|
||||
waitDialog.setMessage(message)
|
||||
}
|
||||
return waitDialog
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个信息对话框,注意需要自己手动调用show方法
|
||||
*
|
||||
* @param context
|
||||
* @param message
|
||||
* @param onClickListener
|
||||
* @return
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun getMessageDialog(context: Context, message: String,
|
||||
onClickListener: DialogInterface.OnClickListener? = null): AlertDialog.Builder {
|
||||
val builder = getDialog(context)
|
||||
builder.setMessage(message)
|
||||
builder.setPositiveButton("确定", onClickListener)
|
||||
return builder
|
||||
}
|
||||
|
||||
fun getConfirmDialog(context: Context, message: String,
|
||||
onClickListener: DialogInterface.OnClickListener): AlertDialog.Builder {
|
||||
val builder = getDialog(context)
|
||||
builder.setMessage(message)
|
||||
builder.setPositiveButton("确定", onClickListener)
|
||||
builder.setNegativeButton("取消", null)
|
||||
return builder
|
||||
}
|
||||
|
||||
fun getConfirmDialog(context: Context, message: String,
|
||||
onOKClickListener: DialogInterface.OnClickListener, onCancleClickListener: DialogInterface.OnClickListener): AlertDialog.Builder {
|
||||
val builder = getDialog(context)
|
||||
builder.setMessage(message)
|
||||
builder.setPositiveButton("确定", onOKClickListener)
|
||||
builder.setNegativeButton("取消", onCancleClickListener)
|
||||
return builder
|
||||
}
|
||||
|
||||
fun getSelectDialog(context: Context, title: String, arrays: Array<String>,
|
||||
onClickListener: DialogInterface.OnClickListener): AlertDialog.Builder {
|
||||
val builder = getDialog(context)
|
||||
builder.setItems(arrays, onClickListener)
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
builder.setTitle(title)
|
||||
}
|
||||
builder.setNegativeButton("取消", null)
|
||||
return builder
|
||||
}
|
||||
|
||||
fun getSelectDialog(context: Context, arrays: Array<String>,
|
||||
onClickListener: DialogInterface.OnClickListener): AlertDialog.Builder {
|
||||
return getSelectDialog(context, "", arrays, onClickListener)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个单选的对话框
|
||||
*
|
||||
* @param context
|
||||
* @param title
|
||||
* @param arrays
|
||||
* @param selectIndex
|
||||
* @param onClickListener
|
||||
* @param onOKClickListener
|
||||
* 点击确定的回调接口
|
||||
* @param onCancleClickListener
|
||||
* 点击取消的回调接口
|
||||
* @return
|
||||
*/
|
||||
fun getSingleChoiceDialog(context: Context, title: String, arrays: Array<String>,
|
||||
selectIndex: Int, onClickListener: DialogInterface.OnClickListener,
|
||||
onOKClickListener: DialogInterface.OnClickListener, onCancleClickListener: DialogInterface.OnClickListener? = null): AlertDialog.Builder {
|
||||
val builder = getDialog(context)
|
||||
builder.setSingleChoiceItems(arrays, selectIndex, onClickListener)
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
builder.setTitle(title)
|
||||
}
|
||||
builder.setPositiveButton("确定", onOKClickListener)
|
||||
builder.setNegativeButton("取消", onCancleClickListener)
|
||||
return builder
|
||||
}
|
||||
|
||||
fun getSingleChoiceDialog(context: Context, title: String, arrays: Array<String>,
|
||||
selectIndex: Int, onClickListener: DialogInterface.OnClickListener): AlertDialog.Builder {
|
||||
val builder = getDialog(context)
|
||||
builder.setSingleChoiceItems(arrays, selectIndex, onClickListener)
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
builder.setTitle(title)
|
||||
}
|
||||
builder.setPositiveButton("取消", null)
|
||||
return builder
|
||||
}
|
||||
|
||||
fun getSingleChoiceDialog(context: Context, arrays: Array<String>, selectIndex: Int,
|
||||
onClickListener: DialogInterface.OnClickListener, onOKClickListener: DialogInterface.OnClickListener,
|
||||
onCancleClickListener: DialogInterface.OnClickListener): AlertDialog.Builder {
|
||||
return getSingleChoiceDialog(context, "", arrays, selectIndex, onClickListener, onOKClickListener,
|
||||
onCancleClickListener)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个多选的对话框
|
||||
*
|
||||
* @param context
|
||||
* @param title
|
||||
* @param arrays
|
||||
* @param checkedItems
|
||||
* @param onMultiChoiceClickListener
|
||||
* @param onOKClickListener
|
||||
* 点击确定的回调接口
|
||||
* @param onCancleListener
|
||||
* 点击取消的回调接口
|
||||
* @return
|
||||
*/
|
||||
fun getMultiChoiceDialog(context: Context, title: String, arrays: Array<String>,
|
||||
checkedItems: BooleanArray, onMultiChoiceClickListener: DialogInterface.OnMultiChoiceClickListener,
|
||||
onOKClickListener: DialogInterface.OnClickListener, onCancleListener: DialogInterface.OnClickListener): AlertDialog.Builder {
|
||||
val builder = getDialog(context)
|
||||
builder.setMultiChoiceItems(arrays, checkedItems, onMultiChoiceClickListener)
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
builder.setTitle(title)
|
||||
}
|
||||
builder.setPositiveButton("确定", onOKClickListener)
|
||||
builder.setNegativeButton("取消", onCancleListener)
|
||||
return builder
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.os.Environment
|
||||
import android.os.Environment.MEDIA_MOUNTED
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import java.io.File
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
||||
class FileUtils {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* 外置存储卡的路径
|
||||
* /storage/emulated/0
|
||||
*/
|
||||
fun getExternalStorePath(): String? {
|
||||
return if (isExistExternalStore()) {
|
||||
Environment.getExternalStorageDirectory().absolutePath
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* 外置存储卡文件路径
|
||||
* /storage/emulated/0/Android/data/com.bbgo.wanandroid/files
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getExternalFilePath(): String? {
|
||||
return BaseApplication.getContext().getExternalFilesDir(null)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* 外置存储卡缓存的路径
|
||||
* /storage/emulated/0/Android/data/com.bbgo.wanandroid/cache
|
||||
*/
|
||||
fun getExternalCachePath(): String? {
|
||||
return if (isExistExternalStore()) {
|
||||
return BaseApplication.getContext().externalCacheDir?.absolutePath
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* 内置存储卡文件的路径
|
||||
* /data/user/0/com.bbgo.wanandroid/files
|
||||
*/
|
||||
fun getInternalFilePath(): String {
|
||||
return BaseApplication.getContext().filesDir.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* 内置存储卡缓存的路径
|
||||
* /data/user/0/com.bbgo.wanandroid/cache
|
||||
*/
|
||||
fun getInternalCachePath(): String {
|
||||
return BaseApplication.getContext().cacheDir.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先使用外置存储卡,没有则使用内部存储卡
|
||||
*/
|
||||
fun getStoreFilePath(): String? {
|
||||
return if (isExistExternalStore()) {
|
||||
getExternalFilePath()
|
||||
} else {
|
||||
getInternalFilePath()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先使用外置存储卡,没有则使用内部存储卡
|
||||
*/
|
||||
fun getStoreCachePath(): String? {
|
||||
return if (isExistExternalStore()) {
|
||||
getExternalCachePath()
|
||||
} else {
|
||||
getInternalCachePath()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定路径
|
||||
*/
|
||||
fun getFilePath(dir: String): String? {
|
||||
val filePath = if (isExistExternalStore()) {
|
||||
BaseApplication.getContext().getExternalFilesDir(dir)?.absolutePath
|
||||
} else {
|
||||
BaseApplication.getContext().filesDir.absolutePath + File.separator + dir
|
||||
}
|
||||
val file = File(filePath)
|
||||
if (!file.exists()) {
|
||||
file.mkdir()
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有外存卡
|
||||
*/
|
||||
fun isExistExternalStore(): Boolean {
|
||||
return Environment.getExternalStorageState() == MEDIA_MOUNTED
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化单位
|
||||
*/
|
||||
fun formatSize(size: Double): String {
|
||||
val kiloByte = size / 1024
|
||||
if (kiloByte < 1) {
|
||||
return "0KB"
|
||||
}
|
||||
val megaByte = kiloByte / 1024
|
||||
if (megaByte < 1) {
|
||||
val result1 = BigDecimal(java.lang.Double.toString(kiloByte))
|
||||
return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB"
|
||||
}
|
||||
val gigaByte = megaByte / 1024
|
||||
if (gigaByte < 1) {
|
||||
val result2 = BigDecimal(java.lang.Double.toString(megaByte))
|
||||
return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB"
|
||||
}
|
||||
val teraBytes = gigaByte / 1024
|
||||
if (teraBytes < 1) {
|
||||
val result3 = BigDecimal(java.lang.Double.toString(gigaByte))
|
||||
return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB"
|
||||
}
|
||||
val result4 = BigDecimal(teraBytes)
|
||||
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB"
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件名,不包含后缀
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 文件名
|
||||
*/
|
||||
fun getFileNameNotSuffix(file: File?): String {
|
||||
return if (file == null || !file.exists()) {
|
||||
""
|
||||
} else getStringNotSuffix(file.name)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串,不包含后缀
|
||||
*
|
||||
* @param str 字符串 适用于文件名
|
||||
* @return 文件名
|
||||
*/
|
||||
fun getStringNotSuffix(str: String): String {
|
||||
val indexOf = str.lastIndexOf(".")
|
||||
return str.substring(0, if (indexOf < 0) str.length - 1 else indexOf)
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.ImageView
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import com.bbgo.common_base.R
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
|
||||
/**
|
||||
* Created by chenxz on 2018/6/12.
|
||||
*/
|
||||
object ImageLoader {
|
||||
|
||||
// 1.开启无图模式 2.非WiFi环境 不加载图片
|
||||
private val isLoadImage = !SettingUtil.getIsNoPhotoMode() || NetWorkUtil.isWifi(BaseApplication.getContext())
|
||||
|
||||
/**
|
||||
* 加载图片
|
||||
* @param context
|
||||
* @param url
|
||||
* @param iv
|
||||
*/
|
||||
fun load(context: Context, url: String?, iv: ImageView) {
|
||||
if (isLoadImage) {
|
||||
val options = RequestOptions()
|
||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||
.placeholder(R.drawable.bg_placeholder)
|
||||
Glide.with(context)
|
||||
.load(url)
|
||||
.transition(DrawableTransitionOptions().crossFade())
|
||||
.apply(options)
|
||||
.into(iv)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/8/27 4:33 下午
|
||||
*/
|
||||
class MD5Utils {
|
||||
|
||||
companion object {
|
||||
//生成MD5
|
||||
fun getMD5(message: String): String {
|
||||
var md5 = ""
|
||||
try {
|
||||
val md = MessageDigest.getInstance("MD5") // 创建一个md5算法对象
|
||||
val messageByte = message.toByteArray(charset("UTF-8"))
|
||||
val md5Byte = md.digest(messageByte) // 获得MD5字节数组,16*8=128位
|
||||
md5 = bytesToHex(md5Byte) // 转换为16进制字符串
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return md5
|
||||
}
|
||||
|
||||
// 二进制转十六进制
|
||||
private fun bytesToHex(bytes: ByteArray): String {
|
||||
val hexStr = StringBuffer()
|
||||
var num: Int
|
||||
for (i in bytes.indices) {
|
||||
num = bytes[i].toInt()
|
||||
if (num < 0) {
|
||||
num += 256
|
||||
}
|
||||
if (num < 16) {
|
||||
hexStr.append("0")
|
||||
}
|
||||
hexStr.append(Integer.toHexString(num))
|
||||
}
|
||||
return hexStr.toString().uppercase(Locale.getDefault())
|
||||
}
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.telephony.TelephonyManager
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.NetworkInterface
|
||||
import java.net.SocketException
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* Created by chenxz on 2018/4/21.
|
||||
*/
|
||||
class NetWorkUtil {
|
||||
companion object {
|
||||
|
||||
var NET_CNNT_BAIDU_OK = 1 // NetworkAvailable
|
||||
var NET_CNNT_BAIDU_TIMEOUT = 2 // no NetworkAvailable
|
||||
var NET_NOT_PREPARE = 3 // Net no ready
|
||||
var NET_ERROR = 4 //net error
|
||||
private val TIMEOUT = 3000 // TIMEOUT
|
||||
/**
|
||||
* check NetworkAvailable
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
@JvmStatic
|
||||
fun isNetworkAvailable(context: Context): Boolean {
|
||||
val manager = context.applicationContext.getSystemService(
|
||||
Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val info = manager.activeNetworkInfo
|
||||
return !(null == info || !info.isAvailable)
|
||||
}
|
||||
|
||||
/**
|
||||
* check NetworkConnected
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
fun isNetworkConnected(context: Context): Boolean {
|
||||
val manager = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val info = manager.activeNetworkInfo
|
||||
return !(null == info || !info.isConnected)
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到ip地址
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getLocalIpAddress(): String {
|
||||
var ret = ""
|
||||
try {
|
||||
val en = NetworkInterface.getNetworkInterfaces()
|
||||
while (en.hasMoreElements()) {
|
||||
val enumIpAddress = en.nextElement().inetAddresses
|
||||
while (enumIpAddress.hasMoreElements()) {
|
||||
val netAddress = enumIpAddress.nextElement()
|
||||
if (!netAddress.isLoopbackAddress) {
|
||||
ret = netAddress.hostAddress.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex: SocketException) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ping "http://www.baidu.com"
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JvmStatic
|
||||
private fun pingNetWork(): Boolean {
|
||||
var result = false
|
||||
var httpUrl: HttpURLConnection? = null
|
||||
try {
|
||||
httpUrl = URL("http://www.baidu.com")
|
||||
.openConnection() as HttpURLConnection
|
||||
httpUrl.connectTimeout = TIMEOUT
|
||||
httpUrl.connect()
|
||||
result = true
|
||||
} catch (e: IOException) {
|
||||
} finally {
|
||||
if (null != httpUrl) {
|
||||
httpUrl.disconnect()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* check is3G
|
||||
*
|
||||
* @param context
|
||||
* @return boolean
|
||||
*/
|
||||
@JvmStatic
|
||||
fun is3G(context: Context): Boolean {
|
||||
val connectivityManager = context
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val activeNetInfo = connectivityManager.activeNetworkInfo
|
||||
return activeNetInfo != null && activeNetInfo.type == ConnectivityManager.TYPE_MOBILE
|
||||
}
|
||||
|
||||
/**
|
||||
* isWifi
|
||||
*
|
||||
* @param context
|
||||
* @return boolean
|
||||
*/
|
||||
@JvmStatic
|
||||
fun isWifi(context: Context): Boolean {
|
||||
val connectivityManager = context
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val activeNetInfo = connectivityManager.activeNetworkInfo
|
||||
return activeNetInfo != null && activeNetInfo.type == ConnectivityManager.TYPE_WIFI
|
||||
}
|
||||
|
||||
/**
|
||||
* is2G
|
||||
*
|
||||
* @param context
|
||||
* @return boolean
|
||||
*/
|
||||
@JvmStatic
|
||||
fun is2G(context: Context): Boolean {
|
||||
val connectivityManager = context
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val activeNetInfo = connectivityManager.activeNetworkInfo
|
||||
return activeNetInfo != null && (activeNetInfo.subtype == TelephonyManager.NETWORK_TYPE_EDGE
|
||||
|| activeNetInfo.subtype == TelephonyManager.NETWORK_TYPE_GPRS || activeNetInfo
|
||||
.subtype == TelephonyManager.NETWORK_TYPE_CDMA)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断MOBILE网络是否可用
|
||||
*/
|
||||
fun isMobile(context: Context?): Boolean {
|
||||
if (context != null) {
|
||||
//获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
|
||||
val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
//获取NetworkInfo对象
|
||||
val networkInfo = manager.activeNetworkInfo
|
||||
//判断NetworkInfo对象是否为空 并且类型是否为MOBILE
|
||||
if (null != networkInfo && networkInfo.type == ConnectivityManager.TYPE_MOBILE)
|
||||
return networkInfo.isAvailable
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
|
||||
|
||||
/**
|
||||
* Created by wangyb on 2018/4/21.
|
||||
*/
|
||||
object RomUtil {
|
||||
|
||||
internal object AvailableRomType {
|
||||
val MIUI = 1
|
||||
val FLYME = 2
|
||||
val ANDROID_NATIVE = 3
|
||||
val NA = 4
|
||||
}
|
||||
|
||||
fun isLightStatusBarAvailable(): Boolean {
|
||||
return isMIUIV6OrAbove() || isFlymeV4OrAbove() || isAndroidMOrAbove()
|
||||
}
|
||||
|
||||
fun getLightStatausBarAvailableRomType(): Int {
|
||||
if (isMIUIV6OrAbove()) {
|
||||
return AvailableRomType.MIUI
|
||||
}
|
||||
|
||||
if (isFlymeV4OrAbove()) {
|
||||
return AvailableRomType.FLYME
|
||||
}
|
||||
|
||||
return if (isAndroidMOrAbove()) {
|
||||
AvailableRomType.ANDROID_NATIVE
|
||||
} else AvailableRomType.NA
|
||||
|
||||
}
|
||||
|
||||
//Flyme V4的displayId格式为 [Flyme OS 4.x.x.xA]
|
||||
//Flyme V5的displayId格式为 [Flyme 5.x.x.x beta]
|
||||
private fun isFlymeV4OrAbove(): Boolean {
|
||||
val displayId = Build.DISPLAY
|
||||
if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) {
|
||||
val displayIdArray = displayId.split(" ".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
|
||||
for (temp in displayIdArray) {
|
||||
//版本号4以上,形如4.x.
|
||||
if (temp.matches("^[4-9]\\.(\\d+\\.)+\\S*".toRegex())) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//MIUI V6对应的versionCode是4
|
||||
//MIUI V7对应的versionCode是5
|
||||
private fun isMIUIV6OrAbove(): Boolean {
|
||||
val miuiVersionCodeStr = getSystemProperty("ro.miui.ui.version.code")
|
||||
if (!TextUtils.isEmpty(miuiVersionCodeStr)) {
|
||||
try {
|
||||
val miuiVersionCode = Integer.parseInt(miuiVersionCodeStr)
|
||||
if (miuiVersionCode >= 4) {
|
||||
return true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//Android Api 23以上
|
||||
private fun isAndroidMOrAbove(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
}
|
||||
|
||||
private fun getSystemProperty(propName: String): String? {
|
||||
val line: String
|
||||
var input: BufferedReader? = null
|
||||
try {
|
||||
val p = Runtime.getRuntime().exec("getprop $propName")
|
||||
input = BufferedReader(InputStreamReader(p.inputStream), 1024)
|
||||
line = input.readLine()
|
||||
input.close()
|
||||
} catch (ex: IOException) {
|
||||
return null
|
||||
} finally {
|
||||
if (input != null) {
|
||||
try {
|
||||
input.close()
|
||||
} catch (e: IOException) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.graphics.Color
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import com.bbgo.common_base.R
|
||||
import com.bbgo.common_base.ext.Prefs
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 2021/5/24 2:19 下午
|
||||
* description: todo
|
||||
*/
|
||||
object SettingUtil {
|
||||
|
||||
/**
|
||||
* 获取是否开启无图模式
|
||||
*/
|
||||
fun getIsNoPhotoMode(): Boolean {
|
||||
return Prefs.getBoolean("switch_noPhotoMode", false) //&& NetWorkUtil.isMobile(App.context)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取是否开启显示首页置顶文章,true: 不显示 false: 显示
|
||||
*/
|
||||
fun getIsShowTopArticle(): Boolean {
|
||||
return Prefs.getBoolean("switch_show_top", false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主题颜色
|
||||
*/
|
||||
fun getColor(): Int {
|
||||
val defaultColor = BaseApplication.getContext().resources.getColor(R.color.colorPrimary)
|
||||
val color = Prefs.getInt("color", defaultColor)
|
||||
return if (color != 0 && Color.alpha(color) != 255) {
|
||||
defaultColor
|
||||
} else color
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主题颜色
|
||||
*/
|
||||
fun setColor(color: Int) {
|
||||
Prefs.putInt("color", color)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取是否开启导航栏上色
|
||||
*/
|
||||
fun getNavBar(): Boolean {
|
||||
return Prefs.getBoolean("nav_bar", false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置夜间模式
|
||||
*/
|
||||
fun setIsNightMode(flag: Boolean) {
|
||||
Prefs.putBoolean("switch_nightMode", flag)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取是否开启夜间模式
|
||||
*/
|
||||
fun getIsNightMode(): Boolean {
|
||||
return Prefs.getBoolean("switch_nightMode", false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取是否开启自动切换夜间模式
|
||||
*/
|
||||
fun getIsAutoNightMode(): Boolean {
|
||||
return Prefs.getBoolean("auto_nightMode", false)
|
||||
}
|
||||
|
||||
fun getNightStartHour(): String {
|
||||
return Prefs.getString("night_startHour", "22")
|
||||
}
|
||||
|
||||
fun setNightStartHour(nightStartHour: String) {
|
||||
Prefs.putString("night_startHour", nightStartHour)
|
||||
}
|
||||
|
||||
fun getNightStartMinute(): String {
|
||||
return Prefs.getString("night_startMinute", "00")
|
||||
}
|
||||
|
||||
fun setNightStartMinute(nightStartMinute: String) {
|
||||
Prefs.putString("night_startMinute", nightStartMinute)
|
||||
}
|
||||
|
||||
fun getDayStartHour(): String {
|
||||
return Prefs.getString("day_startHour", "06")
|
||||
}
|
||||
|
||||
fun setDayStartHour(dayStartHour: String) {
|
||||
Prefs.putString("day_startHour", dayStartHour)
|
||||
}
|
||||
|
||||
fun getDayStartMinute(): String {
|
||||
return Prefs.getString("day_startMinute", "00")
|
||||
}
|
||||
|
||||
fun setDayStartMinute(dayStartMinute: String) {
|
||||
Prefs.putString("day_startMinute", dayStartMinute)
|
||||
}
|
||||
}
|
||||
+804
@@ -0,0 +1,804 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.IntRange
|
||||
import androidx.annotation.NonNull
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import com.bbgo.common_base.R
|
||||
|
||||
/**
|
||||
* Created by Jaeger on 16/2/14.
|
||||
*
|
||||
*
|
||||
* Email: chjie.jaeger@gmail.com
|
||||
* GitHub: https://github.com/laobie
|
||||
*/
|
||||
object StatusBarUtil {
|
||||
const val DEFAULT_STATUS_BAR_ALPHA = 112
|
||||
private val FAKE_STATUS_BAR_VIEW_ID = R.id.statusbarutil_fake_status_bar_view
|
||||
private val FAKE_TRANSLUCENT_VIEW_ID = R.id.statusbarutil_translucent_view
|
||||
private const val TAG_KEY_HAVE_SET_OFFSET = -123
|
||||
|
||||
/**
|
||||
* 设置状态栏颜色
|
||||
*
|
||||
* @param activity 需要设置的 activity
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
fun setColor(activity: Activity, @ColorInt color: Int) {
|
||||
setColor(activity, color, DEFAULT_STATUS_BAR_ALPHA)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏颜色
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param color 状态栏颜色值
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
*/
|
||||
fun setColor(
|
||||
activity: Activity,
|
||||
@ColorInt color: Int,
|
||||
@IntRange(from = 0, to = 255) statusBarAlpha: Int
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
activity.window.statusBarColor =
|
||||
calculateStatusColor(color, statusBarAlpha)
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
val decorView = activity.window.decorView as ViewGroup
|
||||
val fakeStatusBarView = decorView.findViewById<View>(
|
||||
FAKE_STATUS_BAR_VIEW_ID
|
||||
)
|
||||
if (fakeStatusBarView != null) {
|
||||
if (fakeStatusBarView.visibility == View.GONE) {
|
||||
fakeStatusBarView.visibility = View.VISIBLE
|
||||
}
|
||||
fakeStatusBarView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha))
|
||||
} else {
|
||||
decorView.addView(createStatusBarView(activity, color, statusBarAlpha))
|
||||
}
|
||||
setRootView(activity)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为滑动返回界面设置状态栏颜色
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
fun setColorForSwipeBack(activity: Activity, color: Int) {
|
||||
setColorForSwipeBack(activity, color, DEFAULT_STATUS_BAR_ALPHA)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为滑动返回界面设置状态栏颜色
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param color 状态栏颜色值
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
*/
|
||||
fun setColorForSwipeBack(
|
||||
activity: Activity, @ColorInt color: Int,
|
||||
@IntRange(from = 0, to = 255) statusBarAlpha: Int
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
val contentView = activity.findViewById<View>(android.R.id.content) as ViewGroup
|
||||
val rootView = contentView.getChildAt(0)
|
||||
val statusBarHeight = getStatusBarHeight(activity)
|
||||
if (rootView != null && rootView is CoordinatorLayout) {
|
||||
val coordinatorLayout: CoordinatorLayout = rootView as CoordinatorLayout
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
coordinatorLayout.fitsSystemWindows = false
|
||||
contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha))
|
||||
val isNeedRequestLayout = contentView.paddingTop < statusBarHeight
|
||||
if (isNeedRequestLayout) {
|
||||
contentView.setPadding(0, statusBarHeight, 0, 0)
|
||||
coordinatorLayout.post(Runnable { coordinatorLayout.requestLayout() })
|
||||
}
|
||||
} else {
|
||||
coordinatorLayout.setStatusBarBackgroundColor(
|
||||
calculateStatusColor(
|
||||
color,
|
||||
statusBarAlpha
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
contentView.setPadding(0, statusBarHeight, 0, 0)
|
||||
contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha))
|
||||
}
|
||||
setTransparentForWindow(activity)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏纯色 不加半透明效果
|
||||
*
|
||||
* @param activity 需要设置的 activity
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
fun setColorNoTranslucent(activity: Activity, @ColorInt color: Int) {
|
||||
setColor(activity, color, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏颜色(5.0以下无半透明效果,不建议使用)
|
||||
*
|
||||
* @param activity 需要设置的 activity
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
@Deprecated("")
|
||||
fun setColorDiff(activity: Activity, @ColorInt color: Int) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return
|
||||
}
|
||||
transparentStatusBar(activity)
|
||||
val contentView = activity.findViewById<View>(android.R.id.content) as ViewGroup
|
||||
// 移除半透明矩形,以免叠加
|
||||
val fakeStatusBarView = contentView.findViewById<View>(
|
||||
FAKE_STATUS_BAR_VIEW_ID
|
||||
)
|
||||
if (fakeStatusBarView != null) {
|
||||
if (fakeStatusBarView.visibility == View.GONE) {
|
||||
fakeStatusBarView.visibility = View.VISIBLE
|
||||
}
|
||||
fakeStatusBarView.setBackgroundColor(color)
|
||||
} else {
|
||||
contentView.addView(createStatusBarView(activity, color))
|
||||
}
|
||||
setRootView(activity)
|
||||
}
|
||||
|
||||
/**
|
||||
* 使状态栏半透明
|
||||
*
|
||||
*
|
||||
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
*/
|
||||
fun setTranslucent(activity: Activity) {
|
||||
setTranslucent(activity, DEFAULT_STATUS_BAR_ALPHA)
|
||||
}
|
||||
|
||||
/**
|
||||
* 使状态栏半透明
|
||||
*
|
||||
*
|
||||
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
*/
|
||||
fun setTranslucent(activity: Activity, @IntRange(from = 0, to = 255) statusBarAlpha: Int) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return
|
||||
}
|
||||
setTransparent(activity)
|
||||
addTranslucentView(activity, statusBarAlpha)
|
||||
}
|
||||
|
||||
/**
|
||||
* 针对根布局是 CoordinatorLayout, 使状态栏半透明
|
||||
*
|
||||
*
|
||||
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
*/
|
||||
fun setTranslucentForCoordinatorLayout(
|
||||
activity: Activity,
|
||||
@IntRange(from = 0, to = 255) statusBarAlpha: Int
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return
|
||||
}
|
||||
transparentStatusBar(activity)
|
||||
addTranslucentView(activity, statusBarAlpha)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏全透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
*/
|
||||
fun setTransparent(activity: Activity) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return
|
||||
}
|
||||
transparentStatusBar(activity)
|
||||
setRootView(activity)
|
||||
}
|
||||
|
||||
/**
|
||||
* 使状态栏透明(5.0以上半透明效果,不建议使用)
|
||||
*
|
||||
*
|
||||
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
*/
|
||||
@Deprecated("")
|
||||
fun setTranslucentDiff(activity: Activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
// 设置状态栏透明
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
setRootView(activity)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为DrawerLayout 布局设置状态栏变色
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
fun setColorForDrawerLayout(
|
||||
activity: Activity,
|
||||
drawerLayout: DrawerLayout,
|
||||
@ColorInt color: Int
|
||||
) {
|
||||
setColorForDrawerLayout(activity, drawerLayout, color, DEFAULT_STATUS_BAR_ALPHA)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为DrawerLayout 布局设置状态栏颜色,纯色
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
fun setColorNoTranslucentForDrawerLayout(
|
||||
activity: Activity,
|
||||
drawerLayout: DrawerLayout,
|
||||
@ColorInt color: Int
|
||||
) {
|
||||
setColorForDrawerLayout(activity, drawerLayout, color, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为DrawerLayout 布局设置状态栏变色
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
* @param color 状态栏颜色值
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
*/
|
||||
fun setColorForDrawerLayout(
|
||||
activity: Activity, drawerLayout: DrawerLayout, @ColorInt color: Int,
|
||||
@IntRange(from = 0, to = 255) statusBarAlpha: Int
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
activity.window.statusBarColor = Color.TRANSPARENT
|
||||
} else {
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
}
|
||||
// 生成一个状态栏大小的矩形
|
||||
// 添加 statusBarView 到布局中
|
||||
val contentLayout = drawerLayout.getChildAt(0) as ViewGroup
|
||||
val fakeStatusBarView = contentLayout.findViewById<View>(
|
||||
FAKE_STATUS_BAR_VIEW_ID
|
||||
)
|
||||
if (fakeStatusBarView != null) {
|
||||
if (fakeStatusBarView.visibility == View.GONE) {
|
||||
fakeStatusBarView.visibility = View.VISIBLE
|
||||
}
|
||||
fakeStatusBarView.setBackgroundColor(color)
|
||||
} else {
|
||||
contentLayout.addView(createStatusBarView(activity, color), 0)
|
||||
}
|
||||
// 内容布局不是 LinearLayout 时,设置padding top
|
||||
if (contentLayout !is LinearLayout && contentLayout.getChildAt(1) != null) {
|
||||
contentLayout.getChildAt(1)
|
||||
.setPadding(
|
||||
contentLayout.paddingLeft,
|
||||
getStatusBarHeight(activity) + contentLayout.paddingTop,
|
||||
contentLayout.paddingRight,
|
||||
contentLayout.paddingBottom
|
||||
)
|
||||
}
|
||||
// 设置属性
|
||||
setDrawerLayoutProperty(drawerLayout, contentLayout)
|
||||
addTranslucentView(activity, statusBarAlpha)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 DrawerLayout 属性
|
||||
*
|
||||
* @param drawerLayout DrawerLayout
|
||||
* @param drawerLayoutContentLayout DrawerLayout 的内容布局
|
||||
*/
|
||||
private fun setDrawerLayoutProperty(
|
||||
drawerLayout: DrawerLayout,
|
||||
drawerLayoutContentLayout: ViewGroup
|
||||
) {
|
||||
val drawer = drawerLayout.getChildAt(1) as ViewGroup
|
||||
drawerLayout.setFitsSystemWindows(false)
|
||||
drawerLayoutContentLayout.fitsSystemWindows = false
|
||||
drawerLayoutContentLayout.clipToPadding = true
|
||||
drawer.fitsSystemWindows = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 为DrawerLayout 布局设置状态栏变色(5.0以下无半透明效果,不建议使用)
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
@Deprecated("")
|
||||
fun setColorForDrawerLayoutDiff(
|
||||
activity: Activity,
|
||||
drawerLayout: DrawerLayout,
|
||||
@ColorInt color: Int
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
// 生成一个状态栏大小的矩形
|
||||
val contentLayout = drawerLayout.getChildAt(0) as ViewGroup
|
||||
val fakeStatusBarView = contentLayout.findViewById<View>(
|
||||
FAKE_STATUS_BAR_VIEW_ID
|
||||
)
|
||||
if (fakeStatusBarView != null) {
|
||||
if (fakeStatusBarView.visibility == View.GONE) {
|
||||
fakeStatusBarView.visibility = View.VISIBLE
|
||||
}
|
||||
fakeStatusBarView.setBackgroundColor(
|
||||
calculateStatusColor(
|
||||
color,
|
||||
DEFAULT_STATUS_BAR_ALPHA
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// 添加 statusBarView 到布局中
|
||||
contentLayout.addView(createStatusBarView(activity, color), 0)
|
||||
}
|
||||
// 内容布局不是 LinearLayout 时,设置padding top
|
||||
if (contentLayout !is LinearLayout && contentLayout.getChildAt(1) != null) {
|
||||
contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0)
|
||||
}
|
||||
// 设置属性
|
||||
setDrawerLayoutProperty(drawerLayout, contentLayout)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 DrawerLayout 布局设置状态栏透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
*/
|
||||
fun setTranslucentForDrawerLayout(activity: Activity, drawerLayout: DrawerLayout) {
|
||||
setTranslucentForDrawerLayout(activity, drawerLayout, DEFAULT_STATUS_BAR_ALPHA)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 DrawerLayout 布局设置状态栏透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
*/
|
||||
fun setTranslucentForDrawerLayout(
|
||||
activity: Activity, drawerLayout: DrawerLayout,
|
||||
@IntRange(from = 0, to = 255) statusBarAlpha: Int
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return
|
||||
}
|
||||
setTransparentForDrawerLayout(activity, drawerLayout)
|
||||
addTranslucentView(activity, statusBarAlpha)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 DrawerLayout 布局设置状态栏透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
*/
|
||||
fun setTransparentForDrawerLayout(activity: Activity, drawerLayout: DrawerLayout) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
activity.window.statusBarColor = Color.TRANSPARENT
|
||||
} else {
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
}
|
||||
val contentLayout = drawerLayout.getChildAt(0) as ViewGroup
|
||||
// 内容布局不是 LinearLayout 时,设置padding top
|
||||
if (contentLayout !is LinearLayout && contentLayout.getChildAt(1) != null) {
|
||||
contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0)
|
||||
}
|
||||
|
||||
// 设置属性
|
||||
setDrawerLayoutProperty(drawerLayout, contentLayout)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 DrawerLayout 布局设置状态栏透明(5.0以上半透明效果,不建议使用)
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
*/
|
||||
@Deprecated("")
|
||||
fun setTranslucentForDrawerLayoutDiff(activity: Activity, drawerLayout: DrawerLayout) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
// 设置状态栏透明
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
// 设置内容布局属性
|
||||
val contentLayout = drawerLayout.getChildAt(0) as ViewGroup
|
||||
contentLayout.fitsSystemWindows = true
|
||||
contentLayout.clipToPadding = true
|
||||
// 设置抽屉布局属性
|
||||
val vg = drawerLayout.getChildAt(1) as ViewGroup
|
||||
vg.fitsSystemWindows = false
|
||||
// 设置 DrawerLayout 属性
|
||||
drawerLayout.setFitsSystemWindows(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为头部是 ImageView 的界面设置状态栏全透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
fun setTransparentForImageView(activity: Activity, needOffsetView: View?) {
|
||||
setTranslucentForImageView(activity, 0, needOffsetView)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为头部是 ImageView 的界面设置状态栏透明(使用默认透明度)
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
fun setTranslucentForImageView(activity: Activity, needOffsetView: View?) {
|
||||
setTranslucentForImageView(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为头部是 ImageView 的界面设置状态栏透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
fun setTranslucentForImageView(
|
||||
activity: Activity, @IntRange(from = 0, to = 255) statusBarAlpha: Int,
|
||||
needOffsetView: View?
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
return
|
||||
}
|
||||
setTransparentForWindow(activity)
|
||||
addTranslucentView(activity, statusBarAlpha)
|
||||
if (needOffsetView != null) {
|
||||
val haveSetOffset = needOffsetView.getTag(TAG_KEY_HAVE_SET_OFFSET)
|
||||
if (haveSetOffset != null && haveSetOffset as Boolean) {
|
||||
return
|
||||
}
|
||||
val layoutParams = needOffsetView.layoutParams as MarginLayoutParams
|
||||
layoutParams.setMargins(
|
||||
layoutParams.leftMargin, layoutParams.topMargin + getStatusBarHeight(activity),
|
||||
layoutParams.rightMargin, layoutParams.bottomMargin
|
||||
)
|
||||
needOffsetView.setTag(TAG_KEY_HAVE_SET_OFFSET, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 fragment 头部是 ImageView 的设置状态栏透明
|
||||
*
|
||||
* @param activity fragment 对应的 activity
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
fun setTranslucentForImageViewInFragment(activity: Activity, needOffsetView: View?) {
|
||||
setTranslucentForImageViewInFragment(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 fragment 头部是 ImageView 的设置状态栏透明
|
||||
*
|
||||
* @param activity fragment 对应的 activity
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
fun setTransparentForImageViewInFragment(activity: Activity, needOffsetView: View?) {
|
||||
setTranslucentForImageViewInFragment(activity, 0, needOffsetView)
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 fragment 头部是 ImageView 的设置状态栏透明
|
||||
*
|
||||
* @param activity fragment 对应的 activity
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
fun setTranslucentForImageViewInFragment(
|
||||
activity: Activity, @IntRange(from = 0, to = 255) statusBarAlpha: Int,
|
||||
needOffsetView: View?
|
||||
) {
|
||||
setTranslucentForImageView(activity, statusBarAlpha, needOffsetView)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
clearPreviousSetting(activity)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏伪状态栏 View
|
||||
*
|
||||
* @param activity 调用的 Activity
|
||||
*/
|
||||
fun hideFakeStatusBarView(activity: Activity) {
|
||||
val decorView = activity.window.decorView as ViewGroup
|
||||
val fakeStatusBarView = decorView.findViewById<View>(FAKE_STATUS_BAR_VIEW_ID)
|
||||
if (fakeStatusBarView != null) {
|
||||
fakeStatusBarView.visibility = View.GONE
|
||||
}
|
||||
val fakeTranslucentView = decorView.findViewById<View>(
|
||||
FAKE_TRANSLUCENT_VIEW_ID
|
||||
)
|
||||
if (fakeTranslucentView != null) {
|
||||
fakeTranslucentView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
fun setLightMode(activity: Activity) {
|
||||
setMIUIStatusBarDarkIcon(activity, true)
|
||||
setMeizuStatusBarDarkIcon(activity, true)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
activity.window.decorView.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
fun setDarkMode(activity: Activity) {
|
||||
setMIUIStatusBarDarkIcon(activity, false)
|
||||
setMeizuStatusBarDarkIcon(activity, false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
activity.window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 MIUI V6 以上状态栏颜色
|
||||
*/
|
||||
private fun setMIUIStatusBarDarkIcon(@NonNull activity: Activity, darkIcon: Boolean) {
|
||||
val clazz: Class<out Window?> = activity.window.javaClass
|
||||
try {
|
||||
val layoutParams = Class.forName("android.view.MiuiWindowManager\$LayoutParams")
|
||||
val field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE")
|
||||
val darkModeFlag = field.getInt(layoutParams)
|
||||
val extraFlagField = clazz.getMethod(
|
||||
"setExtraFlags",
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
extraFlagField.invoke(activity.window, if (darkIcon) darkModeFlag else 0, darkModeFlag)
|
||||
} catch (e: Exception) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改魅族状态栏字体颜色 Flyme 4.0
|
||||
*/
|
||||
private fun setMeizuStatusBarDarkIcon(@NonNull activity: Activity, darkIcon: Boolean) {
|
||||
try {
|
||||
val lp = activity.window.attributes
|
||||
val darkFlag =
|
||||
WindowManager.LayoutParams::class.java.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON")
|
||||
val meizuFlags = WindowManager.LayoutParams::class.java.getDeclaredField("meizuFlags")
|
||||
darkFlag.isAccessible = true
|
||||
meizuFlags.isAccessible = true
|
||||
val bit = darkFlag.getInt(null)
|
||||
var value = meizuFlags.getInt(lp)
|
||||
value = if (darkIcon) {
|
||||
value or bit
|
||||
} else {
|
||||
value and bit.inv()
|
||||
}
|
||||
meizuFlags.setInt(lp, value)
|
||||
activity.window.attributes = lp
|
||||
} catch (e: Exception) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
private fun clearPreviousSetting(activity: Activity) {
|
||||
val decorView = activity.window.decorView as ViewGroup
|
||||
val fakeStatusBarView = decorView.findViewById<View>(FAKE_STATUS_BAR_VIEW_ID)
|
||||
if (fakeStatusBarView != null) {
|
||||
decorView.removeView(fakeStatusBarView)
|
||||
val rootView =
|
||||
(activity.findViewById<View>(android.R.id.content) as ViewGroup).getChildAt(0) as ViewGroup
|
||||
rootView.setPadding(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加半透明矩形条
|
||||
*
|
||||
* @param activity 需要设置的 activity
|
||||
* @param statusBarAlpha 透明值
|
||||
*/
|
||||
private fun addTranslucentView(
|
||||
activity: Activity,
|
||||
@IntRange(from = 0, to = 255) statusBarAlpha: Int
|
||||
) {
|
||||
val contentView = activity.findViewById<View>(android.R.id.content) as ViewGroup
|
||||
val fakeTranslucentView = contentView.findViewById<View>(
|
||||
FAKE_TRANSLUCENT_VIEW_ID
|
||||
)
|
||||
if (fakeTranslucentView != null) {
|
||||
if (fakeTranslucentView.visibility == View.GONE) {
|
||||
fakeTranslucentView.visibility = View.VISIBLE
|
||||
}
|
||||
fakeTranslucentView.setBackgroundColor(Color.argb(statusBarAlpha, 0, 0, 0))
|
||||
} else {
|
||||
contentView.addView(createTranslucentStatusBarView(activity, statusBarAlpha))
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 生成一个和状态栏大小相同的半透明矩形条
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param color 状态栏颜色值
|
||||
* @param alpha 透明值
|
||||
* @return 状态栏矩形条
|
||||
*/
|
||||
/**
|
||||
* 生成一个和状态栏大小相同的彩色矩形条
|
||||
*
|
||||
* @param activity 需要设置的 activity
|
||||
* @param color 状态栏颜色值
|
||||
* @return 状态栏矩形条
|
||||
*/
|
||||
private fun createStatusBarView(
|
||||
activity: Activity,
|
||||
@ColorInt color: Int,
|
||||
alpha: Int = 0
|
||||
): View {
|
||||
// 绘制一个和状态栏一样高的矩形
|
||||
val statusBarView = View(activity)
|
||||
val params = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
getStatusBarHeight(activity)
|
||||
)
|
||||
statusBarView.layoutParams = params
|
||||
statusBarView.setBackgroundColor(calculateStatusColor(color, alpha))
|
||||
statusBarView.id = FAKE_STATUS_BAR_VIEW_ID
|
||||
return statusBarView
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置根布局参数
|
||||
*/
|
||||
private fun setRootView(activity: Activity) {
|
||||
val parent = activity.findViewById<View>(android.R.id.content) as ViewGroup
|
||||
var i = 0
|
||||
val count = parent.childCount
|
||||
while (i < count) {
|
||||
val childView = parent.getChildAt(i)
|
||||
if (childView is ViewGroup) {
|
||||
childView.setFitsSystemWindows(true)
|
||||
childView.clipToPadding = true
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置透明
|
||||
*/
|
||||
private fun setTransparentForWindow(activity: Activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.window.statusBarColor = Color.TRANSPARENT
|
||||
activity.window
|
||||
.decorView.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
activity.window
|
||||
.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
|
||||
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使状态栏透明
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
private fun transparentStatusBar(activity: Activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
||||
activity.window.statusBarColor = Color.TRANSPARENT
|
||||
} else {
|
||||
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建半透明矩形 View
|
||||
*
|
||||
* @param alpha 透明值
|
||||
* @return 半透明 View
|
||||
*/
|
||||
private fun createTranslucentStatusBarView(activity: Activity, alpha: Int): View {
|
||||
// 绘制一个和状态栏一样高的矩形
|
||||
val statusBarView = View(activity)
|
||||
val params = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
getStatusBarHeight(activity)
|
||||
)
|
||||
statusBarView.layoutParams = params
|
||||
statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0))
|
||||
statusBarView.id = FAKE_TRANSLUCENT_VIEW_ID
|
||||
return statusBarView
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态栏高度
|
||||
*
|
||||
* @param context context
|
||||
* @return 状态栏高度
|
||||
*/
|
||||
private fun getStatusBarHeight(context: Context): Int {
|
||||
// 获得状态栏高度
|
||||
val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||
return context.resources.getDimensionPixelSize(resourceId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算状态栏颜色
|
||||
*
|
||||
* @param color color值
|
||||
* @param alpha alpha值
|
||||
* @return 最终的状态栏颜色
|
||||
*/
|
||||
private fun calculateStatusColor(@ColorInt color: Int, alpha: Int): Int {
|
||||
if (alpha == 0) {
|
||||
return color
|
||||
}
|
||||
val a = 1 - alpha / 255f
|
||||
var red = color shr 16 and 0xff
|
||||
var green = color shr 8 and 0xff
|
||||
var blue = color and 0xff
|
||||
red = (red * a + 0.5).toInt()
|
||||
green = (green * a + 0.5).toInt()
|
||||
blue = (blue * a + 0.5).toInt()
|
||||
return 0xff shl 24 or (red shl 16) or (green shl 8) or blue
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.bbgo.common_base.util
|
||||
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
|
||||
/**
|
||||
* @author chenxz
|
||||
* @date 2019/11/24
|
||||
* @desc StringUtil
|
||||
*/
|
||||
object StringUtil {
|
||||
|
||||
fun getString(stream: InputStream): String {
|
||||
val reader = BufferedReader(InputStreamReader(stream, "utf-8"))
|
||||
val sb = StringBuilder()
|
||||
var s: String? = reader.readLine()
|
||||
while (s != null) {
|
||||
sb.append(s).append("\n")
|
||||
s = reader.readLine()
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
}
|
||||
+843
@@ -0,0 +1,843 @@
|
||||
package com.bbgo.common_base.util;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 时间相关工具类
|
||||
*/
|
||||
public class TimeUtils {
|
||||
|
||||
private TimeUtils() {
|
||||
throw new UnsupportedOperationException("u can't instantiate me...");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>在工具类中经常使用到工具类的格式化描述,这个主要是一个日期的操作类,所以日志格式主要使用 SimpleDateFormat的定义格式.</p>
|
||||
* 格式的意义如下: 日期和时间模式 <br>
|
||||
* <p>日期和时间格式由日期和时间模式字符串指定。在日期和时间模式字符串中,未加引号的字母 'A' 到 'Z' 和 'a' 到 'z'
|
||||
* 被解释为模式字母,用来表示日期或时间字符串元素。文本可以使用单引号 (') 引起来,以免进行解释。"''"
|
||||
* 表示单引号。所有其他字符均不解释;只是在格式化时将它们简单复制到输出字符串,或者在分析时与输入字符串进行匹配。
|
||||
* </p>
|
||||
* 定义了以下模式字母(所有其他字符 'A' 到 'Z' 和 'a' 到 'z' 都被保留): <br>
|
||||
* <table border="1" cellspacing="1" cellpadding="1" summary="Chart shows pattern letters, date/time component,
|
||||
* presentation, and examples.">
|
||||
* <tr>
|
||||
* <th align="left">字母</th>
|
||||
* <th align="left">日期或时间元素</th>
|
||||
* <th align="left">表示</th>
|
||||
* <th align="left">示例</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>G</code></td>
|
||||
* <td>Era 标志符</td>
|
||||
* <td>Text</td>
|
||||
* <td><code>AD</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>y</code> </td>
|
||||
* <td>年 </td>
|
||||
* <td>Year </td>
|
||||
* <td><code>1996</code>; <code>96</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>M</code> </td>
|
||||
* <td>年中的月份 </td>
|
||||
* <td>Month </td>
|
||||
* <td><code>July</code>; <code>Jul</code>; <code>07</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>w</code> </td>
|
||||
* <td>年中的周数 </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>27</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>W</code> </td>
|
||||
* <td>月份中的周数 </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>2</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>D</code> </td>
|
||||
* <td>年中的天数 </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>189</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>d</code> </td>
|
||||
* <td>月份中的天数 </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>10</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>F</code> </td>
|
||||
* <td>月份中的星期 </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>2</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>E</code> </td>
|
||||
* <td>星期中的天数 </td>
|
||||
* <td>Text </td>
|
||||
* <td><code>Tuesday</code>; <code>Tue</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>a</code> </td>
|
||||
* <td>Am/pm 标记 </td>
|
||||
* <td>Text </td>
|
||||
* <td><code>PM</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>H</code> </td>
|
||||
* <td>一天中的小时数(0-23) </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>0</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>k</code> </td>
|
||||
* <td>一天中的小时数(1-24) </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>24</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>K</code> </td>
|
||||
* <td>am/pm 中的小时数(0-11) </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>0</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>h</code> </td>
|
||||
* <td>am/pm 中的小时数(1-12) </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>12</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>m</code> </td>
|
||||
* <td>小时中的分钟数 </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>30</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>s</code> </td>
|
||||
* <td>分钟中的秒数 </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>55</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>S</code> </td>
|
||||
* <td>毫秒数 </td>
|
||||
* <td>Number </td>
|
||||
* <td><code>978</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>z</code> </td>
|
||||
* <td>时区 </td>
|
||||
* <td>General time zone </td>
|
||||
* <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code> </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>Z</code> </td>
|
||||
* <td>时区 </td>
|
||||
* <td>RFC 822 time zone </td>
|
||||
* <td><code>-0800</code> </td>
|
||||
* </tr>
|
||||
* </table>
|
||||
* <pre>
|
||||
* HH:mm 15:44
|
||||
* h:mm a 3:44 下午
|
||||
* HH:mm z 15:44 CST
|
||||
* HH:mm Z 15:44 +0800
|
||||
* HH:mm zzzz 15:44 中国标准时间
|
||||
* HH:mm:ss 15:44:40
|
||||
* yyyy-MM-dd 2016-08-12
|
||||
* yyyy-MM-dd HH:mm 2016-08-12 15:44
|
||||
* yyyy-MM-dd HH:mm:ss 2016-08-12 15:44:40
|
||||
* yyyy-MM-dd HH:mm:ss zzzz 2016-08-12 15:44:40 中国标准时间
|
||||
* EEEE yyyy-MM-dd HH:mm:ss zzzz 星期五 2016-08-12 15:44:40 中国标准时间
|
||||
* yyyy-MM-dd HH:mm:ss.SSSZ 2016-08-12 15:44:40.461+0800
|
||||
* yyyy-MM-dd'T'HH:mm:ss.SSSZ 2016-08-12T15:44:40.461+0800
|
||||
* yyyy.MM.dd G 'at' HH:mm:ss z 2016.08.12 公元 at 15:44:40 CST
|
||||
* K:mm a 3:44 下午
|
||||
* EEE, MMM d, ''yy 星期五, 八月 12, '16
|
||||
* hh 'o''clock' a, zzzz 03 o'clock 下午, 中国标准时间
|
||||
* yyyyy.MMMMM.dd GGG hh:mm aaa 02016.八月.12 公元 03:44 下午
|
||||
* EEE, d MMM yyyy HH:mm:ss Z 星期五, 12 八月 2016 15:44:40 +0800
|
||||
* yyMMddHHmmssZ 160812154440+0800
|
||||
* yyyy-MM-dd'T'HH:mm:ss.SSSZ 2016-08-12T15:44:40.461+0800
|
||||
* EEEE 'DATE('yyyy-MM-dd')' 'TIME('HH:mm:ss')' zzzz 星期五 DATE(2016-08-12) TIME(15:44:40) 中国标准时间
|
||||
* </pre>
|
||||
* 注意:SimpleDateFormat不是线程安全的,线程安全需用{@code ThreadLocal<SimpleDateFormat>}
|
||||
*/
|
||||
public static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/**
|
||||
* 将时间戳转为时间字符串
|
||||
* <p>格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param millis 毫秒时间戳
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String millis2String(long millis) {
|
||||
return new SimpleDateFormat(DEFAULT_PATTERN, Locale.getDefault()).format(new Date(millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将时间戳转为时间字符串
|
||||
* <p>格式为pattern</p>
|
||||
*
|
||||
* @param millis 毫秒时间戳
|
||||
* @param pattern 时间格式
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String millis2String(long millis, String pattern) {
|
||||
return new SimpleDateFormat(pattern, Locale.getDefault()).format(new Date(millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将时间字符串转为时间戳
|
||||
* <p>time格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @return 毫秒时间戳
|
||||
*/
|
||||
public static long string2Millis(String time) {
|
||||
return string2Millis(time, DEFAULT_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将时间字符串转为时间戳
|
||||
* <p>time格式为pattern</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @param pattern 时间格式
|
||||
* @return 毫秒时间戳
|
||||
*/
|
||||
public static long string2Millis(String time, String pattern) {
|
||||
try {
|
||||
return new SimpleDateFormat(pattern, Locale.getDefault()).parse(time).getTime();
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将时间字符串转为Date类型
|
||||
* <p>time格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @return Date类型
|
||||
*/
|
||||
public static Date string2Date(String time) {
|
||||
return string2Date(time, DEFAULT_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将时间字符串转为Date类型
|
||||
* <p>time格式为pattern</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @param pattern 时间格式
|
||||
* @return Date类型
|
||||
*/
|
||||
public static Date string2Date(String time, String pattern) {
|
||||
return new Date(string2Millis(time, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Date类型转为时间字符串
|
||||
* <p>格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param date Date类型时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String date2String(Date date) {
|
||||
return date2String(date, DEFAULT_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Date类型转为时间字符串
|
||||
* <p>格式为pattern</p>
|
||||
*
|
||||
* @param date Date类型时间
|
||||
* @param pattern 时间格式
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String date2String(Date date, String pattern) {
|
||||
return new SimpleDateFormat(pattern, Locale.getDefault()).format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Date类型转为时间戳
|
||||
*
|
||||
* @param date Date类型时间
|
||||
* @return 毫秒时间戳
|
||||
*/
|
||||
public static long date2Millis(Date date) {
|
||||
return date.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将时间戳转为Date类型
|
||||
*
|
||||
* @param millis 毫秒时间戳
|
||||
* @return Date类型时间
|
||||
*/
|
||||
public static Date millis2Date(long millis) {
|
||||
return new Date(millis);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前毫秒时间戳
|
||||
*
|
||||
* @return 毫秒时间戳
|
||||
*/
|
||||
public static long getNowTimeMills() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间字符串
|
||||
* <p>格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String getNowTimeString() {
|
||||
return millis2String(System.currentTimeMillis(), DEFAULT_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间字符串
|
||||
* <p>格式为pattern</p>
|
||||
*
|
||||
* @param pattern 时间格式
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String getNowTimeString(String pattern) {
|
||||
return millis2String(System.currentTimeMillis(), pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前Date
|
||||
*
|
||||
* @return Date类型时间
|
||||
*/
|
||||
public static Date getNowTimeDate() {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是同一天
|
||||
*/
|
||||
public static boolean isSameDay(long first, long second) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTimeInMillis(first);
|
||||
Calendar calendar1 = Calendar.getInstance();
|
||||
calendar1.setTimeInMillis(second);
|
||||
return calendar.get(Calendar.YEAR) == calendar1.get(Calendar.YEAR) &&
|
||||
calendar.get(Calendar.DAY_OF_YEAR) == calendar1.get(Calendar.DAY_OF_YEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否闰年
|
||||
* <p>time格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @return {@code true}: 闰年<br>{@code false}: 平年
|
||||
*/
|
||||
public static boolean isLeapYear(String time) {
|
||||
return isLeapYear(string2Date(time, DEFAULT_PATTERN));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否闰年
|
||||
* <p>time格式为pattern</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @param pattern 时间格式
|
||||
* @return {@code true}: 闰年<br>{@code false}: 平年
|
||||
*/
|
||||
public static boolean isLeapYear(String time, String pattern) {
|
||||
return isLeapYear(string2Date(time, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否闰年
|
||||
*
|
||||
* @param date Date类型时间
|
||||
* @return {@code true}: 闰年<br>{@code false}: 平年
|
||||
*/
|
||||
public static boolean isLeapYear(Date date) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
int year = cal.get(Calendar.YEAR);
|
||||
return isLeapYear(year);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否闰年
|
||||
*
|
||||
* @param millis 毫秒时间戳
|
||||
* @return {@code true}: 闰年<br>{@code false}: 平年
|
||||
*/
|
||||
public static boolean isLeapYear(long millis) {
|
||||
return isLeapYear(millis2Date(millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否闰年
|
||||
*
|
||||
* @param year 年份
|
||||
* @return {@code true}: 闰年<br>{@code false}: 平年
|
||||
*/
|
||||
public static boolean isLeapYear(int year) {
|
||||
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星期
|
||||
* <p>time格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @return 星期
|
||||
*/
|
||||
public static String getWeek(String time) {
|
||||
return getWeek(string2Date(time, DEFAULT_PATTERN));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星期
|
||||
* <p>time格式为pattern</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @param pattern 时间格式
|
||||
* @return 星期
|
||||
*/
|
||||
public static String getWeek(String time, String pattern) {
|
||||
return getWeek(string2Date(time, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星期
|
||||
*
|
||||
* @param date Date类型时间
|
||||
* @return 星期
|
||||
*/
|
||||
public static String getWeek(Date date) {
|
||||
return new SimpleDateFormat("EEEE", Locale.getDefault()).format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星期
|
||||
*
|
||||
* @param millis 毫秒时间戳
|
||||
* @return 星期
|
||||
*/
|
||||
public static String getWeek(long millis) {
|
||||
return getWeek(new Date(millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星期
|
||||
* <p>注意:周日的Index才是1,周六为7</p>
|
||||
* <p>time格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @return 1...5
|
||||
*/
|
||||
public static int getWeekIndex(String time) {
|
||||
return getWeekIndex(string2Date(time, DEFAULT_PATTERN));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星期
|
||||
* <p>注意:周日的Index才是1,周六为7</p>
|
||||
* <p>time格式为pattern</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @param pattern 时间格式
|
||||
* @return 1...7
|
||||
*/
|
||||
public static int getWeekIndex(String time, String pattern) {
|
||||
return getWeekIndex(string2Date(time, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星期
|
||||
* <p>注意:周日的Index才是1,周六为7</p>
|
||||
*
|
||||
* @param date Date类型时间
|
||||
* @return 1...7
|
||||
*/
|
||||
public static int getWeekIndex(Date date) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
return cal.get(Calendar.DAY_OF_WEEK);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星期
|
||||
* <p>注意:周日的Index才是1,周六为7</p>
|
||||
*
|
||||
* @param millis 毫秒时间戳
|
||||
* @return 1...7
|
||||
*/
|
||||
public static int getWeekIndex(long millis) {
|
||||
return getWeekIndex(millis2Date(millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取月份中的第几周
|
||||
* <p>注意:国外周日才是新的一周的开始</p>
|
||||
* <p>time格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @return 1...5
|
||||
*/
|
||||
public static int getWeekOfMonth(String time) {
|
||||
return getWeekOfMonth(string2Date(time, DEFAULT_PATTERN));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取月份中的第几周
|
||||
* <p>注意:国外周日才是新的一周的开始</p>
|
||||
* <p>time格式为pattern</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @param pattern 时间格式
|
||||
* @return 1...5
|
||||
*/
|
||||
public static int getWeekOfMonth(String time, String pattern) {
|
||||
return getWeekOfMonth(string2Date(time, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取月份中的第几周
|
||||
* <p>注意:国外周日才是新的一周的开始</p>
|
||||
*
|
||||
* @param date Date类型时间
|
||||
* @return 1...5
|
||||
*/
|
||||
public static int getWeekOfMonth(Date date) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
return cal.get(Calendar.WEEK_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取月份中的第几周
|
||||
* <p>注意:国外周日才是新的一周的开始</p>
|
||||
*
|
||||
* @param millis 毫秒时间戳
|
||||
* @return 1...5
|
||||
*/
|
||||
public static int getWeekOfMonth(long millis) {
|
||||
return getWeekOfMonth(millis2Date(millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取年份中的第几周
|
||||
* <p>注意:国外周日才是新的一周的开始</p>
|
||||
* <p>time格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @return 1...54
|
||||
*/
|
||||
public static int getWeekOfYear(String time) {
|
||||
return getWeekOfYear(string2Date(time, DEFAULT_PATTERN));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取年份中的第几周
|
||||
* <p>注意:国外周日才是新的一周的开始</p>
|
||||
* <p>time格式为pattern</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @param pattern 时间格式
|
||||
* @return 1...54
|
||||
*/
|
||||
public static int getWeekOfYear(String time, String pattern) {
|
||||
return getWeekOfYear(string2Date(time, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取年份中的第几周
|
||||
* <p>注意:国外周日才是新的一周的开始</p>
|
||||
*
|
||||
* @param date Date类型时间
|
||||
* @return 1...54
|
||||
*/
|
||||
public static int getWeekOfYear(Date date) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
return cal.get(Calendar.WEEK_OF_YEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取年份中的第几周
|
||||
* <p>注意:国外周日才是新的一周的开始</p>
|
||||
*
|
||||
* @param millis 毫秒时间戳
|
||||
* @return 1...54
|
||||
*/
|
||||
public static int getWeekOfYear(long millis) {
|
||||
return getWeekOfYear(millis2Date(millis));
|
||||
}
|
||||
|
||||
private static final String[] CHINESE_ZODIAC = {"猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊"};
|
||||
|
||||
/**
|
||||
* 获取生肖
|
||||
* <p>time格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @return 生肖
|
||||
*/
|
||||
public static String getChineseZodiac(String time) {
|
||||
return getChineseZodiac(string2Date(time, DEFAULT_PATTERN));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生肖
|
||||
* <p>time格式为pattern</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @param pattern 时间格式
|
||||
* @return 生肖
|
||||
*/
|
||||
public static String getChineseZodiac(String time, String pattern) {
|
||||
return getChineseZodiac(string2Date(time, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生肖
|
||||
*
|
||||
* @param date Date类型时间
|
||||
* @return 生肖
|
||||
*/
|
||||
public static String getChineseZodiac(Date date) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
return CHINESE_ZODIAC[cal.get(Calendar.YEAR) % 12];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生肖
|
||||
*
|
||||
* @param millis 毫秒时间戳
|
||||
* @return 生肖
|
||||
*/
|
||||
public static String getChineseZodiac(long millis) {
|
||||
return getChineseZodiac(millis2Date(millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生肖
|
||||
*
|
||||
* @param year 年
|
||||
* @return 生肖
|
||||
*/
|
||||
public static String getChineseZodiac(int year) {
|
||||
return CHINESE_ZODIAC[year % 12];
|
||||
}
|
||||
|
||||
private static final String[] ZODIAC = {"水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "魔羯座"};
|
||||
private static final int[] ZODIAC_FLAGS = {20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22};
|
||||
|
||||
/**
|
||||
* 获取星座
|
||||
* <p>time格式为yyyy-MM-dd HH:mm:ss</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @return 生肖
|
||||
*/
|
||||
public static String getZodiac(String time) {
|
||||
return getZodiac(string2Date(time, DEFAULT_PATTERN));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星座
|
||||
* <p>time格式为pattern</p>
|
||||
*
|
||||
* @param time 时间字符串
|
||||
* @param pattern 时间格式
|
||||
* @return 生肖
|
||||
*/
|
||||
public static String getZodiac(String time, String pattern) {
|
||||
return getZodiac(string2Date(time, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星座
|
||||
*
|
||||
* @param date Date类型时间
|
||||
* @return 星座
|
||||
*/
|
||||
public static String getZodiac(Date date) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
int month = cal.get(Calendar.MONTH) + 1;
|
||||
int day = cal.get(Calendar.DAY_OF_MONTH);
|
||||
return getZodiac(month, day);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星座
|
||||
*
|
||||
* @param millis 毫秒时间戳
|
||||
* @return 星座
|
||||
*/
|
||||
public static String getZodiac(long millis) {
|
||||
return getZodiac(millis2Date(millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星座
|
||||
*
|
||||
* @param month 月
|
||||
* @param day 日
|
||||
* @return 星座
|
||||
*/
|
||||
public static String getZodiac(int month, int day) {
|
||||
return ZODIAC[day >= ZODIAC_FLAGS[month - 1]
|
||||
? month - 1
|
||||
: (month + 10) % 12];
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频播放毫秒值转成时间串
|
||||
*
|
||||
* @param second 当前毫秒值
|
||||
* @return 时间串
|
||||
*/
|
||||
public static String secondForTime(long second) {
|
||||
if (second == 0) {
|
||||
return "00:00";
|
||||
}
|
||||
|
||||
long seconds = second % 60;
|
||||
long minutes = (second / 60) % 60;
|
||||
long hours = second / 3600;
|
||||
|
||||
if (hours > 0) {
|
||||
return String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, seconds).toString();
|
||||
} else {
|
||||
return String.format(Locale.US, "%02d:%02d", minutes, seconds).toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频播放毫秒值转成时间串
|
||||
*
|
||||
* @param second 当前毫秒值
|
||||
* @return 时间串
|
||||
*/
|
||||
public static String secondForTime(String second) {
|
||||
long timeSecond = 0;
|
||||
try {
|
||||
timeSecond = Long.valueOf(second);
|
||||
} catch (NumberFormatException e) {
|
||||
return "00:00";
|
||||
}
|
||||
|
||||
return secondForTime(timeSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频播放毫秒值转成时间串
|
||||
*
|
||||
* @param millisecond 当前毫秒值
|
||||
* @return 时间串
|
||||
*/
|
||||
public static String stringForTime(long millisecond) {
|
||||
if (millisecond <= 0) {
|
||||
return "00:00";
|
||||
}
|
||||
int totalSeconds = (int) (millisecond / 1000);
|
||||
return secondForTime(totalSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当时时分
|
||||
*/
|
||||
public static String getCurHourMinute() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
return millis2String(calendar.getTime().getTime(), "HH:mm");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* long转文本时间格式
|
||||
* 00:00
|
||||
*/
|
||||
public static String timeParse(long duration) {
|
||||
String time = "";
|
||||
long minute = duration / 60000;
|
||||
long seconds = duration % 60000;
|
||||
long second = Math.round((float) seconds / 1000);
|
||||
if (minute < 10) {
|
||||
time += "0";
|
||||
}
|
||||
time += minute + ":";
|
||||
if (second < 10) {
|
||||
time += "0";
|
||||
}
|
||||
time += second;
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件名转换成时间
|
||||
*/
|
||||
public static String FileNameToTime(String nameStr) {
|
||||
String tmpStr = "";
|
||||
if (nameStr.length() > 0) {
|
||||
for (int i = 0; i < nameStr.length(); i++) {
|
||||
String tmp = "" + nameStr.charAt(i);
|
||||
if ((tmp).matches("[0-9]")) {
|
||||
tmpStr += tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tmpStr.length() < 14) { // 防止相机返回异常数据导致崩溃
|
||||
return "2000-01-01 01:01:01";
|
||||
}
|
||||
tmpStr = tmpStr.substring(0, 4) + "-" + tmpStr.substring(4, 6) + "-" + tmpStr.substring(6, 8) + " "
|
||||
+ tmpStr.substring(8, 10) + ":" + tmpStr.substring(10, 12) + ":" + tmpStr.substring(12, 14);
|
||||
return tmpStr;
|
||||
}
|
||||
|
||||
public static boolean isEvening() {
|
||||
return !isCurrentInTimeScope(7, 0, 19, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前系统时间是否在指定时间的范围内
|
||||
*
|
||||
* @param beginHour 开始小时,例如22
|
||||
* @param beginMin 开始小时的分钟数,例如30
|
||||
* @param endHour 结束小时,例如 8
|
||||
* @param endMin 结束小时的分钟数,例如0
|
||||
* @return true表示在范围内,否则false
|
||||
*/
|
||||
public static boolean isCurrentInTimeScope(int beginHour, int beginMin, int endHour, int endMin) {
|
||||
Calendar cal = Calendar.getInstance();// 当前日期
|
||||
int hour = cal.get(Calendar.HOUR_OF_DAY);// 获取小时
|
||||
int minute = cal.get(Calendar.MINUTE);// 获取分钟
|
||||
int minuteOfDay = hour * 60 + minute;// 从0:00分开是到目前为止的分钟数
|
||||
final int start = beginHour * 60 + beginMin;// 起始时间 00:20的分钟数
|
||||
final int end = endHour * 60 + endMin;// 结束时间 8:00的分钟数
|
||||
return (minuteOfDay >= start && minuteOfDay <= end);
|
||||
// if (minuteOfDay >= start && minuteOfDay <= end) {
|
||||
// System.out.println("当前时间-->白天");
|
||||
// } else {
|
||||
// System.out.println("当前时间-->夜里");
|
||||
// }
|
||||
// return true;
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
package com.bbgo.common_base.util.log
|
||||
|
||||
import com.bbgo.common_base.BuildConfig
|
||||
import timber.log.Timber
|
||||
|
||||
object Logs {
|
||||
|
||||
private var TAG = "WanAndroid"
|
||||
|
||||
init {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
} else {
|
||||
Timber.plant(ReleaseTree())
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun tag(tag: String) {
|
||||
TAG = tag
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun v(message: String?, vararg args: Any?) {
|
||||
Timber.tag(TAG).v(message, args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun v(tag: String = TAG, message: String?, vararg args: Any?) {
|
||||
Timber.tag(tag).v(message, args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun v(tag: String = TAG, t: Throwable?, message: String?, vararg args: Any?) {
|
||||
Timber.tag(tag).v(t, message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun v(t: Throwable?, message: String?, vararg args: Any?) {
|
||||
Timber.tag(TAG).v(t, message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun v(t: Throwable?) {
|
||||
Timber.tag(TAG).v(t)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun v(tag: String = TAG, t: Throwable?) {
|
||||
Timber.tag(tag).v(t)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun d(message: String?, vararg args: Any?) {
|
||||
Timber.tag(TAG).d(message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun d(tag: String = TAG, message: String?, vararg args: Any?) {
|
||||
Timber.tag(tag).d(message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun d(t: Throwable?, message: String?, vararg args: Any?) {
|
||||
Timber.tag(TAG).v(t, message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun d(tag: String = TAG, t: Throwable?, message: String?, vararg args: Any?) {
|
||||
Timber.tag(tag).v(t, message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun d(t: Throwable?) {
|
||||
Timber.tag(TAG).d(t)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun d(tag: String = TAG, t: Throwable?) {
|
||||
Timber.tag(tag).d(t)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun i(message: String?, vararg args: Any?) {
|
||||
Timber.tag(TAG).i(message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun i(tag: String = TAG, message: String?, vararg args: Any?) {
|
||||
Timber.tag(tag).i(message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun i(t: Throwable?, message: String?, vararg args: Any?) {
|
||||
Timber.tag(TAG).i(t, message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun i(tag: String = TAG, t: Throwable?, message: String?, vararg args: Any?) {
|
||||
Timber.tag(tag).i(t, message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun i(t: Throwable?) {
|
||||
Timber.tag(TAG).i(t)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun i(tag: String = TAG, t: Throwable?) {
|
||||
Timber.tag(tag).i(t)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun w(message: String?, vararg args: Any?) {
|
||||
Timber.tag(TAG).w(message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun w(tag: String = TAG, message: String?, vararg args: Any?) {
|
||||
Timber.tag(tag).w(message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun w(t: Throwable?, message: String?, vararg args: Any?) {
|
||||
Timber.tag(TAG).w(t, message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun w(tag: String = TAG, t: Throwable?, message: String?, vararg args: Any?) {
|
||||
Timber.tag(tag).w(t, message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun w(t: Throwable?) {
|
||||
Timber.tag(TAG).w(t)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun w(tag: String = TAG, t: Throwable?) {
|
||||
Timber.tag(tag).w(t)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun e(message: String?, vararg args: Any?) {
|
||||
Timber.tag(TAG).e(message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun e(tag: String = TAG, message: String?, vararg args: Any?) {
|
||||
Timber.tag(tag).e(message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun e(t: Throwable?, message: String?, vararg args: Any?) {
|
||||
Timber.tag(TAG).e(message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun e(tag: String = TAG, t: Throwable?, message: String?, vararg args: Any?) {
|
||||
Timber.tag(tag).e(message, *args)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun e(t: Throwable?) {
|
||||
Timber.tag(TAG).e(t)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun e(tag: String = TAG, t: Throwable?) {
|
||||
Timber.tag(tag).e(t)
|
||||
}
|
||||
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
package com.bbgo.common_base.util.log
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.bbgo.common_base.BaseApplication.Companion.getContext
|
||||
import com.bbgo.common_base.pool.ThreadPoolUtils
|
||||
import com.bbgo.common_base.util.FileUtils
|
||||
import com.bbgo.common_base.util.TimeUtils
|
||||
import timber.log.Timber
|
||||
import java.io.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* @Author:Wangyuebin
|
||||
* @Date:2022-09-22
|
||||
* @Description:
|
||||
*/
|
||||
class ReleaseTree : Timber.Tree() {
|
||||
|
||||
companion object {
|
||||
private const val LOG_DIR = "log"
|
||||
// 日志文件名日期格式
|
||||
private const val LOG_FILE_NAME_DATE_FORMAT = "yyyy-MM-dd"
|
||||
// 保存日志的天数
|
||||
private const val SAVE_LOG_DAYS = 7
|
||||
// 日志文件名
|
||||
private const val LOG_FILE_NAME_FORMAT = "%s.log"
|
||||
// 日志文件名称
|
||||
private var mLogFile: File? = null
|
||||
// 当前日期
|
||||
private var mCurrentDate: String? = null
|
||||
// 日志队列
|
||||
private val mLogQueue = LinkedBlockingQueue<String>()
|
||||
|
||||
/**
|
||||
* 初始化日志文件名
|
||||
*/
|
||||
private fun init() {
|
||||
mCurrentDate = TimeUtils.date2String(Date(), LOG_FILE_NAME_DATE_FORMAT)
|
||||
val logFileName = String.format(LOG_FILE_NAME_FORMAT, mCurrentDate)
|
||||
val logDir = FileUtils.getExternalFilePath() + File.separator + LOG_DIR
|
||||
val fileDir = File(logDir)
|
||||
if (!fileDir.exists()) {
|
||||
fileDir.mkdirs()
|
||||
}
|
||||
if (mLogFile == null) {
|
||||
mLogFile = File(logDir, logFileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 日志写入器
|
||||
private val mLogWriter: LogWriter = LogWriter()
|
||||
|
||||
init {
|
||||
init()
|
||||
}
|
||||
|
||||
override fun isLoggable(tag: String?, priority: Int): Boolean {
|
||||
return priority >= Log.INFO
|
||||
}
|
||||
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
putLogQueue(priority, tag, message, t)
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存日志
|
||||
*/
|
||||
private fun putLogQueue(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
val logContent = StringBuilder()
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
||||
logContent.append(sdf.format(Date())).append(" /").append(getContext().packageName).append(" ")
|
||||
var level = "I"
|
||||
when (priority) {
|
||||
Log.DEBUG -> level = "D"
|
||||
Log.INFO -> level = "I"
|
||||
Log.VERBOSE -> level = "V"
|
||||
Log.WARN -> level = "W"
|
||||
Log.ERROR -> level = "E"
|
||||
}
|
||||
logContent.append(level).append("/").append(tag).append(": ").append(message).append("\n")
|
||||
if (t != null) {
|
||||
val writer: Writer = StringWriter()
|
||||
val pw = PrintWriter(writer)
|
||||
t.printStackTrace(pw)
|
||||
var cause = t.cause
|
||||
// 循环着把所有的异常信息写入writer中
|
||||
while (cause != null) {
|
||||
cause.printStackTrace(pw)
|
||||
cause = cause.cause
|
||||
}
|
||||
pw.close() // 记得关闭
|
||||
val result = writer.toString()
|
||||
logContent.delete(0, logContent.length)
|
||||
logContent.append("FATAL Exception:").append("\n").append(result).append("\n")
|
||||
}
|
||||
mLogQueue.put(logContent.toString())
|
||||
// 确保运行着
|
||||
mLogWriter.ensureRunning()
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志写入器
|
||||
*/
|
||||
class LogWriter : Runnable {
|
||||
// 消费者线程是否在运行
|
||||
private val isLogWriterRunning = AtomicBoolean(false)
|
||||
|
||||
override fun run() {
|
||||
while (isLogWriterRunning.get()) {
|
||||
try {
|
||||
// 检查日期发生变化
|
||||
val currentDate: String = TimeUtils.getNowTimeString()
|
||||
if (TextUtils.isEmpty(mCurrentDate) || mCurrentDate != currentDate) {
|
||||
checkDeletePastDueLog()
|
||||
init()
|
||||
}
|
||||
if (mLogFile != null) {
|
||||
var bw: BufferedWriter? = null
|
||||
try {
|
||||
if (mLogFile?.exists() == false) {
|
||||
mLogFile?.createNewFile()
|
||||
}
|
||||
bw = BufferedWriter(FileWriter(mLogFile, true))
|
||||
bw.write(mLogQueue.take())
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
bw?.close()
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
isLogWriterRunning.set(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保运行着
|
||||
*/
|
||||
fun ensureRunning() {
|
||||
// 写入线程在运行
|
||||
if (isLogWriterRunning.get()) {
|
||||
return
|
||||
}
|
||||
// 启动线程
|
||||
isLogWriterRunning.set(true)
|
||||
Thread(this).start()
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测删除过期日志,只保存近7天的日志
|
||||
*/
|
||||
private fun checkDeletePastDueLog() {
|
||||
val logDirPath: String = FileUtils.getExternalFilePath() + File.separator + LOG_DIR
|
||||
val logDir = File(logDirPath)
|
||||
if (!logDir.exists() || logDir.listFiles().isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
logDir.listFiles()?.let {
|
||||
for (logFile in it) {
|
||||
if (logFile.exists() && isPastDueLogFile(logFile)) {
|
||||
logFile.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是过期的日志文件
|
||||
*/
|
||||
private fun isPastDueLogFile(logFile: File): Boolean {
|
||||
val fileName: String = FileUtils.getFileNameNotSuffix(logFile)
|
||||
// 日志文件创建时间
|
||||
val logFileCreateTime = TimeUtils.string2Millis(
|
||||
fileName,
|
||||
LOG_FILE_NAME_DATE_FORMAT
|
||||
)
|
||||
// 日志文件名称格式解析错误,删除
|
||||
if (logFileCreateTime <= 0) {
|
||||
return true
|
||||
}
|
||||
// 检测时间是否已经过期
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.add(Calendar.DATE, -SAVE_LOG_DAYS)
|
||||
return calendar.timeInMillis > logFileCreateTime
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.bbgo.common_base.widget;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
/**
|
||||
* Created by Meiji on 2016/12/12.
|
||||
*/
|
||||
|
||||
public class BottomNavigationBehavior extends CoordinatorLayout.Behavior<View> {
|
||||
|
||||
private ObjectAnimator outAnimator, inAnimator;
|
||||
|
||||
public BottomNavigationBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
// 垂直滑动
|
||||
@Override
|
||||
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes, int type) {
|
||||
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed, int type) {
|
||||
if (dy > 0) {// 上滑隐藏
|
||||
if (outAnimator == null) {
|
||||
outAnimator = ObjectAnimator.ofFloat(child, "translationY", 0, child.getHeight());
|
||||
outAnimator.setDuration(200);
|
||||
}
|
||||
if (!outAnimator.isRunning() && child.getTranslationY() <= 0) {
|
||||
outAnimator.start();
|
||||
}
|
||||
} else if (dy < 0) {// 下滑显示
|
||||
if (inAnimator == null) {
|
||||
inAnimator = ObjectAnimator.ofFloat(child, "translationY", child.getHeight(), 0);
|
||||
inAnimator.setDuration(200);
|
||||
}
|
||||
if (!inAnimator.isRunning() && child.getTranslationY() >= child.getHeight()) {
|
||||
inAnimator.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package com.bbgo.common_base.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import com.bbgo.common_base.R
|
||||
|
||||
/**
|
||||
* Created by chenxz on 2018/4/22.
|
||||
*/
|
||||
class CircleImageView(context: Context, attrs: AttributeSet) : AppCompatImageView(context, attrs) {
|
||||
|
||||
private val mType: Int
|
||||
private val mBorderColor: Int
|
||||
private val mBorderWidth: Int
|
||||
private val mRectRoundRadius: Int
|
||||
|
||||
private val mPaintBitmap = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val mPaintBorder = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
|
||||
private val mRectBorder = RectF()
|
||||
private val mRectBitmap = RectF()
|
||||
|
||||
private var mRawBitmap: Bitmap? = null
|
||||
private var mShader: BitmapShader? = null
|
||||
private val mMatrix = Matrix()
|
||||
|
||||
init {
|
||||
//取xml文件中设定的参数
|
||||
val ta = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView)
|
||||
mType = ta.getInt(R.styleable.CircleImageView_type, DEFAULT_TYPE)
|
||||
mBorderColor = ta.getColor(R.styleable.CircleImageView_borderColor, DEFAULT_BORDER_COLOR)
|
||||
mBorderWidth = ta.getDimensionPixelSize(R.styleable.CircleImageView_borderWidth, dip2px(DEFAULT_BORDER_WIDTH))
|
||||
mRectRoundRadius = ta.getDimensionPixelSize(R.styleable.CircleImageView_rectRoundRadius, dip2px(DEFAULT_RECT_ROUND_RADIUS))
|
||||
ta.recycle()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
val rawBitmap = getBitmap(drawable)
|
||||
|
||||
if (rawBitmap != null && mType != TYPE_NONE) {
|
||||
val viewWidth = width
|
||||
val viewHeight = height
|
||||
val viewMinSize = Math.min(viewWidth, viewHeight)
|
||||
val dstWidth = (if (mType == TYPE_CIRCLE) viewMinSize else viewWidth).toFloat()
|
||||
val dstHeight = (if (mType == TYPE_CIRCLE) viewMinSize else viewHeight).toFloat()
|
||||
val halfBorderWidth = mBorderWidth / 2.0f
|
||||
val doubleBorderWidth = (mBorderWidth * 2).toFloat()
|
||||
|
||||
if (mShader == null || rawBitmap != mRawBitmap) {
|
||||
mRawBitmap = rawBitmap
|
||||
mShader = BitmapShader(mRawBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
|
||||
}
|
||||
if (mShader != null) {
|
||||
mMatrix.setScale((dstWidth - doubleBorderWidth) / rawBitmap.width, (dstHeight - doubleBorderWidth) / rawBitmap.height)
|
||||
mShader!!.setLocalMatrix(mMatrix)
|
||||
}
|
||||
|
||||
mPaintBitmap.shader = mShader
|
||||
mPaintBorder.style = Paint.Style.STROKE
|
||||
mPaintBorder.strokeWidth = mBorderWidth.toFloat()
|
||||
mPaintBorder.color = if (mBorderWidth > 0) mBorderColor else Color.TRANSPARENT
|
||||
|
||||
if (mType == TYPE_CIRCLE) {
|
||||
val radius = viewMinSize / 2.0f
|
||||
canvas.drawCircle(radius, radius, radius - halfBorderWidth, mPaintBorder)
|
||||
canvas.translate(mBorderWidth.toFloat(), mBorderWidth.toFloat())
|
||||
canvas.drawCircle(radius - mBorderWidth, radius - mBorderWidth, radius - mBorderWidth, mPaintBitmap)
|
||||
} else if (mType == TYPE_ROUNDED_RECT) {
|
||||
mRectBorder.set(halfBorderWidth, halfBorderWidth, dstWidth - halfBorderWidth, dstHeight - halfBorderWidth)
|
||||
mRectBitmap.set(0.0f, 0.0f, dstWidth - doubleBorderWidth, dstHeight - doubleBorderWidth)
|
||||
val borderRadius = if (mRectRoundRadius - halfBorderWidth > 0.0f) mRectRoundRadius - halfBorderWidth else 0.0f
|
||||
val bitmapRadius = if (mRectRoundRadius - mBorderWidth > 0.0f) (mRectRoundRadius - mBorderWidth).toFloat() else 0.0f
|
||||
canvas.drawRoundRect(mRectBorder, borderRadius, borderRadius, mPaintBorder)
|
||||
canvas.translate(mBorderWidth.toFloat(), mBorderWidth.toFloat())
|
||||
canvas.drawRoundRect(mRectBitmap, bitmapRadius, bitmapRadius, mPaintBitmap)
|
||||
}
|
||||
} else {
|
||||
super.onDraw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dip2px(dipVal: Int): Int {
|
||||
val scale = resources.displayMetrics.density
|
||||
return (dipVal * scale + 0.5f).toInt()
|
||||
}
|
||||
|
||||
private fun getBitmap(drawable: Drawable?): Bitmap? {
|
||||
if (drawable == null) return null
|
||||
return when (drawable) {
|
||||
is BitmapDrawable -> drawable.bitmap
|
||||
is ColorDrawable -> {
|
||||
val rect = drawable.getBounds()
|
||||
val width = rect.right - rect.left
|
||||
val height = rect.bottom - rect.top
|
||||
val color = drawable.color
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
canvas.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color))
|
||||
bitmap
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* android.widget.ImageView
|
||||
*/
|
||||
val TYPE_NONE = 0
|
||||
/**
|
||||
* 圆形
|
||||
*/
|
||||
val TYPE_CIRCLE = 1
|
||||
/**
|
||||
* 圆角矩形
|
||||
*/
|
||||
val TYPE_ROUNDED_RECT = 2
|
||||
|
||||
private val DEFAULT_TYPE = TYPE_NONE
|
||||
private val DEFAULT_BORDER_COLOR = Color.TRANSPARENT
|
||||
private val DEFAULT_BORDER_WIDTH = 0
|
||||
private val DEFAULT_RECT_ROUND_RADIUS = 0
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.bbgo.common_base.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
|
||||
/**
|
||||
* Created by chenxz on 2018/5/13.
|
||||
*
|
||||
* FAB 行为控制器
|
||||
*/
|
||||
class ScaleDownShowBehavior : FloatingActionButton.Behavior {
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout,
|
||||
child: FloatingActionButton,
|
||||
directTargetChild: View,
|
||||
target: View,
|
||||
axes: Int,
|
||||
type: Int): Boolean {
|
||||
return axes == ViewCompat.SCROLL_AXIS_VERTICAL ||
|
||||
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: FloatingActionButton, target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int) {
|
||||
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type)
|
||||
if (dyConsumed > 0 && child.visibility == View.VISIBLE) {
|
||||
child.visibility = View.INVISIBLE
|
||||
} else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
|
||||
child.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package com.bbgo.common_base.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.OrientationHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
|
||||
|
||||
/**
|
||||
* Created by chenxz on 2018/5/9.
|
||||
*
|
||||
* 简单的 RecyclerView 分割线
|
||||
*/
|
||||
class SpaceItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
|
||||
|
||||
private var mDivider: Drawable? = null
|
||||
private val mSectionOffsetV: Int = 0
|
||||
private val mSectionOffsetH: Int = 0
|
||||
private var mDrawOver = true
|
||||
private var attrs: IntArray = intArrayOf(android.R.attr.listDivider)
|
||||
|
||||
init {
|
||||
var a = context.obtainStyledAttributes(attrs)
|
||||
mDivider = a.getDrawable(0)
|
||||
a.recycle()
|
||||
}
|
||||
|
||||
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
super.onDrawOver(c, parent, state)
|
||||
if (mDivider != null && mDrawOver) {
|
||||
draw(c, parent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
super.onDraw(c, parent, state)
|
||||
if (mDivider != null && mDrawOver) {
|
||||
draw(c, parent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
if (getOrientation(parent.layoutManager!!) == RecyclerView.VERTICAL) {
|
||||
outRect.set(mSectionOffsetH, 0, mSectionOffsetH, mSectionOffsetV)
|
||||
} else {
|
||||
outRect.set(0, 0, mSectionOffsetV, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun draw(c: Canvas, parent: RecyclerView) {
|
||||
val left = parent.paddingLeft
|
||||
val right = parent.width - parent.paddingRight
|
||||
|
||||
val childCount = parent.childCount
|
||||
for (i in 0 until childCount) {
|
||||
val child = parent.getChildAt(i)
|
||||
|
||||
val params = child.layoutParams as RecyclerView.LayoutParams
|
||||
|
||||
val top = child.bottom + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child))
|
||||
val bottom = top + if (mDivider!!.intrinsicHeight <= 0) 1 else mDivider!!.intrinsicHeight
|
||||
|
||||
mDivider?.let {
|
||||
it.setBounds(left, top, right, bottom)
|
||||
it.draw(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOrientation(layoutManager: RecyclerView.LayoutManager): Int {
|
||||
if (layoutManager is LinearLayoutManager) {
|
||||
return layoutManager.orientation
|
||||
} else if (layoutManager is StaggeredGridLayoutManager) {
|
||||
return layoutManager.orientation
|
||||
}
|
||||
return OrientationHelper.HORIZONTAL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator">
|
||||
<scale android:fromXScale="2.0" android:toXScale="1.0"
|
||||
android:fromYScale="2.0" android:toYScale="1.0"
|
||||
android:pivotX="50%p" android:pivotY="50%p"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
@@ -0,0 +1,10 @@
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:zAdjustment="top">
|
||||
<scale android:fromXScale="1.0" android:toXScale=".5"
|
||||
android:fromYScale="1.0" android:toYScale=".5"
|
||||
android:pivotX="50%p" android:pivotY="50%p"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
<alpha android:fromAlpha="1.0" android:toAlpha="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime"/>
|
||||
</set>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<alpha
|
||||
android:duration="1000"
|
||||
android:fromAlpha="0"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:toAlpha="1.0"/>
|
||||
</set>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<alpha
|
||||
android:duration="1500"
|
||||
android:fromAlpha="1.0"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:toAlpha="0"/>
|
||||
</set>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate android:fromYDelta="-50%p" android:toYDelta="0" android:duration="1000"/>
|
||||
|
||||
<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="1000" />
|
||||
|
||||
</set>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate android:fromYDelta="0" android:toYDelta="-50%p" android:duration="1000"/>
|
||||
|
||||
<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="1000" />
|
||||
|
||||
</set>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="@color/list_divider" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="@color/Grey300"/>
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="#A6000000" />
|
||||
<padding android:left="8dp" android:top="4dp" android:right="8dp" android:bottom="4dp" />
|
||||
<corners android:radius="2dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="#99000000" />
|
||||
<padding android:left="8dp" android:top="4dp" android:right="8dp" android:bottom="4dp" />
|
||||
<corners android:radius="2dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</selector>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108"
|
||||
android:tint="#FFFFFF">
|
||||
<group android:scaleX="2.39598"
|
||||
android:scaleY="2.39598"
|
||||
android:translateX="25.24824"
|
||||
android:translateY="21.25494">
|
||||
<path
|
||||
android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z"
|
||||
android:fillColor="#FF000000"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colorPrimary"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</merge>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<attr name="textColorPrimary" format="color" />
|
||||
<attr name="viewBackground" format="color" />
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">@color/Cyan</color>
|
||||
<color name="colorPrimaryDark">@color/transparent</color>
|
||||
<color name="colorAccent">@color/Cyan</color>
|
||||
<color name="textColorPrimary">#616161</color>
|
||||
<color name="viewBackground">@color/White</color>
|
||||
<color name="windowBackground">@color/Grey100</color>
|
||||
<color name="line_divider">@color/Grey300</color>
|
||||
<color name="list_divider">@color/Grey400</color>
|
||||
<color name="mask_color">#f5f5f5</color>
|
||||
<color name="transparent">#00000000</color>
|
||||
|
||||
<!-- item -->
|
||||
<color name="common_color">#19191B</color>
|
||||
<color name="item_title">#19191B</color>
|
||||
<color name="item_author">@color/Grey700</color>
|
||||
<color name="item_desc">@color/Grey600</color>
|
||||
<color name="item_time">@color/Grey600</color>
|
||||
<color name="item_date">@color/Grey600</color>
|
||||
<color name="item_chapter">@color/Grey600</color>
|
||||
<color name="item_tag_tv">#ffFFFFFF</color>
|
||||
<color name="item_nav_color_tv">#19191B</color>
|
||||
<color name="color_title_bg">#CCFFFFFF</color>
|
||||
<color name="color_about_tv">@color/Grey600</color>
|
||||
<color name="transparent_75">#ba000000</color>
|
||||
<color name="vertical_tab_layout_bg">@color/Grey100</color>
|
||||
<color name="vertical_tab_layout_indicator_color">@color/White</color>
|
||||
<color name="item_flow_layout_bg">@color/Grey200</color>
|
||||
<color name="arrow_color">@color/Grey700</color>
|
||||
<color name="sticky_header_bg">@color/Grey100</color>
|
||||
|
||||
<color name="light_red">#e26d2e</color>
|
||||
<color name="deep_red">#dc3b1f</color>
|
||||
<color name="while_most_color">#e4e4e4</color>
|
||||
<color name="title_black">#222222</color>
|
||||
<color name="grey_search">#FF999999</color>
|
||||
|
||||
<color name="White">#FFFFFF</color>
|
||||
<color name="Black">#000000</color>
|
||||
<color name="Red">#f44336</color>
|
||||
<color name="Pink">#e91e63</color>
|
||||
<color name="Purple">#9c27b0</color>
|
||||
<color name="Deep_Purple">#673ab7</color>
|
||||
<color name="Indigo">#3f51b5</color>
|
||||
<color name="Blue">#2196f3</color>
|
||||
<color name="Light_Blue">#03a9f4</color>
|
||||
<color name="Cyan">#00bcd4</color>
|
||||
<color name="Teal">#009688</color>
|
||||
<color name="Green">#4caf50</color>
|
||||
<color name="Light_Green">#8bc34a</color>
|
||||
<color name="Lime">#cddc39</color>
|
||||
<color name="Yellow">#ffeb3b</color>
|
||||
<color name="Amber">#ffc107</color>
|
||||
<color name="Orange">#ff9800</color>
|
||||
<color name="Deep_Orange">#ff5722</color>
|
||||
<color name="Brown">#795548</color>
|
||||
<color name="Grey">#9e9e9e</color>
|
||||
<color name="Blue_Grey">#607d8b</color>
|
||||
|
||||
<color name="Grey50">#FAFAFA</color>
|
||||
<color name="Grey100">#F5F5F5</color>
|
||||
<color name="Grey200">#EEEEEE</color>
|
||||
<color name="Grey300">#E0E0E0</color>
|
||||
<color name="Grey400">#bdbdbd</color>
|
||||
<color name="Grey500">#9e9e9e</color>
|
||||
<color name="Grey600">#757575</color>
|
||||
<color name="Grey700">#616161</color>
|
||||
<color name="Grey800">#424242</color>
|
||||
<color name="Grey900">#212121</color>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,85 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
|
||||
<dimen name="avatar_width">80dp</dimen>
|
||||
<dimen name="avatar_height">80dp</dimen>
|
||||
|
||||
<dimen name="appbar_layout_height">260dp</dimen>
|
||||
|
||||
<!-- item -->
|
||||
<dimen name="item_tv_title">16sp</dimen>
|
||||
<dimen name="item_tv_content">14sp</dimen>
|
||||
<dimen name="item_tv_date">12sp</dimen>
|
||||
<dimen name="item_tv_tag">12sp</dimen>
|
||||
<dimen name="item_tv_author">12sp</dimen>
|
||||
<dimen name="item_tv_via">12sp</dimen>
|
||||
<dimen name="item_content_padding">10dp</dimen>
|
||||
<dimen name="item_img_width">120dp</dimen>
|
||||
<dimen name="item_img_height">90dp</dimen>
|
||||
|
||||
<dimen name="textSize_small_10">10sp</dimen>
|
||||
<dimen name="textSize_small">12sp</dimen>
|
||||
<dimen name="textSize_content">14sp</dimen>
|
||||
<dimen name="textSize_titleSmall">16sp</dimen>
|
||||
<dimen name="textSize_title">18sp</dimen>
|
||||
|
||||
<!-- dp -->
|
||||
<dimen name="dp_05">0.5dp</dimen>
|
||||
<dimen name="dp_1">1dp</dimen>
|
||||
<dimen name="dp_2">2dp</dimen>
|
||||
<dimen name="dp_3">3dp</dimen>
|
||||
<dimen name="dp_4">4dp</dimen>
|
||||
<dimen name="dp_5">5dp</dimen>
|
||||
<dimen name="dp_6">6dp</dimen>
|
||||
<dimen name="dp_8">8dp</dimen>
|
||||
<dimen name="dp_10">10dp</dimen>
|
||||
<dimen name="dp_12">12dp</dimen>
|
||||
<dimen name="dp_16">16dp</dimen>
|
||||
<dimen name="dp_18">18dp</dimen>
|
||||
<dimen name="dp_20">20dp</dimen>
|
||||
<dimen name="dp_24">24dp</dimen>
|
||||
<dimen name="dp_26">26dp</dimen>
|
||||
<dimen name="dp_28">28dp</dimen>
|
||||
<dimen name="dp_30">30dp</dimen>
|
||||
<dimen name="dp_36">36dp</dimen>
|
||||
<dimen name="dp_46">46dp</dimen>
|
||||
<dimen name="dp_50">50dp</dimen>
|
||||
<dimen name="dp_54">54dp</dimen>
|
||||
<dimen name="dp_60">60dp</dimen>
|
||||
<dimen name="dp_80">80dp</dimen>
|
||||
<dimen name="dp_90">90dp</dimen>
|
||||
<dimen name="dp_100">100dp</dimen>
|
||||
<dimen name="dp_110">110dp</dimen>
|
||||
<dimen name="dp_168">168dp</dimen>
|
||||
<dimen name="dp_180">180dp</dimen>
|
||||
<dimen name="dp_200">200dp</dimen>
|
||||
<dimen name="dp_230">230dp</dimen>
|
||||
<dimen name="dp_240">240dp</dimen>
|
||||
<dimen name="dp_260">260dp</dimen>
|
||||
<dimen name="dp_280">280dp</dimen>
|
||||
<dimen name="dp_300">300dp</dimen>
|
||||
|
||||
<!-- sp -->
|
||||
<dimen name="sp_8">8sp</dimen>
|
||||
<dimen name="sp_9">9sp</dimen>
|
||||
<dimen name="sp_10">10sp</dimen>
|
||||
<dimen name="sp_12">12sp</dimen>
|
||||
<dimen name="sp_13">13sp</dimen>
|
||||
<dimen name="sp_14">14sp</dimen>
|
||||
<dimen name="sp_15">15sp</dimen>
|
||||
<dimen name="sp_16">16sp</dimen>
|
||||
<dimen name="sp_17">17sp</dimen>
|
||||
<dimen name="sp_18">18sp</dimen>
|
||||
<dimen name="sp_20">20sp</dimen>
|
||||
<dimen name="sp_22">22sp</dimen>
|
||||
<dimen name="sp_24">24sp</dimen>
|
||||
<dimen name="sp_40">40sp</dimen>
|
||||
<dimen name="sp_50">50sp</dimen>
|
||||
|
||||
<!--BottomNavigationView 的选中没有选中的字体大小-->
|
||||
<dimen name="design_bottom_navigation_active_text_size">@dimen/sp_13</dimen>
|
||||
<dimen name="design_bottom_navigation_text_size">@dimen/sp_12</dimen>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item type="id" name="expandable_text"/>
|
||||
<item type="id" name="expand_collapse"/>
|
||||
|
||||
<item name="statusbarutil_fake_status_bar_view" type="id" />
|
||||
<item name="statusbarutil_translucent_view" type="id" />
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">common-base</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,146 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="BaseAppTheme" parent="@style/Theme.AppCompat.DayNight">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="textColorPrimary">@color/textColorPrimary</item>
|
||||
<item name="viewBackground">@color/viewBackground</item>
|
||||
<!--关闭启动窗口-->
|
||||
<item name="android:windowDisablePreview">true</item>
|
||||
<item name="android:listDivider">@drawable/bg_divider</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme" parent="BaseAppTheme.NoActionBar"></style>
|
||||
|
||||
<style name="AppTheme.FullScreen" parent="@style/Theme.AppCompat.DayNight">
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="android:windowDisablePreview">true</item>
|
||||
</style>
|
||||
|
||||
<style name="SplashTheme" parent="BaseAppTheme.NoActionBar">
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
</style>
|
||||
|
||||
<style name="BaseAppTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="BaseAppTheme.NoActionBar.Slidable" parent="BaseAppTheme.NoActionBar">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
|
||||
<style name="MyTabLayoutStyle" parent="Base.Widget.Design.TabLayout">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:background">@color/colorPrimary</item>
|
||||
<item name="tabIndicatorColor">@color/viewBackground</item>
|
||||
<item name="tabIndicatorHeight">@dimen/dp_1</item>
|
||||
<item name="tabSelectedTextColor">@color/viewBackground</item>
|
||||
<item name="tabTextAppearance">@style/MyTabTextAppearance</item>
|
||||
</style>
|
||||
|
||||
<style name="MyTabTextAppearance">
|
||||
<item name="android:textSize">@dimen/sp_14</item>
|
||||
<item name="textAllCaps">false</item>
|
||||
<item name="android:textColor">?android:textColorSecondary</item>
|
||||
</style>
|
||||
|
||||
<style name="WindowAnimationFadeInOut">
|
||||
<item name="android:windowEnterAnimation">@anim/fade_in</item>
|
||||
<item name="android:windowExitAnimation">@anim/fade_out</item>
|
||||
</style>
|
||||
|
||||
<style name="ToolbarStyle" parent="AppTheme">
|
||||
<item name="android:textColorSecondary">@android:color/white</item>
|
||||
<item name="searchHintIcon">@null</item>
|
||||
</style>
|
||||
|
||||
<!-- 设置Toolbar标题字体的大小 -->
|
||||
<style name="Toolbar.TitleText" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
|
||||
<item name="android:textSize">@dimen/sp_18</item>
|
||||
</style>
|
||||
|
||||
<!-- 浮动窗口动画 -->
|
||||
<style name="anim_float_view">
|
||||
<item name="android:windowEnterAnimation">@anim/push_up_in</item>
|
||||
<item name="android:windowExitAnimation">@anim/push_up_out</item>
|
||||
</style>
|
||||
|
||||
<style name="MenuLabelsStyle">
|
||||
<item name="android:background">@drawable/fab_label_background</item>
|
||||
<item name="android:textColor">#FFFFFF</item>
|
||||
<item name="android:textSize">@dimen/sp_14</item>
|
||||
<item name="android:maxLines">2</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
</style>
|
||||
|
||||
<style name="RecyclerViewStyle">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:scrollbarSize">@dimen/dp_2</item>
|
||||
<item name="android:scrollbars">vertical</item>
|
||||
</style>
|
||||
|
||||
<style name="TabLayout" parent="Base.Widget.Design.TabLayout">
|
||||
<item name="tabIndicatorColor">@color/colorPrimary</item>
|
||||
<item name="tabIndicatorHeight">2dp</item>
|
||||
<item name="tabPaddingStart">12dp</item>
|
||||
<item name="tabPaddingEnd">12dp</item>
|
||||
<item name="tabBackground">@color/colorPrimary</item>
|
||||
<item name="tabTextAppearance">@style/TextAppearance.Design.Tab</item>
|
||||
<item name="tabSelectedTextColor">@color/colorPrimary</item>
|
||||
<item name="tabTextColor">@color/colorPrimary</item>
|
||||
</style>
|
||||
|
||||
<declare-styleable name="CircleProgressView">
|
||||
<attr name="cpv_progressNormalColor" format="color"/>
|
||||
<attr name="cpv_progressReachColor" format="color"/>
|
||||
<attr name="cpv_progressTextColor" format="color"/>
|
||||
<attr name="cpv_progressTextSize" format="dimension"/>
|
||||
<attr name="cpv_progressTextOffset" format="dimension"/>
|
||||
<attr name="cpv_progressNormalSize" format="dimension"/>
|
||||
<attr name="cpv_progressReachSize" format="dimension"/>
|
||||
<attr name="cpv_radius" format="dimension"/>
|
||||
<attr name="cpv_progressTextVisible" format="boolean"/>
|
||||
<attr name="cpv_progressStartArc" format="integer"/>
|
||||
<attr name="cpv_progressTextSkewX" format="dimension"/>
|
||||
<attr name="cpv_progressTextPrefix" format="string"/>
|
||||
<attr name="cpv_progressTextSuffix" format="string"/>
|
||||
<attr name="cpv_innerBackgroundColor" format="color"/>
|
||||
<attr name="cpv_progressStyle" format="enum">
|
||||
<enum name="Normal" value="0"/>
|
||||
<enum name="FillInner" value="1"/>
|
||||
<enum name="FillInnerArc" value="2"/>
|
||||
</attr>
|
||||
<attr name="cpv_innerProgressColor" format="color"/>
|
||||
<attr name="cpv_innerPadding" format="dimension"/>
|
||||
<attr name="cpv_outerColor" format="color"/>
|
||||
<attr name="cpv_outerSize" format="dimension"/>
|
||||
<attr name="cpv_reachCapRound" format="boolean"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="CircleImageView">
|
||||
<attr name="type" format="enum">
|
||||
<enum name="none" value="0" />
|
||||
<enum name="circle" value="1" />
|
||||
<enum name="rect_rounded" value="2" />
|
||||
</attr>
|
||||
<attr name="borderColor" format="color" />
|
||||
<attr name="borderWidth" format="dimension" />
|
||||
<attr name="rectRoundRadius" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="transition_movie_img">transition_movie_img</string>
|
||||
<string name="transition_news_img">transition_news_img</string>
|
||||
<string name="transition_book_img">transition_book_img</string>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user