Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#48 프로필 화면 구현 #139

Merged
merged 47 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c87bc17
feat(MainActivity): 로그인 하거나 온보딩 화면에서 회원 정보 등록 후 메인 화면으로 이동하도록 변경
ki960213 Jul 28, 2023
16e263e
feat(MainActivity): 처음 메인 화면에 가면 프로필 화면이 보이도록 구현
ki960213 Jul 28, 2023
9d88772
feat(MainActivity): 하단 내비게이션 뷰의 스타일 설정 및 테마 변경
ki960213 Jul 28, 2023
a64be8f
feat(MainActivity): 메인 화면에 가면 처음에 행사 목록 화면으로 이동하도록 구현
ki960213 Jul 28, 2023
3534dd9
feat(MainActivity): 하단 내비게이션 뷰 메뉴 클릭 시 화면이 변경되도록 구현
ki960213 Jul 28, 2023
e2c2c6f
feat: 폰트 추가 및 하단 내비게이션 뷰의 스타일 지정
ki960213 Jul 28, 2023
fe77950
feat(MyProfileFragment): 회원 정보 화면 xml 일부 구현
ki960213 Jul 28, 2023
de86666
feat(MyProfileFragment): 회원 정보 화면 일부 구현
ki960213 Jul 29, 2023
a50e817
feat(MyProfileFragment): 회원의 프로필에서 활동 내역을 보여주는 기능 구현
ki960213 Jul 29, 2023
5a3192f
refactor(MyProfileScreenUiState): MyProfileScreenUiState의 속성명 변경
ki960213 Jul 29, 2023
4577a10
refactor(MyProfileFragment): 리사이클러 뷰 설정하는 코드 리팩터링
ki960213 Jul 29, 2023
e66ba27
refactor(fragment_my_profile): 바인딩 어댑터 속성명 변경
ki960213 Jul 29, 2023
b1e5ded
feat(fragment_my_profile): xml 수정
ki960213 Jul 29, 2023
81d00ed
feat(item_myprofile_activities): xml 수정
ki960213 Jul 29, 2023
a214fd6
refactor(MemberWithoutActivitiesApiModel): 클래스명 변경
ki960213 Jul 29, 2023
8caac36
refactor(MyProfileFragment): 리사이클러 뷰들 초기화 하는 로직 리팩터링
ki960213 Jul 30, 2023
8b6ae13
refactor(bg_myprofile_job.xml): 마이 프로필 화면의 직무 태그 배경의 파일명 변경
ki960213 Jul 30, 2023
5b5e0c5
refactor(color_bottom_navigation_view_icon.xml): 필요없는 주석 제거
ki960213 Jul 30, 2023
94cb706
feat(fragment_my_profile): 프로필 이미지가 원 모양으로 보이도록 변경
ki960213 Jul 31, 2023
c44439b
feat(MyProfileFragment): 아래로 당기면 새로고침하는 기능 구현
ki960213 Jul 31, 2023
bacd0ee
feat(fragment_my_profile.xml): progress bar가 화면의 가운데에 오도록 변경
ki960213 Jul 31, 2023
933d0bd
refactor(SwipeRefreshLayoutBindingAdapter): SwipeRefreshLayout의 새로고침 …
ki960213 Jul 31, 2023
aace342
refactor(Member1): 회원의 활동 목록을 요청하는 메서드 명 변경
ki960213 Jul 31, 2023
0319245
refactor(MemberRepository, MemberService): 데이터를 반환하는 함수들의 네이밍 패턴 변경
ki960213 Jul 31, 2023
696f2ac
refactor(ActivitiesAssociatedByActivityType, ActivityApiModel): ApiMo…
ki960213 Jul 31, 2023
b27e3a5
feat(LoginActivity): 로그인 성공 시 LoginActivity가 사라지도록 변경
ki960213 Jul 31, 2023
11582ed
refactor(MainActivity): 변수명 변경
ki960213 Jul 31, 2023
8470ca7
fix: 바뀐 api 명세에 맞춰 ApiModel 수정
ki960213 Jul 31, 2023
0828bc2
fix(nanum_square.xml): 각 폰트마다 fontWeight 설정
ki960213 Jul 31, 2023
b7544c9
fix(fragment_my_profile.xml): 스크롤이 되지 않는 버그 수정
ki960213 Jul 31, 2023
006b04a
refactor(member/dto): 서버에서 받아오는 응답 dto에 @SerialName 추가
ki960213 Aug 1, 2023
e69b430
feat(MemberService): 특정 회원의 활동 이력을 조회하는 Api의 수정에 따라 Service 코드 변경
ki960213 Aug 1, 2023
a218c2c
feat(MainActivity): 하단 내비게이션으로 프래그먼트를 선택할 때 show/hide 방식으로 보여주도록 변경
ki960213 Aug 1, 2023
d3948ce
refactor(MyProfileFragment): 코드 컨벤션 지키도록 변경
ki960213 Aug 1, 2023
d4781c6
feat(MyProfileFragment): 로그인 되어있지 않으면 로그인 화면으로 이동하도록 변경
ki960213 Aug 1, 2023
e4dba6d
fix(myProfile/adapter): areItemsTheSame 메서드와 areContentsTheSame 메서드 사…
ki960213 Aug 1, 2023
6a94947
refactor(myProfile/adapter): 리사이클러 뷰 어댑터와 뷰 홀더 파일 분리
ki960213 Aug 1, 2023
f9f90d2
refactor(MyProfileFragment): 리사이클러 뷰 아이템을 설정하는 로직을 바인딩 어댑터를 사용하는 게 아니…
ki960213 Aug 1, 2023
ef7d87a
refactor(ActivitiesAdapterDecoration): 싱글톤 패턴 제거
ki960213 Aug 1, 2023
3913d17
feat(ImageViewBindingAdapter): 이미지 뷰 바인딩 어댑터로 에러 이미지를 설정할 수 있도록 변경
ki960213 Aug 1, 2023
bd24a29
refactor(MemberRepositoryImpl): 데이터 소스 모델과 데이터 모델을 매핑하는 로직을 Repositor…
ki960213 Aug 1, 2023
aeb2b30
fix(MemberServiceImpl): 회원 정보 요청 에러 해결
ki960213 Aug 1, 2023
f87e92e
feat(fragment_my_profile.xml): 내 프로필 화면의 한 줄 소개 너비 넓힘
ki960213 Aug 1, 2023
5a91bca
feat: android-main과 머지
ki960213 Aug 1, 2023
127a874
style: ktlint 적용
ki960213 Aug 1, 2023
f35a5de
fix(IntExt): px 확장 변수 제거 및 dp 확장 변수 변경
ki960213 Aug 1, 2023
bf98e2b
Merge remote-tracking branch 'origin/android-main' into Feature/#48-프…
ki960213 Aug 1, 2023
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
21 changes: 16 additions & 5 deletions android/2023-emmsale/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension

plugins {
id("org.jetbrains.kotlin.android")
id("com.android.application") version "8.0.2"
id("com.google.gms.google-services")
kotlin("plugin.serialization") version "1.8.21"
id("kotlin-kapt")
id("com.google.firebase.crashlytics")
}

android {
Expand Down Expand Up @@ -63,6 +61,12 @@ android {
dataBinding {
enable = true
}
tasks.withType(Test::class) {
useJUnitPlatform()
testLogging {
events.addAll(arrayOf(org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED, org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED, org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED))
}
}
flavorDimensions += "environment"
productFlavors {
create("staging") {
Expand Down Expand Up @@ -95,19 +99,26 @@ dependencies {
implementation("androidx.browser:browser:1.5.0")
implementation("androidx.work:work-runtime-ktx:2.8.1")
implementation("com.google.firebase:firebase-messaging-ktx:23.2.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation("androidx.fragment:fragment-ktx:1.6.0")
implementation("androidx.fragment:fragment-ktx:1.6.1")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("com.squareup.okhttp3:mockwebserver:4.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
implementation("com.github.bumptech.glide:glide:4.15.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")

implementation(platform("com.google.firebase:firebase-bom:32.2.0"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-messaging-ktx")
implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation("com.google.firebase:firebase-messaging:23.2.0")

implementation("com.github.bumptech.glide:glide:4.15.1")
testImplementation("org.junit.jupiter", "junit-jupiter", "5.8.2")
testImplementation("org.assertj", "assertj-core", "3.22.0")
testImplementation("io.mockk:mockk-android:1.13.5")
testImplementation("io.mockk:mockk-agent:1.13.5")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2")
}
4 changes: 2 additions & 2 deletions android/2023-emmsale/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
tools:targetApi="33">
<activity
android:name=".presentation.ui.main.MainActivity"
android:exported="true" />
android:exported="false" />
<activity
android:name=".presentation.ui.onboarding.OnboardingActivity"
android:exported="true" />
Expand Down Expand Up @@ -53,4 +53,4 @@
</service>
</application>

</manifest>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.emmsale.data.activity

data class Activity1(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 DataModel 을 이렇게 Activity1 과 Acitivty 이런 방식으로 두지만, 추후에 시간이 여유롭다면 Acitivity1 하나만 있는게 좋을 것 같습니다. 나중에Onboarding 을 Activity1 의 데이터 모델을 이용하여 해결했으면 좋겠네요.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

커디 프로젝트,,, 빠른 리팩토링이 시급합니다..

val id: Long,
val activityType: ActivityType,
val name: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.emmsale.data.activity

enum class ActivityType {
EDUCATION, CLUB, EVENT, JOB
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.emmsale.data.member

import com.emmsale.data.activity.Activity1
import com.emmsale.data.activity.ActivityType

data class Member1(
val id: Long,
val name: String,
val description: String,
val imageUrl: String,
val activities: List<Activity1>,
) {
fun getActivities(activityType: ActivityType): List<Activity1> =
activities.filter { it.activityType == activityType }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ package com.emmsale.data.member
import com.emmsale.data.common.ApiResult

interface MemberRepository {

suspend fun getMember(memberId: Long): ApiResult<Member1>

suspend fun updateMember(member: Member): ApiResult<Unit>
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
package com.emmsale.data.member

import com.emmsale.data.common.ApiError
import com.emmsale.data.common.ApiResult
import com.emmsale.data.common.ApiSuccess
import com.emmsale.data.common.handleApi
import com.emmsale.data.member.dto.MemberApiModel
import com.emmsale.data.member.mapper.toData
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext

class MemberRepositoryImpl(
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
private val memberService: MemberService,
) : MemberRepository {

override suspend fun getMember(memberId: Long): ApiResult<Member1> = withContext(dispatcher) {
val memberResponseDeferred = async { memberService.getMember(memberId) }
val activitiesResponseDeferred = async { memberService.getActivities(memberId) }
val memberResponse = memberResponseDeferred.await()
val activitiesResponse = activitiesResponseDeferred.await()

val memberApiModel = memberResponse.body() ?: return@withContext ApiError(
memberResponse.code(),
memberResponse.errorBody().toString()
)
val activitiesApiModels = activitiesResponse.body() ?: return@withContext ApiError(
activitiesResponse.code(),
activitiesResponse.errorBody().toString()
)

ApiSuccess(memberApiModel.toData(activitiesApiModels))
}

override suspend fun updateMember(member: Member): ApiResult<Unit> = withContext(dispatcher) {
handleApi(memberService.updateMember(MemberApiModel.from(member))) { }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package com.emmsale.data.member

import com.emmsale.data.member.dto.ActivitiesAssociatedByActivityTypeApiModel
import com.emmsale.data.member.dto.MemberApiModel
import com.emmsale.data.member.dto.MemberWithoutActivitiesApiModel
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path

interface MemberService {

@GET("members/{memberId}")
suspend fun getMember(@Path("memberId") memberId: Long): Response<MemberWithoutActivitiesApiModel>

@GET("members/{memberId}/activities")
suspend fun getActivities(@Path("memberId") memberId: Long): Response<List<ActivitiesAssociatedByActivityTypeApiModel>>

@POST("/members")
suspend fun updateMember(@Body member: MemberApiModel): Response<Unit>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.emmsale.data.member.dto

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ActivitiesAssociatedByActivityTypeApiModel(
@SerialName("activityType")
val activityType: String,
@SerialName("memberActivityResponses")
val memberActivityResponses: List<ActivityApiModel>,
)

@Serializable
data class ActivityApiModel(
val id: Long,
val name: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.emmsale.data.member.dto

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class MemberWithoutActivitiesApiModel(
@SerialName("id")
val id: Long,
@SerialName("name")
val name: String = "",
@SerialName("description")
val description: String = "",
@SerialName("imageUrl")
val imageUrl: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.emmsale.data.member.mapper

import com.emmsale.data.activity.Activity1
import com.emmsale.data.activity.ActivityType
import com.emmsale.data.member.Member1
import com.emmsale.data.member.dto.ActivitiesAssociatedByActivityTypeApiModel
import com.emmsale.data.member.dto.MemberWithoutActivitiesApiModel

fun MemberWithoutActivitiesApiModel.toData(activities: List<ActivitiesAssociatedByActivityTypeApiModel>): Member1 =
Member1(
id = this.id,
name = this.name,
description = this.description,
imageUrl = this.imageUrl,
activities = activities.toData()
)

private fun List<ActivitiesAssociatedByActivityTypeApiModel>.toData(): List<Activity1> =
this.flatMap { it.toData() }

private fun ActivitiesAssociatedByActivityTypeApiModel.toData(): List<Activity1> =
memberActivityResponses.map {
Activity1(
id = it.id,
activityType = activityType.toData(),
name = it.name
)
}

private fun String.toData(): ActivityType =
when (this) {
"동아리" -> ActivityType.CLUB
"컨퍼런스" -> ActivityType.EVENT
"교육" -> ActivityType.EDUCATION
"직무" -> ActivityType.JOB
else -> throw IllegalStateException("회원의 활동 Json 데이터를 도메인 모델로 매핑하는 데 실패했습니다. 서버와 Api 스펙을 다시 상의해보세요.")
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import androidx.core.content.ContextCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import com.emmsale.R
import com.emmsale.presentation.utils.extension.px
import com.emmsale.presentation.utils.extension.dp

class ActivityTag : AppCompatCheckBox {
constructor(context: Context) : super(context)
Expand All @@ -25,7 +25,7 @@ class ActivityTag : AppCompatCheckBox {
gravity = Gravity.CENTER
background = ContextCompat.getDrawable(context, R.drawable.bg_activity_tag)
setTextColor(ContextCompat.getColor(context, R.color.black))
updatePadding(12.px, 0, 12.px, 0)
updatePadding(12.dp, 0, 12.dp, 0)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import androidx.appcompat.widget.AppCompatCheckBox
import androidx.core.content.ContextCompat
import androidx.core.view.updatePadding
import com.emmsale.R
import com.emmsale.presentation.utils.extension.px
import com.emmsale.presentation.utils.extension.dp

class EventTag : AppCompatCheckBox {
constructor(context: Context) : super(context)
Expand All @@ -25,7 +25,7 @@ class EventTag : AppCompatCheckBox {
minimumHeight = 0
background = ContextCompat.getDrawable(context, R.drawable.bg_event_tag)
setTextColor(ContextCompat.getColor(context, R.color.black))
updatePadding(12.px, 6.px, 12.px, 6.px)
updatePadding(12.dp, 6.dp, 12.dp, 6.dp)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.emmsale.presentation.ui.login

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
Expand All @@ -16,6 +17,7 @@ import com.emmsale.databinding.ActivityLoginBinding
import com.emmsale.presentation.common.extension.checkPostNotificationPermission
import com.emmsale.presentation.common.extension.showToast
import com.emmsale.presentation.ui.login.uistate.LoginUiState
import com.emmsale.presentation.ui.main.MainActivity
import com.emmsale.presentation.ui.onboarding.OnboardingActivity
import com.emmsale.presentation.utils.builder.uri
import com.google.android.material.snackbar.Snackbar
Expand Down Expand Up @@ -64,7 +66,7 @@ class LoginActivity : AppCompatActivity() {
}

private fun navigateToMain() {
// startActivity(MainActivity.getIntent(this))
MainActivity.startActivity(this)
finish()
}

Expand Down Expand Up @@ -118,6 +120,11 @@ class LoginActivity : AppCompatActivity() {

companion object {
private const val GITHUB_CODE_PARAMETER = "code"

fun startActivity(context: Context) {
val intent = Intent(context, LoginActivity::class.java)
context.startActivity(intent)
}
}

@SuppressLint("InlinedApi")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,65 @@
package com.emmsale.presentation.ui.main

import android.os.Bundle
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.commit
import androidx.fragment.app.commitNow
import com.emmsale.R
import com.emmsale.databinding.ActivityMainBinding
import com.emmsale.presentation.ui.main.events.EventsFragment
import com.emmsale.presentation.ui.main.myProfile.MyProfileFragment

class MainActivity : AppCompatActivity() {

private val binding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setContentView(binding.root)

initBottomNavigationView()
}

private fun initBottomNavigationView() {
val mainBottomNavigationView = binding.bnvMain

addAllFragments()

mainBottomNavigationView.setOnItemSelectedListener {
when (it.itemId) {
R.id.mi_main_profile -> showFragment(MyProfileFragment.TAG)
R.id.mi_main_home -> showFragment(EventsFragment.TAG)
}
return@setOnItemSelectedListener true
}

mainBottomNavigationView.selectedItemId = R.id.mi_main_home
}

private fun addAllFragments() {
supportFragmentManager.commitNow {
add(R.id.fcv_main, MyProfileFragment(), MyProfileFragment.TAG)
add(R.id.fcv_main, EventsFragment(), EventsFragment.TAG)
}
}

private fun showFragment(tag: String) {
supportFragmentManager.commit {
val fragment = supportFragmentManager.findFragmentByTag(tag)
?: throw IllegalStateException("태그 ${tag}로 프래그먼트를 찾을 수 없습니다. 프래그먼트 초기화 로직을 다시 살펴보세요.")
supportFragmentManager.fragments.forEach { hide(it) }
show(fragment)
}
}

companion object {
fun startActivity(context: Context) {
val intent = Intent(context, MainActivity::class.java)
context.startActivity(intent)
}
Comment on lines +60 to +63
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래와 같이 한 줄에 작성해도 좋을 것 같아요~!

fun startActivity(context: Context) {
    startActivity(Intent(context, MainActivity::class.java))
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인텐트를 생성하고 액티비티를 시작하는 두 작업을 한 줄에 작성하는 게 더 보기 좋을까요? ❓ 궁금하네요ㅠㅠ

}
}
Loading
Loading