Favourites categories screen

pull/163/head
Koitharu 4 years ago
parent 1381a7d957
commit c5de765e52
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -91,7 +91,7 @@
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name="org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity" android:name="org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity"
android:label="@string/favourites_categories" android:label="@string/favourites_categories"
android:windowSoftInputMode="stateAlwaysHidden" /> android:windowSoftInputMode="stateAlwaysHidden" />
<activity <activity

@ -20,7 +20,7 @@ import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter
import org.koitharu.kotatsu.explore.ui.adapter.ExploreListEventListener import org.koitharu.kotatsu.explore.ui.adapter.ExploreListEventListener
import org.koitharu.kotatsu.explore.ui.model.ExploreItem import org.koitharu.kotatsu.explore.ui.model.ExploreItem
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.history.ui.HistoryActivity import org.koitharu.kotatsu.history.ui.HistoryActivity
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
@ -88,7 +88,7 @@ class ExploreFragment : BaseFragment<FragmentExploreBinding>(),
R.id.button_local -> MangaListActivity.newIntent(v.context, MangaSource.LOCAL) R.id.button_local -> MangaListActivity.newIntent(v.context, MangaSource.LOCAL)
R.id.button_bookmarks -> BookmarksActivity.newIntent(v.context) R.id.button_bookmarks -> BookmarksActivity.newIntent(v.context)
R.id.button_suggestions -> SuggestionsActivity.newIntent(v.context) R.id.button_suggestions -> SuggestionsActivity.newIntent(v.context)
R.id.button_favourites -> CategoriesActivity.newIntent(v.context) R.id.button_favourites -> FavouriteCategoriesActivity.newIntent(v.context)
R.id.button_random -> { R.id.button_random -> {
viewModel.openRandom() viewModel.openRandom()
return return

@ -1,9 +1,9 @@
package org.koitharu.kotatsu.favourites.data package org.koitharu.kotatsu.favourites.data
import java.util.*
import org.koitharu.kotatsu.core.db.entity.SortOrder import org.koitharu.kotatsu.core.db.entity.SortOrder
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import java.util.*
fun FavouriteCategoryEntity.toFavouriteCategory(id: Long = categoryId.toLong()) = FavouriteCategory( fun FavouriteCategoryEntity.toFavouriteCategory(id: Long = categoryId.toLong()) = FavouriteCategory(
id = id, id = id,

@ -15,6 +15,17 @@ abstract class FavouriteCategoriesDao {
@Query("SELECT * FROM favourite_categories ORDER BY sort_key") @Query("SELECT * FROM favourite_categories ORDER BY sort_key")
abstract fun observeAll(): Flow<List<FavouriteCategoryEntity>> abstract fun observeAll(): Flow<List<FavouriteCategoryEntity>>
@MapInfo(valueColumn = "cover")
@Query(
"""
SELECT favourite_categories.*, manga.cover_url AS cover
FROM favourite_categories JOIN manga ON manga.manga_id IN
(SELECT manga_id FROM favourites WHERE favourites.category_id == favourite_categories.category_id)
ORDER BY favourite_categories.sort_key
"""
)
abstract fun observeAllWithDetails(): Flow<Map<FavouriteCategoryEntity, List<String>>>
@Query("SELECT * FROM favourite_categories WHERE category_id = :id") @Query("SELECT * FROM favourite_categories WHERE category_id = :id")
abstract fun observe(id: Long): Flow<FavouriteCategoryEntity?> abstract fun observe(id: Long): Flow<FavouriteCategoryEntity?>

@ -13,4 +13,31 @@ class FavouriteCategoryEntity(
@ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "order") val order: String, @ColumnInfo(name = "order") val order: String,
@ColumnInfo(name = "track") val track: Boolean, @ColumnInfo(name = "track") val track: Boolean,
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FavouriteCategoryEntity
if (categoryId != other.categoryId) return false
if (createdAt != other.createdAt) return false
if (sortKey != other.sortKey) return false
if (title != other.title) return false
if (order != other.order) return false
if (track != other.track) return false
return true
}
override fun hashCode(): Int {
var result = categoryId
result = 31 * result + createdAt.hashCode()
result = 31 * result + sortKey
result = 31 * result + title.hashCode()
result = 31 * result + order.hashCode()
result = 31 * result + track.hashCode()
return result
}
}

@ -5,10 +5,7 @@ import kotlinx.coroutines.flow.*
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.* import org.koitharu.kotatsu.core.db.entity.*
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity import org.koitharu.kotatsu.favourites.data.*
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.favourites.data.FavouriteManga
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
@ -55,6 +52,13 @@ class FavouritesRepository(
}.distinctUntilChanged() }.distinctUntilChanged()
} }
fun observeCategoriesWithDetails(): Flow<Map<FavouriteCategory, List<String>>> {
return db.favouriteCategoriesDao.observeAllWithDetails()
.map {
it.mapKeys { (k, _) -> k.toFavouriteCategory() }
}
}
fun observeCategory(id: Long): Flow<FavouriteCategory?> { fun observeCategory(id: Long): Flow<FavouriteCategory?> {
return db.favouriteCategoriesDao.observe(id) return db.favouriteCategoriesDao.observe(id)
.map { it?.toFavouriteCategory() } .map { it?.toFavouriteCategory() }

@ -49,14 +49,14 @@ class FavouritesContainerFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val adapter = FavouritesPagerAdapter(this, this) val adapter = FavouritesPagerAdapter(this, this)
viewModel.visibleCategories.value?.let(::onCategoriesChanged) viewModel.allCategories.value?.let(::onCategoriesChanged)
binding.pager.adapter = adapter binding.pager.adapter = adapter
pagerAdapter = adapter pagerAdapter = adapter
TabLayoutMediator(binding.tabs, binding.pager, adapter).attach() TabLayoutMediator(binding.tabs, binding.pager, adapter).attach()
actionModeDelegate.addListener(this, viewLifecycleOwner) actionModeDelegate.addListener(this, viewLifecycleOwner)
addMenuProvider(FavouritesContainerMenuProvider(view.context)) addMenuProvider(FavouritesContainerMenuProvider(view.context))
viewModel.visibleCategories.observe(viewLifecycleOwner, ::onCategoriesChanged) viewModel.allCategories.observe(viewLifecycleOwner, ::onCategoriesChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onError.observe(viewLifecycleOwner, ::onError)
} }
@ -103,10 +103,10 @@ class FavouritesContainerFragment :
} }
override fun onTabLongClick(tabView: View, item: CategoryListModel): Boolean { override fun onTabLongClick(tabView: View, item: CategoryListModel): Boolean {
when (item) { /*when (item) {
is CategoryListModel.All -> showAllCategoriesMenu(tabView) is CategoryListModel.All -> showAllCategoriesMenu(tabView)
is CategoryListModel.CategoryItem -> showCategoryMenu(tabView, item.category) is CategoryListModel.CategoryItem -> showCategoryMenu(tabView, item.category)
} }*/
return true return true
} }
@ -133,7 +133,12 @@ class FavouritesContainerFragment :
menu.setOnMenuItemClickListener { menu.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.action_remove -> editDelegate.deleteCategory(category) R.id.action_remove -> editDelegate.deleteCategory(category)
R.id.action_edit -> startActivity(FavouritesCategoryEditActivity.newIntent(tabView.context, category.id)) R.id.action_edit -> startActivity(
FavouritesCategoryEditActivity.newIntent(
tabView.context,
category.id
)
)
else -> return@setOnMenuItemClickListener false else -> return@setOnMenuItemClickListener false
} }
true true

