Mark nsfw sources

pull/440/head
Koitharu 3 years ago
parent 01e27ba91f
commit f105f4b496
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -81,24 +81,24 @@ afterEvaluate {
} }
dependencies { dependencies {
//noinspection GradleDependency //noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:8e452f4271') { implementation('com.github.KotatsuApp:kotatsu-parsers:74ffe9418b') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.22' implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.22'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.activity:activity-ktx:1.7.2' implementation 'androidx.activity:activity-ktx:1.7.2'
implementation 'androidx.fragment:fragment-ktx:1.6.0' implementation 'androidx.fragment:fragment-ktx:1.6.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-service:2.6.1' implementation 'androidx.lifecycle:lifecycle-service:2.6.1'
implementation 'androidx.lifecycle:lifecycle-process:2.6.1' implementation 'androidx.lifecycle:lifecycle-process:2.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.3.1-rc1' implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02'
implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05' implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
@ -145,14 +145,14 @@ dependencies {
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
testImplementation 'org.json:json:20230618' testImplementation 'org.json:json:20230618'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test:core-ktx:1.5.0' androidTestImplementation 'androidx.test:core-ktx:1.5.0'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5' androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2' androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
androidTestImplementation 'androidx.room:room-testing:2.5.2' androidTestImplementation 'androidx.room:room-testing:2.5.2'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0' androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0'

@ -1,13 +1,13 @@
package org.koitharu.kotatsu.core.ui.util package org.koitharu.kotatsu.core.ui.util
import androidx.fragment.app.Fragment import androidx.core.view.MenuHost
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
class MenuInvalidator( class MenuInvalidator(
private val fragment: Fragment, private val host: MenuHost,
) : FlowCollector<Any> { ) : FlowCollector<Any?> {
override suspend fun emit(value: Any) { override suspend fun emit(value: Any?) {
fragment.activity?.invalidateMenu() host.invalidateMenu()
} }
} }

@ -1,316 +0,0 @@
package org.koitharu.kotatsu.core.ui.widgets
import android.animation.LayoutTransition
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import androidx.annotation.AttrRes
import androidx.annotation.MenuRes
import androidx.annotation.StringRes
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.withStyledAttributes
import androidx.core.view.*
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomsheet.BottomSheetBehavior
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
import org.koitharu.kotatsu.core.util.ext.getThemeDrawable
import org.koitharu.kotatsu.core.util.ext.parents
import org.koitharu.kotatsu.databinding.LayoutSheetHeaderBinding
import java.util.*
import com.google.android.material.R as materialR
private const val THROTTLE_DELAY = 200L
@Deprecated("")
class BottomSheetHeaderBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = materialR.attr.appBarLayoutStyle,
) : AppBarLayout(context, attrs, defStyleAttr), MenuHost {
private val binding = LayoutSheetHeaderBinding.inflate(LayoutInflater.from(context), this)
private val closeDrawable = context.getThemeDrawable(materialR.attr.actionModeCloseDrawable)
private val bottomSheetCallback = Callback()
private val adjustStateRunnable = Runnable { adjustState() }
private var bottomSheetBehavior: BottomSheetBehavior<*>? = null
private val locationBuffer = IntArray(2)
private val expansionListeners = LinkedList<OnExpansionChangeListener>()
private var fitStatusBar = false
private val minHandleHeight = context.resources.getDimensionPixelSize(R.dimen.bottom_sheet_handle_size_min)
private val maxHandleHeight = context.resources.getDimensionPixelSize(R.dimen.bottom_sheet_handle_size_max)
private var isLayoutSuppressedCompat = false
private var isLayoutCalledWhileSuppressed = false
private var isBsExpanded = false
private var stateAdjustedAt = 0L
@Deprecated("")
val toolbar: MaterialToolbar
get() = binding.toolbar
val menu: Menu
get() = binding.toolbar.menu
var title: CharSequence?
get() = binding.toolbar.title
set(value) {
binding.toolbar.title = value
}
var subtitle: CharSequence?
get() = binding.toolbar.subtitle
set(value) {
binding.toolbar.subtitle = value
}
val isExpanded: Boolean
get() = binding.dragHandle.isGone
init {
setBackgroundResource(R.drawable.sheet_toolbar_background)
layoutTransition = LayoutTransition().apply {
setDuration(context.getAnimationDuration(R.integer.config_tinyAnimTime))
}
context.withStyledAttributes(attrs, R.styleable.BottomSheetHeaderBar, defStyleAttr) {
binding.toolbar.title = getString(R.styleable.BottomSheetHeaderBar_title)
fitStatusBar = getBoolean(R.styleable.BottomSheetHeaderBar_fitStatusBar, fitStatusBar)
val menuResId = getResourceId(R.styleable.BottomSheetHeaderBar_menu, 0)
if (menuResId != 0) {
binding.toolbar.inflateMenu(menuResId)
}
}
binding.toolbar.setNavigationOnClickListener(bottomSheetCallback)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
setBottomSheetBehavior(findParentBottomSheetBehavior())
}
override fun onDetachedFromWindow() {
setBottomSheetBehavior(null)
super.onDetachedFromWindow()
}
override fun addView(child: View?, index: Int) {
if (shouldAddView(child)) {
super.addView(child, index)
} else {
binding.toolbar.addView(child, index)
}
}
override fun addView(child: View?, width: Int, height: Int) {
if (shouldAddView(child)) {
super.addView(child, width, height)
} else {
binding.toolbar.addView(child, width, height)
}
}
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
if (shouldAddView(child)) {
super.addView(child, index, params)
} else {
binding.toolbar.addView(child, index, convertLayoutParams(params))
}
}
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets {
dispatchInsets(if (insets != null) WindowInsetsCompat.toWindowInsetsCompat(insets) else null)
return super.onApplyWindowInsets(insets)
}
override fun addMenuProvider(provider: MenuProvider) {
binding.toolbar.addMenuProvider(provider)
}
override fun addMenuProvider(provider: MenuProvider, owner: LifecycleOwner) {
binding.toolbar.addMenuProvider(provider, owner)
}
override fun addMenuProvider(provider: MenuProvider, owner: LifecycleOwner, state: Lifecycle.State) {
binding.toolbar.addMenuProvider(provider, owner, state)
}
override fun removeMenuProvider(provider: MenuProvider) {
binding.toolbar.removeMenuProvider(provider)
}
override fun invalidateMenu() {
binding.toolbar.invalidateMenu()
}
fun inflateMenu(@MenuRes resId: Int) {
binding.toolbar.inflateMenu(resId)
}
fun setNavigationOnClickListener(onClickListener: OnClickListener) {
binding.toolbar.setNavigationOnClickListener(onClickListener)
}
fun addOnExpansionChangeListener(listener: OnExpansionChangeListener) {
expansionListeners.add(listener)
}
fun removeOnExpansionChangeListener(listener: OnExpansionChangeListener) {
expansionListeners.remove(listener)
}
fun setTitle(@StringRes resId: Int) {
binding.toolbar.setTitle(resId)
}
fun setSubtitle(@StringRes resId: Int) {
binding.toolbar.setSubtitle(resId)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
if (isLayoutSuppressedCompat) {
isLayoutCalledWhileSuppressed = true
} else {
super.onLayout(changed, l, t, r, b)
}
}
private fun setBottomSheetBehavior(behavior: BottomSheetBehavior<*>?) {
bottomSheetBehavior?.removeBottomSheetCallback(bottomSheetCallback)
bottomSheetBehavior = behavior
if (behavior != null) {
onBottomSheetStateChanged(behavior.state)
behavior.addBottomSheetCallback(bottomSheetCallback)
}
}
private fun onBottomSheetStateChanged(newState: Int) {
val expanded = newState == BottomSheetBehavior.STATE_EXPANDED && isOnTopOfScreen()
if (isBsExpanded != expanded) {
isBsExpanded = expanded
postAdjustState()
}
}
private fun suppressLayoutCompat(suppress: Boolean) {
if (suppress == isLayoutSuppressedCompat) return
isLayoutSuppressedCompat = suppress
if (!suppress && isLayoutCalledWhileSuppressed) {
requestLayout()
}
isLayoutCalledWhileSuppressed = false
}
private fun dispatchInsets(insets: WindowInsetsCompat?) {
if (!fitStatusBar) {
return
}
val isExpanded = binding.dragHandle.isGone
val topInset = insets?.getInsets(WindowInsetsCompat.Type.systemBars())?.top ?: 0
if (isExpanded) {
updatePadding(top = topInset)
} else {
updatePadding(top = 0)
binding.dragHandle.updateLayoutParams {
height = topInset.coerceIn(minHandleHeight, maxHandleHeight)
}
}
}
private fun findParentBottomSheetBehavior(): BottomSheetBehavior<*>? {
for (p in parents) {
val layoutParams = (p as? View)?.layoutParams
if (layoutParams is CoordinatorLayout.LayoutParams) {
val behavior = layoutParams.behavior
if (behavior is BottomSheetBehavior<*>) {
return behavior
}
}
}
return null
}
private fun isOnTopOfScreen(): Boolean {
getLocationInWindow(locationBuffer)
val topInset = ViewCompat.getRootWindowInsets(this)
?.getInsets(WindowInsetsCompat.Type.systemBars())?.top ?: 0
val zeroTop = (layoutParams as? MarginLayoutParams)?.topMargin ?: 0
return (locationBuffer[1] - topInset) <= zeroTop
}
private fun dismissBottomSheet() {
val behavior = bottomSheetBehavior ?: return
if (behavior.isHideable) {
behavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
}
private fun shouldAddView(child: View?): Boolean {
if (child == null) {
return true
}
val viewId = child.id
return viewId == R.id.dragHandle || viewId == R.id.toolbar
}
private fun convertLayoutParams(params: ViewGroup.LayoutParams?): Toolbar.LayoutParams? {
return when (params) {
null -> null
is MarginLayoutParams -> {
val lp = Toolbar.LayoutParams(params)
if (params is LayoutParams) {
lp.gravity = params.gravity
}
lp
}
else -> Toolbar.LayoutParams(params)
}
}
private fun postAdjustState() {
removeCallbacks(adjustStateRunnable)
val now = System.currentTimeMillis()
if (stateAdjustedAt + THROTTLE_DELAY < now) {
adjustState()
} else {
postDelayed(adjustStateRunnable, THROTTLE_DELAY)
}
}
private fun adjustState() {
suppressLayoutCompat(true)
binding.toolbar.navigationIcon = (if (isBsExpanded) closeDrawable else null)
binding.dragHandle.isGone = isBsExpanded
expansionListeners.forEach { it.onExpansionStateChanged(this, isBsExpanded) }
dispatchInsets(ViewCompat.getRootWindowInsets(this))
stateAdjustedAt = System.currentTimeMillis()
suppressLayoutCompat(false)
}
private inner class Callback : BottomSheetBehavior.BottomSheetCallback(), OnClickListener {
override fun onStateChanged(bottomSheet: View, newState: Int) {
onBottomSheetStateChanged(newState)
}
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
override fun onClick(v: View?) {
dismissBottomSheet()
}
}
fun interface OnExpansionChangeListener {
fun onExpansionStateChanged(headerBar: BottomSheetHeaderBar, isExpanded: Boolean)
}
}

@ -32,19 +32,6 @@ class PieChart @JvmOverloads constructor(
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr), PieChartInterface { ) : View(context, attrs, defStyleAttr), PieChartInterface {
companion object {
private const val DEFAULT_MARGIN_TEXT_1 = 2f
private const val DEFAULT_MARGIN_TEXT_2 = 10f
private const val DEFAULT_MARGIN_TEXT_3 = 2f
private const val DEFAULT_MARGIN_SMALL_CIRCLE = 12f
private const val TEXT_WIDTH_PERCENT = 0.40
private const val CIRCLE_WIDTH_PERCENT = 0.50
const val DEFAULT_VIEW_SIZE_HEIGHT = 150
const val DEFAULT_VIEW_SIZE_WIDTH = 250
}
private var marginTextFirst: Float = context.resources.resolveDp(DEFAULT_MARGIN_TEXT_1) private var marginTextFirst: Float = context.resources.resolveDp(DEFAULT_MARGIN_TEXT_1)
private var marginTextSecond: Float = context.resources.resolveDp(DEFAULT_MARGIN_TEXT_2) private var marginTextSecond: Float = context.resources.resolveDp(DEFAULT_MARGIN_TEXT_2)
private var marginTextThird: Float = context.resources.resolveDp(DEFAULT_MARGIN_TEXT_3) private var marginTextThird: Float = context.resources.resolveDp(DEFAULT_MARGIN_TEXT_3)
@ -341,6 +328,19 @@ class PieChart @JvmOverloads constructor(
.setLineSpacing(spacingAdd, spacingMult) .setLineSpacing(spacingAdd, spacingMult)
.build() .build()
} }
companion object {
private const val DEFAULT_MARGIN_TEXT_1 = 2f
private const val DEFAULT_MARGIN_TEXT_2 = 10f
private const val DEFAULT_MARGIN_TEXT_3 = 2f
private const val DEFAULT_MARGIN_SMALL_CIRCLE = 12f
private const val TEXT_WIDTH_PERCENT = 0.40
private const val CIRCLE_WIDTH_PERCENT = 0.50
const val DEFAULT_VIEW_SIZE_HEIGHT = 150
const val DEFAULT_VIEW_SIZE_WIDTH = 250
}
} }
interface PieChartInterface { interface PieChartInterface {

@ -35,6 +35,7 @@ import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.dialog.RecyclerViewAlertDialog import org.koitharu.kotatsu.core.ui.dialog.RecyclerViewAlertDialog
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.util.ViewBadge import org.koitharu.kotatsu.core.util.ViewBadge
import org.koitharu.kotatsu.core.util.ext.doOnExpansionsChanged import org.koitharu.kotatsu.core.util.ext.doOnExpansionsChanged
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
@ -131,12 +132,8 @@ class DetailsActivity :
viewBinding.toolbarChapters?.subtitle = it viewBinding.toolbarChapters?.subtitle = it
viewBinding.textViewSubtitle?.textAndVisible = it viewBinding.textViewSubtitle?.textAndVisible = it
} }
viewModel.isChaptersReversed.observe(this) { viewModel.isChaptersReversed.observe(this, MenuInvalidator(viewBinding.toolbarChapters ?: this))
viewBinding.toolbarChapters?.invalidateMenu() ?: invalidateOptionsMenu() viewModel.favouriteCategories.observe(this, MenuInvalidator(this))
}
viewModel.favouriteCategories.observe(this) {
invalidateOptionsMenu()
}
viewModel.branches.observe(this) { viewModel.branches.observe(this) {
viewBinding.buttonDropdown.isVisible = it.size > 1 viewBinding.buttonDropdown.isVisible = it.size > 1
} }

