Optimize imports, disallow wildcard imports because of klint, run linter

This commit is contained in:
jobobby04
2020-04-04 16:30:05 -04:00
committed by Jobobby04
parent f18891a07e
commit 23ac3d18e5
138 changed files with 1192 additions and 1027 deletions
+7 -7
View File
@@ -1,18 +1,18 @@
package exh.ui
import java.util.UUID
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
typealias LoadingHandle = String
/**
* Class used to manage loader UIs
*/
class LoaderManager(parentContext: CoroutineContext = EmptyCoroutineContext): CoroutineScope {
class LoaderManager(parentContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope {
override val coroutineContext = Dispatchers.Main + parentContext
private val openLoadingHandles = mutableListOf<LoadingHandle>()
@@ -25,7 +25,7 @@ class LoaderManager(parentContext: CoroutineContext = EmptyCoroutineContext): Co
handle to (openLoadingHandles.size == 1)
}
if(shouldUpdateLoadingStatus) {
if (shouldUpdateLoadingStatus) {
launch {
updateLoadingStatus(true)
}
@@ -36,13 +36,13 @@ class LoaderManager(parentContext: CoroutineContext = EmptyCoroutineContext): Co
@Synchronized
fun closeProgressBar(handle: LoadingHandle?) {
if(handle == null) return
if (handle == null) return
val shouldUpdateLoadingStatus = synchronized(this) {
openLoadingHandles.remove(handle) && openLoadingHandles.isEmpty()
}
if(shouldUpdateLoadingStatus) {
if (shouldUpdateLoadingStatus) {
launch {
updateLoadingStatus(false)
}
@@ -6,11 +6,11 @@ import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlin.coroutines.CoroutineContext
abstract class BaseExhController(bundle: Bundle? = null) : BaseController(bundle), CoroutineScope {
abstract val layoutId: Int
@@ -1,6 +1,5 @@
package exh.ui.batchadd
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -44,14 +43,14 @@ class BatchAddController : NucleusController<BatchAddPresenter>() {
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
progressSubscriptions.clear()
if(it == BatchAddPresenter.STATE_INPUT_TO_PROGRESS) {
if (it == BatchAddPresenter.STATE_INPUT_TO_PROGRESS) {
showProgress(this)
progressSubscriptions += presenter.progressRelay
.observeOn(AndroidSchedulers.mainThread())
.combineLatest(presenter.progressTotalRelay, { progress, total ->
//Show hide dismiss button
// Show hide dismiss button
progress_dismiss_btn.visibility =
if(progress == total)
if (progress == total)
View.VISIBLE
else View.GONE
@@ -79,7 +78,7 @@ class BatchAddController : NucleusController<BatchAddPresenter>() {
}?.let {
progressSubscriptions += it
}
} else if(it == BatchAddPresenter.STATE_PROGRESS_TO_INPUT) {
} else if (it == BatchAddPresenter.STATE_PROGRESS_TO_INPUT) {
hideProgress(this)
presenter.currentlyAddingRelay.call(BatchAddPresenter.STATE_IDLE)
}
@@ -124,8 +123,8 @@ class BatchAddController : NucleusController<BatchAddPresenter>() {
private fun formatProgress(progress: Int, total: Int) = "$progress/$total"
private fun addGalleries(galleries: String) {
//Check text box has content
if(galleries.isBlank()) {
// Check text box has content
if (galleries.isBlank()) {
noGalleriesSpecified()
return
}
@@ -8,7 +8,7 @@ import exh.GalleryAdder
import exh.metadata.nullIfBlank
import kotlin.concurrent.thread
class BatchAddPresenter: BasePresenter<BatchAddController>() {
class BatchAddPresenter : BasePresenter<BatchAddController>() {
private val galleryAdder by lazy { GalleryAdder() }
@@ -34,7 +34,7 @@ class BatchAddPresenter: BasePresenter<BatchAddController>() {
splitGalleries.forEachIndexed { i, s ->
val result = galleryAdder.addGallery(s, true)
if(result is GalleryAddEvent.Success) {
if (result is GalleryAddEvent.Success) {
succeeded.add(s)
} else {
failed.add(s)
@@ -46,7 +46,7 @@ class BatchAddPresenter: BasePresenter<BatchAddController>() {
}) + " " + result.logMessage)
}
//Show report
// Show report
val summary = "\nSummary:\nAdded: ${succeeded.size} gallerie(s)\nFailed: ${failed.size} gallerie(s)"
eventRelay?.call(summary)
}
@@ -7,21 +7,23 @@ import android.webkit.WebView
import androidx.annotation.RequiresApi
import eu.kanade.tachiyomi.util.system.asJsoup
import exh.ui.captcha.BrowserActionActivity.Companion.CROSS_WINDOW_SCRIPT_INNER
import java.nio.charset.Charset
import org.jsoup.nodes.DataNode
import org.jsoup.nodes.Element
import java.nio.charset.Charset
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class AutoSolvingWebViewClient(activity: BrowserActionActivity,
verifyComplete: (String) -> Boolean,
injectScript: String?,
headers: Map<String, String>)
: HeadersInjectingWebViewClient(activity, verifyComplete, injectScript, headers) {
class AutoSolvingWebViewClient(
activity: BrowserActionActivity,
verifyComplete: (String) -> Boolean,
injectScript: String?,
headers: Map<String, String>
) :
HeadersInjectingWebViewClient(activity, verifyComplete, injectScript, headers) {
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
// Inject our custom script into the recaptcha iframes
val lastPathSegment = request.url.pathSegments.lastOrNull()
if(lastPathSegment == "anchor" || lastPathSegment == "bframe") {
if (lastPathSegment == "anchor" || lastPathSegment == "bframe") {
val oReq = request.toOkHttpRequest()
val response = activity.httpClient.newCall(oReq).execute()
val doc = response.asJsoup()
@@ -34,4 +36,4 @@ class AutoSolvingWebViewClient(activity: BrowserActionActivity,
}
return super.shouldInterceptRequest(view, request)
}
}
}
@@ -4,17 +4,19 @@ import android.os.Build
import android.webkit.WebView
import android.webkit.WebViewClient
open class BasicWebViewClient(protected val activity: BrowserActionActivity,
protected val verifyComplete: (String) -> Boolean,
private val injectScript: String?) : WebViewClient() {
open class BasicWebViewClient(
protected val activity: BrowserActionActivity,
protected val verifyComplete: (String) -> Boolean,
private val injectScript: String?
) : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
if(verifyComplete(url)) {
if (verifyComplete(url)) {
activity.finish()
} else {
if(injectScript != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
if (injectScript != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
view.evaluateJavascript("(function() {$injectScript})();", null)
}
}
}
}
@@ -6,7 +6,12 @@ import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import android.view.MotionEvent
import android.webkit.*
import android.webkit.CookieManager
import android.webkit.CookieSyncManager
import android.webkit.JavascriptInterface
import android.webkit.JsResult
import android.webkit.WebChromeClient
import android.webkit.WebView
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.afollestad.materialdialogs.MaterialDialog
@@ -22,6 +27,9 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import exh.source.DelegatedHttpSource
import exh.util.melt
import java.io.Serializable
import java.net.URL
import java.util.UUID
import kotlinx.android.synthetic.main.eh_activity_captcha.*
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaTypeOrNull
@@ -33,10 +41,6 @@ import rx.Single
import rx.schedulers.Schedulers
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import java.io.Serializable
import java.net.URL
import java.util.*
import kotlin.collections.HashMap
class BrowserActionActivity : AppCompatActivity() {
private val sourceManager: SourceManager by injectLazy()
@@ -58,8 +62,8 @@ class BrowserActionActivity : AppCompatActivity() {
setContentView(eu.kanade.tachiyomi.R.layout.eh_activity_captcha)
val sourceId = intent.getLongExtra(SOURCE_ID_EXTRA, -1)
val originalSource = if(sourceId != -1L) sourceManager.get(sourceId) else null
val source = if(originalSource != null) {
val originalSource = if (sourceId != -1L) sourceManager.get(sourceId) else null
val source = if (originalSource != null) {
originalSource as? ActionCompletionVerifier
?: run {
(originalSource as? HttpSource)?.let {
@@ -72,24 +76,24 @@ class BrowserActionActivity : AppCompatActivity() {
it.value.joinToString(",")
} ?: emptyMap()) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap<String, String> ?: emptyMap())
val cookies: HashMap<String, String>?
= intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String>
val cookies: HashMap<String, String>? =
intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String>
val script: String? = intent.getStringExtra(SCRIPT_EXTRA)
val url: String? = intent.getStringExtra(URL_EXTRA)
val actionName = intent.getStringExtra(ACTION_NAME_EXTRA)
val verifyComplete = if(source != null) {
val verifyComplete = if (source != null) {
source::verifyComplete!!
} else intent.getSerializableExtra(VERIFY_LAMBDA_EXTRA) as? (String) -> Boolean
if(verifyComplete == null || url == null) {
if (verifyComplete == null || url == null) {
finish()
return
}
val actionStr = actionName ?: "Solve captcha"
toolbar.title = if(source != null) {
toolbar.title = if (source != null) {
"${source.name}: $actionStr"
} else actionStr
@@ -115,13 +119,13 @@ class BrowserActionActivity : AppCompatActivity() {
webview.webChromeClient = object : WebChromeClient() {
override fun onJsAlert(view: WebView?, url: String?, message: String, result: JsResult): Boolean {
if(message.startsWith("exh-")) {
if (message.startsWith("exh-")) {
loadedInners++
// Wait for both inner scripts to be loaded
if(loadedInners >= 2) {
if (loadedInners >= 2) {
// Attempt to autosolve captcha
if(preferencesHelper.eh_autoSolveCaptchas().getOrDefault()
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (preferencesHelper.eh_autoSolveCaptchas().getOrDefault() &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webview.post {
// 10 seconds to auto-solve captcha
strictValidationStartTime = System.currentTimeMillis() + 1000 * 10
@@ -141,7 +145,7 @@ class BrowserActionActivity : AppCompatActivity() {
}
webview.webViewClient = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if(actionName == null && preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) {
if (actionName == null && preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) {
// Fetch auto-solve credentials early for speed
credentialsObservable = httpClient.newCall(Request.Builder()
// Rob demo credentials
@@ -196,11 +200,11 @@ class BrowserActionActivity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JavascriptInterface
fun callback(result: String?, loopId: String, stage: Int) {
if(loopId != currentLoopId) return
if (loopId != currentLoopId) return
when(stage) {
when (stage) {
STAGE_CHECKBOX -> {
if(result!!.toBoolean()) {
if (result!!.toBoolean()) {
webview.postDelayed({
getAudioButtonLocation(loopId)
}, 250)
@@ -211,7 +215,7 @@ class BrowserActionActivity : AppCompatActivity() {
}
}
STAGE_GET_AUDIO_BTN_LOCATION -> {
if(result != null) {
if (result != null) {
val splitResult = result.split(" ").map { it.toFloat() }
val origX = splitResult[0]
val origY = splitResult[1]
@@ -231,11 +235,11 @@ class BrowserActionActivity : AppCompatActivity() {
}
}
STAGE_DOWNLOAD_AUDIO -> {
if(result != null) {
if (result != null) {
Timber.d("Got audio URL: $result")
performRecognize(result)
.observeOn(Schedulers.io())
.subscribe ({
.subscribe({
Timber.d("Got audio transcript: $it")
webview.post {
typeResult(loopId, it!!
@@ -253,7 +257,7 @@ class BrowserActionActivity : AppCompatActivity() {
}
}
STAGE_TYPE_RESULT -> {
if(result!!.toBoolean()) {
if (result!!.toBoolean()) {
// Fail if captcha still not solved after 1.5s
strictValidationStartTime = System.currentTimeMillis() + 1500
} else {
@@ -293,7 +297,7 @@ class BrowserActionActivity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun doStageCheckbox(loopId: String) {
if(loopId != currentLoopId) return
if (loopId != currentLoopId) return
webview.evaluateJavascript("""
(function() {
@@ -415,27 +419,26 @@ class BrowserActionActivity : AppCompatActivity() {
doStageCheckbox(loopId)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JavascriptInterface
fun validateCaptchaCallback(result: Boolean, loopId: String) {
if(loopId != validateCurrentLoopId) return
if (loopId != validateCurrentLoopId) return
if(result) {
if (result) {
Timber.d("Captcha solved!")
webview.post {
webview.evaluateJavascript(SOLVE_UI_SCRIPT_HIDE, null)
}
val asbtn = intent.getStringExtra(ASBTN_EXTRA)
if(asbtn != null) {
if (asbtn != null) {
webview.post {
webview.evaluateJavascript("(function() {document.querySelector('$asbtn').click();})();", null)
}
}
} else {
val savedStrictValidationStartTime = strictValidationStartTime
if(savedStrictValidationStartTime != null
&& System.currentTimeMillis() > savedStrictValidationStartTime) {
if (savedStrictValidationStartTime != null &&
System.currentTimeMillis() > savedStrictValidationStartTime) {
captchaSolveFail()
} else {
webview.postDelayed({
@@ -447,7 +450,7 @@ class BrowserActionActivity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun runValidateCaptcha(loopId: String) {
if(loopId != validateCurrentLoopId) return
if (loopId != validateCurrentLoopId) return
webview.evaluateJavascript("""
(function() {
@@ -624,12 +627,14 @@ class BrowserActionActivity : AppCompatActivity() {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
fun launchCaptcha(context: Context,
source: ActionCompletionVerifier,
cookies: Map<String, String>,
script: String?,
url: String,
autoSolveSubmitBtnSelector: String? = null) {
fun launchCaptcha(
context: Context,
source: ActionCompletionVerifier,
cookies: Map<String, String>,
script: String?,
url: String,
autoSolveSubmitBtnSelector: String? = null
) {
val intent = baseIntent(context).apply {
putExtra(SOURCE_ID_EXTRA, source.id)
putExtra(COOKIES_EXTRA, HashMap(cookies))
@@ -641,9 +646,11 @@ class BrowserActionActivity : AppCompatActivity() {
context.startActivity(intent)
}
fun launchUniversal(context: Context,
source: HttpSource,
url: String) {
fun launchUniversal(
context: Context,
source: HttpSource,
url: String
) {
val intent = baseIntent(context).apply {
putExtra(SOURCE_ID_EXTRA, source.id)
putExtra(URL_EXTRA, url)
@@ -652,9 +659,11 @@ class BrowserActionActivity : AppCompatActivity() {
context.startActivity(intent)
}
fun launchUniversal(context: Context,
sourceId: Long,
url: String) {
fun launchUniversal(
context: Context,
sourceId: Long,
url: String
) {
val intent = baseIntent(context).apply {
putExtra(SOURCE_ID_EXTRA, sourceId)
putExtra(URL_EXTRA, url)
@@ -663,11 +672,13 @@ class BrowserActionActivity : AppCompatActivity() {
context.startActivity(intent)
}
fun launchAction(context: Context,
completionVerifier: ActionCompletionVerifier,
script: String?,
url: String,
actionName: String) {
fun launchAction(
context: Context,
completionVerifier: ActionCompletionVerifier,
script: String?,
url: String,
actionName: String
) {
val intent = baseIntent(context).apply {
putExtra(SOURCE_ID_EXTRA, completionVerifier.id)
putExtra(SCRIPT_EXTRA, script)
@@ -678,12 +689,14 @@ class BrowserActionActivity : AppCompatActivity() {
context.startActivity(intent)
}
fun launchAction(context: Context,
completionVerifier: (String) -> Boolean,
script: String?,
url: String,
actionName: String,
headers: Map<String, String>? = emptyMap()) {
fun launchAction(
context: Context,
completionVerifier: (String) -> Boolean,
script: String?,
url: String,
actionName: String,
headers: Map<String, String>? = emptyMap()
) {
val intent = baseIntent(context).apply {
putExtra(HEADERS_EXTRA, HashMap(headers))
putExtra(VERIFY_LAMBDA_EXTRA, completionVerifier as Serializable)
@@ -697,7 +710,7 @@ class BrowserActionActivity : AppCompatActivity() {
}
}
class NoopActionCompletionVerifier(private val source: HttpSource): DelegatedHttpSource(source),
class NoopActionCompletionVerifier(private val source: HttpSource) : DelegatedHttpSource(source),
ActionCompletionVerifier {
override val versionId get() = source.versionId
override val lang: String get() = source.lang
@@ -708,4 +721,3 @@ class NoopActionCompletionVerifier(private val source: HttpSource): DelegatedHtt
interface ActionCompletionVerifier : Source {
fun verifyComplete(url: String): Boolean
}
@@ -7,11 +7,13 @@ import android.webkit.WebView
import androidx.annotation.RequiresApi
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
open class HeadersInjectingWebViewClient(activity: BrowserActionActivity,
verifyComplete: (String) -> Boolean,
injectScript: String?,
private val headers: Map<String, String>)
: BasicWebViewClient(activity, verifyComplete, injectScript) {
open class HeadersInjectingWebViewClient(
activity: BrowserActionActivity,
verifyComplete: (String) -> Boolean,
injectScript: String?,
private val headers: Map<String, String>
) :
BasicWebViewClient(activity, verifyComplete, injectScript) {
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
// Temp disabled as it's unreliable
@@ -16,4 +16,4 @@ fun WebResourceRequest.toOkHttpRequest(): Request {
}
return request.build()
}
}
@@ -23,7 +23,7 @@ class InterceptActivity : BaseRxActivity<InterceptActivityPresenter>() {
super.onCreate(savedInstanceState)
setContentView(R.layout.eh_activity_intercept)
//Show back button
// Show back button
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
@@ -31,7 +31,7 @@ class InterceptActivity : BaseRxActivity<InterceptActivityPresenter>() {
}
private fun processLink() {
if(Intent.ACTION_VIEW == intent.action) {
if (Intent.ACTION_VIEW == intent.action) {
intercept_progress.visible()
intercept_status.text = "Loading gallery..."
presenter.loadGallery(intent.dataString)
@@ -52,7 +52,7 @@ class InterceptActivity : BaseRxActivity<InterceptActivityPresenter>() {
statusSubscription = presenter.status
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
when(it) {
when (it) {
is InterceptResult.Success -> {
intercept_progress.gone()
intercept_status.text = "Launching app..."
@@ -3,8 +3,8 @@ package exh.ui.intercept
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import exh.GalleryAddEvent
import exh.GalleryAdder
import rx.subjects.BehaviorSubject
import kotlin.concurrent.thread
import rx.subjects.BehaviorSubject
class InterceptActivityPresenter : BasePresenter<InterceptActivity>() {
private val galleryAdder = GalleryAdder()
@@ -13,11 +13,11 @@ class InterceptActivityPresenter : BasePresenter<InterceptActivity>() {
@Synchronized
fun loadGallery(gallery: String) {
//Do not load gallery if already loading
if(status.value is InterceptResult.Idle) {
// Do not load gallery if already loading
if (status.value is InterceptResult.Idle) {
status.onNext(InterceptResult.Loading())
//Load gallery async
// Load gallery async
thread {
val result = galleryAdder.addGallery(gallery)
@@ -35,6 +35,6 @@ class InterceptActivityPresenter : BasePresenter<InterceptActivity>() {
sealed class InterceptResult {
class Idle : InterceptResult()
class Loading : InterceptResult()
data class Success(val mangaId: Long): InterceptResult()
data class Failure(val reason: String): InterceptResult()
}
data class Success(val mangaId: Long) : InterceptResult()
data class Failure(val reason: String) : InterceptResult()
}
@@ -28,21 +28,21 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
val prefs: PreferencesHelper by injectLazy()
val fingerprintSupported
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& Reprint.isHardwarePresent()
&& Reprint.hasFingerprintRegistered()
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
Reprint.isHardwarePresent() &&
Reprint.hasFingerprintRegistered()
val useFingerprint
get() = fingerprintSupported
&& prefs.eh_lockUseFingerprint().getOrDefault()
get() = fingerprintSupported &&
prefs.eh_lockUseFingerprint().getOrDefault()
@SuppressLint("NewApi")
override fun onAttached() {
super.onAttached()
if(fingerprintSupported) {
if (fingerprintSupported) {
updateSummary()
onChange {
if(it as Boolean)
if (it as Boolean)
tryChange()
else
prefs.eh_lockUseFingerprint().set(false)
@@ -51,7 +51,7 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
} else {
title = "Fingerprint unsupported"
shouldDisableView = true
summary = if(!Reprint.hasFingerprintRegistered())
summary = if (!Reprint.hasFingerprintRegistered())
"No fingerprints enrolled!"
else
"Fingerprint unlock is unsupported on this device!"
@@ -61,7 +61,7 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
private fun updateSummary() {
isChecked = useFingerprint
title = if(isChecked)
title = if (isChecked)
"Fingerprint enabled"
else
"Fingerprint disabled"
@@ -146,4 +146,4 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
subscription.unsubscribe()
}
}
}
}
@@ -1,6 +1,5 @@
package exh.ui.lock
import android.content.Intent
import android.view.WindowManager
import androidx.fragment.app.FragmentActivity
import com.bluelinelabs.conductor.Router
@@ -8,7 +7,6 @@ import com.bluelinelabs.conductor.RouterTransaction
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import uy.kohesive.injekt.injectLazy
import java.util.Date
object LockActivityDelegate {
private val preferences by injectLazy<PreferencesHelper>()
@@ -20,7 +18,6 @@ object LockActivityDelegate {
.popChangeHandler(LockChangeHandler(animate)))
}
fun onCreate(activity: FragmentActivity) {
preferences.secureScreen().asObservable()
.subscribe {
@@ -42,5 +39,4 @@ object LockActivityDelegate {
private fun isAppLocked(router: Router): Boolean {
return router.backstack.lastOrNull()?.controller() is LockController
}
}
@@ -7,10 +7,10 @@ import android.view.View
import android.view.ViewGroup
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler
import java.util.*
import java.util.ArrayList
class LockChangeHandler : AnimatorChangeHandler {
constructor(): super()
constructor() : super()
constructor(removesFromViewOnPush: Boolean) : super(removesFromViewOnPush)
@@ -36,6 +36,4 @@ class LockChangeHandler : AnimatorChangeHandler {
override fun copy(): ControllerChangeHandler =
LockChangeHandler(animationDuration, removesFromViewOnPush())
}
@@ -22,8 +22,8 @@ class LockController : NucleusController<LockPresenter>() {
val prefs: PreferencesHelper by injectLazy()
override fun inflateView(inflater: LayoutInflater, container: ViewGroup)
= inflater.inflate(R.layout.activity_lock, container, false)!!
override fun inflateView(inflater: LayoutInflater, container: ViewGroup) =
inflater.inflate(R.layout.activity_lock, container, false)!!
override fun createPresenter() = LockPresenter()
@@ -32,13 +32,13 @@ class LockController : NucleusController<LockPresenter>() {
override fun onViewCreated(view: View) {
super.onViewCreated(view)
if(!lockEnabled(prefs)) {
if (!lockEnabled(prefs)) {
closeLock()
return
}
with(view) {
//Setup pin lock
// Setup pin lock
pin_lock_view.attachIndicatorDots(indicator_dots)
pin_lock_view.pinLength = prefs.eh_lockLength().getOrDefault()
@@ -47,7 +47,7 @@ class LockController : NucleusController<LockPresenter>() {
override fun onComplete(pin: String) {
if (sha512(pin, prefs.eh_lockSalt().get()!!) == prefs.eh_lockHash().get()) {
//Yay!
// Yay!
closeLock()
} else {
MaterialDialog.Builder(context)
@@ -72,7 +72,7 @@ class LockController : NucleusController<LockPresenter>() {
super.onAttach(view)
with(view) {
//Fingerprint
// Fingerprint
if (presenter.useFingerprint) {
swirl_container.visibility = View.VISIBLE
swirl_container.removeAllViews()
@@ -90,7 +90,7 @@ class LockController : NucleusController<LockPresenter>() {
val lockColor = resolvColor(android.R.attr.windowBackground)
setBackgroundColor(lockColor)
val bgColor = resolvColor(android.R.attr.colorBackground)
//Disable elevation if lock color is same as background color
// Disable elevation if lock color is same as background color
if (lockColor == bgColor)
this@with.swirl_container.cardElevation = 0f
setState(SwirlView.State.OFF, true)
@@ -8,12 +8,12 @@ import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.preference.onChange
import java.math.BigInteger
import java.security.SecureRandom
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy
import java.math.BigInteger
import java.security.SecureRandom
class LockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
SwitchPreferenceCompat(context, attrs) {
@@ -33,7 +33,7 @@ class LockPreference @JvmOverloads constructor(context: Context, attrs: Attribut
private fun updateSummary() {
isChecked = lockEnabled(prefs)
if(isChecked) {
if (isChecked) {
title = "Lock enabled"
summary = "Tap to disable or change pin code"
} else {
@@ -43,7 +43,7 @@ class LockPreference @JvmOverloads constructor(context: Context, attrs: Attribut
}
fun tryChange() {
if(!notifyLockSecurity(context)) {
if (!notifyLockSecurity(context)) {
MaterialDialog.Builder(context)
.title("Lock application")
.content("Enter a pin to lock the application. Enter nothing to disable the pin lock.")
@@ -76,7 +76,7 @@ class LockPreference @JvmOverloads constructor(context: Context, attrs: Attribut
val salt: String?
val hash: String?
val length: Int
if(password.isEmpty()) {
if (password.isEmpty()) {
salt = null
hash = null
length = -1
@@ -7,13 +7,12 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import uy.kohesive.injekt.injectLazy
class LockPresenter: BasePresenter<LockController>() {
class LockPresenter : BasePresenter<LockController>() {
val prefs: PreferencesHelper by injectLazy()
val useFingerprint
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& Reprint.isHardwarePresent()
&& Reprint.hasFingerprintRegistered()
&& prefs.eh_lockUseFingerprint().getOrDefault()
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
Reprint.isHardwarePresent() &&
Reprint.hasFingerprintRegistered() &&
prefs.eh_lockUseFingerprint().getOrDefault()
}
+14 -13
View File
@@ -13,11 +13,10 @@ import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.security.MessageDigest
import kotlin.experimental.and
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/**
* Password hashing utils
@@ -40,22 +39,24 @@ fun sha512(passwordToHash: String, salt: String): String {
/**
* Check if lock is enabled
*/
fun lockEnabled(prefs: PreferencesHelper = Injekt.get())
= prefs.eh_lockHash().get() != null
&& prefs.eh_lockSalt().get() != null
&& prefs.eh_lockLength().getOrDefault() != -1
fun lockEnabled(prefs: PreferencesHelper = Injekt.get()) =
prefs.eh_lockHash().get() != null &&
prefs.eh_lockSalt().get() != null &&
prefs.eh_lockLength().getOrDefault() != -1
/**
* Check if the lock will function properly
*
* @return true if action is required, false if lock is working properly
*/
fun notifyLockSecurity(context: Context,
prefs: PreferencesHelper = Injekt.get()): Boolean {
fun notifyLockSecurity(
context: Context,
prefs: PreferencesHelper = Injekt.get()
): Boolean {
return false
if (!prefs.eh_lockManually().getOrDefault()
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& !hasAccessToUsageStats(context)) {
if (!prefs.eh_lockManually().getOrDefault() &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
!hasAccessToUsageStats(context)) {
MaterialDialog.Builder(context)
.title("Permission required")
.content("${context.getString(R.string.app_name)} requires the usage stats permission to detect when you leave the app. " +
@@ -66,7 +67,7 @@ fun notifyLockSecurity(context: Context,
.onPositive { _, _ ->
try {
context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
} catch(e: ActivityNotFoundException) {
} catch (e: ActivityNotFoundException) {
XLog.e("Device does not support USAGE_ACCESS_SETTINGS shortcut!")
MaterialDialog.Builder(context)
.title("Grant permission manually")
@@ -12,14 +12,14 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.visible
import exh.uconfig.WarnConfigureDialogController
import java.net.HttpCookie
import kotlinx.android.synthetic.main.eh_activity_login.view.*
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import java.net.HttpCookie
/**
* LoginController
@@ -104,17 +104,17 @@ class LoginController : NucleusController<LoginPresenter>() {
Timber.d(url)
val parsedUrl = Uri.parse(url)
if (parsedUrl.host.equals("forums.e-hentai.org", ignoreCase = true)) {
//Hide distracting content
if(!parsedUrl.queryParameterNames.contains(PARAM_SKIP_INJECT)
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
// Hide distracting content
if (!parsedUrl.queryParameterNames.contains(PARAM_SKIP_INJECT) &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
view.evaluateJavascript(HIDE_JS, null)
//Check login result
// Check login result
if (parsedUrl.getQueryParameter("code")?.toInt() != 0) {
if (checkLoginCookies(url)) view.loadUrl("https://exhentai.org/")
}
} else if (parsedUrl.host.equals("exhentai.org", ignoreCase = true)) {
//At ExHentai, check that everything worked out...
// At ExHentai, check that everything worked out...
if (applyExHentaiCookies(url)) {
preferenceManager.enableExhentai().set(true)
finishLogin()
@@ -128,7 +128,7 @@ class LoginController : NucleusController<LoginPresenter>() {
fun finishLogin() {
router.popCurrentController()
//Upload settings
// Upload settings
WarnConfigureDialogController.uploadSettings(router)
}
@@ -138,9 +138,9 @@ class LoginController : NucleusController<LoginPresenter>() {
fun checkLoginCookies(url: String): Boolean {
getCookies(url)?.let { parsed ->
return parsed.filter {
(it.name.equals(MEMBER_ID_COOKIE, ignoreCase = true)
|| it.name.equals(PASS_HASH_COOKIE, ignoreCase = true))
&& it.value.isNotBlank()
(it.name.equals(MEMBER_ID_COOKIE, ignoreCase = true) ||
it.name.equals(PASS_HASH_COOKIE, ignoreCase = true)) &&
it.value.isNotBlank()
}.count() >= 2
}
return false
@@ -164,10 +164,10 @@ class LoginController : NucleusController<LoginPresenter>() {
}
}
//Missing a cookie
// Missing a cookie
if (memberId == null || passHash == null || igneous == null) return false
//Update prefs
// Update prefs
preferenceManager.memberIdVal().set(memberId)
preferenceManager.passHashVal().set(passHash)
preferenceManager.igneousVal().set(igneous)
@@ -177,8 +177,8 @@ class LoginController : NucleusController<LoginPresenter>() {
return false
}
fun getCookies(url: String): List<HttpCookie>?
= CookieManager.getInstance().getCookie(url)?.let {
fun getCookies(url: String): List<HttpCookie>? =
CookieManager.getInstance().getCookie(url)?.let {
it.split("; ").flatMap {
HttpCookie.parse(it)
}
@@ -2,6 +2,4 @@ package exh.ui.login
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
class LoginPresenter: BasePresenter<LoginController>() {
}
class LoginPresenter : BasePresenter<LoginController>()
@@ -12,9 +12,9 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.SourceManager
import exh.EXH_SOURCE_ID
import exh.isLewdSource
import kotlin.concurrent.thread
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import kotlin.concurrent.thread
class MetadataFetchDialog {
@@ -25,7 +25,7 @@ class MetadataFetchDialog {
val preferenceHelper: PreferencesHelper by injectLazy()
fun show(context: Activity) {
//Too lazy to actually deal with orientation changes
// Too lazy to actually deal with orientation changes
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
var running = true
@@ -55,7 +55,7 @@ class MetadataFetchDialog {
val mangaWithMissingMetadata = libraryMangas
.filterIndexed { index, libraryManga ->
if(index % 100 == 0) {
if (index % 100 == 0) {
context.runOnUiThread {
progressDialog.setContent("[Stage 1/2] Scanning for missing metadata...")
progressDialog.setProgress(index + 1)
@@ -69,9 +69,9 @@ class MetadataFetchDialog {
progressDialog.maxProgress = mangaWithMissingMetadata.size
}
//Actual metadata fetch code
for((i, manga) in mangaWithMissingMetadata.withIndex()) {
if(!running) break
// Actual metadata fetch code
for ((i, manga) in mangaWithMissingMetadata.withIndex()) {
if (!running) break
context.runOnUiThread {
progressDialog.setContent("[Stage 2/2] Processing: ${manga.title}")
progressDialog.setProgress(i + 1)
@@ -88,10 +88,10 @@ class MetadataFetchDialog {
context.runOnUiThread {
// Ensure activity still exists before we do anything to the activity
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !context.isDestroyed) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !context.isDestroyed) {
progressDialog.dismiss()
//Enable orientation changes again
// Enable orientation changes again
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
if (running) displayMigrationComplete(context)
@@ -103,12 +103,12 @@ class MetadataFetchDialog {
fun askMigration(activity: Activity, explicit: Boolean) {
var extra = ""
db.getLibraryMangas().asRxSingle().subscribe {
if(!explicit && it.none { isLewdSource(it.source) }) {
if (!explicit && it.none { isLewdSource(it.source) }) {
// Do not open dialog on startup if no manga
// Also do not check again
preferenceHelper.migrateLibraryAsked().set(true)
} else {
//Not logged in but have ExHentai galleries
// Not logged in but have ExHentai galleries
if (!preferenceHelper.enableExhentai().getOrDefault()) {
it.find { it.source == EXH_SOURCE_ID }?.let {
extra = "<b><font color='red'>If you use ExHentai, please log in first before fetching your library metadata!</font></b><br><br>"
@@ -132,7 +132,6 @@ class MetadataFetchDialog {
}
}
}
}
fun adviseMigrationLater(activity: Activity) {
@@ -5,7 +5,7 @@ class MigrationStatus {
val NOT_INITIALIZED = -1
val COMPLETED = 0
//Migration process
// Migration process
val NOTIFY_USER = 1
val OPEN_BACKUP_MENU = 2
val PERFORM_BACKUP = 3
@@ -76,16 +76,16 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
updateOptionsState()
begin_migration_btn.setOnClickListener {
if(!showingOptions) {
if (!showingOptions) {
showingOptions = true
updateOptionsState()
return@setOnClickListener
}
var flags = 0
if(mig_chapters.isChecked) flags = flags or MigrationFlags.CHAPTERS
if(mig_categories.isChecked) flags = flags or MigrationFlags.CATEGORIES
if(mig_categories.isChecked) flags = flags or MigrationFlags.TRACK
if (mig_chapters.isChecked) flags = flags or MigrationFlags.CHAPTERS
if (mig_categories.isChecked) flags = flags or MigrationFlags.CATEGORIES
if (mig_categories.isChecked) flags = flags or MigrationFlags.TRACK
router.replaceTopController(MigrationProcedureController.create(
MigrationProcedureConfig(
@@ -97,7 +97,7 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
enableLenientSearch = use_smart_search.isChecked,
migrationFlags = flags,
copy = copy_manga.isChecked,
extraSearchParams = if(extra_search_param.isChecked && extra_search_param_text.text.isNotBlank()) {
extraSearchParams = if (extra_search_param.isChecked && extra_search_param_text.text.isNotBlank()) {
extra_search_param_text.text.toString()
} else null
)
@@ -109,7 +109,7 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
if (showingOptions) {
begin_migration_btn.text = "Begin migration"
options_group.visible()
if(extra_search_param.isChecked) {
if (extra_search_param.isChecked) {
extra_search_param_text.visible()
} else {
extra_search_param_text.gone()
@@ -122,7 +122,7 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
}
override fun handleBack(): Boolean {
if(showingOptions) {
if (showingOptions) {
showingOptions = false
updateOptionsState()
return true
@@ -142,7 +142,7 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
}
private fun updatePrioritizeChapterCount(migrationMode: Boolean) {
migration_mode.text = if(migrationMode) {
migration_mode.text = if (migrationMode) {
"Currently using the source with the most chapters and the above list to break ties (slow with many sources or smart search)"
} else {
"Currently using the first source in the list that has the manga"
@@ -182,4 +182,4 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
})
}
}
}
}
@@ -4,8 +4,10 @@ import android.os.Bundle
import eu.davidea.flexibleadapter.FlexibleAdapter
import exh.debug.DebugFunctions.sourceManager
class MigrationSourceAdapter(val items: List<MigrationSourceItem>,
val controller: MigrationDesignController): FlexibleAdapter<MigrationSourceItem>(
class MigrationSourceAdapter(
val items: List<MigrationSourceItem>,
val controller: MigrationDesignController
) : FlexibleAdapter<MigrationSourceItem>(
items,
controller,
true
@@ -29,4 +31,4 @@ class MigrationSourceAdapter(val items: List<MigrationSourceItem>,
companion object {
private const val SELECTED_SOURCES_KEY = "selected_sources"
}
}
}
@@ -1,14 +1,14 @@
package exh.ui.migration.manga.design
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.view.getRound
import kotlinx.android.synthetic.main.eh_source_item.*
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
class MigrationSourceHolder(view: View, val adapter: FlexibleAdapter<MigrationSourceItem>):
class MigrationSourceHolder(view: View, val adapter: FlexibleAdapter<MigrationSourceItem>) :
BaseFlexibleViewHolder(view, adapter) {
init {
setDragHandleView(reorder)
@@ -20,10 +20,10 @@ class MigrationSourceHolder(view: View, val adapter: FlexibleAdapter<MigrationSo
// Update circle letter image.
itemView.post {
image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(),false))
image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(), false))
}
if(sourceEnabled) {
if (sourceEnabled) {
title.alpha = 1.0f
image.alpha = 1.0f
title.paintFlags = title.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
@@ -37,4 +37,4 @@ class MigrationSourceHolder(view: View, val adapter: FlexibleAdapter<MigrationSo
companion object {
private const val DISABLED_ALPHA = 0.3f
}
}
}
@@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.android.parcel.Parcelize
class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean): AbstractFlexibleItem<MigrationSourceHolder>() {
class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) : AbstractFlexibleItem<MigrationSourceHolder>() {
override fun getLayoutRes() = R.layout.eh_source_item
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): MigrationSourceHolder {
@@ -25,10 +25,12 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean): A
* @param position The position of this item in the adapter.
* @param payloads List of partial changes.
*/
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>,
holder: MigrationSourceHolder,
position: Int,
payloads: List<Any?>?) {
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>,
holder: MigrationSourceHolder,
position: Int,
payloads: List<Any?>?
) {
holder.bind(source, sourceEnabled)
}
@@ -52,7 +54,7 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean): A
}
@Parcelize
data class ParcelableSI(val sourceId: Long, val sourceEnabled: Boolean): Parcelable
data class ParcelableSI(val sourceId: Long, val sourceEnabled: Boolean) : Parcelable
fun asParcelable(): ParcelableSI {
return ParcelableSI(source.id, sourceEnabled)
@@ -68,4 +70,4 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean): A
)
}
}
}
}
@@ -5,8 +5,8 @@ import android.util.AttributeSet
import android.view.MotionEvent
class DeactivatableViewPager : androidx.viewpager.widget.ViewPager {
constructor(context: Context): super(context)
constructor(context: Context, attrs: AttributeSet): super(context, attrs)
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun onTouchEvent(event: MotionEvent): Boolean {
return !isEnabled || super.onTouchEvent(event)
@@ -15,4 +15,4 @@ class DeactivatableViewPager : androidx.viewpager.widget.ViewPager {
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
return isEnabled && super.onInterceptTouchEvent(event)
}
}
}
@@ -6,14 +6,17 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import exh.util.DeferredField
import exh.util.await
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
class MigratingManga(private val db: DatabaseHelper,
private val sourceManager: SourceManager,
val mangaId: Long,
parentContext: CoroutineContext) {
class MigratingManga(
private val db: DatabaseHelper,
private val sourceManager: SourceManager,
val mangaId: Long,
parentContext: CoroutineContext
) {
val searchResult = DeferredField<Long?>()
// <MAX, PROGRESS>
@@ -24,11 +27,11 @@ class MigratingManga(private val db: DatabaseHelper,
@Volatile
private var manga: Manga? = null
suspend fun manga(): Manga? {
if(manga == null) manga = db.getManga(mangaId).await()
if (manga == null) manga = db.getManga(mangaId).await()
return manga
}
suspend fun mangaSource(): Source {
return sourceManager.getOrStub(manga()?.source ?: -1)
}
}
}
@@ -22,20 +22,27 @@ import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.visible
import exh.MERGED_SOURCE_ID
import exh.util.await
import kotlinx.android.synthetic.main.eh_manga_card.view.*
import kotlinx.android.synthetic.main.eh_migration_process_item.view.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collect
import uy.kohesive.injekt.injectLazy
import java.text.DateFormat
import java.text.DecimalFormat
import java.util.*
import java.util.Date
import kotlin.coroutines.CoroutineContext
import kotlinx.android.synthetic.main.eh_manga_card.view.*
import kotlinx.android.synthetic.main.eh_migration_process_item.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy
class MigrationProcedureAdapter(val controller: MigrationProcedureController,
val migratingManga: List<MigratingManga>,
override val coroutineContext: CoroutineContext) : androidx.viewpager.widget.PagerAdapter(), CoroutineScope {
class MigrationProcedureAdapter(
val controller: MigrationProcedureController,
val migratingManga: List<MigratingManga>,
override val coroutineContext: CoroutineContext
) : androidx.viewpager.widget.PagerAdapter(), CoroutineScope {
private val db: DatabaseHelper by injectLazy()
private val gson: Gson by injectLazy()
private val sourceManager: SourceManager by injectLazy()
@@ -69,7 +76,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
performMigration(item)
}
controller.nextMigration()
} catch(e: Exception) {
} catch (e: Exception) {
logger.e("Migration failure!", e)
controller.migrationFailure()
}
@@ -81,7 +88,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
}
suspend fun performMigration(manga: MigratingManga) {
if(!manga.searchResult.initialized) {
if (!manga.searchResult.initialized) {
return
}
@@ -96,9 +103,11 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
}
}
private fun migrateMangaInternal(prevManga: Manga,
manga: Manga,
replace: Boolean) {
private fun migrateMangaInternal(
prevManga: Manga,
manga: Manga,
replace: Boolean
) {
db.inTransaction {
// Update chapters read
if (MigrationFlags.hasChapters(controller.config.migrationFlags)) {
@@ -147,7 +156,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
tag.launch {
val manga = migratingManga.manga()
val source = migratingManga.mangaSource()
if(manga != null) {
if (manga != null) {
withContext(Dispatchers.Main) {
eh_manga_card_from.loading_group.gone()
eh_manga_card_from.attachManga(tag, manga, source)
@@ -174,7 +183,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
sourceManager.get(it)
}
withContext(Dispatchers.Main) {
if(searchResult != null && resultSource != null) {
if (searchResult != null && resultSource != null) {
eh_manga_card_to.loading_group.gone()
eh_manga_card_to.attachManga(tag, searchResult, resultSource)
eh_manga_card_to.setOnClickListener {
@@ -263,7 +272,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
(objectAsView.tag as? ViewTag)?.destroy()
}
class ViewTag(parent: CoroutineContext): CoroutineScope {
class ViewTag(parent: CoroutineContext) : CoroutineScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
@@ -277,4 +286,4 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
cancel()
}
}
}
}
@@ -5,11 +5,11 @@ import kotlinx.android.parcel.Parcelize
@Parcelize
data class MigrationProcedureConfig(
val mangaIds: List<Long>,
val targetSourceIds: List<Long>,
val useSourceWithMostChapters: Boolean,
val enableLenientSearch: Boolean,
val migrationFlags: Int,
val copy: Boolean,
val extraSearchParams: String?
): Parcelable
val mangaIds: List<Long>,
val targetSourceIds: List<Long>,
val useSourceWithMostChapters: Boolean,
val enableLenientSearch: Boolean,
val migrationFlags: Int,
val copy: Boolean,
val extraSearchParams: String?
) : Parcelable
@@ -15,13 +15,21 @@ import eu.kanade.tachiyomi.util.system.toast
import exh.smartsearch.SmartSearchEngine
import exh.ui.base.BaseExhController
import exh.util.await
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.android.synthetic.main.eh_migration_process.*
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.atomic.AtomicInteger
// TODO Will probably implode if activity is fully destroyed
class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(bundle), CoroutineScope {
@@ -70,7 +78,7 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
pager.adapter = adapter
pager.isEnabled = false
if(migrationsJob == null) {
if (migrationsJob == null) {
migrationsJob = launch {
runMigrations(newMigratingManga)
}
@@ -89,7 +97,7 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
fun nextMigration() {
adapter?.let { adapter ->
if(pager.currentItem >= adapter.count - 1) {
if (pager.currentItem >= adapter.count - 1) {
applicationContext?.toast("All migrations complete!")
router.popCurrentController()
} else {
@@ -115,11 +123,11 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
suspend fun runMigrations(mangas: List<MigratingManga>) {
val sources = config.targetSourceIds.mapNotNull { sourceManager.get(it) as? CatalogueSource }
for(manga in mangas) {
if(!manga.searchResult.initialized && manga.migrationJob.isActive) {
for (manga in mangas) {
if (!manga.searchResult.initialized && manga.migrationJob.isActive) {
val mangaObj = manga.manga()
if(mangaObj == null) {
if (mangaObj == null) {
manga.searchResult.initialize(null)
continue
}
@@ -131,39 +139,39 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
val validSources = sources.filter {
it.id != mangaSource.id
}
if(config.useSourceWithMostChapters) {
if (config.useSourceWithMostChapters) {
val sourceSemaphore = Semaphore(3)
val processedSources = AtomicInteger()
validSources.map { source ->
async {
sourceSemaphore.withPermit {
try {
val searchResult = if (config.enableLenientSearch) {
smartSearchEngine.smartSearch(source, mangaObj.title)
} else {
smartSearchEngine.normalSearch(source, mangaObj.title)
}
async {
sourceSemaphore.withPermit {
try {
val searchResult = if (config.enableLenientSearch) {
smartSearchEngine.smartSearch(source, mangaObj.title)
} else {
smartSearchEngine.normalSearch(source, mangaObj.title)
}
if(searchResult != null) {
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
val chapters = source.fetchChapterList(localManga).toSingle().await(Schedulers.io())
withContext(Dispatchers.IO) {
syncChaptersWithSource(db, chapters, localManga, source)
}
manga.progress.send(validSources.size to processedSources.incrementAndGet())
localManga to chapters.size
} else {
null
}
} catch(e: CancellationException) {
// Ignore cancellations
throw e
} catch(e: Exception) {
logger.e("Failed to search in source: ${source.id}!", e)
null
}
}
if (searchResult != null) {
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
val chapters = source.fetchChapterList(localManga).toSingle().await(Schedulers.io())
withContext(Dispatchers.IO) {
syncChaptersWithSource(db, chapters, localManga, source)
}
manga.progress.send(validSources.size to processedSources.incrementAndGet())
localManga to chapters.size
} else {
null
}
} catch (e: CancellationException) {
// Ignore cancellations
throw e
} catch (e: Exception) {
logger.e("Failed to search in source: ${source.id}!", e)
null
}
}
}
}.mapNotNull { it.await() }.maxBy { it.second }?.first
} else {
@@ -183,28 +191,28 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
}
localManga
} else null
} catch(e: CancellationException) {
} catch (e: CancellationException) {
// Ignore cancellations
throw e
} catch(e: Exception) {
} catch (e: Exception) {
logger.e("Failed to search in source: ${source.id}!", e)
null
}
manga.progress.send(validSources.size to (index + 1))
if(searchResult != null) return@async searchResult
if (searchResult != null) return@async searchResult
}
null
}
}.await()
} catch(e: CancellationException) {
} catch (e: CancellationException) {
// Ignore canceled migrations
continue
}
if(result != null && result.thumbnail_url == null) {
if (result != null && result.thumbnail_url == null) {
try {
val newManga = sourceManager.getOrStub(result.source)
.fetchMangaDetails(result)
@@ -213,10 +221,10 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
result.copyFrom(newManga)
db.insertManga(result).await()
} catch(e: CancellationException) {
} catch (e: CancellationException) {
// Ignore cancellations
throw e
} catch(e: Exception) {
} catch (e: Exception) {
logger.e("Could not load search manga details", e)
}
}
@@ -241,4 +249,4 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
})
}
}
}
}
@@ -14,7 +14,13 @@ import eu.kanade.tachiyomi.ui.catalogue.browse.BrowseCatalogueController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.android.synthetic.main.eh_smart_search.*
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy
class SmartSearchController(bundle: Bundle? = null) : NucleusController<SmartSearchPresenter>(), CoroutineScope {
@@ -37,7 +43,7 @@ class SmartSearchController(bundle: Bundle? = null) : NucleusController<SmartSea
appbar.bringToFront()
if(source == null || smartSearchConfig == null) {
if (source == null || smartSearchConfig == null) {
router.popCurrentController()
applicationContext?.toast("Missing data!")
return
@@ -47,7 +53,7 @@ class SmartSearchController(bundle: Bundle? = null) : NucleusController<SmartSea
presenter
launch(Dispatchers.Default) {
for(event in presenter.smartSearchChannel) {
for (event in presenter.smartSearchChannel) {
withContext(NonCancellable) {
if (event is SmartSearchPresenter.SearchResults.Found) {
val transaction = MangaController(event.manga, true, smartSearchConfig).withFadeTransaction()
@@ -81,4 +87,4 @@ class SmartSearchController(bundle: Bundle? = null) : NucleusController<SmartSea
const val ARG_SOURCE_ID = "SOURCE_ID"
const val ARG_SMART_SEARCH_CONFIG = "SMART_SEARCH_CONFIG"
}
}
}
@@ -8,10 +8,15 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
import exh.smartsearch.SmartSearchEngine
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
class SmartSearchPresenter(private val source: CatalogueSource?, private val config: CatalogueController.SmartSearchConfig?):
class SmartSearchPresenter(private val source: CatalogueSource?, private val config: CatalogueController.SmartSearchConfig?) :
BasePresenter<SmartSearchController>(), CoroutineScope {
private val logger = XLog.tag("SmartSearchPresenter")
@@ -24,7 +29,7 @@ class SmartSearchPresenter(private val source: CatalogueSource?, private val con
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
if(source != null && config != null) {
if (source != null && config != null) {
launch(Dispatchers.Default) {
val result = try {
val resultManga = smartSearchEngine.smartSearch(source, config.origTitle)
@@ -48,7 +53,6 @@ class SmartSearchPresenter(private val source: CatalogueSource?, private val con
}
}
override fun onDestroy() {
super.onDestroy()
@@ -58,8 +62,8 @@ class SmartSearchPresenter(private val source: CatalogueSource?, private val con
data class SearchEntry(val manga: SManga, val dist: Double)
sealed class SearchResults {
data class Found(val manga: Manga): SearchResults()
object NotFound: SearchResults()
object Error: SearchResults()
data class Found(val manga: Manga) : SearchResults()
object NotFound : SearchResults()
object Error : SearchResults()
}
}
}