diff --git a/app/build.gradle b/app/build.gradle
index a414ebf4b..22cc5695f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 34
- versionCode = 633
- versionName = '6.8.3'
+ versionCode = 634
+ versionName = '7.0-a1'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
@@ -104,7 +104,7 @@ dependencies {
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
- implementation 'com.google.android.material:material:1.12.0-beta01'
+ implementation 'com.google.android.material:material:1.12.0-rc01'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.7.0'
implementation 'androidx.webkit:webkit:1.10.0'
diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml
new file mode 100644
index 000000000..5b4f937ef
--- /dev/null
+++ b/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugAD.kt b/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugAD.kt
new file mode 100644
index 000000000..596669443
--- /dev/null
+++ b/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugAD.kt
@@ -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,
+) = adapterDelegateViewBinding(
+ { 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
+ }
+ }
+}
diff --git a/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugItem.kt b/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugItem.kt
new file mode 100644
index 000000000..0e59c15ef
--- /dev/null
+++ b/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugItem.kt
@@ -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
+ }
+}
diff --git a/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugActivity.kt b/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugActivity.kt
new file mode 100644
index 000000000..bcd0c59a0
--- /dev/null
+++ b/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugActivity.kt
@@ -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(), OnListItemClickListener {
+
+ @Inject
+ lateinit var coil: ImageLoader
+
+ private val viewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(ActivityTrackerDebugBinding.inflate(layoutInflater))
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ val tracksAdapter = BaseListAdapter()
+ .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))
+ }
+}
diff --git a/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugViewModel.kt b/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugViewModel.kt
new file mode 100644
index 000000000..942741882
--- /dev/null
+++ b/app/src/debug/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugViewModel.kt
@@ -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.toUiList(): List = 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,
+ )
+ }
+}
diff --git a/app/src/debug/res/layout/activity_tracker_debug.xml b/app/src/debug/res/layout/activity_tracker_debug.xml
new file mode 100644
index 000000000..0ba8c74e9
--- /dev/null
+++ b/app/src/debug/res/layout/activity_tracker_debug.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/debug/res/layout/item_track_debug.xml b/app/src/debug/res/layout/item_track_debug.xml
new file mode 100644
index 000000000..e9f7702d9
--- /dev/null
+++ b/app/src/debug/res/layout/item_track_debug.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/debug/res/menu/opt_settings.xml b/app/src/debug/res/menu/opt_settings.xml
index f54e09cc0..b8bc2c309 100644
--- a/app/src/debug/res/menu/opt_settings.xml
+++ b/app/src/debug/res/menu/opt_settings.xml
@@ -8,4 +8,9 @@
android:title="@string/leak_canary_display_activity_label"
app:showAsAction="never" />
-
\ No newline at end of file
+
+
+
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/SettingsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/SettingsActivity.kt
index 0d85d8b05..b04cc0655 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/SettingsActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/SettingsActivity.kt
@@ -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)
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TracksDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TracksDao.kt
index e05053655..937f3514b 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TracksDao.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TracksDao.kt
@@ -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
+ @Transaction
+ @Query("SELECT * FROM tracks ORDER BY last_check_time DESC")
+ abstract fun observeAll(): Flow>
+
@Query("SELECT manga_id FROM tracks")
abstract suspend fun findAllIds(): LongArray
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/work/TrackWorker.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/work/TrackWorker.kt
index d3060499b..3d6d46fee 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/work/TrackWorker.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/work/TrackWorker.kt
@@ -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()
.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
}
}
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
index 5991f6498..0a060ced1 100644
--- a/app/src/main/res/values/ids.xml
+++ b/app/src/main/res/values/ids.xml
@@ -3,6 +3,7 @@
+