Refactor and deprecations fixes

This commit is contained in:
Koitharu
2022-02-26 10:14:40 +02:00
parent 94e9fa35e2
commit def2d5f494
33 changed files with 169 additions and 248 deletions

View File

@@ -5,29 +5,29 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
class MangaIntent( class MangaIntent private constructor(
val manga: Manga?, val manga: Manga?,
val mangaId: Long, val mangaId: Long,
val uri: Uri? val uri: Uri?,
) { ) {
constructor(intent: Intent?) : this(
manga = intent?.getParcelableExtra(KEY_MANGA),
mangaId = intent?.getLongExtra(KEY_ID, ID_NONE) ?: ID_NONE,
uri = intent?.data
)
constructor(args: Bundle?) : this(
manga = args?.getParcelable(KEY_MANGA),
mangaId = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE,
uri = null
)
companion object { companion object {
fun from(intent: Intent?) = MangaIntent(
manga = intent?.getParcelableExtra(KEY_MANGA),
mangaId = intent?.getLongExtra(KEY_ID, ID_NONE) ?: ID_NONE,
uri = intent?.data
)
fun from(args: Bundle?) = MangaIntent(
manga = args?.getParcelable(KEY_MANGA),
mangaId = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE,
uri = null
)
const val ID_NONE = 0L const val ID_NONE = 0L
const val KEY_MANGA = "manga" const val KEY_MANGA = "manga"
const val KEY_ID = "id" const val KEY_ID = "id"
} }
} }

View File

@@ -1,24 +0,0 @@
package org.koitharu.kotatsu.base.domain
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSettings
object MangaProviderFactory {
fun getSources(settings: AppSettings, includeHidden: Boolean): List<MangaSource> {
val list = MangaSource.values().toList() - MangaSource.LOCAL
val order = settings.sourcesOrder
val sorted = list.sortedBy { x ->
val e = order.indexOf(x.ordinal)
if (e == -1) order.size + x.ordinal else e
}
return if (includeHidden) {
sorted
} else {
val hidden = settings.hiddenSources
sorted.filterNot { x ->
x.name in hidden
}
}
}
}

View File

@@ -5,7 +5,6 @@ import android.net.Uri
import android.util.Size import android.util.Size
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@@ -14,7 +13,6 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.utils.CacheUtils
import org.koitharu.kotatsu.utils.ext.await import org.koitharu.kotatsu.utils.ext.await
import org.koitharu.kotatsu.utils.ext.medianOrNull import org.koitharu.kotatsu.utils.ext.medianOrNull
import java.io.InputStream import java.io.InputStream
@@ -40,15 +38,14 @@ object MangaUtils : KoinComponent {
} }
} }
} else { } else {
val client = get<OkHttpClient>()
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
.get() .get()
.header(CommonHeaders.REFERER, page.referer) .header(CommonHeaders.REFERER, page.referer)
.cacheControl(CacheUtils.CONTROL_DISABLED) .cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
.build() .build()
client.newCall(request).await().use { get<OkHttpClient>().newCall(request).await().use {
withContext(Dispatchers.IO) { runInterruptible(Dispatchers.IO) {
getBitmapSize(it.body?.byteStream()) getBitmapSize(it.body?.byteStream())
} }
} }
@@ -66,10 +63,10 @@ object MangaUtils : KoinComponent {
val options = BitmapFactory.Options().apply { val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true inJustDecodeBounds = true
} }
BitmapFactory.decodeStream(input, null, options) BitmapFactory.decodeStream(input, null, options)?.recycle()
val imageHeight: Int = options.outHeight val imageHeight: Int = options.outHeight
val imageWidth: Int = options.outWidth val imageWidth: Int = options.outWidth
check(imageHeight > 0 && imageWidth > 0) check(imageHeight > 0 && imageWidth > 0)
return Size(imageWidth, imageHeight) return Size(imageWidth, imageHeight)
} }
} }

View File

