149 lines
5.8 KiB
Kotlin
149 lines
5.8 KiB
Kotlin
package app.closer.ui.dates
|
|
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import app.closer.domain.model.DateIdea
|
|
import app.closer.domain.model.DateMatch
|
|
import app.closer.domain.model.DateSwipe
|
|
import app.closer.domain.model.SwipeAction
|
|
import app.closer.domain.repository.AuthRepository
|
|
import app.closer.domain.repository.CoupleRepository
|
|
import app.closer.domain.repository.DateMatchRepository
|
|
import app.closer.domain.repository.UserRepository
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
import javax.inject.Inject
|
|
import kotlinx.coroutines.flow.Flow
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
import kotlinx.coroutines.flow.asStateFlow
|
|
import kotlinx.coroutines.flow.collect
|
|
import kotlinx.coroutines.flow.combine
|
|
import kotlinx.coroutines.flow.update
|
|
import kotlinx.coroutines.launch
|
|
|
|
data class DateMatchUiState(
|
|
val isLoading: Boolean = true,
|
|
val error: String? = null,
|
|
val coupleId: String? = null,
|
|
val currentUserId: String? = null,
|
|
val partnerName: String? = null,
|
|
val dateIdeas: List<DateIdea> = emptyList(),
|
|
val ownSwipes: Map<String, SwipeAction> = emptyMap(),
|
|
val matches: List<DateMatch> = emptyList(),
|
|
val currentIndex: Int = 0,
|
|
val justMatched: DateMatch? = null
|
|
) {
|
|
val currentIdea: DateIdea? get() = dateIdeas.getOrNull(currentIndex)
|
|
val nextIdea: DateIdea? get() = dateIdeas.getOrNull(currentIndex + 1)
|
|
val hasMore: Boolean get() = currentIndex < dateIdeas.size
|
|
|
|
fun userAction(ideaId: String): SwipeAction? = ownSwipes[ideaId]
|
|
}
|
|
|
|
@HiltViewModel
|
|
class DateMatchViewModel @Inject constructor(
|
|
private val repository: DateMatchRepository,
|
|
private val authRepository: AuthRepository,
|
|
private val coupleRepository: CoupleRepository,
|
|
private val userRepository: UserRepository
|
|
) : ViewModel() {
|
|
|
|
private val _uiState = MutableStateFlow(DateMatchUiState())
|
|
val uiState: StateFlow<DateMatchUiState> = _uiState.asStateFlow()
|
|
|
|
init {
|
|
loadDateMatch()
|
|
}
|
|
|
|
private fun loadDateMatch() {
|
|
viewModelScope.launch {
|
|
_uiState.value = DateMatchUiState(isLoading = true)
|
|
try {
|
|
val uid = authRepository.currentUserId
|
|
val couple = uid?.let { runCatching { coupleRepository.getCoupleForUser(it) }.getOrNull() }
|
|
val partnerName = couple?.userIds?.firstOrNull { it != uid }?.let { partnerId ->
|
|
runCatching { userRepository.getUser(partnerId)?.displayName }.getOrNull()
|
|
}
|
|
val ideas = repository.getDateIdeas()
|
|
_uiState.value = DateMatchUiState(
|
|
isLoading = false,
|
|
coupleId = couple?.id,
|
|
currentUserId = uid,
|
|
partnerName = partnerName,
|
|
dateIdeas = ideas
|
|
)
|
|
|
|
if (couple != null && uid != null) {
|
|
observeData(couple.id, uid)
|
|
}
|
|
} catch (e: Exception) {
|
|
_uiState.value = DateMatchUiState(
|
|
isLoading = false,
|
|
error = e.message ?: "Could not load date ideas."
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun observeData(coupleId: String, userId: String) {
|
|
val swipesFlow: Flow<List<DateSwipe>> = repository.observeOwnSwipes(coupleId, userId)
|
|
val matchesFlow: Flow<List<DateMatch>> = repository.observeMatches(coupleId)
|
|
|
|
viewModelScope.launch {
|
|
// Matches are created server-side and stream in via observeMatches.
|
|
// Track ids already seen so the "It's a match!" celebration only fires
|
|
// for matches created after this screen started observing — not for
|
|
// matches that already existed on load.
|
|
var knownMatchIds: Set<String>? = null
|
|
combine(swipesFlow, matchesFlow) { swipes, matches -> swipes to matches }
|
|
.collect { (swipes, matches) ->
|
|
val newMatch = knownMatchIds?.let { known ->
|
|
matches.firstOrNull { it.id !in known }
|
|
}
|
|
knownMatchIds = matches.mapTo(mutableSetOf()) { it.id }
|
|
_uiState.update { state ->
|
|
state.copy(
|
|
ownSwipes = swipes.associate { it.dateIdeaId to it.action },
|
|
matches = matches,
|
|
justMatched = newMatch ?: state.justMatched
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun swipeCurrent(action: SwipeAction) {
|
|
val current = _uiState.value.currentIdea ?: return
|
|
val coupleId = _uiState.value.coupleId ?: return
|
|
val userId = _uiState.value.currentUserId ?: return
|
|
|
|
viewModelScope.launch {
|
|
_uiState.update { it.copy(justMatched = null) }
|
|
val result = repository.recordSwipe(coupleId, userId, current.id, action)
|
|
result.fold(
|
|
onSuccess = {
|
|
// Advance to the next idea. A resulting match (if both partners
|
|
// loved this idea) is created server-side and surfaces via the
|
|
// observeMatches flow, which sets justMatched.
|
|
_uiState.update { it.copy(currentIndex = it.currentIndex + 1) }
|
|
},
|
|
onFailure = { err ->
|
|
_uiState.update {
|
|
it.copy(error = err.message ?: "Could not save swipe.")
|
|
}
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
fun skipCurrent() = swipeCurrent(SwipeAction.SKIP)
|
|
fun maybeCurrent() = swipeCurrent(SwipeAction.MAYBE)
|
|
fun loveCurrent() = swipeCurrent(SwipeAction.LOVE)
|
|
|
|
fun dismissJustMatched() {
|
|
_uiState.update { it.copy(justMatched = null) }
|
|
}
|
|
|
|
fun retry() = loadDateMatch()
|
|
}
|