添加公共库

添加Coil 图片加载库
背景图片在应用启动时预加载
master
pan 4 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 { buildTypes {
val appName = "${rootProject.extra["background_app_name"]}"
debug {
manifestPlaceholders.apply {
this["background_app_name"] = appName
}
}
release { release {
isMinifyEnabled = false isMinifyEnabled = false
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" "proguard-rules.pro"
) )
manifestPlaceholders.apply {
this["background_app_name"] = appName
}
} }
} }
compileOptions { compileOptions {
@ -34,29 +43,32 @@ android {
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
useIR = true
} }
buildFeatures { buildFeatures {
compose = true compose = true
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String
kotlinCompilerVersion = "1.4.32"
} }
} }
dependencies { dependencies {
implementation(project(":lib"))
implementation("androidx.core:core-ktx:1.3.2") // optional - Test helpers
implementation("androidx.appcompat:appcompat:1.2.0") testImplementation("androidx.room:room-testing:${rootProject.extra["room_version"]}")
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")
testImplementation("junit:junit:4.13.2") 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") 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.test.espresso:espresso-core:3.3.0")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:${rootProject.extra["compose_version"]}") 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. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
//Jetpack Compose版本 //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") val lifecycle_version by extra("2.3.1")
//APP应用名字 //APP应用名字
@ -13,6 +13,8 @@ buildscript {
repositories { repositories {
maven("https://maven.aliyun.com/repository/google") maven("https://maven.aliyun.com/repository/google")
maven("https://maven.aliyun.com/repository/public") maven("https://maven.aliyun.com/repository/public")
google()
mavenCentral()
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:7.0.0-alpha15") classpath("com.android.tools.build:gradle:7.0.0-alpha15")

@ -1,7 +1,6 @@
plugins { plugins {
id("com.android.application") id("com.android.application")
id("kotlin-android") id("kotlin-android")
id("org.jetbrains.kotlin.plugin.serialization") version "1.4.32"
id("kotlin-kapt") id("kotlin-kapt")
} }
@ -50,7 +49,6 @@ android {
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
useIR = true
} }
buildFeatures { buildFeatures {
compose = true compose = true
@ -65,76 +63,9 @@ android {
} }
dependencies { dependencies {
/** implementation(project(":lib"))
* 针对最新的平台功能和 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"]}")
kapt("androidx.room:room-compiler:${rootProject.extra["room_version"]}") 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 // optional - Test helpers
testImplementation("androidx.room:room-testing:${rootProject.extra["room_version"]}") 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.test.espresso:espresso-core:3.3.0")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:${rootProject.extra["compose_version"]}") androidTestImplementation("androidx.compose.ui:ui-test-junit4:${rootProject.extra["compose_version"]}")
} }

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

@ -11,10 +11,10 @@ interface UrlPath {
* @property path * @property path
*/ */
enum class AccountApi(val path: String) : UrlPath { enum class AccountApi(val path: String) : UrlPath {
register("/register"), Register("/register"),
checkId("/register/checkId"), CheckId("/register/checkId"),
login("/login"), Login("/login"),
loginToken("/login/token"); LoginToken("/login/token");
override fun build(): String { 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() val tokenList = db?.tokenDao()?.queryAll()
if (tokenList != null && tokenList.size == 1) { if (tokenList != null && tokenList.size == 1) {
val currentToken: Token = tokenList[0] val currentToken: Token = tokenList[0]
val url = Api.buildUrl(AccountApi.loginToken) val url = Api.buildUrl(AccountApi.LoginToken)
val action = "校验token" val action = "校验token"
Logger.i("${action}api=$url") Logger.i("${action}api=$url")
HttpClient.post( HttpClient.post(

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

@ -432,7 +432,7 @@ class AssociationActivity : ComponentActivity() {
val weight = 0.5F val weight = 0.5F
val spaceWeight = (1 - 0.5F) / 2 val spaceWeight = (1 - 0.5F) / 2
Spacer(modifier = Modifier.weight(spaceWeight)) 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)) Spacer(modifier = Modifier.weight(spaceWeight))
} }

@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.gyf.csams.NOT_IMPL_TIP import com.gyf.csams.NOT_IMPL_TIP
import com.gyf.csams.R
import com.gyf.csams.uikit.ScrollList import com.gyf.csams.uikit.ScrollList
import com.gyf.csams.uikit.SendInterface import com.gyf.csams.uikit.SendInterface
import com.gyf.csams.uikit.StringForm 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.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import com.gyf.csams.Api
import com.gyf.csams.MainApi
import com.gyf.csams.R import com.gyf.csams.R
import com.gyf.csams.activity.ui.ActivityDetailActivity import com.gyf.csams.activity.ui.ActivityDetailActivity
import com.gyf.csams.association.ui.AssociationActivity import com.gyf.csams.association.ui.AssociationActivity
@ -41,9 +43,14 @@ import com.gyf.csams.util.randomChinese
* *
*/ */
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
lateinit var imageModel: ImageModel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
imageModel = ImageModel(application = application, Api.buildUrl(MainApi.HotActivity))
setContent { setContent {
CSAMSTheme { CSAMSTheme {
Body { nav, scaffoldState -> 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 @Composable
private fun PosterWithDesc(model: CarouselViewModel = viewModel()) { private fun PosterWithDesc() {
Carousel(model = model) {
val context = LocalContext.current val context = LocalContext.current as MainActivity
Carousel(imageBitmap = context.imageModel.image) {
Column(modifier = Modifier.clickable(onClick = { Column(modifier = Modifier.clickable(onClick = {
context.startActivity(Intent(context, ActivityDetailActivity::class.java)) context.startActivity(Intent(context, ActivityDetailActivity::class.java))
})) { })) {
Poster( Poster(
modifier = Modifier modifier = Modifier
.weight(0.6F) .weight(0.6F)
.fillMaxWidth(), id = it .fillMaxWidth()
) )
DescCard( DescCard(

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

@ -1,10 +1,18 @@
package com.gyf.csams.uikit package com.gyf.csams.uikit
import android.app.Application
import androidx.compose.material.SnackbarDuration import androidx.compose.material.SnackbarDuration
import androidx.lifecycle.LiveData import androidx.compose.ui.graphics.ImageBitmap
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.*
import androidx.lifecycle.ViewModel 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 com.orhanobut.logger.Logger
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
interface FormLength { interface FormLength {
val nameLengthError: String 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() { abstract class ScrollList<T> : ViewModel() {
protected val _data = MutableLiveData<MutableList<T>>(mutableListOf()) protected val _data = MutableLiveData<MutableList<T>>(mutableListOf())
val data: LiveData<MutableList<T>> = _data val data: LiveData<MutableList<T>> = _data

@ -15,7 +15,12 @@ object HttpClient {
private val JSON_CONTENT_TYPE = "application/json; charset=UTF-8".toMediaType() private val JSON_CONTENT_TYPE = "application/json; charset=UTF-8".toMediaType()
/**
* 构建url查询参数
*
* @param params
* @return
*/
private fun buildQueryParams(params: Map<String, String>?): String { private fun buildQueryParams(params: Map<String, String>?): String {
return if (params?.isNotEmpty() == true) { return if (params?.isNotEmpty() == true) {
val urlPath = StringBuilder("?") val urlPath = StringBuilder("?")
@ -29,6 +34,12 @@ object HttpClient {
} }
} }
/**
* 构建表单参数
*
* @param params
* @return
*/
private fun buildFormBody(params: Map<String, String>?): FormBody { private fun buildFormBody(params: Map<String, String>?): FormBody {
val builder = FormBody.Builder() val builder = FormBody.Builder()
if (params?.isNotEmpty() == true) { 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) 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>( class SimpleCallback<T>(
private val action: String, private val action: String,
private val onSuccess: (res: ApiResponse<T>) -> Unit, 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 distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

57
gradlew vendored

@ -1,5 +1,21 @@
#!/usr/bin/env sh #!/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 ## Gradle start up script for UN*X
@ -24,11 +40,11 @@ cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`" APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null cd "$SAVED" >/dev/null
foreground_app_name="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` 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. # 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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD="maximum"
@ -66,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; 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 # For Darwin, add options to specify how the application appears in the dock
if $darwin; then 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 fi
# For Cygwin, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if $cygwin ; then if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath # We build the pattern for arguments to be converted via cygpath
@ -138,19 +156,19 @@ if $cygwin ; then
else else
eval `echo args$i`="\"$arg\"" eval `echo args$i`="\"$arg\""
fi fi
i=$((i+1)) i=`expr $i + 1`
done done
case $i in case $i in
(0) set -- ;; 0) set -- ;;
(1) set -- "$args0" ;; 1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;; 2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;; 3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
@ -159,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " " echo " "
} }
APP_ARGS=$(save "$@") APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules # 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" 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" "$@" 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 @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% 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. @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 @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 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_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail 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 :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @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 :end
@rem End local scope for the variables with windows NT shell @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/google")
maven("https://maven.aliyun.com/repository/public") maven("https://maven.aliyun.com/repository/public")
maven("https://maven.aliyun.com/repository/jcenter") maven("https://maven.aliyun.com/repository/jcenter")
google()
mavenCentral()
} }
} }
rootProject.name = "CSAMS" rootProject.name = "CSAMS"
//前台
include(":foreground") include(":foreground")
//后台
include(":background") include(":background")
//公共库
include(":lib")

Loading…
Cancel
Save