@@ -17,8 +17,7 @@ abstract class AlertDialogFragment<B : ViewBinding> : DialogFragment() {
get() = checkNotNull(viewBinding) get() = checkNotNull(viewBinding)
final override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { final override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val inflater = activity?.layoutInflater ?: LayoutInflater.from(requireContext()) val binding = onInflateView(layoutInflater, null)
val binding = onInflateView(inflater, null)
viewBinding = binding viewBinding = binding
return MaterialAlertDialogBuilder(requireContext(), theme) return MaterialAlertDialogBuilder(requireContext(), theme)
.setView(binding.root) .setView(binding.root)
@@ -43,4 +42,4 @@ abstract class AlertDialogFragment<B : ViewBinding> : DialogFragment() {
protected fun bindingOrNull(): B? = viewBinding protected fun bindingOrNull(): B? = viewBinding
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
} }

View File

@@ -35,9 +35,10 @@ abstract class BaseActivity<B : ViewBinding> : AppCompatActivity(), OnApplyWindo
private var lastInsets: Insets = Insets.NONE private var lastInsets: Insets = Insets.NONE
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
val settings = get<AppSettings>()
when { when {
get<AppSettings>().isAmoledTheme -> setTheme(R.style.ThemeOverlay_Kotatsu_AMOLED) settings.isAmoledTheme -> setTheme(R.style.ThemeOverlay_Kotatsu_AMOLED)
get<AppSettings>().isDynamicTheme -> setTheme(R.style.Theme_Kotatsu_Monet) settings.isDynamicTheme -> setTheme(R.style.Theme_Kotatsu_Monet)
} }
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
@@ -130,4 +131,4 @@ abstract class BaseActivity<B : ViewBinding> : AppCompatActivity(), OnApplyWindo
super.onBackPressed() super.onBackPressed()
} }
} }
} }

View File

@@ -10,8 +10,7 @@ import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
abstract class BaseBottomSheet<B : ViewBinding> : abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
BottomSheetDialogFragment() {
private var viewBinding: B? = null private var viewBinding: B? = null
@@ -40,4 +39,4 @@ abstract class BaseBottomSheet<B : ViewBinding> :
} }
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
} }

View File

@@ -11,8 +11,8 @@ import androidx.preference.PreferenceFragmentCompat
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : PreferenceFragmentCompat(),
PreferenceFragmentCompat(), OnApplyWindowInsetsListener { OnApplyWindowInsetsListener {
protected val settings by inject<AppSettings>(mode = LazyThreadSafetyMode.NONE) protected val settings by inject<AppSettings>(mode = LazyThreadSafetyMode.NONE)
@@ -36,4 +36,4 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
) )
return insets return insets
} }
} }

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.core.network package org.koitharu.kotatsu.core.network
import okhttp3.CacheControl
object CommonHeaders { object CommonHeaders {
const val REFERER = "Referer" const val REFERER = "Referer"
@@ -7,4 +9,7 @@ object CommonHeaders {
const val ACCEPT = "Accept" const val ACCEPT = "Accept"
const val CONTENT_DISPOSITION = "Content-Disposition" const val CONTENT_DISPOSITION = "Content-Disposition"
const val COOKIE = "Cookie" const val COOKIE = "Cookie"
}
val CACHE_CONTROL_DISABLED: CacheControl
get() = CacheControl.Builder().noStore().build()
}

View File

@@ -2,27 +2,24 @@ package org.koitharu.kotatsu.core.network
import okhttp3.CookieJar import okhttp3.CookieJar
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.koin.android.ext.koin.androidContext
import org.koin.core.qualifier.named
import org.koin.dsl.bind import org.koin.dsl.bind
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.CacheUtils import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.utils.DownloadManagerHelper import org.koitharu.kotatsu.utils.DownloadManagerHelper
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
val networkModule val networkModule
get() = module { get() = module {
single { AndroidCookieJar() } bind CookieJar::class single { AndroidCookieJar() } bind CookieJar::class
single(named(CacheUtils.QUALIFIER_HTTP)) { CacheUtils.createHttpCache(androidContext()) }
single { single {
OkHttpClient.Builder().apply { OkHttpClient.Builder().apply {
connectTimeout(20, TimeUnit.SECONDS) connectTimeout(20, TimeUnit.SECONDS)
readTimeout(60, TimeUnit.SECONDS) readTimeout(60, TimeUnit.SECONDS)
writeTimeout(20, TimeUnit.SECONDS) writeTimeout(20, TimeUnit.SECONDS)
cookieJar(get()) cookieJar(get())
cache(get(named(CacheUtils.QUALIFIER_HTTP))) cache(get<LocalStorageManager>().createHttpCache())
addInterceptor(UserAgentInterceptor()) addInterceptor(UserAgentInterceptor())
addInterceptor(CloudFlareInterceptor()) addInterceptor(CloudFlareInterceptor())
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@@ -32,4 +29,4 @@ val networkModule
} }
factory { DownloadManagerHelper(get(), get()) } factory { DownloadManagerHelper(get(), get()) }
single { MangaLoaderContext(get(), get()) } single { MangaLoaderContext(get(), get()) }
} }

View File

@@ -64,10 +64,10 @@ abstract class RemoteMangaRepository(
protected fun generateUid(url: String): Long { protected fun generateUid(url: String): Long {
var h = 1125899906842597L var h = 1125899906842597L
source.name.forEach { c -> source.name.forEach { c ->
h = 31 * h + c.toLong() h = 31 * h + c.code
} }
url.forEach { c -> url.forEach { c ->
h = 31 * h + c.toLong() h = 31 * h + c.code
} }
return h return h
} }
@@ -75,7 +75,7 @@ abstract class RemoteMangaRepository(
protected fun generateUid(id: Long): Long { protected fun generateUid(id: Long): Long {
var h = 1125899906842597L var h = 1125899906842597L
source.name.forEach { c -> source.name.forEach { c ->
h = 31 * h + c.toLong() h = 31 * h + c.code
} }
h = 31 * h + id h = 31 * h + id
return h return h
@@ -84,4 +84,4 @@ abstract class RemoteMangaRepository(
protected fun parseFailed(message: String? = null): Nothing { protected fun parseFailed(message: String? = null): Nothing {
throw ParseException(message) throw ParseException(message)
} }
} }

View File

@@ -13,6 +13,7 @@ import com.google.android.material.color.DynamicColors
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.utils.ext.toUriOrNull import org.koitharu.kotatsu.utils.ext.toUriOrNull
import java.io.File import java.io.File
@@ -122,22 +123,17 @@ class AppSettings(context: Context) {
val isPagesNumbersEnabled: Boolean val isPagesNumbersEnabled: Boolean
get() = prefs.getBoolean(KEY_PAGES_NUMBERS, false) get() = prefs.getBoolean(KEY_PAGES_NUMBERS, false)
fun getFallbackStorageDir(): File? { var mangaStorageDir: File?
return prefs.getString(KEY_LOCAL_STORAGE, null)?.let { get() = prefs.getString(KEY_LOCAL_STORAGE, null)?.let {
File(it) File(it)
}?.takeIf { it.exists() } }?.takeIf { it.exists() }
} set(value) = prefs.edit {
if (value == null) {
@Deprecated("Use LocalStorageManager instead")
fun setStorageDir(file: File?) {
prefs.edit {
if (file == null) {
remove(KEY_LOCAL_STORAGE) remove(KEY_LOCAL_STORAGE)
} else { } else {
putString(KEY_LOCAL_STORAGE, file.path) putString(KEY_LOCAL_STORAGE, value.path)
} }
} }
}
fun getDateFormat(format: String = prefs.getString(KEY_DATE_FORMAT, "").orEmpty()): DateFormat = fun getDateFormat(format: String = prefs.getString(KEY_DATE_FORMAT, "").orEmpty()): DateFormat =
when (format) { when (format) {
@@ -145,7 +141,21 @@ class AppSettings(context: Context) {
else -> SimpleDateFormat(format, Locale.getDefault()) else -> SimpleDateFormat(format, Locale.getDefault())
} }
@Deprecated("Use observe()") fun getMangaSources(includeHidden: Boolean): List<MangaSource> {
val list = MangaSource.values().toMutableList()
list.remove(MangaSource.LOCAL)
val order = sourcesOrder
list.sortBy { x ->
val e = order.indexOf(x.ordinal)
if (e == -1) order.size + x.ordinal else e
}
if (!includeHidden) {
val hidden = hiddenSources
list.removeAll { x -> x.name in hidden }
}
return list
}
fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) { fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
prefs.registerOnSharedPreferenceChangeListener(listener) prefs.registerOnSharedPreferenceChangeListener(listener)
} }

View File

@@ -10,4 +10,4 @@ enum class ReaderMode(val id: Int) {
fun valueOf(id: Int) = values().firstOrNull { it.id == id } fun valueOf(id: Int) = values().firstOrNull { it.id == id }
} }
} }

View File

@@ -47,7 +47,7 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
TabLayoutMediator.TabConfigurationStrategy { TabLayoutMediator.TabConfigurationStrategy {
private val viewModel by viewModel<DetailsViewModel> { private val viewModel by viewModel<DetailsViewModel> {
parametersOf(MangaIntent.from(intent)) parametersOf(MangaIntent(intent))
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -280,4 +280,4 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
.putExtra(MangaIntent.KEY_ID, mangaId) .putExtra(MangaIntent.KEY_ID, mangaId)
} }
} }
} }

View File

@@ -15,9 +15,7 @@ import androidx.core.view.updatePadding
import coil.ImageLoader import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.util.CoilUtils import coil.util.CoilUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@@ -33,7 +31,7 @@ import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.utils.FileSizeUtils import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickListener, class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickListener,
@@ -114,10 +112,8 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
val file = manga.url.toUri().toFileOrNull() val file = manga.url.toUri().toFileOrNull()
if (file != null) { if (file != null) {
viewLifecycleScope.launch { viewLifecycleScope.launch {
val size = withContext(Dispatchers.IO) { val size = file.computeSize()
file.length() textViewSize.text = FileSize.BYTES.format(requireContext(), size)
}
textViewSize.text = FileSizeUtils.formatBytes(requireContext(), size)
} }
sizeContainer.isVisible = true sizeContainer.isVisible = true
} else { } else {
@@ -270,4 +266,4 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
.lifecycle(viewLifecycleOwner) .lifecycle(viewLifecycleOwner)
.enqueueWith(coil) .enqueueWith(coil)
} }
} }

