diff --git a/app/build.gradle b/app/build.gradle index 3a59b113a..966ba8516 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/io/legado/app/base/BaseViewModel.kt b/app/src/main/java/io/legado/app/base/BaseViewModel.kt new file mode 100644 index 000000000..74bc4bfc2 --- /dev/null +++ b/app/src/main/java/io/legado/app/base/BaseViewModel.kt @@ -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 = 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() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt b/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt index c9b9f0cf2..ce7b3d4a6 100644 --- a/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt +++ b/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt @@ -257,7 +257,7 @@ abstract class CommonRecyclerAdapter(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 = items @@ -280,7 +280,7 @@ abstract class CommonRecyclerAdapter(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(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(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) { + final override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList) { if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) { - itemDelegates.getValue(getItemViewType(holder.layoutPosition)) - .convert(holder, getItem(holder.layoutPosition), payloads) + getItem(holder.layoutPosition)?.let { + itemDelegates.getValue(getItemViewType(holder.layoutPosition)) + .convert(holder, it, payloads) + } } } @@ -339,15 +344,17 @@ abstract class CommonRecyclerAdapter(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(protected val context: Context) : Rec return position >= getActualItemCount() + getHeaderCount() } - private fun getRealPosition(position: Int): Int { + private fun getActualPosition(position: Int): Int { return position - getHeaderCount() } diff --git a/app/src/main/java/io/legado/app/data/api/CommonHttpApi.kt b/app/src/main/java/io/legado/app/data/api/CommonHttpApi.kt new file mode 100644 index 000000000..08e06f52f --- /dev/null +++ b/app/src/main/java/io/legado/app/data/api/CommonHttpApi.kt @@ -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): Deferred + + @FormUrlEncoded + @POST + fun post(@Url url: String, @FieldMap map: Map): Deferred +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/CoroutinesCallAdapterFactory.kt b/app/src/main/java/io/legado/app/help/http/CoroutinesCallAdapterFactory.kt new file mode 100644 index 000000000..bf296f5bb --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/CoroutinesCallAdapterFactory.kt @@ -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, + 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 or Deferred") + } + 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 or Response") + } + ResponseCallAdapter( + getParameterUpperBound( + 0, + responseType + ) + ) + } else { + BodyCallAdapter(responseType) + } + } + + private class BodyCallAdapter( + private val responseType: Type + ) : CallAdapter> { + + override fun responseType() = responseType + + override fun adapt(call: Call): Deferred { + val deferred = CompletableDeferred() + + deferred.invokeOnCompletion { + if (deferred.isCancelled) { + call.cancel() + } + } + + call.enqueue(object : Callback { + override fun onFailure(call: Call, t: Throwable) { + deferred.completeExceptionally(t) + } + + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + deferred.complete(response.body()!!) + } else { + deferred.completeExceptionally(HttpException(response)) + } + } + }) + + return deferred + } + } + + private class ResponseCallAdapter( + private val responseType: Type + ) : CallAdapter>> { + + override fun responseType() = responseType + + override fun adapt(call: Call): Deferred> { + val deferred = CompletableDeferred>() + + deferred.invokeOnCompletion { + if (deferred.isCancelled) { + call.cancel() + } + } + + call.enqueue(object : Callback { + override fun onFailure(call: Call, t: Throwable) { + deferred.completeExceptionally(t) + } + + override fun onResponse(call: Call, response: Response) { + deferred.complete(response) + } + }) + + return deferred + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt new file mode 100644 index 000000000..59d92b07d --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt @@ -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 getApiService(baseUrl: String, clazz: Class): T { + return getRetrofit(baseUrl).create(clazz) + } + + fun getRetrofit(baseUrl: String): Retrofit { + return Retrofit.Builder().baseUrl(baseUrl) + //增加返回值为字符串的支持(以实体类返回) +// .addConverterFactory(EncodeConverter.create()) + //增加返回值为Observable的支持 + .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() + 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/SSLHelper.kt b/app/src/main/java/io/legado/app/help/http/SSLHelper.kt new file mode 100644 index 000000000..dcf5b7709 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/SSLHelper.kt @@ -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, authType: String) { + } + + @Throws(CertificateException::class) + override fun checkServerTrusted(chain: Array, authType: String) { + } + + override fun getAcceptedIssuers(): Array { + 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(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? { + 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? { + 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): X509TrustManager? { + for (trustManager in trustManagers) { + if (trustManager is X509TrustManager) { + return trustManager + } + } + return null + } +} diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index db76d4916..ce40a7e33 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -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(), 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(), 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() R.id.nav_send -> { } @@ -98,60 +66,4 @@ class MainActivity : BaseActivity(), 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() - if (rFile.exists()) try { - val items: List> = 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, grantResults: IntArray) { - // Forward results to EasyPermissions - EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) - } - - } diff --git a/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt index 14994c535..226c9207d 100644 --- a/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt @@ -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() { 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 diff --git a/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt index a9a847a5b..0f9f2072b 100644 --- a/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt @@ -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> = 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 { +// +// } + } + +} diff --git a/app/src/main/java/io/legado/app/utils/ViewModelExtensions.kt b/app/src/main/java/io/legado/app/utils/ViewModelExtensions.kt new file mode 100644 index 000000000..ed963ec8e --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/ViewModelExtensions.kt @@ -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 AppCompatActivity.getViewModel(clazz: Class) = ViewModelProviders.of(this).get(clazz) + +fun Fragment.getViewModel(clazz: Class) = ViewModelProviders.of(this).get(clazz) + +fun Fragment.getViewModelOfActivity(clazz: Class) = ViewModelProviders.of(requireActivity()).get(clazz) diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml index da4d4a9c6..9e2a9e07c 100644 --- a/app/src/main/res/layout/activity_search.xml +++ b/app/src/main/res/layout/activity_search.xml @@ -2,7 +2,6 @@ -