@ -6,7 +6,7 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
class FavouritesContainerMenuProvider( class FavouritesContainerMenuProvider(
private val context: Context, private val context: Context,
@ -19,7 +19,7 @@ class FavouritesContainerMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) { return when (menuItem.itemId) {
R.id.action_categories -> { R.id.action_categories -> {
context.startActivity(CategoriesActivity.newIntent(context)) context.startActivity(FavouriteCategoriesActivity.newIntent(context))
true true
} }
else -> false else -> false

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.favourites.ui package org.koitharu.kotatsu.favourites.ui
import android.annotation.SuppressLint
import android.view.View import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.AsyncListDiffer
@ -7,7 +8,6 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
@ -24,24 +24,21 @@ class FavouritesPagerAdapter(
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
val item = differ.currentList[position] val item = differ.currentList[position]
return FavouritesListFragment.newInstance(item.id) return FavouritesListFragment.newInstance(item.category.id)
} }
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
return differ.currentList[position].id return differ.currentList[position].category.id
} }
override fun containsItem(itemId: Long): Boolean { override fun containsItem(itemId: Long): Boolean {
return differ.currentList.any { it.id == itemId } return differ.currentList.any { it.category.id == itemId }
} }
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) { override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
val item = differ.currentList[position] val item = differ.currentList[position]
tab.text = when (item) { tab.text = item.category.title
is CategoryListModel.All -> tab.view.context.getString(R.string.all_favourites) tab.view.tag = item.category.id
is CategoryListModel.CategoryItem -> item.category.title
}
tab.view.tag = item.id
tab.view.setOnLongClickListener(this) tab.view.setOnLongClickListener(this)
} }
@ -51,7 +48,7 @@ class FavouritesPagerAdapter(
override fun onLongClick(v: View): Boolean { override fun onLongClick(v: View): Boolean {
val itemId = v.tag as? Long ?: return false val itemId = v.tag as? Long ?: return false
val item = differ.currentList.find { x -> x.id == itemId } ?: return false val item = differ.currentList.find { x -> x.category.id == itemId } ?: return false
return longClickListener.onTabLongClick(v, item) return longClickListener.onTabLongClick(v, item)
} }
@ -60,14 +57,11 @@ class FavouritesPagerAdapter(
override fun areItemsTheSame( override fun areItemsTheSame(
oldItem: CategoryListModel, oldItem: CategoryListModel,
newItem: CategoryListModel newItem: CategoryListModel
): Boolean = when { ): Boolean {
oldItem is CategoryListModel.All && newItem is CategoryListModel.All -> true return oldItem.category.id == newItem.category.id
oldItem is CategoryListModel.CategoryItem && newItem is CategoryListModel.CategoryItem -> {
oldItem.category.id == newItem.category.id
}
else -> false
} }
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame( override fun areContentsTheSame(
oldItem: CategoryListModel, oldItem: CategoryListModel,
newItem: CategoryListModel newItem: CategoryListModel

@ -1,6 +0,0 @@
package org.koitharu.kotatsu.favourites.ui.categories
interface AllCategoriesToggleListener {
fun onAllCategoriesToggle(isVisible: Boolean)
}

@ -1,49 +1,49 @@
package org.koitharu.kotatsu.favourites.ui.categories package org.koitharu.kotatsu.favourites.ui.categories
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.favourites.ui.categories.adapter.allCategoriesAD
import org.koitharu.kotatsu.favourites.ui.categories.adapter.categoryAD import org.koitharu.kotatsu.favourites.ui.categories.adapter.categoryAD
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import kotlin.jvm.internal.Intrinsics
class CategoriesAdapter( class CategoriesAdapter(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
onItemClickListener: OnListItemClickListener<FavouriteCategory>, onItemClickListener: OnListItemClickListener<FavouriteCategory>,
allCategoriesToggleListener: AllCategoriesToggleListener, listListener: ListStateHolderListener,
) : AsyncListDifferDelegationAdapter<CategoryListModel>(DiffCallback()) { ) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
init { init {
delegatesManager.addDelegate(categoryAD(onItemClickListener)) delegatesManager.addDelegate(categoryAD(coil, lifecycleOwner, onItemClickListener))
.addDelegate(allCategoriesAD(allCategoriesToggleListener)) .addDelegate(emptyStateListAD(listListener))
setHasStableIds(true) .addDelegate(loadingStateAD())
} }
override fun getItemId(position: Int): Long { private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
return items[position].id
}
private class DiffCallback : DiffUtil.ItemCallback<CategoryListModel>() {
override fun areItemsTheSame( override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
oldItem: CategoryListModel, return when {
newItem: CategoryListModel, oldItem is CategoryListModel && newItem is CategoryListModel -> {
): Boolean = oldItem.id == newItem.id oldItem.category.id == newItem.category.id
}
else -> oldItem.javaClass == newItem.javaClass
}
}
override fun areContentsTheSame( override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
oldItem: CategoryListModel, return Intrinsics.areEqual(oldItem, newItem)
newItem: CategoryListModel, }
): Boolean = oldItem == newItem
override fun getChangePayload( override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? {
oldItem: CategoryListModel, return super.getChangePayload(oldItem, newItem)
newItem: CategoryListModel,
): Any? = when {
oldItem is CategoryListModel.All && newItem is CategoryListModel.All -> Unit
oldItem is CategoryListModel.CategoryItem &&
newItem is CategoryListModel.CategoryItem &&
oldItem.category.title != newItem.category.title -> null
else -> Unit
} }
} }
} }

@ -1,36 +1,38 @@
package org.koitharu.kotatsu.favourites.ui.categories package org.koitharu.kotatsu.favourites.ui.categories
import android.app.ActivityOptions
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.measureHeight import org.koitharu.kotatsu.utils.ext.measureHeight
class CategoriesActivity : class FavouriteCategoriesActivity :
BaseActivity<ActivityCategoriesBinding>(), BaseActivity<ActivityCategoriesBinding>(),
OnListItemClickListener<FavouriteCategory>, OnListItemClickListener<FavouriteCategory>,
View.OnClickListener, View.OnClickListener,
CategoriesEditDelegate.CategoriesEditCallback, CategoriesEditDelegate.CategoriesEditCallback,
AllCategoriesToggleListener { ListStateHolderListener {
private val viewModel by viewModel<FavouritesCategoriesViewModel>() private val viewModel by viewModel<FavouritesCategoriesViewModel>()
@ -42,7 +44,7 @@ class CategoriesActivity :
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityCategoriesBinding.inflate(layoutInflater)) setContentView(ActivityCategoriesBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
adapter = CategoriesAdapter(this, this) adapter = CategoriesAdapter(get(), this, this, this)
editDelegate = CategoriesEditDelegate(this, this) editDelegate = CategoriesEditDelegate(this, this)
binding.recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
@ -50,7 +52,7 @@ class CategoriesActivity :
reorderHelper = ItemTouchHelper(ReorderHelperCallback()) reorderHelper = ItemTouchHelper(ReorderHelperCallback())
reorderHelper.attachToRecyclerView(binding.recyclerView) reorderHelper.attachToRecyclerView(binding.recyclerView)
viewModel.allCategories.observe(this, ::onCategoriesChanged) viewModel.detalizedCategories.observe(this, ::onCategoriesChanged)
viewModel.onError.observe(this, ::onError) viewModel.onError.observe(this, ::onError)
} }
@ -61,7 +63,7 @@ class CategoriesActivity :
} }
override fun onItemClick(item: FavouriteCategory, view: View) { override fun onItemClick(item: FavouriteCategory, view: View) {
val menu = PopupMenu(view.context, view) /*val menu = PopupMenu(view.context, view)
menu.inflate(R.menu.popup_category) menu.inflate(R.menu.popup_category)
menu.setOnMenuItemClickListener { menuItem -> menu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) { when (menuItem.itemId) {
@ -70,7 +72,11 @@ class CategoriesActivity :
} }
true true
} }
menu.show() menu.show()*/
val intent = FavouritesActivity.newIntent(this, item)
val options =
ActivityOptions.makeScaleUpAnimation(view, view.width / 2, view.height / 2, view.width, view.height)
startActivity(intent, options.toBundle())
} }
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean { override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {
@ -79,9 +85,9 @@ class CategoriesActivity :
return true return true
} }
override fun onAllCategoriesToggle(isVisible: Boolean) { override fun onRetryClick(error: Throwable) = Unit
viewModel.setAllCategoriesVisible(isVisible)
} override fun onEmptyActionClick() = Unit
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
binding.fabAdd.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.fabAdd.updateLayoutParams<ViewGroup.MarginLayoutParams> {
@ -96,9 +102,8 @@ class CategoriesActivity :
) )
} }
private fun onCategoriesChanged(categories: List<CategoryListModel>) { private fun onCategoriesChanged(categories: List<ListModel>) {
adapter.items = categories adapter.items = categories
binding.textViewHolder.isVisible = categories.isEmpty()
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
@ -152,6 +157,6 @@ class CategoriesActivity :
SortOrder.RATING, SortOrder.RATING,
) )
fun newIntent(context: Context) = Intent(context, CategoriesActivity::class.java) fun newIntent(context: Context) = Intent(context, FavouriteCategoriesActivity::class.java)
} }
} }

@ -3,14 +3,15 @@ package org.koitharu.kotatsu.favourites.ui.categories
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.mapItems
import org.koitharu.kotatsu.utils.ext.requireValue
import java.util.* import java.util.*
class FavouritesCategoriesViewModel( class FavouritesCategoriesViewModel(
@ -20,19 +21,25 @@ class FavouritesCategoriesViewModel(
private var reorderJob: Job? = null private var reorderJob: Job? = null
val allCategories = combine( val allCategories = repository.observeCategories()
repository.observeCategories(), .mapItems {
observeAllCategoriesVisible(), CategoryListModel(
) { list, showAll -> mangaCount = 0,
mapCategories(list, showAll, true) covers = listOf(),
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default) category = it,
)
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
val visibleCategories = combine( val detalizedCategories = repository.observeCategoriesWithDetails()
repository.observeCategories(), .map {
observeAllCategoriesVisible(), it.map { (category, covers) ->
) { list, showAll -> CategoryListModel(
mapCategories(list, showAll, showAll && list.isNotEmpty()) mangaCount = covers.size,
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default) covers = covers.take(3),
category = category,
)
}
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
fun deleteCategory(id: Long) { fun deleteCategory(id: Long) {
launchJob { launchJob {
@ -48,30 +55,13 @@ class FavouritesCategoriesViewModel(
val prevJob = reorderJob val prevJob = reorderJob
reorderJob = launchJob(Dispatchers.Default) { reorderJob = launchJob(Dispatchers.Default) {
prevJob?.join() prevJob?.join()
val items = allCategories.value ?: error("This should not happen") val items = detalizedCategories.requireValue()
val ids = items.mapTo(ArrayList(items.size)) { it.id } val ids = items.mapNotNullTo(ArrayList(items.size)) {
(it as? CategoryListModel)?.category?.id
}
Collections.swap(ids, oldPos, newPos) Collections.swap(ids, oldPos, newPos)
ids.remove(0L) ids.remove(0L)
repository.reorderCategories(ids) repository.reorderCategories(ids)
} }
} }
private fun mapCategories(
categories: List<FavouriteCategory>,
isAllCategoriesVisible: Boolean,
withAllCategoriesItem: Boolean,
): List<CategoryListModel> {
val result = ArrayList<CategoryListModel>(categories.size + 1)
if (withAllCategoriesItem) {
result.add(CategoryListModel.All(isAllCategoriesVisible))
}
categories.mapTo(result) {
CategoryListModel.CategoryItem(it)
}
return result
}
private fun observeAllCategoriesVisible() = settings.observeAsFlow(AppSettings.KEY_ALL_FAVOURITES_VISIBLE) {
isAllFavouritesVisible
}
} }

@ -1,20 +0,0 @@
package org.koitharu.kotatsu.favourites.ui.categories.adapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.databinding.ItemCategoriesAllBinding
import org.koitharu.kotatsu.favourites.ui.categories.AllCategoriesToggleListener
fun allCategoriesAD(
allCategoriesToggleListener: AllCategoriesToggleListener,
) = adapterDelegateViewBinding<CategoryListModel.All, CategoryListModel, ItemCategoriesAllBinding>(
{ inflater, parent -> ItemCategoriesAllBinding.inflate(inflater, parent, false) }
) {
binding.imageViewToggle.setOnClickListener {
allCategoriesToggleListener.onAllCategoriesToggle(!item.isVisible)
}
bind {
binding.imageViewToggle.isChecked = item.isVisible
}
}

@ -1,30 +1,65 @@
package org.koitharu.kotatsu.favourites.ui.categories.adapter package org.koitharu.kotatsu.favourites.ui.categories.adapter
import android.view.MotionEvent import android.view.View
import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.request.Disposable
import coil.size.Scale
import coil.util.CoilUtils
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ItemCategoryBinding import org.koitharu.kotatsu.databinding.ItemCategoryBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest
fun categoryAD( fun categoryAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<FavouriteCategory> clickListener: OnListItemClickListener<FavouriteCategory>
) = adapterDelegateViewBinding<CategoryListModel.CategoryItem, CategoryListModel, ItemCategoryBinding>( ) = adapterDelegateViewBinding<CategoryListModel, ListModel, ItemCategoryBinding>(
{ inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) } { inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) }
) { ) {
binding.imageViewMore.setOnClickListener { val eventListener = object : OnClickListener, OnLongClickListener {
clickListener.onItemClick(item.category, it) override fun onClick(v: View) = clickListener.onItemClick(item.category, v)
} override fun onLongClick(v: View) = clickListener.onItemLongClick(item.category, v)
@Suppress("ClickableViewAccessibility")
binding.imageViewHandle.setOnTouchListener { _, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
clickListener.onItemLongClick(item.category, itemView)
} else {
false
}
} }
val coverViews = arrayOf(binding.imageViewCover1, binding.imageViewCover2, binding.imageViewCover3)
val imageRequests = arrayOfNulls<Disposable?>(coverViews.size)
itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener)
bind { bind {
imageRequests.forEach { it?.dispose() }
binding.textViewTitle.text = item.category.title binding.textViewTitle.text = item.category.title
binding.textViewSubtitle.text = context.resources.getQuantityString(
R.plurals.items,
item.mangaCount,
item.mangaCount,
)
repeat(coverViews.size) { i ->
imageRequests[i] = coverViews[i].newImageRequest(item.covers.getOrNull(i))
.placeholder(R.drawable.ic_placeholder)
.fallback(null)
.error(R.drawable.ic_placeholder)
.scale(Scale.FILL)
.allowRgb565(true)
.lifecycle(lifecycleOwner)
.enqueueWith(coil)
}
}
onViewRecycled {
repeat(coverViews.size) { i ->
imageRequests[i]?.dispose()
imageRequests[i] = null
CoilUtils.dispose(coverViews[i])
coverViews[i].setImageDrawable(null)
}
} }
} }

@ -3,59 +3,33 @@ package org.koitharu.kotatsu.favourites.ui.categories.adapter
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
sealed interface CategoryListModel : ListModel { class CategoryListModel(
val mangaCount: Int,
val id: Long val covers: List<String>,
class All(
val isVisible: Boolean,
) : CategoryListModel {
override val id: Long = 0L
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as All
if (isVisible != other.isVisible) return false
return true
}
override fun hashCode(): Int {
return isVisible.hashCode()
}
}
class CategoryItem(
val category: FavouriteCategory, val category: FavouriteCategory,
) : CategoryListModel { ) : ListModel {
override val id: Long
get() = category.id
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as CategoryItem other as CategoryListModel
if (mangaCount != other.mangaCount) return false
if (covers != other.covers) return false
if (category.id != other.category.id) return false if (category.id != other.category.id) return false
if (category.title != other.category.title) return false if (category.title != other.category.title) return false
if (category.order != other.category.order) return false if (category.order != other.category.order) return false
if (category.isTrackingEnabled != other.category.isTrackingEnabled) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = category.id.hashCode() var result = mangaCount
result = 31 * result + covers.hashCode()
result = 31 * result + category.id.hashCode()
result = 31 * result + category.title.hashCode() result = 31 * result + category.title.hashCode()
result = 31 * result + category.order.hashCode() result = 31 * result + category.order.hashCode()
result = 31 * result + category.isTrackingEnabled.hashCode()
return result return result
} }
}
} }