@ -13,10 +13,10 @@ import androidx.core.graphics.Insets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import coil.ImageLoader import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.FlowCollector
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
@ -64,10 +64,10 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
downloadsAdapter.items = it downloadsAdapter.items = it
} }
viewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.recyclerView)) viewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.recyclerView))
val menuObserver = FlowCollector<Any> { _ -> invalidateOptionsMenu() } val menuInvalidator = MenuInvalidator(this)
viewModel.hasActiveWorks.observe(this, menuObserver) viewModel.hasActiveWorks.observe(this, menuInvalidator)
viewModel.hasPausedWorks.observe(this, menuObserver) viewModel.hasPausedWorks.observe(this, menuInvalidator)
viewModel.hasCancellableWorks.observe(this, menuObserver) viewModel.hasCancellableWorks.observe(this, menuInvalidator)
} }
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {

@ -11,6 +11,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.model.titleRes import org.koitharu.kotatsu.core.ui.model.titleRes
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.core.util.ext.withArgs
@ -31,7 +32,7 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
if (viewModel.categoryId != NO_ID) { if (viewModel.categoryId != NO_ID) {
addMenuProvider(FavouritesListMenuProvider(binding.root.context, viewModel)) addMenuProvider(FavouritesListMenuProvider(binding.root.context, viewModel))
} }
viewModel.sortOrder.observe(viewLifecycleOwner) { activity?.invalidateOptionsMenu() } viewModel.sortOrder.observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))
} }
override fun onScrolledToEnd() = Unit override fun onScrolledToEnd() = Unit

@ -24,7 +24,7 @@ class HistoryListFragment : MangaListFragment() {
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
addMenuProvider(HistoryListMenuProvider(binding.root.context, viewModel)) addMenuProvider(HistoryListMenuProvider(binding.root.context, viewModel))
val menuInvalidator = MenuInvalidator(this) val menuInvalidator = MenuInvalidator(requireActivity())
viewModel.isGroupingEnabled.observe(viewLifecycleOwner, menuInvalidator) viewModel.isGroupingEnabled.observe(viewLifecycleOwner, menuInvalidator)
viewModel.sortOrder.observe(viewLifecycleOwner, menuInvalidator) viewModel.sortOrder.observe(viewLifecycleOwner, menuInvalidator)
} }

@ -12,7 +12,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.AlertDialogFragment import org.koitharu.kotatsu.core.ui.AlertDialogFragment
import org.koitharu.kotatsu.databinding.DialogImportBinding import org.koitharu.kotatsu.databinding.DialogImportBinding
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.OnClickListener { class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.OnClickListener {
@ -22,9 +21,6 @@ class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.On
private val importDirCall = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { private val importDirCall = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
startImport(listOfNotNull(it)) startImport(listOfNotNull(it))
} }
private val backupSelectCall = registerForActivityResult(ActivityResultContracts.OpenDocument()) {
restoreBackup(it)
}
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogImportBinding { override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogImportBinding {
return DialogImportBinding.inflate(inflater, container, false) return DialogImportBinding.inflate(inflater, container, false)
@ -41,14 +37,12 @@ class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.On
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
binding.buttonDir.setOnClickListener(this) binding.buttonDir.setOnClickListener(this)
binding.buttonFile.setOnClickListener(this) binding.buttonFile.setOnClickListener(this)
binding.buttonBackup.setOnClickListener(this)
} }
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.button_file -> importFileCall.launch(arrayOf("*/*")) R.id.button_file -> importFileCall.launch(arrayOf("*/*"))
R.id.button_dir -> importDirCall.launch(null) R.id.button_dir -> importDirCall.launch(null)
R.id.button_backup -> backupSelectCall.launch(arrayOf("*/*"))
} }
} }
@ -62,13 +56,6 @@ class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.On
dismiss() dismiss()
} }
private fun restoreBackup(uri: Uri?) {
if (uri != null) {
RestoreDialogFragment.show(parentFragmentManager, uri)
dismiss()
}
}
companion object { companion object {
private const val TAG = "ImportDialogFragment" private const val TAG = "ImportDialogFragment"

@ -38,6 +38,7 @@ import org.koitharu.kotatsu.R
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.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.OptionsMenuBadgeHelper import org.koitharu.kotatsu.core.ui.util.OptionsMenuBadgeHelper
import org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView import org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView
import org.koitharu.kotatsu.core.util.ext.hideKeyboard import org.koitharu.kotatsu.core.util.ext.hideKeyboard
@ -134,7 +135,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
viewModel.isLoading.observe(this, this::onLoadingStateChanged) viewModel.isLoading.observe(this, this::onLoadingStateChanged)
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged) viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
viewModel.counters.observe(this, ::onCountersChanged) viewModel.counters.observe(this, ::onCountersChanged)
viewModel.appUpdate.observe(this) { invalidateMenu() } viewModel.appUpdate.observe(this, MenuInvalidator(this))
viewModel.onFirstStart.observeEvent(this) { OnboardDialogFragment.showWelcome(supportFragmentManager) } viewModel.onFirstStart.observeEvent(this) { OnboardDialogFragment.showWelcome(supportFragmentManager) }
viewModel.isFeedAvailable.observe(this, ::onFeedAvailabilityChanged) viewModel.isFeedAvailable.observe(this, ::onFeedAvailabilityChanged)
searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged) searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.search.ui.suggestion.adapter package org.koitharu.kotatsu.search.ui.suggestion.adapter
import androidx.core.text.buildSpannedString
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
@ -13,6 +14,7 @@ import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceBinding import org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceBinding
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
import org.koitharu.kotatsu.settings.sources.adapter.appendNsfwLabel
fun searchSuggestionSourceAD( fun searchSuggestionSourceAD(
coil: ImageLoader, coil: ImageLoader,
@ -30,7 +32,15 @@ fun searchSuggestionSourceAD(
} }
bind { bind {
binding.textViewTitle.text = item.source.title binding.textViewTitle.text = if (item.isNsfw) {
buildSpannedString {
append(item.source.title)
append(' ')
appendNsfwLabel(context)
}
} else {
item.source.title
}
binding.switchLocal.isChecked = item.isEnabled binding.switchLocal.isChecked = item.isEnabled
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)
binding.imageViewCover.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { binding.imageViewCover.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run {

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.search.ui.suggestion.model
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.areItemsEquals import org.koitharu.kotatsu.parsers.util.areItemsEquals
@ -64,6 +65,9 @@ sealed interface SearchSuggestionItem : ListModel {
val isEnabled: Boolean, val isEnabled: Boolean,
) : SearchSuggestionItem { ) : SearchSuggestionItem {
val isNsfw: Boolean
get() = source.contentType == ContentType.HENTAI
override fun areItemsTheSame(other: ListModel): Boolean { override fun areItemsTheSame(other: ListModel): Boolean {
return other is Source && other.source == source return other is Source && other.source == source
} }

@ -1,6 +1,14 @@
package org.koitharu.kotatsu.settings.sources.adapter package org.koitharu.kotatsu.settings.sources.adapter
import android.content.Context
import android.graphics.Color
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan
import android.text.style.SuperscriptSpan
import android.view.View import android.view.View
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@ -14,6 +22,7 @@ import org.koitharu.kotatsu.core.ui.list.OnTipCloseListener
import org.koitharu.kotatsu.core.util.ext.crossfade import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.core.util.ext.textAndVisible
@ -102,7 +111,15 @@ fun sourceConfigItemDelegate2(
binding.imageViewConfig.setOnClickListener(eventListener) binding.imageViewConfig.setOnClickListener(eventListener)
bind { bind {
binding.textViewTitle.text = item.source.title binding.textViewTitle.text = if (item.isNsfw) {
buildSpannedString {
append(item.source.title)
append(' ')
appendNsfwLabel(context)
}
} else {
item.source.title
}
binding.imageViewAdd.isGone = item.isEnabled binding.imageViewAdd.isGone = item.isEnabled
binding.imageViewRemove.isVisible = item.isEnabled binding.imageViewRemove.isVisible = item.isEnabled
binding.imageViewConfig.isVisible = item.isEnabled binding.imageViewConfig.isVisible = item.isEnabled
@ -142,3 +159,11 @@ fun sourceConfigTipDelegate(
fun sourceConfigEmptySearchDelegate() = adapterDelegate<SourceConfigItem.EmptySearchResult, SourceConfigItem>( fun sourceConfigEmptySearchDelegate() = adapterDelegate<SourceConfigItem.EmptySearchResult, SourceConfigItem>(
R.layout.item_sources_empty, R.layout.item_sources_empty,
) { } ) { }
fun SpannableStringBuilder.appendNsfwLabel(context: Context) = inSpans(
ForegroundColorSpan(context.getThemeColor(com.google.android.material.R.attr.colorError, Color.RED)),
RelativeSizeSpan(0.74f),
SuperscriptSpan(),
) {
append(context.getString(R.string.nsfw))
}

@ -4,6 +4,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
sealed interface SourceConfigItem : ListModel { sealed interface SourceConfigItem : ListModel {
@ -70,6 +71,9 @@ sealed interface SourceConfigItem : ListModel {
val isDraggable: Boolean, val isDraggable: Boolean,
) : SourceConfigItem { ) : SourceConfigItem {
val isNsfw: Boolean
get() = source.contentType == ContentType.HENTAI
override fun areItemsTheSame(other: ListModel): Boolean { override fun areItemsTheSame(other: ListModel): Boolean {
return other is SourceItem && other.source == source return other is SourceItem && other.source == source
} }

@ -32,21 +32,4 @@
app:subtitle="@string/folder_with_images_import_description" app:subtitle="@string/folder_with_images_import_description"
app:title="@string/folder_with_images" /> app:title="@string/folder_with_images" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="4dp" />
<org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView
android:id="@+id/button_backup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="?android:listPreferredItemPaddingStart"
android:minHeight="?android:listPreferredItemHeightSmall"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd"
app:icon="@drawable/ic_backup_restore"
app:subtitle="@string/restore_backup_description"
app:title="@string/restore_backup" />
</LinearLayout> </LinearLayout>

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
style="?materialCardViewElevatedStyle" style="?materialCardViewOutlinedStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"> android:layout_margin="@dimen/margin_small">

Loading…
Cancel
Save