Cleanup extensions

pull/141/head
Koitharu 4 years ago
parent 9c9a389aa5
commit 5e82c75893
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -7,7 +7,7 @@
<option name="testRunner" value="GRADLE" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" /> <option name="gradleJvm" value="Android Studio default JDK" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

@ -26,7 +26,8 @@ import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
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.core.prefs.AppSettings
abstract class BaseActivity<B : ViewBinding> : AppCompatActivity(), abstract class BaseActivity<B : ViewBinding> :
AppCompatActivity(),
WindowInsetsDelegate.WindowInsetsListener { WindowInsetsDelegate.WindowInsetsListener {
protected lateinit var binding: B protected lateinit var binding: B

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.base.ui.dialog
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.BaseAdapter import android.widget.BaseAdapter
@ -12,7 +13,6 @@ import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ItemStorageBinding import org.koitharu.kotatsu.databinding.ItemStorageBinding
import org.koitharu.kotatsu.local.data.LocalStorageManager import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.utils.ext.inflate
import java.io.File import java.io.File
class StorageSelectDialog private constructor(private val delegate: AlertDialog) : class StorageSelectDialog private constructor(private val delegate: AlertDialog) :
@ -66,7 +66,7 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
val volumes = getAvailableVolumes(storageManager) val volumes = getAvailableVolumes(storageManager)
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: parent.inflate(R.layout.item_storage) val view = convertView ?: LayoutInflater.from(parent.context).inflate(R.layout.item_storage, parent, false)
val binding = (view.tag as? ItemStorageBinding) ?: ItemStorageBinding.bind(view).also { val binding = (view.tag as? ItemStorageBinding) ?: ItemStorageBinding.bind(view).also {
view.tag = it view.tag = it
} }

@ -13,12 +13,12 @@ import android.graphics.drawable.shapes.RectShape
import android.util.AttributeSet import android.util.AttributeSet
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.appcompat.widget.AppCompatCheckedTextView import androidx.appcompat.widget.AppCompatCheckedTextView
import androidx.core.content.res.use
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes
import com.google.android.material.ripple.RippleUtils import com.google.android.material.ripple.RippleUtils
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel import com.google.android.material.shape.ShapeAppearanceModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.ext.getThemeColorStateList
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
class ListItemTextView @JvmOverloads constructor( class ListItemTextView @JvmOverloads constructor(
@ -119,8 +119,7 @@ class ListItemTextView @JvmOverloads constructor(
} }
private fun getRippleColorFallback(context: Context): ColorStateList { private fun getRippleColorFallback(context: Context): ColorStateList {
return context.obtainStyledAttributes(intArrayOf(android.R.attr.colorControlHighlight)).use { return context.getThemeColorStateList(android.R.attr.colorControlHighlight)
it.getColorStateList(0) ?: ColorStateList.valueOf(Color.TRANSPARENT)
} ?: ColorStateList.valueOf(Color.TRANSPARENT)
} }
} }

@ -11,6 +11,10 @@ import androidx.collection.arraySetOf
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
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
@ -19,10 +23,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.getEnumValue import org.koitharu.kotatsu.utils.ext.getEnumValue
import org.koitharu.kotatsu.utils.ext.putEnumValue import org.koitharu.kotatsu.utils.ext.putEnumValue
import org.koitharu.kotatsu.utils.ext.toUriOrNull import org.koitharu.kotatsu.utils.ext.toUriOrNull
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class AppSettings(context: Context) { class AppSettings(context: Context) {

@ -5,6 +5,7 @@ import android.os.Bundle
import android.text.Spanned import android.text.Spanned
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.view.* import android.view.*
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.net.toUri import androidx.core.net.toUri
@ -224,14 +225,16 @@ class DetailsFragment :
if (viewModel.readingHistory.value == null) { if (viewModel.readingHistory.value == null) {
return false return false
} }
v.showPopupMenu(R.menu.popup_read) { val menu = PopupMenu(v.context, v)
when (it.itemId) { menu.inflate(R.menu.popup_read)
menu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.action_read -> { R.id.action_read -> {
val branch = viewModel.selectedBranchValue val branch = viewModel.selectedBranchValue
startActivity( startActivity(
ReaderActivity.newIntent( ReaderActivity.newIntent(
context = context ?: return@showPopupMenu false, context = context ?: return@setOnMenuItemClickListener false,
manga = viewModel.manga.value ?: return@showPopupMenu false, manga = viewModel.manga.value ?: return@setOnMenuItemClickListener false,
state = viewModel.chapters.value?.firstOrNull { c -> state = viewModel.chapters.value?.firstOrNull { c ->
c.chapter.branch == branch c.chapter.branch == branch
}?.let { c -> }?.let { c ->
@ -244,6 +247,7 @@ class DetailsFragment :
else -> false else -> false
} }
} }
menu.show()
return true return true
} }
else -> return false else -> return false

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.favourites.ui
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
@ -10,7 +11,6 @@ import androidx.core.view.updatePadding
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import java.util.*
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
@ -25,7 +25,7 @@ import org.koitharu.kotatsu.main.ui.AppBarOwner
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.measureHeight import org.koitharu.kotatsu.utils.ext.measureHeight
import org.koitharu.kotatsu.utils.ext.showPopupMenu import java.util.*
class FavouritesContainerFragment : class FavouritesContainerFragment :
BaseFragment<FragmentFavouritesBinding>(), BaseFragment<FragmentFavouritesBinding>(),
@ -123,22 +123,24 @@ class FavouritesContainerFragment :
override fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean { override fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean {
val menuRes = if (category.id == 0L) R.menu.popup_category_empty else R.menu.popup_category val menuRes = if (category.id == 0L) R.menu.popup_category_empty else R.menu.popup_category
tabView.showPopupMenu(menuRes, { menu -> val menu = PopupMenu(tabView.context, tabView)
createOrderSubmenu(menu, category) menu.inflate(menuRes)
}) { createOrderSubmenu(menu.menu, category)
menu.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.action_remove -> editDelegate.deleteCategory(category) R.id.action_remove -> editDelegate.deleteCategory(category)
R.id.action_rename -> editDelegate.renameCategory(category) R.id.action_rename -> editDelegate.renameCategory(category)
R.id.action_create -> editDelegate.createCategory() R.id.action_create -> editDelegate.createCategory()
R.id.action_order -> return@showPopupMenu false R.id.action_order -> return@setOnMenuItemClickListener false
else -> { else -> {
val order = CategoriesActivity.SORT_ORDERS.getOrNull(it.order) val order = CategoriesActivity.SORT_ORDERS.getOrNull(it.order)
?: return@showPopupMenu false ?: return@setOnMenuItemClickListener false
viewModel.setCategoryOrder(category.id, order) viewModel.setCategoryOrder(category.id, order)
} }
} }
true true
} }
menu.show()
return true return true
} }

@ -6,6 +6,7 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
@ -23,11 +24,12 @@ import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.measureHeight import org.koitharu.kotatsu.utils.ext.measureHeight
import org.koitharu.kotatsu.utils.ext.showPopupMenu
class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(), class CategoriesActivity :
BaseActivity<ActivityCategoriesBinding>(),
OnListItemClickListener<FavouriteCategory>, OnListItemClickListener<FavouriteCategory>,
View.OnClickListener, CategoriesEditDelegate.CategoriesEditCallback { View.OnClickListener,
CategoriesEditDelegate.CategoriesEditCallback {
private val viewModel by viewModel<FavouritesCategoriesViewModel>() private val viewModel by viewModel<FavouritesCategoriesViewModel>()
@ -58,26 +60,27 @@ class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
} }
override fun onItemClick(item: FavouriteCategory, view: View) { override fun onItemClick(item: FavouriteCategory, view: View) {
view.showPopupMenu(R.menu.popup_category, { menu -> val menu = PopupMenu(view.context, view)
createOrderSubmenu(menu, item) menu.inflate(R.menu.popup_category)
}) { createOrderSubmenu(menu.menu, item)
when (it.itemId) { menu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.action_remove -> editDelegate.deleteCategory(item) R.id.action_remove -> editDelegate.deleteCategory(item)
R.id.action_rename -> editDelegate.renameCategory(item) R.id.action_rename -> editDelegate.renameCategory(item)
R.id.action_order -> return@showPopupMenu false R.id.action_order -> return@setOnMenuItemClickListener false
else -> { else -> {
val order = SORT_ORDERS.getOrNull(it.order) ?: return@showPopupMenu false val order = SORT_ORDERS.getOrNull(menuItem.order) ?: return@setOnMenuItemClickListener false
viewModel.setCategoryOrder(item.id, order) viewModel.setCategoryOrder(item.id, order)
} }
} }
true true
} }
menu.show()
} }
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean { override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {
reorderHelper.startDrag( val viewHolder = binding.recyclerView.findContainingViewHolder(view) ?: return false
binding.recyclerView.findContainingViewHolder(view) ?: return false reorderHelper.startDrag(viewHolder)
)
return true return true
} }
@ -90,7 +93,7 @@ class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
binding.recyclerView.updatePadding( binding.recyclerView.updatePadding(
left = insets.left, left = insets.left,
right = insets.right, right = insets.right,
bottom = 2 * insets.bottom + binding.fabAdd.measureHeight() bottom = 2 * insets.bottom + binding.fabAdd.measureHeight(),
) )
} }

