Skip to content

Commit

Permalink
Merge pull request #23 from stefanmedack/feature/save_play_back_position
Browse files Browse the repository at this point in the history
Feature/save play back position
  • Loading branch information
stefanmedack authored Apr 11, 2018
2 parents d67f36e + 3ccd763 commit f394c43
Show file tree
Hide file tree
Showing 34 changed files with 549 additions and 82 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ apply plugin: 'io.fabric'
apply plugin: 'com.google.gms.oss.licenses.plugin'

ext {
androidKtx = "0.2"
androidKtx = "0.3"
archComponentsVersion = "1.0.0"
archComponentsLifecycleVersion = "1.1.0"
assertjVersion = "3.8.0"
Expand Down
69 changes: 64 additions & 5 deletions app/schemas/de.stefanmedack.ccctv.persistence.C3Db/4.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "b4f07c1ab0e63f114f2051554ea13acd",
"identityHash": "b9b0ea16175a3837aaec897c21b449e7",
"entities": [
{
"tableName": "bookmarks",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`event_id` INTEGER NOT NULL, PRIMARY KEY(`event_id`), FOREIGN KEY(`event_id`) REFERENCES `events`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`event_id` INTEGER NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`event_id`), FOREIGN KEY(`event_id`) REFERENCES `events`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "eventId",
"columnName": "event_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "created_at",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
Expand All @@ -23,12 +29,12 @@
},
"indices": [
{
"name": "event_idx",
"name": "event_bookmark_idx",
"unique": false,
"columnNames": [
"event_id"
],
"createSql": "CREATE INDEX `event_idx` ON `${TABLE_NAME}` (`event_id`)"
"createSql": "CREATE INDEX `event_bookmark_idx` ON `${TABLE_NAME}` (`event_id`)"
}
],
"foreignKeys": [
Expand Down Expand Up @@ -267,11 +273,64 @@
]
}
]
},
{
"tableName": "play_positions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`event_id` INTEGER NOT NULL, `seconds` INTEGER NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`event_id`), FOREIGN KEY(`event_id`) REFERENCES `events`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "eventId",
"columnName": "event_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "seconds",
"columnName": "seconds",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "created_at",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"event_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "event_play_position_idx",
"unique": false,
"columnNames": [
"event_id"
],
"createSql": "CREATE INDEX `event_play_position_idx` ON `${TABLE_NAME}` (`event_id`)"
}
],
"foreignKeys": [
{
"table": "events",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"event_id"
],
"referencedColumns": [
"id"
]
}
]
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"b4f07c1ab0e63f114f2051554ea13acd\")"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"b9b0ea16175a3837aaec897c21b449e7\")"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ abstract class BaseDbTest {
val bookmarkDao get() = db.bookmarkDao()
val conferenceDao get() = db.conferenceDao()
val eventDao get() = db.eventDao()
val playPositionDao get() = db.playPositionDao()

fun initDbWithConference(conferenceId: Int) {
conferenceDao.insert(minimalConferenceEntity.copy(id = conferenceId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.amshove.kluent.shouldNotEqual
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.OffsetDateTime

@RunWith(AndroidJUnit4::class)
class BookmarkDaoTest : BaseDbTest() {
Expand Down Expand Up @@ -51,7 +52,7 @@ class BookmarkDaoTest : BaseDbTest() {
}

@Test
fun loading_bookmarked_events_does_not_load_not_bookmarked_events() {
fun loading_bookmarked_events_filters_not_bookmarked_events() {
eventDao.insert(minimalEventEntity.copy(conferenceId = 3, id = 42))
bookmarkDao.insert(Bookmark(42))

Expand All @@ -62,15 +63,31 @@ class BookmarkDaoTest : BaseDbTest() {
}

@Test
fun not_bookmarked_events_are_not_bookmarked() {
fun loading_bookmarked_events_should_deliver_the_latest_bookmarks_first() {
for (i in 42..44) {
eventDao.insert(minimalEventEntity.copy(conferenceId = 3, id = i))
}
bookmarkDao.insert(Bookmark(eventId = 42, createdAt = OffsetDateTime.now().minusDays(1)))
bookmarkDao.insert(Bookmark(eventId = 43, createdAt = OffsetDateTime.now()))
bookmarkDao.insert(Bookmark(eventId = 44, createdAt = OffsetDateTime.now().minusDays(2)))

val bookmarkedEvents = bookmarkDao.getBookmarkedEvents().getSingleTestResult()

bookmarkedEvents[0].id shouldEqual 43
bookmarkedEvents[1].id shouldEqual 42
bookmarkedEvents[2].id shouldEqual 44
}

@Test
fun isBookmarked_returns_false_for_not_bookmarked_events() {

val isBookmarked = bookmarkDao.isBookmarked(8).getSingleTestResult()

isBookmarked shouldEqual false
}

@Test
fun bookmarked_events_are_bookmarked() {
fun isBookmarked_returns_true_for_bookmarked_events() {
bookmarkDao.insert(Bookmark(8))

val isBookmarked = bookmarkDao.isBookmarked(8).getSingleTestResult()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package de.stefanmedack.ccctv.persistence

import android.arch.persistence.room.EmptyResultSetException
import android.database.sqlite.SQLiteException
import android.support.test.runner.AndroidJUnit4
import de.stefanmedack.ccctv.getSingleTestResult
import de.stefanmedack.ccctv.minimalEventEntity
import de.stefanmedack.ccctv.persistence.entities.PlayPosition
import org.amshove.kluent.shouldBeInstanceOf
import org.amshove.kluent.shouldEqual
import org.amshove.kluent.shouldNotEqual
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.OffsetDateTime

@RunWith(AndroidJUnit4::class)
class PlayPositionDaoTest : BaseDbTest() {

@Before
fun setup() {
initDbWithConferenceAndEvent(conferenceId = 3, eventId = 8)
}

@Test
fun get_played_events_from_empty_table_returns_empty_list() {

val emptyList = playPositionDao.getPlayedEvents().getSingleTestResult()

emptyList shouldEqual listOf()
}

@Test
fun insert_play_position_without_matching_event_throws_exception() {
val exception = try {
playPositionDao.insert(PlayPosition(eventId = 42))
} catch (ex: SQLiteException) {
ex
}

exception shouldNotEqual null
exception shouldBeInstanceOf SQLiteException::class
}

@Test
fun insert_and_retrieve_played_event() {
playPositionDao.insert(PlayPosition(eventId = 8))

val playedEvents = playPositionDao.getPlayedEvents().getSingleTestResult()

playedEvents.size shouldEqual 1
playedEvents.first().id shouldEqual 8
}

@Test
fun loading_played_events_filters_not_played_events() {
eventDao.insert(minimalEventEntity.copy(conferenceId = 3, id = 42))
playPositionDao.insert(PlayPosition(eventId = 42))

val playedEvents = playPositionDao.getPlayedEvents().getSingleTestResult()

playedEvents.size shouldEqual 1
playedEvents.first().id shouldEqual 42
}

@Test
fun loading_played_events_should_deliver_the_latest_played_events_first() {
for (i in 42..44) {
eventDao.insert(minimalEventEntity.copy(conferenceId = 3, id = i))
}
playPositionDao.insert(PlayPosition(eventId = 42, createdAt = OffsetDateTime.now().minusDays(1)))
playPositionDao.insert(PlayPosition(eventId = 43, createdAt = OffsetDateTime.now()))
playPositionDao.insert(PlayPosition(eventId = 44, createdAt = OffsetDateTime.now().minusDays(2)))

val playedEvents = playPositionDao.getPlayedEvents().getSingleTestResult()

playedEvents[0].id shouldEqual 43
playedEvents[1].id shouldEqual 42
playedEvents[2].id shouldEqual 44
}

@Test
fun loading_playback_seconds_errors_for_not_played_events() {

val seconds = playPositionDao.getPlaybackSeconds(8).test().errors()

seconds.first() shouldBeInstanceOf EmptyResultSetException::class.java
}

@Test
fun loading_playback_seconds_returns_same_seconds_for_played_events() {
playPositionDao.insert(PlayPosition(eventId = 8, seconds = 123))

val seconds = playPositionDao.getPlaybackSeconds(8).getSingleTestResult()

seconds shouldEqual 123
}

@Test
fun delete_play_position_removes_existing_play_position() {
playPositionDao.insert(PlayPosition(eventId = 8, seconds = 123))
playPositionDao.getPlayedEvents().getSingleTestResult().size shouldEqual 1
playPositionDao.getPlaybackSeconds(8).getSingleTestResult() shouldEqual 123

playPositionDao.delete(PlayPosition(eventId = 8))
playPositionDao.getPlayedEvents().getSingleTestResult().size shouldEqual 0
playPositionDao.getPlaybackSeconds(8).test().errorCount() shouldEqual 1
}

@Test
fun delete_play_position_without_matching_event_does_nothing() {
playPositionDao.delete(PlayPosition(eventId = 42))
playPositionDao.delete(PlayPosition(eventId = 43))
}

@Test
fun updating_a_played_event_does_not_change_play_position() {
val updatedEvent = minimalEventEntity.copy(conferenceId = 3, id = 8, title = "updated")
playPositionDao.insert(PlayPosition(eventId = 8))
eventDao.insert(updatedEvent)

val playedEvents = playPositionDao.getPlayedEvents().getSingleTestResult()

playedEvents.size shouldEqual 1
playedEvents.first() shouldEqual updatedEvent
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import de.stefanmedack.ccctv.persistence.C3Db
import de.stefanmedack.ccctv.persistence.daos.BookmarkDao
import de.stefanmedack.ccctv.persistence.daos.ConferenceDao
import de.stefanmedack.ccctv.persistence.daos.EventDao
import de.stefanmedack.ccctv.persistence.daos.PlayPositionDao
import javax.inject.Singleton

@Module
Expand All @@ -34,4 +35,8 @@ class DatabaseModule {
@Singleton
fun provideEventDao(db: C3Db): EventDao = db.eventDao()

@Provides
@Singleton
fun providePlayPositionDao(db: C3Db): PlayPositionDao = db.playPositionDao()

}
6 changes: 5 additions & 1 deletion app/src/main/java/de/stefanmedack/ccctv/persistence/C3Db.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import android.arch.persistence.room.TypeConverters
import de.stefanmedack.ccctv.persistence.daos.BookmarkDao
import de.stefanmedack.ccctv.persistence.daos.ConferenceDao
import de.stefanmedack.ccctv.persistence.daos.EventDao
import de.stefanmedack.ccctv.persistence.daos.PlayPositionDao
import de.stefanmedack.ccctv.persistence.entities.Bookmark
import de.stefanmedack.ccctv.persistence.entities.Conference
import de.stefanmedack.ccctv.persistence.entities.Event
import de.stefanmedack.ccctv.persistence.entities.PlayPosition

@Database(
entities = [
Bookmark::class,
Conference::class,
Event::class
Event::class,
PlayPosition::class
],
version = 4)
@TypeConverters(C3TypeConverters::class)
Expand All @@ -23,5 +26,6 @@ abstract class C3Db : RoomDatabase() {
abstract fun bookmarkDao(): BookmarkDao
abstract fun conferenceDao(): ConferenceDao
abstract fun eventDao(): EventDao
abstract fun playPositionDao(): PlayPositionDao

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import io.reactivex.Flowable
@Dao
interface BookmarkDao {

@Query("SELECT events.* FROM Events INNER JOIN Bookmarks WHERE events.id = bookmarks.event_id")
@Query("SELECT events.* FROM Events INNER JOIN Bookmarks WHERE events.id = bookmarks.event_id ORDER BY created_at DESC")
fun getBookmarkedEvents(): Flowable<List<Event>>

@Query("SELECT COUNT(*) FROM Bookmarks WHERE event_id = :eventId")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package de.stefanmedack.ccctv.persistence.daos

import android.arch.persistence.room.*
import de.stefanmedack.ccctv.persistence.entities.Event
import de.stefanmedack.ccctv.persistence.entities.PlayPosition
import io.reactivex.Flowable
import io.reactivex.Single

@Dao
interface PlayPositionDao {

@Query("SELECT events.* FROM Events INNER JOIN play_positions WHERE events.id = play_positions.event_id ORDER BY created_at DESC")
fun getPlayedEvents(): Flowable<List<Event>>

@Query("SELECT seconds FROM play_positions WHERE event_id = :eventId")
fun getPlaybackSeconds(eventId: Int) : Single<Int>

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(playPosition: PlayPosition)

@Delete
fun delete(playPosition: PlayPosition)

}
Loading

0 comments on commit f394c43

Please sign in to comment.