Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d242acd502 | ||
|
|
d37b44d3f6 | ||
|
|
e4c4d2bbf0 | ||
|
|
040d3e4433 | ||
|
|
b4f93fc0a5 | ||
|
|
c4e7807d18 | ||
|
|
8e55a4d824 |
@@ -14,8 +14,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 32
|
targetSdkVersion 32
|
||||||
versionCode 422
|
versionCode 425
|
||||||
versionName '3.4.10'
|
versionName '3.4.13'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation('com.github.KotatsuApp:kotatsu-parsers:f112a06ab6') {
|
implementation('com.github.KotatsuApp:kotatsu-parsers:551a1d70ae') {
|
||||||
exclude group: 'org.json', module: 'json'
|
exclude group: 'org.json', module: 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ dependencies {
|
|||||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||||
implementation 'androidx.work:work-runtime-ktx:2.7.1'
|
implementation 'androidx.work:work-runtime-ktx:2.7.1'
|
||||||
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
|
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
|
||||||
implementation 'com.google.android.material:material:1.7.0-beta01'
|
implementation 'com.google.android.material:material:1.7.0-rc01'
|
||||||
//noinspection LifecycleAnnotationProcessorWithJava8
|
//noinspection LifecycleAnnotationProcessorWithJava8
|
||||||
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1'
|
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1'
|
||||||
|
|
||||||
@@ -119,8 +119,8 @@ dependencies {
|
|||||||
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
||||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||||
|
|
||||||
implementation 'ch.acra:acra-mail:5.9.5'
|
implementation 'ch.acra:acra-mail:5.9.6'
|
||||||
implementation 'ch.acra:acra-dialog:5.9.5'
|
implementation 'ch.acra:acra-dialog:5.9.6'
|
||||||
|
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
|
||||||
|
|
||||||
|
|||||||
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
@@ -10,4 +10,6 @@
|
|||||||
}
|
}
|
||||||
-keep public class ** extends org.koitharu.kotatsu.base.ui.BaseFragment
|
-keep public class ** extends org.koitharu.kotatsu.base.ui.BaseFragment
|
||||||
-keep class org.koitharu.kotatsu.core.db.entity.* { *; }
|
-keep class org.koitharu.kotatsu.core.db.entity.* { *; }
|
||||||
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
||||||
|
-keep class org.koitharu.kotatsu.core.exceptions.* { *; }
|
||||||
|
-keep class org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment
|
||||||
@@ -25,7 +25,7 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
|||||||
|
|
||||||
override fun onInflateView(
|
override fun onInflateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?
|
container: ViewGroup?,
|
||||||
) = FragmentCloudflareBinding.inflate(inflater, container, false)
|
) = FragmentCloudflareBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
@@ -49,6 +49,7 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
|||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
binding.webView.stopLoading()
|
binding.webView.stopLoading()
|
||||||
|
binding.webView.destroy()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
|||||||
|
|
||||||
override fun onCheckPassed() {
|
override fun onCheckPassed() {
|
||||||
pendingResult.putBoolean(EXTRA_RESULT, true)
|
pendingResult.putBoolean(EXTRA_RESULT, true)
|
||||||
dismiss()
|
dismissAllowingStateLoss()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ import java.io.File
|
|||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
|
||||||
import kotlinx.coroutines.channels.trySendBlocking
|
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||||
import org.koitharu.kotatsu.core.network.DoHProvider
|
import org.koitharu.kotatsu.core.network.DoHProvider
|
||||||
@@ -64,6 +61,9 @@ class AppSettings(context: Context) {
|
|||||||
val readerPageSwitch: Set<String>
|
val readerPageSwitch: Set<String>
|
||||||
get() = prefs.getStringSet(KEY_READER_SWITCHERS, null) ?: setOf(PAGE_SWITCH_TAPS)
|
get() = prefs.getStringSet(KEY_READER_SWITCHERS, null) ?: setOf(PAGE_SWITCH_TAPS)
|
||||||
|
|
||||||
|
val isReaderTapsAdaptive: Boolean
|
||||||
|
get() = !prefs.getBoolean(KEY_READER_TAPS_LTR, false)
|
||||||
|
|
||||||
var isTrafficWarningEnabled: Boolean
|
var isTrafficWarningEnabled: Boolean
|
||||||
get() = prefs.getBoolean(KEY_TRAFFIC_WARNING, true)
|
get() = prefs.getBoolean(KEY_TRAFFIC_WARNING, true)
|
||||||
set(value) = prefs.edit { putBoolean(KEY_TRAFFIC_WARNING, value) }
|
set(value) = prefs.edit { putBoolean(KEY_TRAFFIC_WARNING, value) }
|
||||||
@@ -314,6 +314,7 @@ class AppSettings(context: Context) {
|
|||||||
const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown"
|
const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown"
|
||||||
const val KEY_ALL_FAVOURITES_VISIBLE = "all_favourites_visible"
|
const val KEY_ALL_FAVOURITES_VISIBLE = "all_favourites_visible"
|
||||||
const val KEY_DOH = "doh"
|
const val KEY_DOH = "doh"
|
||||||
|
const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
|
||||||
|
|
||||||
// About
|
// About
|
||||||
const val KEY_APP_UPDATE = "app_update"
|
const val KEY_APP_UPDATE = "app_update"
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
|
|||||||
x.altTitle?.contains(query, ignoreCase = true) == true
|
x.altTitle?.contains(query, ignoreCase = true) == true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
list.sortWith(compareBy(AlphanumComparator()) { x -> x.title })
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
|
|||||||
x.tags.containsAll(tags)
|
x.tags.containsAll(tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
list.sortWith(compareBy(AlphanumComparator()) { x -> x.title })
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Activity
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import org.acra.dialog.CrashReportDialog
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
|
||||||
class AppProtectHelper(private val settings: AppSettings) : Application.ActivityLifecycleCallbacks {
|
class AppProtectHelper(private val settings: AppSettings) : Application.ActivityLifecycleCallbacks {
|
||||||
@@ -11,7 +12,7 @@ class AppProtectHelper(private val settings: AppSettings) : Application.Activity
|
|||||||
private var isUnlocked = settings.appPassword.isNullOrEmpty()
|
private var isUnlocked = settings.appPassword.isNullOrEmpty()
|
||||||
|
|
||||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||||
if (activity !is ProtectActivity && !isUnlocked) {
|
if (!isUnlocked && activity !is ProtectActivity && activity !is CrashReportDialog) {
|
||||||
val sourceIntent = Intent(activity, activity.javaClass)
|
val sourceIntent = Intent(activity, activity.javaClass)
|
||||||
activity.intent?.let {
|
activity.intent?.let {
|
||||||
sourceIntent.putExtras(it)
|
sourceIntent.putExtras(it)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.transition.TransitionManager
|
|||||||
import androidx.transition.TransitionSet
|
import androidx.transition.TransitionSet
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -47,7 +48,6 @@ import org.koitharu.kotatsu.utils.GridTouchHelper
|
|||||||
import org.koitharu.kotatsu.utils.ScreenOrientationHelper
|
import org.koitharu.kotatsu.utils.ScreenOrientationHelper
|
||||||
import org.koitharu.kotatsu.utils.ShareHelper
|
import org.koitharu.kotatsu.utils.ShareHelper
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.*
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class ReaderActivity :
|
class ReaderActivity :
|
||||||
BaseFullscreenActivity<ActivityReaderBinding>(),
|
BaseFullscreenActivity<ActivityReaderBinding>(),
|
||||||
@@ -67,6 +67,9 @@ class ReaderActivity :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val readerMode: ReaderMode?
|
||||||
|
get() = readerManager.currentMode
|
||||||
|
|
||||||
private lateinit var touchHelper: GridTouchHelper
|
private lateinit var touchHelper: GridTouchHelper
|
||||||
private lateinit var orientationHelper: ScreenOrientationHelper
|
private lateinit var orientationHelper: ScreenOrientationHelper
|
||||||
private lateinit var controlDelegate: ReaderControlDelegate
|
private lateinit var controlDelegate: ReaderControlDelegate
|
||||||
@@ -82,7 +85,7 @@ class ReaderActivity :
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
touchHelper = GridTouchHelper(this, this)
|
touchHelper = GridTouchHelper(this, this)
|
||||||
orientationHelper = ScreenOrientationHelper(this)
|
orientationHelper = ScreenOrientationHelper(this)
|
||||||
controlDelegate = ReaderControlDelegate(lifecycleScope, get(), this)
|
controlDelegate = ReaderControlDelegate(get(), this, this)
|
||||||
binding.toolbarBottom.inflateMenu(R.menu.opt_reader_bottom)
|
binding.toolbarBottom.inflateMenu(R.menu.opt_reader_bottom)
|
||||||
binding.toolbarBottom.setOnMenuItemClickListener(::onOptionsItemSelected)
|
binding.toolbarBottom.setOnMenuItemClickListener(::onOptionsItemSelected)
|
||||||
insetsDelegate.interceptingWindowInsetsListener = this
|
insetsDelegate.interceptingWindowInsetsListener = this
|
||||||
@@ -146,7 +149,7 @@ class ReaderActivity :
|
|||||||
ChaptersBottomSheet.show(
|
ChaptersBottomSheet.show(
|
||||||
supportFragmentManager,
|
supportFragmentManager,
|
||||||
viewModel.manga?.chapters.orEmpty(),
|
viewModel.manga?.chapters.orEmpty(),
|
||||||
viewModel.getCurrentState()?.chapterId ?: 0L
|
viewModel.getCurrentState()?.chapterId ?: 0L,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
R.id.action_screen_rotate -> {
|
R.id.action_screen_rotate -> {
|
||||||
@@ -317,12 +320,12 @@ class ReaderActivity :
|
|||||||
binding.appbarTop.updatePadding(
|
binding.appbarTop.updatePadding(
|
||||||
top = systemBars.top,
|
top = systemBars.top,
|
||||||
right = systemBars.right,
|
right = systemBars.right,
|
||||||
left = systemBars.left
|
left = systemBars.left,
|
||||||
)
|
)
|
||||||
binding.appbarBottom?.updatePadding(
|
binding.appbarBottom?.updatePadding(
|
||||||
bottom = systemBars.bottom,
|
bottom = systemBars.bottom,
|
||||||
right = systemBars.right,
|
right = systemBars.right,
|
||||||
left = systemBars.left
|
left = systemBars.left,
|
||||||
)
|
)
|
||||||
return WindowInsetsCompat.Builder(insets)
|
return WindowInsetsCompat.Builder(insets)
|
||||||
.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE)
|
.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE)
|
||||||
|
|||||||
@@ -1,33 +1,39 @@
|
|||||||
package org.koitharu.kotatsu.reader.ui
|
package org.koitharu.kotatsu.reader.ui
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.SoundEffectConstants
|
import android.view.SoundEffectConstants
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import kotlinx.coroutines.Dispatchers
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import kotlinx.coroutines.flow.flowOn
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||||
import org.koitharu.kotatsu.utils.GridTouchHelper
|
import org.koitharu.kotatsu.utils.GridTouchHelper
|
||||||
|
|
||||||
class ReaderControlDelegate(
|
class ReaderControlDelegate(
|
||||||
scope: LifecycleCoroutineScope,
|
private val settings: AppSettings,
|
||||||
settings: AppSettings,
|
private val listener: OnInteractionListener,
|
||||||
private val listener: OnInteractionListener
|
owner: LifecycleOwner,
|
||||||
) {
|
) : DefaultLifecycleObserver, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
private var isTapSwitchEnabled: Boolean = true
|
private var isTapSwitchEnabled: Boolean = true
|
||||||
private var isVolumeKeysSwitchEnabled: Boolean = false
|
private var isVolumeKeysSwitchEnabled: Boolean = false
|
||||||
|
private var isReaderTapsAdaptive: Boolean = true
|
||||||
|
|
||||||
init {
|
init {
|
||||||
settings.observeAsFlow(AppSettings.KEY_READER_SWITCHERS) { readerPageSwitch }
|
owner.lifecycle.addObserver(this)
|
||||||
.flowOn(Dispatchers.Default)
|
settings.subscribe(this)
|
||||||
.onEach {
|
updateSettings()
|
||||||
isTapSwitchEnabled = AppSettings.PAGE_SWITCH_TAPS in it
|
}
|
||||||
isVolumeKeysSwitchEnabled = AppSettings.PAGE_SWITCH_VOLUME_KEYS in it
|
|
||||||
}.launchIn(scope)
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
|
settings.unsubscribe(this)
|
||||||
|
owner.lifecycle.removeObserver(this)
|
||||||
|
super.onDestroy(owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
updateSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onGridTouch(area: Int, view: View) {
|
fun onGridTouch(area: Int, view: View) {
|
||||||
@@ -41,7 +47,7 @@ class ReaderControlDelegate(
|
|||||||
view.playSoundEffect(SoundEffectConstants.NAVIGATION_UP)
|
view.playSoundEffect(SoundEffectConstants.NAVIGATION_UP)
|
||||||
}
|
}
|
||||||
GridTouchHelper.AREA_LEFT -> if (isTapSwitchEnabled) {
|
GridTouchHelper.AREA_LEFT -> if (isTapSwitchEnabled) {
|
||||||
listener.switchPageBy(-1)
|
listener.switchPageBy(if (isReaderTapsReversed()) 1 else -1)
|
||||||
view.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT)
|
view.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT)
|
||||||
}
|
}
|
||||||
GridTouchHelper.AREA_BOTTOM -> if (isTapSwitchEnabled) {
|
GridTouchHelper.AREA_BOTTOM -> if (isTapSwitchEnabled) {
|
||||||
@@ -49,7 +55,7 @@ class ReaderControlDelegate(
|
|||||||
view.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN)
|
view.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN)
|
||||||
}
|
}
|
||||||
GridTouchHelper.AREA_RIGHT -> if (isTapSwitchEnabled) {
|
GridTouchHelper.AREA_RIGHT -> if (isTapSwitchEnabled) {
|
||||||
listener.switchPageBy(1)
|
listener.switchPageBy(if (isReaderTapsReversed()) -1 else 1)
|
||||||
view.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT)
|
view.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,17 +78,25 @@ class ReaderControlDelegate(
|
|||||||
KeyEvent.KEYCODE_PAGE_DOWN,
|
KeyEvent.KEYCODE_PAGE_DOWN,
|
||||||
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
|
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
-> {
|
||||||
listener.switchPageBy(1)
|
listener.switchPageBy(1)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||||
|
listener.switchPageBy(if (isReaderTapsReversed()) -1 else 1)
|
||||||
|
true
|
||||||
|
}
|
||||||
KeyEvent.KEYCODE_PAGE_UP,
|
KeyEvent.KEYCODE_PAGE_UP,
|
||||||
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
|
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
|
||||||
KeyEvent.KEYCODE_DPAD_UP,
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
-> {
|
||||||
listener.switchPageBy(-1)
|
listener.switchPageBy(-1)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
||||||
|
listener.switchPageBy(if (isReaderTapsReversed()) 1 else -1)
|
||||||
|
true
|
||||||
|
}
|
||||||
KeyEvent.KEYCODE_DPAD_CENTER -> {
|
KeyEvent.KEYCODE_DPAD_CENTER -> {
|
||||||
listener.toggleUiVisibility()
|
listener.toggleUiVisibility()
|
||||||
true
|
true
|
||||||
@@ -97,8 +111,21 @@ class ReaderControlDelegate(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateSettings() {
|
||||||
|
val switch = settings.readerPageSwitch
|
||||||
|
isTapSwitchEnabled = AppSettings.PAGE_SWITCH_TAPS in switch
|
||||||
|
isVolumeKeysSwitchEnabled = AppSettings.PAGE_SWITCH_VOLUME_KEYS in switch
|
||||||
|
isReaderTapsAdaptive = settings.isReaderTapsAdaptive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isReaderTapsReversed(): Boolean {
|
||||||
|
return isReaderTapsAdaptive && listener.readerMode == ReaderMode.REVERSED
|
||||||
|
}
|
||||||
|
|
||||||
interface OnInteractionListener {
|
interface OnInteractionListener {
|
||||||
|
|
||||||
|
val readerMode: ReaderMode?
|
||||||
|
|
||||||
fun switchPageBy(delta: Int)
|
fun switchPageBy(delta: Int)
|
||||||
|
|
||||||
fun toggleUiVisibility()
|
fun toggleUiVisibility()
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ private const val USER_AGENT_SHIKIMORI = "Kotatsu"
|
|||||||
class ShikimoriInterceptor(private val storage: ShikimoriStorage) : Interceptor {
|
class ShikimoriInterceptor(private val storage: ShikimoriStorage) : Interceptor {
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request().newBuilder()
|
val sourceRequest = chain.request()
|
||||||
|
val request = sourceRequest.newBuilder()
|
||||||
request.header(CommonHeaders.USER_AGENT, USER_AGENT_SHIKIMORI)
|
request.header(CommonHeaders.USER_AGENT, USER_AGENT_SHIKIMORI)
|
||||||
storage.accessToken?.let {
|
if (!sourceRequest.url.pathSegments.contains("oauth")) {
|
||||||
request.header(CommonHeaders.AUTHORIZATION, "Bearer $it")
|
storage.accessToken?.let {
|
||||||
|
request.header(CommonHeaders.AUTHORIZATION, "Bearer $it")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val response = chain.proceed(request.build())
|
val response = chain.proceed(request.build())
|
||||||
if (!response.isSuccessful && !response.isRedirect) {
|
if (!response.isSuccessful && !response.isRedirect) {
|
||||||
@@ -21,4 +24,4 @@ class ShikimoriInterceptor(private val storage: ShikimoriStorage) : Interceptor
|
|||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,13 +40,14 @@ class ShikimoriRepository(
|
|||||||
|
|
||||||
suspend fun authorize(code: String?) {
|
suspend fun authorize(code: String?) {
|
||||||
val body = FormBody.Builder()
|
val body = FormBody.Builder()
|
||||||
body.add("grant_type", "authorization_code")
|
|
||||||
body.add("client_id", BuildConfig.SHIKIMORI_CLIENT_ID)
|
body.add("client_id", BuildConfig.SHIKIMORI_CLIENT_ID)
|
||||||
body.add("client_secret", BuildConfig.SHIKIMORI_CLIENT_SECRET)
|
body.add("client_secret", BuildConfig.SHIKIMORI_CLIENT_SECRET)
|
||||||
if (code != null) {
|
if (code != null) {
|
||||||
|
body.add("grant_type", "authorization_code")
|
||||||
body.add("redirect_uri", REDIRECT_URI)
|
body.add("redirect_uri", REDIRECT_URI)
|
||||||
body.add("code", code)
|
body.add("code", code)
|
||||||
} else {
|
} else {
|
||||||
|
body.add("grant_type", "refresh_token")
|
||||||
body.add("refresh_token", checkNotNull(storage.refreshToken))
|
body.add("refresh_token", checkNotNull(storage.refreshToken))
|
||||||
}
|
}
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package org.koitharu.kotatsu.settings
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -47,7 +49,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
findPreference<Preference>(KEY_AUTH)?.run {
|
findPreference<Preference>(KEY_AUTH)?.run {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
loadUsername(this)
|
loadUsername(viewLifecycleOwner, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +64,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadUsername(preference: Preference) = viewLifecycleScope.launch {
|
private fun loadUsername(owner: LifecycleOwner, preference: Preference) = owner.lifecycleScope.launch {
|
||||||
runCatching {
|
runCatching {
|
||||||
preference.summary = null
|
preference.summary = null
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
@@ -89,11 +91,12 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveError(error: Throwable): Unit {
|
private fun resolveError(error: Throwable) {
|
||||||
viewLifecycleScope.launch {
|
viewLifecycleScope.launch {
|
||||||
if (exceptionResolver.resolve(error)) {
|
if (exceptionResolver.resolve(error)) {
|
||||||
val pref = findPreference<Preference>(KEY_AUTH) ?: return@launch
|
val pref = findPreference<Preference>(KEY_AUTH) ?: return@launch
|
||||||
loadUsername(pref)
|
val lifecycleOwner = awaitViewLifecycle()
|
||||||
|
loadUsername(lifecycleOwner, pref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import kotlin.math.roundToInt
|
|||||||
|
|
||||||
class GridTouchHelper(
|
class GridTouchHelper(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val listener: OnGridTouchListener
|
private val listener: OnGridTouchListener,
|
||||||
) : GestureDetector.SimpleOnGestureListener() {
|
) : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
|
||||||
private val detector = GestureDetector(context, this)
|
private val detector = GestureDetector(context, this)
|
||||||
@@ -16,7 +16,7 @@ class GridTouchHelper(
|
|||||||
private var isDispatching = false
|
private var isDispatching = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
detector.setIsLongpressEnabled(false)
|
detector.setIsLongpressEnabled(true)
|
||||||
detector.setOnDoubleTapListener(this)
|
detector.setOnDoubleTapListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ class GridTouchHelper(
|
|||||||
}
|
}
|
||||||
2 -> AREA_RIGHT
|
2 -> AREA_RIGHT
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -66,4 +66,4 @@ class GridTouchHelper(
|
|||||||
|
|
||||||
fun onProcessTouch(rawX: Int, rawY: Int): Boolean
|
fun onProcessTouch(rawX: Int, rawY: Int): Boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import coil.request.ImageResult
|
|||||||
import coil.request.SuccessResult
|
import coil.request.SuccessResult
|
||||||
import coil.util.CoilUtils
|
import coil.util.CoilUtils
|
||||||
import com.google.android.material.progressindicator.BaseProgressIndicator
|
import com.google.android.material.progressindicator.BaseProgressIndicator
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||||
import org.koitharu.kotatsu.utils.progress.ImageRequestIndicatorListener
|
import org.koitharu.kotatsu.utils.progress.ImageRequestIndicatorListener
|
||||||
|
|
||||||
@@ -45,9 +46,28 @@ fun ImageResult.toBitmapOrNull() = when (this) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun ImageRequest.Builder.referer(referer: String): ImageRequest.Builder {
|
fun ImageRequest.Builder.referer(referer: String): ImageRequest.Builder {
|
||||||
return setHeader(CommonHeaders.REFERER, referer)
|
if (referer.isEmpty()) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setHeader(CommonHeaders.REFERER, referer)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
val baseUrl = referer.baseUrl()
|
||||||
|
if (baseUrl != null) {
|
||||||
|
setHeader(CommonHeaders.REFERER, baseUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ImageRequest.Builder.indicator(indicator: BaseProgressIndicator<*>): ImageRequest.Builder {
|
fun ImageRequest.Builder.indicator(indicator: BaseProgressIndicator<*>): ImageRequest.Builder {
|
||||||
return listener(ImageRequestIndicatorListener(indicator))
|
return listener(ImageRequestIndicatorListener(indicator))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.baseUrl(): String? {
|
||||||
|
return (this.toHttpUrlOrNull()?.newBuilder("/") ?: return null)
|
||||||
|
.username("")
|
||||||
|
.password("")
|
||||||
|
.build()
|
||||||
|
.toString()
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,12 @@ import androidx.fragment.app.DialogFragment
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.coroutineScope
|
import androidx.lifecycle.coroutineScope
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
|
||||||
inline fun <T : Fragment> T.withArgs(size: Int, block: Bundle.() -> Unit): T {
|
inline fun <T : Fragment> T.withArgs(size: Int, block: Bundle.() -> Unit): T {
|
||||||
val b = Bundle(size)
|
val b = Bundle(size)
|
||||||
@@ -49,4 +53,20 @@ fun DialogFragment.showAllowStateLoss(manager: FragmentManager, tag: String?) {
|
|||||||
|
|
||||||
fun Fragment.addMenuProvider(provider: MenuProvider) {
|
fun Fragment.addMenuProvider(provider: MenuProvider) {
|
||||||
requireActivity().addMenuProvider(provider, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
requireActivity().addMenuProvider(provider, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Fragment.awaitViewLifecycle(): LifecycleOwner = suspendCancellableCoroutine { cont ->
|
||||||
|
val liveData = viewLifecycleOwnerLiveData
|
||||||
|
val observer = object : Observer<LifecycleOwner> {
|
||||||
|
override fun onChanged(result: LifecycleOwner?) {
|
||||||
|
if (result != null) {
|
||||||
|
liveData.removeObserver(this)
|
||||||
|
cont.resume(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
liveData.observeForever(observer)
|
||||||
|
cont.invokeOnCancellation {
|
||||||
|
liveData.removeObserver(observer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -325,4 +325,6 @@
|
|||||||
<string name="not_found_404">Content not found or removed</string>
|
<string name="not_found_404">Content not found or removed</string>
|
||||||
<string name="downloading_manga">Downloading manga</string>
|
<string name="downloading_manga">Downloading manga</string>
|
||||||
<string name="download_summary_pattern" translatable="false"><b>%1$s</b> %2$s</string>
|
<string name="download_summary_pattern" translatable="false"><b>%1$s</b> %2$s</string>
|
||||||
</resources>
|
<string name="reader_control_ltr_summary">Tap on the right edge or pressing the right key always switches to the next page</string>
|
||||||
|
<string name="reader_control_ltr">Ergonomic reader control</string>
|
||||||
|
</resources>
|
||||||
|
|||||||
@@ -29,6 +29,12 @@
|
|||||||
android:title="@string/switch_pages"
|
android:title="@string/switch_pages"
|
||||||
app:allowDividerAbove="true" />
|
app:allowDividerAbove="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="reader_taps_ltr"
|
||||||
|
android:summary="@string/reader_control_ltr_summary"
|
||||||
|
android:title="@string/reader_control_ltr" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="reader_animation"
|
android:key="reader_animation"
|
||||||
|
|||||||
Reference in New Issue
Block a user