diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 83b0d3cf3..ffcc55356 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -66,6 +66,9 @@ + (), RecyclerViewOwner, - ExploreListEventListener, OnListItemClickListener { + ExploreListEventListener, + OnListItemClickListener { private val viewModel by viewModel() private var exploreAdapter: ExploreAdapter? = null @@ -78,6 +80,7 @@ class ExploreFragment : BaseFragment(), R.id.button_history -> HistoryActivity.newIntent(v.context) R.id.button_local -> MangaListActivity.newIntent(v.context, MangaSource.LOCAL) R.id.button_bookmarks -> BookmarksActivity.newIntent(v.context) + R.id.button_suggestions -> SuggestionsActivity.newIntent(v.context) else -> return } startActivity(intent) @@ -90,7 +93,7 @@ class ExploreFragment : BaseFragment(), override fun onRetryClick(error: Throwable) = Unit - override fun onEmptyActionClick() = Unit + override fun onEmptyActionClick() = onManageClick(requireView()) companion object { diff --git a/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt index c60b60eb0..8d5b2c962 100644 --- a/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt @@ -19,7 +19,11 @@ class ExploreViewModel( ) : BaseViewModel() { val content: LiveData> = settings.observe() - .filter { it == AppSettings.KEY_SOURCES_HIDDEN || it == AppSettings.KEY_SOURCES_ORDER } + .filter { + it == AppSettings.KEY_SOURCES_HIDDEN || + it == AppSettings.KEY_SOURCES_ORDER || + it == AppSettings.KEY_SUGGESTIONS + } .onStart { emit("") } .map { settings.getMangaSources(includeHidden = false) } .distinctUntilChanged() @@ -27,13 +31,20 @@ class ExploreViewModel( .asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList()) private fun buildList(sources: List): List { - val result = ArrayList(sources.size + 2) - result += ExploreItem.Buttons + val result = ArrayList(sources.size + 3) + result += ExploreItem.Buttons( + isSuggestionsEnabled = settings.isSuggestionsEnabled, + ) + result += ExploreItem.Header(R.string.remote_sources, sources.isNotEmpty()) if (sources.isNotEmpty()) { - result += ExploreItem.Header(R.string.enabled_sources) sources.mapTo(result) { ExploreItem.Source(it) } } else { - // TODO + result += ExploreItem.EmptyHint( + icon = R.drawable.ic_empty_search, + textPrimary = R.string.no_manga_sources, + textSecondary = R.string.no_manga_sources_text, + actionStringRes = R.string.manage, + ) } return result } diff --git a/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt index 4d8c47455..e9e0f9aff 100644 --- a/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt @@ -16,4 +16,5 @@ class ExploreAdapter( exploreButtonsAD(listener), exploreSourcesHeaderAD(listener), exploreSourceItemAD(coil, clickListener, lifecycleOwner), + exploreEmptyHintListAD(listener), ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt b/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt index 5df25dbc5..7a53d2402 100644 --- a/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt +++ b/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt @@ -1,19 +1,22 @@ package org.koitharu.kotatsu.explore.ui.adapter import android.view.View +import androidx.core.view.isVisible import androidx.lifecycle.LifecycleOwner import coil.ImageLoader import coil.request.Disposable import coil.request.ImageRequest import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.databinding.ItemEmptyCardBinding import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding import org.koitharu.kotatsu.databinding.ItemExploreHeaderBinding import org.koitharu.kotatsu.databinding.ItemExploreSourceBinding import org.koitharu.kotatsu.explore.ui.model.ExploreItem +import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener import org.koitharu.kotatsu.utils.ext.enqueueWith +import org.koitharu.kotatsu.utils.ext.setTextAndVisible import org.koitharu.kotatsu.utils.image.FaviconFallbackDrawable fun exploreButtonsAD( @@ -26,6 +29,10 @@ fun exploreButtonsAD( binding.buttonHistory.setOnClickListener(clickListener) binding.buttonLocal.setOnClickListener(clickListener) binding.buttonSuggestions.setOnClickListener(clickListener) + + bind { + binding.buttonSuggestions.isVisible = item.isSuggestionsEnabled + } } fun exploreSourcesHeaderAD( @@ -41,7 +48,8 @@ fun exploreSourcesHeaderAD( binding.buttonMore.setOnClickListener(listenerAdapter) bind { - binding.textViewTitle.setText(R.string.remote_sources) + binding.textViewTitle.setText(item.titleResId) + binding.buttonMore.isVisible = item.isButtonVisible } } @@ -77,4 +85,20 @@ fun exploreSourceItemAD( imageRequest?.dispose() imageRequest = null } +} + +fun exploreEmptyHintListAD( + listener: ListStateHolderListener, +) = adapterDelegateViewBinding( + { inflater, parent -> ItemEmptyCardBinding.inflate(inflater, parent, false) } +) { + + binding.buttonRetry.setOnClickListener { listener.onEmptyActionClick() } + + bind { + binding.icon.setImageResource(item.icon) + binding.textPrimary.setText(item.textPrimary) + binding.textSecondary.setTextAndVisible(item.textSecondary) + binding.buttonRetry.setTextAndVisible(item.actionStringRes) + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt b/app/src/main/java/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt index 59e6d174a..2c1a593d7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt @@ -1,26 +1,56 @@ package org.koitharu.kotatsu.explore.ui.model import android.net.Uri +import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import org.koitharu.kotatsu.list.ui.model.EmptyState import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.MangaSource sealed interface ExploreItem : ListModel { - object Buttons : ExploreItem - - class Header( - @StringRes val titleResId: Int, + class Buttons( + val isSuggestionsEnabled: Boolean ) : ExploreItem { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - other as Header - return titleResId == other.titleResId + + other as Buttons + + if (isSuggestionsEnabled != other.isSuggestionsEnabled) return false + + return true } - override fun hashCode(): Int = titleResId + override fun hashCode(): Int { + return isSuggestionsEnabled.hashCode() + } + } + + class Header( + @StringRes val titleResId: Int, + val isButtonVisible: Boolean, + ) : ExploreItem { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Header + + if (titleResId != other.titleResId) return false + if (isButtonVisible != other.isButtonVisible) return false + + return true + } + + override fun hashCode(): Int { + var result = titleResId + result = 31 * result + isButtonVisible.hashCode() + return result + } } class Source( @@ -46,4 +76,10 @@ sealed interface ExploreItem : ListModel { } } + class EmptyHint( + @DrawableRes icon: Int, + @StringRes textPrimary: Int, + @StringRes textSecondary: Int, + @StringRes actionStringRes: Int, + ) : EmptyState(icon, textPrimary, textSecondary, actionStringRes), ExploreItem } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryMenuProvider.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryMenuProvider.kt index 7a7354328..849506353 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryMenuProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryMenuProvider.kt @@ -38,7 +38,7 @@ class LibraryMenuProvider( } private fun showClearHistoryDialog() { - val selectionListener = RememberSelectionDialogListener(-1) + val selectionListener = RememberSelectionDialogListener(2) MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered) .setTitle(R.string.clear_history) .setSingleChoiceItems( diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt index eff8b5462..aa1e88f10 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -168,21 +168,20 @@ abstract class MangaListFragment : } override fun onWindowInsetsChanged(insets: Insets) { - val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top binding.root.updatePadding( left = insets.left, right = insets.right, ) + binding.recyclerView.updatePadding( + bottom = insets.bottom, + ) if (activity is MainActivity) { + val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top binding.swipeRefreshLayout.setProgressViewOffset( true, headerHeight + resources.resolveDp(-72), headerHeight + resources.resolveDp(10), ) - } else { - binding.recyclerView.updatePadding( - bottom = insets.bottom, - ) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/EmptyState.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/EmptyState.kt index 179bb25b5..ddef7eac6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/EmptyState.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/EmptyState.kt @@ -3,9 +3,32 @@ package org.koitharu.kotatsu.list.ui.model import androidx.annotation.DrawableRes import androidx.annotation.StringRes -data class EmptyState( +open class EmptyState( @DrawableRes val icon: Int, @StringRes val textPrimary: Int, @StringRes val textSecondary: Int, @StringRes val actionStringRes: Int, -) : ListModel \ No newline at end of file +) : ListModel { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as EmptyState + + if (icon != other.icon) return false + if (textPrimary != other.textPrimary) return false + if (textSecondary != other.textSecondary) return false + if (actionStringRes != other.actionStringRes) return false + + return true + } + + override fun hashCode(): Int { + var result = icon + result = 31 * result + textPrimary + result = 31 * result + textSecondary + result = 31 * result + actionStringRes + return result + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt index 4b4b26dab..3fd5bb551 100644 --- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -30,7 +30,6 @@ import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.databinding.ActivityMainBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.explore.ui.ExploreFragment -import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment import org.koitharu.kotatsu.library.ui.LibraryFragment import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaTag @@ -42,6 +41,7 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel import org.koitharu.kotatsu.settings.AppUpdateChecker +import org.koitharu.kotatsu.settings.SettingsHeadersFragment import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker @@ -256,6 +256,10 @@ class MainActivity : setPrimaryFragment(FeedFragment.newInstance()) binding.root.isLiftAppBarOnScroll = true // --//-- } + R.id.nav_tools -> { + setPrimaryFragment(SettingsHeadersFragment()) + binding.root.isLiftAppBarOnScroll = true // --//-- + } else -> return false } appBar.setExpanded(true) diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt index 3ecc7d9ed..d01e19ea6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt @@ -55,13 +55,7 @@ class MangaListActivity : BaseActivity() { left = insets.left, right = insets.right ) - updateLayoutParams { - topMargin = insets.top - } } - binding.container.updatePadding( - bottom = insets.bottom - ) } private class ApplyFilterRunnable( diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt index ed9cae71e..90e6e6c8e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt @@ -5,7 +5,6 @@ import android.os.Bundle import android.view.View import androidx.preference.ListPreference import androidx.preference.Preference -import java.io.File import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.koitharu.kotatsu.R @@ -19,6 +18,7 @@ import org.koitharu.kotatsu.settings.utils.SliderPreference import org.koitharu.kotatsu.utils.ext.getStorageName import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat import org.koitharu.kotatsu.utils.ext.viewLifecycleScope +import java.io.File class ContentSettingsFragment : BasePreferenceFragment(R.string.content), @@ -30,9 +30,6 @@ class ContentSettingsFragment : override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_content) - findPreference(AppSettings.KEY_SUGGESTIONS)?.setSummary( - if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled - ) findPreference(AppSettings.KEY_DOWNLOADS_PARALLELISM)?.run { summary = value.toString() setOnPreferenceChangeListener { preference, newValue -> @@ -54,6 +51,9 @@ class ContentSettingsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) findPreference(AppSettings.KEY_LOCAL_STORAGE)?.bindStorageName() + findPreference(AppSettings.KEY_SUGGESTIONS)?.setSummary( + if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled + ) bindRemoteSourcesSummary() settings.subscribe(this) } diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsActivity.kt new file mode 100644 index 000000000..46ff3fc5c --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsActivity.kt @@ -0,0 +1,45 @@ +package org.koitharu.kotatsu.suggestions.ui + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.ViewGroup +import androidx.core.graphics.Insets +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding +import androidx.fragment.app.commit +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseActivity +import org.koitharu.kotatsu.databinding.ActivityContainerBinding + +class SuggestionsActivity : BaseActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(ActivityContainerBinding.inflate(layoutInflater)) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + val fm = supportFragmentManager + if (fm.findFragmentById(R.id.container) == null) { + fm.commit { + val fragment = SuggestionsFragment.newInstance() + replace(R.id.container, fragment) + } + } + } + + override fun onWindowInsetsChanged(insets: Insets) { + binding.toolbar.updateLayoutParams { + leftMargin = insets.left + rightMargin = insets.right + } + binding.root.updatePadding( + left = insets.left, + right = insets.right, + ) + } + + companion object { + + fun newIntent(context: Context) = Intent(context, SuggestionsActivity::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_empty_card.xml b/app/src/main/res/layout/item_empty_card.xml new file mode 100644 index 000000000..1b4c86e30 --- /dev/null +++ b/app/src/main/res/layout/item_empty_card.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + +