diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index 6c9399995..175a8f51f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -1118,8 +1118,7 @@ class MangaController : fun onTrackingSearchResultsError(error: Throwable) { Timber.e(error) - activity?.toast(error.message) - getTrackingSearchDialog()?.onSearchResultsError() + getTrackingSearchDialog()?.onSearchResultsError(error.message) } private fun getTrackingSearchDialog(): TrackSearchDialog? { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt index c85aec790..49d49240a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt @@ -4,6 +4,8 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import eu.kanade.tachiyomi.databinding.TrackItemBinding +import eu.kanade.tachiyomi.util.view.applyElevationOverlay +import uy.kohesive.injekt.api.get class TrackAdapter(listener: OnClickListener) : RecyclerView.Adapter() { @@ -29,6 +31,7 @@ class TrackAdapter(listener: OnClickListener) : RecyclerView.Adapter 0) track.total_chapters else "-" + binding.trackChapters.text = track.last_chapter_read.toString() + if (track.total_chapters > 0) { + binding.trackChapters.text = "${binding.trackChapters.text} / ${track.total_chapters}" + } binding.trackStatus.text = item.service.getStatus(track.status) - if (item.service.getScoreList().isEmpty()) { - binding.trackScore.isVisible = false - binding.vertDivider2.isVisible = false - } else { - binding.trackScore.text = if (track.score == 0f) "-" else item.service.displayScore(track) + val supportsScoring = item.service.getScoreList().isNotEmpty() + if (supportsScoring) { + if (track.score != 0F) { + item.service.getScoreList() + binding.trackScore.text = item.service.displayScore(track) + binding.trackScore.alpha = SET_STATUS_TEXT_ALPHA + } else { + binding.trackScore.text = ctx.getString(R.string.score) + binding.trackScore.alpha = UNSET_STATUS_TEXT_ALPHA + } } + binding.trackScore.isVisible = supportsScoring + binding.vertDivider2.isVisible = supportsScoring - if (item.service.supportsReadingDates) { - binding.trackStartDate.text = - if (track.started_reading_date != 0L) dateFormat.format(track.started_reading_date) else "-" - binding.trackFinishDate.text = - if (track.finished_reading_date != 0L) dateFormat.format(track.finished_reading_date) else "-" - } else { - binding.bottomDivider.isVisible = false - binding.bottomRow.isVisible = false + val supportsReadingDates = item.service.supportsReadingDates + if (supportsReadingDates) { + if (track.started_reading_date != 0L) { + binding.trackStartDate.text = dateFormat.format(track.started_reading_date) + binding.trackStartDate.alpha = SET_STATUS_TEXT_ALPHA + } else { + binding.trackStartDate.text = ctx.getString(R.string.track_started_reading_date) + binding.trackStartDate.alpha = UNSET_STATUS_TEXT_ALPHA + } + if (track.finished_reading_date != 0L) { + binding.trackFinishDate.text = dateFormat.format(track.finished_reading_date) + binding.trackFinishDate.alpha = SET_STATUS_TEXT_ALPHA + } else { + binding.trackFinishDate.text = ctx.getString(R.string.track_finished_reading_date) + binding.trackFinishDate.alpha = UNSET_STATUS_TEXT_ALPHA + } } + binding.bottomDivider.isVisible = supportsReadingDates + binding.bottomRow.isVisible = supportsReadingDates } } + + companion object { + private const val SET_STATUS_TEXT_ALPHA = 1F + private const val UNSET_STATUS_TEXT_ALPHA = 0.5F + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt index af5c92b02..4ce598e45 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt @@ -1,76 +1,57 @@ package eu.kanade.tachiyomi.ui.manga.track -import android.content.Context -import android.view.View +import android.view.LayoutInflater import android.view.ViewGroup -import android.widget.ArrayAdapter -import androidx.core.view.isVisible -import coil.clear -import coil.load -import eu.kanade.tachiyomi.R +import androidx.recyclerview.widget.RecyclerView import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.databinding.TrackSearchItemBinding -import eu.kanade.tachiyomi.util.view.inflate +import eu.kanade.tachiyomi.util.view.applyElevationOverlay -class TrackSearchAdapter(context: Context) : - ArrayAdapter(context, R.layout.track_search_item, mutableListOf()) { +class TrackSearchAdapter( + private val currentTrackUrl: String?, + private val onSelectionChanged: (TrackSearch?) -> Unit +) : RecyclerView.Adapter() { + var selectedItemPosition = -1 + set(value) { + if (field != value) { + val previousPosition = field + field = value + // Just notify the now-unselected item + notifyItemChanged(previousPosition, UncheckPayload) + onSelectionChanged(items.getOrNull(value)) + } + } - override fun getView(position: Int, view: View?, parent: ViewGroup): View { - var v = view - // Get the data item for this position - val track = getItem(position)!! - // Check if an existing view is being reused, otherwise inflate the view - val holder: TrackSearchHolder // view lookup cache stored in tag - if (v == null) { - v = parent.inflate(R.layout.track_search_item) - holder = TrackSearchHolder(v) - v.tag = holder + var items = emptyList() + set(value) { + if (field != value) { + field = value + selectedItemPosition = value.indexOfFirst { it.tracking_url == currentTrackUrl } + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackSearchHolder { + val binding = TrackSearchItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + binding.container.applyElevationOverlay() + return TrackSearchHolder(binding, this) + } + + override fun onBindViewHolder(holder: TrackSearchHolder, position: Int) { + holder.bind(items[position], position) + } + + override fun onBindViewHolder(holder: TrackSearchHolder, position: Int, payloads: MutableList) { + if (payloads.getOrNull(0) == UncheckPayload) { + holder.setUnchecked() } else { - holder = v.tag as TrackSearchHolder + super.onBindViewHolder(holder, position, payloads) } - holder.onSetValues(track) - return v } - fun setItems(syncs: List) { - setNotifyOnChange(false) - clear() - addAll(syncs) - notifyDataSetChanged() - } - - class TrackSearchHolder(private val view: View) { - - private val binding = TrackSearchItemBinding.bind(view) - - fun onSetValues(track: TrackSearch) { - binding.trackSearchTitle.text = track.title - binding.trackSearchSummary.text = track.summary - binding.trackSearchCover.clear() - if (track.cover_url.isNotEmpty()) { - binding.trackSearchCover.load(track.cover_url) - } - - val hasStatus = track.publishing_status.isNotBlank() - binding.trackSearchStatus.isVisible = hasStatus - binding.trackSearchStatusResult.isVisible = hasStatus - if (hasStatus) { - binding.trackSearchStatusResult.text = track.publishing_status.capitalize() - } - - val hasType = track.publishing_type.isNotBlank() - binding.trackSearchType.isVisible = hasType - binding.trackSearchTypeResult.isVisible = hasType - if (hasType) { - binding.trackSearchTypeResult.text = track.publishing_type.capitalize() - } - - val hasStartDate = track.start_date.isNotBlank() - binding.trackSearchStart.isVisible = hasStartDate - binding.trackSearchStartResult.isVisible = hasStartDate - if (hasStartDate) { - binding.trackSearchStartResult.text = track.start_date - } - } + companion object { + private object UncheckPayload } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt index 11ee42b58..a770e02d1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt @@ -2,29 +2,31 @@ package eu.kanade.tachiyomi.ui.manga.track import android.app.Dialog import android.os.Bundle +import android.view.KeyEvent import android.view.LayoutInflater import android.view.View +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import androidx.appcompat.app.AppCompatDialog +import androidx.core.content.getSystemService import androidx.core.os.bundleOf +import androidx.core.view.WindowCompat import androidx.core.view.isVisible -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.customview.customView +import dev.chrisbanes.insetter.applyInsetter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.databinding.TrackSearchDialogBinding import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.manga.MangaController -import kotlinx.coroutines.flow.debounce +import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import reactivecircus.flowbinding.android.widget.itemClicks -import reactivecircus.flowbinding.android.widget.textChanges +import reactivecircus.flowbinding.android.widget.editorActionEvents import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.concurrent.TimeUnit class TrackSearchDialog : DialogController { @@ -32,59 +34,130 @@ class TrackSearchDialog : DialogController { private var adapter: TrackSearchAdapter? = null - private var selectedItem: Track? = null - private val service: TrackService + private val currentTrackUrl: String? private val trackController get() = targetController as MangaController - constructor(target: MangaController, service: TrackService) : super( - bundleOf(KEY_SERVICE to service.id) - ) { + private lateinit var currentlySearched: String + + constructor( + target: MangaController, + _service: TrackService, + _currentTrackUrl: String? + ) : super(bundleOf(KEY_SERVICE to _service.id, KEY_CURRENT_URL to _currentTrackUrl)) { targetController = target - this.service = service + service = _service + currentTrackUrl = _currentTrackUrl } @Suppress("unused") constructor(bundle: Bundle) : super(bundle) { service = Injekt.get().getService(bundle.getInt(KEY_SERVICE))!! + currentTrackUrl = bundle.getString(KEY_CURRENT_URL) } @Suppress("DEPRECATION") override fun onCreateDialog(savedViewState: Bundle?): Dialog { binding = TrackSearchDialogBinding.inflate(LayoutInflater.from(activity!!)) - val dialog = MaterialDialog(activity!!) - .customView(view = binding!!.root) - .positiveButton(android.R.string.ok) { onPositiveButtonClick() } - .negativeButton(android.R.string.cancel) - .neutralButton(R.string.action_remove) { onRemoveButtonClick() } - onViewCreated(dialog.view, savedViewState) - - return dialog - } - - fun onViewCreated(view: View, savedState: Bundle?) { - // Create adapter - val adapter = TrackSearchAdapter(view.context) - this.adapter = adapter - binding!!.trackSearchList.adapter = adapter - - // Set listeners - selectedItem = null - - binding!!.trackSearchList.itemClicks() - .onEach { position -> - selectedItem = adapter.getItem(position) + // Toolbar stuff + binding!!.toolbar.setNavigationOnClickListener { dialog?.dismiss() } + binding!!.toolbar.setOnMenuItemClickListener { + when (it.itemId) { + R.id.done -> { + val adapter = adapter ?: return@setOnMenuItemClickListener true + val item = adapter.items.getOrNull(adapter.selectedItemPosition) + if (item != null) { + trackController.presenter.registerTracking(item, service) + dialog?.dismiss() + } + } + R.id.remove -> { + trackController.presenter.unregisterTracking(service) + dialog?.dismiss() + } } - .launchIn(trackController.viewScope) + true + } + binding!!.toolbar.menu.findItem(R.id.remove).isVisible = currentTrackUrl != null + + // Create adapter + adapter = TrackSearchAdapter(currentTrackUrl) { which -> + binding!!.toolbar.menu.findItem(R.id.done).isEnabled = which != null + } + binding!!.trackSearchRecyclerview.adapter = adapter // Do an initial search based on the manga's title - if (savedState == null) { - val title = trackController.presenter.manga.title - binding!!.trackSearch.append(title) - search(title) + if (savedViewState == null) { + currentlySearched = trackController.presenter.manga.title + binding!!.titleInput.editText?.append(currentlySearched) + } + search(currentlySearched) + + // Input listener + binding?.titleInput?.editText + ?.editorActionEvents { + when (it.actionId) { + EditorInfo.IME_ACTION_SEARCH -> { + true + } + else -> { + it.keyEvent?.action == KeyEvent.ACTION_DOWN && it.keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER + } + } + } + ?.filter { it.view.text.isNotBlank() } + ?.onEach { + val query = it.view.text.toString() + if (query != currentlySearched) { + currentlySearched = query + search(it.view.text.toString()) + it.view.context.getSystemService()?.hideSoftInputFromWindow(it.view.windowToken, 0) + it.view.clearFocus() + } + } + ?.launchIn(trackController.viewScope) + + // Edge to edge + binding!!.appbar.applyInsetter { + type(navigationBars = true, statusBars = true) { + padding(left = true, top = true, right = true) + } + } + binding!!.titleInput.applyInsetter { + type(navigationBars = true) { + margin(horizontal = true) + } + } + binding!!.progress.applyInsetter { + type(navigationBars = true) { + margin() + } + } + binding!!.message.applyInsetter { + type(navigationBars = true) { + margin() + } + } + binding!!.trackSearchRecyclerview.applyInsetter { + type(navigationBars = true) { + padding(vertical = true) + margin(horizontal = true) + } + } + + return AppCompatDialog(activity!!, R.style.ThemeOverlay_Tachiyomi_Dialog_Fullscreen).apply { + setContentView(binding!!.root) + } + } + + override fun onAttach(view: View) { + super.onAttach(view) + dialog?.window?.let { window -> + window.setNavigationBarTransparentCompat(window.context) + WindowCompat.setDecorFitsSystemWindows(window, false) } } @@ -94,46 +167,39 @@ class TrackSearchDialog : DialogController { adapter = null } - override fun onAttach(view: View) { - super.onAttach(view) - binding!!.trackSearch.textChanges() - .debounce(TimeUnit.SECONDS.toMillis(1)) - .filter { it.isNotBlank() } - .onEach { search(it.toString()) } - .launchIn(trackController.viewScope) - } - private fun search(query: String) { val binding = binding ?: return binding.progress.isVisible = true - binding.trackSearchList.isVisible = false + binding.trackSearchRecyclerview.isVisible = false + binding.message.isVisible = false trackController.presenter.trackingSearch(query, service) } fun onSearchResults(results: List) { - selectedItem = null val binding = binding ?: return binding.progress.isVisible = false - binding.trackSearchList.isVisible = true - adapter?.setItems(results) + + val emptyResult = results.isEmpty() + adapter?.items = results + binding.trackSearchRecyclerview.isVisible = !emptyResult + binding.trackSearchRecyclerview.scrollToPosition(0) + binding.message.isVisible = emptyResult + if (emptyResult) { + binding.message.text = binding.message.context.getString(R.string.no_results_found) + } } - fun onSearchResultsError() { + fun onSearchResultsError(message: String?) { val binding = binding ?: return binding.progress.isVisible = false - binding.trackSearchList.isVisible = false - adapter?.setItems(emptyList()) - } - - private fun onPositiveButtonClick() { - trackController.presenter.registerTracking(selectedItem, service) - } - - private fun onRemoveButtonClick() { - trackController.presenter.unregisterTracking(service) + binding.trackSearchRecyclerview.isVisible = false + binding.message.isVisible = true + binding.message.text = message ?: binding.message.context.getString(R.string.unknown_error) + adapter?.items = emptyList() } private companion object { const val KEY_SERVICE = "service_id" + const val KEY_CURRENT_URL = "current_url" } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchHolder.kt new file mode 100644 index 000000000..c07701a4f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchHolder.kt @@ -0,0 +1,61 @@ +package eu.kanade.tachiyomi.ui.manga.track + +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import coil.clear +import coil.load +import eu.kanade.tachiyomi.data.track.model.TrackSearch +import eu.kanade.tachiyomi.databinding.TrackSearchItemBinding +import eu.kanade.tachiyomi.util.view.setMaxLinesAndEllipsize +import java.util.Locale + +class TrackSearchHolder( + private val binding: TrackSearchItemBinding, + private val adapter: TrackSearchAdapter +) : RecyclerView.ViewHolder(binding.root) { + fun bind(track: TrackSearch, position: Int) { + binding.container.isChecked = position == adapter.selectedItemPosition + binding.container.setOnClickListener { + adapter.selectedItemPosition = position + binding.container.isChecked = true + } + + binding.trackSearchTitle.text = track.title + binding.trackSearchCover.clear() + if (track.cover_url.isNotEmpty()) { + binding.trackSearchCover.load(track.cover_url) + } + + val hasStatus = track.publishing_status.isNotBlank() + binding.trackSearchStatus.isVisible = hasStatus + binding.trackSearchStatusResult.isVisible = hasStatus + if (hasStatus) { + binding.trackSearchStatusResult.text = track.publishing_status.lowercase().replaceFirstChar { + it.titlecase(Locale.getDefault()) + } + } + + val hasType = track.publishing_type.isNotBlank() + binding.trackSearchType.isVisible = hasType + binding.trackSearchTypeResult.isVisible = hasType + if (hasType) { + binding.trackSearchTypeResult.text = track.publishing_type.lowercase().replaceFirstChar { + it.titlecase(Locale.getDefault()) + } + } + + val hasStartDate = track.start_date.isNotBlank() + binding.trackSearchStart.isVisible = hasStartDate + binding.trackSearchStartResult.isVisible = hasStartDate + if (hasStartDate) { + binding.trackSearchStartResult.text = track.start_date + } + + binding.trackSearchSummary.setMaxLinesAndEllipsize() + binding.trackSearchSummary.text = track.summary + } + + fun setUnchecked() { + binding.container.isChecked = false + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSheet.kt index c34784908..bccca0c6a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSheet.kt @@ -96,7 +96,8 @@ class TrackSheet( } } } else { - TrackSearchDialog(controller, item.service).showDialog(controller.router, TAG_SEARCH_CONTROLLER) + TrackSearchDialog(controller, item.service, item.track?.tracking_url) + .showDialog(controller.router, TAG_SEARCH_CONTROLLER) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index 0ff872bf1..eedb0d90c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -38,6 +38,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.lang.truncateCenter import timber.log.Timber +import uy.kohesive.injekt.api.get import java.io.File import kotlin.math.roundToInt diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 5e608b393..4581241a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -4,10 +4,12 @@ package eu.kanade.tachiyomi.util.view import android.annotation.SuppressLint import android.graphics.Point +import android.text.TextUtils import android.view.Gravity import android.view.Menu import android.view.MenuItem import android.view.View +import android.widget.TextView import androidx.annotation.MenuRes import androidx.annotation.StringRes import androidx.appcompat.view.menu.MenuBuilder @@ -16,12 +18,17 @@ import androidx.appcompat.widget.TooltipCompat import androidx.core.content.ContextCompat import androidx.core.view.forEach import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.card.MaterialCardView import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup +import com.google.android.material.elevation.ElevationOverlayProvider import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.snackbar.Snackbar import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.system.getResourceColor +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get /** * Returns coordinates of view. @@ -174,3 +181,21 @@ inline fun ChipGroup.setChips( addView(chip) } } + +/** + * Applies elevation overlay to a MaterialCardView + */ +inline fun MaterialCardView.applyElevationOverlay() { + if (Injekt.get().isDarkMode()) { + val provider = ElevationOverlayProvider(context) + setCardBackgroundColor(provider.compositeOverlay(cardBackgroundColor.defaultColor, cardElevation)) + } +} + +/** + * Sets TextView max lines dynamically. Can only be called when the view is already laid out. + */ +inline fun TextView.setMaxLinesAndEllipsize(_ellipsize: TextUtils.TruncateAt = TextUtils.TruncateAt.END) = post { + maxLines = (measuredHeight - paddingTop - paddingBottom) / lineHeight + ellipsize = _ellipsize +} diff --git a/app/src/main/res/anim/fade_in_short.xml b/app/src/main/res/anim/fade_in_short.xml new file mode 100644 index 000000000..763776244 --- /dev/null +++ b/app/src/main/res/anim/fade_in_short.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out_short.xml b/app/src/main/res/anim/fade_out_short.xml new file mode 100644 index 000000000..2aef4ef5b --- /dev/null +++ b/app/src/main/res/anim/fade_out_short.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/track_controller.xml b/app/src/main/res/layout/track_controller.xml index 144d46235..47b67ee7b 100644 --- a/app/src/main/res/layout/track_controller.xml +++ b/app/src/main/res/layout/track_controller.xml @@ -5,6 +5,5 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" - android:paddingTop="8dp" - android:paddingBottom="8dp" + android:paddingVertical="8dp" tools:listitem="@layout/track_item" /> diff --git a/app/src/main/res/layout/track_item.xml b/app/src/main/res/layout/track_item.xml index 71123ba12..d37f78e12 100644 --- a/app/src/main/res/layout/track_item.xml +++ b/app/src/main/res/layout/track_item.xml @@ -1,192 +1,194 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipToPadding="false" + android:orientation="vertical" + android:paddingHorizontal="16dp" + android:paddingVertical="8dp"> + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + + + - - - - - - - - -