pull/32/head
atbest 6 years ago
commit 18c649c2ab
  1. 13
      app/build.gradle
  2. 52
      app/src/main/java/io/legado/app/base/BaseViewModel.kt
  3. 31
      app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt
  4. 14
      app/src/main/java/io/legado/app/data/api/CommonHttpApi.kt
  5. 107
      app/src/main/java/io/legado/app/help/http/CoroutinesCallAdapterFactory.kt
  6. 64
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  7. 175
      app/src/main/java/io/legado/app/help/http/SSLHelper.kt
  8. 94
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  9. 5
      app/src/main/java/io/legado/app/ui/search/SearchActivity.kt
  10. 37
      app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt
  11. 12
      app/src/main/java/io/legado/app/utils/ViewModelExtensions.kt
  12. 1
      app/src/main/res/layout/activity_search.xml

@ -41,6 +41,12 @@ kapt {
}
}
kotlin{
experimental{
coroutines "enable"
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@ -68,12 +74,17 @@ dependencies {
implementation "org.jetbrains.anko:anko-sdk27-listeners:$anko_version"
//
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
//
implementation 'pub.devrel:easypermissions:3.0.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.jayway.jsonpath:json-path:2.4.0'
implementation 'org.jsoup:jsoup:1.12.1'
//Retrofit
implementation 'com.squareup.okhttp3:logging-interceptor:3.14.0'//
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

@ -0,0 +1,52 @@
package io.legado.app.base
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
open class BaseViewModel(application: Application) : AndroidViewModel(application), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main
private val launchManager: MutableList<Job> = mutableListOf()
protected fun launchOnUI(
tryBlock: suspend CoroutineScope.() -> Unit,//成功
errorBlock: suspend CoroutineScope.(Throwable) -> Unit,//失败
finallyBlock: suspend CoroutineScope.() -> Unit//结束
) {
launchOnUI {
tryCatch(tryBlock, errorBlock, finallyBlock)
}
}
/**
* add launch task to [launchManager]
*/
private fun launchOnUI(block: suspend CoroutineScope.() -> Unit) {
val job = launch { block() }//主线程
launchManager.add(job)
job.invokeOnCompletion { launchManager.remove(job) }
}
private suspend fun tryCatch(
tryBlock: suspend CoroutineScope.() -> Unit,
errorBlock: suspend CoroutineScope.(Throwable) -> Unit,
finallyBlock: suspend CoroutineScope.() -> Unit
) {
try {
coroutineScope { tryBlock() }
} catch (e: Throwable) {
coroutineScope { errorBlock(e) }
} finally {
coroutineScope { finallyBlock() }
}
}
override fun onCleared() {
super.onCleared()
launchManager.clear()
}
}

@ -257,7 +257,7 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
return footerItems?.size() ?: 0
}
fun getItem(position: Int): ITEM = items[position % items.size]
fun getItem(position: Int): ITEM? = if (position in 0 until items.size) items[position] else null
fun getItems(): List<ITEM> = items
@ -280,7 +280,7 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
return when {
isHeader(position) -> TYPE_HEADER_VIEW + position
isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount()
else -> getItemViewType(getItem(getRealPosition(position)), getRealPosition(position))
else -> getItem(getActualPosition(position))?.let { getItemViewType(it, getActualPosition(position)) } ?: 0
}
}
@ -299,13 +299,17 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
if (itemClickListener != null) {
holder.itemView.setOnClickListener {
itemClickListener!!.invoke(holder, getItem(holder.layoutPosition))
getItem(holder.layoutPosition)?.let {
itemClickListener?.invoke(holder, it)
}
}
}
if (itemLongClickListener != null) {
holder.itemView.setOnLongClickListener {
itemLongClickListener!!.invoke(holder, getItem(holder.layoutPosition))
getItem(holder.layoutPosition)?.let {
itemLongClickListener?.invoke(holder, it) ?: true
} ?: true
}
}
@ -315,14 +319,15 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
onBindViewHolder(holder, position, mutableListOf())
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) {
getItem(holder.layoutPosition)?.let {
itemDelegates.getValue(getItemViewType(holder.layoutPosition))
.convert(holder, getItem(holder.layoutPosition), payloads)
.convert(holder, it, payloads)
}
}
}
@ -339,15 +344,17 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
if (manager is GridLayoutManager) {
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (isHeader(position) || isFooter(position)) manager.spanCount else getSpanSize(
getItem(position), getItemViewType(position), position
return getItem(position)?.let {
if (isHeader(position) || isFooter(position)) manager.spanCount else getSpanSize(
it, getItemViewType(position), position
)
} ?: manager.spanCount
}
}
}
}
fun setAnimationConfig(item: ItemAnimation) {
fun setItemAnimation(item: ItemAnimation) {
itemAnimation = item
}
@ -359,7 +366,7 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
return position >= getActualItemCount() + getHeaderCount()
}
private fun getRealPosition(position: Int): Int {
private fun getActualPosition(position: Int): Int {
return position - getHeaderCount()
}

@ -0,0 +1,14 @@
package io.legado.app.data.api
import kotlinx.coroutines.Deferred
import retrofit2.http.*
interface CommonHttpApi {
@GET
fun get(@Url url: String, @QueryMap map: Map<String, String>): Deferred<String>
@FormUrlEncoded
@POST
fun post(@Url url: String, @FieldMap map: Map<String, String>): Deferred<String>
}

@ -0,0 +1,107 @@
package io.legado.app.help.http
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import retrofit2.*
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
class CoroutinesCallAdapterFactory private constructor() : CallAdapter.Factory() {
companion object {
@JvmStatic @JvmName("create")
operator fun invoke() = CoroutinesCallAdapterFactory()
}
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (Deferred::class.java != getRawType(returnType)) {
return null
}
if (returnType !is ParameterizedType) {
throw IllegalStateException(
"Deferred return type must be parameterized as Deferred<Foo> or Deferred<out Foo>")
}
val responseType = getParameterUpperBound(0, returnType)
val rawDeferredType = getRawType(responseType)
return if (rawDeferredType == Response::class.java) {
if (responseType !is ParameterizedType) {
throw IllegalStateException(
"Response must be parameterized as Response<Foo> or Response<out Foo>")
}
ResponseCallAdapter<Any>(
getParameterUpperBound(
0,
responseType
)
)
} else {
BodyCallAdapter<Any>(responseType)
}
}
private class BodyCallAdapter<T>(
private val responseType: Type
) : CallAdapter<T, Deferred<T>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<T> {
val deferred = CompletableDeferred<T>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
deferred.complete(response.body()!!)
} else {
deferred.completeExceptionally(HttpException(response))
}
}
})
return deferred
}
}
private class ResponseCallAdapter<T>(
private val responseType: Type
) : CallAdapter<T, Deferred<Response<T>>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<Response<T>> {
val deferred = CompletableDeferred<Response<T>>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
deferred.complete(response)
}
})
return deferred
}
}
}

