Code cleanup and refactor
This commit is contained in:
@@ -16,8 +16,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 639
|
||||
versionName = '7.0-rc2'
|
||||
versionCode = 640
|
||||
versionName = '7.0-rc3'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
||||
ksp {
|
||||
@@ -87,8 +87,8 @@ dependencies {
|
||||
}
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.23'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.24'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.core:core-ktx:1.13.1'
|
||||
@@ -151,14 +151,14 @@ dependencies {
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
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:rules:1.5.0'
|
||||
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
|
||||
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 'com.squareup.moshi:moshi-kotlin:1.15.1'
|
||||
|
||||
@@ -36,7 +36,7 @@ class CaptchaNotifier(
|
||||
.build()
|
||||
manager.createNotificationChannel(channel)
|
||||
|
||||
val intent = CloudFlareActivity.newIntent(context, exception.url, exception.headers)
|
||||
val intent = CloudFlareActivity.newIntent(context, exception)
|
||||
.setData(exception.url.toUri())
|
||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setContentTitle(channel.name)
|
||||
|
||||
@@ -23,12 +23,15 @@ import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.koitharu.kotatsu.R
|
||||
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.cookies.MutableCookieJar
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.util.TaggedActivityResult
|
||||
import org.koitharu.kotatsu.core.util.ext.configureForParser
|
||||
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import javax.inject.Inject
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
@@ -137,6 +140,10 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
|
||||
|
||||
override fun onCheckPassed() {
|
||||
pendingResult = RESULT_OK
|
||||
val source = intent?.getStringExtra(ARG_SOURCE)
|
||||
if (source != null) {
|
||||
CaptchaNotifier(this).dismiss(MangaSource(source))
|
||||
}
|
||||
finishAfterTransition()
|
||||
}
|
||||
|
||||
@@ -174,9 +181,9 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
|
||||
}
|
||||
}
|
||||
|
||||
class Contract : ActivityResultContract<Pair<String, Headers?>, TaggedActivityResult>() {
|
||||
override fun createIntent(context: Context, input: Pair<String, Headers?>): Intent {
|
||||
return newIntent(context, input.first, input.second)
|
||||
class Contract : ActivityResultContract<CloudFlareProtectedException, TaggedActivityResult>() {
|
||||
override fun createIntent(context: Context, input: CloudFlareProtectedException): Intent {
|
||||
return newIntent(context, input)
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): TaggedActivityResult {
|
||||
@@ -188,13 +195,23 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
|
||||
|
||||
const val TAG = "CloudFlareActivity"
|
||||
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,
|
||||
url: String,
|
||||
source: MangaSource?,
|
||||
headers: Headers?,
|
||||
) = Intent(context, CloudFlareActivity::class.java).apply {
|
||||
data = url.toUri()
|
||||
putExtra(ARG_SOURCE, source?.name)
|
||||
headers?.get(CommonHeaders.USER_AGENT)?.let {
|
||||
putExtra(ARG_UA, it)
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ abstract class MangaSourcesDao {
|
||||
@Query("SELECT * FROM sources ORDER BY sort_key")
|
||||
abstract suspend fun findAll(): List<MangaSourceEntity>
|
||||
|
||||
@Query("SELECT * FROM sources WHERE enabled = 0 ORDER BY sort_key")
|
||||
abstract suspend fun findAllDisabled(): List<MangaSourceEntity>
|
||||
@Query("SELECT source FROM sources WHERE enabled = 1")
|
||||
abstract suspend fun findAllEnabledNames(): List<String>
|
||||
|
||||
@Query("SELECT * FROM sources ORDER BY sort_key")
|
||||
abstract fun observeAll(): Flow<List<MangaSourceEntity>>
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.annotation.StringRes
|
||||
import androidx.collection.ArrayMap
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import okhttp3.Headers
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.alternatives.ui.AlternativesActivity
|
||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||
@@ -30,7 +29,7 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
||||
private val activity: FragmentActivity?
|
||||
private val fragment: Fragment?
|
||||
private val sourceAuthContract: ActivityResultLauncher<MangaSource>
|
||||
private val cloudflareContract: ActivityResultLauncher<Pair<String, Headers?>>
|
||||
private val cloudflareContract: ActivityResultLauncher<CloudFlareProtectedException>
|
||||
|
||||
constructor(activity: FragmentActivity) {
|
||||
this.activity = activity
|
||||
@@ -55,7 +54,7 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
||||
}
|
||||
|
||||
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 NotFoundException -> {
|
||||
openInBrowser(e.url)
|
||||
@@ -70,9 +69,9 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
||||
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
|
||||
cloudflareContract.launch(url to headers)
|
||||
cloudflareContract.launch(e)
|
||||
}
|
||||
|
||||
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.parsers.model.Manga
|
||||
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.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
||||
@@ -91,6 +92,14 @@ class AppShortcutManager @Inject constructor(
|
||||
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
|
||||
suspend fun await(): Boolean {
|
||||
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.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
import org.koitharu.kotatsu.parsers.util.SuspendLazy
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@@ -38,15 +38,10 @@ class MangaLoaderContextImpl @Inject constructor(
|
||||
) : MangaLoaderContext() {
|
||||
|
||||
private var webViewCached: WeakReference<WebView>? = null
|
||||
|
||||
private val userAgentLazy = SuspendLazy {
|
||||
withContext(Dispatchers.Main) {
|
||||
obtainWebView().settings.userAgentString
|
||||
}.sanitizeHeaderValue()
|
||||
}
|
||||
private val webViewUserAgent by lazy { obtainWebViewUserAgent() }
|
||||
|
||||
@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()
|
||||
suspendCoroutine { cont ->
|
||||
webView.evaluateJavascript(script) { result ->
|
||||
@@ -55,13 +50,7 @@ class MangaLoaderContextImpl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDefaultUserAgent(): String = runCatching {
|
||||
runBlocking {
|
||||
userAgentLazy.get()
|
||||
}
|
||||
}.onFailure { e ->
|
||||
e.printStackTraceDebug()
|
||||
}.getOrDefault(UserAgents.FIREFOX_MOBILE)
|
||||
override fun getDefaultUserAgent(): String = webViewUserAgent
|
||||
|
||||
override fun getConfig(source: MangaSource): MangaSourceConfig {
|
||||
return SourceSettings(androidContext, source)
|
||||
@@ -86,4 +75,22 @@ class MangaLoaderContextImpl @Inject constructor(
|
||||
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(
|
||||
private val parser: MangaParser,
|
||||
private val cache: MemoryContentCache, // TODO fix concurrency
|
||||
private val cache: MemoryContentCache,
|
||||
private val mirrorSwitchInterceptor: MirrorSwitchInterceptor,
|
||||
) : 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 {
|
||||
if (source == MangaSource.LOCAL) {
|
||||
return false
|
||||
}
|
||||
if (context.isPowerSaveMode()) {
|
||||
if (source == MangaSource.LOCAL || context.isPowerSaveMode()) {
|
||||
return false
|
||||
}
|
||||
val entryPoint = EntryPointAccessors.fromApplication(
|
||||
|
||||
@@ -173,7 +173,7 @@ class DetailsViewModel @Inject constructor(
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())
|
||||
|
||||
val branches: StateFlow<List<MangaBranch>> = combine(
|
||||
details,
|
||||
@@ -220,7 +220,7 @@ class DetailsViewModel @Inject constructor(
|
||||
chaptersQuery,
|
||||
) { list, reversed, query ->
|
||||
(if (reversed) list.asReversed() else list).filterSearch(query)
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
|
||||
|
||||
val readingTime = combine(
|
||||
details,
|
||||
@@ -228,7 +228,7 @@ class DetailsViewModel @Inject constructor(
|
||||
history,
|
||||
) { m, b, h ->
|
||||
readingTimeUseCase.invoke(m, b, h)
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, null)
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, null)
|
||||
|
||||
val selectedBranchValue: String?
|
||||
get() = selectedBranch.value
|
||||
|
||||
@@ -322,7 +322,7 @@ class DownloadsViewModel @Inject constructor(
|
||||
emit(mapChapters())
|
||||
}
|
||||
}
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||
|
||||
private suspend fun tryLoad(manga: Manga) = runCatchingCancellable {
|
||||
(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.observeAsFlow
|
||||
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.util.mapToSet
|
||||
import java.util.Collections
|
||||
@@ -48,8 +47,17 @@ class MangaSourcesRepository @Inject constructor(
|
||||
return dao.findAllEnabled(order).toSources(settings.isNsfwContentDisabled, order)
|
||||
}
|
||||
|
||||
suspend fun getDisabledSources(): List<MangaSource> {
|
||||
return dao.findAllDisabled().toSources(settings.isNsfwContentDisabled, null)
|
||||
suspend fun getDisabledSources(): Set<MangaSource> {
|
||||
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> {
|
||||
@@ -143,6 +151,7 @@ class MangaSourcesRepository @Inject constructor(
|
||||
result
|
||||
}.distinctUntilChanged()
|
||||
} else {
|
||||
assimilateNewSources()
|
||||
flowOf(emptySet())
|
||||
}
|
||||
}
|
||||
@@ -199,7 +208,7 @@ class MangaSourcesRepository @Inject constructor(
|
||||
val result = ArrayList<MangaSource>(size)
|
||||
for (entity in this) {
|
||||
val source = MangaSource(entity.source)
|
||||
if (skipNsfwSources && source.contentType == ContentType.HENTAI) {
|
||||
if (skipNsfwSources && source.isNsfw()) {
|
||||
continue
|
||||
}
|
||||
if (source in remoteSources) {
|
||||
|
||||
@@ -17,11 +17,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity
|
||||
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.dialog.TwoButtonsAlertDialog
|
||||
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.observe
|
||||
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.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
|
||||
@@ -64,9 +61,6 @@ class ExploreFragment :
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
@Inject
|
||||
lateinit var shortcutManager: AppShortcutManager
|
||||
|
||||
private val viewModel by viewModels<ExploreViewModel>()
|
||||
private var exploreAdapter: ExploreAdapter? = null
|
||||
private var sourceSelectionController: ListSelectionController? = null
|
||||
@@ -213,9 +207,7 @@ class ExploreFragment :
|
||||
|
||||
R.id.action_shortcut -> {
|
||||
val source = selectedSources.singleOrNull() ?: return false
|
||||
viewLifecycleScope.launch {
|
||||
shortcutManager.requestPinShortcut(source)
|
||||
}
|
||||
viewModel.requestPinShortcut(source)
|
||||
mode.finish()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
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.observeAsFlow
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||
@@ -43,6 +44,7 @@ class ExploreViewModel @Inject constructor(
|
||||
private val suggestionRepository: SuggestionRepository,
|
||||
private val exploreRepository: ExploreRepository,
|
||||
private val sourcesRepository: MangaSourcesRepository,
|
||||
private val shortcutManager: AppShortcutManager,
|
||||
) : BaseViewModel() {
|
||||
|
||||
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) {
|
||||
settings.isSuggestionsEnabled = isAccepted
|
||||
settings.closeTip(TIP_SUGGESTIONS)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -177,10 +175,4 @@ class FavouriteCategoriesActivity :
|
||||
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
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -16,7 +17,7 @@ fun categoriesHeaderAD() = adapterDelegateViewBinding<CategoriesHeaderItem, List
|
||||
val onClickListener = View.OnClickListener { v ->
|
||||
val intent = when (v.id) {
|
||||
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
|
||||
}
|
||||
v.context.startActivity(intent)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.container
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -102,7 +103,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
@@ -19,7 +20,7 @@ class FavouritesContainerMenuProvider(
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_manage -> {
|
||||
context.startActivity(FavouriteCategoriesActivity.newIntent(context))
|
||||
context.startActivity(Intent(context, FavouriteCategoriesActivity::class.java))
|
||||
}
|
||||
|
||||
else -> return false
|
||||
|
||||
@@ -82,7 +82,7 @@ class ListConfigViewModel @Inject constructor(
|
||||
ListConfigSection.General -> null
|
||||
ListConfigSection.Updated -> null
|
||||
ListConfigSection.History -> settings.historySortOrder
|
||||
ListConfigSection.Suggestions -> ListSortOrder.RELEVANCE // TODO
|
||||
ListConfigSection.Suggestions -> ListSortOrder.RELEVANCE
|
||||
}
|
||||
|
||||
fun setSortOrder(position: Int) {
|
||||
|
||||
@@ -72,9 +72,6 @@ class SingleMangaImporter @Inject constructor(
|
||||
return LocalMangaInput.of(dest).getManga()
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: progress
|
||||
*/
|
||||
private suspend fun DocumentFile.copyTo(destDir: File) {
|
||||
if (isDirectory) {
|
||||
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.widgets.SlidingBottomNavigationView
|
||||
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.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
||||
@@ -77,7 +76,7 @@ private const val TAG_SEARCH = "search"
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNavOwner,
|
||||
View.OnClickListener,
|
||||
View.OnFocusChangeListener, SearchSuggestionListener,
|
||||
MainNavigationDelegate.OnFragmentChangedListener {
|
||||
MainNavigationDelegate.OnFragmentChangedListener, View.OnLayoutChangeListener {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
@@ -136,6 +135,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
}
|
||||
viewModel.isBottomNavPinned.observe(this, ::setNavbarPinned)
|
||||
searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)
|
||||
viewBinding.bottomNav?.addOnLayoutChangeListener(this)
|
||||
}
|
||||
|
||||
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) {
|
||||
val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH)
|
||||
if (v?.id == R.id.searchView && hasFocus) {
|
||||
@@ -418,12 +434,17 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
view.layoutParams = lp
|
||||
}
|
||||
}
|
||||
viewBinding.container.updateLayoutParams<MarginLayoutParams> {
|
||||
bottomMargin = if (isPinned) {
|
||||
(bottomNavBar?.measureHeight()
|
||||
?.coerceAtLeast(resources.getDimensionPixelSize(materialR.dimen.m3_bottom_nav_min_height)) ?: 0)
|
||||
} else {
|
||||
0
|
||||
updateContainerBottomMargin()
|
||||
}
|
||||
|
||||
private fun updateContainerBottomMargin() {
|
||||
val bottomNavBar = viewBinding.bottomNav ?: return
|
||||
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.animation.Interpolator
|
||||
import android.widget.Scroller
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
@@ -57,7 +56,7 @@ class DoublePageSnapHelper : SnapHelper() {
|
||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||
check(layoutManager.canScrollHorizontally()) { "RecyclerView must be scrollable" }
|
||||
orientationHelper = OrientationHelper.createHorizontalHelper(layoutManager)
|
||||
layoutDirectionHelper = LayoutDirectionHelper(ViewCompat.getLayoutDirection(recyclerView))
|
||||
layoutDirectionHelper = LayoutDirectionHelper(recyclerView.layoutDirection)
|
||||
scroller = Scroller(target.context, snapInterpolator)
|
||||
initItemDimensionIfNeeded(layoutManager)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.OverScroller
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.view.GestureDetectorCompat
|
||||
import androidx.core.view.ViewConfigurationCompat
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.widgets.ZoomControl
|
||||
@@ -39,7 +38,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
|
||||
ZoomControl.ZoomControlListener {
|
||||
|
||||
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 transformMatrix = Matrix()
|
||||
@@ -339,7 +338,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
|
||||
if (overScroller.computeScrollOffset()) {
|
||||
transformMatrix.postTranslate(
|
||||
overScroller.currX.toFloat() - prevPos.x,
|
||||
overScroller.currY.toFloat() - prevPos.y
|
||||
overScroller.currY.toFloat() - prevPos.y,
|
||||
)
|
||||
prevPos.set(overScroller.currX, overScroller.currY)
|
||||
invalidateTarget()
|
||||
|
||||
@@ -31,7 +31,7 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) {
|
||||
ScrobblerUser(
|
||||
id = lines[0].toLong(),
|
||||
nickname = lines[1],
|
||||
avatar = lines[2],
|
||||
avatar = lines[2].takeUnless(String::isEmpty),
|
||||
service = ScrobblerService.valueOf(lines[3]),
|
||||
)
|
||||
}
|
||||
@@ -43,7 +43,7 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) {
|
||||
val str = StringJoiner("\n")
|
||||
.add(value.id)
|
||||
.add(value.nickname)
|
||||
.add(value.avatar)
|
||||
.add(value.avatar.orEmpty())
|
||||
.add(value.service.name)
|
||||
.complete()
|
||||
putString(KEY_USER, str)
|
||||
|
||||
@@ -67,11 +67,9 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
if (intent != null) {
|
||||
setIntent(intent)
|
||||
processIntent(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
val rv = viewBinding.recyclerView
|
||||
|
||||
@@ -20,7 +20,7 @@ class SplitSwitchPreference @JvmOverloads constructor(
|
||||
|
||||
var onContainerClickListener: OnPreferenceClickListener? = null
|
||||
|
||||
private val containerClickListener = View.OnClickListener { v ->
|
||||
private val containerClickListener = View.OnClickListener {
|
||||
onContainerClickListener?.onPreferenceClick(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,18 @@
|
||||
package org.koitharu.kotatsu.stats.ui.views
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.DashPathEffect
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PathEffect
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffXfermode
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Xfermode
|
||||
import android.util.AttributeSet
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
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.resolveDp
|
||||
import org.koitharu.kotatsu.parsers.util.replaceWith
|
||||
import org.koitharu.kotatsu.parsers.util.toIntUp
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.random.Random
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
@@ -126,7 +109,7 @@ class BarChartView @JvmOverloads constructor(
|
||||
bars.clear()
|
||||
return
|
||||
}
|
||||
var fullWidth = rawData.size * (barWidth + minBarSpacing) + minBarSpacing
|
||||
val fullWidth = rawData.size * (barWidth + minBarSpacing) + minBarSpacing
|
||||
val windowSize = (fullWidth / width.toFloat()).toIntUp()
|
||||
bars.replaceWith(
|
||||
rawData.chunked(windowSize) { it.average() },
|
||||
|
||||
@@ -3,28 +3,17 @@ package org.koitharu.kotatsu.stats.ui.views
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffXfermode
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Xfermode
|
||||
import android.util.AttributeSet
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.collection.MutableIntList
|
||||
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.resolveDp
|
||||
import org.koitharu.kotatsu.parsers.util.replaceWith
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.sqrt
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class PieChartView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
@@ -34,8 +23,8 @@ class PieChartView @JvmOverloads constructor(
|
||||
private val segments = ArrayList<Segment>()
|
||||
private val chartBounds = RectF()
|
||||
private val clearColor = context.getThemeColor(android.R.attr.colorBackground)
|
||||
private val touchDetector = GestureDetectorCompat(context, this)
|
||||
private var hightlightedSegment = -1
|
||||
private val touchDetector = GestureDetector(context, this)
|
||||
private var highlightedSegment = -1
|
||||
|
||||
var onSegmentClickListener: OnSegmentClickListener? = null
|
||||
|
||||
@@ -49,7 +38,7 @@ class PieChartView @JvmOverloads constructor(
|
||||
var angle = 0f
|
||||
for ((i, segment) in segments.withIndex()) {
|
||||
paint.color = segment.color
|
||||
if (i == hightlightedSegment) {
|
||||
if (i == highlightedSegment) {
|
||||
paint.color = ColorUtils.setAlphaComponent(paint.color, 180)
|
||||
}
|
||||
paint.style = Paint.Style.FILL
|
||||
@@ -91,7 +80,7 @@ class PieChartView @JvmOverloads constructor(
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
if (event.actionMasked == MotionEvent.ACTION_CANCEL || event.actionMasked == MotionEvent.ACTION_UP) {
|
||||
hightlightedSegment = -1
|
||||
highlightedSegment = -1
|
||||
invalidate()
|
||||
}
|
||||
return super.onTouchEvent(event) || touchDetector.onTouchEvent(event)
|
||||
@@ -102,8 +91,8 @@ class PieChartView @JvmOverloads constructor(
|
||||
return false
|
||||
}
|
||||
val segment = findSegmentIndex(e.x, e.y)
|
||||
if (segment != hightlightedSegment) {
|
||||
hightlightedSegment = segment
|
||||
if (segment != highlightedSegment) {
|
||||
highlightedSegment = segment
|
||||
invalidate()
|
||||
return true
|
||||
} else {
|
||||
|
||||
@@ -79,12 +79,10 @@ class SyncHelper @AssistedInject constructor(
|
||||
.build()
|
||||
val response = httpClient.newCall(request).execute().log().parseJsonOrNull()
|
||||
if (response != null) {
|
||||
val timestamp = response.getLong(FIELD_TIMESTAMP)
|
||||
val categoriesResult =
|
||||
upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES), timestamp)
|
||||
val categoriesResult = upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES))
|
||||
stats.numDeletes += categoriesResult.first().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.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
||||
stats.numEntries += stats.numInserts + stats.numDeletes
|
||||
@@ -105,7 +103,6 @@ class SyncHelper @AssistedInject constructor(
|
||||
if (response != null) {
|
||||
val result = upsertHistory(
|
||||
json = response.getJSONArray(TABLE_HISTORY),
|
||||
timestamp = response.getLong(FIELD_TIMESTAMP),
|
||||
)
|
||||
stats.numDeletes += result.first().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) {
|
||||
if (logger.isEnabled) {
|
||||
logger.log("Sync finshed: ${result.toDebugString()}")
|
||||
logger.log("Sync finished: ${result.toDebugString()}")
|
||||
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 operations = ArrayList<ContentProviderOperation>()
|
||||
json.mapJSONTo(operations) { jo ->
|
||||
@@ -139,7 +136,7 @@ class SyncHelper @AssistedInject constructor(
|
||||
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 operations = ArrayList<ContentProviderOperation>()
|
||||
json.mapJSONTo(operations) { jo ->
|
||||
@@ -150,7 +147,7 @@ class SyncHelper @AssistedInject constructor(
|
||||
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 operations = ArrayList<ContentProviderOperation>()
|
||||
json.mapJSONTo(operations) { jo ->
|
||||
|
||||
@@ -31,6 +31,7 @@ class RecentWidgetProvider : BaseAppWidgetProvider() {
|
||||
} else {
|
||||
views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root)
|
||||
}
|
||||
// TODO security
|
||||
val adapter = Intent(context, RecentWidgetService::class.java)
|
||||
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
|
||||
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
||||
|
||||
@@ -31,6 +31,7 @@ class ShelfWidgetProvider : BaseAppWidgetProvider() {
|
||||
} else {
|
||||
views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root)
|
||||
}
|
||||
// TODO security
|
||||
val adapter = Intent(context, ShelfWidgetService::class.java)
|
||||
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
|
||||
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/appbar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||
|
||||
@@ -301,7 +301,7 @@
|
||||
<string name="other_cache">Other cache</string>
|
||||
<string name="storage_usage">Storage usage</string>
|
||||
<string name="available">Available</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="memory_usage_pattern">%1$s - %2$s</string>
|
||||
<string name="removed_from_favourites">Removed from favourites</string>
|
||||
<string name="options">Options</string>
|
||||
<string name="not_found_404">Content not found or removed</string>
|
||||
|
||||
@@ -5,9 +5,9 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
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.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