@ -43,7 +43,7 @@ class ListModeSelectDialog : AlertDialogFragment<DialogListModeBinding>(),
binding.textViewGridTitle.isVisible = mode == ListMode.GRID binding.textViewGridTitle.isVisible = mode == ListMode.GRID
binding.sliderGrid.isVisible = mode == ListMode.GRID binding.sliderGrid.isVisible = mode == ListMode.GRID
binding.sliderGrid.setLabelFormatter(IntPercentLabelFormatter()) binding.sliderGrid.setLabelFormatter(IntPercentLabelFormatter(view.context))
binding.sliderGrid.setValueRounded(settings.gridSize.toFloat()) binding.sliderGrid.setValueRounded(settings.gridSize.toFloat())
binding.sliderGrid.addOnSliderTouchListener(this) binding.sliderGrid.addOnSliderTouchListener(this)

@ -40,7 +40,7 @@ class MangaSelectionDecoration(context: Context) : AbstractSelectionItemDecorati
override fun getItemId(parent: RecyclerView, child: View): Long { override fun getItemId(parent: RecyclerView, child: View): Long {
val holder = parent.getChildViewHolder(child) ?: return NO_ID val holder = parent.getChildViewHolder(child) ?: return NO_ID
val item = holder.getItem<MangaItemModel>() ?: return NO_ID val item = holder.getItem(MangaItemModel::class.java) ?: return NO_ID
return item.id return item.id
} }

