diff --git a/app/build.gradle b/app/build.gradle index f857c98fc..5ce95c226 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,24 +81,24 @@ afterEvaluate { } dependencies { //noinspection GradleDependency - implementation('com.github.KotatsuApp:kotatsu-parsers:8e452f4271') { + implementation('com.github.KotatsuApp:kotatsu-parsers:74ffe9418b') { exclude group: 'org.json', module: 'json' } 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.core:core-ktx:1.10.1' 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-livedata-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-service:2.6.1' implementation 'androidx.lifecycle:lifecycle-process:2.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 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.preference:preference-ktx:1.2.0' implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05' @@ -145,14 +145,14 @@ dependencies { testImplementation 'junit:junit:4.13.2' 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:rules:1.5.0' androidTestImplementation 'androidx.test:core-ktx:1.5.0' 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 'com.squareup.moshi:moshi-kotlin:1.15.0' diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/MenuInvalidator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/MenuInvalidator.kt index 2a184b63d..d2fd6978e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/MenuInvalidator.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/MenuInvalidator.kt @@ -1,13 +1,13 @@ package org.koitharu.kotatsu.core.ui.util -import androidx.fragment.app.Fragment +import androidx.core.view.MenuHost import kotlinx.coroutines.flow.FlowCollector class MenuInvalidator( - private val fragment: Fragment, -) : FlowCollector { + private val host: MenuHost, +) : FlowCollector { - override suspend fun emit(value: Any) { - fragment.activity?.invalidateMenu() + override suspend fun emit(value: Any?) { + host.invalidateMenu() } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/BottomSheetHeaderBar.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/BottomSheetHeaderBar.kt deleted file mode 100644 index 1fa1dcf37..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/BottomSheetHeaderBar.kt +++ /dev/null @@ -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() - 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) - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/PieChart.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/PieChart.kt index 7d2984d3b..b5cb32206 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/PieChart.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/PieChart.kt @@ -32,19 +32,6 @@ class PieChart @JvmOverloads constructor( defStyleAttr: Int = 0 ) : 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 marginTextSecond: Float = context.resources.resolveDp(DEFAULT_MARGIN_TEXT_2) private var marginTextThird: Float = context.resources.resolveDp(DEFAULT_MARGIN_TEXT_3) @@ -341,6 +328,19 @@ class PieChart @JvmOverloads constructor( .setLineSpacing(spacingAdd, spacingMult) .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 { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index e41b998d2..c828e93a4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -35,6 +35,7 @@ import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.dialog.RecyclerViewAlertDialog 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.ext.doOnExpansionsChanged import org.koitharu.kotatsu.core.util.ext.getAnimationDuration @@ -131,12 +132,8 @@ class DetailsActivity : viewBinding.toolbarChapters?.subtitle = it viewBinding.textViewSubtitle?.textAndVisible = it } - viewModel.isChaptersReversed.observe(this) { - viewBinding.toolbarChapters?.invalidateMenu() ?: invalidateOptionsMenu() - } - viewModel.favouriteCategories.observe(this) { - invalidateOptionsMenu() - } + viewModel.isChaptersReversed.observe(this, MenuInvalidator(viewBinding.toolbarChapters ?: this)) + viewModel.favouriteCategories.observe(this, MenuInvalidator(this)) viewModel.branches.observe(this) { viewBinding.buttonDropdown.isVisible = it.size > 1 } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt index 8e8f8ef9d..fa22c000c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt @@ -13,10 +13,10 @@ import androidx.core.graphics.Insets import androidx.core.view.updatePadding import coil.ImageLoader import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.FlowCollector import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity 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.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent @@ -64,10 +64,10 @@ class DownloadsActivity : BaseActivity(), downloadsAdapter.items = it } viewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.recyclerView)) - val menuObserver = FlowCollector { _ -> invalidateOptionsMenu() } - viewModel.hasActiveWorks.observe(this, menuObserver) - viewModel.hasPausedWorks.observe(this, menuObserver) - viewModel.hasCancellableWorks.observe(this, menuObserver) + val menuInvalidator = MenuInvalidator(this) + viewModel.hasActiveWorks.observe(this, menuInvalidator) + viewModel.hasPausedWorks.observe(this, menuInvalidator) + viewModel.hasCancellableWorks.observe(this, menuInvalidator) } override fun onWindowInsetsChanged(insets: Insets) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt index 9e22b6a62..19d08a66b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt @@ -11,6 +11,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.list.ListSelectionController 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.observe import org.koitharu.kotatsu.core.util.ext.withArgs @@ -31,7 +32,7 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis if (viewModel.categoryId != NO_ID) { addMenuProvider(FavouritesListMenuProvider(binding.root.context, viewModel)) } - viewModel.sortOrder.observe(viewLifecycleOwner) { activity?.invalidateOptionsMenu() } + viewModel.sortOrder.observe(viewLifecycleOwner, MenuInvalidator(requireActivity())) } override fun onScrolledToEnd() = Unit diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt index 006a64a30..4d455ba5a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt @@ -24,7 +24,7 @@ class HistoryListFragment : MangaListFragment() { override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { super.onViewBindingCreated(binding, savedInstanceState) addMenuProvider(HistoryListMenuProvider(binding.root.context, viewModel)) - val menuInvalidator = MenuInvalidator(this) + val menuInvalidator = MenuInvalidator(requireActivity()) viewModel.isGroupingEnabled.observe(viewLifecycleOwner, menuInvalidator) viewModel.sortOrder.observe(viewLifecycleOwner, menuInvalidator) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt index 7436ccc65..370bd0328 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt @@ -12,7 +12,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.AlertDialogFragment import org.koitharu.kotatsu.databinding.DialogImportBinding -import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment class ImportDialogFragment : AlertDialogFragment(), View.OnClickListener { @@ -22,9 +21,6 @@ class ImportDialogFragment : AlertDialogFragment(), View.On private val importDirCall = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { startImport(listOfNotNull(it)) } - private val backupSelectCall = registerForActivityResult(ActivityResultContracts.OpenDocument()) { - restoreBackup(it) - } override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogImportBinding { return DialogImportBinding.inflate(inflater, container, false) @@ -41,14 +37,12 @@ class ImportDialogFragment : AlertDialogFragment(), View.On super.onViewBindingCreated(binding, savedInstanceState) binding.buttonDir.setOnClickListener(this) binding.buttonFile.setOnClickListener(this) - binding.buttonBackup.setOnClickListener(this) } override fun onClick(v: View) { when (v.id) { R.id.button_file -> importFileCall.launch(arrayOf("*/*")) R.id.button_dir -> importDirCall.launch(null) - R.id.button_backup -> backupSelectCall.launch(arrayOf("*/*")) } } @@ -62,13 +56,6 @@ class ImportDialogFragment : AlertDialogFragment(), View.On dismiss() } - private fun restoreBackup(uri: Uri?) { - if (uri != null) { - RestoreDialogFragment.show(parentFragmentManager, uri) - dismiss() - } - } - companion object { private const val TAG = "ImportDialogFragment" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt index 0a115283f..233dde479 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -38,6 +38,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.prefs.AppSettings 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.widgets.SlidingBottomNavigationView import org.koitharu.kotatsu.core.util.ext.hideKeyboard @@ -134,7 +135,7 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav viewModel.isLoading.observe(this, this::onLoadingStateChanged) viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged) viewModel.counters.observe(this, ::onCountersChanged) - viewModel.appUpdate.observe(this) { invalidateMenu() } + viewModel.appUpdate.observe(this, MenuInvalidator(this)) viewModel.onFirstStart.observeEvent(this) { OnboardDialogFragment.showWelcome(supportFragmentManager) } viewModel.isFeedAvailable.observe(this, ::onFeedAvailabilityChanged) searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt index 138a4bff8..8d05c03ec 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.search.ui.suggestion.adapter +import androidx.core.text.buildSpannedString import androidx.lifecycle.LifecycleOwner import coil.ImageLoader 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.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem +import org.koitharu.kotatsu.settings.sources.adapter.appendNsfwLabel fun searchSuggestionSourceAD( coil: ImageLoader, @@ -30,7 +32,15 @@ fun searchSuggestionSourceAD( } 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 val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) binding.imageViewCover.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt index 3f00c91b7..e2445dbe2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.search.ui.suggestion.model import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.list.ui.ListModelDiffCallback 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.MangaSource import org.koitharu.kotatsu.parsers.util.areItemsEquals @@ -64,6 +65,9 @@ sealed interface SearchSuggestionItem : ListModel { val isEnabled: Boolean, ) : SearchSuggestionItem { + val isNsfw: Boolean + get() = source.contentType == ContentType.HENTAI + override fun areItemsTheSame(other: ListModel): Boolean { return other is Source && other.source == source } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt index e407ab2c3..493950106 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt @@ -1,6 +1,14 @@ 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 androidx.core.text.buildSpannedString +import androidx.core.text.inSpans import androidx.core.view.isGone import androidx.core.view.isVisible 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.disposeImageRequest 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.source import org.koitharu.kotatsu.core.util.ext.textAndVisible @@ -102,7 +111,15 @@ fun sourceConfigItemDelegate2( binding.imageViewConfig.setOnClickListener(eventListener) 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.imageViewRemove.isVisible = item.isEnabled binding.imageViewConfig.isVisible = item.isEnabled @@ -142,3 +159,11 @@ fun sourceConfigTipDelegate( fun sourceConfigEmptySearchDelegate() = adapterDelegate( 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)) +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt index e805d078f..fc3a231b5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt @@ -4,6 +4,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.MangaSource sealed interface SourceConfigItem : ListModel { @@ -70,6 +71,9 @@ sealed interface SourceConfigItem : ListModel { val isDraggable: Boolean, ) : SourceConfigItem { + val isNsfw: Boolean + get() = source.contentType == ContentType.HENTAI + override fun areItemsTheSame(other: ListModel): Boolean { return other is SourceItem && other.source == source } diff --git a/app/src/main/res/layout/dialog_import.xml b/app/src/main/res/layout/dialog_import.xml index 5f1fb3b45..5adac836f 100644 --- a/app/src/main/res/layout/dialog_import.xml +++ b/app/src/main/res/layout/dialog_import.xml @@ -32,21 +32,4 @@ app:subtitle="@string/folder_with_images_import_description" app:title="@string/folder_with_images" /> - - - - diff --git a/app/src/main/res/layout/item_tip.xml b/app/src/main/res/layout/item_tip.xml index 0c73ff9fb..0beb8d9f9 100644 --- a/app/src/main/res/layout/item_tip.xml +++ b/app/src/main/res/layout/item_tip.xml @@ -3,7 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - style="?materialCardViewElevatedStyle" + style="?materialCardViewOutlinedStyle" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/margin_small">