添加公共库

添加Coil 图片加载库
背景图片在应用启动时预加载
master
pan 3 years ago
parent dc58940561
commit df1349c089
  1. 32
      background/build.gradle.kts
  2. 4
      build.gradle.kts
  3. 74
      foreground/build.gradle.kts
  4. 123
      foreground/src/main/java/com/gyf/csams/APP.kt
  5. 16
      foreground/src/main/java/com/gyf/csams/Api.kt
  6. 2
      foreground/src/main/java/com/gyf/csams/InitViewModel.kt
  7. 6
      foreground/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt
  8. 2
      foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt
  9. 34
      foreground/src/main/java/com/gyf/csams/main/model/MainViewModel.kt
  10. 32
      foreground/src/main/java/com/gyf/csams/main/ui/MainActivity.kt
  11. 54
      foreground/src/main/java/com/gyf/csams/uikit/BaseView.kt
  12. 82
      foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt
  13. 32
      foreground/src/main/java/com/gyf/csams/util/HttpUtil.kt
  14. 46
      foreground/src/main/java/com/gyf/csams/util/ImageUtil.kt
  15. 5
      gradle/wrapper/gradle-wrapper.properties
  16. 57
      gradlew
  17. 43
      gradlew.bat
  18. 1
      lib/.gitignore
  19. 106
      lib/build.gradle.kts
  20. 0
      lib/consumer-rules.pro
  21. 21
      lib/proguard-rules.pro
  22. 22
      lib/src/androidTest/java/com/gyf/lib/ExampleInstrumentedTest.kt
  23. 4
      lib/src/main/AndroidManifest.xml
  24. 16
      lib/src/test/java/com/gyf/lib/ExampleUnitTest.kt
  25. 6
      settings.gradle.kts