@ -14,7 +14,7 @@ import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.utils.ext.doOnCurrentItemChanged import org.koitharu.kotatsu.utils.ext.doOnCurrentItemChanged
import org.koitharu.kotatsu.utils.ext.findCenterViewPosition import org.koitharu.kotatsu.utils.ext.findCenterViewPosition
import org.koitharu.kotatsu.utils.ext.firstItem import org.koitharu.kotatsu.utils.ext.firstVisibleItemPosition
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() { class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
@ -52,7 +52,7 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
setItems.await() ?: return@launchWhenCreated setItems.await() ?: return@launchWhenCreated
if (position != -1) { if (position != -1) {
with(binding.recyclerView) { with(binding.recyclerView) {
firstItem = position firstVisibleItemPosition = position
post { post {
(findViewHolderForAdapterPosition(position) as? WebtoonHolder) (findViewHolderForAdapterPosition(position) as? WebtoonHolder)
?.restoreScroll(pendingState.scroll) ?.restoreScroll(pendingState.scroll)
@ -91,6 +91,6 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
} }
override fun switchPageTo(position: Int, smooth: Boolean) { override fun switchPageTo(position: Int, smooth: Boolean) {
binding.recyclerView.firstItem = position binding.recyclerView.firstVisibleItemPosition = position
} }
} }

@ -31,7 +31,7 @@ class SearchSuggestionItemCallback(
): Boolean = false ): Boolean = false
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val item = viewHolder.getItem<SearchSuggestionItem.RecentQuery>() ?: return val item = viewHolder.getItem(SearchSuggestionItem.RecentQuery::class.java) ?: return
listener.onRemoveQuery(item.query) listener.onRemoveQuery(item.query)
} }

@ -25,9 +25,10 @@ class AppearanceSettingsFragment :
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_appearance) addPreferencesFromResource(R.xml.pref_appearance)
findPreference<SliderPreference>(AppSettings.KEY_GRID_SIZE)?.run { findPreference<SliderPreference>(AppSettings.KEY_GRID_SIZE)?.run {
summary = "%d%%".format(value) val pattern = context.getString(R.string.percent_string_pattern)
summary = pattern.format(value.toString())
setOnPreferenceChangeListener { preference, newValue -> setOnPreferenceChangeListener { preference, newValue ->
preference.summary = "%d%%".format(newValue) preference.summary = pattern.format(newValue.toString())
true true
} }
} }