View File

@@ -21,7 +21,6 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.local.data.MangaZip import org.koitharu.kotatsu.local.data.MangaZip
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.utils.CacheUtils
import org.koitharu.kotatsu.utils.ext.await import org.koitharu.kotatsu.utils.ext.await
import org.koitharu.kotatsu.utils.ext.deleteAwait import org.koitharu.kotatsu.utils.ext.deleteAwait
import org.koitharu.kotatsu.utils.ext.waitForNetwork import org.koitharu.kotatsu.utils.ext.waitForNetwork
@@ -134,7 +133,7 @@ class DownloadManager(
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
.header(CommonHeaders.REFERER, referer) .header(CommonHeaders.REFERER, referer)
.cacheControl(CacheUtils.CONTROL_DISABLED) .cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
.get() .get()
.build() .build()
val call = okHttp.newCall(request) val call = okHttp.newCall(request)
@@ -234,4 +233,4 @@ class DownloadManager(
private const val DOWNLOAD_ERROR_DELAY = 500L private const val DOWNLOAD_ERROR_DELAY = 500L
private const val TEMP_PAGE_FILE = "page.tmp" private const val TEMP_PAGE_FILE = "page.tmp"
} }
} }

View File

@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.local.data package org.koitharu.kotatsu.local.data
enum class Cache(val dir: String) { enum class CacheDir(val dir: String) {
THUMBS("image_cache"), THUMBS("image_cache"),
PAGES("pages"); PAGES("pages");
} }

View File

