Tracker debug info

This commit is contained in:
Koitharu
2024-04-11 10:19:32 +03:00
parent 59dd53c025
commit 4d7ff5f6cc
13 changed files with 323 additions and 7 deletions

View File

@@ -0,0 +1,12 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".tracker.ui.debug.TrackerDebugActivity"
android:label="@string/check_for_new_chapters" />
</application>
</manifest>

View File

@@ -0,0 +1,73 @@
package org.koitharu.kotatsu.tracker.ui.debug
import android.graphics.Color
import android.text.format.DateUtils
import androidx.core.content.ContextCompat
import androidx.core.text.bold
import androidx.core.text.buildSpannedString
import androidx.core.text.color
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.drawableStart
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemTrackDebugBinding
import org.koitharu.kotatsu.tracker.data.TrackEntity
import com.google.android.material.R as materialR
fun trackDebugAD(
lifecycleOwner: LifecycleOwner,
coil: ImageLoader,
clickListener: OnListItemClickListener<TrackDebugItem>,
) = adapterDelegateViewBinding<TrackDebugItem, TrackDebugItem, ItemTrackDebugBinding>(
{ layoutInflater, parent -> ItemTrackDebugBinding.inflate(layoutInflater, parent, false) },
) {
val indicatorNew = ContextCompat.getDrawable(context, R.drawable.ic_new)
itemView.setOnClickListener { v ->
clickListener.onItemClick(item, v)
}
bind {
binding.imageViewCover.newImageRequest(lifecycleOwner, item.manga.coverUrl)?.run {
placeholder(R.drawable.ic_placeholder)
fallback(R.drawable.ic_placeholder)
error(R.drawable.ic_error_placeholder)
allowRgb565(true)
source(item.manga.source)
enqueueWith(coil)
}
binding.textViewTitle.text = item.manga.title
binding.textViewSummary.text = buildSpannedString {
item.lastCheckTime?.let {
append(
DateUtils.getRelativeDateTimeString(
context,
it.toEpochMilli(),
DateUtils.MINUTE_IN_MILLIS,
DateUtils.WEEK_IN_MILLIS,
0,
),
)
}
if (item.lastResult == TrackEntity.RESULT_FAILED) {
append(" - ")
bold {
color(context.getThemeColor(materialR.attr.colorError, Color.RED)) {
append(getString(R.string.error))
}
}
}
}
binding.textViewTitle.drawableStart = if (item.newChapters > 0) {
indicatorNew
} else {
null
}
}
}

View File

@@ -0,0 +1,19 @@
package org.koitharu.kotatsu.tracker.ui.debug
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import java.time.Instant
data class TrackDebugItem(
val manga: Manga,
val lastChapterId: Long,
val newChapters: Int,
val lastCheckTime: Instant?,
val lastChapterDate: Instant?,
val lastResult: Int,
) : ListModel {
override fun areItemsTheSame(other: ListModel): Boolean {
return other is TrackDebugItem && other.manga.id == manga.id
}
}

View File

@@ -0,0 +1,57 @@
package org.koitharu.kotatsu.tracker.ui.debug
import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.databinding.ActivityTrackerDebugBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import javax.inject.Inject
@AndroidEntryPoint
class TrackerDebugActivity : BaseActivity<ActivityTrackerDebugBinding>(), OnListItemClickListener<TrackDebugItem> {
@Inject
lateinit var coil: ImageLoader
private val viewModel by viewModels<TrackerDebugViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityTrackerDebugBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val tracksAdapter = BaseListAdapter<TrackDebugItem>()
.addDelegate(ListItemType.FEED, trackDebugAD(this, coil, this))
with(viewBinding.recyclerView) {
adapter = tracksAdapter
addItemDecoration(TypedListSpacingDecoration(context, false))
}
viewModel.content.observe(this, tracksAdapter)
}
override fun onWindowInsetsChanged(insets: Insets) {
val rv = viewBinding.recyclerView
rv.updatePadding(
left = insets.left + rv.paddingTop,
right = insets.right + rv.paddingTop,
bottom = insets.bottom,
)
viewBinding.toolbar.updatePadding(
left = insets.left,
right = insets.right,
)
}
override fun onItemClick(item: TrackDebugItem, view: View) {
startActivity(DetailsActivity.newIntent(this, item.manga))
}
}