@ -18,7 +18,7 @@ val settingsModule
single { BackupRepository(get()) } single { BackupRepository(get()) }
single { RestoreRepository(get()) } single { RestoreRepository(get()) }
single { AppSettings(androidContext()) } single(createdAtStart = true) { AppSettings(androidContext()) }
viewModel { BackupViewModel(get(), androidContext()) } viewModel { BackupViewModel(get(), androidContext()) }
viewModel { params -> viewModel { params ->

@ -2,8 +2,8 @@ package org.koitharu.kotatsu.utils
import android.view.View import android.view.View
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
import com.google.android.material.bottomsheet.BottomSheetBehavior
open class BottomSheetToolbarController( open class BottomSheetToolbarController(
protected val toolbar: Toolbar, protected val toolbar: Toolbar,
@ -17,7 +17,5 @@ open class BottomSheetToolbarController(
} }
} }
override fun onSlide(bottomSheet: View, slideOffset: Float) { override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
}
} }

@ -5,8 +5,10 @@ import android.view.GestureDetector
import android.view.MotionEvent import android.view.MotionEvent
import kotlin.math.roundToInt import kotlin.math.roundToInt
class GridTouchHelper(context: Context, private val listener: OnGridTouchListener) : class GridTouchHelper(
GestureDetector.SimpleOnGestureListener() { context: Context,
private val listener: OnGridTouchListener
) : GestureDetector.SimpleOnGestureListener() {
private val detector = GestureDetector(context, this) private val detector = GestureDetector(context, this)
private val width = context.resources.displayMetrics.widthPixels private val width = context.resources.displayMetrics.widthPixels

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.utils
import kotlinx.coroutines.flow.MutableStateFlow
class SelectionController {
private val state = MutableStateFlow(emptySet<Int>())
}

@ -1,9 +0,0 @@
package org.koitharu.kotatsu.utils
class WordSet(private vararg val words: String) {
fun anyWordIn(dateString: String): Boolean = words.any {
dateString.contains(it, ignoreCase = true)
}
}

@ -1,16 +1,14 @@
package org.koitharu.kotatsu.utils.ext package org.koitharu.kotatsu.utils.ext
import android.content.res.Resources import android.content.res.Resources
import android.util.Log
import java.io.FileNotFoundException
import java.net.SocketTimeoutException
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.util.format import java.io.FileNotFoundException
import java.net.SocketTimeoutException
fun Throwable.getDisplayMessage(resources: Resources) = when (this) { fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
is AuthRequiredException -> resources.getString(R.string.auth_required) is AuthRequiredException -> resources.getString(R.string.auth_required)
@ -23,11 +21,3 @@ fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
is WrongPasswordException -> resources.getString(R.string.wrong_password) is WrongPasswordException -> resources.getString(R.string.wrong_password)
else -> localizedMessage ?: resources.getString(R.string.error_occurred) else -> localizedMessage ?: resources.getString(R.string.error_occurred)
} }
inline fun <T> measured(tag: String, block: () -> T): T {
val time = System.currentTimeMillis()
val res = block()
val spent = System.currentTimeMillis() - time
Log.d("measured", "$tag ${spent.format(1)} ms")
return res
}

@ -3,10 +3,8 @@ package org.koitharu.kotatsu.utils.ext
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineExceptionHandler
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
val IgnoreErrors val IgnoreErrors
get() = CoroutineExceptionHandler { _, e -> get() = CoroutineExceptionHandler { _, e ->

@ -15,6 +15,6 @@ fun Date.formatRelative(minResolution: Long): CharSequence = DateUtils.getRelati
fun Date.daysDiff(other: Long): Int { fun Date.daysDiff(other: Long): Int {
val thisDay = time / TimeUnit.DAYS.toMillis(1L) val thisDay = time / TimeUnit.DAYS.toMillis(1L)
val otherDay = other/ TimeUnit.DAYS.toMillis(1L) val otherDay = other / TimeUnit.DAYS.toMillis(1L)
return (thisDay - otherDay).toInt() return (thisDay - otherDay).toInt()
} }

@ -26,11 +26,12 @@ fun <T : Parcelable> Fragment.parcelableArgument(name: String): Lazy<T> {
} }
} }
inline fun <reified T : Serializable> Fragment.serializableArgument(name: String): Lazy<T> { fun <T : Serializable> Fragment.serializableArgument(name: String): Lazy<T> {
return lazy(LazyThreadSafetyMode.NONE) { return lazy(LazyThreadSafetyMode.NONE) {
requireNotNull(arguments?.getSerializable(name) as? T) { @Suppress("UNCHECKED_CAST")
requireNotNull(arguments?.getSerializable(name)) {
"No argument $name passed into ${javaClass.simpleName}" "No argument $name passed into ${javaClass.simpleName}"
} } as T
} }
} }

@ -4,10 +4,10 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.liveData import androidx.lifecycle.liveData
import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.utils.BufferedObserver
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.utils.BufferedObserver
fun <T> LiveData<T?>.observeNotNull(owner: LifecycleOwner, observer: Observer<T>) { fun <T> LiveData<T?>.observeNotNull(owner: LifecycleOwner, observer: Observer<T>) {
this.observe(owner) { this.observe(owner) {

@ -9,17 +9,17 @@ fun ListPreference.setDefaultValueCompat(defaultValue: String) {
} }
} }
fun <E: Enum<E>> SharedPreferences.getEnumValue(key: String, enumClass: Class<E>): E? { fun <E : Enum<E>> SharedPreferences.getEnumValue(key: String, enumClass: Class<E>): E? {
val stringValue = getString(key, null) ?: return null val stringValue = getString(key, null) ?: return null
return enumClass.enumConstants?.find { return enumClass.enumConstants?.find {
it.name == stringValue it.name == stringValue
} }
} }
fun <E: Enum<E>> SharedPreferences.getEnumValue(key: String, defaultValue: E): E { fun <E : Enum<E>> SharedPreferences.getEnumValue(key: String, defaultValue: E): E {
return getEnumValue(key, defaultValue.javaClass) ?: defaultValue return getEnumValue(key, defaultValue.javaClass) ?: defaultValue
} }
fun <E: Enum<E>> SharedPreferences.Editor.putEnumValue(key: String, value: E?) { fun <E : Enum<E>> SharedPreferences.Editor.putEnumValue(key: String, value: E?) {
putString(key, value?.name) putString(key, value?.name)
} }

@ -5,12 +5,11 @@ import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.content.res.use
import androidx.core.view.isGone import androidx.core.view.isGone
var TextView.textAndVisible: CharSequence? var TextView.textAndVisible: CharSequence?
inline get() = text?.takeIf { visibility == View.VISIBLE } get() = text?.takeIf { visibility == View.VISIBLE }
inline set(value) { set(value) {
text = value text = value
isGone = value.isNullOrEmpty() isGone = value.isNullOrEmpty()
} }
@ -40,8 +39,5 @@ fun TextView.setTextAndVisible(@StringRes textResId: Int) {
} }
fun TextView.setTextColorAttr(@AttrRes attrResId: Int) { fun TextView.setTextColorAttr(@AttrRes attrResId: Int) {
val colors = context.obtainStyledAttributes(intArrayOf(attrResId)).use { setTextColor(context.getThemeColorStateList(attrResId))
it.getColorStateList(0)
}
setTextColor(colors)
} }

@ -4,20 +4,24 @@ import android.content.Context
import android.graphics.Color import android.graphics.Color
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.Px
import androidx.core.content.res.use import androidx.core.content.res.use
@Px fun Context.getThemeDrawable(
fun Context.getThemeDimen(@AttrRes resId: Int) = obtainStyledAttributes(intArrayOf(resId)).use { @AttrRes resId: Int,
it.getDimension(0, 0f) ) = obtainStyledAttributes(intArrayOf(resId)).use {
}
fun Context.getThemeDrawable(@AttrRes resId: Int) = obtainStyledAttributes(intArrayOf(resId)).use {
it.getDrawable(0) it.getDrawable(0)
} }
@ColorInt @ColorInt
fun Context.getThemeColor(@AttrRes resId: Int, @ColorInt default: Int = Color.TRANSPARENT) = fun Context.getThemeColor(
obtainStyledAttributes(intArrayOf(resId)).use { @AttrRes resId: Int,
it.getColor(0, default) @ColorInt default: Int = Color.TRANSPARENT
} ) = obtainStyledAttributes(intArrayOf(resId)).use {
it.getColor(0, default)
}
fun Context.getThemeColorStateList(
@AttrRes resId: Int,
) = obtainStyledAttributes(intArrayOf(resId)).use {
it.getColorStateList(0)
}