@ -0,0 +1,64 @@
package io.legado.app.help.http
import okhttp3.*
import retrofit2.Retrofit
import java.util.*
import java.util.concurrent.TimeUnit
object HttpHelper {
val client: OkHttpClient = getOkHttpClient()
fun <T> getApiService(baseUrl: String, clazz: Class<T>): T {
return getRetrofit(baseUrl).create(clazz)
}
fun getRetrofit(baseUrl: String): Retrofit {
return Retrofit.Builder().baseUrl(baseUrl)
//增加返回值为字符串的支持(以实体类返回)
// .addConverterFactory(EncodeConverter.create())
//增加返回值为Observable<T>的支持
.addCallAdapterFactory(CoroutinesCallAdapterFactory.invoke())
.client(client)
.build()
}
private fun getOkHttpClient(): OkHttpClient {
val cs = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.build()
val specs = ArrayList<ConnectionSpec>()
specs.add(cs)
specs.add(ConnectionSpec.COMPATIBLE_TLS)
specs.add(ConnectionSpec.CLEARTEXT)
val sslParams = SSLHelper.getSslSocketFactory()
return OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
.hostnameVerifier(SSLHelper.unsafeHostnameVerifier)
.connectionSpecs(specs)
.followRedirects(true)
.followSslRedirects(true)
.protocols(listOf(Protocol.HTTP_1_1))
.addInterceptor(getHeaderInterceptor())
.build()
}
private fun getHeaderInterceptor(): Interceptor {
return Interceptor { chain ->
val request = chain.request()
.newBuilder()
.addHeader("Keep-Alive", "300")
.addHeader("Connection", "Keep-Alive")
.addHeader("Cache-Control", "no-cache")
.build()
chain.proceed(request)
}
}
}

@ -0,0 +1,175 @@
package io.legado.app.help.http
import javax.net.ssl.*
import java.io.IOException
import java.io.InputStream
import java.security.KeyManagementException
import java.security.KeyStore
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
object SSLHelper {
val sslSocketFactory: SSLParams
get() = getSslSocketFactoryBase(null, null, null)
/**
* 为了解决客户端不信任服务器数字证书的问题网络上大部分的解决方案都是让客户端不对证书做任何检查
* 这是一种有很大安全漏洞的办法
*/
val unsafeTrustManager: X509TrustManager = object : X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
}
/**
* 此类是用于主机名验证的基接口 在握手期间如果 URL 的主机名和服务器的标识主机名不匹配
* 则验证机制可以回调此接口的实现程序来确定是否应该允许此连接策略可以是基于证书的或依赖于其他验证方案
* 当验证 URL 主机名使用的默认规则失败时使用这些回调如果主机名是可接受的则返回 true
*/
val unsafeHostnameVerifier: HostnameVerifier = HostnameVerifier { _, _ -> true }
class SSLParams {
var sSLSocketFactory: SSLSocketFactory? = null
var trustManager: X509TrustManager? = null
}
/**
* https单向认证
* 可以额外配置信任服务端的证书策略否则默认是按CA证书去验证的若不是CA可信任的证书则无法通过验证
*/
fun getSslSocketFactory(trustManager: X509TrustManager): SSLParams {
return getSslSocketFactoryBase(trustManager, null, null)
}
/**
* https单向认证
* 用含有服务端公钥的证书校验服务端证书
*/
fun getSslSocketFactory(vararg certificates: InputStream): SSLParams {
return getSslSocketFactoryBase(null, null, null, *certificates)
}
/**
* https双向认证
* bksFile password -> 客户端使用bks证书校验服务端证书
* certificates -> 用含有服务端公钥的证书校验服务端证书
*/
fun getSslSocketFactory(bksFile: InputStream, password: String, vararg certificates: InputStream): SSLParams {
return getSslSocketFactoryBase(null, bksFile, password, *certificates)
}
/**
* https双向认证
* bksFile password -> 客户端使用bks证书校验服务端证书
* X509TrustManager -> 如果需要自己校验那么可以自己实现相关校验如果不需要自己校验那么传null即可
*/
fun getSslSocketFactory(bksFile: InputStream, password: String, trustManager: X509TrustManager): SSLParams {
return getSslSocketFactoryBase(trustManager, bksFile, password)
}
private fun getSslSocketFactoryBase(
trustManager: X509TrustManager?,
bksFile: InputStream?,
password: String?,
vararg certificates: InputStream
): SSLParams {
val sslParams = SSLParams()
try {
val keyManagers = prepareKeyManager(bksFile, password)
val trustManagers = prepareTrustManager(*certificates)
val manager: X509TrustManager?
manager = //优先使用用户自定义的TrustManager
trustManager ?: if (trustManagers != null) {
//然后使用默认的TrustManager
chooseTrustManager(trustManagers)
} else {
//否则使用不安全的TrustManager
unsafeTrustManager
}
// 创建TLS类型的SSLContext对象, that uses our TrustManager
val sslContext = SSLContext.getInstance("TLS")
// 用上面得到的trustManagers初始化SSLContext,这样sslContext就会信任keyStore中的证书
// 第一个参数是授权的密钥管理器,用来授权验证,比如授权自签名的证书验证。第二个是被授权的证书管理器,用来验证服务器端的证书
sslContext.init(keyManagers, manager?.let { arrayOf<TrustManager>(it) }, null)
// 通过sslContext获取SSLSocketFactory对象
sslParams.sSLSocketFactory = sslContext.socketFactory
sslParams.trustManager = manager
return sslParams
} catch (e: NoSuchAlgorithmException) {
throw AssertionError(e)
} catch (e: KeyManagementException) {
throw AssertionError(e)
}
}
private fun prepareKeyManager(bksFile: InputStream?, password: String?): Array<KeyManager>? {
try {
if (bksFile == null || password == null) return null
val clientKeyStore = KeyStore.getInstance("BKS")
clientKeyStore.load(bksFile, password.toCharArray())
val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
kmf.init(clientKeyStore, password.toCharArray())
return kmf.keyManagers
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
private fun prepareTrustManager(vararg certificates: InputStream): Array<TrustManager>? {
if (certificates.isEmpty()) return null
try {
val certificateFactory = CertificateFactory.getInstance("X.509")
// 创建一个默认类型的KeyStore,存储我们信任的证书
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
keyStore.load(null)
var index = 0
for (certStream in certificates) {
val certificateAlias = Integer.toString(index++)
// 证书工厂根据证书文件的流生成证书 cert
val cert = certificateFactory.generateCertificate(certStream)
// 将 cert 作为可信证书放入到keyStore中
keyStore.setCertificateEntry(certificateAlias, cert)
try {
certStream?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
//我们创建一个默认类型的TrustManagerFactory
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
//用我们之前的keyStore实例初始化TrustManagerFactory,这样tmf就会信任keyStore中的证书
tmf.init(keyStore)
//通过tmf获取TrustManager数组,TrustManager也会信任keyStore中的证书
return tmf.trustManagers
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
private fun chooseTrustManager(trustManagers: Array<TrustManager>): X509TrustManager? {
for (trustManager in trustManagers) {
if (trustManager is X509TrustManager) {
return trustManager
}
}
return null
}
}

@ -1,47 +1,26 @@
package io.legado.app.ui.main
import android.Manifest
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.navigation.NavigationView
import com.jayway.jsonpath.Configuration
import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.Option
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.constant.AppConst.APP_TAG
import io.legado.app.constant.AppConst.RC_IMPORT_YUEDU_DATA
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.ui.replacerule.ReplaceRuleActivity
import io.legado.app.ui.search.SearchActivity
import io.legado.app.utils.getSdPath
import io.legado.app.utils.readBool
import io.legado.app.utils.readInt
import io.legado.app.utils.readString
import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar_main.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.startActivity
import org.jetbrains.anko.uiThread
import pub.devrel.easypermissions.AfterPermissionGranted
import pub.devrel.easypermissions.EasyPermissions
import java.io.File
import java.lang.Exception
class MainActivity : BaseActivity<MainDataBinding, MainViewModel>(), NavigationView.OnNavigationItemSelectedListener {
override val viewModel: MainViewModel
get() = ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(MainViewModel::class.java)
get() = getViewModel(MainViewModel::class.java)
override val layoutID: Int
get() = R.layout.activity_main
private val PERMISSONS = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
override fun onViewModelCreated(viewModel: MainViewModel, savedInstanceState: Bundle?) {
fab.setOnClickListener { startActivity(Intent(this, SearchActivity::class.java)) }
@ -79,17 +58,6 @@ class MainActivity : BaseActivity<MainDataBinding, MainViewModel>(), NavigationV
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here.
when (item.itemId) {
R.id.nav_backup -> {
// Handle the camera action
}
R.id.nav_import -> {
}
R.id.nav_import_old -> importYueDu()
R.id.nav_import_github -> {
}
R.id.nav_replace_rule -> startActivity<ReplaceRuleActivity>()
R.id.nav_send -> {
}
@ -98,60 +66,4 @@ class MainActivity : BaseActivity<MainDataBinding, MainViewModel>(), NavigationV
drawerLayout.closeDrawer(GravityCompat.START)
return true
}
/*
* import from YueDu backup data
* */
@AfterPermissionGranted(RC_IMPORT_YUEDU_DATA)
fun importYueDu() {
if (!EasyPermissions.hasPermissions(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
EasyPermissions.requestPermissions(this, getString(R.string.perm_request_storage), RC_IMPORT_YUEDU_DATA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
return
}
val yuedu = File(getSdPath(), "YueDu")
val jsonPath = JsonPath.using(Configuration.builder()
.options(Option.SUPPRESS_EXCEPTIONS)
.build())
// Replace rules
val rFile = File(yuedu, "myBookReplaceRule.json")
val replaceRules = mutableListOf<ReplaceRule>()
if (rFile.exists()) try {
val items: List<Map<String, Any>> = jsonPath.parse(rFile.readText()).read("$.*")
for (item in items) {
val jsonItem = jsonPath.parse(item)
val rRule = ReplaceRule()
rRule.name = jsonItem.readString("$.replaceSummary")
rRule.pattern = jsonItem.readString("$.regex")
rRule.replacement = jsonItem.readString("$.replacement")
rRule.isRegex = jsonItem.readBool("$.isRegex")
rRule.scope = jsonItem.readString("$.useTo")
rRule.isEnabled = jsonItem.readBool("$.enable")
rRule.order = jsonItem.readInt("$.serialNumber")
replaceRules.add(rRule)
// Log.e(APP_TAG, rRule.toString())
}
doAsync {
App.db.replaceRuleDao().insert(*replaceRules.toTypedArray())
val count = App.db.replaceRuleDao().all.size
val maxId = App.db.replaceRuleDao().maxOrder
uiThread {
Log.e(APP_TAG, "$count records were inserted to database, and max id is $maxId.")
}
}
} catch (e: Exception) {
Log.e(APP_TAG, e.localizedMessage)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
// Forward results to EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
}
}

@ -1,15 +1,14 @@
package io.legado.app.ui.search
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.help.LayoutManager
import io.legado.app.utils.getViewModel
class SearchActivity : BaseActivity<SearchDataBinding, SearchViewModel>() {
override val viewModel: SearchViewModel
get() = ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(SearchViewModel::class.java)
get() = getViewModel(SearchViewModel::class.java)
override val layoutID: Int
get() = R.layout.activity_search

@ -1,6 +1,39 @@
package io.legado.app.ui.search
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import io.legado.app.base.BaseViewModel
import io.legado.app.data.api.CommonHttpApi
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.http.HttpHelper
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext
class SearchViewModel(application: Application) : AndroidViewModel(application)
class SearchViewModel(application: Application) : BaseViewModel(application) {
val searchBooks: LiveData<List<SearchBook>> = MutableLiveData()
public fun search(start: () -> Unit, finally: () -> Unit) {
launchOnUI(
{
start()
val searchResponse = withContext(IO) {
HttpHelper.getApiService(
"http:www.baidu.com",
CommonHttpApi::class.java
).get("", mutableMapOf())
}
val result = searchResponse.await()
},
{ Log.i("TAG", "${it.message}") },
{ finally() })
// GlobalScope.launch {
//
// }
}
}

@ -0,0 +1,12 @@
package io.legado.app.utils
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProviders
fun <T : ViewModel> AppCompatActivity.getViewModel(clazz: Class<T>) = ViewModelProviders.of(this).get(clazz)
fun <T : ViewModel> Fragment.getViewModel(clazz: Class<T>) = ViewModelProviders.of(this).get(clazz)
fun <T : ViewModel> Fragment.getViewModelOfActivity(clazz: Class<T>) = ViewModelProviders.of(requireActivity()).get(clazz)

@ -2,7 +2,6 @@
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data class=".ui.search.SearchDataBinding">
<variable name="SearchViewModel" type="io.legado.app.ui.search.SearchViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout

Loading…
Cancel
Save