Details activity improvements

This commit is contained in:
Koitharu
2024-12-11 12:47:18 +02:00
parent ee10b013a1
commit 146ba95af6
12 changed files with 100 additions and 137 deletions

View File

@@ -15,7 +15,7 @@ class ReadingTimeUseCase @Inject constructor(
private val statsRepository: StatsRepository, private val statsRepository: StatsRepository,
) { ) {
suspend fun invoke(manga: MangaDetails?, branch: String?, history: MangaHistory?): ReadingTime? { suspend operator fun invoke(manga: MangaDetails?, branch: String?, history: MangaHistory?): ReadingTime? {
if (!settings.isReadingTimeEstimationEnabled) { if (!settings.isReadingTimeEstimationEnabled) {
return null return null
} }

View File

@@ -2,15 +2,9 @@ package org.koitharu.kotatsu.details.ui
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.text.style.DynamicDrawableSpan
import android.text.style.ForegroundColorSpan
import android.text.style.ImageSpan
import android.text.style.RelativeSizeSpan
import android.transition.TransitionManager import android.transition.TransitionManager
import android.view.Gravity import android.view.Gravity
import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup.MarginLayoutParams
@@ -19,8 +13,6 @@ import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.core.text.method.LinkMovementMethodCompat import androidx.core.text.method.LinkMovementMethodCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
@@ -79,7 +71,6 @@ import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.drawable import org.koitharu.kotatsu.core.util.ext.drawable
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
import org.koitharu.kotatsu.core.util.ext.isTextTruncated import org.koitharu.kotatsu.core.util.ext.isTextTruncated
import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit
@@ -168,7 +159,8 @@ class DetailsActivity :
TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView) TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)
viewBinding.containerBottomSheet?.let { sheet -> viewBinding.containerBottomSheet?.let { sheet ->
onBackPressedDispatcher.addCallback(BottomSheetCollapseCallback(sheet)) onBackPressedDispatcher.addCallback(BottomSheetCollapseCallback(sheet))
BottomSheetBehavior.from(sheet).addBottomSheetCallback(DetailsBottomSheetCallback(viewBinding.swipeRefreshLayout)) BottomSheetBehavior.from(sheet)
.addBottomSheetCallback(DetailsBottomSheetCallback(viewBinding.swipeRefreshLayout))
} }
TitleExpandListener(viewBinding.textViewTitle).attach() TitleExpandListener(viewBinding.textViewTitle).attach()
@@ -187,18 +179,14 @@ class DetailsActivity :
viewModel.scrobblingInfo.observe(this, ::onScrobblingInfoChanged) viewModel.scrobblingInfo.observe(this, ::onScrobblingInfoChanged)
viewModel.localSize.observe(this, ::onLocalSizeChanged) viewModel.localSize.observe(this, ::onLocalSizeChanged)
viewModel.relatedManga.observe(this, ::onRelatedMangaChanged) viewModel.relatedManga.observe(this, ::onRelatedMangaChanged)
viewModel.readingTime.observe(this, ::onReadingTimeChanged)
// viewModel.selectedBranch.observe(this) {
// viewBinding.infoLayout?.chipBranch?.text = it.ifNullOrEmpty { getString(R.string.system_default) }
// }
viewModel.favouriteCategories.observe(this, ::onFavoritesChanged) viewModel.favouriteCategories.observe(this, ::onFavoritesChanged)
val menuInvalidator = MenuInvalidator(this) val menuInvalidator = MenuInvalidator(this)
viewModel.isStatsAvailable.observe(this, menuInvalidator) viewModel.isStatsAvailable.observe(this, menuInvalidator)
viewModel.remoteManga.observe(this, menuInvalidator) viewModel.remoteManga.observe(this, menuInvalidator)
// viewModel.branches.observe(this) { viewModel.branches.observe(this) {
// viewBinding.infoLayout?.chipBranch?.isVisible = it.size > 1 || !it.firstOrNull()?.name.isNullOrEmpty() infoBinding.textViewTranslation.textAndVisible = it.singleOrNull()?.name
// viewBinding.infoLayout?.chipBranch?.isCloseIconVisible = it.size > 1 infoBinding.textViewTranslationLabel.isVisible = infoBinding.textViewTranslation.isVisible
// } }
viewModel.chapters.observe(this, PrefetchObserver(this)) viewModel.chapters.observe(this, PrefetchObserver(this))
viewModel.onDownloadStarted viewModel.onDownloadStarted
.filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) } .filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) }
@@ -379,11 +367,6 @@ class DetailsActivity :
} }
} }
private fun onReadingTimeChanged(time: ReadingTime?) {
// TODO
// chip.textAndVisible = time?.formatShort(chip.resources)
}
private fun onLocalSizeChanged(size: Long) { private fun onLocalSizeChanged(size: Long) {
if (size == 0L) { if (size == 0L) {
infoBinding.textViewLocal.isVisible = false infoBinding.textViewLocal.isVisible = false
@@ -518,13 +501,14 @@ class DetailsActivity :
} }
private fun onHistoryChanged(info: HistoryInfo, isLoading: Boolean) = with(infoBinding) { private fun onHistoryChanged(info: HistoryInfo, isLoading: Boolean) = with(infoBinding) {
textViewChapters.textAndVisible = when { textViewChapters.textAndVisible = if (isLoading) {
isLoading -> null null
} else when {
info.currentChapter >= 0 -> getString(R.string.chapter_d_of_d, info.currentChapter + 1, info.totalChapters) info.currentChapter >= 0 -> getString(R.string.chapter_d_of_d, info.currentChapter + 1, info.totalChapters)
info.totalChapters == 0 -> getString(R.string.no_chapters) info.totalChapters == 0 -> getString(R.string.no_chapters)
info.totalChapters == -1 -> getString(R.string.error_occurred) info.totalChapters == -1 -> getString(R.string.error_occurred)
else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters) else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters)
} }.withEstimatedTime(info.estimatedTime)
textViewProgress.textAndVisible = if (info.percent <= 0f) { textViewProgress.textAndVisible = if (info.percent <= 0f) {
null null
} else { } else {
@@ -541,53 +525,6 @@ class DetailsActivity :
// buttonDownload?.isEnabled = info.isValid && info.canDownload // buttonDownload?.isEnabled = info.isValid && info.canDownload
} }
private fun showBranchPopupMenu(v: View) {
val branches = viewModel.branches.value
if (branches.size <= 1) {
return
}
val menu = PopupMenu(v.context, v)
for ((i, branch) in branches.withIndex()) {
val title = buildSpannedString {
if (branch.isCurrent) {
inSpans(
ImageSpan(
this@DetailsActivity,
R.drawable.ic_current_chapter,
DynamicDrawableSpan.ALIGN_BASELINE,
),
) {
append(' ')
}
append(' ')
}
append(branch.name ?: getString(R.string.system_default))
append(' ')
append(' ')
inSpans(
ForegroundColorSpan(
v.context.getThemeColor(
android.R.attr.textColorSecondary,
Color.LTGRAY,
),
),
RelativeSizeSpan(0.74f),
) {
append(branch.count.toString())
}
}
val item = menu.menu.add(R.id.group_branches, Menu.NONE, i, title)
item.isCheckable = true
item.isChecked = branch.isSelected
}
menu.menu.setGroupCheckable(R.id.group_branches, true, true)
menu.setOnMenuItemClickListener {
viewModel.setSelectedBranch(branches.getOrNull(it.order)?.name)
true
}
menu.show()
}
private fun openReader(isIncognitoMode: Boolean) { private fun openReader(isIncognitoMode: Boolean) {
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return
if (viewModel.historyInfo.value.isChapterMissing) { if (viewModel.historyInfo.value.isChapterMissing) {
@@ -638,6 +575,14 @@ class DetailsActivity :
request.enqueueWith(coil) request.enqueueWith(coil)
} }
private fun String.withEstimatedTime(time: ReadingTime?): String {
if (time == null) {
return this
}
val timeFormatted = time.formatShort(resources)
return getString(R.string.chapters_time_pattern, this, timeFormatted)
}
private class PrefetchObserver( private class PrefetchObserver(
private val context: Context, private val context: Context,
) : FlowCollector<List<ChapterListItem>?> { ) : FlowCollector<List<ChapterListItem>?> {

View File

@@ -112,12 +112,14 @@ class DetailsViewModel @Inject constructor(
history, history,
interactor.observeIncognitoMode(manga), interactor.observeIncognitoMode(manga),
) { m, b, h, im -> ) { m, b, h, im ->
HistoryInfo(m, b, h, im) val estimatedTime = readingTimeUseCase.invoke(m, b, h)
}.stateIn( HistoryInfo(m, b, h, im, estimatedTime)
scope = viewModelScope + Dispatchers.Default, }.withErrorHandling()
started = SharingStarted.Eagerly, .stateIn(
initialValue = HistoryInfo(null, null, null, false), scope = viewModelScope + Dispatchers.Default,
) started = SharingStarted.Eagerly,
initialValue = HistoryInfo(null, null, null, false, null),
)
val localSize = mangaDetails val localSize = mangaDetails
.map { it?.local } .map { it?.local }
@@ -170,14 +172,6 @@ class DetailsViewModel @Inject constructor(
}.sortedWith(BranchComparator()) }.sortedWith(BranchComparator())
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList()) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
val readingTime = combine(
mangaDetails,
selectedBranch,
history,
) { m, b, h ->
readingTimeUseCase.invoke(m, b, h)
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, null)
val selectedBranchValue: String? val selectedBranchValue: String?
get() = selectedBranch.value get() = selectedBranch.value
@@ -222,14 +216,6 @@ class DetailsViewModel @Inject constructor(
} }
} }
fun startChaptersSelection() {
val chapters = chapters.value
val chapter = chapters.find {
it.isUnread && !it.isDownloaded
} ?: chapters.firstOrNull() ?: return
onSelectChapter.call(chapter.chapter.id)
}
fun removeFromHistory() { fun removeFromHistory() {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
val handle = historyRepository.delete(setOf(mangaId)) val handle = historyRepository.delete(setOf(mangaId))

View File

@@ -13,15 +13,20 @@ import android.widget.Toast
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.text.buildSpannedString import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans import androidx.core.text.inSpans
import androidx.core.view.MenuCompat
import androidx.core.view.get import androidx.core.view.get
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialSplitButton import com.google.android.material.button.MaterialSplitButton
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.details.ui.model.HistoryInfo import org.koitharu.kotatsu.details.ui.model.HistoryInfo
import org.koitharu.kotatsu.download.ui.dialog.DownloadDialogFragment
import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity
class ReadButtonDelegate( class ReadButtonDelegate(
@@ -46,10 +51,17 @@ class ReadButtonDelegate(
when (item.itemId) { when (item.itemId) {
R.id.action_incognito -> openReader(isIncognitoMode = true) R.id.action_incognito -> openReader(isIncognitoMode = true)
R.id.action_forget -> viewModel.removeFromHistory() R.id.action_forget -> viewModel.removeFromHistory()
else -> { R.id.action_download -> DownloadDialogFragment.show(
fm = (context.findActivity() as? FragmentActivity)?.supportFragmentManager ?: return false,
manga = setOf(viewModel.getMangaOrNull() ?: return false),
)
Menu.NONE -> {
val branch = viewModel.branches.value.getOrNull(item.order) ?: return false val branch = viewModel.branches.value.getOrNull(item.order) ?: return false
viewModel.setSelectedBranch(branch.name) viewModel.setSelectedBranch(branch.name)
} }
else -> return false
} }
return true return true
} }
@@ -67,11 +79,7 @@ class ReadButtonDelegate(
private fun showMenu() { private fun showMenu() {
val menu = PopupMenu(context, buttonMenu) val menu = PopupMenu(context, buttonMenu)
menu.inflate(R.menu.popup_read) menu.inflate(R.menu.popup_read)
menu.menu.setGroupDividerEnabled(true) prepareMenu(menu.menu)
menu.menu.populateBranchList()
menu.menu.findItem(R.id.action_forget)?.isVisible = viewModel.historyInfo.value.run {
!isIncognitoMode && history != null
}
menu.setOnMenuItemClickListener(this) menu.setOnMenuItemClickListener(this)
menu.setForceShowIcon(true) menu.setForceShowIcon(true)
menu.setOnDismissListener(this) menu.setOnDismissListener(this)
@@ -79,6 +87,15 @@ class ReadButtonDelegate(
menu.show() menu.show()
} }
private fun prepareMenu(menu: Menu) {
MenuCompat.setGroupDividerEnabled(menu, true)
menu.populateBranchList()
val history = viewModel.historyInfo.value
menu.findItem(R.id.action_incognito)?.isVisible = !history.isIncognitoMode
menu.findItem(R.id.action_forget)?.isVisible = history.history != null
menu.findItem(R.id.action_download)?.isVisible = viewModel.getMangaOrNull()?.isLocal == false
}
private fun openReader(isIncognitoMode: Boolean) { private fun openReader(isIncognitoMode: Boolean) {
val detailsViewModel = viewModel as? DetailsViewModel ?: return val detailsViewModel = viewModel as? DetailsViewModel ?: return
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.details.ui.model
import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.details.data.MangaDetails import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.details.data.ReadingTime
data class HistoryInfo( data class HistoryInfo(
val totalChapters: Int, val totalChapters: Int,
@@ -10,6 +11,7 @@ data class HistoryInfo(
val isIncognitoMode: Boolean, val isIncognitoMode: Boolean,
val isChapterMissing: Boolean, val isChapterMissing: Boolean,
val canDownload: Boolean, val canDownload: Boolean,
val estimatedTime: ReadingTime?,
) { ) {
val isValid: Boolean val isValid: Boolean
get() = totalChapters >= 0 get() = totalChapters >= 0
@@ -29,7 +31,8 @@ fun HistoryInfo(
manga: MangaDetails?, manga: MangaDetails?,
branch: String?, branch: String?,
history: MangaHistory?, history: MangaHistory?,
isIncognitoMode: Boolean isIncognitoMode: Boolean,
estimatedTime: ReadingTime?,
): HistoryInfo { ): HistoryInfo {
val chapters = if (manga?.chapters?.isEmpty() == true) { val chapters = if (manga?.chapters?.isEmpty() == true) {
emptyList() emptyList()
@@ -48,5 +51,6 @@ fun HistoryInfo(
isIncognitoMode = isIncognitoMode, isIncognitoMode = isIncognitoMode,
isChapterMissing = history != null && manga?.isLoaded == true && manga.allChapters.none { it.id == history.chapterId }, isChapterMissing = history != null && manga?.isLoaded == true && manga.allChapters.none { it.id == history.chapterId },
canDownload = manga?.isLocal == false, canDownload = manga?.isLocal == false,
estimatedTime = estimatedTime,
) )
} }

View File

@@ -61,7 +61,6 @@ abstract class ChaptersPagesViewModel(
val readingState = MutableStateFlow<ReaderState?>(null) val readingState = MutableStateFlow<ReaderState?>(null)
val onActionDone = MutableEventFlow<ReversibleAction>() val onActionDone = MutableEventFlow<ReversibleAction>()
val onSelectChapter = MutableEventFlow<Long>()
val onDownloadStarted = MutableEventFlow<Unit>() val onDownloadStarted = MutableEventFlow<Unit>()
val onMangaRemoved = MutableEventFlow<Manga>() val onMangaRemoved = MutableEventFlow<Manga>()

View File

@@ -5,19 +5,15 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.ancestors
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.dialog.CommonAlertDialogs import org.koitharu.kotatsu.core.ui.dialog.CommonAlertDialogs
@@ -30,7 +26,6 @@ import org.koitharu.kotatsu.core.util.ext.dismissParentDialog
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
import org.koitharu.kotatsu.core.util.ext.findParentCallback import org.koitharu.kotatsu.core.util.ext.findParentCallback
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
@@ -100,7 +95,6 @@ class ChaptersFragment :
viewModel.isChaptersEmpty.observe(viewLifecycleOwner) { viewModel.isChaptersEmpty.observe(viewLifecycleOwner) {
binding.textViewHolder.isVisible = it binding.textViewHolder.isVisible = it
} }
viewModel.onSelectChapter.observeEvent(viewLifecycleOwner, ::onSelectChapter)
} }
override fun onDestroyView() { override fun onDestroyView() {
@@ -127,11 +121,11 @@ class ChaptersFragment :
} }
override fun onItemLongClick(item: ChapterListItem, view: View): Boolean { override fun onItemLongClick(item: ChapterListItem, view: View): Boolean {
return selectionController?.onItemLongClick(view, item.chapter.id) ?: false return selectionController?.onItemLongClick(view, item.chapter.id) == true
} }
override fun onItemContextClick(item: ChapterListItem, view: View): Boolean { override fun onItemContextClick(item: ChapterListItem, view: View): Boolean {
return selectionController?.onItemContextClick(view, item.chapter.id) ?: false return selectionController?.onItemContextClick(view, item.chapter.id) == true
} }
override fun onChipClick(chip: Chip, data: Any?) { override fun onChipClick(chip: Chip, data: Any?) {
@@ -166,25 +160,6 @@ class ChaptersFragment :
} }
} }
private suspend fun onSelectChapter(chapterId: Long) {
if (!isResumed) {
view?.ancestors?.firstNotNullOfOrNull { it as? ViewPager2 }?.setCurrentItem(0, true)
}
val position = withContext(Dispatchers.Default) {
val predicate: (ListModel) -> Boolean = { x -> x is ChapterListItem && x.chapter.id == chapterId }
val items = chaptersAdapter?.observeItems()?.firstOrNull { it.any(predicate) }
items?.indexOfFirst(predicate) ?: -1
}
if (position >= 0) {
selectionController?.startSelection(chapterId)
val lm = (viewBinding?.recyclerViewChapters?.layoutManager as? LinearLayoutManager)
if (lm != null) {
val offset = resources.getDimensionPixelOffset(R.dimen.chapter_list_item_height)
lm.scrollToPositionWithOffset(position, offset)
}
}
}
private fun onLoadingStateChanged(isLoading: Boolean) { private fun onLoadingStateChanged(isLoading: Boolean) {
requireViewBinding().progressBar.isVisible = isLoading requireViewBinding().progressBar.isVisible = isLoading
} }

View File

@@ -75,6 +75,7 @@ class DownloadDialogFragment : AlertDialogFragment<DialogDownloadBinding>(), Vie
binding.buttonConfirm.setOnClickListener(this) binding.buttonConfirm.setOnClickListener(this)
binding.textViewMore.setOnClickListener(this) binding.textViewMore.setOnClickListener(this)
binding.textViewTip.isVisible = viewModel.manga.size == 1
binding.textViewSummary.text = viewModel.manga.joinToStringWithLimit(binding.root.context, 120) { it.title } binding.textViewSummary.text = viewModel.manga.joinToStringWithLimit(binding.root.context, 120) { it.title }
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)

View File

@@ -92,18 +92,21 @@
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/textView_tip"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_normal" android:drawablePadding="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small" android:paddingHorizontal="@dimen/margin_normal"
android:paddingVertical="@dimen/margin_small"
android:text="@string/chapter_selection_hint" android:text="@string/chapter_selection_hint"
android:textAppearance="?attr/textAppearanceBodySmall" /> android:textAppearance="?attr/textAppearanceBodySmall"
app:drawableStartCompat="@drawable/ic_tap" />
<com.google.android.material.materialswitch.MaterialSwitch <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_start" android:id="@+id/switch_start"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall" android:layout_height="?android:listPreferredItemHeightSmall"
android:layout_marginTop="@dimen/margin_normal" android:layout_marginTop="@dimen/margin_small"
android:checked="true" android:checked="true"
android:drawablePadding="?android:listPreferredItemPaddingStart" android:drawablePadding="?android:listPreferredItemPaddingStart"
android:ellipsize="end" android:ellipsize="end"

View File

@@ -75,6 +75,32 @@
app:layout_constraintStart_toEndOf="@id/barrier_table" app:layout_constraintStart_toEndOf="@id/barrier_table"
tools:text="Author name" /> tools:text="Author name" />
<TextView
android:id="@+id/textView_translation_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:singleLine="true"
android:text="@string/translation"
android:textAppearance="?textAppearanceTitleSmall"
app:layout_constraintStart_toStartOf="@id/card_details"
app:layout_constraintTop_toBottomOf="@id/textView_author_label" />
<TextView
android:id="@+id/textView_translation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginEnd="@dimen/margin_normal"
android:singleLine="true"
android:textAppearance="?textAppearanceBodyMedium"
app:layout_constraintBaseline_toBaselineOf="@id/textView_translation_label"
app:layout_constraintEnd_toEndOf="@id/card_details"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/barrier_table"
tools:text="English" />
<TextView <TextView
android:id="@+id/textView_rating_label" android:id="@+id/textView_rating_label"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -85,7 +111,7 @@
android:text="@string/rating" android:text="@string/rating"
android:textAppearance="?textAppearanceTitleSmall" android:textAppearance="?textAppearanceTitleSmall"
app:layout_constraintStart_toStartOf="@id/card_details" app:layout_constraintStart_toStartOf="@id/card_details"
app:layout_constraintTop_toBottomOf="@id/textView_author_label" /> app:layout_constraintTop_toBottomOf="@id/textView_translation_label" />
<RatingBar <RatingBar
android:id="@+id/ratingBar_rating" android:id="@+id/ratingBar_rating"
@@ -230,6 +256,6 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:barrierDirection="end" app:barrierDirection="end"
app:constraint_referenced_ids="textView_source_label,textView_author_label,textView_rating_label,textView_state_label,textView_progress_label,textView_chapters_label,textView_local_label" /> app:constraint_referenced_ids="textView_source_label,textView_author_label,textView_rating_label,textView_state_label,textView_progress_label,textView_chapters_label,textView_local_label,textView_translation_label" />
</merge> </merge>

View File

@@ -12,4 +12,9 @@
android:icon="@drawable/ic_delete" android:icon="@drawable/ic_delete"
android:title="@string/remove_from_history" /> android:title="@string/remove_from_history" />
<item
android:id="@+id/action_download"
android:icon="@drawable/ic_download"
android:title="@string/download" />
</menu> </menu>

View File

@@ -772,4 +772,6 @@
<string name="author">Author</string> <string name="author">Author</string>
<string name="rating">Rating</string> <string name="rating">Rating</string>
<string name="source">Source</string> <string name="source">Source</string>
<string name="translation">Translation</string>
<string name="chapters_time_pattern" translatable="false">%1$s (%2$s)</string>
</resources> </resources>