diff --git a/app/src/androidTest/assets/sample-db-schema-v0.realm b/app/src/androidTest/assets/sample-db-schema-v0.realm
new file mode 100644
index 0000000..b65f0f2
Binary files /dev/null and b/app/src/androidTest/assets/sample-db-schema-v0.realm differ
diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepositoryTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/CoffeeEventRepositoryTest.kt
similarity index 76%
rename from app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepositoryTest.kt
rename to app/src/androidTest/java/com/codemate/koffeemate/data/local/CoffeeEventRepositoryTest.kt
index cd1f1ed..c6f07b2 100644
--- a/app/src/androidTest/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepositoryTest.kt
+++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/CoffeeEventRepositoryTest.kt
@@ -16,7 +16,8 @@
package com.codemate.koffeemate.data.local
-import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent
+import com.codemate.koffeemate.data.models.CoffeeBrewingEvent
+import com.codemate.koffeemate.data.models.User
import io.realm.Realm
import io.realm.RealmConfiguration
import org.hamcrest.core.IsEqual.equalTo
@@ -24,7 +25,7 @@ import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
-class RealmCoffeeEventRepositoryTest {
+class CoffeeEventRepositoryTest {
lateinit var coffeeEventRepository: RealmCoffeeEventRepository
@Before
@@ -51,9 +52,9 @@ class RealmCoffeeEventRepositoryTest {
@Test
fun recordBrewingEvent_WithUserId_SavesUserId() {
- coffeeEventRepository.recordBrewingEvent("abc123")
+ coffeeEventRepository.recordBrewingEvent(User(id = "abc123"))
- assertThat(coffeeEventRepository.getLastBrewingEvent()!!.userId, equalTo("abc123"))
+ assertThat(coffeeEventRepository.getLastBrewingEvent()!!.user!!.id, equalTo("abc123"))
}
@Test
@@ -68,35 +69,35 @@ class RealmCoffeeEventRepositoryTest {
@Test
fun getLastBrewingEvent_WhenHavingAccidentsAndSuccessfulEvents_ReturnsOnlyLastBrewingEvent() {
val lastSuccessfulEvent = coffeeEventRepository.recordBrewingEvent()
- coffeeEventRepository.recordBrewingAccident("test")
+ coffeeEventRepository.recordBrewingAccident(User())
assertThat(coffeeEventRepository.getLastBrewingEvent(), equalTo(lastSuccessfulEvent))
}
@Test
fun getLastBrewingAccident_ReturnsLastBrewingAccident() {
- val userId = "abc123"
- coffeeEventRepository.recordBrewingAccident(userId)
- coffeeEventRepository.recordBrewingAccident(userId)
+ val user = User(id = "abc123")
+ coffeeEventRepository.recordBrewingAccident(user)
+ coffeeEventRepository.recordBrewingAccident(user)
- val lastAccident = coffeeEventRepository.recordBrewingAccident(userId)
+ val lastAccident = coffeeEventRepository.recordBrewingAccident(user)
assertThat(coffeeEventRepository.getLastBrewingAccident(), equalTo(lastAccident))
}
@Test
fun getAccidentCountForUser_ReturnsAccidentCountForThatSpecificUser() {
- val userId = "abc123"
- assertThat(coffeeEventRepository.getAccidentCountForUser(userId), equalTo(0L))
+ val user = User(id = "abc123")
+ assertThat(coffeeEventRepository.getAccidentCountForUser(user), equalTo(0L))
- coffeeEventRepository.recordBrewingAccident(userId)
- coffeeEventRepository.recordBrewingAccident(userId)
- coffeeEventRepository.recordBrewingAccident(userId)
+ coffeeEventRepository.recordBrewingAccident(user)
+ coffeeEventRepository.recordBrewingAccident(user)
+ coffeeEventRepository.recordBrewingAccident(user)
- val otherUserId = "someotherid"
- coffeeEventRepository.recordBrewingAccident(otherUserId)
- coffeeEventRepository.recordBrewingAccident(otherUserId)
+ val otherUser = User(id = "someotherid")
+ coffeeEventRepository.recordBrewingAccident(otherUser)
+ coffeeEventRepository.recordBrewingAccident(otherUser)
- assertThat(coffeeEventRepository.getAccidentCountForUser(userId), equalTo(3L))
+ assertThat(coffeeEventRepository.getAccidentCountForUser(user), equalTo(3L))
}
private fun coffeeEventCount() =
diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt
new file mode 100644
index 0000000..67cb152
--- /dev/null
+++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/MigrationTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 Codemate Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.codemate.koffeemate.data.local
+
+import android.content.Context
+import android.support.test.InstrumentationRegistry
+import com.codemate.koffeemate.data.models.CoffeeBrewingEvent
+import io.realm.Realm
+import io.realm.RealmConfiguration
+import org.hamcrest.core.IsEqual.equalTo
+import org.hamcrest.core.IsNull.nullValue
+import org.junit.Assert.assertThat
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+import java.io.IOException
+
+class MigrationTest {
+ lateinit var context: Context
+
+ @Before
+ fun setUp() {
+ context = InstrumentationRegistry.getContext()
+ }
+
+ /*************************************************************
+ * Migration tests from schema version 0 to 1
+ *************************************************************/
+ @Test
+ fun testMigrationFromVersionZeroToOne() {
+ val config = RealmConfiguration.Builder()
+ .name("migration-test.realm")
+ .schemaVersion(1)
+ .migration(Migration())
+ .build()
+
+ // The "sample-db-schema-v0.realm" contains three sample records,
+ // in the old database schema, which used userIds instead of User
+ // objects.
+ copyRealmFromAssets(context, "sample-db-schema-v0.realm", config)
+ val realm = Realm.getInstance(config)
+
+ val all = realm.where(CoffeeBrewingEvent::class.java).findAll()
+ assertThat(all.size, equalTo(3))
+
+ val brewingEventWithoutUserId = all[0]
+ assertThat(brewingEventWithoutUserId.id, equalTo("adf9c9b9-e521-462f-9d67-ff2a11d7b62c"))
+ assertThat(brewingEventWithoutUserId.time, equalTo(1485872637115L))
+ assertThat(brewingEventWithoutUserId.isSuccessful, equalTo(true))
+ assertThat(brewingEventWithoutUserId.user, nullValue())
+
+ val brewingEventWithUserId = all[1]
+ assertThat(brewingEventWithUserId.id, equalTo("0e742762-7181-4bc0-b7b5-d1ff68991dd6"))
+ assertThat(brewingEventWithUserId.time, equalTo(1485872637117L))
+ assertThat(brewingEventWithUserId.isSuccessful, equalTo(true))
+ assertThat(brewingEventWithUserId.user!!.id, equalTo("abc-123"))
+ assertThat(brewingEventWithUserId.user!!.last_updated, equalTo(0L))
+
+ val brewingAccident = all[2]
+ assertThat(brewingAccident.id, equalTo("480bb3b9-a01f-45cb-87cd-113465d4038a"))
+ assertThat(brewingAccident.time, equalTo(1485872637118L))
+ assertThat(brewingAccident.isSuccessful, equalTo(false))
+ assertThat(brewingAccident.user!!.id, equalTo("abc-123"))
+ assertThat(brewingEventWithUserId.user!!.last_updated, equalTo(0L))
+
+ // Make sure we don't generate different timestamps for the users in
+ // this new schema.
+ assertThat(brewingEventWithUserId.user!!.last_updated, equalTo(brewingAccident.user!!.last_updated))
+
+ realm.close()
+ }
+
+ @Throws(IOException::class)
+ fun copyRealmFromAssets(context: Context, realmPath: String, config: RealmConfiguration) {
+ Realm.deleteRealm(config)
+
+ context.assets.open(realmPath).use { inputStream ->
+ val outFile = File(config.realmDirectory, config.realmFileName)
+
+ outFile.outputStream().use { outputStream ->
+ inputStream.copyTo(outputStream)
+ }
+ }
+ }
+}
diff --git a/app/src/androidTest/java/com/codemate/koffeemate/data/local/UserRepositoryTest.kt b/app/src/androidTest/java/com/codemate/koffeemate/data/local/UserRepositoryTest.kt
new file mode 100644
index 0000000..33ccdbc
--- /dev/null
+++ b/app/src/androidTest/java/com/codemate/koffeemate/data/local/UserRepositoryTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 Codemate Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.codemate.koffeemate.data.local
+
+import com.codemate.koffeemate.data.models.User
+import io.realm.Realm
+import io.realm.RealmConfiguration
+import org.hamcrest.core.IsEqual.equalTo
+import org.junit.Assert.assertThat
+import org.junit.Before
+import org.junit.Test
+
+class UserRepositoryTest {
+ val TEST_USERS_UNIQUE = listOf(User(id = "abc123"), User(id = "123abc"), User(id = "a1b2c3"))
+ val TEST_USERS_DUPLICATE = listOf(User(id = "abc123"), User(id = "abc123"), User(id = "abc123"))
+
+ lateinit var userRepository: UserRepository
+
+ @Before
+ fun setUp() {
+ val realmConfig = RealmConfiguration.Builder()
+ .name("test.realm")
+ .inMemory()
+ .build()
+
+ Realm.setDefaultConfiguration(realmConfig)
+ Realm.getDefaultInstance().executeTransaction(Realm::deleteAll)
+
+ userRepository = RealmUserRepository()
+ }
+
+ @Test
+ fun addAll_WhenUsersAreUnique_PersistsAllInDatabase() {
+ userRepository.addAll(TEST_USERS_UNIQUE)
+
+ val all = userRepository.getAll()
+ assertThat(all[0].id, equalTo(TEST_USERS_UNIQUE[0].id))
+ assertThat(all[1].id, equalTo(TEST_USERS_UNIQUE[1].id))
+ assertThat(all[2].id, equalTo(TEST_USERS_UNIQUE[2].id))
+ }
+
+ @Test
+ fun addAll_WhenUsersAreDuplicate_PersistsOnlyOne() {
+ userRepository.addAll(TEST_USERS_DUPLICATE)
+
+ val all = userRepository.getAll()
+ assertThat(all.size, equalTo(1))
+ }
+
+ @Test
+ fun addAll_WhenTryingToAddExistingUser_UpdatesIt() {
+ userRepository.addAll(listOf(User(id = "abc123", name = "John Smith")))
+ assertThat(userRepository.getAll().first().name, equalTo("John Smith"))
+
+ userRepository.addAll(listOf(User(id = "abc123", name = "Kevin Doe")))
+
+ val all = userRepository.getAll()
+ assertThat(all.size, equalTo(1))
+ assertThat(all.first().name, equalTo("Kevin Doe"))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1b342fd..fdb0e95 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,10 +21,6 @@
-
-
diff --git a/app/src/main/java/com/codemate/koffeemate/KoffeemateApp.kt b/app/src/main/java/com/codemate/koffeemate/KoffeemateApp.kt
index 9e27e83..80b68e9 100644
--- a/app/src/main/java/com/codemate/koffeemate/KoffeemateApp.kt
+++ b/app/src/main/java/com/codemate/koffeemate/KoffeemateApp.kt
@@ -1,12 +1,14 @@
package com.codemate.koffeemate
import android.app.Application
+import com.codemate.koffeemate.data.local.Migration
import com.codemate.koffeemate.data.network.SlackApi
import com.codemate.koffeemate.di.components.AppComponent
import com.codemate.koffeemate.di.components.DaggerAppComponent
import com.codemate.koffeemate.di.modules.AppModule
import com.codemate.koffeemate.di.modules.NetModule
import io.realm.Realm
+import io.realm.RealmConfiguration
class KoffeemateApp : Application() {
companion object {
@@ -16,10 +18,21 @@ class KoffeemateApp : Application() {
override fun onCreate() {
super.onCreate()
- Realm.init(this)
+ initializeRealm()
+
appComponent = DaggerAppComponent.builder()
.appModule(AppModule(this))
.netModule(NetModule(SlackApi.BASE_URL))
.build()
}
+
+ private fun initializeRealm() {
+ Realm.init(this)
+
+ val configuration = RealmConfiguration.Builder()
+ .migration(Migration())
+ .schemaVersion(1)
+ .build()
+ Realm.setDefaultConfiguration(configuration)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt
index 3ad2100..7251845 100644
--- a/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt
+++ b/app/src/main/java/com/codemate/koffeemate/data/local/CoffeeEventRepository.kt
@@ -1,11 +1,82 @@
package com.codemate.koffeemate.data.local
-import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent
+import com.codemate.koffeemate.data.models.CoffeeBrewingEvent
+import com.codemate.koffeemate.data.models.User
+import io.realm.Realm
+import io.realm.Sort
+import java.util.*
interface CoffeeEventRepository {
- fun recordBrewingEvent(userId: String? = ""): CoffeeBrewingEvent
- fun recordBrewingAccident(userId: String): CoffeeBrewingEvent
- fun getAccidentCountForUser(userId: String): Long
+ fun recordBrewingEvent(user: User? = null): CoffeeBrewingEvent
+ fun recordBrewingAccident(user: User): CoffeeBrewingEvent
+ fun getAccidentCountForUser(user: User): Long
+
fun getLastBrewingEvent(): CoffeeBrewingEvent?
fun getLastBrewingAccident(): CoffeeBrewingEvent?
+}
+
+class RealmCoffeeEventRepository : CoffeeEventRepository {
+ override fun recordBrewingEvent(user: User?) = with(Realm.getDefaultInstance()) {
+ var event: CoffeeBrewingEvent? = null
+ executeTransaction {
+ event = newEvent(it).apply {
+ time = System.currentTimeMillis()
+ isSuccessful = true
+ this.user = if (user != null) copyToRealmOrUpdate(user) else null
+ }
+ }
+
+ close()
+ return@with event!!
+ }
+
+ override fun recordBrewingAccident(user: User) = with(Realm.getDefaultInstance()) {
+ var event: CoffeeBrewingEvent? = null
+ executeTransaction {
+ event = newEvent(it).apply {
+ time = System.currentTimeMillis()
+ isSuccessful = false
+ this.user = copyToRealmOrUpdate(user)
+ }
+ }
+
+ close()
+ return@with event!!
+ }
+
+ override fun getAccidentCountForUser(user: User) = with(Realm.getDefaultInstance()) {
+ val count = where(CoffeeBrewingEvent::class.java)
+ .equalTo("isSuccessful", false)
+ .equalTo("user.id", user.id)
+ .count()
+
+ close()
+ return@with count
+ }
+
+ override fun getLastBrewingEvent() = with(Realm.getDefaultInstance()) {
+ val lastEvent = where(CoffeeBrewingEvent::class.java)
+ .equalTo("isSuccessful", true)
+ .findAllSorted("time", Sort.ASCENDING)
+ .lastOrNull()
+
+ close()
+ return@with lastEvent
+ }
+
+ override fun getLastBrewingAccident() = with(Realm.getDefaultInstance()) {
+ val lastAccident = where(CoffeeBrewingEvent::class.java)
+ .equalTo("isSuccessful", false)
+ .findAllSorted("time", Sort.ASCENDING)
+ .lastOrNull()
+
+ close()
+ return@with lastAccident
+ }
+
+ private fun newEvent(realm: Realm) =
+ realm.createObject(
+ CoffeeBrewingEvent::class.java,
+ UUID.randomUUID().toString()
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt b/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt
new file mode 100644
index 0000000..a7045e7
--- /dev/null
+++ b/app/src/main/java/com/codemate/koffeemate/data/local/Migration.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017 Codemate Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.codemate.koffeemate.data.local
+
+import io.realm.DynamicRealm
+import io.realm.DynamicRealmObject
+import io.realm.FieldAttribute
+import io.realm.RealmMigration
+import io.realm.exceptions.RealmPrimaryKeyConstraintException
+
+class Migration : RealmMigration {
+ override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
+ val schema = realm.schema
+
+ /*************************************************************
+ // Version 0
+ class CoffeeBrewingEvent
+ @PrimaryKey
+ id: String
+ time: Long
+ isSuccessful: Boolean
+ userId: String
+
+ // Version 1
+ Changed Tables:
+ class CoffeeBrewingEvent
+ @PrimaryKey
+ id: String
+ time: Long
+ isSuccessful: Boolean
+ user: User
+
+ New Tables:
+ class User
+ @PrimaryKey
+ id: String
+ name: String
+ profile: Profile
+ real_name: String
+ is_bot: Boolean
+ deleted: Boolean
+ last_updated: Long
+
+ class Profile
+ first_name: String
+ last_name: String
+ real_name: String
+ image_72: String
+ image_192: String
+ image_512: String
+ *************************************************************/
+ if (oldVersion == 0L) {
+ val profileSchema = schema.create("Profile")
+ .addField("first_name", String::class.java)
+ .addField("last_name", String::class.java)
+ .addField("real_name", String::class.java)
+ .addField("image_72", String::class.java)
+ .addField("image_192", String::class.java)
+ .addField("image_512", String::class.java)
+
+ val userSchema = schema.create("User")
+ .addField("id", String::class.java, FieldAttribute.PRIMARY_KEY)
+ .addField("name", String::class.java)
+ .addRealmObjectField("profile", profileSchema)
+ .addField("real_name", String::class.java)
+ .addField("is_bot", Boolean::class.java)
+ .addField("deleted", Boolean::class.java)
+ .addField("last_updated", Long::class.java)
+
+ schema.get("CoffeeBrewingEvent")
+ .addRealmObjectField("user", userSchema)
+ .transform { brewingEvent ->
+ brewingEvent.getString("userId")?.let { previousUserId ->
+ if (previousUserId.isNotBlank()) {
+ var user: DynamicRealmObject?
+
+ // DynamicRealm doesn't allow us create duplicate User objects,
+ // since the id field is a primary key. insertOrUpdate() and similar
+ // methods are unavailable when using DynamicRealms.
+ //
+ // Try-catch is the only way to handle the migration of userIds to
+ // users in this case.
+ try {
+ user = realm.createObject("User", previousUserId)
+ } catch (e: RealmPrimaryKeyConstraintException) {
+ user = realm.where("User")
+ .equalTo("id", previousUserId)
+ .findFirst()
+ }
+
+ brewingEvent.setObject("user", user)
+ }
+ }
+ }
+ .removeField("userId")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepository.kt
deleted file mode 100644
index a0848a6..0000000
--- a/app/src/main/java/com/codemate/koffeemate/data/local/RealmCoffeeEventRepository.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.codemate.koffeemate.data.local
-
-import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent
-import io.realm.Realm
-import io.realm.Sort
-import java.util.*
-
-class RealmCoffeeEventRepository : CoffeeEventRepository {
- override fun recordBrewingAccident(userId: String) = with(Realm.getDefaultInstance()) {
- var event: CoffeeBrewingEvent? = null
- executeTransaction {
- event = newEvent(it).apply {
- time = System.currentTimeMillis()
- isSuccessful = false
- this.userId = userId
- }
- }
-
- close()
- return@with event!!
- }
-
- override fun recordBrewingEvent(userId: String?) = with(Realm.getDefaultInstance()) {
- var event: CoffeeBrewingEvent? = null
- executeTransaction {
- event = newEvent(it).apply {
- time = System.currentTimeMillis()
- isSuccessful = true
- this.userId = userId ?: ""
- }
- }
-
- close()
- return@with event!!
- }
-
- override fun getAccidentCountForUser(userId: String) =
- Realm.getDefaultInstance()
- .where(CoffeeBrewingEvent::class.java)
- .equalTo("isSuccessful", false)
- .equalTo("userId", userId)
- .count()
-
- override fun getLastBrewingEvent(): CoffeeBrewingEvent? {
- return Realm.getDefaultInstance()
- .where(CoffeeBrewingEvent::class.java)
- .equalTo("isSuccessful", true)
- .findAllSorted("time", Sort.ASCENDING)
- .lastOrNull()
- }
-
- override fun getLastBrewingAccident(): CoffeeBrewingEvent? {
- return Realm.getDefaultInstance()
- .where(CoffeeBrewingEvent::class.java)
- .equalTo("isSuccessful", false)
- .findAllSorted("time", Sort.ASCENDING)
- .lastOrNull()
- }
-
- private fun newEvent(realm: Realm) =
- realm.createObject(
- CoffeeBrewingEvent::class.java,
- UUID.randomUUID().toString()
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt
new file mode 100644
index 0000000..79c7862
--- /dev/null
+++ b/app/src/main/java/com/codemate/koffeemate/data/local/UserRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 Codemate Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.codemate.koffeemate.data.local
+
+import com.codemate.koffeemate.data.models.User
+import io.realm.Realm
+
+interface UserRepository {
+ fun addAll(users: List)
+ fun getAll(): List
+}
+
+class RealmUserRepository : UserRepository {
+ override fun addAll(users: List) {
+ with(Realm.getDefaultInstance()) {
+ executeTransaction { copyToRealmOrUpdate(users) }
+ close()
+ }
+ }
+
+ override fun getAll(): List = with(Realm.getDefaultInstance()) {
+ val all = where(User::class.java).findAll()
+ val copy = copyFromRealm(all)
+
+ close()
+ return@with copy
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/data/local/models/CoffeeBrewingEvent.kt b/app/src/main/java/com/codemate/koffeemate/data/models/CoffeeBrewingEvent.kt
similarity index 74%
rename from app/src/main/java/com/codemate/koffeemate/data/local/models/CoffeeBrewingEvent.kt
rename to app/src/main/java/com/codemate/koffeemate/data/models/CoffeeBrewingEvent.kt
index 58a363b..e18f0bb 100644
--- a/app/src/main/java/com/codemate/koffeemate/data/local/models/CoffeeBrewingEvent.kt
+++ b/app/src/main/java/com/codemate/koffeemate/data/models/CoffeeBrewingEvent.kt
@@ -1,4 +1,4 @@
-package com.codemate.koffeemate.data.local.models
+package com.codemate.koffeemate.data.models
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
@@ -8,5 +8,5 @@ open class CoffeeBrewingEvent(
open var id: String = "",
open var time: Long = 0,
open var isSuccessful: Boolean = false,
- open var userId: String = ""
+ open var user: User? = null
) : RealmObject()
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt
new file mode 100644
index 0000000..0329c3b
--- /dev/null
+++ b/app/src/main/java/com/codemate/koffeemate/data/models/Profile.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 Codemate Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.codemate.koffeemate.data.models
+
+import io.realm.RealmObject
+
+open class Profile(
+ open var first_name: String = "",
+ open var last_name: String = "",
+ open var real_name: String = "",
+ open var image_72: String? = null,
+ open var image_192: String? = null,
+ open var image_512: String? = null
+) : RealmObject() {
+ val largestAvailableImage: String
+ get() = image_512 ?: image_192 ?: image_72 ?: ""
+
+ val smallestAvailableImage: String
+ get() = image_72 ?: image_192 ?: image_512 ?: ""
+}
diff --git a/app/src/main/java/com/codemate/koffeemate/data/models/User.kt b/app/src/main/java/com/codemate/koffeemate/data/models/User.kt
new file mode 100644
index 0000000..1bc81d9
--- /dev/null
+++ b/app/src/main/java/com/codemate/koffeemate/data/models/User.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 Codemate Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.codemate.koffeemate.data.models
+
+import io.realm.RealmObject
+import io.realm.annotations.PrimaryKey
+
+class UserListResponse {
+ var members = listOf()
+}
+
+fun List.isFreshEnough(maxStaleness: Long): Boolean {
+ val oldestAcceptedTimestamp = System.currentTimeMillis() - maxStaleness
+ val sorted = sortedByDescending(User::last_updated)
+ val freshestUser = sorted.first()
+
+ return freshestUser.last_updated > oldestAcceptedTimestamp
+}
+
+open class User(
+ @PrimaryKey
+ open var id: String = "",
+ open var name: String = "",
+ open var profile: Profile = Profile(),
+ open var real_name: String? = null,
+ open var is_bot: Boolean = false,
+ open var deleted: Boolean = false,
+ open var last_updated: Long = 0
+) : RealmObject()
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt b/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt
index 9587d4a..91803e9 100644
--- a/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt
+++ b/app/src/main/java/com/codemate/koffeemate/data/network/SlackApi.kt
@@ -1,14 +1,16 @@
package com.codemate.koffeemate.data.network
import com.codemate.koffeemate.BuildConfig
-import com.codemate.koffeemate.data.network.models.UserListResponse
+import com.codemate.koffeemate.data.models.UserListResponse
import com.codemate.koffeemate.extensions.toRequestBody
import okhttp3.HttpUrl
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.ResponseBody
-import retrofit2.Call
import retrofit2.Response
+import retrofit2.Retrofit
+import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
+import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.*
import rx.Observable
@@ -40,5 +42,15 @@ interface SlackApi {
companion object {
val BASE_URL = HttpUrl.parse("https://slack.com/api/")!!
+
+ fun create(baseUrl: HttpUrl): SlackApi {
+ val retrofit = Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .addConverterFactory(GsonConverterFactory.create())
+ .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
+ .build()
+
+ return retrofit.create(SlackApi::class.java)
+ }
}
}
diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/SlackService.kt b/app/src/main/java/com/codemate/koffeemate/data/network/SlackService.kt
deleted file mode 100644
index 7302767..0000000
--- a/app/src/main/java/com/codemate/koffeemate/data/network/SlackService.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2016 Codemate Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.codemate.koffeemate.data.network
-
-import okhttp3.HttpUrl
-import retrofit2.Retrofit
-import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
-import retrofit2.converter.gson.GsonConverterFactory
-
-object SlackService {
- fun getApi(baseUrl: HttpUrl): SlackApi {
- val retrofit = Retrofit.Builder()
- .baseUrl(baseUrl)
- .addConverterFactory(GsonConverterFactory.create())
- .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
- .build()
-
- return retrofit.create(SlackApi::class.java)
- }
-}
diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt b/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt
deleted file mode 100644
index 802c471..0000000
--- a/app/src/main/java/com/codemate/koffeemate/data/network/models/Profile.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.codemate.koffeemate.data.network.models
-
-class Profile {
- lateinit var first_name: String
- lateinit var last_name: String
- lateinit var real_name: String
-
- var image_72: String? = null
- var image_192: String? = null
- var image_512: String? = null
-
- val largestAvailableImage: String
- get() {
- var imageUrl: String? = image_512
-
- if (imageUrl.isNullOrBlank()) {
- imageUrl = image_192
- }
-
- if (imageUrl.isNullOrBlank()) {
- imageUrl = image_72
- }
-
- return imageUrl ?: ""
- }
-
- val smallestAvailableImage: String
- get() {
- var imageUrl: String? = image_72
-
- if (imageUrl.isNullOrBlank()) {
- imageUrl = image_192
- }
-
- if (imageUrl.isNullOrBlank()) {
- imageUrl = image_512
- }
-
- return imageUrl ?: ""
- }
-}
diff --git a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt b/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt
deleted file mode 100644
index fe38840..0000000
--- a/app/src/main/java/com/codemate/koffeemate/data/network/models/User.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.codemate.koffeemate.data.network.models
-
-class UserListResponse {
- var members = listOf()
-}
-
-class User {
- lateinit var id: String
- lateinit var name: String
- lateinit var profile: Profile
-
- var real_name: String? = null
- var is_bot: Boolean = false
- var deleted: Boolean = false
-}
diff --git a/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt b/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt
index 50bb9b4..63626a9 100644
--- a/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt
+++ b/app/src/main/java/com/codemate/koffeemate/di/components/AppComponent.kt
@@ -16,11 +16,7 @@
package com.codemate.koffeemate.di.components
-import com.codemate.koffeemate.di.modules.ActivityModule
-import com.codemate.koffeemate.di.modules.AppModule
-import com.codemate.koffeemate.di.modules.NetModule
-import com.codemate.koffeemate.di.modules.PersistenceModule
-import com.codemate.koffeemate.ui.userselector.UserSelectorActivity
+import com.codemate.koffeemate.di.modules.*
import com.codemate.koffeemate.ui.userselector.UserSelectorFragment
import dagger.Component
import javax.inject.Singleton
@@ -29,7 +25,8 @@ import javax.inject.Singleton
@Component(modules = arrayOf(
AppModule::class,
PersistenceModule::class,
- NetModule::class)
+ NetModule::class,
+ ThreadingModule::class)
)
interface AppComponent {
fun inject(userSelectorFragment: UserSelectorFragment)
diff --git a/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt b/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt
index c4d66fa..d25e57f 100644
--- a/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt
+++ b/app/src/main/java/com/codemate/koffeemate/di/modules/AppModule.kt
@@ -21,16 +21,8 @@ import com.codemate.koffeemate.KoffeemateApp
import com.codemate.koffeemate.common.AndroidAwardBadgeCreator
import com.codemate.koffeemate.common.AwardBadgeCreator
import com.codemate.koffeemate.common.BrewingProgressUpdater
-import com.codemate.koffeemate.data.local.CoffeeEventRepository
-import com.codemate.koffeemate.data.local.CoffeePreferences
-import com.codemate.koffeemate.data.network.SlackApi
-import com.codemate.koffeemate.ui.userselector.LoadUsersUseCase
-import com.codemate.koffeemate.ui.main.PostAccidentUseCase
-import com.codemate.koffeemate.ui.main.SendCoffeeAnnouncementUseCase
import dagger.Module
import dagger.Provides
-import rx.android.schedulers.AndroidSchedulers
-import rx.schedulers.Schedulers
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@@ -51,41 +43,4 @@ class AppModule(val app: KoffeemateApp) {
@Provides
@Singleton
fun provideAwardBadgeCreator(ctx: Context): AwardBadgeCreator = AndroidAwardBadgeCreator(ctx)
-
- /**
- * Move these to a better place once you actually understand Dagger ¯\_(ツ)_/¯
- */
- @Provides
- @Singleton
- fun provideSendCoffeeAnnouncementUseCase(slackApi: SlackApi) =
- SendCoffeeAnnouncementUseCase(
- slackApi,
- Schedulers.newThread(),
- AndroidSchedulers.mainThread()
- )
-
- @Provides
- @Singleton
- fun provideLoadUsersUseCase(slackApi: SlackApi) =
- LoadUsersUseCase(
- slackApi,
- Schedulers.newThread(),
- AndroidSchedulers.mainThread()
- )
-
- @Provides
- @Singleton
- fun providePostAccidentUseCase(
- slackApi: SlackApi,
- coffeeEventRepository: CoffeeEventRepository,
- coffeePreferences: CoffeePreferences,
- awardBadgeCreator: AwardBadgeCreator
- ) = PostAccidentUseCase(
- slackApi,
- coffeeEventRepository,
- coffeePreferences,
- awardBadgeCreator,
- Schedulers.newThread(),
- AndroidSchedulers.mainThread()
- )
}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/di/modules/NetModule.kt b/app/src/main/java/com/codemate/koffeemate/di/modules/NetModule.kt
index 4b86b84..0a70494 100644
--- a/app/src/main/java/com/codemate/koffeemate/di/modules/NetModule.kt
+++ b/app/src/main/java/com/codemate/koffeemate/di/modules/NetModule.kt
@@ -16,7 +16,7 @@
package com.codemate.koffeemate.di.modules
-import com.codemate.koffeemate.data.network.SlackService
+import com.codemate.koffeemate.data.network.SlackApi
import dagger.Module
import dagger.Provides
import okhttp3.HttpUrl
@@ -26,5 +26,5 @@ import javax.inject.Singleton
class NetModule(val baseUrl: HttpUrl) {
@Provides
@Singleton
- fun provideApi() = SlackService.getApi(baseUrl)
+ fun provideApi() = SlackApi.create(baseUrl)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/di/modules/PersistenceModule.kt b/app/src/main/java/com/codemate/koffeemate/di/modules/PersistenceModule.kt
index b0ea703..dd4431d 100644
--- a/app/src/main/java/com/codemate/koffeemate/di/modules/PersistenceModule.kt
+++ b/app/src/main/java/com/codemate/koffeemate/di/modules/PersistenceModule.kt
@@ -17,9 +17,7 @@
package com.codemate.koffeemate.di.modules
import android.content.Context
-import com.codemate.koffeemate.data.local.CoffeeEventRepository
-import com.codemate.koffeemate.data.local.CoffeePreferences
-import com.codemate.koffeemate.data.local.RealmCoffeeEventRepository
+import com.codemate.koffeemate.data.local.*
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@@ -33,4 +31,8 @@ class PersistenceModule {
@Provides
@Singleton
fun provideCoffeeEventRepository(): CoffeeEventRepository = RealmCoffeeEventRepository()
+
+ @Provides
+ @Singleton
+ fun provideUserRepository(): UserRepository = RealmUserRepository()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/di/modules/ThreadingModule.kt b/app/src/main/java/com/codemate/koffeemate/di/modules/ThreadingModule.kt
new file mode 100644
index 0000000..7e11cfb
--- /dev/null
+++ b/app/src/main/java/com/codemate/koffeemate/di/modules/ThreadingModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 Codemate Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.codemate.koffeemate.di.modules
+
+import dagger.Module
+import dagger.Provides
+import rx.Scheduler
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
+import javax.inject.Named
+
+@Module
+class ThreadingModule {
+ @Provides
+ @Named("subscriber")
+ fun provideSubscriber(): Scheduler = Schedulers.newThread()
+
+ @Provides
+ @Named("observer")
+ fun provideObserver(): Scheduler = AndroidSchedulers.mainThread()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt
index b1ff92d..4e53164 100644
--- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt
+++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainActivity.kt
@@ -1,7 +1,6 @@
package com.codemate.koffeemate.ui.main
import android.app.ProgressDialog
-import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
@@ -10,12 +9,11 @@ import com.bumptech.glide.Glide
import com.codemate.koffeemate.KoffeemateApp
import com.codemate.koffeemate.R
import com.codemate.koffeemate.common.ScreenSaver
-import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent
-import com.codemate.koffeemate.data.network.models.User
+import com.codemate.koffeemate.data.models.CoffeeBrewingEvent
+import com.codemate.koffeemate.data.models.User
import com.codemate.koffeemate.di.modules.ActivityModule
import com.codemate.koffeemate.extensions.loadBitmap
import com.codemate.koffeemate.ui.settings.SettingsActivity
-import com.codemate.koffeemate.ui.userselector.UserSelectorActivity
import com.codemate.koffeemate.ui.userselector.UserSelectorFragment
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.view_coffee_progress.view.*
@@ -23,7 +21,8 @@ import org.jetbrains.anko.*
import javax.inject.Inject
class MainActivity : AppCompatActivity(), MainView, UserSelectorFragment.UserSelectListener {
- private val REQUEST_CODE_SHAME_USER = 1
+ private val REQUEST_WHOS_BREWING = 1
+ private val REQUEST_WHO_FAILED_BREWING = 2
@Inject
lateinit var presenter: MainPresenter
@@ -91,20 +90,28 @@ class MainActivity : AppCompatActivity(), MainView, UserSelectorFragment.UserSel
// Functions for identifying who brews the coffee
override fun selectCoffeeBrewingPerson() {
- UserSelectorFragment
- .newInstance()
- .show(supportFragmentManager, "user_selector")
+ UserSelectorFragment.newInstance(
+ title = getString(R.string.prompt_select_person_below),
+ requestCode = REQUEST_WHOS_BREWING
+ ).show(supportFragmentManager, "user_selector")
}
override fun clearCoffeeBrewingPerson() {
coffeeProgressView.userSetterButton.clearUser()
}
- override fun onUserSelected(user: User) {
- Glide.with(this)
- .load(user.profile.smallestAvailableImage)
- .into(coffeeProgressView.userSetterButton)
- presenter.personBrewingCoffee = user
+ override fun onUserSelected(user: User, requestCode: Int) {
+ when (requestCode) {
+ REQUEST_WHOS_BREWING -> {
+ Glide.with(this)
+ .load(user.profile.smallestAvailableImage)
+ .into(coffeeProgressView.userSetterButton)
+ presenter.personBrewingCoffee = user
+ }
+ REQUEST_WHO_FAILED_BREWING -> {
+ showPostAccidentAnnouncementPrompt(user)
+ }
+ }
}
// MainView methods -->
@@ -156,35 +163,24 @@ class MainActivity : AppCompatActivity(), MainView, UserSelectorFragment.UserSel
// Shaming users for coffee brewing failures -->
override fun launchUserSelector() {
- startActivityForResult(
- intentFor(),
- REQUEST_CODE_SHAME_USER
- )
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == REQUEST_CODE_SHAME_USER && resultCode == RESULT_OK && data != null) {
- val userId = data.getStringExtra(UserSelectorActivity.RESULT_USER_ID)
- val fullName = data.getStringExtra(UserSelectorActivity.RESULT_USER_FULL_NAME)
- val firstName = data.getStringExtra(UserSelectorActivity.RESULT_USER_FIRST_NAME)
- val largestProfilePicUrl = data.getStringExtra(UserSelectorActivity.RESULT_USER_PROFILE_LARGEST_PIC_URL)
-
- showPostAccidentAnnouncementPrompt(userId, fullName, firstName, largestProfilePicUrl)
- }
+ UserSelectorFragment.newInstance(
+ title = getString(R.string.prompt_who_is_guilty),
+ requestCode = REQUEST_WHO_FAILED_BREWING
+ ).show(supportFragmentManager, "user_selector")
}
- override fun showPostAccidentAnnouncementPrompt(userId: String, fullName: String, firstName: String, largestProfilePicUrl: String) {
+ override fun showPostAccidentAnnouncementPrompt(user: User) {
alert {
title(R.string.prompt_reset_the_counter)
- message(getString(R.string.message_posting_to_slack_fmt, fullName))
+ message(getString(R.string.message_posting_to_slack_fmt, user.profile.real_name))
negativeButton(R.string.action_cancel)
positiveButton(R.string.action_announce_coffee_accident) {
accidentProgress = indeterminateProgressDialog(R.string.progress_message_shaming_person_on_slack)
- val comment = getString(R.string.message_congratulations_to_user_fmt, firstName)
+ val comment = getString(R.string.message_congratulations_to_user_fmt, user.profile.first_name)
- Glide.with(this@MainActivity).loadBitmap(largestProfilePicUrl) { profilePic ->
- presenter.announceCoffeeBrewingAccident(comment, userId, firstName, profilePic)
+ Glide.with(this@MainActivity).loadBitmap(user.profile.largestAvailableImage) { profilePic ->
+ presenter.announceCoffeeBrewingAccident(comment, user, profilePic)
}
}
}.show()
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt
index 545723c..8582ccd 100644
--- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt
+++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainPresenter.kt
@@ -5,8 +5,10 @@ import com.codemate.koffeemate.common.BrewingProgressUpdater
import com.codemate.koffeemate.common.ScreenSaver
import com.codemate.koffeemate.data.local.CoffeeEventRepository
import com.codemate.koffeemate.data.local.CoffeePreferences
-import com.codemate.koffeemate.data.network.models.User
+import com.codemate.koffeemate.data.models.User
import com.codemate.koffeemate.ui.base.BasePresenter
+import com.codemate.koffeemate.usecases.PostAccidentUseCase
+import com.codemate.koffeemate.usecases.SendCoffeeAnnouncementUseCase
import okhttp3.ResponseBody
import retrofit2.Response
import rx.Subscriber
@@ -58,7 +60,7 @@ class MainPresenter @Inject constructor(
getView()?.updateCoffeeProgress(0)
getView()?.resetCoffeeViewStatus()
- coffeeEventRepository.recordBrewingEvent(personBrewingCoffee?.id)
+ coffeeEventRepository.recordBrewingEvent(personBrewingCoffee)
updateLastBrewingEventTime()
}
@@ -107,13 +109,7 @@ class MainPresenter @Inject constructor(
if (coffeePreferences.isAccidentChannelSet()) {
personBrewingCoffee?.let {
- getView()?.showPostAccidentAnnouncementPrompt(
- it.id,
- it.profile.real_name,
- it.profile.first_name,
- it.profile.largestAvailableImage
- )
-
+ getView()?.showPostAccidentAnnouncementPrompt(it)
return
}
@@ -123,10 +119,10 @@ class MainPresenter @Inject constructor(
}
}
- fun announceCoffeeBrewingAccident(comment: String, userId: String, userName: String, profilePic: Bitmap) {
+ fun announceCoffeeBrewingAccident(comment: String, user: User, profilePic: Bitmap) {
ensureViewIsAttached()
- postAccidentUseCase.execute(comment, userId, userName, profilePic).subscribe(
+ postAccidentUseCase.execute(comment, user, profilePic).subscribe(
object : Subscriber>() {
override fun onNext(response: Response) {
getView()?.showAccidentPostedSuccessfullyMessage()
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt b/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt
index 8ace990..803977e 100644
--- a/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt
+++ b/app/src/main/java/com/codemate/koffeemate/ui/main/MainView.kt
@@ -1,6 +1,7 @@
package com.codemate.koffeemate.ui.main
-import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent
+import com.codemate.koffeemate.data.models.CoffeeBrewingEvent
+import com.codemate.koffeemate.data.models.User
import com.codemate.koffeemate.ui.base.MvpView
interface MainView : MvpView {
@@ -18,12 +19,7 @@ interface MainView : MvpView {
fun clearCoffeeBrewingPerson()
fun launchUserSelector()
- fun showPostAccidentAnnouncementPrompt(
- userId: String,
- fullName: String,
- firstName: String,
- largestProfilePicUrl: String
- )
+ fun showPostAccidentAnnouncementPrompt(user: User)
fun showAccidentPostedSuccessfullyMessage()
fun showErrorPostingAccidentMessage()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt
deleted file mode 100644
index aeafefa..0000000
--- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorActivity.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.codemate.koffeemate.ui.userselector
-
-import android.content.Intent
-import android.os.Bundle
-import android.support.v7.app.AppCompatActivity
-import android.view.MenuItem
-import com.codemate.koffeemate.R
-import com.codemate.koffeemate.data.network.models.User
-
-class UserSelectorActivity : AppCompatActivity(), UserSelectorFragment.UserSelectListener {
- companion object {
- val RESULT_USER_ID = "user_id"
- val RESULT_USER_FULL_NAME = "user_full_name"
- val RESULT_USER_FIRST_NAME = "user_first_name"
- val RESULT_USER_PROFILE_LARGEST_PIC_URL = "user_profile_largest_pic_url"
- val RESULT_USER_PROFILE_SMALLEST_PIC_URL = "user_profile_smallest_pic_url"
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- supportFragmentManager.beginTransaction()
- .replace(android.R.id.content, UserSelectorFragment.newInstance())
- .commit()
-
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- supportActionBar?.setTitle(R.string.prompt_select_guilty_person)
- }
-
- override fun onUserSelected(user: User) {
- val intent = Intent()
- intent.putExtra(RESULT_USER_ID, user.id)
- intent.putExtra(RESULT_USER_FULL_NAME, user.profile.real_name)
- intent.putExtra(RESULT_USER_FIRST_NAME, user.profile.first_name)
- intent.putExtra(RESULT_USER_PROFILE_LARGEST_PIC_URL, user.profile.largestAvailableImage)
- intent.putExtra(RESULT_USER_PROFILE_SMALLEST_PIC_URL, user.profile.smallestAvailableImage)
-
- setResult(RESULT_OK, intent)
- finish()
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == android.R.id.home) {
- finish()
- }
-
- return super.onOptionsItemSelected(item)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt
index 4525bab..1e414ac 100644
--- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt
+++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorAdapter.kt
@@ -6,7 +6,7 @@ import android.view.View
import android.view.ViewGroup
import com.bumptech.glide.Glide
import com.codemate.koffeemate.R
-import com.codemate.koffeemate.data.network.models.User
+import com.codemate.koffeemate.data.models.User
import kotlinx.android.synthetic.main.recycler_item_user.view.*
import org.jetbrains.anko.onClick
import java.util.*
@@ -37,7 +37,7 @@ class UserSelectorAdapter(val onUserSelectedListener: (user: User) -> Unit) :
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(user: User) = with(itemView) {
Glide.with(context)
- .load(user.profile.image_72)
+ .load(user.profile.smallestAvailableImage)
.into(profileImage)
userName.text = user.profile.real_name
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt
index d4b9da2..3354ea9 100644
--- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt
+++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorFragment.kt
@@ -28,7 +28,7 @@ import android.view.ViewGroup
import com.codemate.koffeemate.KoffeemateApp
import com.codemate.koffeemate.R
import com.codemate.koffeemate.common.BasicListItemAnimator
-import com.codemate.koffeemate.data.network.models.User
+import com.codemate.koffeemate.data.models.User
import kotlinx.android.synthetic.main.fragment_user_selector.*
import kotlinx.android.synthetic.main.fragment_user_selector.view.*
import org.jetbrains.anko.onClick
@@ -38,12 +38,22 @@ class UserSelectorFragment : DialogFragment(), UserSelectorView {
private lateinit var userSelectorAdapter: UserSelectorAdapter
private lateinit var userSelectListener: UserSelectListener
+ private var requestCode: Int = 0
+
@Inject
lateinit var presenter: UserSelectorPresenter
companion object {
- fun newInstance(): UserSelectorFragment {
+ private val ARG_TITLE = "title"
+ private val ARG_REQUEST_CODE = "request_code"
+
+ fun newInstance(title: String, requestCode: Int): UserSelectorFragment {
+ val args = Bundle()
+ args.putString(ARG_TITLE, title)
+ args.putInt(ARG_REQUEST_CODE, requestCode)
+
val fragment = UserSelectorFragment()
+ fragment.arguments = args
fragment.setStyle(DialogFragment.STYLE_NORMAL, R.style.TitledDialog)
return fragment
@@ -51,7 +61,7 @@ class UserSelectorFragment : DialogFragment(), UserSelectorView {
}
interface UserSelectListener {
- fun onUserSelected(user: User)
+ fun onUserSelected(user: User, requestCode: Int)
}
@Suppress("UNCHECKED_CAST")
@@ -72,8 +82,9 @@ class UserSelectorFragment : DialogFragment(), UserSelectorView {
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
-
KoffeemateApp.appComponent.inject(this)
+
+ requestCode = arguments.getInt(ARG_REQUEST_CODE)
setUpUserRecycler()
presenter.attachView(this)
@@ -86,14 +97,15 @@ class UserSelectorFragment : DialogFragment(), UserSelectorView {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
- dialog.setTitle(R.string.prompt_select_person_below)
+ val title = arguments.getString(ARG_TITLE)
+ dialog.setTitle(title)
return dialog
}
private fun setUpUserRecycler() {
userSelectorAdapter = UserSelectorAdapter { user ->
- userSelectListener.onUserSelected(user)
+ userSelectListener.onUserSelected(user, requestCode)
dismiss()
}
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt
index c353dad..94106f7 100644
--- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt
+++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenter.kt
@@ -1,7 +1,8 @@
package com.codemate.koffeemate.ui.userselector
-import com.codemate.koffeemate.data.network.models.User
+import com.codemate.koffeemate.data.models.User
import com.codemate.koffeemate.ui.base.BasePresenter
+import com.codemate.koffeemate.usecases.LoadUsersUseCase
import rx.Subscriber
import javax.inject.Inject
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorView.kt b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorView.kt
index 0e4f05e..46b6858 100644
--- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorView.kt
+++ b/app/src/main/java/com/codemate/koffeemate/ui/userselector/UserSelectorView.kt
@@ -1,6 +1,6 @@
package com.codemate.koffeemate.ui.userselector
-import com.codemate.koffeemate.data.network.models.User
+import com.codemate.koffeemate.data.models.User
import com.codemate.koffeemate.ui.base.MvpView
interface UserSelectorView : MvpView {
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt b/app/src/main/java/com/codemate/koffeemate/usecases/LoadUsersUseCase.kt
similarity index 51%
rename from app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt
rename to app/src/main/java/com/codemate/koffeemate/usecases/LoadUsersUseCase.kt
index 2c62d74..48fa8bc 100644
--- a/app/src/main/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCase.kt
+++ b/app/src/main/java/com/codemate/koffeemate/usecases/LoadUsersUseCase.kt
@@ -14,22 +14,39 @@
* limitations under the License.
*/
-package com.codemate.koffeemate.ui.userselector
+package com.codemate.koffeemate.usecases
import com.codemate.koffeemate.BuildConfig
+import com.codemate.koffeemate.data.local.UserRepository
+import com.codemate.koffeemate.data.models.User
+import com.codemate.koffeemate.data.models.isFreshEnough
import com.codemate.koffeemate.data.network.SlackApi
-import com.codemate.koffeemate.data.network.models.User
-import com.codemate.koffeemate.data.network.models.UserListResponse
import rx.Observable
import rx.Scheduler
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Named
-open class LoadUsersUseCase(
+open class LoadUsersUseCase @Inject constructor(
+ var userRepository: UserRepository,
var slackApi: SlackApi,
- var subscriber: Scheduler,
- var observer: Scheduler
+ @Named("subscriber") var subscriber: Scheduler,
+ @Named("observer") var observer: Scheduler
) {
+ val MAX_CACHE_STALENESS = TimeUnit.HOURS.toMillis(12)
+
fun execute(): Observable> {
- return slackApi.getUsers(BuildConfig.SLACK_AUTH_TOKEN)
+ val currentTime = System.currentTimeMillis()
+ val cachedUsers = Observable.just(userRepository.getAll())
+ val networkUsers = slackApi.getUsers(BuildConfig.SLACK_AUTH_TOKEN)
+ .flatMap { userResponse ->
+ val usersWithTimestamp = userResponse.members.toMutableList()
+ usersWithTimestamp.forEach {
+ it.last_updated = currentTime
+ }
+
+ Observable.just(usersWithTimestamp)
+ }
.subscribeOn(subscriber)
.observeOn(observer)
.map {
@@ -37,10 +54,17 @@ open class LoadUsersUseCase(
it.profile.real_name
}
}
+ .doOnNext { userRepository.addAll(it) }
+
+ return Observable
+ .concat(cachedUsers, networkUsers)
+ .first {
+ it.isNotEmpty() && it.isFreshEnough(MAX_CACHE_STALENESS)
+ }
}
- private fun filterNonCompanyUsers(response: UserListResponse): List {
- return response.members.filter {
+ private fun filterNonCompanyUsers(response: List): List {
+ return response.filter {
!it.is_bot
// At Codemate, profiles starting with "Ext-" aren't employees,
// but customers instead: they don't hang out in the office.
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt b/app/src/main/java/com/codemate/koffeemate/usecases/PostAccidentUseCase.kt
similarity index 82%
rename from app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt
rename to app/src/main/java/com/codemate/koffeemate/usecases/PostAccidentUseCase.kt
index 6bbc060..aa23397 100644
--- a/app/src/main/java/com/codemate/koffeemate/ui/main/PostAccidentUseCase.kt
+++ b/app/src/main/java/com/codemate/koffeemate/usecases/PostAccidentUseCase.kt
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.codemate.koffeemate.ui.main
+package com.codemate.koffeemate.usecases
import android.graphics.Bitmap
import com.codemate.koffeemate.common.AwardBadgeCreator
import com.codemate.koffeemate.data.local.CoffeeEventRepository
import com.codemate.koffeemate.data.local.CoffeePreferences
+import com.codemate.koffeemate.data.models.User
import com.codemate.koffeemate.data.network.SlackApi
import com.codemate.koffeemate.extensions.toRequestBody
import okhttp3.MediaType
@@ -29,28 +30,29 @@ import okhttp3.ResponseBody
import retrofit2.Response
import rx.Observable
import rx.Scheduler
+import javax.inject.Inject
+import javax.inject.Named
-open class PostAccidentUseCase(
+open class PostAccidentUseCase @Inject constructor(
var slackApi: SlackApi,
val coffeeEventRepository: CoffeeEventRepository,
val coffeePreferences: CoffeePreferences,
val awardBadgeCreator: AwardBadgeCreator,
- var subscriber: Scheduler,
- var observer: Scheduler
+ @Named("subscriber") var subscriber: Scheduler,
+ @Named("observer") var observer: Scheduler
) {
fun execute(
comment: String,
- userId: String,
- userName: String,
+ user: User,
profilePic: Bitmap
): Observable> {
- coffeeEventRepository.recordBrewingAccident(userId)
+ coffeeEventRepository.recordBrewingAccident(user)
- val awardCount = coffeeEventRepository.getAccidentCountForUser(userId)
+ val awardCount = coffeeEventRepository.getAccidentCountForUser(user)
val profilePicWithAward = awardBadgeCreator.createBitmapFileWithAward(profilePic, awardCount)
// Evaluates to "johns-certificate.png" etc
- val fileName = "${userName.toLowerCase()}s-certificate.png"
+ val fileName = "${user.profile.first_name.toLowerCase()}s-certificate.png"
val channel = coffeePreferences.getAccidentChannel()
return slackApi.postImage(
diff --git a/app/src/main/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCase.kt b/app/src/main/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCase.kt
similarity index 79%
rename from app/src/main/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCase.kt
rename to app/src/main/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCase.kt
index ad04100..6e94b48 100644
--- a/app/src/main/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCase.kt
+++ b/app/src/main/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCase.kt
@@ -14,18 +14,20 @@
* limitations under the License.
*/
-package com.codemate.koffeemate.ui.main
+package com.codemate.koffeemate.usecases
import com.codemate.koffeemate.data.network.SlackApi
import okhttp3.ResponseBody
import retrofit2.Response
import rx.Observable
import rx.Scheduler
+import javax.inject.Inject
+import javax.inject.Named
-open class SendCoffeeAnnouncementUseCase(
+open class SendCoffeeAnnouncementUseCase @Inject constructor(
var slackApi: SlackApi,
- var subscriber: Scheduler,
- var observer: Scheduler
+ @Named("subscriber") var subscriber: Scheduler,
+ @Named("observer") var observer: Scheduler
) {
fun execute(channel: String, newCoffeeMessage: String): Observable> {
return slackApi.postMessage(channel, newCoffeeMessage)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 460093f..12462d5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -63,7 +63,7 @@
Reset the counter?
- Who is guilty of this coffee brewing accident?
+ Who failed coffee brewing?
Who is brewing?
Select a guilty person
diff --git a/app/src/test/java/com/codemate/koffeemate/testutils/CommonTestUtils.kt b/app/src/test/java/com/codemate/koffeemate/testutils/CommonTestUtils.kt
index 3cda3eb..3cccba6 100644
--- a/app/src/test/java/com/codemate/koffeemate/testutils/CommonTestUtils.kt
+++ b/app/src/test/java/com/codemate/koffeemate/testutils/CommonTestUtils.kt
@@ -16,8 +16,8 @@
package com.codemate.koffeemate.testutils
-import com.codemate.koffeemate.data.network.models.Profile
-import com.codemate.koffeemate.data.network.models.User
+import com.codemate.koffeemate.data.models.Profile
+import com.codemate.koffeemate.data.models.User
import java.io.File
fun Any.getResourceFile(path: String): File {
diff --git a/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt
index 1abe82f..3682c44 100644
--- a/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt
+++ b/app/src/test/java/com/codemate/koffeemate/ui/main/MainPresenterTest.kt
@@ -8,10 +8,12 @@ import com.codemate.koffeemate.common.BrewingProgressUpdater
import com.codemate.koffeemate.common.ScreenSaver
import com.codemate.koffeemate.data.local.CoffeeEventRepository
import com.codemate.koffeemate.data.local.CoffeePreferences
-import com.codemate.koffeemate.data.local.models.CoffeeBrewingEvent
+import com.codemate.koffeemate.data.models.CoffeeBrewingEvent
import com.codemate.koffeemate.data.network.SlackApi
import com.codemate.koffeemate.testutils.fakeUser
import com.codemate.koffeemate.testutils.getResourceFile
+import com.codemate.koffeemate.usecases.PostAccidentUseCase
+import com.codemate.koffeemate.usecases.SendCoffeeAnnouncementUseCase
import com.nhaarman.mockito_kotlin.*
import okhttp3.MediaType
import okhttp3.ResponseBody
@@ -131,7 +133,7 @@ class MainPresenterTest {
verify(view).updateCoffeeProgress(0)
verify(view).resetCoffeeViewStatus()
- verify(mockCoffeeEventRepository).recordBrewingEvent(user.id)
+ verify(mockCoffeeEventRepository).recordBrewingEvent(user)
}
@Test
@@ -226,12 +228,7 @@ class MainPresenterTest {
presenter.personBrewingCoffee = user
presenter.launchAccidentReportingScreen()
- verify(view).showPostAccidentAnnouncementPrompt(
- user.id,
- user.profile.real_name,
- user.profile.first_name,
- user.profile.largestAvailableImage
- )
+ verify(view).showPostAccidentAnnouncementPrompt(user)
verifyNoMoreInteractions(view)
}
@@ -273,7 +270,7 @@ class MainPresenterTest {
.thenReturn(emptySuccessResponse)
presenter.personBrewingCoffee = fakeUser()
- presenter.announceCoffeeBrewingAccident("", "", "", mock())
+ presenter.announceCoffeeBrewingAccident("", fakeUser(), mock())
assertThat(presenter.personBrewingCoffee, nullValue())
}
@@ -283,7 +280,7 @@ class MainPresenterTest {
whenever(mockSlackApi.postImage(any(), any(), any(), any(), any()))
.thenReturn(emptySuccessResponse)
- presenter.announceCoffeeBrewingAccident("", "", "", mock())
+ presenter.announceCoffeeBrewingAccident("", fakeUser(), mock())
verify(view).showAccidentPostedSuccessfullyMessage()
verifyNoMoreInteractions(view)
@@ -294,7 +291,7 @@ class MainPresenterTest {
whenever(mockSlackApi.postImage(any(), any(), any(), any(), any()))
.thenReturn(Observable.error(Throwable()))
- presenter.announceCoffeeBrewingAccident("", "", "", mock())
+ presenter.announceCoffeeBrewingAccident("", fakeUser(), mock())
verify(view).showErrorPostingAccidentMessage()
}
diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt b/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt
index 59f1bb0..3e9d3c7 100644
--- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt
+++ b/app/src/test/java/com/codemate/koffeemate/ui/userselector/UserSelectorPresenterTest.kt
@@ -2,23 +2,18 @@ package com.codemate.koffeemate.ui.userselector
import android.graphics.Bitmap
import com.codemate.koffeemate.common.AwardBadgeCreator
-import com.codemate.koffeemate.data.local.CoffeeEventRepository
import com.codemate.koffeemate.data.local.CoffeePreferences
+import com.codemate.koffeemate.data.local.UserRepository
+import com.codemate.koffeemate.data.models.UserListResponse
import com.codemate.koffeemate.data.network.SlackApi
-import com.codemate.koffeemate.data.network.models.Profile
-import com.codemate.koffeemate.data.network.models.User
-import com.codemate.koffeemate.data.network.models.UserListResponse
import com.codemate.koffeemate.testutils.fakeUser
import com.codemate.koffeemate.testutils.getResourceFile
-import com.codemate.koffeemate.ui.main.PostAccidentUseCase
+import com.codemate.koffeemate.usecases.LoadUsersUseCase
import com.nhaarman.mockito_kotlin.*
-import okhttp3.MediaType
-import okhttp3.ResponseBody
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-import retrofit2.Response
import rx.Observable
import rx.schedulers.Schedulers
@@ -51,6 +46,7 @@ class UserSelectorPresenterTest {
MockitoAnnotations.initMocks(this)
val loadUsersUseCase = LoadUsersUseCase(
+ mock(),
mockSlackApi,
Schedulers.immediate(),
Schedulers.immediate()
diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/usecases/LoadUsersUseCaseTest.kt
similarity index 56%
rename from app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt
rename to app/src/test/java/com/codemate/koffeemate/usecases/LoadUsersUseCaseTest.kt
index db7af43..9ad462e 100644
--- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/LoadUsersUseCaseTest.kt
+++ b/app/src/test/java/com/codemate/koffeemate/usecases/LoadUsersUseCaseTest.kt
@@ -14,13 +14,15 @@
* limitations under the License.
*/
-package com.codemate.koffeemate.ui.userselector
+package com.codemate.koffeemate.usecases
import com.codemate.koffeemate.BuildConfig
+import com.codemate.koffeemate.data.local.UserRepository
+import com.codemate.koffeemate.data.models.User
import com.codemate.koffeemate.data.network.SlackApi
-import com.codemate.koffeemate.data.network.SlackService
-import com.codemate.koffeemate.data.network.models.User
import com.codemate.koffeemate.testutils.getResourceFile
+import com.nhaarman.mockito_kotlin.verify
+import com.nhaarman.mockito_kotlin.whenever
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.hamcrest.core.IsEqual.equalTo
@@ -28,10 +30,15 @@ import org.junit.After
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
import rx.observers.TestSubscriber
import rx.schedulers.Schedulers
class LoadUsersUseCaseTest {
+ @Mock
+ lateinit var mockUserRepository: UserRepository
+
lateinit var mockServer: MockWebServer
lateinit var slackApi: SlackApi
lateinit var useCase: LoadUsersUseCase
@@ -39,11 +46,14 @@ class LoadUsersUseCaseTest {
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
mockServer = MockWebServer()
mockServer.start()
- slackApi = SlackService.getApi(mockServer.url("/"))
+ slackApi = SlackApi.create(mockServer.url("/"))
useCase = LoadUsersUseCase(
+ mockUserRepository,
slackApi,
Schedulers.immediate(),
Schedulers.immediate()
@@ -67,14 +77,73 @@ class LoadUsersUseCaseTest {
}
@Test
- fun execute_WithValidData_CallsOnNextWithUsersAndCompletes() {
- val userListJson = getResourceFile("seeds/sample_userlist_response.json").readText()
- mockServer.enqueue(MockResponse().setBody(userListJson))
+ fun execute_WhenLoadingUsersFromApi_CachesThem() {
+ enqueUserJsonResponseFromServer()
+
+ useCase.execute().subscribe(testSubscriber)
+ testSubscriber.assertValueCount(1)
+
+ val userList = testSubscriber.onNextEvents[0]
+ verify(mockUserRepository).addAll(userList)
+ }
+
+ @Test
+ fun execute_WhenHasNoCachedUsers_LoadsThemFromApi() {
+ enqueUserJsonResponseFromServer()
+
+ useCase.execute().subscribe(testSubscriber)
+ testSubscriber.assertValueCount(1)
+
+ val userList = testSubscriber.onNextEvents[0]
+ verifyUsersFromApi(userList)
+ }
+
+ @Test
+ fun execute_WhenHasFreshEnoughCachedUsers_LoadsThemFromCache() {
+ val currentTime = System.currentTimeMillis()
+ val cachedUsers = listOf(
+ User(last_updated = currentTime),
+ User(last_updated = currentTime),
+ User(last_updated = currentTime)
+ )
+
+ whenever(mockUserRepository.getAll()).thenReturn(cachedUsers)
+ enqueUserJsonResponseFromServer()
useCase.execute().subscribe(testSubscriber)
testSubscriber.assertValueCount(1)
val userList = testSubscriber.onNextEvents[0]
+ assertThat(userList.size, equalTo(3))
+ assertThat(userList, equalTo(cachedUsers))
+ }
+
+ @Test
+ fun execute_WhenHasTooOldCachedUsers_LoadsThemFromApi() {
+ val currentTime = System.currentTimeMillis() - useCase.MAX_CACHE_STALENESS
+ val cachedUsers = listOf(
+ User(last_updated = currentTime),
+ User(last_updated = currentTime),
+ User(last_updated = currentTime)
+ )
+
+ whenever(mockUserRepository.getAll()).thenReturn(cachedUsers)
+ enqueUserJsonResponseFromServer()
+
+ useCase.execute().subscribe(testSubscriber)
+ testSubscriber.assertValueCount(1)
+
+ val userList = testSubscriber.onNextEvents[0]
+ verifyUsersFromApi(userList)
+ }
+
+ // Utility functions -->
+ private fun enqueUserJsonResponseFromServer() {
+ val userListJson = getResourceFile("seeds/sample_userlist_response.json").readText()
+ mockServer.enqueue(MockResponse().setBody(userListJson))
+ }
+
+ private fun verifyUsersFromApi(userList: List) {
assertThat(userList.size, equalTo(2))
with(userList[0]) {
diff --git a/app/src/test/java/com/codemate/koffeemate/ui/userselector/PostAccidentUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/usecases/PostAccidentUseCaseTest.kt
similarity index 75%
rename from app/src/test/java/com/codemate/koffeemate/ui/userselector/PostAccidentUseCaseTest.kt
rename to app/src/test/java/com/codemate/koffeemate/usecases/PostAccidentUseCaseTest.kt
index e6f883a..3e35ed5 100644
--- a/app/src/test/java/com/codemate/koffeemate/ui/userselector/PostAccidentUseCaseTest.kt
+++ b/app/src/test/java/com/codemate/koffeemate/usecases/PostAccidentUseCaseTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.codemate.koffeemate.ui.userselector
+package com.codemate.koffeemate.usecases
import android.content.SharedPreferences
import android.graphics.Bitmap
@@ -23,18 +23,10 @@ import com.codemate.koffeemate.common.AwardBadgeCreator
import com.codemate.koffeemate.data.local.CoffeeEventRepository
import com.codemate.koffeemate.data.local.CoffeePreferences
import com.codemate.koffeemate.data.network.SlackApi
-import com.codemate.koffeemate.data.network.SlackService
-import com.codemate.koffeemate.data.network.models.Profile
-import com.codemate.koffeemate.data.network.models.User
-import com.codemate.koffeemate.testutils.RegexMatcher
+import com.codemate.koffeemate.testutils.RegexMatcher.Companion.matchesPattern
import com.codemate.koffeemate.testutils.fakeUser
import com.codemate.koffeemate.testutils.getResourceFile
-import com.codemate.koffeemate.ui.main.PostAccidentUseCase
-import com.nhaarman.mockito_kotlin.mock
-import com.nhaarman.mockito_kotlin.verify
-import com.nhaarman.mockito_kotlin.verifyNoMoreInteractions
-import com.nhaarman.mockito_kotlin.whenever
-import okhttp3.Dispatcher
+import com.nhaarman.mockito_kotlin.*
import okhttp3.ResponseBody
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
@@ -50,8 +42,6 @@ import rx.observers.TestSubscriber
import rx.schedulers.Schedulers
class PostAccidentUseCaseTest {
- val TEST_USER_ID = "abc123"
-
@Mock
lateinit var mockCoffeePreferences: CoffeePreferences
@@ -79,12 +69,12 @@ class PostAccidentUseCaseTest {
mockCoffeePreferences.preferences = mock()
whenever(mockCoffeePreferences.getAccidentChannel()).thenReturn("test-channel")
- whenever(mockCoffeeEventRepository.getAccidentCountForUser(TEST_USER_ID)).thenReturn(1)
+ whenever(mockCoffeeEventRepository.getAccidentCountForUser(any())).thenReturn(1)
whenever(mockAwardBadgeCreator.createBitmapFileWithAward(mockBitmap, 1))
.thenReturn(getResourceFile("images/empty.png"))
- slackApi = SlackService.getApi(mockServer.url("/"))
+ slackApi = SlackApi.create(mockServer.url("/"))
useCase = PostAccidentUseCase(
slackApi,
mockCoffeeEventRepository,
@@ -105,26 +95,26 @@ class PostAccidentUseCaseTest {
@Test
fun announceCoffeeBrewingAccident_ShouldMakeCorrectRequest() {
val user = fakeUser()
- useCase.execute("Test comment", user.id, user.profile.first_name, mockBitmap).subscribe(testSubscriber)
+ useCase.execute("Test comment", user, mockBitmap).subscribe(testSubscriber)
// TODO: There has to be a better way to verify these multipart post params, right? :S
val requestBody = mockServer.takeRequest().body.readUtf8()
assertThat(requestBody, StringContains.containsString("filename=\"jormas-certificate.png\""))
- assertThat(requestBody, RegexMatcher.matchesPattern(".*channels.*test-channel.*"))
- assertThat(requestBody, RegexMatcher.matchesPattern(".*initial_comment.*Test comment.*"))
- assertThat(requestBody, RegexMatcher.matchesPattern(".*token.*${BuildConfig.SLACK_AUTH_TOKEN}.*"))
+ assertThat(requestBody, matchesPattern(".*channels.*test-channel.*"))
+ assertThat(requestBody, matchesPattern(".*initial_comment.*Test comment.*"))
+ assertThat(requestBody, matchesPattern(".*token.*${BuildConfig.SLACK_AUTH_TOKEN}.*"))
}
@Test
fun announceCoffeeBrewingAccident_WhenSuccessful_NotifiesUIAndStoresEvent() {
val user = fakeUser()
- useCase.execute("", user.id, user.profile.first_name, mockBitmap).subscribe(testSubscriber)
+ useCase.execute("", user, mockBitmap).subscribe(testSubscriber)
testSubscriber.assertValueCount(1)
testSubscriber.assertCompleted()
- verify(mockCoffeeEventRepository).recordBrewingAccident(user.id)
- verify(mockCoffeeEventRepository).getAccidentCountForUser(TEST_USER_ID)
+ verify(mockCoffeeEventRepository).recordBrewingAccident(user)
+ verify(mockCoffeeEventRepository).getAccidentCountForUser(user)
verifyNoMoreInteractions(mockCoffeeEventRepository)
}
}
\ No newline at end of file
diff --git a/app/src/test/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCaseTest.kt b/app/src/test/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCaseTest.kt
similarity index 93%
rename from app/src/test/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCaseTest.kt
rename to app/src/test/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCaseTest.kt
index a7889a7..915b689 100644
--- a/app/src/test/java/com/codemate/koffeemate/ui/main/SendCoffeeAnnouncementUseCaseTest.kt
+++ b/app/src/test/java/com/codemate/koffeemate/usecases/SendCoffeeAnnouncementUseCaseTest.kt
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.codemate.koffeemate.ui.main
+package com.codemate.koffeemate.usecases
import com.codemate.koffeemate.BuildConfig
import com.codemate.koffeemate.data.network.SlackApi
-import com.codemate.koffeemate.data.network.SlackService
-import okhttp3.Dispatcher
import okhttp3.ResponseBody
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
@@ -49,7 +47,7 @@ class SendCoffeeAnnouncementUseCaseTest {
mockServer = MockWebServer()
mockServer.start()
- slackApi = SlackService.getApi(mockServer.url("/"))
+ slackApi = SlackApi.create(mockServer.url("/"))
useCase = SendCoffeeAnnouncementUseCase(
slackApi,
Schedulers.immediate(),