Merge branch 'devel' of https://github.com/nv95/Kotatsu into devel

This commit is contained in:
Koitharu
2020-04-10 17:14:40 +03:00
24 changed files with 87 additions and 137 deletions

8
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel>
<module name="Kotatsu.app" target="1.8" />
</bytecodeTargetLevel>
</component>
</project>

1
.idea/gradle.xml generated
View File

@@ -15,6 +15,7 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

View File

@@ -81,7 +81,7 @@ dependencies {
implementation 'com.github.moxy-community:moxy-ktx:2.1.2'
kapt 'com.github.moxy-community:moxy-compiler:2.1.2'
implementation 'com.squareup.okhttp3:okhttp:4.4.1'
implementation 'com.squareup.okhttp3:okhttp:4.5.0'
implementation 'com.squareup.okio:okio:2.5.0'
implementation 'org.jsoup:jsoup:1.13.1'

View File

@@ -5,7 +5,7 @@ import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.utils.ext.sub
import org.koitharu.kotatsu.utils.ext.takeIfReadable
import org.koitharu.kotatsu.utils.ext.toFileName
import org.koitharu.kotatsu.utils.ext.toFileNameSafe
import java.io.File
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
@@ -91,7 +91,7 @@ class MangaZip(val file: File) {
const val INDEX_ENTRY = "index.json"
fun findInDir(root: File, manga: Manga): MangaZip {
val name = manga.title.toFileName() + ".cbz"
val name = manga.title.toFileNameSafe() + ".cbz"
val file = File(root, name)
return MangaZip(file)
}

View File

@@ -25,10 +25,9 @@ import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.ui.browser.BrowserActivity
import org.koitharu.kotatsu.ui.common.BaseActivity
import org.koitharu.kotatsu.ui.download.DownloadService
import org.koitharu.kotatsu.utils.MangaShortcut
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ShortcutUtils
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.showDialog
class MangaDetailsActivity : BaseActivity(), MangaDetailsView {
@@ -119,14 +118,14 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView {
}
R.id.action_delete -> {
manga?.let { m ->
showDialog {
setTitle(R.string.delete_manga)
setMessage(getString(R.string.text_delete_local_manga, m.title))
setPositiveButton(R.string.delete) { _, _ ->
AlertDialog.Builder(this)
.setTitle(R.string.delete_manga)
.setMessage(getString(R.string.text_delete_local_manga, m.title))
.setPositiveButton(R.string.delete) { _, _ ->
presenter.deleteLocal(m)
}
setNegativeButton(android.R.string.cancel, null)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
true
}
@@ -156,7 +155,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView {
R.id.action_shortcut -> {
manga?.let {
lifecycleScope.launch {
if (!ShortcutUtils.requestPinShortcut(this@MangaDetailsActivity, manga)) {
if (!MangaShortcut(it).requestPinShortcut(this@MangaDetailsActivity)) {
Snackbar.make(
pager,
R.string.operation_not_supported,

View File

@@ -14,7 +14,7 @@ import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.domain.history.HistoryRepository
import org.koitharu.kotatsu.ui.common.BasePresenter
import org.koitharu.kotatsu.ui.main.list.MangaListView
import org.koitharu.kotatsu.utils.ShortcutUtils
import org.koitharu.kotatsu.utils.MangaShortcut
@InjectViewState
class HistoryListPresenter : BasePresenter<MangaListView<MangaHistory>>() {
@@ -62,7 +62,7 @@ class HistoryListPresenter : BasePresenter<MangaListView<MangaHistory>>() {
repository.clear()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutUtils.clearAppShortcuts(get())
MangaShortcut.clearAppShortcuts(get())
}
viewState.onListChanged(emptyList())
} catch (_: CancellationException) {
@@ -84,7 +84,7 @@ class HistoryListPresenter : BasePresenter<MangaListView<MangaHistory>>() {
repository.delete(manga)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutUtils.removeAppShortcut(get(), manga)
MangaShortcut(manga).removeAppShortcut(get())
}
viewState.onItemRemoved(manga)
} catch (_: CancellationException) {

View File

@@ -6,6 +6,7 @@ import android.content.Intent
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_list.*
import moxy.ktx.moxyPresenter
@@ -14,7 +15,6 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.ui.main.list.MangaListFragment
import org.koitharu.kotatsu.utils.ext.ellipsize
import org.koitharu.kotatsu.utils.ext.showDialog
import java.io.File
class LocalListFragment : MangaListFragment<File>() {
@@ -81,14 +81,14 @@ class LocalListFragment : MangaListFragment<File>() {
override fun onPopupMenuItemSelected(item: MenuItem, data: Manga): Boolean {
return when (item.itemId) {
R.id.action_delete -> {
context?.showDialog {
setTitle(R.string.delete_manga)
setMessage(getString(R.string.text_delete_local_manga, data.title))
setPositiveButton(R.string.delete) { _, _ ->
AlertDialog.Builder(context ?: return false)
.setTitle(R.string.delete_manga)
.setMessage(getString(R.string.text_delete_local_manga, data.title))
.setPositiveButton(R.string.delete) { _, _ ->
presenter.delete(data)
}
setNegativeButton(android.R.string.cancel, null)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
true
}
else -> super.onPopupMenuItemSelected(item, data)

View File

@@ -19,8 +19,8 @@ import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.domain.history.HistoryRepository
import org.koitharu.kotatsu.ui.common.BasePresenter
import org.koitharu.kotatsu.ui.main.list.MangaListView
import org.koitharu.kotatsu.utils.MangaShortcut
import org.koitharu.kotatsu.utils.MediaStoreCompat
import org.koitharu.kotatsu.utils.ShortcutUtils
import org.koitharu.kotatsu.utils.ext.safe
import org.koitharu.kotatsu.utils.ext.sub
import java.io.File
@@ -98,7 +98,7 @@ class LocalListPresenter : BasePresenter<MangaListView<File>>() {
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutUtils.removeAppShortcut(get(), manga)
MangaShortcut(manga).removeAppShortcut(get())
}
viewState.onItemRemoved(manga)
} catch (e: CancellationException) {

View File

@@ -9,6 +9,7 @@ import android.os.Build
import android.os.Bundle
import android.view.*
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.core.view.updatePadding
@@ -34,8 +35,8 @@ import org.koitharu.kotatsu.ui.reader.thumbnails.OnPageSelectListener
import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet
import org.koitharu.kotatsu.ui.reader.wetoon.WebtoonReaderFragment
import org.koitharu.kotatsu.utils.GridTouchHelper
import org.koitharu.kotatsu.utils.MangaShortcut
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ShortcutUtils
import org.koitharu.kotatsu.utils.anim.Motion
import org.koitharu.kotatsu.utils.ext.*
@@ -91,7 +92,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
GlobalScope.launch {
safe {
ShortcutUtils.addAppShortcut(applicationContext, state.manga)
MangaShortcut(state.manga).addAppShortcut(applicationContext)
}
}
}
@@ -207,16 +208,16 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
}
override fun onError(e: Throwable) {
showDialog {
setTitle(R.string.error_occurred)
setMessage(e.message)
setPositiveButton(R.string.close, null)
if (reader?.hasItems != true) {
setOnDismissListener {
finish()
}
val dialog = AlertDialog.Builder(this)
.setTitle(R.string.error_occurred)
.setMessage(e.message)
.setPositiveButton(R.string.close, null)
if (reader?.hasItems != true) {
dialog.setOnDismissListener {
finish()
}
}
dialog.show()
}
override fun onGridTouch(area: Int) {
@@ -225,11 +226,13 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
setUiIsVisible(!appbar_top.isVisible)
}
GridTouchHelper.AREA_TOP,
GridTouchHelper.AREA_LEFT -> if (isTapSwitchEnabled) {
GridTouchHelper.AREA_LEFT,
-> if (isTapSwitchEnabled) {
reader?.switchPageBy(-1)
}
GridTouchHelper.AREA_BOTTOM,
GridTouchHelper.AREA_RIGHT -> if (isTapSwitchEnabled) {
GridTouchHelper.AREA_RIGHT,
-> if (isTapSwitchEnabled) {
reader?.switchPageBy(1)
}
}
@@ -267,13 +270,15 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
KeyEvent.KEYCODE_SPACE,
KeyEvent.KEYCODE_PAGE_DOWN,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_RIGHT -> {
KeyEvent.KEYCODE_DPAD_RIGHT,
-> {
reader?.switchPageBy(1)
true
}
KeyEvent.KEYCODE_PAGE_UP,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_LEFT -> {
KeyEvent.KEYCODE_DPAD_LEFT,
-> {
reader?.switchPageBy(-1)
true
}

View File

@@ -22,25 +22,18 @@ import org.koitharu.kotatsu.domain.MangaDataRepository
import org.koitharu.kotatsu.ui.details.MangaDetailsActivity
import org.koitharu.kotatsu.utils.ext.safe
object ShortcutUtils {
class MangaShortcut(private val manga: Manga) {
suspend fun requestPinShortcut(context: Context, manga: Manga?): Boolean {
return manga != null && ShortcutManagerCompat.requestPinShortcut(
context,
buildShortcutInfo(context, manga).build(),
null
)
}
private val shortcutId = manga.id.toString()
@RequiresApi(Build.VERSION_CODES.N_MR1)
suspend fun addAppShortcut(context: Context, manga: Manga) {
val id = manga.id.toString()
val builder = buildShortcutInfo(context, manga)
suspend fun addAppShortcut(context: Context) {
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
val limit = manager.maxShortcutCountPerActivity
val builder = buildShortcutInfo(context, manga)
val shortcuts = manager.dynamicShortcuts
for (shortcut in shortcuts) {
if (shortcut.id == id) {
if (shortcut.id == shortcutId) {
builder.setRank(shortcut.rank + 1)
manager.updateShortcuts(listOf(builder.build().toShortcutInfo()))
return
@@ -53,22 +46,23 @@ object ShortcutUtils {
manager.addDynamicShortcuts(listOf(builder.build().toShortcutInfo()))
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
fun removeAppShortcut(context: Context, manga: Manga) {
val id = manga.id.toString()
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
manager.removeDynamicShortcuts(listOf(id))
suspend fun requestPinShortcut(context: Context): Boolean {
return ShortcutManagerCompat.requestPinShortcut(
context,
buildShortcutInfo(context, manga).build(),
null
)
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
fun clearAppShortcuts(context: Context) {
fun removeAppShortcut(context: Context) {
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
manager.removeAllDynamicShortcuts()
manager.removeDynamicShortcuts(listOf(shortcutId))
}
private suspend fun buildShortcutInfo(
context: Context,
manga: Manga
manga: Manga,
): ShortcutInfoCompat.Builder {
val icon = safe {
val size = getIconSize(context)
@@ -77,12 +71,7 @@ object ShortcutUtils {
size(size)
scale(Scale.FILL)
}.toBitmap()
ThumbnailUtils.extractThumbnail(
bmp,
size.width,
size.height,
0
)
ThumbnailUtils.extractThumbnail(bmp, size.width, size.height, 0)
}
}
MangaDataRepository().storeManga(manga)
@@ -109,4 +98,13 @@ object ShortcutUtils {
}
}
}
companion object {
@RequiresApi(Build.VERSION_CODES.N_MR1)
fun clearAppShortcuts(context: Context) {
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
manager.removeAllDynamicShortcuts()
}
}
}

View File

@@ -1,9 +1,7 @@
package org.koitharu.kotatsu.utils
import android.content.Context
import android.graphics.Color
import android.view.View
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.R
@@ -14,16 +12,6 @@ import kotlin.math.roundToInt
object UiUtils {
@JvmStatic
@ColorInt
fun invertColor(@ColorInt color: Int): Int {
val red = Color.red(color)
val green = Color.green(color)
val blue = Color.blue(color)
val alpha = Color.alpha(color)
return Color.argb(alpha, 255 - red, 255 - green, 255 - blue)
}
@JvmStatic
fun resolveGridSpanCount(context: Context, width: Int = 0): Int {
val scaleFactor = AppSettings(context).gridSize / 100f

View File

@@ -1,11 +0,0 @@
package org.koitharu.kotatsu.utils.ext
import android.content.Context
import androidx.appcompat.app.AlertDialog
@Deprecated("Useless")
fun Context.showDialog(block: AlertDialog.Builder.() -> Unit): AlertDialog {
return AlertDialog.Builder(this)
.apply(block)
.show()
}

View File

@@ -5,7 +5,6 @@ import okhttp3.internal.closeQuietly
import org.json.JSONObject
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
fun Response.parseHtml(): Document {
try {
@@ -28,6 +27,4 @@ fun Response.parseJson(): JSONObject {
} finally {
closeQuietly()
}
}
fun Element.firstChild(): Element? = children().first()
}

View File

@@ -4,8 +4,6 @@ import java.text.DecimalFormat
import java.text.NumberFormat
import java.util.*
fun Number?.asBoolean() = (this?.toInt() ?: 0) > 0
fun Number.format(decimals: Int = 0, decPoint: Char = '.', thousandsSep: Char? = ' '): String {
val formatter = NumberFormat.getInstance(Locale.US) as DecimalFormat
val symbols = formatter.decimalFormatSymbols

View File

@@ -57,7 +57,7 @@ fun String.transliterate(skipMissing: Boolean): String {
}
}
fun String.toFileName() = this.transliterate(false)
fun String.toFileNameSafe() = this.transliterate(false)
.replace(Regex("[^a-z0-9_\\-]", setOf(RegexOption.IGNORE_CASE)), " ")
.replace(Regex("\\s+"), "_")

View File

@@ -17,6 +17,7 @@ fun Context.getThemeDrawable(@AttrRes resId: Int) = obtainStyledAttributes(intAr
}
@ColorInt
fun Context.getThemeColor(@AttrRes resId: Int, @ColorInt default: Int = Color.TRANSPARENT) = obtainStyledAttributes(intArrayOf(resId)).use {
it.getColor(0, default)
}
fun Context.getThemeColor(@AttrRes resId: Int, @ColorInt default: Int = Color.TRANSPARENT) =
obtainStyledAttributes(intArrayOf(resId)).use {
it.getColor(0, default)
}

View File

@@ -2,11 +2,9 @@ package org.koitharu.kotatsu.utils.ext
import android.app.Activity
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.LayoutRes
import androidx.annotation.MenuRes
@@ -23,7 +21,6 @@ import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import org.koitharu.kotatsu.ui.common.ChipsFactory
import kotlin.math.roundToInt
fun View.hideKeyboard() {
val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
@@ -35,14 +32,9 @@ fun View.showKeyboard() {
imm.showSoftInput(this, 0)
}
val EditText.plainText
get() = text?.toString().orEmpty()
inline fun <reified T : View> ViewGroup.inflate(@LayoutRes resId: Int) =
LayoutInflater.from(context).inflate(resId, this, false) as T
val TextView.hasText get() = !text.isNullOrEmpty()
fun RecyclerView.lookupSpanSize(callback: (Int) -> Int) {
(layoutManager as? GridLayoutManager)?.spanSizeLookup =
object : GridLayoutManager.SpanSizeLookup() {
@@ -53,20 +45,6 @@ fun RecyclerView.lookupSpanSize(callback: (Int) -> Int) {
val RecyclerView.hasItems: Boolean
get() = (adapter?.itemCount ?: 0) > 0
var TextView.drawableStart: Drawable?
get() = compoundDrawablesRelative[0]
set(value) {
val old = compoundDrawablesRelative
setCompoundDrawablesRelativeWithIntrinsicBounds(value, old[1], old[2], old[3])
}
var TextView.drawableEnd: Drawable?
get() = compoundDrawablesRelative[2]
set(value) {
val old = compoundDrawablesRelative
setCompoundDrawablesRelativeWithIntrinsicBounds(old[0], old[1], value, old[3])
}
var TextView.textAndVisible: CharSequence?
get() = text?.takeIf { visibility == View.VISIBLE }
set(value) {
@@ -106,7 +84,7 @@ fun View.disableFor(timeInMillis: Long) {
fun View.showPopupMenu(
@MenuRes menuRes: Int, onPrepare: ((Menu) -> Unit)? = null,
onItemClick: (MenuItem) -> Boolean
onItemClick: (MenuItem) -> Boolean,
) {
val menu = PopupMenu(context, this)
menu.inflate(menuRes)
@@ -221,11 +199,6 @@ fun ViewPager2.callOnPageChaneListeners() {
}
}
@Deprecated("")
fun LinearLayoutManager.findMiddleVisibleItemPosition(): Int {
return ((findFirstVisibleItemPosition() + findLastVisibleItemPosition()) / 2.0).roundToInt()
}
fun RecyclerView.findCenterViewPosition(): Int {
val centerX = width / 2f
val centerY = height / 2f

View File

@@ -2,16 +2,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:scaleX="0.92"
android:scaleY="0.92"
android:translateX="0.96"
android:translateY="0.96">
<path
android:fillColor="#FF000000"
android:pathData="M18,22H6A2,2 0,0 1,4 20V4C4,2.89 4.9,2 6,2H7V9L9.5,7.5L12,9V2H18A2,2 0,0 1,20 4V20A2,2 0,0 1,18 22M14,20H16V18H18V16H16V14H14V16H12V18H14V20Z" />
</group>
</vector>
<path
android:fillColor="#FFFFFF"
android:pathData="M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M20,12L17.5,10.5L15,12V4H20V12Z" />
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 B

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 629 B

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 914 B

After

Width:  |  Height:  |  Size: 938 B

View File

@@ -9,7 +9,7 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0-beta03'
classpath 'com.android.tools.build:gradle:4.1.0-alpha04'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong