Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,18 @@ the user during the login process.

### Version 4.1 - HIGH MAINTENANCE
- [ ] Like book suggestions
- [ ] Bugfixes
- [ ] Split wishlist and suggestions
- [x] Upgrade to Kotlin > 1.4.20
- [x] ViewBinding
- [x] Remove Kotterknife usage
- [ ] Backup file improvements
- [ ] Check Backup pages & labels restore
- [ ] Show path to local backup files
- [ ] Open file with FileProvider
- [x] Improve main screen
- [ ] Improve main screen
- [ ] Bigger book covers
- [ ] Show stars for read books
- [x] Replace buggy SharedElementTransition for DetailPageNavigation
- [x] Use lighter UI for labels (outline instead of filled)
- [x] New labels screen
Expand Down
3 changes: 1 addition & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ apply plugin: 'com.google.firebase.crashlytics'

android {
compileSdkVersion 30
buildToolsVersion '30.0.0'
buildToolsVersion '30.0.2'

defaultConfig {
applicationId "at.shockbytes.dante"
Expand Down Expand Up @@ -120,7 +120,6 @@ dependencies {

// debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"

implementation 'com.tbuonomo:creative-viewpager:1.0.1'
implementation 'pub.devrel:easypermissions:3.0.0'
implementation 'ru.bullyboo.view:circleseekbar:1.0.3'
implementation 'com.f2prateek.rx.preferences2:rx-preferences:2.0.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ data class Suggestion(
val suggestionId: String,
val suggestion: BookSuggestionEntity,
val suggester: Suggester,
val recommendation: String
val recommendation: String,
val likes: Int,
val isLikedByMe: Boolean,
val isReportedByMe: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package at.shockbytes.dante.suggestions

data class SuggestionLikeRequest(
val isLikedByMe: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package at.shockbytes.dante.suggestions

import at.shockbytes.dante.core.book.BookEntity
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
import kotlinx.coroutines.CoroutineScope

Expand All @@ -14,6 +15,8 @@ interface SuggestionsRepository {

fun reportSuggestion(suggestionId: String, scope: CoroutineScope): Completable

fun likeSuggestion(suggestionId: String, isLikedByMe: Boolean, scope: CoroutineScope): Completable

fun getUserReportedSuggestions(): Single<List<String>>

fun suggestBook(bookEntity: BookEntity, recommendation: String): Completable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class DataStoreSuggestionsCache(
private val suggestionsKey = preferencesKey<String>(KEY_SUGGESTION_CACHE)
private val cacheKey = preferencesKey<Long>(KEY_CACHE)
private val reportedSuggestionsKey = preferencesSetKey<String>(KEY_REPORTED_SUGGESTION_CACHE)
private val likesSuggestionsKey = preferencesSetKey<String>(KEY_LIKED_SUGGESTION_CACHE)

override fun lastCacheTimestamp(): Single<Long> {
return singleOf {
Expand All @@ -44,13 +45,37 @@ class DataStoreSuggestionsCache(
}

override fun loadSuggestions(): Single<Suggestions> {
return singleOf {
runBlocking {
dataStore.data.first()[suggestionsKey]
}?.let { data ->
gson.fromJson<Suggestions>(data)
} ?: Suggestions(listOf())
}
return Single
.zip(
loadReportedSuggestions(),
loadLikedSuggestions(),
{ reportedSuggestionIds, likedSuggestionIds ->

loadSuggestionsFromCache().suggestions
.map { suggestion ->

val isLikedByMe =
likedSuggestionIds.contains(suggestion.suggestionId)
val isReportedByMe =
reportedSuggestionIds.contains(suggestion.suggestionId)

suggestion.copy(
isLikedByMe = isLikedByMe,
isReportedByMe = isReportedByMe,
likes = suggestion.likes.inc()
)
}
.let(::Suggestions)
}
)
}

private fun loadSuggestionsFromCache(): Suggestions {
return runBlocking {
dataStore.data.first()[suggestionsKey]
}?.let { data ->
gson.fromJson<Suggestions>(data)
} ?: Suggestions(listOf())
}

override suspend fun cacheSuggestionReport(suggestionId: String) {
Expand All @@ -69,9 +94,35 @@ class DataStoreSuggestionsCache(
}
}

override suspend fun cacheSuggestionLike(suggestionId: String) {
dataStore.edit { preferences ->
val likes = preferences[likesSuggestionsKey].orEmpty().toMutableSet()
likes.add(suggestionId)
preferences[likesSuggestionsKey] = likes
}
}

override suspend fun removeSuggestionLike(suggestionId: String) {
dataStore.edit { preferences ->
val likes = preferences[likesSuggestionsKey].orEmpty().toMutableSet()
if (likes.remove(suggestionId)) {
preferences[likesSuggestionsKey] = likes
}
}
}

override fun loadLikedSuggestions(): Single<List<String>> {
return singleOf {
runBlocking {
dataStore.data.first()[likesSuggestionsKey].orEmpty().toList()
}
}
}

companion object {
private const val KEY_SUGGESTION_CACHE = "key_suggestion_cache"
private const val KEY_CACHE = "key_cache"
private const val KEY_REPORTED_SUGGESTION_CACHE = "key_reported_suggestion_cache"
private const val KEY_LIKED_SUGGESTION_CACHE = "key_liked_suggestion_cache"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,17 @@ interface SuggestionsCache {

fun loadSuggestions(): Single<Suggestions>

// ------------ Reports ------------

suspend fun cacheSuggestionReport(suggestionId: String)

fun loadReportedSuggestions(): Single<List<String>>

// ------------- Likes -------------

suspend fun cacheSuggestionLike(suggestionId: String)

suspend fun removeSuggestionLike(suggestionId: String)

fun loadLikedSuggestions(): Single<List<String>>
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package at.shockbytes.dante.suggestions.firebase

import at.shockbytes.dante.suggestions.SuggestionLikeRequest
import at.shockbytes.dante.suggestions.SuggestionRequest
import at.shockbytes.dante.suggestions.Suggestions
import io.reactivex.Completable
Expand All @@ -13,16 +14,23 @@ import retrofit2.http.Path
interface FirebaseSuggestionsApi {

@GET("suggestions")
fun getSuggestions(
fun getRawSuggestions(
@Header("Authorization") bearerToken: String
): Single<Suggestions>
): Single<RawSuggestions>

@POST("suggestions/{suggestionId}/report")
fun reportSuggestion(
@Header("Authorization") bearerToken: String,
@Path("suggestionId") suggestionId: String
): Completable

@POST("suggestions/{suggestionId}/like")
fun likeSuggestion(
@Header("Authorization") bearerToken: String,
@Path("suggestionId") suggestionId: String,
@Body suggestionLikeRequest: SuggestionLikeRequest
): Completable

@POST("suggestions")
fun suggestBook(
@Header("Authorization") bearerToken: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package at.shockbytes.dante.suggestions.firebase
import at.shockbytes.dante.core.book.BookEntity
import at.shockbytes.dante.core.login.LoginRepository
import at.shockbytes.dante.suggestions.BookSuggestionEntity
import at.shockbytes.dante.suggestions.Suggestion
import at.shockbytes.dante.suggestions.SuggestionLikeRequest
import at.shockbytes.dante.suggestions.SuggestionRequest
import at.shockbytes.dante.suggestions.Suggestions
import at.shockbytes.dante.suggestions.SuggestionsRepository
Expand All @@ -26,7 +28,10 @@ class FirebaseSuggestionsRepository(
private val tracker: Tracker
) : SuggestionsRepository {

override fun loadSuggestions(accessTimestamp: Long, scope: CoroutineScope): Single<Suggestions> {
override fun loadSuggestions(
accessTimestamp: Long,
scope: CoroutineScope
): Single<Suggestions> {
return shouldUseRemoteData(accessTimestamp)
.flatMap { useRemoteSuggestions ->
if (useRemoteSuggestions) {
Expand Down Expand Up @@ -64,7 +69,8 @@ class FirebaseSuggestionsRepository(

private fun loadRemoteSuggestions(scope: CoroutineScope): Single<Suggestions> {
return loginRepository.getAuthorizationHeader()
.flatMap(firebaseSuggestionsApi::getSuggestions)
.flatMap(firebaseSuggestionsApi::getRawSuggestions)
.flatMap(::mapToSuggestions)
.doOnSuccess { suggestions ->
if (suggestions.isNotEmpty()) {
cacheRemoteSuggestions(suggestions, scope)
Expand All @@ -73,6 +79,38 @@ class FirebaseSuggestionsRepository(
.subscribeOn(schedulers.io)
}

private fun mapToSuggestions(rawSuggestions: RawSuggestions): Single<Suggestions> {

return Single
.zip(
suggestionsCache.loadReportedSuggestions(),
suggestionsCache.loadLikedSuggestions(),
{ reportedSuggestionIds, likedSuggestionIds ->

val suggestions = rawSuggestions.suggestions
.map { rawSuggestion ->

val isLikedByMe =
likedSuggestionIds.contains(rawSuggestion.suggestionId)
val isReportedByMe =
reportedSuggestionIds.contains(rawSuggestion.suggestionId)

Suggestion(
suggestionId = rawSuggestion.suggestionId,
suggestion = rawSuggestion.suggestion,
suggester = rawSuggestion.suggester,
recommendation = rawSuggestion.recommendation,
likes = rawSuggestion.likes,
isLikedByMe = isLikedByMe,
isReportedByMe = isReportedByMe
)
}

Suggestions(suggestions)
}
)
}

private fun cacheRemoteSuggestions(suggestions: Suggestions, scope: CoroutineScope) {
scope.launch {
suggestionsCache.cache(suggestions)
Expand All @@ -95,6 +133,25 @@ class FirebaseSuggestionsRepository(
.subscribeOn(schedulers.io)
}

override fun likeSuggestion(
suggestionId: String,
isLikedByMe: Boolean,
scope: CoroutineScope
): Completable {
return loginRepository.getAuthorizationHeader()
.flatMapCompletable { bearerToken ->
firebaseSuggestionsApi.likeSuggestion(
bearerToken,
suggestionId,
SuggestionLikeRequest(isLikedByMe)
)
}
.doOnComplete {
cacheLikedSuggestion(suggestionId, isLikedByMe, scope)
}
.subscribeOn(schedulers.io)
}

override fun getUserReportedSuggestions(): Single<List<String>> {
return suggestionsCache.loadReportedSuggestions()
}
Expand All @@ -105,6 +162,20 @@ class FirebaseSuggestionsRepository(
}
}

private fun cacheLikedSuggestion(
suggestionId: String,
isLikedByMe: Boolean,
scope: CoroutineScope
) {
scope.launch {
if (isLikedByMe) {
suggestionsCache.removeSuggestionLike(suggestionId)
} else {
suggestionsCache.cacheSuggestionLike(suggestionId)
}
}
}

override fun suggestBook(bookEntity: BookEntity, recommendation: String): Completable {
return loginRepository.getAuthorizationHeader()
.flatMapCompletable { bearerToken ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package at.shockbytes.dante.suggestions.firebase

import at.shockbytes.dante.suggestions.BookSuggestionEntity
import at.shockbytes.dante.suggestions.Suggester

data class RawSuggestions(
val suggestions: List<RawSuggestion>
) {

data class RawSuggestion(
val suggestionId: String,
val suggestion: BookSuggestionEntity,
val suggester: Suggester,
val recommendation: String,
val likes: Int
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ interface OnSuggestionActionClickedListener {
fun onAddSuggestionToWishlist(suggestion: Suggestion)

fun onReportBookSuggestion(suggestionId: String, suggestionTitle: String)

fun onLikeBookSuggestion(suggestionId: String, suggestionTitle: String, isLikedByMe: Boolean)
}
Loading