@@ -1,20 +1,41 @@
package org.koitharu.kotatsu.local.data package org.koitharu.kotatsu.local.data
import android.content.Context import android.content.Context
import android.os.StatFs
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import okhttp3.Cache
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.utils.ext.computeSize
import org.koitharu.kotatsu.utils.ext.getStorageName import org.koitharu.kotatsu.utils.ext.getStorageName
import java.io.File import java.io.File
private const val DIR_NAME = "manga" private const val DIR_NAME = "manga"
private const val CACHE_DISK_PERCENTAGE = 0.02
private const val CACHE_SIZE_MIN: Long = 10 * 1024 * 1024 // 10MB
private const val CACHE_SIZE_MAX: Long = 250 * 1024 * 1024 // 250MB
class LocalStorageManager( class LocalStorageManager(
private val context: Context, private val context: Context,
private val settings: AppSettings, private val settings: AppSettings,
) { ) {
fun createHttpCache(): Cache {
val directory = File(context.externalCacheDir ?: context.cacheDir, "http")
directory.mkdirs()
val maxSize = calculateDiskCacheSize(directory)
return Cache(directory, maxSize)
}
suspend fun computeCacheSize(cache: CacheDir) = runInterruptible(Dispatchers.IO) {
getCacheDirs(cache.dir).sumOf { it.computeSize() }
}
suspend fun clearCache(cache: CacheDir) = runInterruptible(Dispatchers.IO) {
getCacheDirs(cache.dir).forEach { it.deleteRecursively() }
}
suspend fun getReadableDirs(): List<File> = runInterruptible(Dispatchers.IO) { suspend fun getReadableDirs(): List<File> = runInterruptible(Dispatchers.IO) {
getConfiguredStorageDirs() getConfiguredStorageDirs()
.filter { it.isReadable() } .filter { it.isReadable() }
@@ -26,7 +47,7 @@ class LocalStorageManager(
} }
suspend fun getDefaultWriteableDir(): File? = runInterruptible(Dispatchers.IO) { suspend fun getDefaultWriteableDir(): File? = runInterruptible(Dispatchers.IO) {
val preferredDir = settings.getFallbackStorageDir()?.takeIf { it.isWriteable() } val preferredDir = settings.mangaStorageDir?.takeIf { it.isWriteable() }
preferredDir ?: getFallbackStorageDir()?.takeIf { it.isWriteable() } preferredDir ?: getFallbackStorageDir()?.takeIf { it.isWriteable() }
} }
@@ -35,7 +56,7 @@ class LocalStorageManager(
@WorkerThread @WorkerThread
private fun getConfiguredStorageDirs(): MutableSet<File> { private fun getConfiguredStorageDirs(): MutableSet<File> {
val set = getAvailableStorageDirs() val set = getAvailableStorageDirs()
settings.getFallbackStorageDir()?.let { settings.mangaStorageDir?.let {
set.add(it) set.add(it)
} }
return set return set
@@ -57,6 +78,24 @@ class LocalStorageManager(
} }
} }
@WorkerThread
private fun getCacheDirs(subDir: String): MutableSet<File> {
val result = LinkedHashSet<File>()
result += File(context.cacheDir, subDir)
result += context.getExternalFilesDirs(subDir)
return result
}
private fun calculateDiskCacheSize(cacheDirectory: File): Long {
return try {
val cacheDir = StatFs(cacheDirectory.absolutePath)
val size = CACHE_DISK_PERCENTAGE * cacheDir.blockCountLong * cacheDir.blockSizeLong
return size.toLong().coerceIn(CACHE_SIZE_MIN, CACHE_SIZE_MAX)
} catch (_: Exception) {
CACHE_SIZE_MIN
}
}
private fun File.isReadable() = runCatching { private fun File.isReadable() = runCatching {
canRead() canRead()
}.getOrDefault(false) }.getOrDefault(false)
@@ -64,4 +103,4 @@ class LocalStorageManager(
private fun File.isWriteable() = runCatching { private fun File.isWriteable() = runCatching {
canWrite() canWrite()
}.getOrDefault(false) }.getOrDefault(false)
} }

View File

@@ -2,35 +2,25 @@ package org.koitharu.kotatsu.local.data
import android.content.Context import android.content.Context
import com.tomclaw.cache.DiskLruCache import com.tomclaw.cache.DiskLruCache
import org.koitharu.kotatsu.utils.FileSizeUtils import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.ext.longHashCode import org.koitharu.kotatsu.utils.ext.longHashCode
import org.koitharu.kotatsu.utils.ext.subdir import org.koitharu.kotatsu.utils.ext.subdir
import org.koitharu.kotatsu.utils.ext.takeIfReadable import org.koitharu.kotatsu.utils.ext.takeIfReadable
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream
class PagesCache(context: Context) { class PagesCache(context: Context) {
private val cacheDir = context.externalCacheDir ?: context.cacheDir private val cacheDir = context.externalCacheDir ?: context.cacheDir
private val lruCache = DiskLruCache.create( private val lruCache = DiskLruCache.create(
cacheDir.subdir(Cache.PAGES.dir), cacheDir.subdir(CacheDir.PAGES.dir),
FileSizeUtils.mbToBytes(200) FileSize.MEGABYTES.convert(200, FileSize.BYTES),
) )
operator fun get(url: String): File? { operator fun get(url: String): File? {
return lruCache.get(url)?.takeIfReadable() return lruCache.get(url)?.takeIfReadable()
} }
@Deprecated("Useless lambda")
fun put(url: String, writer: (OutputStream) -> Unit): File {
val file = File(cacheDir, url.longHashCode().toString())
file.outputStream().use(writer)
val res = lruCache.put(url, file)
file.delete()
return res
}
fun put(url: String, inputStream: InputStream): File { fun put(url: String, inputStream: InputStream): File {
val file = File(cacheDir, url.longHashCode().toString()) val file = File(cacheDir, url.longHashCode().toString())
file.outputStream().use { out -> file.outputStream().use { out ->
@@ -40,4 +30,4 @@ class PagesCache(context: Context) {
file.delete() file.delete()
return res return res
} }
} }

View File

@@ -5,7 +5,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.base.domain.MangaProviderFactory
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
@@ -25,7 +24,7 @@ class MainViewModel(
val remoteSources = settings.observe() val remoteSources = settings.observe()
.filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN } .filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN }
.onStart { emit("") } .onStart { emit("") }
.map { MangaProviderFactory.getSources(settings, includeHidden = false) } .map { settings.getMangaSources(includeHidden = false) }
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default) .asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
fun openLastReader() { fun openLastReader() {
@@ -35,4 +34,4 @@ class MainViewModel(
onOpenReader.call(manga) onOpenReader.call(manga)
} }
} }
} }

View File

