Skip to content

Commit

Permalink
Merge pull request #3126 from CruGlobal/syncFavorites
Browse files Browse the repository at this point in the history
GT-1796 Sync Favorite tools with the API
  • Loading branch information
frett authored Oct 5, 2023
2 parents c0c3bde + 7da63c1 commit d601025
Show file tree
Hide file tree
Showing 37 changed files with 1,545 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -53,7 +54,10 @@ class DashboardViewModel @Inject constructor(
@Suppress("DeferredResultUnused")
syncService.syncToolSharesAsync()
syncsRunning.value++
syncService.syncTools(force)
coroutineScope {
launch { syncService.syncFavoriteTools(force) }
launch { syncService.syncTools(force) }
}
syncsRunning.value--
}
}
Expand Down
12 changes: 10 additions & 2 deletions app/src/main/kotlin/org/cru/godtools/ui/tools/ToolViewModels.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.cru.godtools.downloadmanager.GodToolsDownloadManager
import org.cru.godtools.model.Language
import org.cru.godtools.model.Tool
import org.cru.godtools.model.Translation
import org.cru.godtools.sync.GodToolsSyncService

internal const val EXTRA_ADDITIONAL_LANGUAGE = "additionalLanguage"

Expand All @@ -42,6 +43,7 @@ class ToolViewModels @Inject constructor(
private val languagesRepository: LanguagesRepository,
private val manifestManager: ManifestManager,
private val settings: Settings,
private val syncService: GodToolsSyncService,
private val toolsRepository: ToolsRepository,
private val translationsRepository: TranslationsRepository,
savedState: SavedStateHandle,
Expand Down Expand Up @@ -123,10 +125,16 @@ class ToolViewModels @Inject constructor(
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)

fun pinTool() {
viewModelScope.launch { toolsRepository.pinTool(code) }
viewModelScope.launch {
toolsRepository.pinTool(code)
syncService.syncDirtyFavoriteTools()
}
settings.setFeatureDiscovered(Settings.FEATURE_TOOL_FAVORITE)
}
fun unpinTool() = viewModelScope.launch { toolsRepository.unpinTool(code) }
fun unpinTool() = viewModelScope.launch {
toolsRepository.unpinTool(code)
syncService.syncDirtyFavoriteTools()
}
}

