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

244 lines
9.1 KiB
Kotlin

package com.qingshuige.tangyuan.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.qingshuige.tangyuan.model.Category
import com.qingshuige.tangyuan.model.CreatPostMetadataDto
import com.qingshuige.tangyuan.model.PostBody
import com.qingshuige.tangyuan.model.PostMetadata
import com.qingshuige.tangyuan.model.User
import com.qingshuige.tangyuan.repository.PostRepository
import com.qingshuige.tangyuan.repository.UserRepository
import com.qingshuige.tangyuan.repository.CategoryRepository
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 PostDetail(
val metadata: PostMetadata,
val body: PostBody,
val author: User? = null,
val category: Category? = null
)
data class PostUiState(
val isLoading: Boolean = false,
val posts: List<PostMetadata> = emptyList(),
val currentPost: PostDetail? = null,
val error: String? = null,
val isCreating: Boolean = false,
val createSuccess: Boolean = false
)
@HiltViewModel
class PostViewModel @Inject constructor(
private val postRepository: PostRepository,
private val userRepository: UserRepository,
private val categoryRepository: CategoryRepository
) : ViewModel() {
private val _postUiState = MutableStateFlow(PostUiState())
val postUiState: StateFlow<PostUiState> = _postUiState.asStateFlow()
private val _searchResults = MutableStateFlow<List<PostMetadata>>(emptyList())
val searchResults: StateFlow<List<PostMetadata>> = _searchResults.asStateFlow()
private val _noticePost = MutableStateFlow<PostMetadata?>(null)
val noticePost: StateFlow<PostMetadata?> = _noticePost.asStateFlow()
fun getPostDetail(postId: Int) {
viewModelScope.launch {
_postUiState.value = _postUiState.value.copy(isLoading = true, error = null)
try {
var metadata: PostMetadata? = null
var body: PostBody? = null
var author: User? = null
var category: Category? = null
postRepository.getPostMetadata(postId)
.catch { e -> throw e }
.collect { postMetadata ->
metadata = postMetadata
// Get post body
postRepository.getPostBody(postId)
.catch { e -> throw e }
.collect { postBody ->
body = postBody
}
// Get author
userRepository.getUserById(postMetadata.userId)
.catch { e -> throw e }
.collect { user ->
author = user
}
// Get category
categoryRepository.getCategoryById(postMetadata.categoryId)
.catch { e -> throw e }
.collect { cat ->
category = cat
}
val postDetail = PostDetail(metadata!!, body!!, author, category)
_postUiState.value = _postUiState.value.copy(
isLoading = false,
currentPost = postDetail
)
}
} catch (e: Exception) {
_postUiState.value = _postUiState.value.copy(
isLoading = false,
error = e.message
)
}
}
}
fun getUserPosts(userId: Int) {
viewModelScope.launch {
_postUiState.value = _postUiState.value.copy(isLoading = true, error = null)
try {
// TODO: Call repository getUserPosts method
// val posts = postRepository.getUserPosts(userId)
// _postUiState.value = _postUiState.value.copy(
// isLoading = false,
// posts = posts
// )
} catch (e: Exception) {
_postUiState.value = _postUiState.value.copy(
isLoading = false,
error = e.message
)
}
}
}
fun getRandomPosts(count: Int) {
viewModelScope.launch {
_postUiState.value = _postUiState.value.copy(isLoading = true, error = null)
try {
// TODO: Call repository getRandomPosts method
// val posts = postRepository.getRandomPosts(count)
// _postUiState.value = _postUiState.value.copy(
// isLoading = false,
// posts = posts
// )
} catch (e: Exception) {
_postUiState.value = _postUiState.value.copy(
isLoading = false,
error = e.message
)
}
}
}
fun getPostsByCategory(categoryId: Int) {
viewModelScope.launch {
_postUiState.value = _postUiState.value.copy(isLoading = true, error = null)
try {
// TODO: Call repository getPostsByCategory method
// val posts = postRepository.getPostsByCategory(categoryId)
// _postUiState.value = _postUiState.value.copy(
// isLoading = false,
// posts = posts
// )
} catch (e: Exception) {
_postUiState.value = _postUiState.value.copy(
isLoading = false,
error = e.message
)
}
}
}
fun createPost(metadata: CreatPostMetadataDto, body: PostBody) {
viewModelScope.launch {
_postUiState.value = _postUiState.value.copy(isCreating = true, error = null)
try {
// TODO: Call repository createPost method
// val postId = postRepository.createPostMetadata(metadata)
// postRepository.createPostBody(body.copy(postId = postId))
// _postUiState.value = _postUiState.value.copy(
// isCreating = false,
// createSuccess = true
// )
} catch (e: Exception) {
_postUiState.value = _postUiState.value.copy(
isCreating = false,
error = e.message
)
}
}
}
fun deletePost(postId: Int) {
viewModelScope.launch {
try {
// TODO: Call repository deletePost method
// postRepository.deletePost(postId)
// Remove from current posts list
val updatedPosts = _postUiState.value.posts.filter { it.postId != postId }
_postUiState.value = _postUiState.value.copy(posts = updatedPosts)
} catch (e: Exception) {
_postUiState.value = _postUiState.value.copy(error = e.message)
}
}
}
fun searchPosts(keyword: String) {
viewModelScope.launch {
try {
// TODO: Call repository searchPosts method
// val posts = postRepository.searchPosts(keyword)
// _searchResults.value = posts
} catch (e: Exception) {
_postUiState.value = _postUiState.value.copy(error = e.message)
}
}
}
fun getNoticePost() {
viewModelScope.launch {
try {
// TODO: Call repository getNoticePost method
// val notice = postRepository.getNoticePost()
// _noticePost.value = notice
} catch (e: Exception) {
_postUiState.value = _postUiState.value.copy(error = e.message)
}
}
}
fun getPhtPostMetadata(sectionId: Int, exceptedIds: List<Int>) {
viewModelScope.launch {
_postUiState.value = _postUiState.value.copy(isLoading = true, error = null)
try {
// TODO: Call repository getPhtPostMetadata method
// val posts = postRepository.getPhtPostMetadata(sectionId, exceptedIds)
// _postUiState.value = _postUiState.value.copy(
// isLoading = false,
// posts = posts
// )
} catch (e: Exception) {
_postUiState.value = _postUiState.value.copy(
isLoading = false,
error = e.message
)
}
}
}
fun clearError() {
_postUiState.value = _postUiState.value.copy(error = null)
}
fun clearCreateSuccess() {
_postUiState.value = _postUiState.value.copy(createSuccess = false)
}
}