@@ -15,7 +15,6 @@ import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.utils.CacheUtils
import org.koitharu.kotatsu.utils.ext.await import org.koitharu.kotatsu.utils.ext.await
import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf
import java.io.File import java.io.File
@@ -70,7 +69,7 @@ class PageLoader(
.get() .get()
.header(CommonHeaders.REFERER, page.referer) .header(CommonHeaders.REFERER, page.referer)
.header(CommonHeaders.ACCEPT, "image/webp,image/png;q=0.9,image/jpeg,*/*;q=0.8") .header(CommonHeaders.ACCEPT, "image/webp,image/png;q=0.9,image/jpeg,*/*;q=0.8")
.cacheControl(CacheUtils.CONTROL_DISABLED) .cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
.build() .build()
okHttp.newCall(request).await().use { response -> okHttp.newCall(request).await().use { response ->
check(response.isSuccessful) { check(response.isSuccessful) {
@@ -103,4 +102,4 @@ class PageLoader(
} }
private companion object Lock private companion object Lock
} }

View File

@@ -56,7 +56,7 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
ActivityResultCallback<Boolean>, ReaderControlDelegate.OnInteractionListener { ActivityResultCallback<Boolean>, ReaderControlDelegate.OnInteractionListener {
private val viewModel by viewModel<ReaderViewModel> { private val viewModel by viewModel<ReaderViewModel> {
parametersOf(MangaIntent.from(intent), intent?.getParcelableExtra<ReaderState>(EXTRA_STATE)) parametersOf(MangaIntent(intent), intent?.getParcelableExtra<ReaderState>(EXTRA_STATE))
} }
private lateinit var touchHelper: GridTouchHelper private lateinit var touchHelper: GridTouchHelper
@@ -371,4 +371,4 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
.putExtra(EXTRA_STATE, state) .putExtra(EXTRA_STATE, state)
} }
} }
} }

View File

@@ -160,7 +160,7 @@ class ReaderViewModel(
val downloadId = downloadManagerHelper.downloadPage(page, pageUrl) val downloadId = downloadManagerHelper.downloadPage(page, pageUrl)
val uri = downloadManagerHelper.awaitDownload(downloadId) val uri = downloadManagerHelper.awaitDownload(downloadId)
onPageSaved.postCall(uri) onPageSaved.postCall(uri)
} catch (e: CancellationException) { } catch (_: CancellationException) {
} catch (e: Exception) { } catch (e: Exception) {
onPageSaved.postCall(null) onPageSaved.postCall(null)
} }
@@ -267,4 +267,4 @@ class ReaderViewModel(
} }
} }
} }

View File

@@ -9,7 +9,6 @@ import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.base.domain.MangaProviderFactory
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
@@ -27,7 +26,7 @@ class MangaSearchRepository(
) { ) {
fun globalSearch(query: String, concurrency: Int = DEFAULT_CONCURRENCY): Flow<Manga> = fun globalSearch(query: String, concurrency: Int = DEFAULT_CONCURRENCY): Flow<Manga> =
MangaProviderFactory.getSources(settings, includeHidden = false).asFlow() settings.getMangaSources(includeHidden = false).asFlow()
.flatMapMerge(concurrency) { source -> .flatMapMerge(concurrency) { source ->
runCatching { runCatching {
MangaRepository(source).getList2( MangaRepository(source).getList2(
@@ -128,4 +127,4 @@ class MangaSearchRepository(
return false return false
} }
} }
} }

View File

@@ -20,7 +20,7 @@ import org.koitharu.kotatsu.core.github.AppVersion
import org.koitharu.kotatsu.core.github.GithubRepository import org.koitharu.kotatsu.core.github.GithubRepository
import org.koitharu.kotatsu.core.github.VersionId import org.koitharu.kotatsu.core.github.VersionId
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.utils.FileSizeUtils import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.ext.byte2HexFormatted import org.koitharu.kotatsu.utils.ext.byte2HexFormatted
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
@@ -85,7 +85,7 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
append( append(
activity.getString( activity.getString(
R.string.size_s, R.string.size_s,
FileSizeUtils.formatBytes(activity, version.apkSize) FileSize.BYTES.format(activity, version.apkSize),
) )
) )
appendLine() appendLine()
@@ -144,4 +144,4 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
} }
} }
} }
} }

View File

@@ -5,20 +5,18 @@ import android.view.View
import androidx.preference.Preference import androidx.preference.Preference
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.network.AndroidCookieJar import org.koitharu.kotatsu.core.network.AndroidCookieJar
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.local.data.Cache import org.koitharu.kotatsu.local.data.CacheDir
import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.search.domain.MangaSearchRepository import org.koitharu.kotatsu.search.domain.MangaSearchRepository
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.CacheUtils import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.FileSizeUtils
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
@@ -26,6 +24,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
private val trackerRepo by inject<TrackingRepository>(mode = LazyThreadSafetyMode.NONE) private val trackerRepo by inject<TrackingRepository>(mode = LazyThreadSafetyMode.NONE)
private val searchRepository by inject<MangaSearchRepository>(mode = LazyThreadSafetyMode.NONE) private val searchRepository by inject<MangaSearchRepository>(mode = LazyThreadSafetyMode.NONE)
private val storageManager by inject<LocalStorageManager>(mode = LazyThreadSafetyMode.NONE)
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_history) addPreferencesFromResource(R.xml.pref_history)
@@ -35,18 +34,14 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
findPreference<Preference>(AppSettings.KEY_PAGES_CACHE_CLEAR)?.let { pref -> findPreference<Preference>(AppSettings.KEY_PAGES_CACHE_CLEAR)?.let { pref ->
viewLifecycleScope.launchWhenResumed { viewLifecycleScope.launchWhenResumed {
val size = withContext(Dispatchers.IO) { val size = storageManager.computeCacheSize(CacheDir.PAGES)
CacheUtils.computeCacheSize(pref.context, Cache.PAGES.dir) pref.summary = FileSize.BYTES.format(pref.context, size)
}
pref.summary = FileSizeUtils.formatBytes(pref.context, size)
} }
} }
findPreference<Preference>(AppSettings.KEY_THUMBS_CACHE_CLEAR)?.let { pref -> findPreference<Preference>(AppSettings.KEY_THUMBS_CACHE_CLEAR)?.let { pref ->
viewLifecycleScope.launchWhenResumed { viewLifecycleScope.launchWhenResumed {
val size = withContext(Dispatchers.IO) { val size = storageManager.computeCacheSize(CacheDir.THUMBS)
CacheUtils.computeCacheSize(pref.context, Cache.THUMBS.dir) pref.summary = FileSize.BYTES.format(pref.context, size)
}
pref.summary = FileSizeUtils.formatBytes(pref.context, size)
} }
} }
findPreference<Preference>(AppSettings.KEY_SEARCH_HISTORY_CLEAR)?.let { pref -> findPreference<Preference>(AppSettings.KEY_SEARCH_HISTORY_CLEAR)?.let { pref ->
@@ -68,11 +63,11 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
override fun onPreferenceTreeClick(preference: Preference): Boolean { override fun onPreferenceTreeClick(preference: Preference): Boolean {
return when (preference.key) { return when (preference.key) {
AppSettings.KEY_PAGES_CACHE_CLEAR -> { AppSettings.KEY_PAGES_CACHE_CLEAR -> {
clearCache(preference, Cache.PAGES) clearCache(preference, CacheDir.PAGES)
true true
} }
AppSettings.KEY_THUMBS_CACHE_CLEAR -> { AppSettings.KEY_THUMBS_CACHE_CLEAR -> {
clearCache(preference, Cache.THUMBS) clearCache(preference, CacheDir.THUMBS)
true true
} }
AppSettings.KEY_COOKIES_CLEAR -> { AppSettings.KEY_COOKIES_CLEAR -> {
@@ -100,16 +95,14 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
} }
} }
private fun clearCache(preference: Preference, cache: Cache) { private fun clearCache(preference: Preference, cache: CacheDir) {
val ctx = preference.context.applicationContext val ctx = preference.context.applicationContext
viewLifecycleScope.launch { viewLifecycleScope.launch {
try { try {
preference.isEnabled = false preference.isEnabled = false
val size = withContext(Dispatchers.IO) { storageManager.clearCache(cache)
CacheUtils.clearCache(ctx, cache.dir) val size = storageManager.computeCacheSize(cache)
CacheUtils.computeCacheSize(ctx, cache.dir) preference.summary = FileSize.BYTES.format(ctx, size)
}
preference.summary = FileSizeUtils.formatBytes(ctx, size)
} catch (e: Exception) { } catch (e: Exception) {
preference.summary = e.getDisplayMessage(ctx.resources) preference.summary = e.getDisplayMessage(ctx.resources)
} finally { } finally {
@@ -154,4 +147,4 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
} }
}.show() }.show()
} }
} }

View File

@@ -162,7 +162,7 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
} }
override fun onStorageSelected(file: File) { override fun onStorageSelected(file: File) {
settings.setStorageDir(file) settings.mangaStorageDir = file
} }
private fun Preference.bindStorageName() { private fun Preference.bindStorageName() {

View File

@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.settings.sources
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaProviderFactory
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -70,7 +69,7 @@ class SourcesSettingsViewModel(
} }
private fun buildList() { private fun buildList() {
val sources = MangaProviderFactory.getSources(settings, includeHidden = true) val sources = settings.getMangaSources(includeHidden = true)
val hiddenSources = settings.hiddenSources val hiddenSources = settings.hiddenSources
val query = searchQuery val query = searchQuery
if (!query.isNullOrEmpty()) { if (!query.isNullOrEmpty()) {
@@ -155,4 +154,4 @@ class SourcesSettingsViewModel(
} }
} }
} }
} }

View File