View File

@@ -0,0 +1,36 @@
package org.koitharu.kotatsu.tracker.ui.debug
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.toInstantOrNull
import org.koitharu.kotatsu.tracker.data.TrackWithManga
import javax.inject.Inject
@HiltViewModel
class TrackerDebugViewModel @Inject constructor(
private val db: MangaDatabase
) : BaseViewModel() {
val content = db.getTracksDao().observeAll()
.map { it.toUiList() }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
private fun List<TrackWithManga>.toUiList(): List<TrackDebugItem> = map {
TrackDebugItem(
manga = it.manga.toManga(emptySet()),
lastChapterId = it.track.lastChapterId,
newChapters = it.track.newChapters,
lastCheckTime = it.track.lastCheckTime.toInstantOrNull(),
lastChapterDate = it.track.lastChapterDate.toInstantOrNull(),
lastResult = it.track.lastResult,
)
}
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:toolbarId="@id/toolbar">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="@dimen/list_spacing_normal"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:listitem="@layout/item_track_debug" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="72dp"
android:background="@drawable/list_selector"
android:clipChildren="false">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" />
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceTitleSmall"
app:layout_constraintBottom_toTopOf="@+id/textView_summary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView_cover"
app:layout_constraintTop_toTopOf="@+id/imageView_cover"
tools:text="@tools:sample/lorem" />
<TextView
android:id="@+id/textView_summary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:drawablePadding="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceBodySmall"
app:layout_constraintBottom_toBottomOf="@+id/imageView_cover"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView_cover"
app:layout_constraintTop_toBottomOf="@+id/textView_title"
tools:text="@tools:sample/lorem/random" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -8,4 +8,9 @@
android:title="@string/leak_canary_display_activity_label"
app:showAsAction="never" />
</menu>
<item
android:id="@id/action_tracker"
android:title="@string/check_for_new_chapters"
app:showAsAction="never" />
</menu>

View File

@@ -96,6 +96,13 @@ class SettingsActivity :
true
}
R.id.action_tracker -> {
val intent = Intent()
intent.component = ComponentName(this, "org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity")
startActivity(intent)
true
}
else -> super.onOptionsItemSelected(item)
}

View File

@@ -18,6 +18,10 @@ abstract class TracksDao {
@Query("SELECT * FROM tracks ORDER BY last_check_time ASC LIMIT :limit OFFSET :offset")
abstract suspend fun findAll(offset: Int, limit: Int): List<TrackWithManga>
@Transaction
@Query("SELECT * FROM tracks ORDER BY last_check_time DESC")
abstract fun observeAll(): Flow<List<TrackWithManga>>
@Query("SELECT manga_id FROM tracks")
abstract suspend fun findAllIds(): LongArray

View File

@@ -42,6 +42,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
import org.koitharu.kotatsu.core.db.MangaDatabase
@@ -337,8 +338,9 @@ class TrackWorker @AssistedInject constructor(
}
fun startNow() {
val constraints =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = OneTimeWorkRequestBuilder<TrackWorker>()
.setConstraints(constraints)
.addTag(TAG_ONESHOT)
@@ -370,6 +372,6 @@ class TrackWorker @AssistedInject constructor(
const val MAX_ATTEMPTS = 3
const val DATA_KEY_SUCCESS = "success"
const val DATA_KEY_FAILED = "failed"
const val BATCH_SIZE = 20
val BATCH_SIZE = if (BuildConfig.DEBUG) 20 else 46
}
}

View File

@@ -3,6 +3,7 @@
<item name="toolbar" type="id" />
<item name="container" type="id" />
<item name="action_leaks" type="id" />
<item name="action_tracker" type="id" />
<item name="fast_scroller" type="id" />
<item name="group_branches" type="id" />
<item name="layout_tip" type="id" />