Merge branch 'master' into devel

pull/168/head
Koitharu 4 years ago
commit 3c54bdd003
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -44,7 +44,7 @@ body:
label: Kotatsu version
description: You can find your Kotatsu version in **Settings → About**.
placeholder: |
Example: "3.2.1"
Example: "3.2.2"
validations:
required: true
@ -87,7 +87,7 @@ body:
required: true
- label: If this is an issue with a source, I should be opening an issue in the [parsers repository](https://github.com/nv95/kotatsu-parsers/issues/new).
required: true
- label: I have updated the app to version **[3.2.1](https://github.com/nv95/Kotatsu/releases/latest)**.
- label: I have updated the app to version **[3.2.2](https://github.com/nv95/Kotatsu/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true

@ -33,7 +33,7 @@ body:
required: true
- label: If this is an issue with a source, I should be opening an issue in the [parsers repository](https://github.com/nv95/kotatsu-parsers/issues/new).
required: true
- label: I have updated the app to version **[3.2.1](https://github.com/nv95/Kotatsu/releases/latest)**.
- label: I have updated the app to version **[3.2.2](https://github.com/nv95/Kotatsu/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true

1
.gitignore vendored

@ -10,6 +10,7 @@
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea/kotlinScripting.xml
/.idea/deploymentTargetDropDown.xml
.DS_Store
/build
/captures

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_API_S.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-02-19T19:02:37.198775Z" />
</component>
</project>

@ -14,8 +14,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 32
versionCode 405
versionName '3.2.1'
versionCode 406
versionName '3.2.2'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -66,7 +66,7 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation('com.github.nv95:kotatsu-parsers:090ad4b256') {
implementation('com.github.nv95:kotatsu-parsers:b495e5e457') {
exclude group: 'org.json', module: 'json'
}

@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.medianOrNull
import java.io.File
import java.io.InputStream
import java.util.zip.ZipFile
@ -59,6 +60,14 @@ object MangaUtils : KoinComponent {
}
}
suspend fun getImageMimeType(file: File): String? = runInterruptible(Dispatchers.IO) {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(file.path, options)?.recycle()
options.outMimeType
}
private fun getBitmapSize(input: InputStream?): Size {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true

@ -9,11 +9,12 @@ import android.view.ViewGroup.LayoutParams
import androidx.appcompat.app.AppCompatDialog
import androidx.core.view.updateLayoutParams
import androidx.viewbinding.ViewBinding
import com.google.android.material.R as materialR
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.AppBottomSheetDialog
import com.google.android.material.R as materialR
abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
@ -43,7 +44,9 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return if (resources.getBoolean(R.bool.is_tablet)) {
AppCompatDialog(context, R.style.Theme_Kotatsu_Dialog)
} else super.onCreateDialog(savedInstanceState)
} else {
AppBottomSheetDialog(requireContext(), theme)
}
}
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B

@ -0,0 +1,29 @@
package org.koitharu.kotatsu.base.ui.dialog
import android.content.Context
import android.graphics.Color
import android.view.View
import com.google.android.material.bottomsheet.BottomSheetDialog
class AppBottomSheetDialog(context: Context, theme: Int) : BottomSheetDialog(context, theme) {
/**
* https://github.com/material-components/material-components-android/issues/2582
*/
@Suppress("DEPRECATION")
override fun onAttachedToWindow() {
val window = window
val initialSystemUiVisibility = window?.decorView?.systemUiVisibility ?: 0
super.onAttachedToWindow()
if (window != null) {
// If the navigation bar is translucent at all, the BottomSheet should be edge to edge
val drawEdgeToEdge = edgeToEdgeEnabled && Color.alpha(window.navigationBarColor) < 0xFF
if (drawEdgeToEdge) {
// Copied from super.onAttachedToWindow:
val edgeToEdgeFlags = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
// Fix super-class's window flag bug by respecting the intial system UI visibility:
window.decorView.systemUiVisibility = edgeToEdgeFlags or initialSystemUiVisibility
}
}
}
}

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.list.ui.adapter
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.request.Disposable
import coil.size.Scale
import coil.util.CoilUtils
import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
@ -43,6 +44,7 @@ fun mangaGridItemAD(
.fallback(R.drawable.ic_placeholder)
.error(R.drawable.ic_placeholder)
.allowRgb565(true)
.scale(Scale.FILL)
.lifecycle(lifecycleOwner)
.enqueueWith(coil)
badge = itemView.bindBadge(badge, item.counter)

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.list.ui.adapter
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.request.Disposable
import coil.size.Scale
import coil.util.CoilUtils
import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
@ -44,6 +45,7 @@ fun mangaListDetailedItemAD(
.placeholder(R.drawable.ic_placeholder)
.fallback(R.drawable.ic_placeholder)
.error(R.drawable.ic_placeholder)
.scale(Scale.FILL)
.allowRgb565(true)
.lifecycle(lifecycleOwner)
.enqueueWith(coil)

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.list.ui.adapter
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.request.Disposable
import coil.size.Scale
import coil.util.CoilUtils
import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
@ -44,6 +45,7 @@ fun mangaListItemAD(
.placeholder(R.drawable.ic_placeholder)
.fallback(R.drawable.ic_placeholder)
.error(R.drawable.ic_placeholder)
.scale(Scale.FILL)
.allowRgb565(true)
.lifecycle(lifecycleOwner)
.enqueueWith(coil)

@ -27,7 +27,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel
@ -418,20 +417,19 @@ class MainActivity :
}
private fun onFirstStart() {
lifecycleScope.launch(Dispatchers.Default) {
TrackWorker.setup(applicationContext)
SuggestionsWorker.setup(applicationContext)
if (AppUpdateChecker.isUpdateSupported(this@MainActivity)) {
lifecycleScope.launchWhenResumed {
val isUpdateSupported = withContext(Dispatchers.Default) {
TrackWorker.setup(applicationContext)
SuggestionsWorker.setup(applicationContext)
AppUpdateChecker.isUpdateSupported(this@MainActivity)
}
if (isUpdateSupported) {
AppUpdateChecker(this@MainActivity).checkIfNeeded()
}
val settings = get<AppSettings>()
when {
!settings.isSourcesSelected -> withContext(Dispatchers.Main) {
OnboardDialogFragment.showWelcome(supportFragmentManager)
}
settings.newSources.isNotEmpty() -> withContext(Dispatchers.Main) {
NewSourcesDialogFragment.show(supportFragmentManager)
}
!settings.isSourcesSelected -> OnboardDialogFragment.showWelcome(supportFragmentManager)
settings.newSources.isNotEmpty() -> NewSourcesDialogFragment.show(supportFragmentManager)
}
}
}

@ -2,19 +2,26 @@ package org.koitharu.kotatsu.reader.ui
import android.content.Context
import android.net.Uri
import android.webkit.MimeTypeMap
import androidx.activity.result.ActivityResultLauncher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl.Companion.toHttpUrl
import okio.IOException
import org.koitharu.kotatsu.base.domain.MangaUtils
import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
import org.koitharu.kotatsu.reader.domain.PageLoader
import java.io.File
import kotlin.coroutines.Continuation
import kotlin.coroutines.coroutineContext
import kotlin.coroutines.resume
private const val MAX_FILENAME_LENGTH = 10
private const val EXTENSION_FALLBACK = "png"
class PageSaveHelper(
private val cache: PagesCache,
context: Context,
@ -28,22 +35,17 @@ class PageSaveHelper(
page: MangaPage,
saveLauncher: ActivityResultLauncher<String>,
): Uri {
var pageFile = cache[page.url]
var fileName = pageFile?.name
if (fileName == null) {
fileName = pageLoader.getPageUrl(page).toHttpUrl().pathSegments.last()
}
val cc = coroutineContext
val destination = suspendCancellableCoroutine<Uri> { cont ->
continuation = cont
Dispatchers.Main.dispatch(cc) {
saveLauncher.launch(fileName)
val pageUrl = pageLoader.getPageUrl(page)
val pageFile = pageLoader.loadPage(page, force = false)
val proposedName = getProposedFileName(pageUrl, pageFile)
val destination = withContext(Dispatchers.Main) {
suspendCancellableCoroutine<Uri> { cont ->
continuation = cont
saveLauncher.launch(proposedName)
}.also {
continuation = null
}
}
continuation = null
if (pageFile == null) {
pageFile = pageLoader.loadPage(page, force = false)
}
runInterruptible(Dispatchers.IO) {
contentResolver.openOutputStream(destination)?.use { output ->
pageFile.inputStream().use { input ->
@ -57,4 +59,19 @@ class PageSaveHelper(
fun onActivityResult(uri: Uri): Boolean = continuation?.apply {
resume(uri)
} != null
private suspend fun getProposedFileName(url: String, file: File): String {
var name = url.toHttpUrl().pathSegments.last()
var extension = name.substringAfterLast('.', "")
name = name.substringBeforeLast('.')
if (extension.length !in 2..4) {
val mimeType = MangaUtils.getImageMimeType(file)
extension = if (mimeType != null) {
MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: EXTENSION_FALLBACK
} else {
EXTENSION_FALLBACK
}
}
return name.toFileNameSafe().take(MAX_FILENAME_LENGTH) + "." + extension
}
}

@ -8,6 +8,15 @@ import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.annotation.MainThread
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateEncodingException
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
@ -19,15 +28,6 @@ import org.koitharu.kotatsu.core.github.VersionId
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.util.byte2HexFormatted
import org.koitharu.kotatsu.utils.FileSize
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateEncodingException
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
class AppUpdateChecker(private val activity: ComponentActivity) {
@ -61,25 +61,22 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
@MainThread
private fun showUpdateDialog(version: AppVersion) {
val message = buildString {
append(activity.getString(R.string.new_version_s, version.name))
appendLine()
append(activity.getString(R.string.size_s, FileSize.BYTES.format(activity, version.apkSize)))
appendLine()
appendLine()
append(version.description)
}
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.app_update_available)
.setMessage(buildString {
append(activity.getString(R.string.new_version_s, version.name))
appendLine()
append(
activity.getString(
R.string.size_s,
FileSize.BYTES.format(activity, version.apkSize),
)
)
appendLine()
appendLine()
append(version.description)
})
.setMessage(message)
.setPositiveButton(R.string.download) { _, _ ->
activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(version.apkUrl)))
}
.setNegativeButton(R.string.close, null)
.setCancelable(false)
.create()
.show()
}

@ -18,8 +18,10 @@ import org.koitharu.kotatsu.utils.ext.observeNotNull
import org.koitharu.kotatsu.utils.ext.showAllowStateLoss
import org.koitharu.kotatsu.utils.ext.withArgs
class OnboardDialogFragment : AlertDialogFragment<DialogOnboardBinding>(),
OnListItemClickListener<SourceLocale>, DialogInterface.OnClickListener {
class OnboardDialogFragment :
AlertDialogFragment<DialogOnboardBinding>(),
OnListItemClickListener<SourceLocale>,
DialogInterface.OnClickListener {
private val viewModel by viewModel<OnboardViewModel>()
private var isWelcome: Boolean = false

@ -3,7 +3,6 @@ package org.koitharu.kotatsu.settings.onboard
import androidx.collection.ArraySet
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.MutableLiveData
import java.util.*
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.model.MangaSource
@ -12,6 +11,7 @@ import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
import org.koitharu.kotatsu.utils.ext.map
import org.koitharu.kotatsu.utils.ext.mapToSet
import java.util.*
class OnboardViewModel(
private val settings: AppSettings,
@ -55,6 +55,7 @@ class OnboardViewModel(
settings.hiddenSources = allSources.filterNot { x ->
x.locale in selectedLocales
}.mapToSet { x -> x.name }
settings.markKnownSources(settings.newSources)
}
private fun rebuildList() {

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.tracker.ui.adapter
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.request.Disposable
import coil.size.Scale
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
@ -34,6 +35,7 @@ fun feedItemAD(
.fallback(R.drawable.ic_placeholder)
.error(R.drawable.ic_placeholder)
.allowRgb565(true)
.scale(Scale.FILL)
.lifecycle(lifecycleOwner)
.enqueueWith(coil)
binding.textViewTitle.text = item.title

@ -2,15 +2,16 @@ package org.koitharu.kotatsu.utils
import android.view.View
import androidx.appcompat.widget.Toolbar
import com.google.android.material.R as materialR
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.R as materialR
open class BottomSheetToolbarController(
protected val toolbar: Toolbar,
) : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
val isExpanded = newState == BottomSheetBehavior.STATE_EXPANDED && bottomSheet.top <= 0
if (isExpanded) {
toolbar.setNavigationIcon(materialR.drawable.abc_ic_clear_material)
} else {
toolbar.navigationIcon = null

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="ThemeOverlay.Kotatsu.BottomSheetDialog" parent="ThemeOverlay.Material3.DayNight.BottomSheetDialog">
<item name="android:navigationBarColor">@color/navigation_bar_scrim</item>
<item name="android:windowLightNavigationBar">@bool/light_navigation_bar</item>
</style>
</resources>

@ -22,8 +22,8 @@
<!-- Bottom sheet -->
<style name="ThemeOverlay.Kotatsu.BottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
<item name="android:navigationBarColor">?colorSurfaceVariant</item>
<style name="ThemeOverlay.Kotatsu.BottomSheetDialog" parent="ThemeOverlay.Material3.DayNight.BottomSheetDialog">
<item name="android:statusBarColor">@color/dim</item>
</style>
<!-- Widget styles -->

Loading…
Cancel
Save