Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thumbs-up function 👍 #626

Merged
merged 45 commits into from
Feb 16, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
44b8b10
Implement posting thumbs-up count to firestore
mkeeda Jan 26, 2020
b49b8b0
Fix multiple counter to single counter, and adopt realtime updates
mkeeda Jan 29, 2020
4a2de25
Apply some fixes for thumbs up
takahirom Feb 2, 2020
286d6bd
Debugging thumbs-up function of firestore
mkeeda Feb 1, 2020
bf57b49
Success to show thumbs-up counts if shards are already exists
mkeeda Feb 2, 2020
da8e92f
Remove a white space
mkeeda Feb 2, 2020
075e3b2
Change processing whether shard exists into checking a shard that sha…
mkeeda Feb 3, 2020
a4c3287
Bind error to SystemViewModel
mkeeda Feb 3, 2020
6f6b1c0
Apply some thumbs-up button designs
mkeeda Feb 3, 2020
3cf94b8
Add a thumbs-up icon
mkeeda Feb 3, 2020
0b0483b
Fix indent and variable name
mkeeda Feb 3, 2020
0cd8484
Adjust thumbs-up button styles
mkeeda Feb 4, 2020
707fb7b
Show outlined button if thumbs-up count is zero
mkeeda Feb 4, 2020
f0db2dd
Fix grradlew.bat
mkeeda Feb 5, 2020
856e3ac
Merge branch 'master' into thumb-up
mkeeda Feb 5, 2020
6ebcbfa
Change number of shards; 10 -> 5
mkeeda Feb 5, 2020
e478ca9
Remove unnecessary changes
mkeeda Feb 5, 2020
ab883b9
Fix SessionDetailViewModelTest
mkeeda Feb 8, 2020
3bac614
Add test to get thumbs-up count
mkeeda Feb 8, 2020
8e26ef0
Count up at intervals for FireStore charges
takahirom Feb 7, 2020
59aad5a
Apply ktlintFormat
mkeeda Feb 8, 2020
67e699a
Separate incremented count flow from total count flow
mkeeda Feb 9, 2020
6f07494
Display incremented thumbs-up count by own
mkeeda Feb 11, 2020
7d102fd
Pop-up and drop-out animation
mkeeda Feb 12, 2020
ea1e3d4
Use resource string placeholder
mkeeda Feb 13, 2020
8c64427
Apply ktlintFormat
mkeeda Feb 14, 2020
54940d7
Pass lintDebug task
mkeeda Feb 14, 2020
035602c
Fix thumbsUpCount test
mkeeda Feb 14, 2020
5f1c524
Bind error of incrementation thumbs-up
mkeeda Feb 14, 2020
c88056a
Merge the 2 increment liveDatas
mkeeda Feb 14, 2020
f9d5066
Appley ktlintFormat
mkeeda Feb 15, 2020
7e54b27
Fix SessionDetailViewModelTest; separate error
mkeeda Feb 15, 2020
bc6dad4
Extract animation functions from SessionDetailTileItem
mkeeda Feb 15, 2020
6d1c674
Suppress useless cast for lintDebug task
mkeeda Feb 15, 2020
5889f99
Merge branch 'master' into thumb-up
takahirom Feb 15, 2020
e1cba55
Tweak thumb up animation
takahirom Feb 15, 2020
dbe94a2
Merge branch 'thumb-up' of github.com:mkeeda/conference-app-2020 into…
mkeeda Feb 16, 2020
e556cac
Use UiModel.error
takahirom Feb 15, 2020
489fb21
Scale animation when users continually press thumbs-up
mkeeda Feb 16, 2020
69c94e9
Apply ktlintFormat
mkeeda Feb 16, 2020
521ba2d
Restore error checking
mkeeda Feb 16, 2020
eb5daa5
Minor adjustment ui design
mkeeda Feb 16, 2020
48c71ba
Remove ⭐ and rewrite debug logging messages
mkeeda Feb 16, 2020
3d4c670
LintDebug task can success without type cast
mkeeda Feb 16, 2020
808f2b2
Fix stroke color of blue thumbs-up button
mkeeda Feb 16, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ import kotlinx.coroutines.flow.Flow
interface Firestore {
fun getFavoriteSessionIds(): Flow<List<String>>
suspend fun toggleFavorite(sessionId: SessionId)
fun getThumbsUpCount(sessionId: SessionId): Flow<Int>
suspend fun incrementThumbsUpCount(sessionId: SessionId)
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
package io.github.droidkaigi.confsched2020.data.firestore.internal

import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.DocumentReference
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FieldValue
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.Query
import com.google.firebase.firestore.QuerySnapshot
import com.google.firebase.firestore.Source
import io.github.droidkaigi.confsched2020.data.firestore.Firestore
import io.github.droidkaigi.confsched2020.model.SessionId
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.tasks.await
import timber.log.Timber
import timber.log.debug
import javax.inject.Inject
import kotlin.math.floor

internal class FirestoreImpl @Inject constructor() : Firestore {

Expand All @@ -35,7 +40,7 @@ internal class FirestoreImpl @Inject constructor() : Firestore {
emit(favoritesRef)
}
val favoritesSnapshotFlow = setupFavorites.flatMapLatest {
it.whereEqualTo("favorite", true).toFlow()
it.whereEqualTo(FAVORITE_VALUE_KEY, true).toFlow()
}
return favoritesSnapshotFlow.mapLatest { favorites ->
Timber.debug { "favoritesSnapshotFlow onNext" }
Expand All @@ -57,12 +62,44 @@ internal class FirestoreImpl @Inject constructor() : Firestore {
} else {
Timber.debug { "toggleFavorite: $sessionId document not exits" }
document.reference
.set(mapOf("favorite" to newFavorite))
.set(mapOf(FAVORITE_VALUE_KEY to newFavorite))
.await()
}
Timber.debug { "toggleFavorite: end" }
}

override fun getThumbsUpCount(sessionId: SessionId): Flow<Int> {
val setupThumbsUp = flow {
signInIfNeeded()
val counterRef = getThumbsUpCounterRef(sessionId)
createShardsIfNeeded(counterRef)
emit(counterRef)
}

val thumbsUpSnapshot = setupThumbsUp.flatMapLatest {
it.toFlow()
}

return thumbsUpSnapshot.map { shards ->
var count = 0
shards.forEach { snap ->
count += snap.get(SHARDS_COUNT_KEY, Int::class.java) ?: 0
}
count
}
}

override suspend fun incrementThumbsUpCount(sessionId: SessionId) {
signInIfNeeded()
val counterRef = getThumbsUpCounterRef(sessionId)
createShardsIfNeeded(counterRef)
val shardId = floor(Math.random() * NUM_SHARDS).toInt()
counterRef
.document(shardId.toString())
.update(SHARDS_COUNT_KEY, FieldValue.increment(1))
.await()
}

private fun getFavoritesRef(): CollectionReference {
val firebaseAuth = FirebaseAuth.getInstance()
val firebaseUserId = firebaseAuth.currentUser?.uid ?: throw RuntimeException(
Expand All @@ -83,6 +120,47 @@ internal class FirestoreImpl @Inject constructor() : Firestore {
firebaseAuth.signInAnonymously().await()
Timber.debug { "signInIfNeeded end" }
}

private fun getThumbsUpCounterRef(sessionId: SessionId): CollectionReference {
return FirebaseFirestore
.getInstance()
.collection("confsched/2020/sessions/${sessionId.id}/thumbsup_counters")
}

private suspend fun createShardsIfNeeded(counterRef: CollectionReference) {
val lastShardId = NUM_SHARDS - 1
val lastShard = counterRef
.document(lastShardId.toString())
.get(Source.SERVER)
.await()

if (lastShard.exists()) {
Timber.debug { "createShardsIfNeeded shards already exist" }
return
}

val tasks = arrayListOf<Task<Void>>()
(0 until NUM_SHARDS).forEach {
val makeShard = counterRef
.document(it.toString())
.set(mapOf(SHARDS_COUNT_KEY to 0))
tasks.add(makeShard)
}

try {
Tasks.whenAll(tasks).await()
} catch(e: Exception) {
mkeeda marked this conversation as resolved.
Show resolved Hide resolved
// FIXME: debug code
println("⭐" + e.message)
mkeeda marked this conversation as resolved.
Show resolved Hide resolved
}
Timber.debug { "createShardsIfNeeded creating shards completed" }
}

companion object {
const val NUM_SHARDS = 10
mkeeda marked this conversation as resolved.
Show resolved Hide resolved
const val SHARDS_COUNT_KEY = "shards"
const val FAVORITE_VALUE_KEY = "favorite"
}
}

private suspend fun DocumentReference.fastGet(): DocumentSnapshot {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import io.github.droidkaigi.confsched2020.model.SessionId
import io.github.droidkaigi.confsched2020.model.SessionList
import io.github.droidkaigi.confsched2020.model.SpeechSession
import io.github.droidkaigi.confsched2020.model.repository.SessionRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import timber.log.debug
import javax.inject.Inject

internal class DataSessionRepository @Inject constructor(
private val droidKaigiApi: DroidKaigiApi,
Expand Down Expand Up @@ -129,4 +129,12 @@ internal class DataSessionRepository @Inject constructor(
val response = droidKaigiApi.getSessions()
sessionDatabase.save(response)
}

override fun thumbsUpCounts(sessionId: SessionId): Flow<Int> {
return firestore.getThumbsUpCount(sessionId)
}

override suspend fun incrementThumbsUpCount(sessionId: SessionId) {
firestore.incrementThumbsUpCount(sessionId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ class SessionDetailFragment : Fragment(R.layout.fragment_session_detail), Inject
adapter,
session,
uiModel.showEllipsis,
uiModel.searchQuery
uiModel.searchQuery,
uiModel.thumbsUpCount
)
}
}
Expand Down Expand Up @@ -178,13 +179,20 @@ class SessionDetailFragment : Fragment(R.layout.fragment_session_detail), Inject
adapter: GroupAdapter<ViewHolder<*>>,
session: Session,
showEllipsis: Boolean,
searchQuery: String?
searchQuery: String?,
thumbsUpCount: Int
) {
binding.sessionDetailRecycler.transitionName =
"${session.id}-${navArgs.transitionNameSuffix}"

val items = mutableListOf<Group>()
items += sessionDetailTitleItemFactory.create(session, searchQuery)
items += sessionDetailTitleItemFactory.create(
session,
searchQuery,
thumbsUpCount
) {
sessionDetailViewModel.thumbsUp(session)
}
items += sessionDetailDescriptionItemFactory.create(
session,
showEllipsis,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import java.util.regex.Pattern

class SessionDetailTitleItem @AssistedInject constructor(
@Assisted private val session: Session,
@Assisted private val searchQuery: String?
@Assisted private val searchQuery: String?,
@Assisted private val thumbsUpCount: Int,
@Assisted private val thumbsUpListener: () -> Unit
) :
BindableItem<ItemSessionDetailTitleBinding>() {
override fun getLayout() = R.layout.item_session_detail_title
Expand Down Expand Up @@ -59,6 +61,11 @@ class SessionDetailTitleItem @AssistedInject constructor(
// Test Code
// binding.sessionMessage.text = "セッション部屋がRoom1からRoom3に変更になりました(サンプル)"
// binding.sessionMessage.isVisible = true

binding.thumbsUp.text = thumbsUpCount.toString()
binding.thumbsUp.setOnClickListener {
thumbsUpListener.invoke()
}
}
}

Expand Down Expand Up @@ -87,7 +94,9 @@ class SessionDetailTitleItem @AssistedInject constructor(
interface Factory {
fun create(
session: Session,
searchQuery: String? = null
searchQuery: String? = null,
thumbsUpCount: Int,
thumbsUpListener: () -> Unit
): SessionDetailTitleItem
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import io.github.droidkaigi.confsched2020.ext.combine
import io.github.droidkaigi.confsched2020.ext.toAppError
import io.github.droidkaigi.confsched2020.ext.toLoadingState
import io.github.droidkaigi.confsched2020.model.AppError
import io.github.droidkaigi.confsched2020.model.TextExpandState
import io.github.droidkaigi.confsched2020.model.LoadState
import io.github.droidkaigi.confsched2020.model.LoadingState
import io.github.droidkaigi.confsched2020.model.Session
import io.github.droidkaigi.confsched2020.model.SessionId
import io.github.droidkaigi.confsched2020.model.TextExpandState
import io.github.droidkaigi.confsched2020.model.repository.SessionRepository
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
Expand All @@ -32,10 +32,18 @@ class SessionDetailViewModel @AssistedInject constructor(
val error: AppError?,
val session: Session?,
val showEllipsis: Boolean,
val searchQuery: String?
val searchQuery: String?,
val thumbsUpCount: Int
) {
companion object {
val EMPTY = UiModel(false, null, null, true, null)
val EMPTY = UiModel(
isLoading = false,
error = null,
session = null,
showEllipsis = true,
searchQuery = null,
thumbsUpCount = 0
)
}
}

Expand All @@ -55,16 +63,25 @@ class SessionDetailViewModel @AssistedInject constructor(
private val descriptionTextExpandStateLiveData: MutableLiveData<TextExpandState> =
MutableLiveData(TextExpandState.COLLAPSED)

private val thumbsUpCountLiveData: LiveData<Int> = liveData {
sessionRepository.thumbsUpCounts(sessionId)
.collect { thumbsUpCount ->
emit(thumbsUpCount)
}
}

// Produce UiModel
val uiModel: LiveData<UiModel> = combine(
initialValue = UiModel.EMPTY,
liveData1 = sessionLoadStateLiveData,
liveData2 = favoriteLoadingStateLiveData,
liveData3 = descriptionTextExpandStateLiveData
liveData3 = descriptionTextExpandStateLiveData,
liveData4 = thumbsUpCountLiveData
) { current: UiModel,
sessionLoadState: LoadState<Session>,
favoriteState: LoadingState,
descriptionTextExpandState: TextExpandState ->
descriptionTextExpandState: TextExpandState,
thumbsUpCount: Int ->
val isLoading =
sessionLoadState.isLoading || favoriteState.isLoading
val sessions = when (sessionLoadState) {
Expand All @@ -87,7 +104,8 @@ class SessionDetailViewModel @AssistedInject constructor(
.toAppError(),
session = sessions,
showEllipsis = showEllipsis,
searchQuery = searchQuery
searchQuery = searchQuery,
thumbsUpCount = thumbsUpCount
)
}

Expand All @@ -107,6 +125,17 @@ class SessionDetailViewModel @AssistedInject constructor(
descriptionTextExpandStateLiveData.value = TextExpandState.EXPANDED
}

fun thumbsUp(session: Session) {
viewModelScope.launch {
try {
sessionRepository.incrementThumbsUpCount(session.id)
} catch (e: Exception) {
// TODO: implement
println("⭐ " + e.message)
}
}
}

@AssistedInject.Factory
interface Factory {
fun create(
Expand Down
18 changes: 15 additions & 3 deletions feature/session/src/main/res/layout/item_session_detail_title.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
<TextView
android:id="@+id/session_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.DroidKaigi.SessionMessage"
android:visibility="gone"
Expand All @@ -119,15 +119,27 @@
app:layout_constraintTop_toBottomOf="@id/session_message"
/>

<com.google.android.material.button.MaterialButton
android:id="@+id/thumbs_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
app:layout_constraintStart_toStartOf="@id/guideline_start"
app:layout_constraintTop_toBottomOf="@id/tags"
app:layout_constraintBottom_toTopOf="@id/divider_survey_and_text"
tools:text="thumbs-up"
/>

<View
android:id="@+id/divider_survey_and_text"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="22dp"
android:layout_marginTop="24dp"
android:background="@color/black_alpha_12"
app:layout_constraintEnd_toEndOf="@id/guideline_end"
app:layout_constraintStart_toStartOf="@id/guideline_start"
app:layout_constraintTop_toBottomOf="@id/tags"
app:layout_constraintTop_toBottomOf="@id/thumbs_up"
/>

</androidx.constraintlayout.widget.ConstraintLayout>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ interface SessionRepository {
session: SpeechSession,
sessionFeedback: SessionFeedback
)
fun thumbsUpCounts(sessionId: SessionId): Flow<Int>
suspend fun incrementThumbsUpCount(sessionId: SessionId)
}