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