Merge pull request #5 from tachiyomiorg/master

This commit is contained in:
jmir1 2021-04-26 00:47:44 +02:00 committed by GitHub
commit 9c812e3d54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 168 additions and 159 deletions

View file

@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Autoclose issues
uses: arkon/issue-closer-action@v3.0
uses: arkon/issue-closer-action@v3.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
rules: |

View file

@ -52,6 +52,9 @@ open class App : Application(), LifecycleObserver {
LocaleHelper.updateConfiguration(this, resources.configuration)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
// Reset Incognito Mode on relaunch
preferences.incognitoMode().set(false)
}
override fun attachBaseContext(base: Context) {

View file

@ -81,15 +81,14 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
private fun showPopupMenu(view: View) {
view.popupMenu(
R.menu.download_single,
{
menuRes = R.menu.download_single,
initMenu = {
findItem(R.id.move_to_top).isVisible = bindingAdapterPosition != 0
findItem(R.id.move_to_bottom).isVisible =
bindingAdapterPosition != adapter.itemCount - 1
},
{
onMenuItemClick = {
adapter.downloadItemListener.onMenuItemClick(bindingAdapterPosition, this)
true
}
)
}

View file

@ -727,8 +727,7 @@ class MangaController :
fun onChapterDownloadUpdate(download: Download) {
chaptersAdapter?.currentItems?.find { it.id == download.chapter.id }?.let {
chaptersAdapter?.updateItem(it)
chaptersAdapter?.notifyDataSetChanged()
chaptersAdapter?.updateItem(it, it.status)
}
}

View file

@ -6,42 +6,38 @@ import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.view.isVisible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.databinding.ChapterDownloadViewBinding
import eu.kanade.tachiyomi.util.view.setVectorCompat
class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
private val binding: ChapterDownloadViewBinding
private val binding: ChapterDownloadViewBinding =
ChapterDownloadViewBinding.inflate(LayoutInflater.from(context), this, false)
private var state = Download.State.NOT_DOWNLOADED
private var progress = 0
private var downloadIconAnimator: ObjectAnimator? = null
private var isAnimating = false
init {
binding = ChapterDownloadViewBinding.inflate(LayoutInflater.from(context), this, false)
addView(binding.root)
}
fun setState(state: Download.State, progress: Int = 0) {
val isDirty = this.state.value != state.value || this.progress != progress
this.state = state
this.progress = progress
if (isDirty) {
updateLayout()
updateLayout(state, progress)
}
}
private fun updateLayout() {
binding.downloadIconBorder.isVisible = state == Download.State.NOT_DOWNLOADED
binding.downloadIcon.isVisible = state == Download.State.NOT_DOWNLOADED || state == Download.State.DOWNLOADING
if (state == Download.State.DOWNLOADING) {
if (!isAnimating) {
private fun updateLayout(state: Download.State, progress: Int) {
binding.downloadIcon.isVisible = state == Download.State.NOT_DOWNLOADED ||
state == Download.State.DOWNLOADING || state == Download.State.QUEUE
if (state == Download.State.DOWNLOADING || state == Download.State.QUEUE) {
if (downloadIconAnimator == null) {
downloadIconAnimator =
ObjectAnimator.ofFloat(binding.downloadIcon, "alpha", 1f, 0f).apply {
duration = 1000
@ -49,22 +45,36 @@ class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: Att
repeatMode = ObjectAnimator.REVERSE
}
downloadIconAnimator?.start()
isAnimating = true
}
} else {
downloadIconAnimator?.currentPlayTime = System.currentTimeMillis() % 2000
} else if (downloadIconAnimator != null) {
downloadIconAnimator?.cancel()
downloadIconAnimator = null
binding.downloadIcon.alpha = 1f
isAnimating = false
}
binding.downloadQueued.isVisible = state == Download.State.QUEUE
binding.downloadProgress.isVisible = state == Download.State.DOWNLOADING ||
(state == Download.State.QUEUE && progress > 0)
binding.downloadProgress.progress = progress
state == Download.State.NOT_DOWNLOADED || state == Download.State.QUEUE
if (state == Download.State.DOWNLOADING) {
binding.downloadProgress.setProgressCompat(progress, true)
} else {
binding.downloadProgress.setProgressCompat(100, true)
}
binding.downloadedIcon.isVisible = state == Download.State.DOWNLOADED
binding.downloadStatusIcon.apply {
if (state == Download.State.DOWNLOADED || state == Download.State.ERROR) {
isVisible = true
if (state == Download.State.DOWNLOADED) {
setVectorCompat(R.drawable.ic_check_circle_24dp, android.R.attr.textColorPrimary)
} else {
setVectorCompat(R.drawable.ic_error_outline_24dp, R.attr.colorError)
}
} else {
isVisible = false
}
}
binding.errorIcon.isVisible = state == Download.State.ERROR
this.state = state
this.progress = progress
}
}

View file

@ -51,16 +51,12 @@ class ChaptersSettingsSheet(
private fun showPopupMenu(view: View) {
view.popupMenu(
R.menu.default_chapter_filter,
{
},
{
when (this.itemId) {
menuRes = R.menu.default_chapter_filter,
onMenuItemClick = {
when (itemId) {
R.id.set_as_default -> {
SetChapterSettingsDialog(presenter.manga).showDialog(router)
true
}
else -> true
}
}
)

View file

@ -29,7 +29,6 @@ open class BaseChapterHolder(
},
onMenuItemClick = {
adapter.clickListener.deleteChapter(position)
true
}
)
}

View file

@ -58,6 +58,7 @@ import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.defaultBar
import eu.kanade.tachiyomi.util.view.hideBar
import eu.kanade.tachiyomi.util.view.isDefaultBar
import eu.kanade.tachiyomi.util.view.popupMenu
import eu.kanade.tachiyomi.util.view.setTooltip
import eu.kanade.tachiyomi.util.view.showBar
import eu.kanade.tachiyomi.widget.SimpleAnimationListener
@ -356,13 +357,18 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
setTooltip(R.string.viewer)
setOnClickListener {
val newReadingMode =
ReadingModeType.getNextReadingMode(presenter.getMangaViewer(resolveDefault = false))
presenter.setMangaViewer(newReadingMode.prefValue)
popupMenu(
items = ReadingModeType.values().map { it.prefValue to it.stringRes },
selectedItemId = presenter.getMangaViewer(resolveDefault = false),
) {
val newReadingMode = ReadingModeType.fromPreference(itemId)
menuToggleToast?.cancel()
if (!preferences.showReadingMode()) {
menuToggleToast = toast(newReadingMode.stringRes)
presenter.setMangaViewer(newReadingMode.prefValue)
menuToggleToast?.cancel()
if (!preferences.showReadingMode()) {
menuToggleToast = toast(newReadingMode.stringRes)
}
}
}
}
@ -372,13 +378,18 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
setTooltip(R.string.pref_rotation_type)
setOnClickListener {
val newOrientation = OrientationType.getNextOrientation(preferences.rotation().get())
popupMenu(
items = OrientationType.values().map { it.prefValue to it.stringRes },
selectedItemId = preferences.rotation().get(),
) {
val newOrientation = OrientationType.fromPreference(itemId)
preferences.rotation().set(newOrientation.prefValue)
setOrientation(newOrientation.flag)
preferences.rotation().set(newOrientation.prefValue)
setOrientation(newOrientation.flag)
menuToggleToast?.cancel()
menuToggleToast = toast(newOrientation.stringRes)
menuToggleToast?.cancel()
menuToggleToast = toast(newOrientation.stringRes)
}
}
}
preferences.rotation().asImmediateFlow { updateRotationShortcut(it) }

View file

@ -4,7 +4,6 @@ import android.content.pm.ActivityInfo
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.lang.next
enum class OrientationType(val prefValue: Int, val flag: Int, @StringRes val stringRes: Int, @DrawableRes val iconRes: Int) {
FREE(1, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, R.string.rotation_free, R.drawable.ic_screen_rotation_24dp),
@ -17,10 +16,5 @@ enum class OrientationType(val prefValue: Int, val flag: Int, @StringRes val str
companion object {
fun fromPreference(preference: Int): OrientationType =
values().find { it.prefValue == preference } ?: FREE
fun getNextOrientation(preference: Int): OrientationType {
val current = fromPreference(preference)
return current.next()
}
}
}

View file

@ -22,7 +22,7 @@ class ReaderSettingsSheet(
init {
val sheetBehavior = BottomSheetBehavior.from(binding.root.parent as ViewGroup)
sheetBehavior.isFitToContents = false
sheetBehavior.halfExpandedRatio = 0.5f
sheetBehavior.halfExpandedRatio = 0.25f
val filterTabIndex = getTabViews().indexOf(colorFilterSettings)
binding.tabs.addOnTabSelectedListener(object : SimpleTabSelectedListener() {
@ -36,11 +36,6 @@ class ReaderSettingsSheet(
if (activity.menuVisible != !isFilterTab) {
activity.setMenuVisibility(!isFilterTab)
}
// Partially collapse the sheet for better preview
if (isFilterTab) {
sheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
}
}
})

View file

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.reader.setting
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.lang.next
enum class ReadingModeType(val prefValue: Int, @StringRes val stringRes: Int, @DrawableRes val iconRes: Int) {
DEFAULT(0, R.string.default_viewer, R.drawable.ic_reader_default_24dp),
@ -17,11 +16,6 @@ enum class ReadingModeType(val prefValue: Int, @StringRes val stringRes: Int, @D
companion object {
fun fromPreference(preference: Int): ReadingModeType = values().find { it.prefValue == preference } ?: DEFAULT
fun getNextReadingMode(preference: Int): ReadingModeType {
val current = fromPreference(preference)
return current.next()
}
fun isPagerType(preference: Int): Boolean {
val mode = fromPreference(preference)
return mode == LEFT_TO_RIGHT || mode == RIGHT_TO_LEFT || mode == VERTICAL

View file

@ -235,16 +235,13 @@ class PagerPageHolder(
readImageHeaderSubscription = Observable
.fromCallable {
val stream = streamFn().buffered(16)
openStream = stream
openStream = process(stream)
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (viewer.config.dualPageSplit) {
openStream = processDualPageSplit(openStream!!)
}
if (!isAnimated) {
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
} else {
@ -257,21 +254,31 @@ class PagerPageHolder(
.subscribe({}, {})
}
private fun processDualPageSplit(openStream: InputStream): InputStream {
var inputStream = openStream
val (isDoublePage, stream) = when (page) {
is InsertPage -> Pair(true, inputStream)
else -> ImageUtil.isDoublePage(inputStream)
private fun process(imageStream: InputStream): InputStream {
if (!viewer.config.dualPageSplit) {
return imageStream
}
inputStream = stream
if (!isDoublePage) return inputStream
if (page is InsertPage) {
return splitInHalf(imageStream)
}
val isDoublePage = ImageUtil.isDoublePage(imageStream)
if (!isDoublePage) {
return imageStream
}
onPageSplit()
return splitInHalf(imageStream)
}
private fun splitInHalf(imageStream: InputStream): InputStream {
var side = when {
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
(viewer is R2LPagerViewer || viewer is VerticalPagerViewer) && page is InsertPage -> ImageUtil.Side.LEFT
viewer !is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT
viewer is L2RPagerViewer && page !is InsertPage -> ImageUtil.Side.LEFT
(viewer is R2LPagerViewer || viewer is VerticalPagerViewer) && page !is InsertPage -> ImageUtil.Side.RIGHT
viewer !is L2RPagerViewer && page !is InsertPage -> ImageUtil.Side.RIGHT
else -> error("We should choose a side!")
}
@ -282,11 +289,7 @@ class PagerPageHolder(
}
}
if (page !is InsertPage) {
onPageSplit()
}
return ImageUtil.splitInHalf(inputStream, side)
return ImageUtil.splitInHalf(imageStream, side)
}
private fun onPageSplit() {

View file

@ -385,7 +385,10 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
}
fun onPageSplit(currentPage: ReaderPage, newPage: InsertPage) {
adapter.onPageSplit(currentPage, newPage, this::class.java)
activity.runOnUiThread {
// Need to insert on UI thread else images will go blank
adapter.onPageSplit(currentPage, newPage, this::class.java)
}
}
private fun cleanupPageSplit() {

View file

@ -281,22 +281,13 @@ class WebtoonPageHolder(
readImageHeaderSubscription = Observable
.fromCallable {
val stream = streamFn().buffered(16)
openStream = stream
openStream = process(stream)
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (viewer.config.dualPageSplit) {
val (isDoublePage, stream) = ImageUtil.isDoublePage(openStream!!)
openStream = if (!isDoublePage) {
stream
} else {
val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
ImageUtil.splitAndMerge(stream, upperSide)
}
}
if (!isAnimated) {
val subsamplingView = initSubsamplingImageView()
subsamplingView.isVisible = true
@ -315,6 +306,20 @@ class WebtoonPageHolder(
addSubscription(readImageHeaderSubscription)
}
private fun process(imageStream: InputStream): InputStream {
if (!viewer.config.dualPageSplit) {
return imageStream
}
val isDoublePage = ImageUtil.isDoublePage(imageStream)
if (!isDoublePage) {
return imageStream
}
val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
return ImageUtil.splitAndMerge(imageStream, upperSide)
}
/**
* Called when the page has an error.
*/

View file

@ -242,8 +242,7 @@ class UpdatesController :
adapter?.currentItems
?.filterIsInstance<UpdatesItem>()
?.find { it.chapter.id == download.chapter.id }?.let {
adapter?.updateItem(it)
adapter?.notifyDataSetChanged()
adapter?.updateItem(it, it.status)
}
}

View file

@ -1,7 +0,0 @@
package eu.kanade.tachiyomi.util.lang
inline fun <reified T : Enum<T>> T.next(): T {
val values = enumValues<T>()
val nextOrdinal = (ordinal + 1) % values.size
return values[nextOrdinal]
}

View file

@ -77,15 +77,20 @@ object ImageUtil {
}
/**
* Check whether the image is a double image (width > height), return the result and original stream
* Check whether the image is a double-page spread
* @return true if the width is greater than the height
*/
fun isDoublePage(imageStream: InputStream): Pair<Boolean, InputStream> {
fun isDoublePage(imageStream: InputStream): Boolean {
imageStream.mark(imageStream.available() + 1)
val imageBytes = imageStream.readBytes()
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
return Pair(options.outWidth > options.outHeight, ByteArrayInputStream(imageBytes))
imageStream.reset()
return options.outWidth > options.outHeight
}
/**

View file

@ -2,6 +2,7 @@
package eu.kanade.tachiyomi.util.view
import android.annotation.SuppressLint
import android.graphics.Point
import android.view.Gravity
import android.view.Menu
@ -9,14 +10,18 @@ import android.view.MenuItem
import android.view.View
import androidx.annotation.MenuRes
import androidx.annotation.StringRes
import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu
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.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.snackbar.Snackbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.getResourceColor
/**
* Returns coordinates of view.
@ -63,7 +68,7 @@ inline fun View.setTooltip(@StringRes stringRes: Int) {
inline fun View.popupMenu(
@MenuRes menuRes: Int,
noinline initMenu: (Menu.() -> Unit)? = null,
noinline onMenuItemClick: MenuItem.() -> Boolean
noinline onMenuItemClick: MenuItem.() -> Unit
): PopupMenu {
val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
popup.menuInflater.inflate(menuRes, popup.menu)
@ -71,7 +76,50 @@ inline fun View.popupMenu(
if (initMenu != null) {
popup.menu.initMenu()
}
popup.setOnMenuItemClickListener { it.onMenuItemClick() }
popup.setOnMenuItemClickListener {
it.onMenuItemClick()
true
}
popup.show()
return popup
}
/**
* Shows a popup menu on top of this view.
*
* @param items menu item names to inflate the menu with. List of itemId to stringRes pairs.
* @param selectedItemId optionally show a checkmark beside an item with this itemId.
* @param onMenuItemClick function to execute when a menu item is clicked.
*/
@SuppressLint("RestrictedApi")
inline fun View.popupMenu(
items: List<Pair<Int, Int>>,
selectedItemId: Int? = null,
noinline onMenuItemClick: MenuItem.() -> Unit
): PopupMenu {
val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
items.forEach { (id, stringRes) ->
popup.menu.add(0, id, 0, stringRes)
}
if (selectedItemId != null) {
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)
val emptyIcon = ContextCompat.getDrawable(context, R.drawable.ic_blank_24dp)
popup.menu.forEach { item ->
item.icon = when (item.itemId) {
selectedItemId -> ContextCompat.getDrawable(context, R.drawable.ic_check_24dp)?.mutate()?.apply {
setTint(context.getResourceColor(android.R.attr.textColorPrimary))
}
else -> emptyIcon
}
}
}
popup.setOnMenuItemClickListener {
it.onMenuItemClick()
true
}
popup.show()
return popup

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
android:thicknessRatio="2">
<solid android:color="@android:color/transparent" />
<size
android:width="25dp"
android:height="25dp" />
<stroke
android:width="2dp"
android:color="?colorAccent" />
</shape>

View file

@ -7,16 +7,6 @@
android:padding="8dp"
android:background="?selectableItemBackgroundBorderless">
<ImageView
android:id="@+id/download_icon_border"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="2dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/border_circle"
app:tint="?android:attr/textColorHint"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/download_icon"
android:layout_width="match_parent"
@ -32,42 +22,17 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="1dp"
android:visibility="gone"
app:indicatorColor="?android:attr/textColorHint"
app:indicatorInset="0dp"
app:indicatorSize="24dp"
app:trackThickness="2dp" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/download_queued"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:indeterminate="true"
android:padding="1dp"
android:visibility="gone"
android:progress="100"
app:indicatorColor="?android:attr/textColorHint"
app:indicatorInset="0dp"
app:indicatorSize="24dp"
app:trackThickness="2dp" />
<ImageView
android:id="@+id/downloaded_icon"
android:id="@+id/download_status_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:visibility="gone"
app:srcCompat="@drawable/ic_check_circle_24dp"
app:tint="?android:attr/textColorPrimary"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/error_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:visibility="gone"
app:srcCompat="@drawable/ic_error_outline_24dp"
app:tint="?attr/colorError"
tools:ignore="ContentDescription" />
</FrameLayout>