@ -2,20 +2,15 @@ package org.koitharu.kotatsu.utils.ext
import android.app.Activity import android.app.Activity
import android.graphics.Rect import android.graphics.Rect
import android.view.LayoutInflater
import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.annotation.LayoutRes
import androidx.annotation.MenuRes
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.children import androidx.core.view.children
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder
import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewHolder
import kotlin.math.roundToInt import kotlin.math.roundToInt
fun View.hideKeyboard() { fun View.hideKeyboard() {
@ -28,19 +23,15 @@ fun View.showKeyboard() {
imm.showSoftInput(this, 0) imm.showSoftInput(this, 0)
} }
inline fun <reified T : View> ViewGroup.inflate(@LayoutRes resId: Int) =
LayoutInflater.from(context).inflate(resId, this, false) as T
val RecyclerView.hasItems: Boolean
get() = (adapter?.itemCount ?: 0) > 0
fun RecyclerView.clearItemDecorations() { fun RecyclerView.clearItemDecorations() {
suppressLayout(true)
while (itemDecorationCount > 0) { while (itemDecorationCount > 0) {
removeItemDecorationAt(0) removeItemDecorationAt(0)
} }
suppressLayout(false)
} }
var RecyclerView.firstItem: Int var RecyclerView.firstVisibleItemPosition: Int
get() = (layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition() get() = (layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition()
?: RecyclerView.NO_POSITION ?: RecyclerView.NO_POSITION
set(value) { set(value) {
@ -49,18 +40,6 @@ var RecyclerView.firstItem: Int
} }
} }
inline fun View.showPopupMenu(
@MenuRes menuRes: Int,
onPrepare: (Menu) -> Unit = {},
onItemClick: PopupMenu.OnMenuItemClickListener,
) {
val menu = PopupMenu(context, this)
menu.inflate(menuRes)
menu.setOnMenuItemClickListener(onItemClick)
onPrepare(menu.menu)
menu.show()
}
fun View.hasGlobalPoint(x: Int, y: Int): Boolean { fun View.hasGlobalPoint(x: Int, y: Int): Boolean {
if (visibility != View.VISIBLE) { if (visibility != View.VISIBLE) {
return false return false
@ -97,7 +76,7 @@ inline fun ViewPager2.doOnPageChanged(crossinline callback: (Int) -> Unit) {
} }
val ViewPager2.recyclerView: RecyclerView? val ViewPager2.recyclerView: RecyclerView?
inline get() = children.find { it is RecyclerView } as? RecyclerView get() = children.firstNotNullOfOrNull { it as? RecyclerView }
fun View.resetTransformations() { fun View.resetTransformations() {
alpha = 1f alpha = 1f
@ -106,6 +85,7 @@ fun View.resetTransformations() {
translationZ = 0f translationZ = 0f
scaleX = 1f scaleX = 1f
scaleY = 1f scaleY = 1f
rotation = 0f
rotationX = 0f rotationX = 0f
rotationY = 0f rotationY = 0f
} }
@ -133,8 +113,17 @@ fun RecyclerView.findCenterViewPosition(): Int {
return getChildAdapterPosition(view) return getChildAdapterPosition(view)
} }
inline fun <reified T> RecyclerView.ViewHolder.getItem(): T? { fun <T> RecyclerView.ViewHolder.getItem(clazz: Class<T>): T? {
return ((this as? AdapterDelegateViewBindingViewHolder<*, *>)?.item as? T) val rawItem = when (this) {
is AdapterDelegateViewBindingViewHolder<*, *> -> item
is AdapterDelegateViewHolder<*> -> item
else -> null
} ?: return null
return if (clazz.isAssignableFrom(rawItem.javaClass)) {
clazz.cast(rawItem)
} else {
null
}
} }
fun Slider.setValueRounded(newValue: Float) { fun Slider.setValueRounded(newValue: Float) {

@ -1,7 +1,12 @@
package org.koitharu.kotatsu.utils.progress package org.koitharu.kotatsu.utils.progress
import android.content.Context
import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.LabelFormatter
import org.koitharu.kotatsu.R
class IntPercentLabelFormatter : LabelFormatter { class IntPercentLabelFormatter(context: Context) : LabelFormatter {
override fun getFormattedValue(value: Float) = "%d%%".format(value.toInt())
private val pattern = context.getString(R.string.percent_string_pattern)
override fun getFormattedValue(value: Float) = pattern.format(value.toInt().toString())
} }
Loading…
Cancel
Save