Refactoring

This commit is contained in:
Koitharu
2023-04-23 16:25:31 +03:00
parent 3ed9ed8cab
commit a89ff4d15d
21 changed files with 83 additions and 91 deletions

View File

@@ -11,9 +11,9 @@ import org.koitharu.kotatsu.utils.ext.getParcelableCompat
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
class MangaIntent private constructor( class MangaIntent private constructor(
val manga: Manga?, @JvmField val manga: Manga?,
val mangaId: Long, @JvmField val mangaId: Long,
val uri: Uri?, @JvmField val uri: Uri?,
) { ) {
constructor(intent: Intent?) : this( constructor(intent: Intent?) : this(

View File

@@ -26,34 +26,33 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.util.ActionModeDelegate import org.koitharu.kotatsu.base.ui.util.ActionModeDelegate
import org.koitharu.kotatsu.base.ui.util.BaseActivityEntryPoint import org.koitharu.kotatsu.base.ui.util.BaseActivityEntryPoint
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.base.ui.util.inject
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.utils.ext.getThemeColor import org.koitharu.kotatsu.utils.ext.getThemeColor
import javax.inject.Inject
@Suppress("LeakingThis")
abstract class BaseActivity<B : ViewBinding> : abstract class BaseActivity<B : ViewBinding> :
AppCompatActivity(), AppCompatActivity(),
WindowInsetsDelegate.WindowInsetsListener { WindowInsetsDelegate.WindowInsetsListener {
@Inject private var isAmoledTheme = false
lateinit var settings: AppSettings
protected lateinit var binding: B protected lateinit var binding: B
private set private set
@Suppress("LeakingThis") @JvmField
protected val exceptionResolver = ExceptionResolver(this) protected val exceptionResolver = ExceptionResolver(this)
@Suppress("LeakingThis") @JvmField
protected val insetsDelegate = WindowInsetsDelegate(this) protected val insetsDelegate = WindowInsetsDelegate(this)
@JvmField
val actionModeDelegate = ActionModeDelegate() val actionModeDelegate = ActionModeDelegate()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
EntryPointAccessors.fromApplication(this, BaseActivityEntryPoint::class.java).inject(this) val settings = EntryPointAccessors.fromApplication(this, BaseActivityEntryPoint::class.java).settings
isAmoledTheme = settings.isAmoledTheme
setTheme(settings.colorScheme.styleResId) setTheme(settings.colorScheme.styleResId)
if (settings.isAmoledTheme) { if (isAmoledTheme) {
setTheme(R.style.ThemeOverlay_Kotatsu_Amoled) setTheme(R.style.ThemeOverlay_Kotatsu_Amoled)
} }
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -108,7 +107,7 @@ abstract class BaseActivity<B : ViewBinding> :
protected fun isDarkAmoledTheme(): Boolean { protected fun isDarkAmoledTheme(): Boolean {
val uiMode = resources.configuration.uiMode val uiMode = resources.configuration.uiMode
val isNight = uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES val isNight = uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
return isNight && settings.isAmoledTheme return isNight && isAmoledTheme
} }
@CallSuper @CallSuper

View File

@@ -10,6 +10,7 @@ import org.koitharu.kotatsu.base.ui.util.ActionModeDelegate
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
@Suppress("LeakingThis")
abstract class BaseFragment<B : ViewBinding> : abstract class BaseFragment<B : ViewBinding> :
Fragment(), Fragment(),
WindowInsetsDelegate.WindowInsetsListener { WindowInsetsDelegate.WindowInsetsListener {
@@ -19,10 +20,10 @@ abstract class BaseFragment<B : ViewBinding> :
protected val binding: B protected val binding: B
get() = checkNotNull(viewBinding) get() = checkNotNull(viewBinding)
@Suppress("LeakingThis") @JvmField
protected val exceptionResolver = ExceptionResolver(this) protected val exceptionResolver = ExceptionResolver(this)
@Suppress("LeakingThis") @JvmField
protected val insetsDelegate = WindowInsetsDelegate(this) protected val insetsDelegate = WindowInsetsDelegate(this)
protected val actionModeDelegate: ActionModeDelegate protected val actionModeDelegate: ActionModeDelegate

View File

@@ -9,12 +9,13 @@ import androidx.core.view.updatePadding
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.settings.SettingsHeadersFragment import org.koitharu.kotatsu.settings.SettingsHeadersFragment
import javax.inject.Inject
@Suppress("LeakingThis")
@AndroidEntryPoint @AndroidEntryPoint
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
PreferenceFragmentCompat(), PreferenceFragmentCompat(),
@@ -24,7 +25,7 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
@Inject @Inject
lateinit var settings: AppSettings lateinit var settings: AppSettings
@Suppress("LeakingThis") @JvmField
protected val insetsDelegate = WindowInsetsDelegate(this) protected val insetsDelegate = WindowInsetsDelegate(this)
override val recyclerView: RecyclerView override val recyclerView: RecyclerView
@@ -55,7 +56,6 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
) )
} }
@Suppress("UsePropertyAccessSyntax")
protected fun setTitle(title: CharSequence) { protected fun setTitle(title: CharSequence) {
(parentFragment as? SettingsHeadersFragment)?.setTitle(title) (parentFragment as? SettingsHeadersFragment)?.setTitle(title)
?: activity?.setTitle(title) ?: activity?.setTitle(title)

View File

@@ -3,16 +3,24 @@ package org.koitharu.kotatsu.base.ui
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CancellationException
import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.base.ui.util.CountedBooleanLiveData import org.koitharu.kotatsu.base.ui.util.CountedBooleanLiveData
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
abstract class BaseViewModel : ViewModel() { abstract class BaseViewModel : ViewModel() {
@JvmField
protected val loadingCounter = CountedBooleanLiveData() protected val loadingCounter = CountedBooleanLiveData()
@JvmField
protected val errorEvent = SingleLiveEvent<Throwable>() protected val errorEvent = SingleLiveEvent<Throwable>()
val onError: LiveData<Throwable> val onError: LiveData<Throwable>

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.base.ui package org.koitharu.kotatsu.base.ui
import android.app.Service
import android.content.Intent import android.content.Intent
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
@@ -20,7 +19,7 @@ abstract class CoroutineIntentService : BaseService() {
final override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { final override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
launchCoroutine(intent, startId) launchCoroutine(intent, startId)
return Service.START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }
private fun launchCoroutine(intent: Intent?, startId: Int) = lifecycleScope.launch(errorHandler(startId)) { private fun launchCoroutine(intent: Intent?, startId: Int) = lifecycleScope.launch(errorHandler(startId)) {

View File

@@ -6,8 +6,6 @@ import androidx.recyclerview.widget.RecyclerView
abstract class BoundsScrollListener(private val offsetTop: Int, private val offsetBottom: Int) : abstract class BoundsScrollListener(private val offsetTop: Int, private val offsetBottom: Int) :
RecyclerView.OnScrollListener() { RecyclerView.OnScrollListener() {
constructor(offset: Int = 0) : this(offset, offset)
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
val layoutManager = (recyclerView.layoutManager as? LinearLayoutManager) ?: return val layoutManager = (recyclerView.layoutManager as? LinearLayoutManager) ?: return

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.base.ui.util
import android.app.Activity import android.app.Activity
import android.os.Bundle import android.os.Bundle
import androidx.core.app.ActivityCompat
import org.koitharu.kotatsu.base.ui.DefaultActivityLifecycleCallbacks import org.koitharu.kotatsu.base.ui.DefaultActivityLifecycleCallbacks
import java.util.WeakHashMap import java.util.WeakHashMap
import javax.inject.Inject import javax.inject.Inject
@@ -22,6 +23,6 @@ class ActivityRecreationHandle @Inject constructor() : DefaultActivityLifecycleC
fun recreateAll() { fun recreateAll() {
val snapshot = activities.keys.toList() val snapshot = activities.keys.toList()
snapshot.forEach { it.recreate() } snapshot.forEach { ActivityCompat.recreate(it) }
} }
} }

View File

@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.base.ui.util
import dagger.hilt.EntryPoint import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@EntryPoint @EntryPoint
@@ -11,8 +10,3 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
interface BaseActivityEntryPoint { interface BaseActivityEntryPoint {
val settings: AppSettings val settings: AppSettings
} }
// Hilt cannot inject into parametrized classes
fun BaseActivityEntryPoint.inject(activity: BaseActivity<*>) {
activity.settings = settings
}

View File

@@ -10,8 +10,10 @@ class WindowInsetsDelegate(
private val listener: WindowInsetsListener, private val listener: WindowInsetsListener,
) : OnApplyWindowInsetsListener, View.OnLayoutChangeListener { ) : OnApplyWindowInsetsListener, View.OnLayoutChangeListener {
@JvmField
var handleImeInsets: Boolean = false var handleImeInsets: Boolean = false
@JvmField
var interceptingWindowInsetsListener: OnApplyWindowInsetsListener? = null var interceptingWindowInsetsListener: OnApplyWindowInsetsListener? = null
private var lastInsets: Insets? = null private var lastInsets: Insets? = null

View File

@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.toList import org.koitharu.kotatsu.utils.ext.toList
import java.lang.ref.WeakReference
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -28,10 +29,14 @@ class MangaLoaderContextImpl @Inject constructor(
@ApplicationContext private val androidContext: Context, @ApplicationContext private val androidContext: Context,
) : MangaLoaderContext() { ) : MangaLoaderContext() {
private var webViewCached: WeakReference<WebView>? = null
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
override suspend fun evaluateJs(script: String): String? = withContext(Dispatchers.Main) { override suspend fun evaluateJs(script: String): String? = withContext(Dispatchers.Main) {
val webView = WebView(androidContext) val webView = webViewCached?.get() ?: WebView(androidContext).also {
webView.settings.javaScriptEnabled = true it.settings.javaScriptEnabled = true
webViewCached = WeakReference(it)
}
suspendCoroutine { cont -> suspendCoroutine { cont ->
webView.evaluateJavascript(script) { result -> webView.evaluateJavascript(script) { result ->
cont.resume(result?.takeUnless { it == "null" }) cont.resume(result?.takeUnless { it == "null" })

View File

@@ -12,13 +12,12 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
class ExitCallback( class ExitCallback(
private val activity: BaseActivity<*>, private val activity: MainActivity,
private val snackbarHost: View, private val snackbarHost: View,
) : OnBackPressedCallback(false) { ) : OnBackPressedCallback(false) {

View File

@@ -41,6 +41,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ActivityMainBinding import org.koitharu.kotatsu.databinding.ActivityMainBinding
import org.koitharu.kotatsu.details.service.MangaPrefetchService import org.koitharu.kotatsu.details.service.MangaPrefetchService
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
@@ -68,6 +69,7 @@ import org.koitharu.kotatsu.utils.ext.resolve
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.utils.ext.setNavigationBarTransparentCompat import org.koitharu.kotatsu.utils.ext.setNavigationBarTransparentCompat
import org.koitharu.kotatsu.utils.ext.tryLaunch import org.koitharu.kotatsu.utils.ext.tryLaunch
import javax.inject.Inject
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
private const val TAG_SEARCH = "search" private const val TAG_SEARCH = "search"
@@ -82,6 +84,9 @@ class MainActivity :
SearchSuggestionListener, SearchSuggestionListener,
MainNavigationDelegate.OnFragmentChangedListener { MainNavigationDelegate.OnFragmentChangedListener {
@Inject
lateinit var settings: AppSettings
private val viewModel by viewModels<MainViewModel>() private val viewModel by viewModels<MainViewModel>()
private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>() private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>()
private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback()) private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback())

View File

@@ -35,6 +35,7 @@ import org.koitharu.kotatsu.base.ui.BaseFullscreenActivity
import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.exceptions.resolve.DialogErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.DialogErrorObserver
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.databinding.ActivityReaderBinding import org.koitharu.kotatsu.databinding.ActivityReaderBinding
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@@ -66,6 +67,9 @@ class ReaderActivity :
OnApplyWindowInsetsListener, OnApplyWindowInsetsListener,
IdlingDetector.Callback { IdlingDetector.Callback {
@Inject
lateinit var settings: AppSettings
private val idlingDetector = IdlingDetector(TimeUnit.SECONDS.toMillis(10), this) private val idlingDetector = IdlingDetector(TimeUnit.SECONDS.toMillis(10), this)
private val viewModel: ReaderViewModel by viewModels() private val viewModel: ReaderViewModel by viewModels()

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.scrobbling.mal.data package org.koitharu.kotatsu.scrobbling.mal.data
import android.content.Context import android.content.Context
import android.util.Base64
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@@ -20,7 +21,7 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
import org.koitharu.kotatsu.utils.PKCEGenerator import java.security.SecureRandom
private const val REDIRECT_URI = "kotatsu://mal-auth" private const val REDIRECT_URI = "kotatsu://mal-auth"
private const val BASE_WEB_URL = "https://myanimelist.net" private const val BASE_WEB_URL = "https://myanimelist.net"
@@ -35,7 +36,7 @@ class MALRepository(
) : ScrobblerRepository { ) : ScrobblerRepository {
private val clientId = context.getString(R.string.mal_clientId) private val clientId = context.getString(R.string.mal_clientId)
private var codeVerifier: String = getPKCEChallengeCode() private val codeVerifier: String by lazy(::generateCodeVerifier)
override val oauthUrl: String override val oauthUrl: String
get() = "$BASE_WEB_URL/v1/oauth2/authorize?" + get() = "$BASE_WEB_URL/v1/oauth2/authorize?" +
@@ -177,11 +178,6 @@ class MALRepository(
storage.clear() storage.clear()
} }
private fun getPKCEChallengeCode(): String {
codeVerifier = PKCEGenerator.generateCodeVerifier()
return codeVerifier
}
private fun jsonToManga(json: JSONObject): ScrobblerManga? { private fun jsonToManga(json: JSONObject): ScrobblerManga? {
for (i in 0 until json.length()) { for (i in 0 until json.length()) {
val node = json.getJSONObject("node") val node = json.getJSONObject("node")
@@ -210,4 +206,10 @@ class MALRepository(
avatar = json.getString("picture") ?: AVATAR_STUB, avatar = json.getString("picture") ?: AVATAR_STUB,
service = ScrobblerService.MAL, service = ScrobblerService.MAL,
) )
private fun generateCodeVerifier(): String {
val codeVerifier = ByteArray(50)
SecureRandom().nextBytes(codeVerifier)
return Base64.encodeToString(codeVerifier, Base64.NO_WRAP or Base64.NO_PADDING or Base64.URL_SAFE)
}
} }

View File

@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.ListSelectionController import org.koitharu.kotatsu.base.ui.list.ListSelectionController
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.service.DownloadService import org.koitharu.kotatsu.download.ui.service.DownloadService
@@ -45,6 +46,9 @@ class MultiSearchActivity :
@Inject @Inject
lateinit var coil: ImageLoader lateinit var coil: ImageLoader
@Inject
lateinit var settings: AppSettings
private val viewModel by viewModels<MultiSearchViewModel>() private val viewModel by viewModels<MultiSearchViewModel>()
private lateinit var adapter: MultiSearchAdapter private lateinit var adapter: MultiSearchAdapter
private lateinit var selectionController: ListSelectionController private lateinit var selectionController: ListSelectionController

View File

@@ -1,27 +0,0 @@
package org.koitharu.kotatsu.utils
import android.content.Context
import android.content.res.Resources
object InternalResourceHelper {
fun getBoolean(context: Context, resName: String, defaultValue: Boolean): Boolean {
val id = getResourceId(resName, "bool")
return if (id != 0) {
context.createPackageContext("android", 0).resources.getBoolean(id)
} else {
defaultValue
}
}
/**
* Get resource id from system resources
* @param resName resource name to get
* @param type resource type of [resName] to get
* @return 0 if not available
*/
private fun getResourceId(resName: String, type: String): Int {
return Resources.getSystem().getIdentifier(resName, type, "android")
}
}

View File

@@ -1,16 +0,0 @@
package org.koitharu.kotatsu.utils
import android.util.Base64
import java.security.SecureRandom
object PKCEGenerator {
private const val PKCE_BASE64_ENCODE_SETTINGS = Base64.NO_WRAP or Base64.NO_PADDING or Base64.URL_SAFE
fun generateCodeVerifier(): String {
val codeVerifier = ByteArray(50)
SecureRandom().nextBytes(codeVerifier)
return Base64.encodeToString(codeVerifier, PKCE_BASE64_ENCODE_SETTINGS)
}
}

View File

@@ -1,11 +1,14 @@
package org.koitharu.kotatsu.utils package org.koitharu.kotatsu.utils
import android.view.View import android.view.View
import androidx.annotation.OptIn
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.google.android.material.badge.BadgeDrawable import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils import com.google.android.material.badge.BadgeUtils
import com.google.android.material.badge.ExperimentalBadgeUtils
@OptIn(ExperimentalBadgeUtils::class)
class ViewBadge( class ViewBadge(
private val anchor: View, private val anchor: View,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,

View File

@@ -40,7 +40,6 @@ import org.json.JSONException
import org.jsoup.internal.StringUtil.StringJoiner import org.jsoup.internal.StringUtil.StringJoiner
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.InternalResourceHelper
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserException
import kotlin.math.roundToLong import kotlin.math.roundToLong
@@ -108,7 +107,7 @@ fun SyncResult.onError(error: Throwable) {
fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float, alphaFactor: Float = 0.7f) { fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float, alphaFactor: Float = 0.7f) {
navigationBarColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && navigationBarColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
!InternalResourceHelper.getBoolean(context, "config_navBarNeedsScrim", true) !context.getSystemBoolean("config_navBarNeedsScrim", true)
) { ) {
Color.TRANSPARENT Color.TRANSPARENT
} else { } else {

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.utils.ext package org.koitharu.kotatsu.utils.ext
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import androidx.annotation.Px import androidx.annotation.Px
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -9,3 +11,13 @@ fun Resources.resolveDp(dp: Int) = (dp * displayMetrics.density).roundToInt()
@Px @Px
fun Resources.resolveDp(dp: Float) = dp * displayMetrics.density fun Resources.resolveDp(dp: Float) = dp * displayMetrics.density
@SuppressLint("DiscouragedApi")
fun Context.getSystemBoolean(resName: String, fallback: Boolean): Boolean {
val id = Resources.getSystem().getIdentifier(resName, "bool", "android")
return if (id != 0) {
createPackageContext("android", 0).resources.getBoolean(id)
} else {
fallback
}
}