private fun Flow<Tool?>.attachmentFileFlow(transform: (value: Tool?) -> Long?) = this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class ExternalSingletonsModule {
val syncService: GodToolsSyncService by lazy {
mockk {
coEvery { syncTools(any()) } returns true
coEvery { syncFavoriteTools(any()) } returns true
every { syncFollowupsAsync() } returns CompletableDeferred(true)
every { syncToolSharesAsync() } returns CompletableDeferred(true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class DashboardViewModelTest {
every { syncFollowupsAsync() } returns CompletableDeferred()
every { syncToolSharesAsync() } returns CompletableDeferred()
coEvery { syncTools(any()) } returns true
coEvery { syncFavoriteTools(any()) } returns true
}
private val testScope = TestScope()

Expand All @@ -59,6 +60,7 @@ class DashboardViewModelTest {
syncService.syncFollowupsAsync()
syncService.syncToolSharesAsync()
syncService.syncTools(false)
syncService.syncFavoriteTools(false)
}
}

Expand Down Expand Up @@ -90,7 +92,8 @@ class DashboardViewModelTest {
coVerifyAll {
syncService.syncFollowupsAsync()
syncService.syncToolSharesAsync()
syncService.syncTools(any())
syncService.syncTools(false)
syncService.syncFavoriteTools(false)
}
}
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "dag
hilt-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "dagger" }
javapoet = "com.squareup:javapoet:1.13.0"
json = "org.json:json:20230618"
jsonUnit-assertj = "net.javacrumbs.json-unit:json-unit-assertj:3.2.2"
jsoup = "org.jsoup:jsoup:1.16.1"
junit = "junit:junit:4.13.2"
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinCoroutines" }
Expand All @@ -174,6 +175,7 @@ lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "
materialComponents = "com.google.android.material:material:1.9.0"
mockk = "io.mockk:mockk:1.13.8"
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp3" }
okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp3" }
onesky-gradlePlugin = "co.brainly:plugin:1.6.0"
picasso = "com.squareup.picasso:picasso:2.8"
picasso-transformations = "jp.wasabeef:picasso-transformations:2.4.0"
Expand Down
3 changes: 3 additions & 0 deletions library/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ dependencies {
kapt(libs.hilt.compiler)

testImplementation(libs.json)
testImplementation(libs.jsonUnit.assertj)
testImplementation(libs.kotlin.coroutines.test)
testImplementation(libs.okhttp3.mockwebserver)
}
6 changes: 6 additions & 0 deletions library/api/src/main/kotlin/org/cru/godtools/api/ApiModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ object ApiModule {
@Named(MOBILE_CONTENT_API_AUTHENTICATED) retrofit: Retrofit,
): UserCountersApi = retrofit.create()

@Provides
@Reusable
fun userFavoriteToolsApi(
@Named(MOBILE_CONTENT_API_AUTHENTICATED) retrofit: Retrofit,
): UserFavoriteToolsApi = retrofit.create()

@Provides
@Reusable
fun viewsApi(@Named(MOBILE_CONTENT_API) retrofit: Retrofit): ViewsApi = retrofit.create()
Expand Down
10 changes: 9 additions & 1 deletion library/api/src/main/kotlin/org/cru/godtools/api/UserApi.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package org.cru.godtools.api

import org.ccci.gto.android.common.jsonapi.model.JsonApiObject
import org.ccci.gto.android.common.jsonapi.retrofit2.JsonApiParams
import org.ccci.gto.android.common.jsonapi.retrofit2.model.JsonApiRetrofitObject
import org.cru.godtools.model.User
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.PATCH
import retrofit2.http.QueryMap

internal const val PATH_USER = "users/me"

interface UserApi {
@GET(PATH_USER)
suspend fun getUser(): Response<JsonApiObject<User>>
suspend fun getUser(@QueryMap params: JsonApiParams = JsonApiParams()): Response<JsonApiObject<User>>

@PATCH(PATH_USER)
suspend fun updateUser(@Body user: JsonApiRetrofitObject<User>): Response<JsonApiObject<User>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.cru.godtools.api

import org.ccci.gto.android.common.jsonapi.model.JsonApiObject
import org.ccci.gto.android.common.jsonapi.retrofit2.JsonApiParams
import org.ccci.gto.android.common.jsonapi.retrofit2.annotation.JsonApiFields
import org.cru.godtools.model.Tool
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.HTTP
import retrofit2.http.POST
import retrofit2.http.QueryMap

interface UserFavoriteToolsApi {
companion object {
internal const val PATH_FAVORITE_TOOLS = "$PATH_USER/relationships/favorite-tools"
}

@POST(PATH_FAVORITE_TOOLS)
suspend fun addFavoriteTools(
@QueryMap params: JsonApiParams = JsonApiParams(),
@Body
@JsonApiFields(Tool.JSONAPI_TYPE)
tools: List<Tool>,
): Response<JsonApiObject<Tool>>

@HTTP(method = "DELETE", path = PATH_FAVORITE_TOOLS, hasBody = true)
suspend fun removeFavoriteTools(
@QueryMap params: JsonApiParams = JsonApiParams(),
@Body
@JsonApiFields(Tool.JSONAPI_TYPE)
tools: List<Tool>,
): Response<JsonApiObject<Tool>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.cru.godtools.api

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlinx.coroutines.test.runTest
import net.javacrumbs.jsonunit.assertj.assertThatJson
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.ccci.gto.android.common.jsonapi.retrofit2.JsonApiConverterFactory
import org.cru.godtools.api.UserFavoriteToolsApi.Companion.PATH_FAVORITE_TOOLS
import org.cru.godtools.model.Tool
import org.junit.Rule
import retrofit2.Retrofit

private const val JSON_RESPONSE_FAVORITES = "{data:[{id:5,type:\"resource\"}]}"

class UserFavoriteToolsApiTest {
@get:Rule
val server = MockWebServer()

private var api = Retrofit.Builder()
.baseUrl(server.url("/"))
.addConverterFactory(JsonApiConverterFactory(Tool::class.java))
.build()
.create(UserFavoriteToolsApi::class.java)

@Test
fun `removeFavoriteTools()`() = runTest {
server.enqueue(MockResponse().setBody(JSON_RESPONSE_FAVORITES))

val resp = api.removeFavoriteTools(tools = listOf(Tool("en") { id = 1 })).body()!!.data.single()
assertNotNull(resp) { assertEquals(5, it.id) }

val request = server.takeRequest()
assertEquals("DELETE", request.method)
assertEquals("/$PATH_FAVORITE_TOOLS", request.path)
assertThatJson(request.body.readUtf8()) {
isObject
node("data").isArray.hasSize(1)
node("data[0].id").isEqualTo(1)
node("data[0].attributes").isAbsent()
node("data[0].relationships").isAbsent()
}
}
}
Loading

0 comments on commit d601025

Please sign in to comment.