grtsinry43 6a1bc7ad97
feat: Implement core features with MVVM and Hilt
This commit introduces a comprehensive set of features, establishing the core functionality of the application using an MVVM architecture with Hilt for dependency injection.

**Key Changes:**

*   **UI & Navigation:**
    *   Implemented navigation between the main feed, post details, and login screens using Jetpack Navigation Compose.
    *   Added `TalkScreen` for displaying a feed of posts and `PostDetailScreen` for viewing individual posts and their comments.
    *   Created a `LoginScreen` with input fields and authentication logic.
    *   Introduced `PostCardItem` and `CommentItem` Composables for a consistent and reusable UI.
    *   Added shared element transitions for a smoother user experience when navigating to post details.

*   **Architecture & State Management:**
    *   Integrated Hilt for dependency injection across ViewModels and Repositories.
    *   Created ViewModels (`TalkViewModel`, `PostDetailViewModel`, `UserViewModel`, `CommentViewModel`, etc.) to manage UI state and business logic.
    *   Implemented Repository pattern for abstracting data sources from the backend API.
    *   Defined UI state data classes to ensure a predictable and observable state flow.

*   **Data & Models:**
    *   Introduced data models for `PostCard` and `CommentCard` to aggregate and display complex data structures.
    *   Added `PostDetailRepository` to orchestrate fetching of post and comment data concurrently.
    *   Refined DTOs, such as `CreateCommentDto`, for API interactions.

*   **Dependencies & Tooling:**
    *   Added Hilt, Navigation Compose, and Lifecycle ViewModel dependencies.
    *   Included the `pangu-jvm` library for improved text formatting with spacing between Chinese and English characters.
2025-10-06 00:29:51 +08:00

154 lines
5.8 KiB
Kotlin

package com.qingshuige.tangyuan.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.qingshuige.tangyuan.model.Comment
import com.qingshuige.tangyuan.model.CreateCommentDto
import com.qingshuige.tangyuan.repository.CommentRepository
import com.qingshuige.tangyuan.utils.collectFlow
import com.qingshuige.tangyuan.utils.collectFlowList
import com.qingshuige.tangyuan.utils.UiState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.launch
import javax.inject.Inject
data class CommentUiState(
val isLoading: Boolean = false,
val comments: List<Comment> = emptyList(),
val subComments: Map<Int, List<Comment>> = emptyMap(),
val error: String? = null,
val isCreating: Boolean = false,
val createSuccess: Boolean = false
)
@HiltViewModel
class CommentViewModel @Inject constructor(
private val commentRepository: CommentRepository
) : ViewModel() {
private val _commentUiState = MutableStateFlow(CommentUiState())
val commentUiState: StateFlow<CommentUiState> = _commentUiState.asStateFlow()
private val _searchResults = MutableStateFlow<List<Comment>>(emptyList())
val searchResults: StateFlow<List<Comment>> = _searchResults.asStateFlow()
fun getCommentsForPost(postId: Int) {
viewModelScope.launch {
_commentUiState.value = _commentUiState.value.copy(isLoading = true, error = null)
commentRepository.getCommentsForPost(postId)
.catch { e ->
_commentUiState.value = _commentUiState.value.copy(
isLoading = false,
error = e.message
)
}
.collect { comments ->
_commentUiState.value = _commentUiState.value.copy(
isLoading = false,
comments = comments
)
}
}
}
fun getSubComments(parentCommentId: Int) {
viewModelScope.launch {
commentRepository.getSubComments(parentCommentId)
.catch { e ->
_commentUiState.value = _commentUiState.value.copy(error = e.message)
}
.collect { subComments ->
val currentSubComments = _commentUiState.value.subComments.toMutableMap()
currentSubComments[parentCommentId] = subComments
_commentUiState.value = _commentUiState.value.copy(
subComments = currentSubComments
)
}
}
}
fun getCommentById(commentId: Int) {
viewModelScope.launch {
commentRepository.getCommentById(commentId)
.catch { e ->
_commentUiState.value = _commentUiState.value.copy(error = e.message)
}
.collect { comment ->
// Handle single comment result
}
}
}
fun createComment(createCommentDto: CreateCommentDto) {
viewModelScope.launch {
_commentUiState.value = _commentUiState.value.copy(isCreating = true, error = null)
commentRepository.createComment(createCommentDto)
.catch { e ->
_commentUiState.value = _commentUiState.value.copy(
isCreating = false,
error = e.message
)
}
.collect { result ->
_commentUiState.value = _commentUiState.value.copy(
isCreating = false,
createSuccess = true
)
// Refresh comments for the post
getCommentsForPost(createCommentDto.postId.toInt())
}
}
}
fun deleteComment(commentId: Int) {
viewModelScope.launch {
commentRepository.deleteComment(commentId)
.catch { e ->
_commentUiState.value = _commentUiState.value.copy(error = e.message)
}
.collect { success ->
if (success) {
// Remove from current comments list
val updatedComments = _commentUiState.value.comments.filter {
it.commentId != commentId
}
_commentUiState.value = _commentUiState.value.copy(comments = updatedComments)
// Also remove from sub-comments if exists
val updatedSubComments = _commentUiState.value.subComments.mapValues { entry ->
entry.value.filter { it.commentId != commentId }
}
_commentUiState.value = _commentUiState.value.copy(subComments = updatedSubComments)
}
}
}
}
fun searchComments(keyword: String) {
viewModelScope.launch {
commentRepository.searchComments(keyword)
.catch { e ->
_commentUiState.value = _commentUiState.value.copy(error = e.message)
}
.collect { comments ->
_searchResults.value = comments
}
}
}
fun clearError() {
_commentUiState.value = _commentUiState.value.copy(error = null)
}
fun clearCreateSuccess() {
_commentUiState.value = _commentUiState.value.copy(createSuccess = false)
}
fun clearComments() {
_commentUiState.value = CommentUiState()
}
}