Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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: 2 additions & 2 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.util.Properties

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
Expand All @@ -18,6 +20,15 @@ android {

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

// defaultConfig 블록 안에 추가
val properties = Properties()
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
properties.load(localPropertiesFile.inputStream())
}

val baseUrl = properties.getProperty("BASE_URL") ?: "http://10.0.2.2:8080/"
buildConfigField("String", "BASE_URL", "\"$baseUrl\"")
}

buildTypes {
Expand All @@ -38,6 +49,7 @@ android {
}
buildFeatures {
compose = true
buildConfig = true
}
}

Expand Down Expand Up @@ -74,4 +86,9 @@ dependencies {
androidTestImplementation(libs.androidx.espresso.core)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)

implementation(libs.retrofit)
implementation(libs.retrofit.converter.gson)
implementation(libs.okhttp)
implementation(libs.okhttp.logging.interceptor)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.example.kuit6_android_api.data.api

import androidx.annotation.BoolRes
import com.example.kuit6_android_api.data.model.request.PostCreateRequest
import com.example.kuit6_android_api.data.model.response.BaseResponse
import com.example.kuit6_android_api.data.model.response.PostResponse
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query

interface ApiService {
@GET(value = "/api/posts")
suspend fun getPost(): BaseResponse<List<PostResponse>>

@POST(value = "/api/posts")
suspend fun createPost(
@Query(value = "author") author: String = "규빈",
@Body request: PostCreateRequest
): BaseResponse<PostResponse>

@GET("/api/posts/{id}")
suspend fun getPostDetail(@Path("id") id: Long): BaseResponse<PostResponse>

@PUT("/api/posts/{id}")
suspend fun editPost(
@Path("id") id: Long,
@Body request: PostCreateRequest
): BaseResponse<PostResponse>

@DELETE("/api/posts/{id}")
suspend fun deletePost(@Path("id") id : Long): BaseResponse<Unit>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.example.kuit6_android_api.data.api

import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

object RetrofitClient {

private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}

private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.connectTimeout(timeout = 30, unit = TimeUnit.SECONDS)
.readTimeout(timeout = 30, unit = TimeUnit.SECONDS)
.writeTimeout(timeout = 30, unit = TimeUnit.SECONDS)
.build()

private val retrofit: Retrofit = Retrofit.Builder()
.baseUrl("http://3.34.136.227:8080/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()

val apiService: ApiService = retrofit.create(ApiService::class.java)
}
17 changes: 0 additions & 17 deletions app/src/main/java/com/example/kuit6_android_api/data/model/Post.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.kuit6_android_api.data.model.request

import kotlinx.serialization.SerialName

data class PostCreateRequest(
@SerialName(value = "title") val title: String,
@SerialName(value = "content") val content: String,
@SerialName(value = "imageUrl") val imageUrl: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package com.example.kuit6_android_api.data.model.request

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.kuit6_android_api.data.model.response

import kotlinx.serialization.SerialName

data class AuthResponse(
@SerialName(value = "id") val id: Long,
@SerialName(value = "username") val username: String,
@SerialName(value = "profileImageUrl") val profileImageUrl: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.kuit6_android_api.data.model.response

import kotlinx.serialization.SerialName

data class BaseResponse<T>(
@SerialName(value = "success") val success: Boolean,
@SerialName(value = "message") val message: String?,
@SerialName(value = "data") val data: T?,
@SerialName(value = "timestamp") val timestamp: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.kuit6_android_api.data.model.response

import kotlinx.serialization.SerialName

data class PostResponse(
@SerialName(value = "id") val id: Long,
@SerialName(value = "title") val title: String,
@SerialName(value = "content") val content: String,
@SerialName(value = "imageUrl") val imageUrl: String?,
@SerialName(value = "author") val author: AuthResponse,
@SerialName(value = "createdAt") val createdAt: String,
@SerialName(value = "updatedAt") val updatedAt: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.example.kuit6_android_api.data.model.Author
import com.example.kuit6_android_api.data.model.Post
import com.example.kuit6_android_api.data.model.response.AuthResponse
import com.example.kuit6_android_api.data.model.response.PostResponse
import com.example.kuit6_android_api.util.formatDateTime

@Composable
fun PostItem(
post: Post,
post: PostResponse,
onClick: () -> Unit
) {
Card(
Expand Down Expand Up @@ -153,12 +153,12 @@ fun PostItem(
fun PostItemPreview() {
MaterialTheme {
PostItem(
post = Post(
post = PostResponse(
id = 1,
title = "샘플 게시글 제목",
content = "이것은 샘플 게시글 내용입니다. 미리보기에서는 두 줄까지만 표시됩니다.",
imageUrl = null,
author = Author(1, "testuser", null),
author = AuthResponse(1, "testuser", null),
createdAt = "2025-10-03T12:00:00",
updatedAt = "2025-10-03T12:00:00"
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ fun PostCreateScreen(
Button(
onClick = {
val finalAuthor = author.ifBlank { "anonymous" }
viewModel.createPost(finalAuthor, title, content, null) {
viewModel.createPost(finalAuthor, title, content, viewModel.uploadedImageUrl) {
onPostCreated()
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,96 +1,69 @@
package com.example.kuit6_android_api.ui.post.viewmodel

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.kuit6_android_api.data.model.Author
import com.example.kuit6_android_api.data.model.Post
import com.example.kuit6_android_api.data.api.RetrofitClient
import com.example.kuit6_android_api.data.model.request.PostCreateRequest
import com.example.kuit6_android_api.data.model.response.PostResponse
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.LocalDateTime

class PostViewModel : ViewModel() {

// 더미 데이터
private val dummyPosts = mutableStateListOf(
Post(
id = 1,
title = "Jetpack Compose 시작하기",
content = "Jetpack Compose는 Android의 최신 UI 툴킷입니다. 선언형 UI로 더 쉽고 빠르게 UI를 만들 수 있습니다.",
imageUrl = null,
author = Author(1, "개발자A", null),
createdAt = "2025-10-05T10:00:00",
updatedAt = "2025-10-05T10:00:00"
),
Post(
id = 2,
title = "Kotlin Coroutines 완벽 가이드",
content = "비동기 프로그래밍을 쉽게! Coroutines를 사용하면 복잡한 비동기 코드를 간단하게 작성할 수 있습니다.",
imageUrl = null,
author = Author(2, "개발자B", null),
createdAt = "2025-10-05T11:30:00",
updatedAt = "2025-10-05T11:30:00"
),
Post(
id = 3,
title = "Android MVVM 아키텍처",
content = "MVVM 패턴으로 코드를 구조화하면 테스트와 유지보수가 쉬워집니다. ViewModel과 LiveData/StateFlow를 활용해봅시다.",
imageUrl = null,
author = Author(1, "개발자A", null),
createdAt = "2025-10-05T14:20:00",
updatedAt = "2025-10-05T14:20:00"
)
)

private var nextId = 4L

var posts by mutableStateOf<List<Post>>(emptyList())
var posts by mutableStateOf<List<PostResponse>>(emptyList())
private set

var postDetail by mutableStateOf<Post?>(null)
var postDetail by mutableStateOf<PostResponse?>(null)
private set

var uploadedImageUrl by mutableStateOf<String?>(null)
private set

private val apiService = RetrofitClient.apiService

fun getPosts() {
viewModelScope.launch {
delay(500) // 네트워크 시뮬레이션
posts = dummyPosts.toList()
runCatching {
apiService.getPost()
}.onSuccess { response ->
response.data?.let {
if (response.success) {
posts = response.data
}
}
}
}
}

fun getPostDetail(postId: Long) {
viewModelScope.launch {
delay(300)
postDetail = dummyPosts.find { it.id == postId }
}
}

fun createPost(
author: String = "anonymous",
author: String,
title: String,
content: String,
imageUrl: String? = null,
onSuccess: () -> Unit = {}
) {
viewModelScope.launch {
delay(500)
val newPost = Post(
id = nextId++,
title = title,
content = content,
imageUrl = imageUrl,
author = Author(nextId, author, null),
createdAt = getCurrentDateTime(),
updatedAt = getCurrentDateTime()
)
dummyPosts.add(0, newPost)
posts = dummyPosts.toList()
onSuccess()
runCatching {
val request = PostCreateRequest(title, content, imageUrl)
apiService.createPost(author, request)
}.onSuccess { response ->
if (response.success) {
// 이미지 업로드 관련 코드
clearUploadedImageUrl()
onSuccess()
}
}
}
}

Expand All @@ -102,29 +75,11 @@ class PostViewModel : ViewModel() {
onSuccess: () -> Unit = {}
) {
viewModelScope.launch {
delay(500)
val index = dummyPosts.indexOfFirst { it.id == postId }
if (index != -1) {
val oldPost = dummyPosts[index]
val updatedPost = oldPost.copy(
title = title,
content = content,
imageUrl = imageUrl,
updatedAt = getCurrentDateTime()
)
dummyPosts[index] = updatedPost
postDetail = updatedPost
posts = dummyPosts.toList()
onSuccess()
}
}
}

fun deletePost(postId: Long, onSuccess: () -> Unit = {}) {
viewModelScope.launch {
delay(300)
dummyPosts.removeIf { it.id == postId }
posts = dummyPosts.toList()
onSuccess()
}
}
Expand Down