Code cleanup and refactor
This commit is contained in:
@@ -16,8 +16,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 639
|
versionCode = 640
|
||||||
versionName = '7.0-rc2'
|
versionName = '7.0-rc3'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
||||||
ksp {
|
ksp {
|
||||||
@@ -87,8 +87,8 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.23'
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.24'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.core:core-ktx:1.13.1'
|
implementation 'androidx.core:core-ktx:1.13.1'
|
||||||
@@ -151,14 +151,14 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'org.json:json:20240303'
|
testImplementation 'org.json:json:20240303'
|
||||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0'
|
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||||
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
|
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
|
||||||
|
|
||||||
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0'
|
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.room:room-testing:2.6.1'
|
androidTestImplementation 'androidx.room:room-testing:2.6.1'
|
||||||
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.1'
|
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.1'
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class CaptchaNotifier(
|
|||||||
.build()
|
.build()
|
||||||
manager.createNotificationChannel(channel)
|
manager.createNotificationChannel(channel)
|
||||||
|
|
||||||
val intent = CloudFlareActivity.newIntent(context, exception.url, exception.headers)
|
val intent = CloudFlareActivity.newIntent(context, exception)
|
||||||
.setData(exception.url.toUri())
|
.setData(exception.url.toUri())
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setContentTitle(channel.name)
|
.setContentTitle(channel.name)
|
||||||
|
|||||||
@@ -23,12 +23,15 @@ import okhttp3.HttpUrl
|
|||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
|
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaSource
|
||||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||||
import org.koitharu.kotatsu.core.util.TaggedActivityResult
|
import org.koitharu.kotatsu.core.util.TaggedActivityResult
|
||||||
import org.koitharu.kotatsu.core.util.ext.configureForParser
|
import org.koitharu.kotatsu.core.util.ext.configureForParser
|
||||||
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@@ -137,6 +140,10 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
|
|||||||
|
|
||||||
override fun onCheckPassed() {
|
override fun onCheckPassed() {
|
||||||
pendingResult = RESULT_OK
|
pendingResult = RESULT_OK
|
||||||
|
val source = intent?.getStringExtra(ARG_SOURCE)
|
||||||
|
if (source != null) {
|
||||||
|
CaptchaNotifier(this).dismiss(MangaSource(source))
|
||||||
|
}
|
||||||
finishAfterTransition()
|
finishAfterTransition()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,9 +181,9 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Contract : ActivityResultContract<Pair<String, Headers?>, TaggedActivityResult>() {
|
class Contract : ActivityResultContract<CloudFlareProtectedException, TaggedActivityResult>() {
|
||||||
override fun createIntent(context: Context, input: Pair<String, Headers?>): Intent {
|
override fun createIntent(context: Context, input: CloudFlareProtectedException): Intent {
|
||||||
return newIntent(context, input.first, input.second)
|
return newIntent(context, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun parseResult(resultCode: Int, intent: Intent?): TaggedActivityResult {
|
override fun parseResult(resultCode: Int, intent: Intent?): TaggedActivityResult {
|
||||||
@@ -188,13 +195,23 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
|
|||||||
|
|
||||||
const val TAG = "CloudFlareActivity"
|
const val TAG = "CloudFlareActivity"
|
||||||
private const val ARG_UA = "ua"
|
private const val ARG_UA = "ua"
|
||||||
|
private const val ARG_SOURCE = "_source"
|
||||||
|
|
||||||
fun newIntent(
|
fun newIntent(context: Context, exception: CloudFlareProtectedException) = newIntent(
|
||||||
|
context = context,
|
||||||
|
url = exception.url,
|
||||||
|
source = exception.source,
|
||||||
|
headers = exception.headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun newIntent(
|
||||||
context: Context,
|
context: Context,
|
||||||
url: String,
|
url: String,
|
||||||
|
source: MangaSource?,
|
||||||
headers: Headers?,
|
headers: Headers?,
|
||||||
) = Intent(context, CloudFlareActivity::class.java).apply {
|
) = Intent(context, CloudFlareActivity::class.java).apply {
|
||||||
data = url.toUri()
|
data = url.toUri()
|
||||||
|
putExtra(ARG_SOURCE, source?.name)
|
||||||
headers?.get(CommonHeaders.USER_AGENT)?.let {
|
headers?.get(CommonHeaders.USER_AGENT)?.let {
|
||||||
putExtra(ARG_UA, it)
|
putExtra(ARG_UA, it)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ abstract class MangaSourcesDao {
|
|||||||
@Query("SELECT * FROM sources ORDER BY sort_key")
|
@Query("SELECT * FROM sources ORDER BY sort_key")
|
||||||
abstract suspend fun findAll(): List<MangaSourceEntity>
|
abstract suspend fun findAll(): List<MangaSourceEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM sources WHERE enabled = 0 ORDER BY sort_key")
|
@Query("SELECT source FROM sources WHERE enabled = 1")
|
||||||
abstract suspend fun findAllDisabled(): List<MangaSourceEntity>
|
abstract suspend fun findAllEnabledNames(): List<String>
|
||||||
|
|
||||||
@Query("SELECT * FROM sources ORDER BY sort_key")
|
@Query("SELECT * FROM sources ORDER BY sort_key")
|
||||||
abstract fun observeAll(): Flow<List<MangaSourceEntity>>
|
abstract fun observeAll(): Flow<List<MangaSourceEntity>>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import androidx.annotation.StringRes
|
|||||||
import androidx.collection.ArrayMap
|
import androidx.collection.ArrayMap
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import okhttp3.Headers
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.alternatives.ui.AlternativesActivity
|
import org.koitharu.kotatsu.alternatives.ui.AlternativesActivity
|
||||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||||
@@ -30,7 +29,7 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
|||||||
private val activity: FragmentActivity?
|
private val activity: FragmentActivity?
|
||||||
private val fragment: Fragment?
|
private val fragment: Fragment?
|
||||||
private val sourceAuthContract: ActivityResultLauncher<MangaSource>
|
private val sourceAuthContract: ActivityResultLauncher<MangaSource>
|
||||||
private val cloudflareContract: ActivityResultLauncher<Pair<String, Headers?>>
|
private val cloudflareContract: ActivityResultLauncher<CloudFlareProtectedException>
|
||||||
|
|
||||||
constructor(activity: FragmentActivity) {
|
constructor(activity: FragmentActivity) {
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
@@ -55,7 +54,7 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun resolve(e: Throwable): Boolean = when (e) {
|
suspend fun resolve(e: Throwable): Boolean = when (e) {
|
||||||
is CloudFlareProtectedException -> resolveCF(e.url, e.headers)
|
is CloudFlareProtectedException -> resolveCF(e)
|
||||||
is AuthRequiredException -> resolveAuthException(e.source)
|
is AuthRequiredException -> resolveAuthException(e.source)
|
||||||
is NotFoundException -> {
|
is NotFoundException -> {
|
||||||
openInBrowser(e.url)
|
openInBrowser(e.url)
|
||||||
@@ -70,9 +69,9 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun resolveCF(url: String, headers: Headers): Boolean = suspendCoroutine { cont ->
|
private suspend fun resolveCF(e: CloudFlareProtectedException): Boolean = suspendCoroutine { cont ->
|
||||||
continuations[CloudFlareActivity.TAG] = cont
|
continuations[CloudFlareActivity.TAG] = cont
|
||||||
cloudflareContract.launch(url to headers)
|
cloudflareContract.launch(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun resolveAuthException(source: MangaSource): Boolean = suspendCoroutine { cont ->
|
private suspend fun resolveAuthException(source: MangaSource): Boolean = suspendCoroutine { cont ->
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import org.koitharu.kotatsu.core.util.ext.source
|
|||||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||||
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
||||||
@@ -91,6 +92,14 @@ class AppShortcutManager @Inject constructor(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getMangaShortcuts(): Set<Long> {
|
||||||
|
val shortcuts = ShortcutManagerCompat.getShortcuts(
|
||||||
|
context,
|
||||||
|
ShortcutManagerCompat.FLAG_MATCH_CACHED or ShortcutManagerCompat.FLAG_MATCH_PINNED or ShortcutManagerCompat.FLAG_MATCH_DYNAMIC,
|
||||||
|
)
|
||||||
|
return shortcuts.mapNotNullToSet { it.id.toLongOrNull() }
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
suspend fun await(): Boolean {
|
suspend fun await(): Boolean {
|
||||||
return shortcutsUpdateJob?.join() != null
|
return shortcutsUpdateJob?.join() != null
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
|||||||
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||||
import org.koitharu.kotatsu.parsers.util.SuspendLazy
|
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
@@ -38,15 +38,10 @@ class MangaLoaderContextImpl @Inject constructor(
|
|||||||
) : MangaLoaderContext() {
|
) : MangaLoaderContext() {
|
||||||
|
|
||||||
private var webViewCached: WeakReference<WebView>? = null
|
private var webViewCached: WeakReference<WebView>? = null
|
||||||
|
private val webViewUserAgent by lazy { obtainWebViewUserAgent() }
|
||||||
private val userAgentLazy = SuspendLazy {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
obtainWebView().settings.userAgentString
|
|
||||||
}.sanitizeHeaderValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
override suspend fun evaluateJs(script: String): String? = withContext(Dispatchers.Main) {
|
override suspend fun evaluateJs(script: String): String? = withContext(Dispatchers.Main.immediate) {
|
||||||
val webView = obtainWebView()
|
val webView = obtainWebView()
|
||||||
suspendCoroutine { cont ->
|
suspendCoroutine { cont ->
|
||||||
webView.evaluateJavascript(script) { result ->
|
webView.evaluateJavascript(script) { result ->
|
||||||
@@ -55,13 +50,7 @@ class MangaLoaderContextImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDefaultUserAgent(): String = runCatching {
|
override fun getDefaultUserAgent(): String = webViewUserAgent
|
||||||
runBlocking {
|
|
||||||
userAgentLazy.get()
|
|
||||||
}
|
|
||||||
}.onFailure { e ->
|
|
||||||
e.printStackTraceDebug()
|
|
||||||
}.getOrDefault(UserAgents.FIREFOX_MOBILE)
|
|
||||||
|
|
||||||
override fun getConfig(source: MangaSource): MangaSourceConfig {
|
override fun getConfig(source: MangaSource): MangaSourceConfig {
|
||||||
return SourceSettings(androidContext, source)
|
return SourceSettings(androidContext, source)
|
||||||
@@ -86,4 +75,22 @@ class MangaLoaderContextImpl @Inject constructor(
|
|||||||
webViewCached = WeakReference(it)
|
webViewCached = WeakReference(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun obtainWebViewUserAgent(): String {
|
||||||
|
val mainDispatcher = Dispatchers.Main.immediate
|
||||||
|
return if (!mainDispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
|
||||||
|
obtainWebViewUserAgentImpl()
|
||||||
|
} else {
|
||||||
|
runBlocking(mainDispatcher) {
|
||||||
|
obtainWebViewUserAgentImpl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
private fun obtainWebViewUserAgentImpl() = runCatching {
|
||||||
|
obtainWebView().settings.userAgentString.sanitizeHeaderValue()
|
||||||
|
}.onFailure { e ->
|
||||||
|
e.printStackTraceDebug()
|
||||||
|
}.getOrDefault(UserAgents.FIREFOX_MOBILE)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import java.util.Locale
|
|||||||
|
|
||||||
class RemoteMangaRepository(
|
class RemoteMangaRepository(
|
||||||
private val parser: MangaParser,
|
private val parser: MangaParser,
|
||||||
private val cache: MemoryContentCache, // TODO fix concurrency
|
private val cache: MemoryContentCache,
|
||||||
private val mirrorSwitchInterceptor: MirrorSwitchInterceptor,
|
private val mirrorSwitchInterceptor: MirrorSwitchInterceptor,
|
||||||
) : MangaRepository, Interceptor {
|
) : MangaRepository, Interceptor {
|
||||||
|
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.ui.image
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.ColorFilter
|
|
||||||
import android.graphics.Outline
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Path
|
|
||||||
import android.graphics.PixelFormat
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.RectF
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.graphics.drawable.LayerDrawable
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.ReturnThis
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColorStateList
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
|
||||||
import org.koitharu.kotatsu.parsers.util.toIntUp
|
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
|
|
||||||
class CardDrawable(
|
|
||||||
context: Context,
|
|
||||||
private var corners: Int,
|
|
||||||
) : Drawable() {
|
|
||||||
|
|
||||||
private val cornerSize = context.resources.resolveDp(12f)
|
|
||||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
||||||
private val cornersF = FloatArray(8)
|
|
||||||
private val boundsF = RectF()
|
|
||||||
private val color: ColorStateList
|
|
||||||
private val path = Path()
|
|
||||||
private var alpha = 255
|
|
||||||
private var state: IntArray? = null
|
|
||||||
private var horizontalInset: Int = 0
|
|
||||||
|
|
||||||
init {
|
|
||||||
paint.style = Paint.Style.FILL
|
|
||||||
color = context.getThemeColorStateList(materialR.attr.colorSurfaceContainerHighest)
|
|
||||||
?: ColorStateList.valueOf(Color.TRANSPARENT)
|
|
||||||
setCorners(corners)
|
|
||||||
updateColor()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
canvas.drawPath(path, paint)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAlpha(alpha: Int) {
|
|
||||||
this.alpha = alpha
|
|
||||||
updateColor()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
|
||||||
paint.colorFilter = colorFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getColorFilter(): ColorFilter? = paint.colorFilter
|
|
||||||
|
|
||||||
override fun getOutline(outline: Outline) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
outline.setPath(path)
|
|
||||||
} else if (path.isConvex) {
|
|
||||||
outline.setConvexPath(path)
|
|
||||||
}
|
|
||||||
outline.alpha = 1f
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPadding(padding: Rect): Boolean {
|
|
||||||
padding.set(
|
|
||||||
horizontalInset,
|
|
||||||
0,
|
|
||||||
horizontalInset,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if (corners or TOP != 0) {
|
|
||||||
padding.top += cornerSize.toIntUp()
|
|
||||||
}
|
|
||||||
if (corners or BOTTOM != 0) {
|
|
||||||
padding.bottom += cornerSize.toIntUp()
|
|
||||||
}
|
|
||||||
return horizontalInset != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStateChange(state: IntArray): Boolean {
|
|
||||||
this.state = state
|
|
||||||
if (color.isStateful) {
|
|
||||||
updateColor()
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
|
||||||
override fun getOpacity(): Int = PixelFormat.TRANSPARENT
|
|
||||||
|
|
||||||
override fun onBoundsChange(bounds: Rect) {
|
|
||||||
super.onBoundsChange(bounds)
|
|
||||||
boundsF.set(bounds)
|
|
||||||
boundsF.inset(horizontalInset.toFloat(), 0f)
|
|
||||||
path.reset()
|
|
||||||
path.addRoundRect(boundsF, cornersF, Path.Direction.CW)
|
|
||||||
path.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReturnThis
|
|
||||||
fun setCorners(corners: Int): CardDrawable {
|
|
||||||
this.corners = corners
|
|
||||||
val topLeft = if (corners and TOP_LEFT == TOP_LEFT) cornerSize else 0f
|
|
||||||
val topRight = if (corners and TOP_RIGHT == TOP_RIGHT) cornerSize else 0f
|
|
||||||
val bottomRight = if (corners and BOTTOM_RIGHT == BOTTOM_RIGHT) cornerSize else 0f
|
|
||||||
val bottomLeft = if (corners and BOTTOM_LEFT == BOTTOM_LEFT) cornerSize else 0f
|
|
||||||
cornersF[0] = topLeft
|
|
||||||
cornersF[1] = topLeft
|
|
||||||
cornersF[2] = topRight
|
|
||||||
cornersF[3] = topRight
|
|
||||||
cornersF[4] = bottomRight
|
|
||||||
cornersF[5] = bottomRight
|
|
||||||
cornersF[6] = bottomLeft
|
|
||||||
cornersF[7] = bottomLeft
|
|
||||||
invalidateSelf()
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setHorizontalInset(inset: Int) {
|
|
||||||
horizontalInset = inset
|
|
||||||
invalidateSelf()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateColor() {
|
|
||||||
paint.color = color.getColorForState(state, color.defaultColor)
|
|
||||||
paint.alpha = alpha
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
const val TOP_LEFT = 1
|
|
||||||
const val TOP_RIGHT = 2
|
|
||||||
const val BOTTOM_LEFT = 4
|
|
||||||
const val BOTTOM_RIGHT = 8
|
|
||||||
|
|
||||||
const val LEFT = TOP_LEFT or BOTTOM_LEFT
|
|
||||||
const val TOP = TOP_LEFT or TOP_RIGHT
|
|
||||||
const val RIGHT = TOP_RIGHT or BOTTOM_RIGHT
|
|
||||||
const val BOTTOM = BOTTOM_LEFT or BOTTOM_RIGHT
|
|
||||||
|
|
||||||
const val NONE = 0
|
|
||||||
const val ALL = TOP_LEFT or TOP_RIGHT or BOTTOM_RIGHT or BOTTOM_LEFT
|
|
||||||
|
|
||||||
fun from(d: Drawable?): CardDrawable? = when (d) {
|
|
||||||
null -> null
|
|
||||||
is CardDrawable -> d
|
|
||||||
is LayerDrawable -> (0 until d.numberOfLayers).firstNotNullOfOrNull { i ->
|
|
||||||
from(d.getDrawable(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.util.ext
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.Display
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
val Activity.displayCompat: Display
|
|
||||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
display ?: windowManager.defaultDisplay
|
|
||||||
} else {
|
|
||||||
windowManager.defaultDisplay
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Activity.getDisplaySize(): Rect {
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
windowManager.currentWindowMetrics.bounds
|
|
||||||
} else {
|
|
||||||
val dm = DisplayMetrics()
|
|
||||||
displayCompat.getRealMetrics(dm)
|
|
||||||
Rect(0, 0, dm.widthPixels, dm.heightPixels)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -110,10 +110,7 @@ class MangaPrefetchService : CoroutineIntentService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isPrefetchAvailable(context: Context, source: MangaSource?): Boolean {
|
private fun isPrefetchAvailable(context: Context, source: MangaSource?): Boolean {
|
||||||
if (source == MangaSource.LOCAL) {
|
if (source == MangaSource.LOCAL || context.isPowerSaveMode()) {
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (context.isPowerSaveMode()) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
val entryPoint = EntryPointAccessors.fromApplication(
|
val entryPoint = EntryPointAccessors.fromApplication(
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ class DetailsViewModel @Inject constructor(
|
|||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())
|
||||||
|
|
||||||
val branches: StateFlow<List<MangaBranch>> = combine(
|
val branches: StateFlow<List<MangaBranch>> = combine(
|
||||||
details,
|
details,
|
||||||
@@ -220,7 +220,7 @@ class DetailsViewModel @Inject constructor(
|
|||||||
chaptersQuery,
|
chaptersQuery,
|
||||||
) { list, reversed, query ->
|
) { list, reversed, query ->
|
||||||
(if (reversed) list.asReversed() else list).filterSearch(query)
|
(if (reversed) list.asReversed() else list).filterSearch(query)
|
||||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
|
||||||
|
|
||||||
val readingTime = combine(
|
val readingTime = combine(
|
||||||
details,
|
details,
|
||||||
@@ -228,7 +228,7 @@ class DetailsViewModel @Inject constructor(
|
|||||||
history,
|
history,
|
||||||
) { m, b, h ->
|
) { m, b, h ->
|
||||||
readingTimeUseCase.invoke(m, b, h)
|
readingTimeUseCase.invoke(m, b, h)
|
||||||
}.stateIn(viewModelScope, SharingStarted.Lazily, null)
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, null)
|
||||||
|
|
||||||
val selectedBranchValue: String?
|
val selectedBranchValue: String?
|
||||||
get() = selectedBranch.value
|
get() = selectedBranch.value
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ class DownloadsViewModel @Inject constructor(
|
|||||||
emit(mapChapters())
|
emit(mapChapters())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||||
|
|
||||||
private suspend fun tryLoad(manga: Manga) = runCatchingCancellable {
|
private suspend fun tryLoad(manga: Manga) = runCatchingCancellable {
|
||||||
(mangaRepositoryFactory.create(manga.source) as RemoteMangaRepository).getDetails(manga)
|
(mangaRepositoryFactory.create(manga.source) as RemoteMangaRepository).getDetails(manga)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import org.koitharu.kotatsu.core.model.isNsfw
|
|||||||
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.observeAsFlow
|
||||||
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
|
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
@@ -48,8 +47,17 @@ class MangaSourcesRepository @Inject constructor(
|
|||||||
return dao.findAllEnabled(order).toSources(settings.isNsfwContentDisabled, order)
|
return dao.findAllEnabled(order).toSources(settings.isNsfwContentDisabled, order)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getDisabledSources(): List<MangaSource> {
|
suspend fun getDisabledSources(): Set<MangaSource> {
|
||||||
return dao.findAllDisabled().toSources(settings.isNsfwContentDisabled, null)
|
val result = EnumSet.copyOf(remoteSources)
|
||||||
|
val enabled = dao.findAllEnabledNames()
|
||||||
|
for (name in enabled) {
|
||||||
|
val source = MangaSource(name)
|
||||||
|
result.remove(source)
|
||||||
|
}
|
||||||
|
if (settings.isNsfwContentDisabled) {
|
||||||
|
result.removeAll { it.isNsfw() }
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun observeIsEnabled(source: MangaSource): Flow<Boolean> {
|
fun observeIsEnabled(source: MangaSource): Flow<Boolean> {
|
||||||
@@ -143,6 +151,7 @@ class MangaSourcesRepository @Inject constructor(
|
|||||||
result
|
result
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
} else {
|
} else {
|
||||||
|
assimilateNewSources()
|
||||||
flowOf(emptySet())
|
flowOf(emptySet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +208,7 @@ class MangaSourcesRepository @Inject constructor(
|
|||||||
val result = ArrayList<MangaSource>(size)
|
val result = ArrayList<MangaSource>(size)
|
||||||
for (entity in this) {
|
for (entity in this) {
|
||||||
val source = MangaSource(entity.source)
|
val source = MangaSource(entity.source)
|
||||||
if (skipNsfwSources && source.contentType == ContentType.HENTAI) {
|
if (skipNsfwSources && source.isNsfw()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (source in remoteSources) {
|
if (source in remoteSources) {
|
||||||
|
|||||||
@@ -17,11 +17,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity
|
import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity
|
||||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
|
||||||
import org.koitharu.kotatsu.core.ui.BaseFragment
|
import org.koitharu.kotatsu.core.ui.BaseFragment
|
||||||
import org.koitharu.kotatsu.core.ui.dialog.TwoButtonsAlertDialog
|
import org.koitharu.kotatsu.core.ui.dialog.TwoButtonsAlertDialog
|
||||||
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
||||||
@@ -34,7 +32,6 @@ import org.koitharu.kotatsu.core.util.ext.addMenuProvider
|
|||||||
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
|
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
|
||||||
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.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
|
||||||
import org.koitharu.kotatsu.databinding.FragmentExploreBinding
|
import org.koitharu.kotatsu.databinding.FragmentExploreBinding
|
||||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||||
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
|
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
|
||||||
@@ -64,9 +61,6 @@ class ExploreFragment :
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var coil: ImageLoader
|
lateinit var coil: ImageLoader
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var shortcutManager: AppShortcutManager
|
|
||||||
|
|
||||||
private val viewModel by viewModels<ExploreViewModel>()
|
private val viewModel by viewModels<ExploreViewModel>()
|
||||||
private var exploreAdapter: ExploreAdapter? = null
|
private var exploreAdapter: ExploreAdapter? = null
|
||||||
private var sourceSelectionController: ListSelectionController? = null
|
private var sourceSelectionController: ListSelectionController? = null
|
||||||
@@ -213,9 +207,7 @@ class ExploreFragment :
|
|||||||
|
|
||||||
R.id.action_shortcut -> {
|
R.id.action_shortcut -> {
|
||||||
val source = selectedSources.singleOrNull() ?: return false
|
val source = selectedSources.singleOrNull() ?: return false
|
||||||
viewLifecycleScope.launch {
|
viewModel.requestPinShortcut(source)
|
||||||
shortcutManager.requestPinShortcut(source)
|
|
||||||
}
|
|
||||||
mode.finish()
|
mode.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.mapLatest
|
|||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
||||||
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.observeAsFlow
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||||
@@ -43,6 +44,7 @@ class ExploreViewModel @Inject constructor(
|
|||||||
private val suggestionRepository: SuggestionRepository,
|
private val suggestionRepository: SuggestionRepository,
|
||||||
private val exploreRepository: ExploreRepository,
|
private val exploreRepository: ExploreRepository,
|
||||||
private val sourcesRepository: MangaSourcesRepository,
|
private val sourcesRepository: MangaSourcesRepository,
|
||||||
|
private val shortcutManager: AppShortcutManager,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val isGrid = settings.observeAsStateFlow(
|
val isGrid = settings.observeAsStateFlow(
|
||||||
@@ -106,6 +108,12 @@ class ExploreViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun requestPinShortcut(source: MangaSource) {
|
||||||
|
launchLoadingJob(Dispatchers.Default) {
|
||||||
|
shortcutManager.requestPinShortcut(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun respondSuggestionTip(isAccepted: Boolean) {
|
fun respondSuggestionTip(isAccepted: Boolean) {
|
||||||
settings.isSuggestionsEnabled = isAccepted
|
settings.isSuggestionsEnabled = isAccepted
|
||||||
settings.closeTip(TIP_SUGGESTIONS)
|
settings.closeTip(TIP_SUGGESTIONS)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.koitharu.kotatsu.favourites.ui.categories
|
package org.koitharu.kotatsu.favourites.ui.categories
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -177,10 +175,4 @@ class FavouriteCategoriesActivity :
|
|||||||
viewModel.saveOrder(adapter.items ?: return)
|
viewModel.saveOrder(adapter.items ?: return)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("")
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun newIntent(context: Context) = Intent(context, FavouriteCategoriesActivity::class.java)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.favourites.ui.categories.select.adapter
|
package org.koitharu.kotatsu.favourites.ui.categories.select.adapter
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
@@ -16,7 +17,7 @@ fun categoriesHeaderAD() = adapterDelegateViewBinding<CategoriesHeaderItem, List
|
|||||||
val onClickListener = View.OnClickListener { v ->
|
val onClickListener = View.OnClickListener { v ->
|
||||||
val intent = when (v.id) {
|
val intent = when (v.id) {
|
||||||
R.id.chip_create -> FavouritesCategoryEditActivity.newIntent(v.context)
|
R.id.chip_create -> FavouritesCategoryEditActivity.newIntent(v.context)
|
||||||
R.id.chip_manage -> FavouriteCategoriesActivity.newIntent(v.context)
|
R.id.chip_manage -> Intent(v.context, FavouriteCategoriesActivity::class.java)
|
||||||
else -> return@OnClickListener
|
else -> return@OnClickListener
|
||||||
}
|
}
|
||||||
v.context.startActivity(intent)
|
v.context.startActivity(intent)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.favourites.ui.container
|
package org.koitharu.kotatsu.favourites.ui.container
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -102,7 +103,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
|
|||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.button_retry -> startActivity(
|
R.id.button_retry -> startActivity(
|
||||||
FavouriteCategoriesActivity.newIntent(v.context),
|
Intent(v.context, FavouriteCategoriesActivity::class.java),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.koitharu.kotatsu.favourites.ui.container
|
package org.koitharu.kotatsu.favourites.ui.container
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
@@ -19,7 +20,7 @@ class FavouritesContainerMenuProvider(
|
|||||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.action_manage -> {
|
R.id.action_manage -> {
|
||||||
context.startActivity(FavouriteCategoriesActivity.newIntent(context))
|
context.startActivity(Intent(context, FavouriteCategoriesActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> return false
|
else -> return false
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class ListConfigViewModel @Inject constructor(
|
|||||||
ListConfigSection.General -> null
|
ListConfigSection.General -> null
|
||||||
ListConfigSection.Updated -> null
|
ListConfigSection.Updated -> null
|
||||||
ListConfigSection.History -> settings.historySortOrder
|
ListConfigSection.History -> settings.historySortOrder
|
||||||
ListConfigSection.Suggestions -> ListSortOrder.RELEVANCE // TODO
|
ListConfigSection.Suggestions -> ListSortOrder.RELEVANCE
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSortOrder(position: Int) {
|
fun setSortOrder(position: Int) {
|
||||||
|
|||||||
@@ -72,9 +72,6 @@ class SingleMangaImporter @Inject constructor(
|
|||||||
return LocalMangaInput.of(dest).getManga()
|
return LocalMangaInput.of(dest).getManga()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: progress
|
|
||||||
*/
|
|
||||||
private suspend fun DocumentFile.copyTo(destDir: File) {
|
private suspend fun DocumentFile.copyTo(destDir: File) {
|
||||||
if (isDirectory) {
|
if (isDirectory) {
|
||||||
val subDir = File(destDir, requireName())
|
val subDir = File(destDir, requireName())
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
|
|||||||
import org.koitharu.kotatsu.core.ui.util.OptionsMenuBadgeHelper
|
import org.koitharu.kotatsu.core.ui.util.OptionsMenuBadgeHelper
|
||||||
import org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView
|
import org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView
|
||||||
import org.koitharu.kotatsu.core.util.ext.hideKeyboard
|
import org.koitharu.kotatsu.core.util.ext.hideKeyboard
|
||||||
import org.koitharu.kotatsu.core.util.ext.measureHeight
|
|
||||||
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.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
||||||
@@ -77,7 +76,7 @@ private const val TAG_SEARCH = "search"
|
|||||||
class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNavOwner,
|
class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNavOwner,
|
||||||
View.OnClickListener,
|
View.OnClickListener,
|
||||||
View.OnFocusChangeListener, SearchSuggestionListener,
|
View.OnFocusChangeListener, SearchSuggestionListener,
|
||||||
MainNavigationDelegate.OnFragmentChangedListener {
|
MainNavigationDelegate.OnFragmentChangedListener, View.OnLayoutChangeListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var settings: AppSettings
|
lateinit var settings: AppSettings
|
||||||
@@ -136,6 +135,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
|||||||
}
|
}
|
||||||
viewModel.isBottomNavPinned.observe(this, ::setNavbarPinned)
|
viewModel.isBottomNavPinned.observe(this, ::setNavbarPinned)
|
||||||
searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)
|
searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)
|
||||||
|
viewBinding.bottomNav?.addOnLayoutChangeListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||||
@@ -211,6 +211,22 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLayoutChange(
|
||||||
|
v: View?,
|
||||||
|
left: Int,
|
||||||
|
top: Int,
|
||||||
|
right: Int,
|
||||||
|
bottom: Int,
|
||||||
|
oldLeft: Int,
|
||||||
|
oldTop: Int,
|
||||||
|
oldRight: Int,
|
||||||
|
oldBottom: Int
|
||||||
|
) {
|
||||||
|
if (top != oldTop || bottom != oldBottom) {
|
||||||
|
updateContainerBottomMargin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onFocusChange(v: View?, hasFocus: Boolean) {
|
override fun onFocusChange(v: View?, hasFocus: Boolean) {
|
||||||
val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH)
|
val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH)
|
||||||
if (v?.id == R.id.searchView && hasFocus) {
|
if (v?.id == R.id.searchView && hasFocus) {
|
||||||
@@ -418,12 +434,17 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
|||||||
view.layoutParams = lp
|
view.layoutParams = lp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewBinding.container.updateLayoutParams<MarginLayoutParams> {
|
updateContainerBottomMargin()
|
||||||
bottomMargin = if (isPinned) {
|
}
|
||||||
(bottomNavBar?.measureHeight()
|
|
||||||
?.coerceAtLeast(resources.getDimensionPixelSize(materialR.dimen.m3_bottom_nav_min_height)) ?: 0)
|
private fun updateContainerBottomMargin() {
|
||||||
} else {
|
val bottomNavBar = viewBinding.bottomNav ?: return
|
||||||
0
|
val newMargin = if (bottomNavBar.isPinned) bottomNavBar.height else 0
|
||||||
|
with(viewBinding.container) {
|
||||||
|
val params = layoutParams as MarginLayoutParams
|
||||||
|
if (params.bottomMargin != newMargin) {
|
||||||
|
params.bottomMargin = newMargin
|
||||||
|
layoutParams = params
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.util.DisplayMetrics
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.animation.Interpolator
|
import android.view.animation.Interpolator
|
||||||
import android.widget.Scroller
|
import android.widget.Scroller
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||||
@@ -57,7 +56,7 @@ class DoublePageSnapHelper : SnapHelper() {
|
|||||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||||
check(layoutManager.canScrollHorizontally()) { "RecyclerView must be scrollable" }
|
check(layoutManager.canScrollHorizontally()) { "RecyclerView must be scrollable" }
|
||||||
orientationHelper = OrientationHelper.createHorizontalHelper(layoutManager)
|
orientationHelper = OrientationHelper.createHorizontalHelper(layoutManager)
|
||||||
layoutDirectionHelper = LayoutDirectionHelper(ViewCompat.getLayoutDirection(recyclerView))
|
layoutDirectionHelper = LayoutDirectionHelper(recyclerView.layoutDirection)
|
||||||
scroller = Scroller(target.context, snapInterpolator)
|
scroller = Scroller(target.context, snapInterpolator)
|
||||||
initItemDimensionIfNeeded(layoutManager)
|
initItemDimensionIfNeeded(layoutManager)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import android.view.animation.DecelerateInterpolator
|
|||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.OverScroller
|
import android.widget.OverScroller
|
||||||
import androidx.core.animation.doOnEnd
|
import androidx.core.animation.doOnEnd
|
||||||
import androidx.core.view.GestureDetectorCompat
|
|
||||||
import androidx.core.view.ViewConfigurationCompat
|
import androidx.core.view.ViewConfigurationCompat
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.widgets.ZoomControl
|
import org.koitharu.kotatsu.core.ui.widgets.ZoomControl
|
||||||
@@ -39,7 +38,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
|
|||||||
ZoomControl.ZoomControlListener {
|
ZoomControl.ZoomControlListener {
|
||||||
|
|
||||||
private val scaleDetector = ScaleGestureDetector(context, this)
|
private val scaleDetector = ScaleGestureDetector(context, this)
|
||||||
private val gestureDetector = GestureDetectorCompat(context, GestureListener())
|
private val gestureDetector = GestureDetector(context, GestureListener())
|
||||||
private val overScroller = OverScroller(context, AccelerateDecelerateInterpolator())
|
private val overScroller = OverScroller(context, AccelerateDecelerateInterpolator())
|
||||||
|
|
||||||
private val transformMatrix = Matrix()
|
private val transformMatrix = Matrix()
|
||||||
@@ -339,7 +338,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
|
|||||||
if (overScroller.computeScrollOffset()) {
|
if (overScroller.computeScrollOffset()) {
|
||||||
transformMatrix.postTranslate(
|
transformMatrix.postTranslate(
|
||||||
overScroller.currX.toFloat() - prevPos.x,
|
overScroller.currX.toFloat() - prevPos.x,
|
||||||
overScroller.currY.toFloat() - prevPos.y
|
overScroller.currY.toFloat() - prevPos.y,
|
||||||
)
|
)
|
||||||
prevPos.set(overScroller.currX, overScroller.currY)
|
prevPos.set(overScroller.currX, overScroller.currY)
|
||||||
invalidateTarget()
|
invalidateTarget()
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) {
|
|||||||
ScrobblerUser(
|
ScrobblerUser(
|
||||||
id = lines[0].toLong(),
|
id = lines[0].toLong(),
|
||||||
nickname = lines[1],
|
nickname = lines[1],
|
||||||
avatar = lines[2],
|
avatar = lines[2].takeUnless(String::isEmpty),
|
||||||
service = ScrobblerService.valueOf(lines[3]),
|
service = ScrobblerService.valueOf(lines[3]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) {
|
|||||||
val str = StringJoiner("\n")
|
val str = StringJoiner("\n")
|
||||||
.add(value.id)
|
.add(value.id)
|
||||||
.add(value.nickname)
|
.add(value.nickname)
|
||||||
.add(value.avatar)
|
.add(value.avatar.orEmpty())
|
||||||
.add(value.service.name)
|
.add(value.service.name)
|
||||||
.complete()
|
.complete()
|
||||||
putString(KEY_USER, str)
|
putString(KEY_USER, str)
|
||||||
|
|||||||
@@ -67,10 +67,8 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
|
|||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
if (intent != null) {
|
setIntent(intent)
|
||||||
setIntent(intent)
|
processIntent(intent)
|
||||||
processIntent(intent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowInsetsChanged(insets: Insets) {
|
override fun onWindowInsetsChanged(insets: Insets) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class SplitSwitchPreference @JvmOverloads constructor(
|
|||||||
|
|
||||||
var onContainerClickListener: OnPreferenceClickListener? = null
|
var onContainerClickListener: OnPreferenceClickListener? = null
|
||||||
|
|
||||||
private val containerClickListener = View.OnClickListener { v ->
|
private val containerClickListener = View.OnClickListener {
|
||||||
onContainerClickListener?.onPreferenceClick(this)
|
onContainerClickListener?.onPreferenceClick(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,18 @@
|
|||||||
package org.koitharu.kotatsu.stats.ui.views
|
package org.koitharu.kotatsu.stats.ui.views
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.DashPathEffect
|
import android.graphics.DashPathEffect
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.PathEffect
|
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.graphics.PorterDuffXfermode
|
|
||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
import android.graphics.Xfermode
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.GestureDetector
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.collection.MutableIntList
|
|
||||||
import androidx.core.graphics.ColorUtils
|
|
||||||
import androidx.core.graphics.minus
|
|
||||||
import androidx.core.view.GestureDetectorCompat
|
|
||||||
import androidx.core.view.setPadding
|
|
||||||
import com.google.android.material.color.MaterialColors
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
||||||
import org.koitharu.kotatsu.parsers.util.replaceWith
|
import org.koitharu.kotatsu.parsers.util.replaceWith
|
||||||
import org.koitharu.kotatsu.parsers.util.toIntUp
|
import org.koitharu.kotatsu.parsers.util.toIntUp
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.math.sqrt
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@@ -126,7 +109,7 @@ class BarChartView @JvmOverloads constructor(
|
|||||||
bars.clear()
|
bars.clear()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var fullWidth = rawData.size * (barWidth + minBarSpacing) + minBarSpacing
|
val fullWidth = rawData.size * (barWidth + minBarSpacing) + minBarSpacing
|
||||||
val windowSize = (fullWidth / width.toFloat()).toIntUp()
|
val windowSize = (fullWidth / width.toFloat()).toIntUp()
|
||||||
bars.replaceWith(
|
bars.replaceWith(
|
||||||
rawData.chunked(windowSize) { it.average() },
|
rawData.chunked(windowSize) { it.average() },
|
||||||
|
|||||||
@@ -3,28 +3,17 @@ package org.koitharu.kotatsu.stats.ui.views
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.graphics.PorterDuffXfermode
|
|
||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
import android.graphics.Xfermode
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.collection.MutableIntList
|
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.graphics.minus
|
|
||||||
import androidx.core.view.GestureDetectorCompat
|
|
||||||
import com.google.android.material.color.MaterialColors
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
||||||
import org.koitharu.kotatsu.parsers.util.replaceWith
|
import org.koitharu.kotatsu.parsers.util.replaceWith
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
|
|
||||||
class PieChartView @JvmOverloads constructor(
|
class PieChartView @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
@@ -34,8 +23,8 @@ class PieChartView @JvmOverloads constructor(
|
|||||||
private val segments = ArrayList<Segment>()
|
private val segments = ArrayList<Segment>()
|
||||||
private val chartBounds = RectF()
|
private val chartBounds = RectF()
|
||||||
private val clearColor = context.getThemeColor(android.R.attr.colorBackground)
|
private val clearColor = context.getThemeColor(android.R.attr.colorBackground)
|
||||||
private val touchDetector = GestureDetectorCompat(context, this)
|
private val touchDetector = GestureDetector(context, this)
|
||||||
private var hightlightedSegment = -1
|
private var highlightedSegment = -1
|
||||||
|
|
||||||
var onSegmentClickListener: OnSegmentClickListener? = null
|
var onSegmentClickListener: OnSegmentClickListener? = null
|
||||||
|
|
||||||
@@ -49,7 +38,7 @@ class PieChartView @JvmOverloads constructor(
|
|||||||
var angle = 0f
|
var angle = 0f
|
||||||
for ((i, segment) in segments.withIndex()) {
|
for ((i, segment) in segments.withIndex()) {
|
||||||
paint.color = segment.color
|
paint.color = segment.color
|
||||||
if (i == hightlightedSegment) {
|
if (i == highlightedSegment) {
|
||||||
paint.color = ColorUtils.setAlphaComponent(paint.color, 180)
|
paint.color = ColorUtils.setAlphaComponent(paint.color, 180)
|
||||||
}
|
}
|
||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
@@ -91,7 +80,7 @@ class PieChartView @JvmOverloads constructor(
|
|||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
if (event.actionMasked == MotionEvent.ACTION_CANCEL || event.actionMasked == MotionEvent.ACTION_UP) {
|
if (event.actionMasked == MotionEvent.ACTION_CANCEL || event.actionMasked == MotionEvent.ACTION_UP) {
|
||||||
hightlightedSegment = -1
|
highlightedSegment = -1
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
return super.onTouchEvent(event) || touchDetector.onTouchEvent(event)
|
return super.onTouchEvent(event) || touchDetector.onTouchEvent(event)
|
||||||
@@ -102,8 +91,8 @@ class PieChartView @JvmOverloads constructor(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
val segment = findSegmentIndex(e.x, e.y)
|
val segment = findSegmentIndex(e.x, e.y)
|
||||||
if (segment != hightlightedSegment) {
|
if (segment != highlightedSegment) {
|
||||||
hightlightedSegment = segment
|
highlightedSegment = segment
|
||||||
invalidate()
|
invalidate()
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -79,12 +79,10 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
.build()
|
.build()
|
||||||
val response = httpClient.newCall(request).execute().log().parseJsonOrNull()
|
val response = httpClient.newCall(request).execute().log().parseJsonOrNull()
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
val timestamp = response.getLong(FIELD_TIMESTAMP)
|
val categoriesResult = upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES))
|
||||||
val categoriesResult =
|
|
||||||
upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES), timestamp)
|
|
||||||
stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L
|
stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L
|
||||||
stats.numInserts += categoriesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
stats.numInserts += categoriesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
||||||
val favouritesResult = upsertFavourites(response.getJSONArray(TABLE_FAVOURITES), timestamp)
|
val favouritesResult = upsertFavourites(response.getJSONArray(TABLE_FAVOURITES))
|
||||||
stats.numDeletes += favouritesResult.first().count?.toLong() ?: 0L
|
stats.numDeletes += favouritesResult.first().count?.toLong() ?: 0L
|
||||||
stats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
stats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
||||||
stats.numEntries += stats.numInserts + stats.numDeletes
|
stats.numEntries += stats.numInserts + stats.numDeletes
|
||||||
@@ -105,7 +103,6 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
if (response != null) {
|
if (response != null) {
|
||||||
val result = upsertHistory(
|
val result = upsertHistory(
|
||||||
json = response.getJSONArray(TABLE_HISTORY),
|
json = response.getJSONArray(TABLE_HISTORY),
|
||||||
timestamp = response.getLong(FIELD_TIMESTAMP),
|
|
||||||
)
|
)
|
||||||
stats.numDeletes += result.first().count?.toLong() ?: 0L
|
stats.numDeletes += result.first().count?.toLong() ?: 0L
|
||||||
stats.numInserts += result.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
stats.numInserts += result.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
||||||
@@ -122,12 +119,12 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
|
|
||||||
fun onSyncComplete(result: SyncResult) {
|
fun onSyncComplete(result: SyncResult) {
|
||||||
if (logger.isEnabled) {
|
if (logger.isEnabled) {
|
||||||
logger.log("Sync finshed: ${result.toDebugString()}")
|
logger.log("Sync finished: ${result.toDebugString()}")
|
||||||
logger.flushBlocking()
|
logger.flushBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun upsertHistory(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
|
private fun upsertHistory(json: JSONArray): Array<ContentProviderResult> {
|
||||||
val uri = uri(authorityHistory, TABLE_HISTORY)
|
val uri = uri(authorityHistory, TABLE_HISTORY)
|
||||||
val operations = ArrayList<ContentProviderOperation>()
|
val operations = ArrayList<ContentProviderOperation>()
|
||||||
json.mapJSONTo(operations) { jo ->
|
json.mapJSONTo(operations) { jo ->
|
||||||
@@ -139,7 +136,7 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
return provider.applyBatch(operations)
|
return provider.applyBatch(operations)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun upsertFavouriteCategories(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
|
private fun upsertFavouriteCategories(json: JSONArray): Array<ContentProviderResult> {
|
||||||
val uri = uri(authorityFavourites, TABLE_FAVOURITE_CATEGORIES)
|
val uri = uri(authorityFavourites, TABLE_FAVOURITE_CATEGORIES)
|
||||||
val operations = ArrayList<ContentProviderOperation>()
|
val operations = ArrayList<ContentProviderOperation>()
|
||||||
json.mapJSONTo(operations) { jo ->
|
json.mapJSONTo(operations) { jo ->
|
||||||
@@ -150,7 +147,7 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
return provider.applyBatch(operations)
|
return provider.applyBatch(operations)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun upsertFavourites(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
|
private fun upsertFavourites(json: JSONArray): Array<ContentProviderResult> {
|
||||||
val uri = uri(authorityFavourites, TABLE_FAVOURITES)
|
val uri = uri(authorityFavourites, TABLE_FAVOURITES)
|
||||||
val operations = ArrayList<ContentProviderOperation>()
|
val operations = ArrayList<ContentProviderOperation>()
|
||||||
json.mapJSONTo(operations) { jo ->
|
json.mapJSONTo(operations) { jo ->
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class RecentWidgetProvider : BaseAppWidgetProvider() {
|
|||||||
} else {
|
} else {
|
||||||
views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root)
|
views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root)
|
||||||
}
|
}
|
||||||
|
// TODO security
|
||||||
val adapter = Intent(context, RecentWidgetService::class.java)
|
val adapter = Intent(context, RecentWidgetService::class.java)
|
||||||
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
|
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
|
||||||
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class ShelfWidgetProvider : BaseAppWidgetProvider() {
|
|||||||
} else {
|
} else {
|
||||||
views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root)
|
views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root)
|
||||||
}
|
}
|
||||||
|
// TODO security
|
||||||
val adapter = Intent(context, ShelfWidgetService::class.java)
|
val adapter = Intent(context, ShelfWidgetService::class.java)
|
||||||
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
|
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
|
||||||
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/appbar"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/appbar"
|
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,9 @@ buildscript {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.4.0'
|
classpath 'com.android.tools.build:gradle:8.4.0'
|
||||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23'
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24'
|
||||||
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51.1'
|
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51.1'
|
||||||
classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.23-1.0.20'
|
classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.24-1.0.20'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user