Skip to content
Open
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
4 changes: 3 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- 이미지 선택을 위한 권한 (Android 13 이상) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"
tools:ignore="PhotoAndVideoPolicy,SelectedPhotoAccess" />

<!-- Android 12 이하를 위한 권한 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />

<application
android:name=".App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/com/example/kuit6_android_api/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.kuit6_android_api

import android.app.Application
import com.example.kuit6_android_api.data.di.AppContainer

class App : Application() {
lateinit var container: AppContainer

override fun onCreate() {
super.onCreate()
container = AppContainer()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface ApiService {
@DELETE("/api/posts/{id}")
suspend fun deletePost(
@Path("id") id: Long
): BaseResponse<Unit> // data에 빈 객체 반환
): BaseResponse<PostResponse> // data에 빈 객체 반환

@GET("/api/posts/{id}")
suspend fun getPostDetail(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.kuit6_android_api.data.di

import com.example.kuit6_android_api.data.api.ApiService
import com.example.kuit6_android_api.data.api.RetrofitClient
import com.example.kuit6_android_api.data.repository.PostRepository
import com.example.kuit6_android_api.data.repository.PostRepositoryImpl

class AppContainer{
private val apiService: ApiService by lazy{
RetrofitClient.apiService
}

val postRepository: PostRepository by lazy {
PostRepositoryImpl(apiService)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.kuit6_android_api.data.repository

import com.example.kuit6_android_api.data.model.request.PostCreateRequest
import com.example.kuit6_android_api.data.model.response.PostResponse

interface PostRepository{
suspend fun getPosts(): Result<List<PostResponse>>
suspend fun getPostDetail(postId: Long): Result<PostResponse>
suspend fun createPost(author: String, request: PostCreateRequest): Result<PostResponse>
suspend fun updatePost(postId: Long, request: PostCreateRequest): Result<PostResponse>
suspend fun deletePost(postId: Long): Result<PostResponse>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.example.kuit6_android_api.data.repository

import android.util.Log
import com.example.kuit6_android_api.data.api.ApiService
import com.example.kuit6_android_api.data.model.request.PostCreateRequest
import com.example.kuit6_android_api.data.model.response.PostResponse

class PostRepositoryImpl(
private val apiService: ApiService
): PostRepository{
override suspend fun getPosts(): Result<List<PostResponse>> {
return runCatching {
val response = apiService.getPosts()

if(response.success && response.data != null){
response.data
}else{
throw Exception(response.message ?: "게시글 불러오기 실패")
}
}.onFailure { error ->
Log.e("PostRepository", error.message.toString())
}
}

override suspend fun getPostDetail(postId: Long): Result<PostResponse> {
return runCatching {
val response = apiService.getPostDetail(postId)

if (response.success && response.data != null){
response.data
} else{
throw Exception(response.message ?: "삭제 실패")
}
}.onFailure { error ->
Log.e("PostRepository", error.message.toString())
}
}

override suspend fun createPost(
author: String,
request: PostCreateRequest
): Result<PostResponse> {
return runCatching {
val response = apiService.createPost(author, request)
if (response.success && response.data != null){
response.data
} else{
throw Exception(response.message ?: "생성 실패")
}
}.onFailure { error ->
Log.e("PostRepository", error.message.toString())
}
}

override suspend fun updatePost(
postId: Long,
request: PostCreateRequest
): Result<PostResponse> {
return runCatching {
val response = apiService.updatePost(postId, request)
if (response.success && response.data != null){
response.data
}else{
throw Exception(response.message ?: "업데이트 실패")
}
}.onFailure { error ->
Log.e("PostRepository", error.message.toString())
}
}

override suspend fun deletePost(postId: Long): Result<PostResponse> {
return runCatching {
val response = apiService.deletePost(postId)
if (response.success && response.data != null){
response.data
}else{
throw Exception(response.message ?: "삭제 실패")
}
}.onFailure { error ->
Log.e("PostRepository", error.message.toString())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.example.kuit6_android_api.ui.navigation

import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
Expand All @@ -11,6 +11,11 @@ import com.example.kuit6_android_api.ui.post.screen.PostCreateScreen
import com.example.kuit6_android_api.ui.post.screen.PostDetailScreen
import com.example.kuit6_android_api.ui.post.screen.PostEditScreen
import com.example.kuit6_android_api.ui.post.screen.PostListScreen
import com.example.kuit6_android_api.ui.post.viewmodel.PostCreateViewModel
import com.example.kuit6_android_api.ui.post.viewmodel.PostDetailViewModel
import com.example.kuit6_android_api.ui.post.viewmodel.PostEditViewModel
import com.example.kuit6_android_api.ui.post.viewmodel.PostListViewModel
import com.example.kuit6_android_api.ui.post.viewmodel.PostViewModelFactory

@Composable
fun NavGraph(
Expand All @@ -23,19 +28,30 @@ fun NavGraph(
startDestination = startDestination
) {
composable<PostListRoute> {
val listViewModel: PostListViewModel = viewModel(
factory = PostViewModelFactory { PostListViewModel(it) }
)

PostListScreen(
onPostClick = { postId ->
navController.navigate(PostDetailRoute(postId))
},
onCreatePostClick = {
navController.navigate(PostCreateRoute)
}
},
viewModel = listViewModel
)
}

composable<PostDetailRoute> { backStackEntry ->
val route = backStackEntry.toRoute<PostDetailRoute>()
val detailViewModel: PostDetailViewModel = viewModel(
factory = PostViewModelFactory { PostDetailViewModel(it) }
)

val editViewModel: PostEditViewModel = viewModel(
factory = PostViewModelFactory { PostEditViewModel(it) }
)
PostDetailScreen(
postId = route.postId,
onNavigateBack = {
Expand All @@ -44,24 +60,38 @@ fun NavGraph(
onEditClick = { postId ->
navController.navigate(PostEditRoute(postId))
},
snackBarState = snackBarState
snackBarState = snackBarState,
detailViewModel=detailViewModel,
editViewModel=editViewModel
)
}

composable<PostCreateRoute> {
val createViewModel: PostCreateViewModel = viewModel(
factory = PostViewModelFactory { PostCreateViewModel(it) }
)

PostCreateScreen(
onNavigateBack = {
navController.popBackStack()
},
onPostCreated = {
navController.popBackStack()
},
snackBarState = snackBarState
snackBarState = snackBarState,
postCreateViewModel = createViewModel
)
}

composable<PostEditRoute> { backStackEntry ->
val route = backStackEntry.toRoute<PostEditRoute>()
val detailViewModel: PostDetailViewModel = viewModel(
factory = PostViewModelFactory { PostDetailViewModel(it) }
)

val editViewModel: PostEditViewModel = viewModel(
factory = PostViewModelFactory { PostEditViewModel(it) }
)

PostEditScreen(
postId = route.postId,
Expand All @@ -71,7 +101,9 @@ fun NavGraph(
onPostUpdated = {
navController.popBackStack()
},
snackBarState = snackBarState
snackBarState = snackBarState,
editViewModel = editViewModel,
detailViewModel = detailViewModel
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.kuit6_android_api.ui.post.viewmodel.PostCreateViewModel
import com.example.kuit6_android_api.ui.post.viewmodel.PostViewModel
import kotlinx.coroutines.launch

Expand All @@ -56,7 +57,7 @@ fun PostCreateScreen(
onNavigateBack: () -> Unit,
onPostCreated: () -> Unit,
snackBarState: SnackbarHostState,
viewModel: PostViewModel = viewModel()
postCreateViewModel: PostCreateViewModel = viewModel()
) {
val context = LocalContext.current
var author by remember { mutableStateOf("") }
Expand Down Expand Up @@ -196,7 +197,7 @@ fun PostCreateScreen(
Button(
onClick = {
val finalAuthor = author
viewModel.createPost(finalAuthor, title, content, null) {
postCreateViewModel.createPost(finalAuthor, title, content, null) {
onPostCreated()
scope.launch { snackBarState.showSnackbar("게시글이 작성되었습니다.") }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import com.example.kuit6_android_api.ui.post.viewmodel.PostDetailViewModel
import com.example.kuit6_android_api.ui.post.viewmodel.PostEditViewModel
import com.example.kuit6_android_api.ui.post.viewmodel.PostViewModel
import kotlinx.coroutines.launch

Expand All @@ -56,7 +58,9 @@ fun PostEditScreen(
onNavigateBack: () -> Unit,
onPostUpdated: () -> Unit,
viewModel: PostViewModel = viewModel(),
snackBarState: SnackbarHostState
snackBarState: SnackbarHostState,
editviewModel: PostEditViewModel = viewModel(),
detailViewModel: PostDetailViewModel = viewModel()
) {
val post = viewModel.postDetail

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
Expand All @@ -18,24 +19,24 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.kuit6_android_api.ui.post.component.PostItem
import com.example.kuit6_android_api.ui.post.state.PostListUiState
import com.example.kuit6_android_api.ui.post.viewmodel.PostListViewModel
import com.example.kuit6_android_api.ui.post.viewmodel.PostViewModel

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PostListScreen(
onPostClick: (Long) -> Unit,
onCreatePostClick: () -> Unit,
viewModel: PostViewModel = viewModel()
viewModel: PostListViewModel
) {
val posts = viewModel.posts

LaunchedEffect(Unit) {
viewModel.getPosts()
}
val uiState by viewModel.uiState.collectAsState()

Scaffold(
topBar = {
Expand All @@ -49,19 +50,29 @@ fun PostListScreen(
}
}
) { paddingValues ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.background(MaterialTheme.colorScheme.background),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(posts) { post ->
PostItem(
post = post,
onClick = { onPostClick(post.id) }
)
when (uiState){
is PostListUiState.Loading -> {
CircularProgressIndicator()
}
is PostListUiState.Success -> {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.background(MaterialTheme.colorScheme.background),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(items = (uiState as PostListUiState.Success).posts) { post ->
PostItem(
post = post,
onClick = { onPostClick(post.id) }
)
}
}
}
is PostListUiState.Error -> {
Text(text = "로딩 실패")
}
}
}
Expand Down
Loading