@ -18,7 +18,7 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.ui.titleRes import org.koitharu.kotatsu.core.ui.titleRes
import org.koitharu.kotatsu.databinding.ActivityCategoryEditBinding import org.koitharu.kotatsu.databinding.ActivityCategoryEditBinding
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
@ -84,7 +84,7 @@ class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>
} }
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedSortOrder = CategoriesActivity.SORT_ORDERS.getOrNull(position) selectedSortOrder = FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(position)
} }
private fun onCategoryChanged(category: FavouriteCategory?) { private fun onCategoryChanged(category: FavouriteCategory?) {
@ -114,7 +114,7 @@ class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>
} }
private fun initSortSpinner() { private fun initSortSpinner() {
val entries = CategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) } val entries = FavouriteCategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) }
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, entries) val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, entries)
binding.editSort.setAdapter(adapter) binding.editSort.setAdapter(adapter)
binding.editSort.onItemClickListener = this binding.editSort.onItemClickListener = this
@ -122,9 +122,9 @@ class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>
private fun getSelectedSortOrder(): SortOrder { private fun getSelectedSortOrder(): SortOrder {
selectedSortOrder?.let { return it } selectedSortOrder?.let { return it }
val entries = CategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) } val entries = FavouriteCategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) }
val index = entries.indexOf(binding.editSort.text.toString()) val index = entries.indexOf(binding.editSort.text.toString())
return CategoriesActivity.SORT_ORDERS.getOrNull(index) ?: SortOrder.NEWEST return FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(index) ?: SortOrder.NEWEST
} }
companion object { companion object {

@ -7,7 +7,7 @@ import androidx.core.view.MenuProvider
import androidx.core.view.iterator import androidx.core.view.iterator
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.titleRes import org.koitharu.kotatsu.core.ui.titleRes
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
class FavouritesListMenuProvider( class FavouritesListMenuProvider(
private val viewModel: FavouritesListViewModel, private val viewModel: FavouritesListViewModel,
@ -16,7 +16,7 @@ class FavouritesListMenuProvider(
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_favourites_list, menu) menuInflater.inflate(R.menu.opt_favourites_list, menu)
menu.findItem(R.id.action_order)?.subMenu?.let { submenu -> menu.findItem(R.id.action_order)?.subMenu?.let { submenu ->
for ((i, item) in CategoriesActivity.SORT_ORDERS.withIndex()) { for ((i, item) in FavouriteCategoriesActivity.SORT_ORDERS.withIndex()) {
val menuItem = submenu.add(R.id.group_order, Menu.NONE, i, item.titleRes) val menuItem = submenu.add(R.id.group_order, Menu.NONE, i, item.titleRes)
menuItem.isCheckable = true menuItem.isCheckable = true
} }
@ -28,7 +28,7 @@ class FavouritesListMenuProvider(
menu.findItem(R.id.action_order)?.subMenu?.let { submenu -> menu.findItem(R.id.action_order)?.subMenu?.let { submenu ->
val selectedOrder = viewModel.sortOrder.value val selectedOrder = viewModel.sortOrder.value
for (item in submenu) { for (item in submenu) {
val order = CategoriesActivity.SORT_ORDERS.getOrNull(item.order) val order = FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(item.order)
item.isChecked = order == selectedOrder item.isChecked = order == selectedOrder
} }
} }
@ -38,7 +38,7 @@ class FavouritesListMenuProvider(
return when { return when {
menuItem.itemId == R.id.action_order -> false menuItem.itemId == R.id.action_order -> false
menuItem.groupId == R.id.group_order -> { menuItem.groupId == R.id.group_order -> {
val order = CategoriesActivity.SORT_ORDERS.getOrNull(menuItem.order) ?: return false val order = FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(menuItem.order) ?: return false
viewModel.setSortOrder(order) viewModel.setSortOrder(order)
true true
} }

@ -8,7 +8,7 @@ import androidx.core.view.MenuProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.RememberSelectionDialogListener import org.koitharu.kotatsu.base.ui.dialog.RememberSelectionDialogListener
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.utils.ext.startOfDay import org.koitharu.kotatsu.utils.ext.startOfDay
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -30,7 +30,7 @@ class LibraryMenuProvider(
true true
} }
R.id.action_categories -> { R.id.action_categories -> {
context.startActivity(CategoriesActivity.newIntent(context)) context.startActivity(FavouriteCategoriesActivity.newIntent(context))
true true
} }
else -> false else -> false

@ -23,7 +23,7 @@ import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
@ -103,7 +103,7 @@ class TrackerSettingsFragment :
} }
} }
AppSettings.KEY_TRACK_CATEGORIES -> { AppSettings.KEY_TRACK_CATEGORIES -> {
startActivity(CategoriesActivity.newIntent(preference.context)) startActivity(FavouriteCategoriesActivity.newIntent(preference.context))
true true
} }
KEY_IGNORE_DOZE -> { KEY_IGNORE_DOZE -> {

@ -51,18 +51,6 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<TextView
android:id="@+id/textView_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:gravity="center"
android:text="@string/text_categories_holder"
android:textAppearance="?attr/textAppearanceBody2"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab_add" android:id="@+id/fab_add"
android:layout_width="wrap_content" android:layout_width="wrap_content"

@ -1,41 +1,85 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
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:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?android:windowBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="Overdraw">
<ImageView
android:id="@+id/imageView_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="?listPreferredItemPaddingStart" android:background="@drawable/list_selector"
android:scaleType="center" android:paddingVertical="12dp"
android:src="@drawable/ic_reorder_handle" /> android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover3"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_marginStart="24dp"
android:layout_marginBottom="12dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="W,13:18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover2"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_marginStart="12dp"
android:alpha="50"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="W,13:18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover1"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_marginTop="12dp"
android:alpha="120"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="W,13:18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" />
<TextView <TextView
android:id="@+id/textView_title" android:id="@+id/textView_title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_marginStart="@dimen/margin_normal"
android:ellipsize="marquee" android:ellipsize="end"
android:fadingEdge="horizontal"
android:singleLine="true" android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyLarge" android:textAppearance="?attr/textAppearanceBodyLarge"
app:layout_constraintBottom_toTopOf="@id/textView_subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@tools:sample/lorem[1]" /> tools:text="@tools:sample/lorem[1]" />
<ImageView <TextView
android:id="@+id/imageView_more" android:id="@+id/textView_subtitle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless" android:layout_marginStart="@dimen/margin_normal"
android:padding="?listPreferredItemPaddingEnd" android:layout_marginTop="4dp"
android:scaleType="center" android:singleLine="true"
app:srcCompat="@drawable/abc_ic_menu_overflow_material" /> android:textAppearance="?attr/textAppearanceBodySmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
app:layout_constraintTop_toBottomOf="@id/textView_title"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@tools:sample/lorem[1]" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
Loading…
Cancel
Save