From 1cd6f8067a59bd7cdb7a024773a159f699fbf2f8 Mon Sep 17 00:00:00 2001 From: yeseul Date: Sun, 31 May 2026 22:13:50 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[ori/#87]=20mission=20-=20=EB=A7=88?= =?UTF-8?q?=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week7/app/build.gradle.kts | 1 + .../presentation/profile/ProfileScreen.kt | 326 +++++++++++++++++- .../nike/presentation/profile/model/User.kt | 9 + .../res/drawable/ic_profile_chevron_right.xml | 17 + .../main/res/drawable/ic_profile_event.xml | 13 + .../main/res/drawable/ic_profile_order.xml | 13 + .../src/main/res/drawable/ic_profile_pass.xml | 13 + .../main/res/drawable/ic_profile_setting.xml | 13 + .../res/drawable/img_profile_placeholder.png | Bin 0 -> 386 bytes week7/app/src/main/res/values/strings.xml | 11 + 10 files changed, 411 insertions(+), 5 deletions(-) create mode 100644 week7/app/src/main/java/com/example/nike/presentation/profile/model/User.kt create mode 100644 week7/app/src/main/res/drawable/ic_profile_chevron_right.xml create mode 100644 week7/app/src/main/res/drawable/ic_profile_event.xml create mode 100644 week7/app/src/main/res/drawable/ic_profile_order.xml create mode 100644 week7/app/src/main/res/drawable/ic_profile_pass.xml create mode 100644 week7/app/src/main/res/drawable/ic_profile_setting.xml create mode 100644 week7/app/src/main/res/drawable/img_profile_placeholder.png diff --git a/week7/app/build.gradle.kts b/week7/app/build.gradle.kts index 00b281e8..b15342a7 100644 --- a/week7/app/build.gradle.kts +++ b/week7/app/build.gradle.kts @@ -59,4 +59,5 @@ dependencies { androidTestImplementation(libs.androidx.compose.ui.test.junit4) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) + implementation("io.coil-kt:coil-compose:2.6.0") } \ No newline at end of file diff --git a/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt b/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt index 28087aff..29b05759 100644 --- a/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt +++ b/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt @@ -1,37 +1,340 @@ package com.example.nike.presentation.profile +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import com.example.nike.R import com.example.nike.core.designsystem.theme.NikeTheme +import com.example.nike.presentation.profile.model.User @Composable fun ProfileRoute( modifier: Modifier = Modifier, ) { + val users = emptyList() + + val me = users.find { it.id == 1 } + val following = users.filter { it.id != 1 } + ProfileScreen( + me = me, + followingList = following, modifier = modifier, ) } @Composable private fun ProfileScreen( + me: User?, + followingList: List, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize(), + ) { + MyProfileSection( + me = me, + ) + + HorizontalDivider(color = Color(0xFFF6F6F6), thickness = 8.dp) + + MemberBenefitSection() + + HorizontalDivider(color = Color(0xFFF6F6F6), thickness = 8.dp) + + FollowingListSection( + followingList = followingList, + ) + + Spacer(modifier = Modifier.weight(1f)) + + Footer() + } +} + +@Composable +private fun MyProfileSection( + me: User?, + modifier: Modifier = Modifier, +){ + Column( + modifier = modifier + .fillMaxWidth() + .padding( + top = 21.dp, + bottom = 25.dp, + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AsyncImage( + model = me?.avatar, + contentDescription = null, + modifier = Modifier + .size(84.dp) + .clip(CircleShape), + contentScale = ContentScale.Crop, + placeholder = painterResource(R.drawable.img_profile_placeholder), + error = painterResource(R.drawable.img_profile_placeholder) + ) + + Spacer(modifier = Modifier.height(30.dp)) + + Text( + text = me?.firstName + me?.lastName, + color = Color.Black, + fontSize = 20.sp, + ) + + Spacer(modifier = Modifier.height(30.dp)) + + Box ( + modifier = Modifier + .clip(RoundedCornerShape(50)) + .border( + width = 1.dp, + color = Color(0xFFE4E4E4), + shape = RoundedCornerShape(50), + ) + .padding( + vertical = 16.dp, + horizontal = 50.dp, + ), + contentAlignment = Alignment.Center, + ) { + Text( + text = stringResource(R.string.profile_edit), + color = Color.Black, + fontSize = 16.sp, + ) + } + + Spacer(modifier = Modifier.height(40.dp)) + + MenuRow() + } +} + +@Composable +private fun MenuRow( + modifier: Modifier = Modifier, +){ + Row( + modifier = modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + ) { + MenuItem( + icon = R.drawable.ic_profile_order, + label = R.string.profile_menu_order, + ) + + VerticalDivider( + color = Color(0xFFCDCDCD), + thickness = 1.dp, + modifier = Modifier + .height(31.dp) + ) + + MenuItem( + icon = R.drawable.ic_profile_pass, + label = R.string.profile_menu_pass, + ) + + VerticalDivider( + color = Color(0xFFCDCDCD), + thickness = 1.dp, + modifier = Modifier + .height(30.dp) + ) + + MenuItem( + icon = R.drawable.ic_profile_event, + label = R.string.profile_menu_event, + ) + + VerticalDivider( + color = Color(0xFFCDCDCD), + thickness = 1.dp, + modifier = Modifier + .height(30.dp) + ) + + MenuItem( + icon = R.drawable.ic_profile_setting, + label = R.string.profile_menu_setting, + ) + } +} + +@Composable +private fun MenuItem( + @DrawableRes icon: Int, + @StringRes label: Int, + modifier: Modifier = Modifier, +) { + Column ( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(10.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ){ + Icon( + imageVector = ImageVector.vectorResource(icon), + contentDescription = null, + tint = Color.Unspecified, + ) + + Text( + text = stringResource(label), + color = Color.Black, + fontSize = 12.sp, + ) + } +} + +@Composable +private fun MemberBenefitSection( + modifier: Modifier = Modifier, +){ + Row( + modifier = modifier + .fillMaxWidth() + .padding( + horizontal = 24.dp, + vertical = 32.dp, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Column( + modifier = Modifier, + verticalArrangement = Arrangement.spacedBy(6.dp) + ){ + Text( + text = stringResource(R.string.profile_member_benefit), + color = Color.Black, + fontSize = 16.sp, + ) + + Text( + text = stringResource(R.string.profile_member_benefit_count), + color = Color(0xFF767676), + fontSize = 12.sp, + ) + } + + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_profile_chevron_right), + contentDescription = null, + tint = Color.Unspecified, + ) + } +} + +@Composable +private fun FollowingListSection( + followingList: List, + modifier: Modifier = Modifier, +){ + Column( + modifier = modifier + .fillMaxWidth() + .padding( + vertical = 28.dp, + ), + verticalArrangement = Arrangement.spacedBy(18.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 24.dp + ), + horizontalArrangement = Arrangement.Absolute.SpaceBetween, + ) { + Text( + text = stringResource(R.string.profile_following_list, followingList.size), + color = Color.Black, + fontSize = 14.sp, + ) + + Text( + text = stringResource(R.string.profile_following_list_edit), + color = Color(0xFF767676), + fontSize = 12.sp, + ) + } + + LazyRow( + horizontalArrangement = Arrangement.spacedBy(6.dp), + contentPadding = PaddingValues(horizontal = 24.dp) + ) { + items(followingList) { user -> + AsyncImage( + model = user.avatar, + contentDescription = null, + modifier = Modifier + .size(105.dp), + contentScale = ContentScale.Crop, + ) + } + } + } +} + +@Composable +private fun Footer( modifier: Modifier = Modifier, ) { Box( - modifier = modifier.fillMaxSize(), + modifier = modifier + .fillMaxWidth() + .padding( + vertical = 19.dp, + horizontal = 79.dp, + ) + .background(Color(0xFFF6F6F6)), contentAlignment = Alignment.Center, ) { Text( - text = "프로필 화면", - color = Color.Black, - fontSize = 20.sp + text = stringResource(R.string.profile_footer), + color = Color(0xFF767676), + fontSize = 12.sp, ) } } @@ -39,7 +342,20 @@ private fun ProfileScreen( @Preview(showBackground = true) @Composable private fun ProfileScreenPreview() { + val dummyUsers = listOf( + User(id = 1, email = "me@nike.com", firstName = "김", lastName = "민지", avatar = ""), + User(id = 2, email = "a@nike.com", firstName = "김", lastName = "영희", avatar = ""), + User(id = 3, email = "b@nike.com", firstName = "이", lastName = "서연", avatar = ""), + User(id = 4, email = "c@nike.com", firstName = "박", lastName = "철수", avatar = ""), + ) + + val me = dummyUsers.find { it.id == 1 } + val following = dummyUsers.filter { it.id != 1 } + NikeTheme { - ProfileScreen() + ProfileScreen( + me = me, + followingList = following, + ) } } \ No newline at end of file diff --git a/week7/app/src/main/java/com/example/nike/presentation/profile/model/User.kt b/week7/app/src/main/java/com/example/nike/presentation/profile/model/User.kt new file mode 100644 index 00000000..9a8db189 --- /dev/null +++ b/week7/app/src/main/java/com/example/nike/presentation/profile/model/User.kt @@ -0,0 +1,9 @@ +package com.example.nike.presentation.profile.model + +data class User( + val id: Int, + val email: String, + val firstName: String, + val lastName: String, + val avatar: String, +) \ No newline at end of file diff --git a/week7/app/src/main/res/drawable/ic_profile_chevron_right.xml b/week7/app/src/main/res/drawable/ic_profile_chevron_right.xml new file mode 100644 index 00000000..bd3e8c53 --- /dev/null +++ b/week7/app/src/main/res/drawable/ic_profile_chevron_right.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/week7/app/src/main/res/drawable/ic_profile_event.xml b/week7/app/src/main/res/drawable/ic_profile_event.xml new file mode 100644 index 00000000..87b97327 --- /dev/null +++ b/week7/app/src/main/res/drawable/ic_profile_event.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/week7/app/src/main/res/drawable/ic_profile_order.xml b/week7/app/src/main/res/drawable/ic_profile_order.xml new file mode 100644 index 00000000..bec2bbf2 --- /dev/null +++ b/week7/app/src/main/res/drawable/ic_profile_order.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/week7/app/src/main/res/drawable/ic_profile_pass.xml b/week7/app/src/main/res/drawable/ic_profile_pass.xml new file mode 100644 index 00000000..8512c200 --- /dev/null +++ b/week7/app/src/main/res/drawable/ic_profile_pass.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/week7/app/src/main/res/drawable/ic_profile_setting.xml b/week7/app/src/main/res/drawable/ic_profile_setting.xml new file mode 100644 index 00000000..befb75a8 --- /dev/null +++ b/week7/app/src/main/res/drawable/ic_profile_setting.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/week7/app/src/main/res/drawable/img_profile_placeholder.png b/week7/app/src/main/res/drawable/img_profile_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..62dbd5faf2c199720a00bf9fd5ea4b1544db92ae GIT binary patch literal 386 zcmeAS@N?(olHy`uVBq!ia0vp^*+87d!3HFEKVH8QNO2Z;L>4nJa0`PlBg3pY55jgR3=A9lx&I`x0{MI;t`Q}{`DrEPiAAXlZkg%1iSss2O9smP z^>lFzsbGA2k&%}{f#<-6=KAJvW~*si#Hz}^^KzD-m>{a8?Go0pMj%pglT%9T39gC5 bNZ==%CQF=m;axspKrnc^`njxgN@xNAWfFTC literal 0 HcmV?d00001 diff --git a/week7/app/src/main/res/values/strings.xml b/week7/app/src/main/res/values/strings.xml index 2dca00de..b40db328 100644 --- a/week7/app/src/main/res/values/strings.xml +++ b/week7/app/src/main/res/values/strings.xml @@ -23,4 +23,15 @@ 위시리스트 %d Colours + + 프로필 수정 + 주문 + 패스 + 이벤트 + 설정 + 나이키 멤버 혜택 + 0개 사용 가능 + 팔로잉 (%d) + 편집 + 회원 가입일: 2025년 9월 \ No newline at end of file From 7c7ad3a0c7a0f6d896be26e1955e558e0730654c Mon Sep 17 00:00:00 2001 From: yeseul Date: Sun, 31 May 2026 22:47:07 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[ori/#87]=20mission=20-=20Retrofit=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week7/app/build.gradle.kts | 14 ++++++++ .../nike/data/remote/RetrofitClient.kt | 35 +++++++++++++++++++ .../nike/data/remote/api/ProfileApi.kt | 12 +++++++ .../data/remote/dto/response/UserResponse.kt | 9 +++++ .../remote/repository/ProfileRepository.kt | 12 +++++++ .../example/nike/domain/model/profile/User.kt | 18 ++++++++++ .../presentation/profile/ProfileScreen.kt | 2 +- .../nike/presentation/profile/model/User.kt | 9 ----- week7/build.gradle.kts | 2 +- 9 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 week7/app/src/main/java/com/example/nike/data/remote/RetrofitClient.kt create mode 100644 week7/app/src/main/java/com/example/nike/data/remote/api/ProfileApi.kt create mode 100644 week7/app/src/main/java/com/example/nike/data/remote/dto/response/UserResponse.kt create mode 100644 week7/app/src/main/java/com/example/nike/data/remote/repository/ProfileRepository.kt create mode 100644 week7/app/src/main/java/com/example/nike/domain/model/profile/User.kt delete mode 100644 week7/app/src/main/java/com/example/nike/presentation/profile/model/User.kt diff --git a/week7/app/build.gradle.kts b/week7/app/build.gradle.kts index b15342a7..a5141748 100644 --- a/week7/app/build.gradle.kts +++ b/week7/app/build.gradle.kts @@ -1,9 +1,16 @@ +import java.util.Properties +import kotlin.apply + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.serialization) } +val properties = Properties().apply{ + load(project.rootProject.file("local.properties").inputStream()) +} + android { namespace = "com.example.nike" compileSdk { @@ -20,6 +27,7 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + buildConfigField("String", "BASE_URL", properties["base.url"].toString()) } buildTypes { @@ -37,6 +45,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } @@ -60,4 +69,9 @@ dependencies { debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) implementation("io.coil-kt:coil-compose:2.6.0") + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation("com.squareup.retrofit2:converter-kotlinx-serialization:2.11.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") } \ No newline at end of file diff --git a/week7/app/src/main/java/com/example/nike/data/remote/RetrofitClient.kt b/week7/app/src/main/java/com/example/nike/data/remote/RetrofitClient.kt new file mode 100644 index 00000000..656c0674 --- /dev/null +++ b/week7/app/src/main/java/com/example/nike/data/remote/RetrofitClient.kt @@ -0,0 +1,35 @@ +package com.example.nike.data.remote + +import com.example.nike.BuildConfig +import com.example.nike.data.remote.api.ProfileApi +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 + +object RetrofitClient { + private const val BASE_URL = BuildConfig.BASE_URL + private val json = Json { ignoreUnknownKeys = true } + + private val loggingInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + + private val okHttpClient = OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .build() + + private val instance: Retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient) + .addConverterFactory(json.asConverterFactory("application/json; charset=UTF-8".toMediaType())) + .build() + + fun create(service: Class): T = instance.create(service) + + val profileApi: ProfileApi by lazy { + create(ProfileApi::class.java) + } +} diff --git a/week7/app/src/main/java/com/example/nike/data/remote/api/ProfileApi.kt b/week7/app/src/main/java/com/example/nike/data/remote/api/ProfileApi.kt new file mode 100644 index 00000000..e8d2a513 --- /dev/null +++ b/week7/app/src/main/java/com/example/nike/data/remote/api/ProfileApi.kt @@ -0,0 +1,12 @@ +package com.example.nike.data.remote.api + +import com.example.nike.data.remote.dto.response.UserResponse +import retrofit2.http.GET +import retrofit2.http.Query + +interface ProfileApi { + @GET("users") + suspend fun getUsers( + @Query("page") page: Int = 1, + ): UserResponse +} \ No newline at end of file diff --git a/week7/app/src/main/java/com/example/nike/data/remote/dto/response/UserResponse.kt b/week7/app/src/main/java/com/example/nike/data/remote/dto/response/UserResponse.kt new file mode 100644 index 00000000..fa5e9cb9 --- /dev/null +++ b/week7/app/src/main/java/com/example/nike/data/remote/dto/response/UserResponse.kt @@ -0,0 +1,9 @@ +package com.example.nike.data.remote.dto.response + +import com.example.nike.domain.model.profile.User +import kotlinx.serialization.Serializable + +@Serializable +data class UserResponse( + val data: List, +) \ No newline at end of file diff --git a/week7/app/src/main/java/com/example/nike/data/remote/repository/ProfileRepository.kt b/week7/app/src/main/java/com/example/nike/data/remote/repository/ProfileRepository.kt new file mode 100644 index 00000000..7bff58b1 --- /dev/null +++ b/week7/app/src/main/java/com/example/nike/data/remote/repository/ProfileRepository.kt @@ -0,0 +1,12 @@ +package com.example.nike.data.remote.repository + +import com.example.nike.data.remote.RetrofitClient +import com.example.nike.domain.model.profile.User + +class ProfileRepository { + private val profileApi = RetrofitClient.profileApi + + suspend fun getUsers(): List { + return profileApi.getUsers().data + } +} \ No newline at end of file diff --git a/week7/app/src/main/java/com/example/nike/domain/model/profile/User.kt b/week7/app/src/main/java/com/example/nike/domain/model/profile/User.kt new file mode 100644 index 00000000..efd9da31 --- /dev/null +++ b/week7/app/src/main/java/com/example/nike/domain/model/profile/User.kt @@ -0,0 +1,18 @@ +package com.example.nike.domain.model.profile + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class User( + @SerialName("id") + val id: Int, + @SerialName("email") + val email: String, + @SerialName("first_name") + val firstName: String, + @SerialName("last_name") + val lastName: String, + @SerialName("avatar") + val avatar: String, +) \ No newline at end of file diff --git a/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt b/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt index 29b05759..c694ebd5 100644 --- a/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt +++ b/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt @@ -39,7 +39,7 @@ import androidx.compose.ui.unit.sp import coil.compose.AsyncImage import com.example.nike.R import com.example.nike.core.designsystem.theme.NikeTheme -import com.example.nike.presentation.profile.model.User +import com.example.nike.domain.model.profile.User @Composable fun ProfileRoute( diff --git a/week7/app/src/main/java/com/example/nike/presentation/profile/model/User.kt b/week7/app/src/main/java/com/example/nike/presentation/profile/model/User.kt deleted file mode 100644 index 9a8db189..00000000 --- a/week7/app/src/main/java/com/example/nike/presentation/profile/model/User.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.nike.presentation.profile.model - -data class User( - val id: Int, - val email: String, - val firstName: String, - val lastName: String, - val avatar: String, -) \ No newline at end of file diff --git a/week7/build.gradle.kts b/week7/build.gradle.kts index 3ca28836..00d2c4a2 100644 --- a/week7/build.gradle.kts +++ b/week7/build.gradle.kts @@ -2,5 +2,5 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.compose) apply false - + id("org.jetbrains.kotlin.plugin.serialization") version "2.2.10" apply false } \ No newline at end of file From 7c556b8fd0873d3019c95c8625552530d890bd04 Mon Sep 17 00:00:00 2001 From: yeseul Date: Sun, 31 May 2026 23:24:29 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[ori/#87]=20mission=20-=20ProfileViewModel?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week7/app/build.gradle.kts | 1 + week7/app/src/main/AndroidManifest.xml | 2 + .../nike/data/remote/RetrofitClient.kt | 6 ++ .../presentation/profile/ProfileScreen.kt | 67 +++++++++++-------- .../presentation/profile/ProfileViewModel.kt | 34 ++++++++++ 5 files changed, 82 insertions(+), 28 deletions(-) create mode 100644 week7/app/src/main/java/com/example/nike/presentation/profile/ProfileViewModel.kt diff --git a/week7/app/build.gradle.kts b/week7/app/build.gradle.kts index a5141748..bc15c331 100644 --- a/week7/app/build.gradle.kts +++ b/week7/app/build.gradle.kts @@ -28,6 +28,7 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "BASE_URL", properties["base.url"].toString()) + buildConfigField("String", "API_KEY", "\"${properties["api.key"]}\"") } buildTypes { diff --git a/week7/app/src/main/AndroidManifest.xml b/week7/app/src/main/AndroidManifest.xml index 6d34e1c6..a78a9ac6 100644 --- a/week7/app/src/main/AndroidManifest.xml +++ b/week7/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + val request = chain.request().newBuilder() + .addHeader("x-api-key", BuildConfig.API_KEY) + .build() + chain.proceed(request) + } .build() private val instance: Retrofit = Retrofit.Builder() diff --git a/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt b/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt index c694ebd5..e469e5f5 100644 --- a/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt +++ b/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape @@ -24,6 +25,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -36,6 +38,8 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.example.nike.R import com.example.nike.core.designsystem.theme.NikeTheme @@ -44,8 +48,9 @@ import com.example.nike.domain.model.profile.User @Composable fun ProfileRoute( modifier: Modifier = Modifier, + viewModel: ProfileViewModel = viewModel(), ) { - val users = emptyList() + val users by viewModel.users.collectAsStateWithLifecycle() val me = users.find { it.id == 1 } val following = users.filter { it.id != 1 } @@ -63,27 +68,32 @@ private fun ProfileScreen( followingList: List, modifier: Modifier = Modifier, ) { - Column( - modifier = modifier - .fillMaxSize(), + LazyColumn( + modifier = modifier.fillMaxSize(), ) { - MyProfileSection( - me = me, - ) - - HorizontalDivider(color = Color(0xFFF6F6F6), thickness = 8.dp) + item { + MyProfileSection(me = me) + } - MemberBenefitSection() + item { + HorizontalDivider(color = Color(0xFFF6F6F6), thickness = 8.dp) + } - HorizontalDivider(color = Color(0xFFF6F6F6), thickness = 8.dp) + item { + MemberBenefitSection() + } - FollowingListSection( - followingList = followingList, - ) + item { + HorizontalDivider(color = Color(0xFFF6F6F6), thickness = 8.dp) + } - Spacer(modifier = Modifier.weight(1f)) + item { + FollowingListSection(followingList = followingList) + } - Footer() + item { + Footer() + } } } @@ -91,7 +101,7 @@ private fun ProfileScreen( private fun MyProfileSection( me: User?, modifier: Modifier = Modifier, -){ +) { Column( modifier = modifier .fillMaxWidth() @@ -115,14 +125,14 @@ private fun MyProfileSection( Spacer(modifier = Modifier.height(30.dp)) Text( - text = me?.firstName + me?.lastName, + text = "${me?.firstName} ${me?.lastName}", color = Color.Black, fontSize = 20.sp, ) Spacer(modifier = Modifier.height(30.dp)) - Box ( + Box( modifier = Modifier .clip(RoundedCornerShape(50)) .border( @@ -152,7 +162,7 @@ private fun MyProfileSection( @Composable private fun MenuRow( modifier: Modifier = Modifier, -){ +) { Row( modifier = modifier .fillMaxWidth(), @@ -208,11 +218,11 @@ private fun MenuItem( @StringRes label: Int, modifier: Modifier = Modifier, ) { - Column ( + Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(10.dp), horizontalAlignment = Alignment.CenterHorizontally, - ){ + ) { Icon( imageVector = ImageVector.vectorResource(icon), contentDescription = null, @@ -230,7 +240,7 @@ private fun MenuItem( @Composable private fun MemberBenefitSection( modifier: Modifier = Modifier, -){ +) { Row( modifier = modifier .fillMaxWidth() @@ -244,7 +254,7 @@ private fun MemberBenefitSection( Column( modifier = Modifier, verticalArrangement = Arrangement.spacedBy(6.dp) - ){ + ) { Text( text = stringResource(R.string.profile_member_benefit), color = Color.Black, @@ -270,12 +280,13 @@ private fun MemberBenefitSection( private fun FollowingListSection( followingList: List, modifier: Modifier = Modifier, -){ +) { Column( modifier = modifier .fillMaxWidth() .padding( - vertical = 28.dp, + top = 28.dp, + bottom = 115.dp, ), verticalArrangement = Arrangement.spacedBy(18.dp) ) { @@ -323,12 +334,12 @@ private fun Footer( ) { Box( modifier = modifier + .background(Color(0xFFF6F6F6)) .fillMaxWidth() .padding( vertical = 19.dp, horizontal = 79.dp, - ) - .background(Color(0xFFF6F6F6)), + ), contentAlignment = Alignment.Center, ) { Text( diff --git a/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileViewModel.kt b/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileViewModel.kt new file mode 100644 index 00000000..e564acc4 --- /dev/null +++ b/week7/app/src/main/java/com/example/nike/presentation/profile/ProfileViewModel.kt @@ -0,0 +1,34 @@ +package com.example.nike.presentation.profile + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.nike.data.remote.repository.ProfileRepository +import com.example.nike.domain.model.profile.User +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class ProfileViewModel : ViewModel() { + + private val profileRepository = ProfileRepository() + + private val _users = MutableStateFlow>(emptyList()) + val users: StateFlow> = _users.asStateFlow() + + init { + getUsers() + } + + private fun getUsers() { + viewModelScope.launch { + runCatching { + profileRepository.getUsers() + }.onSuccess { users -> + _users.value = users + }.onFailure { + it.printStackTrace() + } + } + } +} \ No newline at end of file