Collecting reading stats
parent
8cc04b0f7a
commit
6cb6c891dd
@ -0,0 +1,12 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration18To19 : Migration(18, 19) {
|
||||||
|
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL("ALTER TABLE history ADD COLUMN `chapters` INTEGER NOT NULL DEFAULT -1")
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS `stats` (`manga_id` INTEGER NOT NULL, `started_at` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `pages` INTEGER NOT NULL, PRIMARY KEY(`manga_id`, `started_at`), FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package org.koitharu.kotatsu.stats.data
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Upsert
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class StatsDao {
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
abstract suspend fun upsert(entity: StatsEntity)
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.koitharu.kotatsu.stats.data
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "stats",
|
||||||
|
primaryKeys = ["manga_id", "started_at"],
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = MangaEntity::class,
|
||||||
|
parentColumns = ["manga_id"],
|
||||||
|
childColumns = ["manga_id"],
|
||||||
|
onDelete = ForeignKey.CASCADE,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
data class StatsEntity(
|
||||||
|
@ColumnInfo(name = "manga_id") val mangaId: Long,
|
||||||
|
@ColumnInfo(name = "started_at") val startedAt: Long,
|
||||||
|
@ColumnInfo(name = "duration") val duration: Long,
|
||||||
|
@ColumnInfo(name = "pages") val pages: Int,
|
||||||
|
)
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
package org.koitharu.kotatsu.stats.domain
|
||||||
|
|
||||||
|
import androidx.collection.LongSparseArray
|
||||||
|
import androidx.collection.set
|
||||||
|
import dagger.hilt.android.ViewModelLifecycle
|
||||||
|
import dagger.hilt.android.scopes.ViewModelScoped
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
||||||
|
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||||
|
import org.koitharu.kotatsu.stats.data.StatsEntity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ViewModelScoped
|
||||||
|
class StatsCollector @Inject constructor(
|
||||||
|
private val db: MangaDatabase,
|
||||||
|
private val settings: AppSettings,
|
||||||
|
lifecycle: ViewModelLifecycle,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val viewModelScope = RetainedLifecycleCoroutineScope(lifecycle)
|
||||||
|
private val stats = LongSparseArray<Entry>(1)
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun onStateChanged(mangaId: Long, state: ReaderState) {
|
||||||
|
if (!settings.isStatsEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val entry = stats[mangaId]
|
||||||
|
if (entry == null) {
|
||||||
|
stats[mangaId] = Entry(
|
||||||
|
state = state,
|
||||||
|
stats = StatsEntity(
|
||||||
|
mangaId = mangaId,
|
||||||
|
startedAt = now,
|
||||||
|
duration = 0,
|
||||||
|
pages = 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val pagesDelta = if (entry.state.page != state.page || entry.state.chapterId != state.chapterId) 1 else 0
|
||||||
|
val newEntry = entry.copy(
|
||||||
|
stats = StatsEntity(
|
||||||
|
mangaId = mangaId,
|
||||||
|
startedAt = entry.stats.startedAt,
|
||||||
|
duration = now - entry.stats.startedAt,
|
||||||
|
pages = entry.stats.pages + pagesDelta,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
stats[mangaId] = newEntry
|
||||||
|
commit(newEntry.stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun commit(entity: StatsEntity) {
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
db.getStatsDao().upsert(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Entry(
|
||||||
|
val state: ReaderState,
|
||||||
|
val stats: StatsEntity,
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue