This commit is contained in:
coco
2026-07-03 16:23:31 +08:00
commit 7a4fb0e6ae
1979 changed files with 101570 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+37
View File
@@ -0,0 +1,37 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 32
defaultConfig {
minSdk 29
targetSdk 32
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
api 'androidx.core:core-ktx:1.7.0'
api 'androidx.appcompat:appcompat:1.5.0'
api 'com.google.android.material:material:1.6.1'
api "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
api "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
api "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
api "androidx.fragment:fragment-ktx:1.5.0"
api "androidx.activity:activity-ktx:1.5.0"
api project(path: ':network')
api project(path: ':libraryTheme')
api("io.github.jeremyliao:live-event-bus-x:1.8.0")
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1 @@
<manifest package="com.aisier.architecture"></manifest>
@@ -0,0 +1,10 @@
package com.aisier.architecture.anno
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
annotation class ActivityConfiguration(
val useEventBus: Boolean = false,
val needLogin: Boolean = true
)
@@ -0,0 +1,8 @@
package com.aisier.architecture.anno
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
annotation class FragmentConfiguration(
val useEventBus: Boolean = false
)
@@ -0,0 +1,45 @@
package com.aisier.architecture.base
import android.app.ProgressDialog
import android.os.Bundle
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import com.aisier.architecture.ktx.toast
import com.aisier.network.SHOW_TOAST
import com.jeremyliao.liveeventbus.LiveEventBus
import com.ldlywt.colorful.ColorTheme
/**
* <pre>
* author : wutao
* time : 2021/6/18
* desc : 去掉类上面的泛型,因为反射会影响性能。并且优先选择组合而不是继承。
* version: 1.3
</pre> *
*/
abstract class BaseActivity(@LayoutRes contentLayoutId: Int) : AppCompatActivity(contentLayoutId), IUiView {
override fun onCreate(savedInstanceState: Bundle?) {
ColorTheme().applyTheme(this)
super.onCreate(savedInstanceState)
observeUi()
}
private fun observeUi() {
LiveEventBus.get<String>(SHOW_TOAST).observe(this) {
toast(it)
}
}
private var progressDialog: ProgressDialog? = null
override fun showLoading() {
if (progressDialog == null)
progressDialog = ProgressDialog(this)
progressDialog?.show()
}
override fun dismissLoading() {
progressDialog?.takeIf { it.isShowing }?.dismiss()
}
}
@@ -0,0 +1,39 @@
package com.aisier.architecture.base
import android.app.Application
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
/**
* author : wutao
* time : 2019/05/17
* desc :
* version: 1.0
*/
open class BaseApp : Application() {
override fun onCreate() {
super.onCreate()
instance = this
ProcessLifecycleOwner.get().lifecycle.addObserver(ApplicationLifecycleObserver())
}
companion object {
lateinit var instance: BaseApp
private set
}
private inner class ApplicationLifecycleObserver : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
// TODO: "ApplicationObserver: app moved to foreground"
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
// TODO: "ApplicationObserver: app moved to background"
}
}
}
@@ -0,0 +1,35 @@
package com.aisier.architecture.base
import android.app.ProgressDialog
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import com.aisier.architecture.anno.FragmentConfiguration
/**
* author : wutao
* time : 2020/10/14
* desc :
* version: 1.1
*/
abstract class BaseFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayoutId), IUiView {
private var useEventBus = false
init {
this.javaClass.getAnnotation(FragmentConfiguration::class.java)?.let {
useEventBus = it.useEventBus
}
}
private var progressDialog: ProgressDialog? = null
override fun showLoading() {
if (progressDialog == null)
progressDialog = ProgressDialog(requireActivity())
progressDialog?.show()
}
override fun dismissLoading() {
progressDialog?.takeIf { it.isShowing }?.dismiss()
}
}
@@ -0,0 +1,19 @@
package com.aisier.architecture.base
import androidx.lifecycle.ViewModel
/**
* author : wutao
* time : 2019/05/17
* desc :
* version: 1.0
*/
abstract class BaseViewModel : ViewModel()
// TODO MVI
sealed class ViewEffect {
data class ShowLoading(val isShow: Boolean) : ViewEffect()
data class ShowToast(val message: String) : ViewEffect()
data class Success<T>(val data: T) : ViewEffect()
object Empty : ViewEffect()
}
@@ -0,0 +1,10 @@
package com.aisier.architecture.base
import androidx.lifecycle.LifecycleOwner
interface IUiView : LifecycleOwner {
fun showLoading()
fun dismissLoading()
}
@@ -0,0 +1,34 @@
package com.aisier.architecture.ktx
import android.app.Activity
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.widget.Toast
fun Context.toast(id: Int, length: Int = Toast.LENGTH_SHORT) {
toast(getString(id), length)
}
fun Context.toast(msg: String, length: Int = Toast.LENGTH_SHORT) {
try {
if (isOnMainThread()) {
doToast(this, msg, length)
} else {
Handler(Looper.getMainLooper()).post {
doToast(this, msg, length)
}
}
} catch (e: Exception) {
}
}
private fun doToast(context: Context, message: String, length: Int) {
if (context is Activity) {
if (!context.isFinishing && !context.isDestroyed) {
Toast.makeText(context, message, length).show()
}
} else {
Toast.makeText(context, message, length).show()
}
}
@@ -0,0 +1,23 @@
package com.aisier.architecture.ktx
import android.content.Context
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import java.util.Locale
fun EditText.getNotNullText(): String = text?.toString()?.trim() ?: ""
fun EditText.getNotNullUpperCaseText(): String = getNotNullText().toUpperCase(Locale.ENGLISH)
//may only available on real device
fun EditText.openKeyBoard() {
val imm = this.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(this, InputMethodManager.RESULT_SHOWN)
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
}
//may only available on real device
fun EditText.hideKeyBoard() {
val imm = this.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(this.windowToken, 0)
}
@@ -0,0 +1,12 @@
package com.aisier.architecture.ktx
import androidx.annotation.StringRes
import com.aisier.architecture.base.BaseApp
fun stringOf(@StringRes id: Int, vararg formatArgs: Any): String = getString(id, *formatArgs)
fun stringOf(@StringRes id: Int): String = getString(id)
fun getString(@StringRes id: Int, vararg formatArgs: Any?): String {
return BaseApp.instance.resources.getString(id, *formatArgs)
}
@@ -0,0 +1,23 @@
package com.aisier.architecture.ktx
import android.os.Handler
import android.os.Looper
fun isOnMainThread() = Looper.myLooper() == Looper.getMainLooper()
fun ensureBackgroundThread(callback: () -> Unit) {
if (isOnMainThread()) {
Thread {
callback()
}.start()
} else {
callback()
}
}
fun <T> T.mainThread(delayMillis: Long = 0, block: T.() -> Unit) {
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
block()
}, delayMillis)
}
@@ -0,0 +1,26 @@
package com.aisier.architecture.ktx
import android.graphics.drawable.GradientDrawable
import android.view.View
fun View.clickWithLimit(intervalMill: Int = 500, block: ((v: View?) -> Unit)) {
setOnClickListener(object : View.OnClickListener {
var last = 0L
override fun onClick(v: View?) {
if (System.currentTimeMillis() - last > intervalMill) {
block(v)
last = System.currentTimeMillis()
}
}
})
}
/**
* 自定义圆角矩形
*/
fun View.setRoundRectBg(color: Int, cornerRadius: Int) {
background = GradientDrawable().apply {
setColor(color)
setCornerRadius(cornerRadius.toFloat())
}
}
@@ -0,0 +1,88 @@
package com.aisier.architecture.util
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.aisier.architecture.base.IUiView
import com.aisier.network.ResultBuilder
import com.aisier.network.entity.ApiResponse
import com.aisier.network.parseData
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
fun <T> launchFlow(
requestBlock: suspend () -> ApiResponse<T>,
startCallback: (() -> Unit)? = null,
completeCallback: (() -> Unit)? = null,
): Flow<ApiResponse<T>> {
return flow {
emit(requestBlock())
}.onStart {
startCallback?.invoke()
}.onCompletion {
completeCallback?.invoke()
}
}
/**
* 这个方法只是简单的一个封装Loading的普通方法,不返回任何实体类
*/
fun IUiView.launchWithLoading(requestBlock: suspend () -> Unit) {
lifecycleScope.launch {
flow {
emit(requestBlock())
}.onStart {
showLoading()
}.onCompletion {
dismissLoading()
}.collect()
}
}
/**
* 请求不带Loading&&不需要声明LiveData
*/
fun <T> IUiView.launchAndCollect(
requestBlock: suspend () -> ApiResponse<T>,
listenerBuilder: ResultBuilder<T>.() -> Unit,
) {
lifecycleScope.launch {
launchFlow(requestBlock).collect { response ->
response.parseData(listenerBuilder)
}
}
}
/**
* 请求带Loading&&不需要声明LiveData
*/
fun <T> IUiView.launchWithLoadingAndCollect(
requestBlock: suspend () -> ApiResponse<T>,
listenerBuilder: ResultBuilder<T>.() -> Unit,
) {
lifecycleScope.launch {
launchFlow(requestBlock, { showLoading() }, { dismissLoading() }).collect { response ->
response.parseData(listenerBuilder)
}
}
}
fun <T> Flow<ApiResponse<T>>.collectIn(
lifecycleOwner: LifecycleOwner,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
listenerBuilder: ResultBuilder<T>.() -> Unit,
): Job = lifecycleOwner.lifecycleScope.launch {
flowWithLifecycle(lifecycleOwner.lifecycle,
minActiveState).collect { apiResponse: ApiResponse<T> ->
apiResponse.parseData(listenerBuilder)
}
}
@@ -0,0 +1,52 @@
package com.aisier.architecture.util
import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean
/**
利用SingleLiveEvent 使 observe#LiveData时只相应一次onChanged操作
1 SingleLiveEvent 利用 AtomicBoolean (默认为false)进行赋值,当LiveData 进行 setValue时
改变 AtomicBoolean的值(set(true)
2 使用 AtomicBoolean.compareAndSet(true,false)方法,先进行判断(此时的AtomicBoolean的值为true
与 compareAndSet设置的except值(第一个参数)比较,因为相等所以将第二个参数设置为AtomicBoolean值设为false
函数并返回 true ,)
3 当再次进入该页面虽然 LiveData值并没有改变,仍然触发了 observer方法,由于 AtomicBoolean已经为 false ,但是 except值为 true
与if 进行判断所以 并不会继续触发 onChanged(T)方法
即只有在 setValue时相应一次onChanged(T)方法。
*/
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w("SingleLiveEvent", "Multiple observers registered but only one will be notified of changes.")
}
super.observe(owner, { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
@MainThread
fun call() {
value = null
}
}
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<!--数字是不透明度,20表示:不透明度是20,透明度是80-->
<resources>
<!--black-->
<color name="black">#000000</color>
<color name="black_20">#33000000</color>
<!--White-->
<color name="white">#FFFFFF</color>
<color name="white_e0">#E0E0E0</color>
<color name="white_ed">#EDEDED</color>
<!--Red-->
<color name="red_89">#F2D7D4</color>
<color name="red_74">#E5B0AA</color>
<color name="red_46">#BF382A</color>
<color name="red_20">#610F07</color>
<color name="red_11">#380600</color>
<!--Chrome Orange/铬橙-->
<color name="chrome_orange_90">#FCDDCF</color>
<color name="chrome_orange_74">#E5AD93</color>
<color name="chrome_orange_54">#F16222</color>
<color name="chrome_orange_20">#612307</color>
<color name="chrome_orange_11">#381200</color>
<!--Orange/橙-->
<color name="orange_90">#FDECD0</color>
<color name="orange_80">#FAD7A0</color>
<color name="orange_51">#F39C12</color>
<color name="orange_20">#613E07</color>
<color name="orange_11">#382300</color>
<!--Yellow/黄-->
<color name="yellow_87">#FCF6CF</color>
<color name="yellow_80">#FAEEA0</color>
<color name="yellow_51">#F2D411</color>
<color name="yellow_20">#615507</color>
<color name="yellow_11">#383100</color>
<!--Green/绿-->
<color name="green_85">#CCE8D4</color>
<color name="green_71">#99D1AA</color>
<color name="green_40">#0DBF42</color>
<color name="green_22">#006E21</color>
<color name="green_11">#003811</color>
<!--Cyan/蓝绿-->
<color name="cyan_86">#CCEFF0</color>
<color name="cyan_74">#99DFE1</color>
<color name="cyan_35">#00AFB5</color>
<color name="cyan_21">#00696D</color>
<color name="cyan_11">#003638</color>
<!--Blue/蓝-->
<color name="blue_90">#CFDFFC</color>
<color name="blue_80">#A0BFFA</color>
<color name="blue_40">#0D4ABF</color>
<color name="blue_22">#00266E</color>
<color name="blue_11">#001438</color>
<!--Purple/紫-->
<color name="purple_90">#EDCFFC</color>
<color name="purple_80">#DCA0FA</color>
<color name="purple_40">#840DBF</color>
<color name="purple_22">#49006E</color>
<color name="purple_11">#250038</color>
<!--Neutral/中性-->
<color name="gray_94">#EDEFF2</color>
<color name="gray_87">#DCDEE0</color>
<color name="gray_78">#C4C8CC</color>
<color name="gray_55">#A2A5A8</color>
<color name="gray_40">#5E6770</color>
<color name="gray_32">#435160</color>
<color name="gray_20">#2B343D</color>
<color name="gray_10">#1D2329</color>
<color name="gray_14">#171C21</color>
<color name="gray_707">#707070</color>
</resources>
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="sp_11">11sp</dimen>
<dimen name="sp_12">12sp</dimen>
<dimen name="sp_14">14sp</dimen>
<dimen name="sp_16">16sp</dimen>
<dimen name="sp_18">18sp</dimen>
<dimen name="sp_20">20sp</dimen>
<dimen name="sp_24">24sp</dimen>
<dimen name="sp_28">28sp</dimen>
<dimen name="sp_40">40sp</dimen>
<dimen name="dp_0">0dp</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_6">6dp</dimen>
<dimen name="dp_8">8dp</dimen>
<dimen name="dp_10">10dp</dimen>
<dimen name="dp_12">12dp</dimen>
<dimen name="dp_14">14dp</dimen>
<dimen name="dp_15">15dp</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_30">30dp</dimen>
<dimen name="dp_32">32dp</dimen>
<dimen name="dp_36">36dp</dimen>
<dimen name="dp_40">40dp</dimen>
<dimen name="dp_48">48dp</dimen>
<dimen name="dp_50">50dp</dimen>
<dimen name="dp_54">54dp</dimen>
<dimen name="dp_56">56dp</dimen>
<dimen name="dp_60">60dp</dimen>
<dimen name="dp_64">64dp</dimen>
<dimen name="dp_72">72dp</dimen>
<dimen name="dp_76">76dp</dimen>
<dimen name="dp_80">80dp</dimen>
<dimen name="dp_100">100dp</dimen>
<dimen name="dp_110">110dp</dimen>
<dimen name="dp_118">118dp</dimen>
<dimen name="dp_120">120dp</dimen>
<dimen name="dp_130">130dp</dimen>
<dimen name="dp_136">136dp</dimen>
<dimen name="dp_150">150dp</dimen>
<dimen name="dp_160">160dp</dimen>
<dimen name="dp_180">180dp</dimen>
<dimen name="dp_198">198dp</dimen>
<dimen name="dp_200">200dp</dimen>
<dimen name="dp_240">240dp</dimen>
<dimen name="dp_265">265dp</dimen>
<dimen name="dp_280">280dp</dimen>
<dimen name="dp_400">400dp</dimen>
<dimen name="dp_300">300dp</dimen>
<dimen name="dp_340">340dp</dimen>
</resources>
@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="colorPrimary">@color/chrome_orange_54</item>
<item name="colorPrimaryVariant">@color/chrome_orange_54</item>
<item name="colorOnPrimary">@color/white</item>
&lt;!&ndash; Secondary brand color. &ndash;&gt;
<item name="colorSecondary">@color/chrome_orange_20</item>
<item name="colorSecondaryVariant">@color/chrome_orange_90</item>
<item name="colorOnSecondary">@color/black</item>
&lt;!&ndash; Status bar color. &ndash;&gt;
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
</style>-->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar"/>
</resources>