Added tracking sheet to manga details

Using the compact card view dev tachi is for now, maybe forever
Also finally fixed the anilist bug

Co-Authored-By: arkon <arkon@users.noreply.github.com>
This commit is contained in:
Jay 2020-03-09 23:34:00 -07:00
parent 8134d4c2fa
commit 29134f6bb0
17 changed files with 526 additions and 158 deletions

View File

@ -1,18 +1,13 @@
package eu.kanade.tachiyomi.data.track.anilist package eu.kanade.tachiyomi.data.track.anilist
import android.app.DownloadManager
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Locale
data class ALManga( data class ALManga(
val media_id: Int, val media_id: Int,
@ -49,8 +44,7 @@ data class ALUserManga(
val list_status: String, val list_status: String,
val score_raw: Int, val score_raw: Int,
val chapters_read: Int, val chapters_read: Int,
val manga: ALManga, val manga: ALManga
val context: Context = Injekt.get<PreferencesHelper>().context
) { ) {
fun toTrack() = Track.create(TrackManager.ANILIST).apply { fun toTrack() = Track.create(TrackManager.ANILIST).apply {
@ -62,17 +56,15 @@ data class ALUserManga(
total_chapters = manga.total_chapters total_chapters = manga.total_chapters
} }
fun toTrackStatus() = with(context) { fun toTrackStatus() = when (list_status) {
when (list_status) { "CURRENT" -> Anilist.READING
getString(R.string.reading) -> Anilist.READING "COMPLETED" -> Anilist.COMPLETED
getString(R.string.completed) -> Anilist.COMPLETED "PAUSED" -> Anilist.PAUSED
getString(R.string.paused) -> Anilist.PAUSED "DROPPED" -> Anilist.DROPPED
getString(R.string.dropped) -> Anilist.DROPPED "PLANNING" -> Anilist.PLANNING
getString(R.string.plan_to_read) -> Anilist.PLANNING "REPEATING" -> Anilist.REPEATING
getString(R.string.repeating)-> Anilist.REPEATING
else -> throw NotImplementedError("Unknown status") else -> throw NotImplementedError("Unknown status")
} }
}
} }
fun Track.toAnilistStatus() = when (status) { fun Track.toAnilistStatus() = when (status) {

View File

@ -61,6 +61,7 @@ import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
@ -78,6 +79,7 @@ import eu.kanade.tachiyomi.ui.manga.chapter.ChapterMatHolder
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter
import eu.kanade.tachiyomi.ui.manga.chapter.DownloadCustomChaptersDialog import eu.kanade.tachiyomi.ui.manga.chapter.DownloadCustomChaptersDialog
import eu.kanade.tachiyomi.ui.manga.info.EditMangaDialog import eu.kanade.tachiyomi.ui.manga.info.EditMangaDialog
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
@ -143,6 +145,7 @@ class MangaDetailsController : BaseController,
private var snack: Snackbar? = null private var snack: Snackbar? = null
val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false) val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false)
var coverDrawable:Drawable? = null var coverDrawable:Drawable? = null
var trackingBottomSheet: TrackingBottomSheet? = null
/** /**
* Adapter containing a list of chapters. * Adapter containing a list of chapters.
*/ */
@ -444,6 +447,8 @@ class MangaDetailsController : BaseController,
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
snack?.dismiss() snack?.dismiss()
presenter.onDestroy() presenter.onDestroy()
adapter = null
trackingBottomSheet = null
super.onDestroyView(view) super.onDestroyView(view)
} }
@ -869,6 +874,36 @@ class MangaDetailsController : BaseController,
return super.handleBack() return super.handleBack()
} }
override fun showTrackingSheet() {
trackingBottomSheet = TrackingBottomSheet(this)
trackingBottomSheet?.show()
}
fun refreshTracking(trackings: List<TrackItem>) {
trackingBottomSheet?.onNextTrackings(trackings)
}
fun onTrackSearchResults(results: List<TrackSearch>) {
trackingBottomSheet?.onSearchResults(results)
}
fun refreshTracker() {
(recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)
?.updateTracking()
}
fun trackRefreshDone() {
trackingBottomSheet?.onRefreshDone()
}
fun trackRefreshError(error: Exception) {
trackingBottomSheet?.onRefreshError(error)
}
fun trackSearchError(error: Exception) {
trackingBottomSheet?.onSearchResultsError(error)
}
override fun zoomImageFromThumb(thumbView: View) { override fun zoomImageFromThumb(thumbView: View) {
// If there's an animation in progress, cancel it immediately and proceed with this one. // If there's an animation in progress, cancel it immediately and proceed with this one.
currentAnimator?.cancel() currentAnimator?.cancel()

View File

@ -20,11 +20,13 @@ import eu.kanade.tachiyomi.data.library.LibraryServiceListener
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
@ -62,6 +64,7 @@ class MangaDetailsPresenter(private val controller: MangaDetailsController,
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } } private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
var tracks = emptyList<Track>() var tracks = emptyList<Track>()
var trackList: List<TrackItem> = emptyList()
var chapters:List<ChapterItem> = emptyList() var chapters:List<ChapterItem> = emptyList()
private set private set
@ -73,6 +76,7 @@ class MangaDetailsPresenter(private val controller: MangaDetailsController,
headerItem.isLocked = isLockedFromSearch headerItem.isLocked = isLockedFromSearch
downloadManager.addListener(this) downloadManager.addListener(this)
LibraryUpdateService.setListener(this) LibraryUpdateService.setListener(this)
tracks = db.getTracks(manga).executeAsBlocking()
if (!manga.initialized) { if (!manga.initialized) {
isLoading = true isLoading = true
controller.setRefresh(true) controller.setRefresh(true)
@ -81,9 +85,9 @@ class MangaDetailsPresenter(private val controller: MangaDetailsController,
} }
else { else {
updateChapters() updateChapters()
tracks = db.getTracks(manga).executeAsBlocking()
controller.updateChapters(this.chapters) controller.updateChapters(this.chapters)
} }
fetchTrackings()
} }
fun onDestroy() { fun onDestroy() {
@ -161,7 +165,7 @@ class MangaDetailsPresenter(private val controller: MangaDetailsController,
} }
/** /**
* Sets the active display mode. * Sets the active display mode.
* @param mode the mode to set. * @param hide set title to hidden
*/ */
fun hideTitle(hide: Boolean) { fun hideTitle(hide: Boolean) {
manga.displayMode = if (hide) Manga.DISPLAY_NUMBER else Manga.DISPLAY_NAME manga.displayMode = if (hide) Manga.DISPLAY_NUMBER else Manga.DISPLAY_NAME
@ -658,7 +662,124 @@ class MangaDetailsPresenter(private val controller: MangaDetailsController,
return false return false
} }
fun isTracked(): Boolean { fun isTracked(): Boolean = loggedServices.any { service -> tracks.any { it.sync_id == service.id } }
return loggedServices.any { service -> tracks.any { it.sync_id == service.id } }
fun hasTrackers(): Boolean = loggedServices.isNotEmpty()
// Tracking
private fun fetchTrackings() {
launch {
trackList = loggedServices.map { service ->
TrackItem(tracks.find { it.sync_id == service.id }, service)
}
}
}
private suspend fun refreshTracking() {
tracks = withContext(Dispatchers.IO) { db.getTracks(manga).executeAsBlocking() }
trackList = loggedServices.map { service ->
TrackItem(tracks.find { it.sync_id == service.id }, service)
}
withContext(Dispatchers.Main) { controller.refreshTracking(trackList) }
}
fun refreshTrackers() {
launch {
val list = trackList.filter { it.track != null }.map { item ->
withContext(Dispatchers.IO) {
val trackItem = try {
item.service.refresh(item.track!!).toBlocking().single()
} catch (e: Exception) {
trackError(e)
null
}
if (trackItem != null) {
db.insertTrack(trackItem).executeAsBlocking()
trackItem
}
else
item.track
}
}
refreshTracking()
}
}
fun trackSearch(query: String, service: TrackService) {
launch(Dispatchers.IO) {
val results = try {service.search(query).toBlocking().single() }
catch (e: Exception) {
withContext(Dispatchers.Main) { controller.trackSearchError(e) }
null }
if (!results.isNullOrEmpty()) {
withContext(Dispatchers.Main) { controller.onTrackSearchResults(results) }
}
}
}
fun registerTracking(item: Track?, service: TrackService) {
if (item != null) {
item.manga_id = manga.id!!
launch {
val binding = try { service.bind(item).toBlocking().single() }
catch (e: Exception) {
trackError(e)
null
}
withContext(Dispatchers.IO) {
if (binding != null) db.insertTrack(binding).executeAsBlocking() }
refreshTracking()
}
} else {
launch {
withContext(Dispatchers.IO) { db.deleteTrackForManga(manga, service)
.executeAsBlocking() }
refreshTracking()
}
}
}
private fun updateRemote(track: Track, service: TrackService) {
launch {
val binding = try { service.update(track).toBlocking().single() }
catch (e: Exception) {
trackError(e)
null
}
if (binding != null) {
withContext(Dispatchers.IO) { db.insertTrack(binding).executeAsBlocking() }
refreshTracking()
}
else trackRefreshDone()
}
}
private suspend fun trackRefreshDone() {
async(Dispatchers.Main) { controller.trackRefreshDone() }
}
private suspend fun trackError(error: Exception) {
async(Dispatchers.Main) { controller.trackRefreshError(error) }
}
fun setStatus(item: TrackItem, index: Int) {
val track = item.track!!
track.status = item.service.getStatusList()[index]
updateRemote(track, item.service)
}
fun setScore(item: TrackItem, index: Int) {
val track = item.track!!
track.score = item.service.indexToScore(index)
updateRemote(track, item.service)
}
fun setLastChapterRead(item: TrackItem, chapterNumber: Int) {
val track = item.track!!
track.last_chapter_read = chapterNumber
updateRemote(track, item.service)
} }
} }

View File

@ -71,6 +71,7 @@ class MangaHeaderHolder(
true true
} }
manga_cover.setOnClickListener { adapter.coverListener?.zoomImageFromThumb(cover_card) } manga_cover.setOnClickListener { adapter.coverListener?.zoomImageFromThumb(cover_card) }
track_button.setOnClickListener { adapter.coverListener?.showTrackingSheet() }
if (startExpanded) if (startExpanded)
expandDesc() expandDesc()
} }
@ -144,6 +145,7 @@ class MangaHeaderHolder(
val tracked = presenter.isTracked() && !item.isLocked val tracked = presenter.isTracked() && !item.isLocked
with(track_button) { with(track_button) {
visibleIf(presenter.hasTrackers())
text = itemView.context.getString(if (tracked) R.string.action_filter_tracked text = itemView.context.getString(if (tracked) R.string.action_filter_tracked
else R.string.tracking) else R.string.tracking)
@ -232,6 +234,19 @@ class MangaHeaderHolder(
true_backdrop.setBackgroundColor(color) true_backdrop.setBackgroundColor(color)
} }
fun updateTracking() {
val presenter = adapter.coverListener?.mangaPresenter() ?: return
val tracked = presenter.isTracked()
with(track_button) {
text = itemView.context.getString(if (tracked) R.string.action_filter_tracked
else R.string.tracking)
icon = ContextCompat.getDrawable(itemView.context, if (tracked) R.drawable
.ic_check_white_24dp else R.drawable.ic_sync_black_24dp)
checked(tracked)
}
}
override fun onLongClick(view: View?): Boolean { override fun onLongClick(view: View?): Boolean {
super.onLongClick(view) super.onLongClick(view)
return false return false

View File

@ -0,0 +1,185 @@
package eu.kanade.tachiyomi.ui.manga
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.ui.manga.track.SetTrackChaptersDialog
import eu.kanade.tachiyomi.ui.manga.track.SetTrackScoreDialog
import eu.kanade.tachiyomi.ui.manga.track.SetTrackStatusDialog
import eu.kanade.tachiyomi.ui.manga.track.TrackAdapter
import eu.kanade.tachiyomi.ui.manga.track.TrackHolder
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.ui.manga.track.TrackSearchDialog
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.setEdgeToEdge
import kotlinx.android.synthetic.main.tracking_bottom_sheet.*
import timber.log.Timber
class TrackingBottomSheet(private val controller: MangaDetailsController) : BottomSheetDialog
(controller.activity!!, R.style.BottomSheetDialogTheme),
TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener,
SetTrackChaptersDialog.Listener,
SetTrackScoreDialog.Listener {
val activity = controller.activity!!
private var sheetBehavior: BottomSheetBehavior<*>
val presenter = controller.presenter
private var adapter: TrackAdapter? = null
init {
// Use activity theme for this layout
val view = activity.layoutInflater.inflate(R.layout.tracking_bottom_sheet, null)
setContentView(view)
sheetBehavior = BottomSheetBehavior.from(view.parent as ViewGroup)
setEdgeToEdge(activity, display_bottom_sheet, view, false)
val height = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
} else 0
sheetBehavior.peekHeight = 380.dpToPx + height
sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) { }
override fun onStateChanged(p0: View, state: Int) {
if (state == BottomSheetBehavior.STATE_EXPANDED) {
sheetBehavior.skipCollapsed = true
}
}
})
}
override fun onStart() {
super.onStart()
sheetBehavior.skipCollapsed = true
}
/**
* Called when the sheet is created. It initializes the listeners and values of the preferences.
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = TrackAdapter(this)
track_recycler.layoutManager = LinearLayoutManager(context)
track_recycler.adapter = adapter
track_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
adapter?.items = presenter.trackList
}
fun onNextTrackings(trackings: List<TrackItem>) {
onRefreshDone()
adapter?.items = trackings
controller.refreshTracker()
}
fun onSearchResults(results: List<TrackSearch>) {
getSearchDialog()?.onSearchResults(results)
}
fun onSearchResultsError(error: Throwable) {
Timber.e(error)
getSearchDialog()?.onSearchResultsError()
}
private fun getSearchDialog(): TrackSearchDialog? {
return controller.router.getControllerWithTag(TAG_SEARCH_CONTROLLER) as? TrackSearchDialog
}
fun onRefreshDone() {
for (i in adapter!!.items.indices) {
(track_recycler.findViewHolderForAdapterPosition(i) as? TrackHolder)?.setProgress(false)
}
}
fun onRefreshError(error: Throwable) {
for (i in adapter!!.items.indices) {
(track_recycler.findViewHolderForAdapterPosition(i) as? TrackHolder)?.setProgress(false)
}
activity.toast(error.message)
}
override fun onLogoClick(position: Int) {
val track = adapter?.getItem(position)?.track ?: return
if (track.tracking_url.isBlank()) {
activity.toast(R.string.url_not_set)
} else {
activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(track.tracking_url)))
}
}
override fun onSetClick(position: Int) {
val item = adapter?.getItem(position) ?: return
TrackSearchDialog(this, item.service, item.track != null).showDialog(
controller.router, TAG_SEARCH_CONTROLLER)
}
override fun onStatusClick(position: Int) {
val item = adapter?.getItem(position) ?: return
if (item.track == null) return
SetTrackStatusDialog(this, item).showDialog(controller.router)
}
override fun onChaptersClick(position: Int) {
val item = adapter?.getItem(position) ?: return
if (item.track == null) return
SetTrackChaptersDialog(this, item).showDialog(controller.router)
}
override fun onScoreClick(position: Int) {
val item = adapter?.getItem(position) ?: return
if (item.track == null) return
SetTrackScoreDialog(this, item).showDialog(controller.router)
}
override fun setStatus(item: TrackItem, selection: Int) {
presenter.setStatus(item, selection)
refreshItem(item)
}
private fun refreshItem(item: TrackItem) {
refreshTrack(item.service)
}
fun refreshTrack(item: TrackService?) {
val index = adapter?.indexOf(item) ?: -1
if (index > -1 ){
(track_recycler.findViewHolderForAdapterPosition(index) as? TrackHolder)
?.setProgress(true)
}
}
override fun setScore(item: TrackItem, score: Int) {
presenter.setScore(item, score)
refreshItem(item)
}
override fun setChaptersRead(item: TrackItem, chaptersRead: Int) {
presenter.setLastChapterRead(item, chaptersRead)
refreshItem(item)
}
private companion object {
const val TAG_SEARCH_CONTROLLER = "track_search_controller"
}
}

View File

@ -71,5 +71,6 @@ class ChaptersAdapter(
fun favoriteManga(longPress: Boolean) fun favoriteManga(longPress: Boolean)
fun copyToClipboard(content: String, label: Int) fun copyToClipboard(content: String, label: Int)
fun zoomImageFromThumb(thumbView: View) fun zoomImageFromThumb(thumbView: View)
fun showTrackingSheet()
} }
} }

View File

@ -6,7 +6,6 @@ import android.widget.NumberPicker
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView import com.afollestad.materialdialogs.customview.getCustomView
import com.bluelinelabs.conductor.Controller
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
@ -15,14 +14,15 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class SetTrackChaptersDialog<T> : DialogController class SetTrackChaptersDialog<T> : DialogController
where T : Controller, T : SetTrackChaptersDialog.Listener { where T : SetTrackChaptersDialog.Listener {
private val item: TrackItem private val item: TrackItem
private lateinit var listener: Listener
constructor(target: T, item: TrackItem) : super(Bundle().apply { constructor(target: T, item: TrackItem) : super(Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track) putSerializable(KEY_ITEM_TRACK, item.track)
}) { }) {
targetController = target listener = target
this.item = item this.item = item
} }
@ -45,7 +45,7 @@ class SetTrackChaptersDialog<T> : DialogController
// Remove focus to update selected number // Remove focus to update selected number
val np: NumberPicker = view.findViewById(R.id.chapters_picker) val np: NumberPicker = view.findViewById(R.id.chapters_picker)
np.clearFocus() np.clearFocus()
(targetController as? Listener)?.setChaptersRead(item, np.value) listener.setChaptersRead(item, np.value)
} }
val view = dialog.getCustomView() val view = dialog.getCustomView()

View File

@ -15,14 +15,15 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class SetTrackScoreDialog<T> : DialogController class SetTrackScoreDialog<T> : DialogController
where T : Controller, T : SetTrackScoreDialog.Listener { where T : SetTrackScoreDialog.Listener {
private val item: TrackItem private val item: TrackItem
private lateinit var listener: Listener
constructor(target: T, item: TrackItem) : super(Bundle().apply { constructor(target: T, item: TrackItem) : super(Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track) putSerializable(KEY_ITEM_TRACK, item.track)
}) { }) {
targetController = target listener = target
this.item = item this.item = item
} }
@ -46,8 +47,7 @@ class SetTrackScoreDialog<T> : DialogController
val np: NumberPicker = view.findViewById(R.id.score_picker) val np: NumberPicker = view.findViewById(R.id.score_picker)
np.clearFocus() np.clearFocus()
(targetController as? Listener)?.setScore(item, np.value) listener.setScore(item, np.value)
} }

View File

@ -4,7 +4,6 @@ import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsSingleChoice import com.afollestad.materialdialogs.list.listItemsSingleChoice
import com.bluelinelabs.conductor.Controller
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
@ -13,14 +12,16 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class SetTrackStatusDialog<T> : DialogController class SetTrackStatusDialog<T> : DialogController
where T : Controller, T : SetTrackStatusDialog.Listener { where T : SetTrackStatusDialog.Listener {
private val item: TrackItem private val item: TrackItem
private lateinit var listener: Listener
constructor(target: T, item: TrackItem) : super(Bundle().apply { constructor(target: T, item: TrackItem) : super(Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track) putSerializable(KEY_ITEM_TRACK, item.track)
}) { }) {
targetController = target listener = target
// targetController = target
this.item = item this.item = item
} }
@ -43,7 +44,7 @@ class SetTrackStatusDialog<T> : DialogController
.listItemsSingleChoice(items = statusString, initialSelection = selectedIndex, .listItemsSingleChoice(items = statusString, initialSelection = selectedIndex,
waitForPositiveButton = false) waitForPositiveButton = false)
{ dialog, position, _ -> { dialog, position, _ ->
(targetController as? Listener)?.setStatus(item, position) listener.setStatus(item, position)
dialog.dismiss() dialog.dismiss()
} }
} }

View File

@ -1,11 +1,12 @@
package eu.kanade.tachiyomi.ui.manga.track package eu.kanade.tachiyomi.ui.manga.track
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.util.view.inflate import eu.kanade.tachiyomi.util.view.inflate
class TrackAdapter(controller: TrackController) : RecyclerView.Adapter<TrackHolder>() { class TrackAdapter(controller: OnClickListener) : RecyclerView.Adapter<TrackHolder>() {
var items = emptyList<TrackItem>() var items = emptyList<TrackItem>()
set(value) { set(value) {
@ -34,9 +35,13 @@ class TrackAdapter(controller: TrackController) : RecyclerView.Adapter<TrackHold
holder.bind(items[position]) holder.bind(items[position])
} }
fun indexOf(item: TrackService?):Int {
return items.indexOfFirst { item?.id == it.service.id }
}
interface OnClickListener { interface OnClickListener {
fun onLogoClick(position: Int) fun onLogoClick(position: Int)
fun onTitleClick(position: Int) fun onSetClick(position: Int)
fun onStatusClick(position: Int) fun onStatusClick(position: Int)
fun onChaptersClick(position: Int) fun onChaptersClick(position: Int)
fun onScoreClick(position: Int) fun onScoreClick(position: Int)

View File

@ -119,7 +119,7 @@ class TrackController : NucleusController<TrackPresenter>(),
} }
} }
override fun onTitleClick(position: Int) { override fun onSetClick(position: Int) {
val item = adapter?.getItem(position) ?: return val item = adapter?.getItem(position) ?: return
TrackSearchDialog(this, item.service, item.track != null).showDialog(router, TrackSearchDialog(this, item.service, item.track != null).showDialog(router,
TAG_SEARCH_CONTROLLER) TAG_SEARCH_CONTROLLER)

View File

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.manga.track
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.View import android.view.View
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder
import eu.kanade.tachiyomi.util.view.visibleIf
import kotlinx.android.synthetic.main.track_item.* import kotlinx.android.synthetic.main.track_item.*
class TrackHolder(view: View, adapter: TrackAdapter) : BaseViewHolder(view) { class TrackHolder(view: View, adapter: TrackAdapter) : BaseViewHolder(view) {
@ -11,32 +11,28 @@ class TrackHolder(view: View, adapter: TrackAdapter) : BaseViewHolder(view) {
init { init {
val listener = adapter.rowClickListener val listener = adapter.rowClickListener
logo_container.setOnClickListener { listener.onLogoClick(adapterPosition) } logo_container.setOnClickListener { listener.onLogoClick(adapterPosition) }
title_container.setOnClickListener { listener.onTitleClick(adapterPosition) } track_set.setOnClickListener { listener.onSetClick(adapterPosition) }
status_container.setOnClickListener { listener.onStatusClick(adapterPosition) } status_container.setOnClickListener { listener.onStatusClick(adapterPosition) }
chapters_container.setOnClickListener { listener.onChaptersClick(adapterPosition) } chapters_container.setOnClickListener { listener.onChaptersClick(adapterPosition) }
score_container.setOnClickListener { listener.onScoreClick(adapterPosition) } score_container.setOnClickListener { listener.onScoreClick(adapterPosition) }
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@Suppress("DEPRECATION")
fun bind(item: TrackItem) { fun bind(item: TrackItem) {
val track = item.track val track = item.track
track_logo.setImageResource(item.service.getLogo()) track_logo.setImageResource(item.service.getLogo())
logo_container.setBackgroundColor(item.service.getLogoColor()) logo_container.setBackgroundColor(item.service.getLogoColor())
track_group.visibleIf(track != null)
if (track != null) { if (track != null) {
track_title.setTextAppearance(itemView.context, R.style.TextAppearance_Regular_Body1_Secondary)
track_title.isAllCaps = false
track_title.text = track.title
track_chapters.text = "${track.last_chapter_read}/" + track_chapters.text = "${track.last_chapter_read}/" +
if (track.total_chapters > 0) track.total_chapters else "-" if (track.total_chapters > 0) track.total_chapters else "-"
track_status.text = item.service.getStatus(track.status) track_status.text = item.service.getStatus(track.status)
track_score.text = if (track.score == 0f) "-" else item.service.displayScore(track) track_score.text = if (track.score == 0f) "-" else item.service.displayScore(track)
} else {
track_title.setTextAppearance(itemView.context, R.style.TextAppearance_Medium_Button)
track_title.setText(R.string.action_edit)
track_chapters.text = ""
track_score.text = ""
track_status.text = ""
} }
} }
fun setProgress(enabled: Boolean) {
progress.visibleIf(enabled)
track_logo.visibleIf(!enabled)
}
} }

View File

@ -15,11 +15,10 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import kotlinx.android.synthetic.main.track_controller.* import eu.kanade.tachiyomi.ui.manga.MangaDetailsPresenter
import eu.kanade.tachiyomi.ui.manga.TrackingBottomSheet
import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.lang.plusAssign
import kotlinx.android.synthetic.main.track_search_dialog.view.progress import kotlinx.android.synthetic.main.track_search_dialog.view.*
import kotlinx.android.synthetic.main.track_search_dialog.view.track_search
import kotlinx.android.synthetic.main.track_search_dialog.view.track_search_list
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
@ -41,10 +40,14 @@ class TrackSearchDialog : DialogController {
private var searchTextSubscription: Subscription? = null private var searchTextSubscription: Subscription? = null
private val trackController private lateinit var bottomSheet: TrackingBottomSheet
get() = targetController as TrackController //private val trackController
// get() = targetController as TrackController
private var wasPreviouslyTracked:Boolean = false private var wasPreviouslyTracked:Boolean = false
private lateinit var presenter:MangaDetailsPresenter
constructor(target: TrackController, service: TrackService, wasTracked:Boolean) : super(Bundle() constructor(target: TrackController, service: TrackService, wasTracked:Boolean) : super(Bundle()
.apply { .apply {
@ -55,6 +58,16 @@ class TrackSearchDialog : DialogController {
this.service = service this.service = service
} }
constructor(target: TrackingBottomSheet, service: TrackService, wasTracked:Boolean) : super(Bundle()
.apply {
putInt(KEY_SERVICE, service.id)
}) {
wasPreviouslyTracked = wasTracked
bottomSheet = target
presenter = target.presenter
this.service = service
}
@Suppress("unused") @Suppress("unused")
constructor(bundle: Bundle) : super(bundle) { constructor(bundle: Bundle) : super(bundle) {
service = Injekt.get<TrackManager>().getService(bundle.getInt(KEY_SERVICE))!! service = Injekt.get<TrackManager>().getService(bundle.getInt(KEY_SERVICE))!!
@ -97,7 +110,7 @@ class TrackSearchDialog : DialogController {
// Do an initial search based on the manga's title // Do an initial search based on the manga's title
if (savedState == null) { if (savedState == null) {
val title = trackController.presenter.manga.originalTitle() val title = presenter.manga.originalTitle()
view.track_search.append(title) view.track_search.append(title)
search(title) search(title)
} }
@ -129,7 +142,7 @@ class TrackSearchDialog : DialogController {
val view = dialogView ?: return val view = dialogView ?: return
view.progress.visibility = View.VISIBLE view.progress.visibility = View.VISIBLE
view.track_search_list.visibility = View.INVISIBLE view.track_search_list.visibility = View.INVISIBLE
trackController.presenter.search(query, service) presenter.trackSearch(query, service)
} }
fun onSearchResults(results: List<TrackSearch>) { fun onSearchResults(results: List<TrackSearch>) {
@ -153,8 +166,10 @@ class TrackSearchDialog : DialogController {
} }
private fun onPositiveButtonClick() { private fun onPositiveButtonClick() {
trackController.swipe_refresh.isRefreshing = true // trackController.swipe_refresh.isRefreshing = true
trackController.presenter.registerTracking(selectedItem, service) bottomSheet.refreshTrack(service)
presenter.registerTracking(selectedItem,
service)
} }
private companion object { private companion object {

View File

@ -169,7 +169,7 @@ inline val View.marginLeft: Int
object RecyclerWindowInsetsListener : View.OnApplyWindowInsetsListener { object RecyclerWindowInsetsListener : View.OnApplyWindowInsetsListener {
override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
v.setPadding(0,0,0,insets.systemWindowInsetBottom) v.updatePaddingRelative(bottom = insets.systemWindowInsetBottom)
//v.updatePaddingRelative(bottom = v.paddingBottom + insets.systemWindowInsetBottom) //v.updatePaddingRelative(bottom = v.paddingBottom + insets.systemWindowInsetBottom)
return insets return insets
} }

View File

@ -1,22 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/track"
style="@style/Theme.Widget.CardView.Item" style="@style/Theme.Widget.CardView.Item"
android:padding="0dp"> android:padding="0dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:background="?android:attr/colorBackground"
android:minHeight="?attr/actionBarSize">
<FrameLayout <FrameLayout
android:id="@+id/logo_container" android:id="@+id/logo_container"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="0dp" android:layout_height="0dp"
android:foreground="?selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/status_container"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
android:clickable="true"
tools:background="#2E51A2"> tools:background="#2E51A2">
<ImageView <ImageView
@ -26,48 +33,21 @@
android:layout_gravity="center" android:layout_gravity="center"
tools:src="@drawable/tracker_mal" /> tools:src="@drawable/tracker_mal" />
</FrameLayout> <ProgressBar
android:id="@+id/progress"
<LinearLayout
android:id="@+id/title_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectable_list_drawable"
android:clickable="true"
android:padding="16dp"
app:layout_constraintStart_toEndOf="@+id/logo_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
style="@style/TextAppearance.Regular.Body1"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/title" /> android:layout_gravity="center"
android:padding="4dp"
android:visibility="gone"/>
<TextView </FrameLayout>
android:id="@+id/track_title"
style="@style/TextAppearance.Medium.Button" <androidx.constraintlayout.widget.Group
android:layout_width="match_parent" android:id="@+id/track_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" app:constraint_referenced_ids="chapters_container,status_container,score_container" />
android:ellipsize="middle"
android:gravity="end"
android:maxLines="1"
android:text="@string/action_edit" />
</LinearLayout>
<View
android:id="@+id/divider1"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:background="?android:attr/divider"
app:layout_constraintStart_toEndOf="@+id/logo_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_container" />
<LinearLayout <LinearLayout
android:id="@+id/status_container" android:id="@+id/status_container"
@ -75,106 +55,114 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectable_list_drawable" android:background="?attr/selectable_list_drawable"
android:clickable="true" android:clickable="true"
android:padding="16dp" android:focusable="true"
app:layout_constraintStart_toEndOf="@+id/logo_container" android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/logo_container"
app:layout_constraintTop_toBottomOf="@+id/divider1"> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/chapters_container"
app:layout_constraintBottom_toBottomOf="parent"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="16dp">
<TextView <TextView
style="@style/TextAppearance.Regular.Body1" style="@style/TextAppearance.MaterialComponents.Body1"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/status" /> android:text="@string/status" />
<TextView <TextView
android:id="@+id/track_status" android:id="@+id/track_status"
style="@style/TextAppearance.Regular.Body1.Secondary" style="@style/TextAppearance.MaterialComponents.Body2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginTop="8dp"
android:gravity="end" tools:text="Currently Reading" />
tools:text="Reading" />
</LinearLayout> </LinearLayout>
<View
android:id="@+id/divider2"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:background="?android:attr/divider"
app:layout_constraintStart_toEndOf="@+id/logo_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/status_container" />
<LinearLayout <LinearLayout
android:id="@+id/chapters_container" android:id="@+id/chapters_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="0dp"
app:layout_constrainedHeight="true"
android:background="?attr/selectable_list_drawable" android:background="?attr/selectable_list_drawable"
android:clickable="true" android:clickable="true"
android:padding="16dp" android:focusable="true"
app:layout_constraintStart_toEndOf="@+id/logo_container" android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent" android:paddingStart="8dp"
app:layout_constraintTop_toBottomOf="@+id/divider2"> android:paddingTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/status_container"
app:layout_constraintEnd_toStartOf="@id/score_container"
app:layout_constraintBottom_toBottomOf="parent"
android:paddingEnd="8dp"
android:paddingBottom="16dp">
<TextView <TextView
style="@style/TextAppearance.Regular.Body1" style="@style/TextAppearance.MaterialComponents.Body1"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/chapters" /> android:text="@string/chapters" />
<TextView <TextView
android:id="@+id/track_chapters" android:id="@+id/track_chapters"
style="@style/TextAppearance.Regular.Body1.Secondary" style="@style/TextAppearance.MaterialComponents.Body2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginTop="8dp"
android:gravity="end"
tools:text="12/24" /> tools:text="12/24" />
</LinearLayout> </LinearLayout>
<View
android:id="@+id/divider3"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:background="?android:attr/divider"
app:layout_constraintStart_toEndOf="@+id/logo_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chapters_container" />
<LinearLayout <LinearLayout
android:id="@+id/score_container" android:id="@+id/score_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="0dp"
android:background="?attr/selectable_list_drawable" android:background="?attr/selectable_list_drawable"
app:layout_constraintBottom_toBottomOf="parent"
android:clickable="true" android:clickable="true"
android:padding="16dp" android:focusable="true"
app:layout_constraintStart_toEndOf="@+id/logo_container" android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider3"> app:layout_constraintStart_toEndOf="@id/chapters_container"
app:layout_constraintEnd_toStartOf="@id/track_set"
android:paddingStart="8dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp">
<TextView <TextView
style="@style/TextAppearance.Regular.Body1" style="@style/TextAppearance.MaterialComponents.Body1"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/score" /> android:text="@string/score" />
<TextView <TextView
android:id="@+id/track_score" android:id="@+id/track_score"
style="@style/TextAppearance.Regular.Body1.Secondary" style="@style/TextAppearance.MaterialComponents.Body2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginTop="8dp"
android:gravity="end"
tools:text="10" /> tools:text="10" />
</LinearLayout> </LinearLayout>
<ImageButton
android:id="@+id/track_set"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/action_edit"
android:padding="8dp"
android:tint="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/score_container"
app:srcCompat="@drawable/ic_edit_white_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView> </com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/display_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/track_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_bottom_sheet_dialog_fragment"
tools:listitem="@layout/track_item" />
</FrameLayout>

View File

@ -149,7 +149,7 @@
<item name="layout_behavior">eu.kanade.tachiyomi.widget.FABMoveBehaviour</item> <item name="layout_behavior">eu.kanade.tachiyomi.widget.FABMoveBehaviour</item>
</style> </style>
<style name="Theme.Widget.CardView" parent="CardView"> <style name="Theme.Widget.CardView" parent="Widget.MaterialComponents.CardView">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:padding">@dimen/material_component_cards_top_and_bottom_padding</item> <item name="android:padding">@dimen/material_component_cards_top_and_bottom_padding</item>