diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cb22f131e..d3762ec2a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -50,6 +50,8 @@ + diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt index f7b90fa01..68b1ffe42 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.base.ui -import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -53,15 +52,6 @@ abstract class BaseFragment : Fragment(), OnApplyWindowInsetsLi super.onDestroyView() } - open fun getTitle(): CharSequence? = null - - override fun onAttach(context: Context) { - super.onAttach(context) - getTitle()?.let { - activity?.title = it - } - } - override fun onApplyWindowInsets(v: View?, insets: WindowInsetsCompat): WindowInsetsCompat { val newInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) if (newInsets != lastInsets) { diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt index 7d8730c33..c20b615cf 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt @@ -80,6 +80,7 @@ class ChipsView @JvmOverloads constructor( chip.setOnCloseIconClickListener(chipOnCloseListener) chip.setEnsureMinTouchTargetSize(false) chip.setOnClickListener(chipOnClickListener) + chip.isCheckable = false addView(chip) return chip } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt index 7f9655d19..c31307a24 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt @@ -9,6 +9,45 @@ abstract class TagsDao { @Query("SELECT * FROM tags WHERE source = :source") abstract suspend fun findTags(source: String): List + @Query( + """SELECT tags.* FROM tags + LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id + GROUP BY manga_tags.tag_id + ORDER BY COUNT(manga_id) DESC + LIMIT :limit""" + ) + abstract suspend fun findPopularTags(limit: Int): List + + @Query( + """SELECT tags.* FROM tags + LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id + WHERE tags.source = :source + GROUP BY manga_tags.tag_id + ORDER BY COUNT(manga_id) DESC + LIMIT :limit""" + ) + abstract suspend fun findPopularTags(source: String, limit: Int): List + + @Query( + """SELECT tags.* FROM tags + LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id + WHERE tags.source = :source AND title LIKE :query + GROUP BY manga_tags.tag_id + ORDER BY COUNT(manga_id) DESC + LIMIT :limit""" + ) + abstract suspend fun findTags(source: String, query: String, limit: Int): List + + @Query( + """SELECT tags.* FROM tags + LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id + WHERE title LIKE :query + GROUP BY manga_tags.tag_id + ORDER BY COUNT(manga_id) DESC + LIMIT :limit""" + ) + abstract suspend fun findTags(query: String, limit: Int): List + @Insert(onConflict = OnConflictStrategy.IGNORE) abstract suspend fun insert(tag: TagEntity): Long diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt index 0b271ef64..f0da9fbd3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt @@ -16,27 +16,26 @@ import androidx.core.view.updatePadding import coil.ImageLoader import coil.request.ImageRequest import coil.util.CoilUtils +import com.google.android.material.chip.Chip import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.widgets.ChipsView -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.core.model.MangaState +import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.databinding.FragmentDetailsBinding import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesDialog import org.koitharu.kotatsu.image.ui.ImageActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderState +import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.utils.FileSize import org.koitharu.kotatsu.utils.ext.* class DetailsFragment : BaseFragment(), View.OnClickListener, - View.OnLongClickListener { + View.OnLongClickListener, ChipsView.OnChipClickListener { private val viewModel by sharedViewModel() private val coil by inject(mode = LazyThreadSafetyMode.NONE) @@ -54,6 +53,7 @@ class DetailsFragment : BaseFragment(), View.OnClickList binding.buttonRead.setOnLongClickListener(this) binding.imageViewCover.setOnClickListener(this) binding.textViewDescription.movementMethod = LinkMovementMethod.getInstance() + binding.chipsTags.onChipClickListener = this viewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated) viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) viewModel.favouriteCategories.observe(viewLifecycleOwner, ::onFavouriteChanged) @@ -231,6 +231,11 @@ class DetailsFragment : BaseFragment(), View.OnClickList } } + override fun onChipClick(chip: Chip, data: Any?) { + val tag = data as? MangaTag ?: return + startActivity(MangaListActivity.newIntent(requireContext(), tag)) + } + override fun onWindowInsetsChanged(insets: Insets) { binding.root.updatePadding( bottom = insets.bottom, @@ -242,7 +247,8 @@ class DetailsFragment : BaseFragment(), View.OnClickList manga.tags.map { tag -> ChipsView.ChipModel( title = tag.title, - icon = 0 + icon = 0, + data = tag, ) } ) diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt index 096d26cfd..edc8c8519 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt @@ -103,10 +103,6 @@ class FavouritesContainerFragment : BaseFragment(), else -> super.onOptionsItemSelected(item) } - override fun getTitle(): CharSequence? { - return context?.getString(R.string.favourites) - } - private fun onError(e: Throwable) { Snackbar.make(binding.pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show() } diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt index 550fab1a5..8b5aaa9ec 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt @@ -59,10 +59,6 @@ class HistoryListFragment : MangaListFragment() { } } - override fun getTitle(): CharSequence? { - return context?.getString(R.string.history) - } - override fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) { super.onCreatePopupMenu(inflater, menu, data) inflater.inflate(R.menu.popup_history, menu) diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt index 8e6075362..bbcd57ad5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt @@ -59,6 +59,12 @@ class FilterCoordinator( } } + fun setTags(tags: Set) { + currentState.update { oldValue -> + FilterState(oldValue.sortOrder, tags) + } + } + fun reset() { currentState.update { oldValue -> FilterState(oldValue.sortOrder, emptySet()) diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt index e30120259..35c8e8986 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt @@ -92,10 +92,6 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback) { if (result.isEmpty()) return viewModel.importFiles(result) 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 b8d8cb999..b3ea38df9 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 @@ -33,6 +33,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.prefs.AppSection import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.databinding.ActivityMainBinding @@ -43,6 +44,7 @@ import org.koitharu.kotatsu.history.ui.HistoryListFragment import org.koitharu.kotatsu.local.ui.LocalListFragment import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment +import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment @@ -261,6 +263,12 @@ class MainActivity : BaseActivity(), } } + override fun onTagClick(tag: MangaTag) { + startActivity( + MangaListActivity.newIntent(this, tag) + ) + } + override fun onQueryChanged(query: String) { searchSuggestionViewModel.onQueryChanged(query) } @@ -353,17 +361,19 @@ class MainActivity : BaseActivity(), supportFragmentManager.beginTransaction() .replace(R.id.container, fragment, TAG_PRIMARY) .commit() - if (fragment is HistoryListFragment) binding.fab.show() else binding.fab.hide() + adjustFabVisibility(topFragment = fragment) } private fun onSearchOpened() { drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) drawerToggle?.isDrawerIndicatorEnabled = false + adjustFabVisibility(isSearchOpened = true) } private fun onSearchClosed() { drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) drawerToggle?.isDrawerIndicatorEnabled = true + adjustFabVisibility(isSearchOpened = false) } private fun onFirstStart() { @@ -378,4 +388,11 @@ class MainActivity : BaseActivity(), } } } + + private fun adjustFabVisibility( + topFragment: Fragment? = supportFragmentManager.findFragmentByTag(TAG_PRIMARY), + isSearchOpened: Boolean = supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true, + ) { + if (!isSearchOpened && topFragment is HistoryListFragment) binding.fab.show() else binding.fab.hide() + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt index b1b2867e6..ef918db8e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt @@ -25,10 +25,6 @@ class RemoteListFragment : MangaListFragment() { viewModel.loadNextPage() } - override fun getTitle(): CharSequence { - return source.title - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) inflater.inflate(R.menu.opt_list_remote, menu) diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt index 025dfd515..36f1bdcca 100644 --- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt @@ -110,6 +110,10 @@ class RemoteListViewModel( fun resetFilter() = filter.reset() + fun applyFilter(tags: Set) { + filter.setTags(tags) + } + private fun loadList(filterState: FilterState, append: Boolean) { if (loadingJob?.isActive == true) { return diff --git a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt index fc1f293c9..8027505f3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.withContext import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings @@ -79,6 +80,17 @@ class MangaSearchRepository( }.orEmpty() } + suspend fun getTagsSuggestion(query: String, limit: Int, source: MangaSource?): List { + return when { + query.isNotEmpty() && source != null -> db.tagsDao.findTags(source.name, "%$query%", limit) + query.isNotEmpty() -> db.tagsDao.findTags("%$query%", limit) + source != null -> db.tagsDao.findTags(source.name, limit) + else -> db.tagsDao.findPopularTags(limit) + }.map { + it.toMangaTag() + } + } + fun saveSearchQuery(query: String) { recentSuggestions.saveRecentQuery(query, null) } 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 new file mode 100644 index 000000000..eb85bc179 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt @@ -0,0 +1,77 @@ +package org.koitharu.kotatsu.search.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.Fragment +import androidx.fragment.app.commit +import org.koin.androidx.viewmodel.ext.android.getViewModel +import org.koin.core.parameter.parametersOf +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseActivity +import org.koitharu.kotatsu.core.model.MangaTag +import org.koitharu.kotatsu.databinding.ActivitySearchGlobalBinding +import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment +import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel + +class MangaListActivity : BaseActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(ActivitySearchGlobalBinding.inflate(layoutInflater)) + val tag = intent.getParcelableExtra(EXTRA_TAG) ?: run { + finishAfterTransition() + return + } + supportActionBar?.setDisplayHomeAsUpEnabled(true) + val fm = supportFragmentManager + if (fm.findFragmentById(R.id.container) == null) { + fm.commit { + val fragment = RemoteListFragment.newInstance(tag.source) + replace(R.id.container, fragment) + runOnCommit(ApplyFilterRunnable(fragment, tag)) + } + } + } + + override fun onWindowInsetsChanged(insets: Insets) { + with(binding.toolbar) { + updatePadding( + left = insets.left, + right = insets.right + ) + updateLayoutParams { + topMargin = insets.top + } + } + binding.container.updatePadding( + bottom = insets.bottom + ) + } + + private class ApplyFilterRunnable( + private val fragment: Fragment, + private val tag: MangaTag, + ) : Runnable { + + override fun run() { + val viewModel = fragment.getViewModel { + parametersOf(tag.source) + } + viewModel.applyFilter(setOf(tag)) + } + } + + companion object { + + private const val EXTRA_TAG = "tag" + + fun newIntent(context: Context, tag: MangaTag) = + Intent(context, MangaListActivity::class.java) + .putExtra(EXTRA_TAG, tag) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt index 6f2621475..b74bd5d12 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt @@ -21,8 +21,6 @@ class SearchFragment : MangaListFragment() { viewModel.loadNextPage() } - override fun getTitle() = query - companion object { private const val ARG_QUERY = "query" diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt index 2f6ca1ae3..81fc4491a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt @@ -17,10 +17,6 @@ class GlobalSearchFragment : MangaListFragment() { override fun onScrolledToEnd() = Unit - override fun getTitle(): CharSequence? { - return query - } - companion object { private const val ARG_QUERY = "query" diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt index e983be92b..64e53dc6b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt @@ -2,7 +2,7 @@ package org.koitharu.kotatsu.search.ui.suggestion import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.search.ui.suggestion.adapter.SearchSuggestionAdapter +import org.koitharu.kotatsu.search.ui.suggestion.adapter.SEARCH_SUGGESTION_ITEM_TYPE_QUERY import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem import org.koitharu.kotatsu.utils.ext.getItem @@ -18,7 +18,7 @@ class SearchSuggestionItemCallback( override fun getMovementFlags( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, - ): Int = if (viewHolder.itemViewType == SearchSuggestionAdapter.ITEM_TYPE_QUERY) { + ): Int = if (viewHolder.itemViewType == SEARCH_SUGGESTION_ITEM_TYPE_QUERY) { movementFlags } else { 0 diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt index d82a47bb0..f6b0c718d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.search.ui.suggestion import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaTag interface SearchSuggestionListener { @@ -11,4 +12,6 @@ interface SearchSuggestionListener { fun onQueryChanged(query: String) fun onClearSearchHistory() + + fun onTagClick(tag: MangaTag) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt index 4317d5bdb..02e7f2856 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt @@ -2,20 +2,19 @@ package org.koitharu.kotatsu.search.ui.suggestion import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlinx.coroutines.plus import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.search.domain.MangaSearchRepository import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem private const val DEBOUNCE_TIMEOUT = 500L -private const val SEARCH_THRESHOLD = 3 -private const val MAX_MANGA_ITEMS = 3 -private const val MAX_QUERY_ITEMS = 120 -private const val MAX_SUGGESTION_ITEMS = MAX_MANGA_ITEMS + MAX_QUERY_ITEMS + 1 +private const val MAX_MANGA_ITEMS = 6 +private const val MAX_QUERY_ITEMS = 16 +private const val MAX_TAGS_ITEMS = 8 class SearchSuggestionViewModel( private val repository: MangaSearchRepository, @@ -65,28 +64,56 @@ class SearchSuggestionViewModel( private fun setupSuggestion() { suggestionJob?.cancel() suggestionJob = combine( - query - .debounce(DEBOUNCE_TIMEOUT) - .mapLatest { q -> - q to repository.getQuerySuggestion(q, MAX_QUERY_ITEMS) - }, + query.debounce(DEBOUNCE_TIMEOUT), source, - isLocalSearch - ) { (q, queries), src, srcOnly -> - val result = ArrayList(MAX_SUGGESTION_ITEMS) + isLocalSearch, + ::Triple, + ).mapLatest { (searchQuery, src, srcOnly) -> + buildSearchSuggestion(searchQuery, src, srcOnly) + }.distinctUntilChanged() + .onEach { + suggestion.postValue(it) + }.launchIn(viewModelScope + Dispatchers.Default) + } + + private suspend fun buildSearchSuggestion( + searchQuery: String, + src: MangaSource?, + srcOnly: Boolean, + ): List = coroutineScope { + val queriesDeferred = async { + repository.getQuerySuggestion(searchQuery, MAX_QUERY_ITEMS) + } + val tagsDeferred = async { + repository.getTagsSuggestion(searchQuery, MAX_TAGS_ITEMS, src.takeIf { srcOnly }) + } + val mangaDeferred = async { + repository.getMangaSuggestion(searchQuery, MAX_MANGA_ITEMS, src.takeIf { srcOnly }) + } + + val tags = tagsDeferred.await() + val mangaList = mangaDeferred.await() + val queries = queriesDeferred.await() + + buildList(queries.size + 3) { if (src != null) { - result += SearchSuggestionItem.Header(src, isLocalSearch) + add(SearchSuggestionItem.Header(src, isLocalSearch)) } - if (q.length >= SEARCH_THRESHOLD) { - repository.getMangaSuggestion(q, MAX_MANGA_ITEMS, src.takeIf { srcOnly }) - .mapTo(result) { - SearchSuggestionItem.MangaItem(it) - } + if (tags.isNotEmpty()) { + add(SearchSuggestionItem.Tags(mapTags(tags))) } - queries.mapTo(result) { SearchSuggestionItem.RecentQuery(it) } - result - }.onEach { - suggestion.postValue(it) - }.launchIn(viewModelScope + Dispatchers.Default) + if (mangaList.isNotEmpty()) { + add(SearchSuggestionItem.MangaList(mangaList)) + } + queries.mapTo(this) { SearchSuggestionItem.RecentQuery(it) } + } + } + + private fun mapTags(tags: List): List = tags.map { tag -> + ChipsView.ChipModel( + icon = 0, + title = tag.title, + data = tag, + ) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt index 06e6af858..86a8d5283 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt @@ -8,6 +8,8 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem import kotlin.jvm.internal.Intrinsics +const val SEARCH_SUGGESTION_ITEM_TYPE_QUERY = 0 + class SearchSuggestionAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, @@ -15,9 +17,11 @@ class SearchSuggestionAdapter( ) : AsyncListDifferDelegationAdapter(DiffCallback()) { init { - delegatesManager.addDelegate(ITEM_TYPE_MANGA, searchSuggestionMangaAD(coil, lifecycleOwner, listener)) - .addDelegate(ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener)) - .addDelegate(ITEM_TYPE_HEADER, searchSuggestionHeaderAD(listener)) + delegatesManager + .addDelegate(SEARCH_SUGGESTION_ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener)) + .addDelegate(searchSuggestionHeaderAD(listener)) + .addDelegate(searchSuggestionTagsAD(listener)) + .addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener)) } private class DiffCallback : DiffUtil.ItemCallback() { @@ -26,14 +30,10 @@ class SearchSuggestionAdapter( oldItem: SearchSuggestionItem, newItem: SearchSuggestionItem, ): Boolean = when { - oldItem is SearchSuggestionItem.MangaItem && newItem is SearchSuggestionItem.MangaItem -> { - oldItem.manga.id == newItem.manga.id - } oldItem is SearchSuggestionItem.RecentQuery && newItem is SearchSuggestionItem.RecentQuery -> { oldItem.query == newItem.query } - oldItem is SearchSuggestionItem.Header && newItem is SearchSuggestionItem.Header -> true - else -> false + else -> oldItem.javaClass == newItem.javaClass } override fun areContentsTheSame( @@ -41,11 +41,4 @@ class SearchSuggestionAdapter( newItem: SearchSuggestionItem, ): Boolean = Intrinsics.areEqual(oldItem, newItem) } - - companion object { - - const val ITEM_TYPE_MANGA = 0 - const val ITEM_TYPE_QUERY = 1 - const val ITEM_TYPE_HEADER = 2 - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionMangaAD.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionMangaAD.kt deleted file mode 100644 index 2eed6b932..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionMangaAD.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.koitharu.kotatsu.search.ui.suggestion.adapter - -import androidx.lifecycle.LifecycleOwner -import coil.ImageLoader -import coil.request.Disposable -import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.databinding.ItemSearchSuggestionMangaBinding -import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener -import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem -import org.koitharu.kotatsu.utils.ext.enqueueWith -import org.koitharu.kotatsu.utils.ext.newImageRequest -import org.koitharu.kotatsu.utils.ext.textAndVisible - -fun searchSuggestionMangaAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, - listener: SearchSuggestionListener, -) = adapterDelegateViewBinding( - { inflater, parent -> ItemSearchSuggestionMangaBinding.inflate(inflater, parent, false) } -) { - - var imageRequest: Disposable? = null - - itemView.setOnClickListener { - listener.onMangaClick(item.manga) - } - - bind { - imageRequest?.dispose() - imageRequest = binding.imageViewCover.newImageRequest(item.manga.coverUrl) - .placeholder(R.drawable.ic_placeholder) - .fallback(R.drawable.ic_placeholder) - .error(R.drawable.ic_placeholder) - .allowRgb565(true) - .lifecycle(lifecycleOwner) - .enqueueWith(coil) - binding.textViewTitle.text = item.manga.title - binding.textViewSubtitle.textAndVisible = item.manga.altTitle - } - - onViewRecycled { - imageRequest?.dispose() - binding.imageViewCover.setImageDrawable(null) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt new file mode 100644 index 000000000..3c070edad --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt @@ -0,0 +1,23 @@ +package org.koitharu.kotatsu.search.ui.suggestion.adapter + +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.widgets.ChipsView +import org.koitharu.kotatsu.core.model.MangaTag +import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener +import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem + +fun searchSuggestionTagsAD( + listener: SearchSuggestionListener, +) = adapterDelegate(R.layout.item_search_suggestion_tags) { + + val chipGroup = itemView as ChipsView + + chipGroup.onChipClickListener = ChipsView.OnChipClickListener { _, data -> + listener.onTagClick(data as? MangaTag ?: return@OnChipClickListener) + } + + bind { + chipGroup.setChips(item.tags) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt new file mode 100644 index 000000000..9c3eb37b1 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt @@ -0,0 +1,89 @@ +package org.koitharu.kotatsu.search.ui.suggestion.adapter + +import androidx.core.view.updatePadding +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import coil.ImageLoader +import coil.request.Disposable +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.databinding.ItemSearchSuggestionMangaGridBinding +import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener +import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem +import org.koitharu.kotatsu.utils.ScrollResetCallback +import org.koitharu.kotatsu.utils.ext.enqueueWith +import org.koitharu.kotatsu.utils.ext.newImageRequest + +fun searchSuggestionMangaListAD( + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, + listener: SearchSuggestionListener, +) = adapterDelegate(R.layout.item_search_suggestion_manga_list) { + + val adapter = AsyncListDifferDelegationAdapter( + SuggestionMangaDiffCallback(), + searchSuggestionMangaGridAD(coil, lifecycleOwner, listener), + ) + val recyclerView = itemView as RecyclerView + recyclerView.adapter = adapter + val spacing = context.resources.getDimensionPixelOffset(R.dimen.search_suggestions_manga_spacing) + recyclerView.updatePadding( + left = recyclerView.paddingLeft - spacing, + right = recyclerView.paddingRight - spacing, + ) + recyclerView.addItemDecoration(SpacingItemDecoration(spacing)) + val scrollResetCallback = ScrollResetCallback(recyclerView) + + bind { + adapter.setItems(item.items, scrollResetCallback) + } +} + +private fun searchSuggestionMangaGridAD( + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, + listener: SearchSuggestionListener, +) = adapterDelegateViewBinding( + { layoutInflater, parent -> ItemSearchSuggestionMangaGridBinding.inflate(layoutInflater, parent, false) } +) { + + var imageRequest: Disposable? = null + + itemView.setOnClickListener { + listener.onMangaClick(item) + } + + bind { + imageRequest?.dispose() + imageRequest = binding.imageViewCover.newImageRequest(item.coverUrl) + .placeholder(R.drawable.ic_placeholder) + .fallback(R.drawable.ic_placeholder) + .error(R.drawable.ic_placeholder) + .allowRgb565(true) + .lifecycle(lifecycleOwner) + .enqueueWith(coil) + binding.textViewTitle.text = item.title + } + + onViewRecycled { + imageRequest?.dispose() + binding.imageViewCover.setImageDrawable(null) + } +} + +private class SuggestionMangaDiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: Manga, newItem: Manga): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Manga, newItem: Manga): Boolean { + return oldItem.title == newItem.title && oldItem.coverUrl == newItem.coverUrl + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt index d2d9d04d7..369639462 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt @@ -1,21 +1,98 @@ package org.koitharu.kotatsu.search.ui.suggestion.model import kotlinx.coroutines.flow.MutableStateFlow +import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.utils.ext.areItemsEquals -sealed class SearchSuggestionItem { +sealed interface SearchSuggestionItem { - data class MangaItem( - val manga: Manga, - ) : SearchSuggestionItem() + class MangaList( + val items: List, + ) : SearchSuggestionItem { - data class RecentQuery( + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MangaList + + return items.areItemsEquals(other.items) { a, b -> + a.title == b.title && a.coverUrl == b.coverUrl + } + } + + override fun hashCode(): Int { + return items.fold(0) { acc, t -> + var r = 31 * acc + t.title.hashCode() + r = 31 * r + t.coverUrl.hashCode() + r + } + } + } + + class RecentQuery( val query: String, - ) : SearchSuggestionItem() + ) : SearchSuggestionItem { - data class Header( + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RecentQuery + + if (query != other.query) return false + + return true + } + + override fun hashCode(): Int { + return query.hashCode() + } + } + + class Header( val source: MangaSource, val isChecked: MutableStateFlow, - ) : SearchSuggestionItem() + ) : SearchSuggestionItem { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Header + + if (source != other.source) return false + if (isChecked !== other.isChecked) return false + + return true + } + + override fun hashCode(): Int { + var result = source.hashCode() + result = 31 * result + isChecked.hashCode() + return result + } + } + + class Tags( + val tags: List, + ) : SearchSuggestionItem { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Tags + + if (tags != other.tags) return false + + return true + } + + override fun hashCode(): Int { + return tags.hashCode() + } + } } diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt index 350b82aab..02a10e453 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt @@ -1,14 +1,19 @@ package org.koitharu.kotatsu.search.ui.widget +import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import android.view.KeyEvent +import android.view.MotionEvent import android.view.inputmethod.EditorInfo import androidx.annotation.AttrRes import androidx.appcompat.widget.AppCompatEditText +import androidx.core.content.ContextCompat import com.google.android.material.R import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener +private const val DRAWABLE_END = 2 + class SearchEditText @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, @@ -16,6 +21,7 @@ class SearchEditText @JvmOverloads constructor( ) : AppCompatEditText(context, attrs, defStyleAttr) { var searchSuggestionListener: SearchSuggestionListener? = null + private val clearIcon = ContextCompat.getDrawable(context, R.drawable.abc_ic_clear_material) var query: String get() = text?.trim()?.toString().orEmpty() @@ -50,9 +56,32 @@ class SearchEditText @JvmOverloads constructor( lengthAfter: Int, ) { super.onTextChanged(text, start, lengthBefore, lengthAfter) + setCompoundDrawablesRelativeWithIntrinsicBounds( + null, + null, + if (text.isNullOrEmpty()) null else clearIcon, + null, + ) searchSuggestionListener?.onQueryChanged(query) } + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + if (event.action == MotionEvent.ACTION_UP) { + val drawable = compoundDrawablesRelative[DRAWABLE_END] ?: return super.onTouchEvent(event) + val isOnDrawable = drawable.isVisible && if (layoutDirection == LAYOUT_DIRECTION_RTL) { + event.x.toInt() in paddingLeft..(drawable.bounds.width() + paddingLeft) + } else { + event.x.toInt() in (width - drawable.bounds.width() - paddingRight)..(width - paddingRight) + } + if (isOnDrawable) { + text?.clear() + return true + } + } + return super.onTouchEvent(event) + } + override fun clearFocus() { super.clearFocus() text?.clear() diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt index c70793405..507c2a142 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt @@ -46,10 +46,6 @@ class SuggestionsFragment : MangaListFragment() { override fun onScrolledToEnd() = Unit - override fun getTitle(): CharSequence? { - return context?.getString(R.string.suggestions) - } - companion object { fun newInstance() = SuggestionsFragment() diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt index 350979484..47a2168ee 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt @@ -35,8 +35,6 @@ class FeedFragment : BaseFragment(), PaginationScrollListen private var paddingVertical = 0 private var paddingHorizontal = 0 - override fun getTitle() = context?.getString(R.string.updates) - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ScrollResetCallback.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ScrollResetCallback.kt new file mode 100644 index 000000000..5a279f4ec --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ScrollResetCallback.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.utils + +import androidx.recyclerview.widget.RecyclerView +import java.lang.ref.WeakReference + +class ScrollResetCallback(recyclerView: RecyclerView) : Runnable { + + private val recyclerViewRef = WeakReference(recyclerView) + + override fun run() { + recyclerViewRef.get()?.scrollToPosition(0) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt index 441b87c31..58797833c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.utils.ext -import android.util.SparseArray import androidx.collection.ArrayMap import androidx.collection.ArraySet import androidx.collection.LongSparseArray @@ -82,4 +81,18 @@ fun MutableList.move(sourceIndex: Int, targetIndex: Int) { } else { Collections.rotate(subList(targetIndex, sourceIndex + 1), 1) } +} + +inline fun List.areItemsEquals(other: List, equals: (T, T) -> Boolean): Boolean { + if (size != other.size) { + return false + } + for (i in indices) { + val a = this[i] + val b = other[i] + if (!equals(a, b)) { + return false + } + } + return true } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index fe35e0918..e4ed68397 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -48,12 +48,15 @@ style="@style/Widget.Kotatsu.SearchView" android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_marginEnd="2dp" android:background="@null" android:gravity="center_vertical" android:hint="@string/search_manga" android:imeOptions="actionSearch" android:importantForAutofill="no" - android:singleLine="true" /> + android:paddingBottom="1dp" + android:singleLine="true" + tools:drawableEnd="@drawable/abc_ic_clear_material" /> diff --git a/app/src/main/res/layout/item_search_suggestion_manga.xml b/app/src/main/res/layout/item_search_suggestion_manga.xml deleted file mode 100644 index c0cf277fd..000000000 --- a/app/src/main/res/layout/item_search_suggestion_manga.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_search_suggestion_manga_grid.xml b/app/src/main/res/layout/item_search_suggestion_manga_grid.xml new file mode 100644 index 000000000..dad0dddd8 --- /dev/null +++ b/app/src/main/res/layout/item_search_suggestion_manga_grid.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_search_suggestion_manga_list.xml b/app/src/main/res/layout/item_search_suggestion_manga_list.xml new file mode 100644 index 000000000..88bcf54fb --- /dev/null +++ b/app/src/main/res/layout/item_search_suggestion_manga_list.xml @@ -0,0 +1,15 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_search_suggestion_tags.xml b/app/src/main/res/layout/item_search_suggestion_tags.xml new file mode 100644 index 000000000..3a6d64787 --- /dev/null +++ b/app/src/main/res/layout/item_search_suggestion_tags.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 5003ad1fc..f4b747b4a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -18,4 +18,7 @@ 36dp 48dp 16dp + + 124dp + 4dp \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f65fca080..08ab43dec 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,4 +1,4 @@ - + @@ -76,6 +76,10 @@ @bool/elevation_overlay_enabled + + + +