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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ google-services.json

# Android Profiling
*.hprof

# Claude Code (로컬 설정, 커밋하지 않음)
.claude/
7 changes: 0 additions & 7 deletions Yoshi/NikeApp/.claude/settings.local.json

This file was deleted.

24 changes: 24 additions & 0 deletions Yoshi/NikeApp/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import java.util.Properties

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
}

// local.properties 에서 API 키를 읽어옵니다. (local.properties 는 git 에 올라가지 않습니다)
val localProperties = Properties().apply {
val localFile = rootProject.file("local.properties")
if (localFile.exists()) {
localFile.inputStream().use { load(it) }
}
}

android {
namespace = "com.example.NikeApp"
compileSdk = 35
Expand All @@ -17,6 +27,10 @@ android {
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

// local.properties 의 NIKE_API_KEY 값을 BuildConfig.REQRES_API_KEY 로 노출
val reqresApiKey = localProperties.getProperty("NIKE_API_KEY") ?: ""
buildConfigField("String", "REQRES_API_KEY", "\"$reqresApiKey\"")
}

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

Expand All @@ -58,6 +73,15 @@ dependencies {
implementation(libs.androidx.navigation.compose)
implementation(libs.kotlinx.serialization.json)

// Coil — Compose 이미지 로딩
implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp)

// Retrofit + OkHttp — ReqRes API 통신
implementation(libs.retrofit)
implementation(libs.retrofit.kotlinx.serialization)
implementation(libs.okhttp.logging.interceptor)

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.NikeApp.data

import com.example.NikeApp.data.remote.ReqResApi
import com.example.NikeApp.data.remote.ReqResClient
import com.example.NikeApp.data.remote.dto.toUser
import com.example.NikeApp.model.User

/**
* 사용자 데이터 저장소.
* 화면(Composable)은 이 Repository 의 suspend 함수만 호출하면 됩니다.
*/
class UserRepository(
private val api: ReqResApi = ReqResClient.api,
) {
/** 미션 요구사항: 내 정보(userId = 1) 가져오기 */
suspend fun getMyProfile(): User = api.getUser(MY_USER_ID).data.toUser()

/** 팔로잉 리스트 구성: 사용자 목록에서 나(1번)를 제외하고 가져오기 */
suspend fun getFollowing(): List<User> =
api.getUsers(page = 1).data
.filter { it.id != MY_USER_ID }
.map { it.toUser() }

companion object {
/** 워크북 미션 기준 내 userId 는 1번 */
const val MY_USER_ID = 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.NikeApp.data.remote

import com.example.NikeApp.data.remote.dto.SingleUserResponse
import com.example.NikeApp.data.remote.dto.UserListResponse
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query

/**
* ReqRes(https://reqres.in) 사용자 API
*/
interface ReqResApi {

/** 단일 사용자 조회 — 미션 요구사항: userId = 1 */
@GET("api/users/{id}")
suspend fun getUser(@Path("id") id: Int): SingleUserResponse

/** 사용자 목록 조회 — 팔로잉 리스트 구성에 사용 */
@GET("api/users")
suspend fun getUsers(@Query("page") page: Int = 1): UserListResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.example.NikeApp.data.remote

import com.example.NikeApp.BuildConfig
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory

/**
* Retrofit + OkHttp 설정을 한 곳에서 관리하는 싱글톤.
* - 모든 요청에 ReqRes 가 요구하는 `x-api-key` 헤더를 자동으로 붙입니다.
* - API 키는 .env -> BuildConfig.REQRES_API_KEY 를 통해 주입됩니다.
*/
object ReqResClient {

private const val BASE_URL = "https://reqres.in/"

private val json = Json {
ignoreUnknownKeys = true // 응답에 모르는 필드가 있어도 무시
}

private val okHttpClient: OkHttpClient = OkHttpClient.Builder()
// 1) API 키 헤더 자동 추가
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("x-api-key", BuildConfig.REQRES_API_KEY)
.build()
chain.proceed(request)
}
// 2) 요청/응답 로깅 (디버깅용)
.addInterceptor(
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
)
.build()

val api: ReqResApi = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
.create(ReqResApi::class.java)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.example.NikeApp.data.remote.dto

import com.example.NikeApp.model.User
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
* ReqRes API 의 사용자 한 명을 표현하는 DTO
* 예) GET https://reqres.in/api/users/1 -> data 필드
*/
@Serializable
data class UserDto(
val id: Int,
val email: String,
@SerialName("first_name") val firstName: String,
@SerialName("last_name") val lastName: String,
val avatar: String,
)

/** GET /api/users/{id} 응답 */
@Serializable
data class SingleUserResponse(
val data: UserDto,
)

/** GET /api/users?page= 응답 */
@Serializable
data class UserListResponse(
val page: Int,
@SerialName("per_page") val perPage: Int,
val total: Int,
@SerialName("total_pages") val totalPages: Int,
val data: List<UserDto>,
)

/** DTO -> 도메인 모델 변환 (닉네임 = first_name + last_name) */
fun UserDto.toUser(): User = User(
id = id,
nickname = "$firstName $lastName",
avatarUrl = avatar,
)
12 changes: 12 additions & 0 deletions Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/model/User.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.NikeApp.model

/**
* 화면에서 사용하는 사용자 도메인 모델
* - [nickname] 은 ReqRes 의 first_name + last_name 을 합친 값
* - [avatarUrl] 은 프로필/팔로잉 이미지로 사용
*/
data class User(
val id: Int,
val nickname: String,
val avatarUrl: String,
)
Loading