@@ -1,55 +0,0 @@
package org.koitharu.kotatsu.utils
import android.content.Context
import android.os.StatFs
import androidx.annotation.WorkerThread
import okhttp3.Cache
import okhttp3.CacheControl
import org.koitharu.kotatsu.utils.ext.computeSize
import org.koitharu.kotatsu.utils.ext.sub
import java.io.File
object CacheUtils {
const val QUALIFIER_HTTP = "cache_http"
val CONTROL_DISABLED = CacheControl.Builder()
.noStore()
.build()
fun getCacheDirs(context: Context) = (context.externalCacheDirs + context.cacheDir)
.filterNotNull()
.distinctBy { it.absolutePath }
@WorkerThread
fun computeCacheSize(context: Context, name: String) = getCacheDirs(context)
.map { File(it, name) }
.sumOf { x -> x.computeSize() }
@WorkerThread
fun clearCache(context: Context, name: String) = getCacheDirs(context)
.map { File(it, name) }
.forEach { it.deleteRecursively() }
// FIXME need async implementation
fun createHttpCache(context: Context): Cache {
val directory = (context.externalCacheDir ?: context.cacheDir).sub("http")
directory.mkdirs()
val maxSize = calculateDiskCacheSize(directory) // TODO blocking call
return Cache(directory, maxSize)
}
private fun calculateDiskCacheSize(cacheDirectory: File): Long {
return try {
val cacheDir = StatFs(cacheDirectory.absolutePath)
val size = DISK_CACHE_PERCENTAGE * cacheDir.blockCountLong * cacheDir.blockSizeLong
return size.toLong().coerceIn(MIN_DISK_CACHE_SIZE, MAX_DISK_CACHE_SIZE)
} catch (_: Exception) {
MIN_DISK_CACHE_SIZE
}
}
private const val DISK_CACHE_PERCENTAGE = 0.02
private const val MIN_DISK_CACHE_SIZE: Long = 10 * 1024 * 1024 // 10MB
private const val MAX_DISK_CACHE_SIZE: Long = 250 * 1024 * 1024 // 250MB
}

View File

@@ -6,14 +6,14 @@ import java.text.DecimalFormat
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.pow import kotlin.math.pow
enum class FileSize(private val multiplier: Int) {
object FileSizeUtils { BYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024);
fun mbToBytes(mb: Int) = 1024L * 1024L * mb fun convert(amount: Long, target: FileSize): Long = amount * multiplier / target.multiplier
fun kbToBytes(kb: Int) = 1024L * kb fun format(context: Context, amount: Long): String {
val bytes = amount * multiplier
fun formatBytes(context: Context, bytes: Long): String {
val units = context.getString(R.string.text_file_sizes).split('|') val units = context.getString(R.string.text_file_sizes).split('|')
if (bytes <= 0) { if (bytes <= 0) {
return "0 ${units.first()}" return "0 ${units.first()}"
@@ -23,10 +23,13 @@ object FileSizeUtils {
append( append(
DecimalFormat("#,##0.#").format( DecimalFormat("#,##0.#").format(
bytes / 1024.0.pow(digitGroups.toDouble()) bytes / 1024.0.pow(digitGroups.toDouble())
).toString() )
) )
append(' ') val unit = units.getOrNull(digitGroups)
append(units.getOrNull(digitGroups).orEmpty()) if (unit != null) {
append(' ')
append(unit)
}
} }
} }
} }

View File

@@ -5,15 +5,17 @@ import android.os.Build
object PendingIntentCompat { object PendingIntentCompat {
@JvmField
val FLAG_IMMUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val FLAG_IMMUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_IMMUTABLE
} else { } else {
0 0
} }
@JvmField
val FLAG_MUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val FLAG_MUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE PendingIntent.FLAG_MUTABLE
} else { } else {
0 0
} }
} }

View File

@@ -91,4 +91,4 @@ sealed class Motion {
anim.interpolator = DecelerateInterpolator() anim.interpolator = DecelerateInterpolator()
} }
} }
} }

View File

@@ -1,14 +0,0 @@
package org.koitharu.kotatsu.utils.delegates
import android.os.Parcelable
import androidx.fragment.app.Fragment
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class ParcelableArgumentDelegate<T : Parcelable>(private val name: String) :
ReadOnlyProperty<Fragment, T> {
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
return thisRef.requireArguments().getParcelable(name)!!
}
}

View File

@@ -1,12 +0,0 @@
package org.koitharu.kotatsu.utils.delegates
import androidx.fragment.app.Fragment
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class StringArgumentDelegate(private val name: String) : ReadOnlyProperty<Fragment, String?> {
override fun getValue(thisRef: Fragment, property: KProperty<*>): String? {
return thisRef.arguments?.getString(name)
}
}