@ -20,12 +20,21 @@ android {
}
buildTypes {
val appName = "${rootProject.extra["background_app_name"]}"
debug {
manifestPlaceholders.apply {
this["background_app_name"] = appName
}
}
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
manifestPlaceholders.apply {
this["background_app_name"] = appName
}
}
}
compileOptions {
@ -34,29 +43,32 @@ android {
}
kotlinOptions {
jvmTarget = "1.8"
useIR = true
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String
kotlinCompilerVersion = "1.4.32"
}
}
dependencies {
implementation(project(":lib"))
implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.appcompat:appcompat:1.2.0")
implementation("com.google.android.material:material:1.3.0")
implementation("androidx.compose.ui:ui:${rootProject.extra["compose_version"]}")
implementation("androidx.compose.material:material:${rootProject.extra["compose_version"]}")
implementation("androidx.compose.ui:ui-tooling:${rootProject.extra["compose_version"]}")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
implementation("androidx.activity:activity-compose:1.3.0-alpha07")
// optional - Test helpers
testImplementation("androidx.room:room-testing:${rootProject.extra["room_version"]}")
//测试
testImplementation("junit:junit:4.13.2")
/**
* A cross environment JUnit4 runner for Android tests.
* https://developer.android.com/reference/androidx/test/ext/junit/runners/package-summary?hl=en
*/
androidTestImplementation("androidx.test.ext:junit:1.1.2")
/**
* https://developer.android.com/training/testing/espresso
* 使用 Espresso 来编写简洁、美观且可靠的 Android 界面测试。
* 包含核心和基本的 View 匹配器、操作和断言
*/
androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:${rootProject.extra["compose_version"]}")
}

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
//Jetpack Compose版本
val compose_version by extra("1.0.0-beta05")
val compose_version by extra("1.0.0-beta06")
//生命周期组件版本
val lifecycle_version by extra("2.3.1")
//APP应用名字
@ -13,6 +13,8 @@ buildscript {
repositories {
maven("https://maven.aliyun.com/repository/google")
maven("https://maven.aliyun.com/repository/public")
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.0.0-alpha15")

@ -1,7 +1,6 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("org.jetbrains.kotlin.plugin.serialization") version "1.4.32"
id("kotlin-kapt")
}
@ -50,7 +49,6 @@ android {
}
kotlinOptions {
jvmTarget = "1.8"
useIR = true
}
buildFeatures {
compose = true
@ -65,76 +63,9 @@ android {
}
dependencies {
/**
* 针对最新的平台功能和 API 调整应用,同时还支持旧设备。
* https://developer.android.com/jetpack/androidx/releases/core
*/
implementation("androidx.core:core-ktx:1.3.2")
/**
* 允许在平台旧版 API 上访问新 API(很多使用 Material Design)。
* https://developer.android.com/jetpack/androidx/releases/appcompat
*/
implementation("androidx.appcompat:appcompat:1.2.0")
/**
* 与设备互动所需的 Compose UI 的基本组件,包括布局、绘图和输入。
* https://developer.android.com/jetpack/androidx/releases/compose-ui
*/
implementation("androidx.compose.ui:ui:${rootProject.extra["compose_version"]}")
implementation("androidx.compose.ui:ui-tooling:${rootProject.extra["compose_version"]}")
/**
* Compose 的编程模型和状态管理的基本构建块,以及 Compose 编译器插件针对的核心运行时。
* https://developer.android.com/jetpack/androidx/releases/compose-runtime
*/
implementation("androidx.compose.runtime:runtime-livedata:${rootProject.extra["compose_version"]}")
//Material Components
implementation("com.google.android.material:material:1.3.0")
/**
* 使用现成可用的 Material Design 组件构建 Jetpack Compose UI。这是更高层级的 Compose 入口点,旨在提供与 www.material.io 上描述的组件一致的组件。
* https://developer.android.com/jetpack/androidx/releases/compose-material
*/
implementation("androidx.compose.material:material:${rootProject.extra["compose_version"]}")
/**
* 生命周期感知型组件
* https://developer.android.com/jetpack/androidx/releases/lifecycle
*/
implementation("androidx.lifecycle:lifecycle-runtime-ktx:${rootProject.extra["lifecycle_version"]}")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha04")
/**
* 访问基于 Activity 构建的可组合 API。
* https://developer.android.com/jetpack/androidx/releases/activity
*/
implementation("androidx.activity:activity-compose:1.3.0-alpha07")
/**
* Simple, pretty and powerful logger for android
* https://github.com/orhanobut/logger
*/
implementation("com.orhanobut:logger:2.2.0")
/**
*
* https://github.com/square/okhttp
*/
implementation("com.squareup.okhttp3:okhttp:4.9.1")
/**
* https://github.com/google/gson
*/
implementation("com.google.code.gson:gson:2.8.6")
/**
* https://kotlinlang.org/docs/serialization.html
*/
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
implementation("org.jetbrains.kotlin:kotlin-reflect:${rootProject.extra["kotlin_version"]}")
/**
* https://developer.android.com/jetpack/androidx/releases/navigation
*/
implementation("androidx.navigation:navigation-compose:1.0.0-alpha10")
/**
* https://developer.android.com/jetpack/androidx/releases/room
*/
implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
implementation(project(":lib"))
kapt("androidx.room:room-compiler:${rootProject.extra["room_version"]}")
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
// optional - Test helpers
testImplementation("androidx.room:room-testing:${rootProject.extra["room_version"]}")
//测试
@ -152,5 +83,4 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:${rootProject.extra["compose_version"]}")
}

@ -1,112 +1,75 @@
package com.gyf.csams
import android.app.Application
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.LruCache
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.imageLoader
import coil.memory.MemoryCache
import coil.request.ImageRequest
import coil.util.CoilUtils
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.csams.util.ImageUtil
import com.orhanobut.logger.AndroidLogAdapter
import com.orhanobut.logger.DiskLogAdapter
import com.orhanobut.logger.Logger
import okhttp3.OkHttpClient
class APP : Application() {
class APP : Application(), ImageLoaderFactory {
private val backgroundImage = mutableMapOf<BackgroundImage, MemoryCache.Key?>()
private lateinit var memoryCache: LruCache<BackgroundImage, Bitmap>
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
// Use 1/8th of the available memory for this memory cache.
val cacheSize = maxMemory / 8
fun getImage(image: BackgroundImage, reqWidth: Int, reqHeight: Int): ImageBitmap {
val bitmap = memoryCache.get(image)
return if (bitmap == null) {
Logger.i("reqWidth=$reqWidth,reqHeight=$reqHeight")
val cacheValue = decodeSampledBitmapFromResource(
res = resources,
image.id,
reqWidth = reqWidth,
reqHeight = reqHeight
)
memoryCache.put(image, cacheValue)
Logger.i("添加缓存:${image}")
cacheValue.asImageBitmap()
suspend fun getImage(image: BackgroundImage): ImageBitmap? {
val key = backgroundImage[image]
if (key != null) {
return applicationContext.imageLoader.memoryCache[key]?.asImageBitmap()
?: ImageUtil.getImage(applicationContext, image.id)
} else {
Logger.i("从缓存读取:${image}")
bitmap.asImageBitmap()
throw IllegalArgumentException("无法从${key}获取背景图!")
}
}
private fun decodeSampledBitmapFromResource(
res: Resources,
resId: Int,
reqWidth: Int,
reqHeight: Int
): Bitmap {
// First decode with inJustDecodeBounds=true to check dimensions
return BitmapFactory.Options().run {
inJustDecodeBounds = true
BitmapFactory.decodeResource(res, resId, this)
// Calculate inSampleSize
inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
BitmapFactory.decodeResource(res, resId, this)
}
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(applicationContext)
.crossfade(true)
.okHttpClient {
OkHttpClient.Builder()
.cache(CoilUtils.createDefaultCache(applicationContext))
.build()
}
.build()
}
private fun calculateInSampleSize(
options: BitmapFactory.Options,
reqWidth: Int,
reqHeight: Int
): Int {
// Raw height and width of image
val (height: Int, width: Int) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
/**
* 预加载背景图
*
*/
private fun preloadImage() {
BackgroundImage.values().forEach {
Logger.i("预加载背景图:${it.name}")
val request = ImageRequest.Builder(applicationContext)
.data(it.id)
// Optional, but setting a ViewSizeResolver will conserve memory by limiting the size the image should be preloaded into memory at.
.size(800, 600)
.listener { _, metadata ->
Logger.i("metadata.memoryCacheKey=${metadata.memoryCacheKey}")
backgroundImage[it] = metadata.memoryCacheKey
}
.build()
applicationContext.imageLoader.enqueue(request = request)
}
return inSampleSize
}
override fun onCreate() {
super.onCreate()
memoryCache = object : LruCache<BackgroundImage, Bitmap>(cacheSize) {
override fun sizeOf(key: BackgroundImage, bitmap: Bitmap): Int {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.byteCount / 1024
}
}
//初始化日志
Logger.addLogAdapter(AndroidLogAdapter())
Logger.addLogAdapter(DiskLogAdapter())
Logger.i("${BuildConfig.foreground_app_name}启动")
preloadImage()
}
}

@ -11,10 +11,10 @@ interface UrlPath {
* @property path
*/
enum class AccountApi(val path: String) : UrlPath {
register("/register"),
checkId("/register/checkId"),
login("/login"),
loginToken("/login/token");
Register("/register"),
CheckId("/register/checkId"),
Login("/login"),
LoginToken("/login/token");
override fun build(): String {
@ -22,6 +22,14 @@ enum class AccountApi(val path: String) : UrlPath {
}
}
enum class MainApi(val path: String) : UrlPath {
HotActivity("/hotActivity");
override fun build(): String {
return "/api/main/${this.path}"
}
}
/**
* 构建服务端请求接口地址
*

@ -41,7 +41,7 @@ class InitViewModel : ViewModel() {
val tokenList = db?.tokenDao()?.queryAll()
if (tokenList != null && tokenList.size == 1) {
val currentToken: Token = tokenList[0]
val url = Api.buildUrl(AccountApi.loginToken)
val url = Api.buildUrl(AccountApi.LoginToken)
val action = "校验token"
Logger.i("${action}api=$url")
HttpClient.post(

@ -176,7 +176,7 @@ class AccountViewModel(application: Application) : AndroidViewModel(application)
} else {
_isRepeat.postValue(null)
checkJob = viewModelScope.launch {
val url = Api.buildUrl(AccountApi.checkId)
val url = Api.buildUrl(AccountApi.CheckId)
Logger.i("检测${studentId.formDesc},请求接口$url")
HttpClient.get(
url, SimpleCallback<Boolean>(
@ -232,7 +232,7 @@ class AccountViewModel(application: Application) : AndroidViewModel(application)
*/
fun register() {
if (checkForm()) {
val url = Api.buildUrl(AccountApi.register)
val url = Api.buildUrl(AccountApi.Register)
Logger.i("开始$regBtnDesc,请求接口:$url")
HttpClient.post(
url, SimpleCallback<UserResDto>(
@ -288,7 +288,7 @@ class AccountViewModel(application: Application) : AndroidViewModel(application)
*/
fun login() {
if (checkForm()) {
val url = Api.buildUrl(AccountApi.login)
val url = Api.buildUrl(AccountApi.Login)
Logger.i("开始$loginDesc,请求接口:$url")
HttpClient.post(
url,

@ -432,7 +432,7 @@ class AssociationActivity : ComponentActivity() {
val weight = 0.5F
val spaceWeight = (1 - 0.5F) / 2
Spacer(modifier = Modifier.weight(spaceWeight))
Poster(id = R.drawable.ic_launcher_foreground, modifier = Modifier.weight(weight))
Poster(modifier = Modifier.weight(weight))
Spacer(modifier = Modifier.weight(spaceWeight))
}

@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.gyf.csams.NOT_IMPL_TIP
import com.gyf.csams.R
import com.gyf.csams.uikit.ScrollList
import com.gyf.csams.uikit.SendInterface
import com.gyf.csams.uikit.StringForm
@ -43,39 +42,6 @@ class MarqueeViewModel : ViewModel() {
}
}
/**
* 海报轮播
*
*/
class CarouselViewModel : ViewModel() {
val imageList = listOf(
R.drawable.ic_launcher_foreground,
R.drawable.ic_account_fill,
R.drawable.ic_all_fill,
R.drawable.ic_home_fill
)
private val _index = MutableLiveData(0)
val index: LiveData<Int> = _index
private var job: Job? = null
init {
start()
}
private fun start() {
job = viewModelScope.launch {
do {
_index.postValue(if (_index.value == imageList.size - 1) 0 else _index.value?.plus(1))
delay(5000)
} while (job?.isActive == true)
}
}
}
/**
* 社团
*

@ -25,6 +25,8 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.gyf.csams.Api
import com.gyf.csams.MainApi
import com.gyf.csams.R
import com.gyf.csams.activity.ui.ActivityDetailActivity
import com.gyf.csams.association.ui.AssociationActivity
@ -41,9 +43,14 @@ import com.gyf.csams.util.randomChinese
*
*/
class MainActivity : ComponentActivity() {
lateinit var imageModel: ImageModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
imageModel = ImageModel(application = application, Api.buildUrl(MainApi.HotActivity))
setContent {
CSAMSTheme {
Body { nav, scaffoldState ->
@ -69,6 +76,22 @@ class MainActivity : ComponentActivity() {
}
override fun onStart() {
super.onStart()
imageModel.start()
}
override fun onResume() {
super.onResume()
imageModel.start()
}
override fun onPause() {
super.onPause()
imageModel.cancel()
}
/**
* 个人中心
*
@ -467,16 +490,17 @@ class MainActivity : ComponentActivity() {
*
*/
@Composable
private fun PosterWithDesc(model: CarouselViewModel = viewModel()) {
Carousel(model = model) {
val context = LocalContext.current
private fun PosterWithDesc() {
val context = LocalContext.current as MainActivity
Carousel(imageBitmap = context.imageModel.image) {
Column(modifier = Modifier.clickable(onClick = {
context.startActivity(Intent(context, ActivityDetailActivity::class.java))
})) {
Poster(
modifier = Modifier
.weight(0.6F)
.fillMaxWidth(), id = it
.fillMaxWidth()
)
DescCard(

@ -18,6 +18,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
@ -34,7 +35,6 @@ import androidx.navigation.compose.navigate
import androidx.navigation.compose.rememberNavController
import com.gyf.csams.APP
import com.gyf.csams.R
import com.gyf.csams.main.model.CarouselViewModel
import com.gyf.csams.main.model.MarqueeViewModel
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
@ -462,13 +462,14 @@ fun MainFrame(background: @Composable () -> Unit, body: @Composable ColumnScope.
*/
@Composable
fun Carousel(
model: CarouselViewModel = viewModel(),
imageBitmap: LiveData<ImageBitmap>,
durationMillis: Int = 2000,
content: @Composable (id: Int) -> Unit
content: @Composable (imageBitmap: ImageBitmap?) -> Unit
) {
val index: Int by model.index.observeAsState(0)
Crossfade(targetState = index, animationSpec = tween(durationMillis = durationMillis)) {
content(id = model.imageList[it])
val data by imageBitmap.observeAsState()
Crossfade(targetState = data, animationSpec = tween(durationMillis = durationMillis)) {
content(imageBitmap = it)
}
}
@ -600,20 +601,22 @@ enum class BackgroundImage(@DrawableRes val id: Int) {
@Composable
fun Background(image: BackgroundImage, alpha: Float = DefaultAlpha) {
val app = LocalContext.current.applicationContext as APP
BoxWithConstraints {
var i: ImageBitmap? by remember {
mutableStateOf(null)
}
LaunchedEffect(image) {
i = app.getImage(image = image)
}
i?.let {
Image(
bitmap = app.getImage(
image = image,
reqHeight = maxHeight.value.toInt() / 2,
reqWidth = maxWidth.value.toInt() / 2
),
bitmap = it,
contentDescription = null,
contentScale = ContentScale.FillHeight,
contentScale = ContentScale.FillBounds,
alpha = alpha,
modifier = Modifier.fillMaxSize()
)
}
}
/**
@ -654,7 +657,7 @@ fun Body(content: @Composable (nav: NavHostController, scaffoldState: ScaffoldSt
*
*/
@Composable
fun Poster(modifier: Modifier = Modifier, @DrawableRes id: Int) {
fun Poster(modifier: Modifier = Modifier, imageBitmap: ImageBitmap? = null) {
Card(
modifier = modifier,
backgroundColor = Color.Transparent
@ -665,11 +668,18 @@ fun Poster(modifier: Modifier = Modifier, @DrawableRes id: Int) {
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
Image(
painter = painterResource(id = id),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
if (imageBitmap != null) {
Image(
bitmap = imageBitmap,
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
} else {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = null
)
}
}
}
}
@ -764,4 +774,8 @@ fun MyBottomAppBarPreview() {
}
}
}
}
fun TestPreview() {
}

@ -1,10 +1,18 @@
package com.gyf.csams.uikit
import android.app.Application
import androidx.compose.material.SnackbarDuration
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.compose.ui.graphics.ImageBitmap
import androidx.lifecycle.*
import com.google.gson.reflect.TypeToken
import com.gyf.csams.R
import com.gyf.csams.util.HttpClient
import com.gyf.csams.util.ImageUtil
import com.gyf.csams.util.SimpleCallback
import com.orhanobut.logger.Logger
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
interface FormLength {
val nameLengthError: String
@ -77,6 +85,74 @@ class ScaffoldModel : ViewModel() {
}
}
class ImageModel(application: Application, private val urlPath: String) :
AndroidViewModel(application) {
private val _image = MutableLiveData<ImageBitmap>()
private val _imageUrls = MutableLiveData<List<String>>()
val image: LiveData<ImageBitmap> = _image
private var job: Job? = null
/**
* 加载默认图片
*
*/
private fun defaultLoad() {
viewModelScope.launch {
_image.postValue(
ImageUtil.getImage(
getApplication(),
R.drawable.ic_launcher_foreground
)
)
}
}
/**
* 循环加载网络图片
*
*/
fun start() {
Logger.i("启动轮播")
if (job == null || job?.isCompleted == true || job?.isCancelled == true) {
job = viewModelScope.launch {
HttpClient.get(
url = urlPath,
SimpleCallback<List<String>>("请求图片url列表", onSuccess = {
_imageUrls.postValue(it.body)
var index = 0
_imageUrls.value?.apply {
viewModelScope.launch {
do {
val imageBitmap =
ImageUtil.getImage(
context = getApplication(),
data = get(index)
)
Logger.e("成功从image url:${get(index)}解析图片")
_image.postValue(imageBitmap)
delay(5000)
index = if (index == size - 1) 0 else index.plus(1)
} while (job?.isActive == true)
}
}
}, onFail = {
Logger.e("无法从接口地址:${urlPath}获取图片url列表")
defaultLoad()
}, type = object : TypeToken<List<String>>() {}.type)
)
}.apply {
start()
}
}
}
fun cancel() {
Logger.i("停止轮播")
job?.cancel()
}
}
abstract class ScrollList<T> : ViewModel() {
protected val _data = MutableLiveData<MutableList<T>>(mutableListOf())
val data: LiveData<MutableList<T>> = _data

@ -15,7 +15,12 @@ object HttpClient {
private val JSON_CONTENT_TYPE = "application/json; charset=UTF-8".toMediaType()
/**
* 构建url查询参数
*
* @param params
* @return
*/
private fun buildQueryParams(params: Map<String, String>?): String {
return if (params?.isNotEmpty() == true) {
val urlPath = StringBuilder("?")
@ -29,6 +34,12 @@ object HttpClient {
}
}
/**
* 构建表单参数
*
* @param params
* @return
*/
private fun buildFormBody(params: Map<String, String>?): FormBody {
val builder = FormBody.Builder()
if (params?.isNotEmpty() == true) {
@ -92,10 +103,25 @@ object HttpClient {
}
/**
* 接口响应实体
*
* @param T
* @property code
* @property message
* @property body
*/
data class ApiResponse<T>(val code: Int, val message: String, val body: T? = null)
/**
* http请求回调
*
* @param T
* @property action
* @property onSuccess
* @property onFail
* @property type
*/
class SimpleCallback<T>(
private val action: String,
private val onSuccess: (res: ApiResponse<T>) -> Unit,

@ -0,0 +1,46 @@
package com.gyf.csams.util
import android.content.Context
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.core.graphics.drawable.toBitmap
import coil.imageLoader
import coil.request.ErrorResult
import coil.request.ImageRequest
import coil.request.SuccessResult
import com.orhanobut.logger.Logger
class ImageUtil {
companion object {
/**
*
*
* @param data Set the data to load.
* The default supported data types are:
* String (mapped to a Uri)
* Uri ("android.resource", "content", "file", "http", and "https" schemes only)
* HttpUrl
* File
* DrawableRes
* Drawable
* Bitmap
*/
suspend fun getImage(context: Context, data: Any?): ImageBitmap? {
val request = ImageRequest.Builder(context)
.data(data = data)
.build()
return when (val result = context.imageLoader.execute(request)) {
is SuccessResult -> result.drawable.toBitmap().asImageBitmap()
is ErrorResult -> {
val drawable = result.drawable
return if (drawable == null) {
Logger.e("${data}图片加载失败")
null
} else {
drawable.toBitmap().asImageBitmap()
}
}
}
}
}
}

@ -1,6 +1,5 @@
#Sat Apr 17 15:25:36 CST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

57
gradlew vendored

@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@ -24,11 +40,11 @@ cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
foreground_app_name="Gradle"
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@ -66,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@ -106,13 +123,14 @@ fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$foreground_app_name\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@ -138,19 +156,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@ -159,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

43
gradlew.bat vendored

@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

1
lib/.gitignore vendored

@ -0,0 +1 @@
/build

@ -0,0 +1,106 @@
plugins {
id("com.android.library")
id("kotlin-android")
id("org.jetbrains.kotlin.plugin.serialization") version "1.4.32"
}
android {
compileSdk = 30
defaultConfig {
minSdk = 21
targetSdk = 30
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
/**
* 针对最新的平台功能和 API 调整应用,同时还支持旧设备。
* https://developer.android.com/jetpack/androidx/releases/core
*/
api("androidx.core:core-ktx:1.3.2")
/**
* 允许在平台旧版 API 上访问新 API(很多使用 Material Design)。
* https://developer.android.com/jetpack/androidx/releases/appcompat
*/
api("androidx.appcompat:appcompat:1.2.0")
/**
* 与设备互动所需的 Compose UI 的基本组件,包括布局、绘图和输入。
* https://developer.android.com/jetpack/androidx/releases/compose-ui
*/
api("androidx.compose.ui:ui:${rootProject.extra["compose_version"]}")
api("androidx.compose.ui:ui-tooling:${rootProject.extra["compose_version"]}")
/**
* Compose 的编程模型和状态管理的基本构建块,以及 Compose 编译器插件针对的核心运行时。
* https://developer.android.com/jetpack/androidx/releases/compose-runtime
*/
api("androidx.compose.runtime:runtime-livedata:${rootProject.extra["compose_version"]}")
//Material Components
api("com.google.android.material:material:1.3.0")
/**
* 使用现成可用的 Material Design 组件构建 Jetpack Compose UI。这是更高层级的 Compose 入口点,旨在提供与 www.material.io 上描述的组件一致的组件。
* https://developer.android.com/jetpack/androidx/releases/compose-material
*/
api("androidx.compose.material:material:${rootProject.extra["compose_version"]}")
/**
* 生命周期感知型组件
* https://developer.android.com/jetpack/androidx/releases/lifecycle
*/
api("androidx.lifecycle:lifecycle-runtime-ktx:${rootProject.extra["lifecycle_version"]}")
api("androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha04")
/**
* 访问基于 Activity 构建的可组合 API。
* https://developer.android.com/jetpack/androidx/releases/activity
*/
api("androidx.activity:activity-compose:1.3.0-alpha07")
/**
* Simple, pretty and powerful logger for android
* https://github.com/orhanobut/logger
*/
api("com.orhanobut:logger:2.2.0")
/**
*
* https://github.com/square/okhttp
*/
api("com.squareup.okhttp3:okhttp:4.9.1")
/**
* https://github.com/google/gson
*/
api("com.google.code.gson:gson:2.8.6")
/**
* https://kotlinlang.org/docs/serialization.html
*/
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
/**
* https://developer.android.com/jetpack/androidx/releases/navigation
*/
api("androidx.navigation:navigation-compose:1.0.0-alpha10")
/**
* https://developer.android.com/jetpack/androidx/releases/room
*/
api("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
// optional - Kotlin Extensions and Coroutines support for Room
api("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
// https://github.com/coil-kt/coil/blob/master/README-zh.md
api("io.coil-kt:coil:1.2.1")
}

@ -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,22 @@
package com.gyf.lib
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.gyf.lib.test", appContext.packageName)
}
}

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.gyf.lib">
</manifest>

@ -0,0 +1,16 @@
package com.gyf.lib
import org.junit.Assert.*
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

@ -4,8 +4,14 @@ dependencyResolutionManagement {
maven("https://maven.aliyun.com/repository/google")
maven("https://maven.aliyun.com/repository/public")
maven("https://maven.aliyun.com/repository/jcenter")
google()
mavenCentral()
}
}
rootProject.name = "CSAMS"
//前台
include(":foreground")
//后台
include(":background")
//公共库
include(":lib")

Loading…
Cancel
Save