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/#795 게시글 화면과 댓글 화면 ux 개선 #807

Merged
Merged
Show file tree
Hide file tree
Changes from 67 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
104212f
refactor(comment 관련): 공통적으로 사용되는 리사이클러 뷰 디바이더를 common 패키지로 옮기고 이름을 Co…
ki960213 Oct 26, 2023
8bb393d
refactor(themes.xml): EditText의 스타일을 테마에서 지정
ki960213 Oct 26, 2023
052dff1
feat(BasicTextInputWindow): 기본 텍스트 입력창 커스텀 뷰 구현
ki960213 Oct 26, 2023
502c39e
refactor(ChildCommentActivity): 대댓글 화면에서 입력창을 커스텀 뷰를 사용하도록 변경
ki960213 Oct 26, 2023
b637847
feat(layout_basic_input_window.xml): EditText의 줄 간격 2dp로 늘림
ki960213 Oct 26, 2023
54659db
refactor(BasicTextInputWindow): 데이터 바인딩을 쓰도록 리팩터링
ki960213 Oct 26, 2023
627bd2b
feat(ChildComment): 댓글 게시에 성공하면 텍스트가 사라지도록 변경
ki960213 Oct 26, 2023
d56af64
feat(SubTextInputWindow): 서브 입력창 구현
ki960213 Oct 26, 2023
32241ed
feat(ActivityExt): Activity 확장 함수로 hideKeyboard와 showKeyboard 정의
ki960213 Oct 26, 2023
ac0c8cd
feat(BaseViewModel): 미리 상태를 변하게 하는 함수를 정의하고, 네트워크 요청 결과에 따라 미리 정의한 상태…
ki960213 Oct 26, 2023
2920edd
refactor(ChildCommentActivity): 대댓글 화면 전체적인 리팩터링
ki960213 Oct 26, 2023
3eadc97
refactor(KeyboardHider): KeyboardHider의 생성자에서 Activity를 받도록 변경하고 comm…
ki960213 Oct 26, 2023
f411b13
feat(ChildCommentActivity): 대댓글 화면에서 댓글 클릭 시 키보드 닫히게 하는 기능 구현
ki960213 Oct 26, 2023
dff0843
feat(layout_sub_text_input_window): EditText 영역을 넓혀서 사용성 증가
ki960213 Oct 26, 2023
3f57cf4
feat(ChildCommentActivity): 대댓글 알림을 클릭하면 그 대댓글로 스크롤 되는 기능 구현 및 대댓글 작성…
ki960213 Oct 26, 2023
6b81fca
feat(ChildCommentActivity): 댓글 수정 모드일 때 주 입력창이 사라지지 않도록 변경
ki960213 Oct 26, 2023
e482b2a
refactor(BasicTextInputWindow, SubTextInputWindow): 바인딩 변수로 함수가 아닌 리스…
ki960213 Oct 26, 2023
1e75d94
feat(NetworkErrorView): 네트워크 에러 시 보여줄 화면의 커스텀 뷰 구현
ki960213 Oct 27, 2023
e6da29a
refactor(SwipeRefreshLayoutBindingAdapter.kt): onRefresh에 app 네임 스페이스…
ki960213 Oct 27, 2023
138b1eb
feat(layout_network_error.xml): 네트워크 에러 화면 layout 구현
ki960213 Oct 27, 2023
cd85204
refactor(ChildCommentActivity): getIntent와 startActivity에서 스크롤 할 댓글과 …
ki960213 Oct 27, 2023
ae2ca30
refactor(ChildCommentActivity): 불필요한 함수 추출 제거
ki960213 Oct 27, 2023
64a995e
feat(ChildCommentActivity): 알림을 통해 대댓글 화면에 들어오면 그 대댓글이 위치한 곳으로 즉시 이동하…
ki960213 Oct 27, 2023
42639c2
feat: 알림 목록 화면과 내 댓글 화면에서 대댓글 화면 이동 시 해당 댓글 위치로 이동 하는 기능 구현
ki960213 Oct 27, 2023
4ed2a32
Merge remote-tracking branch 'origin/android-main' into Feature/#795-…
ki960213 Oct 27, 2023
eb642c0
feat(ChildCommentActivity): 특정 댓글을 통해 대댓글 화면에 들어갔을 때 하이라이팅 하는 기능 구현
ki960213 Oct 28, 2023
0f3bd5e
feat(ChildCommmentActivity): 구성 변경 시 방금 들어왔다는 플래그를 false로 설정
ki960213 Oct 28, 2023
1fee21b
refactor(string.xml): 댓글 작성사의 사진 설명글의 아이디 변경
ki960213 Oct 29, 2023
a46cae7
refactor(item_all_comment1.xml): 댓글 관련 UI xml을 하나로 합친 xml 구현
ki960213 Oct 29, 2023
6ac6b01
feat(ChildComment): 댓글 하이라이팅 기능 구현
ki960213 Oct 29, 2023
912b746
refactor(FeedDetailActivity): 불필요한 클래스 제거 및 새로운 CommentsAdapter 적용
ki960213 Oct 29, 2023
3d834b1
refactor(CommentViewHolder, CommentsAdapter): 점진적 리팩터링의 흔적인 클래스 명 변경
ki960213 Oct 29, 2023
f2cd97a
refactor(IntervalDecoration): 리사이클러 뷰 아이템 간의 간격 장식 클래스를 공통적인 패키지로 옮김
ki960213 Oct 29, 2023
72f2f1d
feat(FeedDetailActivity): 게시글 상세 화면에 들어갈 때 하이라이팅 기능 추가
ki960213 Oct 29, 2023
a1b3bc9
feat(FeedDetailActivity): 알림 화면, 내 댓글 화면, 대댓글 알림을 통해 행사 상세 화면에 들어갈 때 …
ki960213 Oct 29, 2023
884923f
refactor(item_all_comment.xml): 파일명 변경
ki960213 Oct 29, 2023
d684971
feat(FeatDetailActivity): 게시글 상세 화면에서는 하이라이팅 기능 제거
ki960213 Oct 29, 2023
40cbde7
Revert "feat(FeedDetailActivity): 알림 화면, 내 댓글 화면, 대댓글 알림을 통해 행사 상세 화면…
ki960213 Oct 30, 2023
7d45576
feat(SwipeRefreshLayoutBindingAdapter): 하위 호환성을 위해 우선 onRefresh1 속성의 …
ki960213 Oct 30, 2023
56601cf
feat(ChildCommentActivity): 새로고침이 끝나면 로딩 중 애니메이션이 사라지도록 변경
ki960213 Oct 30, 2023
acca981
feat(SwipeRefreshLayoutBindingAdapter): 새로고침이 끝나면 isRefreshing을 true로…
ki960213 Oct 30, 2023
d7de471
feat(BaseViewModel): refresh 메서드 구현
ki960213 Oct 30, 2023
685ddd9
Merge remote-tracking branch 'origin/android-main' into Feature/#795-…
ki960213 Oct 30, 2023
89f73da
refactor(BaseViewModel): 공통 로직을 함수로 추출
ki960213 Oct 31, 2023
b5b7af3
refactor(BasicTextInputWindow): 뷰의 depth를 줄이도록 개선
ki960213 Oct 31, 2023
dd5bc27
refactor(SubTextInputWindow): @JvmOverloads를 사용하여 불필요한 코드 제거
ki960213 Oct 31, 2023
393005f
refactor(AndroidManifest.xml): 불필요한 windowSoftInputMode 제거
ki960213 Oct 31, 2023
2a03057
refactor(ChildCommentViewModel): 뷰모델 생성 시 수행하는 로직을 함수로 추출
ki960213 Oct 31, 2023
1789c6e
refactor(ChildCommentViewModel): 뷰모델이 처음 댓글을 불러왔는 지 상태를 가지고 있게 하도록 변경
ki960213 Oct 31, 2023
1ce8d93
refactor(SubTextInputWindow): 불필요한 바인딩 어댑터 제거
ki960213 Nov 1, 2023
b8e74b8
refactor(CommentViewHolder): 불필요한 주석 제거
ki960213 Nov 1, 2023
a3b27f0
feat(ChildCommentActivity, BaseViewModel): 네트워크 요청 실패 이벤트 및 잘못된 댓글 조회…
ki960213 Nov 1, 2023
b81969c
refactor(ChildCommentViewModel): 뷰모델에서 Job 객체를 노출하도록 변경
ki960213 Nov 1, 2023
4798eaf
feat(FloatExt.kt): Float 타입의 숫자를 dp 단위로 변환하는 기능 구현
ki960213 Nov 1, 2023
ea05164
refactor(DividerItemDecoration): 리사이클러 뷰의 수평 구분선을 쉽게 그리는 클래스의 생성자 및 클…
ki960213 Nov 1, 2023
d3260e4
refactor(SpaceItemDecoration): 리사이클러 뷰 아이템 간의 간격을 추가할 때 사용하는 클래스의 이름 변경
ki960213 Nov 1, 2023
acce77c
refactor(EditTextExt.kt): 소프트 키보드가 보여지게 하는 기능을 Deprecated 된 명령어를 사용하지…
ki960213 Nov 1, 2023
9caf086
refactor(layout_network_error.xml): 아이디 네이밍 변경
ki960213 Nov 1, 2023
5bfd687
refactor(CommentViewHolder): 불필요한 타입 선언 제거
ki960213 Nov 1, 2023
9b66d3c
refactor(colors.xml): 불필요한 색상 선언 제거
ki960213 Nov 1, 2023
f284c3c
feat(colors.xml): 하이라이팅 색상을 연하게 변경
ki960213 Nov 1, 2023
b35d159
refactor(ChildCommentActivity): getIntent 메서드 변경
ki960213 Nov 1, 2023
579662b
refactor(FeedDetailActivity): getIntent 메서드와 startActivity 메서드 변경
ki960213 Nov 1, 2023
3cf6c4d
refactor(SubTextInputWindow): Int.dp 확장 변수 대신 Float.dp 확장 변수를 사용하도록 변경
ki960213 Nov 1, 2023
0f9f24a
refactor(SubTextInputWindow): SubTextInputWindow 내에 정의된 불필요한 isVisibl…
ki960213 Nov 1, 2023
41853fe
fix(FeedDetailActivity): 처음으로 데이터를 가져왔는지에 대한 정보는 뷰모델이 가지고 있도록 하여 구성 변…
ki960213 Nov 1, 2023
a81e667
feat(ChildCommentViewModel): 새로고침 시엔 네트워크 에러에 의해 새로고침 할 수 없어도 네트워크 화면…
ki960213 Nov 1, 2023
7026a44
refactor(ChildCommentActivity): submitList에 콜백을 넘겨서 아이템이 설정되었는지 관찰하도록 변경
ki960213 Nov 2, 2023
58a6314
refactor(BasicTextInputWindow): 불필요한 매직 넘버 제거와 바인딩 어댑터 메서드 명 변경
ki960213 Nov 2, 2023
2552c59
refactor(ChildCommentActivity): 코드 리팩터링
ki960213 Nov 2, 2023
dafff57
refactor(ViewBindingAdapter.kt): 코드 리팩터링
ki960213 Nov 2, 2023
114d953
refactor(NetworkErrorView): 주 생성자에 @JvmOverloads를 붙여 코드 감소
ki960213 Nov 2, 2023
fb6e300
feat(SingleLiveEvent): 뷰모델에서 이벤트를 알릴 때 사용하는 클래스 구현
ki960213 Nov 2, 2023
4c16fb4
refactor(ChildComment): 대댓글 화면 부분 리팩터링
ki960213 Nov 2, 2023
2ceec5a
refactor(ViewBindingAdapter): 바인딩 어댑터 속성명 변경
ki960213 Nov 3, 2023
bfd2b0c
fix(ViewBindingAdapter.kt): 문자열을 크기로 변경할 때 로직 수정
ki960213 Nov 3, 2023
31d52e6
refactor(item_all_comment.xml): 댓글 뷰 클릭 시 호출되는 함수를 받는 매개변수의 이름을 변경
ki960213 Nov 3, 2023
e7d6060
refactor(BaseViewModel): 코드 리팩터링
ki960213 Nov 3, 2023
93e995b
refactor(BasicTextInputWindow, SubTextInputWindow): Delegates 클래스를 im…
ki960213 Nov 3, 2023
22bad95
refactor(EditTextExt.kt, ActivityExt.kt): showKeyboard 함수를 현재 포커스 된 뷰…
ki960213 Nov 3, 2023
b778644
feat(ActivityExt.kt): showKeyboard 함수를 수행했을 때 대상 뷰가 EditText라면 커서를 마지…
ki960213 Nov 3, 2023
2c26104
refactor(FeedDetailActivity): showKeyboard() 함수를 Deprecated 되지 않은 api…
ki960213 Nov 3, 2023
49222f8
refactor(SwipeRefreshLayoutBindingAdapter.kt): early return과 MainScop…
ki960213 Nov 3, 2023
49dc225
refactor(BaseViewModel): BaseViewModel 제거
ki960213 Nov 3, 2023
4641cf9
Merge remote-tracking branch 'origin/android-main' into Feature/#795-…
ki960213 Nov 3, 2023
e2eab6c
refactor(IntervalItemDecoration): 리사이클러 뷰 아이템 간격 추가하는 ItemDecoration의…
ki960213 Nov 3, 2023
80bb03b
refactor(ChildCommentViewModel): 코드 리팩터링
ki960213 Nov 3, 2023
a9bdbdb
refactor(ChildCommentsUiState): 불필요한 코드 제거
ki960213 Nov 3, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.emmsale.presentation.base

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.emmsale.data.common.retrofit.callAdapter.ApiResponse
import com.emmsale.data.common.retrofit.callAdapter.Failure
import com.emmsale.data.common.retrofit.callAdapter.NetworkError
import com.emmsale.data.common.retrofit.callAdapter.Success
import com.emmsale.data.common.retrofit.callAdapter.Unexpected
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

abstract class BaseViewModel : ViewModel() {

protected abstract fun changeToLoadingState()

protected abstract fun changeToNetworkErrorState()

protected abstract fun changeToSuccessState()

Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. changeToNetworkErrorState -> 이것을 전역적으로 구현하는 것은 동의하지 않습니다. 만약 전역적으로 구현한다면, 화면이 네트워크 에러 상태로 변하는 것이겠죠? 하지만 스크랩 요청같은 경우에는 네트워크 에러일 때, 화면을 네트워크 에러 상태로 변하게 하는 것이 아닌, 단순하게 인터넷과의 연결이 끊어졌습니다 라는 토스트 메세지를 띄울 것 같아요. 따라서, changeToNetworkErrorState 는 onNetworkError 로 handleResponse의 인자로 들어가야 할 것 같습니다.

  2. changeToLoadingState, changeToLoadingState
    changeToSuccessState와 changeToLoadingState 역시 에러상태와 마찬가지로 함수의 인자로 들어가는 것이 맞다고 생각합니다.
    onSuccess 안에서 chnageToSuccessState를 구현한다고 생각되고, changeToLoadingState 도 한개의 로딩상태를 바꾸는 것이 아닌, 다른 로딩상태를 바꿔서 다르게 처리 할 수 있다고 생각하고, 확장성에도 더 좋다고 생각됩니다.

protected fun <T : Any> handleResponse(
getResult: suspend () -> ApiResponse<T>,
onSuccess: (T) -> Unit,
onFailure: (code: Int, message: String?) -> Unit,
onNetworkError:()->Unit,
onLoading: :()->Unit,
):Job = viewModelScope.launch {
        onLoading()
        when (val result = getResult()) {
            is Failure -> onFailure(result.code, result.message)

            NetworkError -> onNetworkError()

            is Success -> onSuccess(result.data)

            is Unexpected -> onUnexpected(result.error)
        }

이렇게 하는 것은 어떨까요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

스캇의 말을 듣고 보니 모든 뷰모델 함수가 같은 로딩 상태 처리와 네트워크 상태 처리 하는 것은 옳지 않다고 생각하네요. UI마다 다르게 처리해야 할 수 있으니... 그래서 BaseViewModel을 그냥 없애는 게 나을 것 같다는 생각이 드네요.

Copy link
Member

Choose a reason for hiding this comment

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

저도 동의합니다~ 아무래도 상황별로 다르게 처리해야 하는 경우가 많을 것 같아요.
BaseViewModel 자체는 좋다고 생각합니다.

예를 들어, 프로젝트 초기에 제안드렸던 viewModelScope.launch와 같은 긴 코드를 우리 프로젝트에서는 launchOnUi {...}, launchOnIO {...}, laucnhOnDefault {...}와 같은 형태로 사용하도록 만들거나, 정말 공통적인 코드를 찾아서 Base 형태로 만드는 것은 좋다고 생각해요 : )
스캇이 제안해주신 내용과도 비슷한 맥락입니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

위의 의견을 반영하여 다시 한 번 리팩토링 후 푸시하겠습니다.

protected abstract fun onUnexpected(throwable: Throwable?)

protected abstract fun onRequestFailByNetworkError()

abstract fun refresh(): Job

protected fun <T : Any> requestToNetwork(
getResult: suspend () -> ApiResponse<T>,
onSuccess: ((T) -> Unit)? = null,
onFailure: ((code: Int, message: String?) -> Unit)? = null,
onLoading: (suspend () -> Unit)? = null,
onNetworkError: (() -> Unit)? = null,
): Job = viewModelScope.launch {
val loadingJob = launch { onLoading?.invoke() ?: changeToLoadingState() }
when (val result = getResult()) {
is Failure -> onFailure?.invoke(result.code, result.message)
NetworkError -> {
onNetworkError?.invoke() ?: changeToNetworkErrorState()
return@launch
}

is Success -> onSuccess?.invoke(result.data)
is Unexpected -> onUnexpected(result.error)
}
loadingJob.cancel()
changeToSuccessState()
}

protected fun <T : Any> commandAndRefresh(
command: suspend () -> ApiResponse<T>,
onSuccess: ((T) -> Unit)? = null,
onFailure: ((code: Int, message: String?) -> Unit)? = null,
onLoading: (suspend () -> Unit)? = null,
onNetworkError: (() -> Unit)? = null,
): Job = viewModelScope.launch {
val loadingJob = launch { onLoading?.invoke() ?: changeToLoadingState() }
when (val result = command()) {
is Failure -> onFailure?.invoke(result.code, result.message)
NetworkError -> onNetworkError?.invoke() ?: onRequestFailByNetworkError()
is Success -> {
refresh().join()
onSuccess?.invoke(result.data)
}

is Unexpected -> onUnexpected(result.error)
}
loadingJob.cancel()
changeToSuccessState()
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
package com.emmsale.presentation.ui.messageList
package com.emmsale.presentation.common

import android.content.Context
import android.app.Activity
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.InputMethodManager
import com.emmsale.presentation.common.extension.hideKeyboard
import kotlin.math.abs

class KeyboardHider(
private val targetView: View,
private val activity: Activity,
tmdgh1592 marked this conversation as resolved.
Show resolved Hide resolved
private val sensitivity: Float = DEFAULT_SENSITIVITY,
private val willConsumeTouchEvent: Boolean = CONSUMED_TOUCH_EVENT,
) {
private val imm by lazy {
targetView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
}

private var startY: Float = -1F
private var movedY: Float = 0F
Expand All @@ -22,7 +18,7 @@ class KeyboardHider(
when (event.action) {
MotionEvent.ACTION_DOWN -> startY = event.y
MotionEvent.ACTION_MOVE -> movedY = abs(event.y - startY)
MotionEvent.ACTION_UP -> if (canHideKeyboard()) hideKeyboard()
MotionEvent.ACTION_UP -> if (canHideKeyboard()) activity.hideKeyboard()
}
return willConsumeTouchEvent
}
Expand All @@ -31,12 +27,6 @@ class KeyboardHider(
return movedY < sensitivity
}

private fun hideKeyboard() {
if (targetView.onCheckIsTextEditor()) {
imm.hideSoftInputFromWindow(targetView.windowToken, 0)
}
}

companion object {
private const val DEFAULT_SENSITIVITY: Float = 10F
private const val CONSUMED_TOUCH_EVENT = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,36 @@ package com.emmsale.presentation.common.bindingadapter
import androidx.annotation.ColorInt
import androidx.databinding.BindingAdapter
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

@BindingAdapter("onRefresh")
@BindingAdapter("app:onRefresh")
fun SwipeRefreshLayout.setOnRefresh(onRefresh: () -> Unit) {
setOnRefreshListener {
onRefresh()
isRefreshing = false
}
}

@BindingAdapter("app:onRefresh1")
fun SwipeRefreshLayout.setOnRefresh1(onRefreshListener: OnRefreshListener) {
setOnRefreshListener {
val job = onRefreshListener.onRefresh()
if (!job.isCancelled) {
CoroutineScope(Dispatchers.Main).launch {
job.join()
isRefreshing = false
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

dispatcher를 메인으로 하신 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

UI를 변경하는 UI 로직이기 때문입니다.

}
}

fun interface OnRefreshListener {
fun onRefresh(): Job
}

@BindingAdapter("app:swipeRefreshColor")
fun SwipeRefreshLayout.setSwipeRefreshColor(@ColorInt color: Int) {
setColorSchemeColors(color)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
import androidx.databinding.BindingAdapter
import com.emmsale.presentation.common.extension.dp

private const val DIMEN_UNIT_DP = "dp"
tmdgh1592 marked this conversation as resolved.
Show resolved Hide resolved

@BindingAdapter("app:visible")
fun View.setVisible(visible: Boolean) {
Expand All @@ -16,3 +19,24 @@ fun View.setLayoutMarginTop(dimen: Float) {
topMargin = dimen.toInt()
}
}

@BindingAdapter("app:layout_marginTop")
tmdgh1592 marked this conversation as resolved.
Show resolved Hide resolved
fun View.setLayoutMarginTop(margin: String) {
validateMarginFormat(margin)
updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = margin.toDimen()
}
}

private fun validateMarginFormat(margin: String) {
require(margin.endsWith(DIMEN_UNIT_DP) || margin.all { it.isDigit() }) {
"숫자만 이루어져 있거나 숫자 뒤에 $DIMEN_UNIT_DP 문자열만 올 수 있습니다."
}
}

private fun String.toDimen(): Int = if (endsWith(DIMEN_UNIT_DP)) {
tmdgh1592 marked this conversation as resolved.
Show resolved Hide resolved
dropLastWhile { !it.isDigit() }
.toInt().dp
tmdgh1592 marked this conversation as resolved.
Show resolved Hide resolved
tmdgh1592 marked this conversation as resolved.
Show resolved Hide resolved
} else {
toInt()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.emmsale.presentation.common.extension

import android.app.Activity
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity

fun Activity.hideKeyboard() {
val imm = getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(window.decorView.windowToken, 0)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.emmsale.presentation.common.extension

import android.content.Context.INPUT_METHOD_SERVICE
import android.view.inputmethod.InputMethodManager
import android.widget.EditText

private const val DELAY_SHOW_SOFT_INPUT: Long = 200

fun EditText.showKeyboard() {
val imm = context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
postDelayed(
tmdgh1592 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

해당 코드를 사용해보시는 것을 추천드립니다.

Suggested change
fun EditText.showKeyboard() {
val imm = context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
postDelayed(
fun Activity.showKeyboard() {
val imm = getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
currentFocus?.postDelayed({
imm.showSoftInput(currentFocus, InputMethodManager.SHOW_IMPLICIT)
}, 100)
}

Copy link
Collaborator Author

@ki960213 ki960213 Nov 3, 2023

Choose a reason for hiding this comment

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

와우! currentFocus라는 게 있군요. 그리고 저희는 사실 포커스 된 뷰의 텍스트를 편집하기 위해 소프트 키보드를 보여주는 것이므로 제안하신 코드가 훨씬 좋은 것 같네요! 👍

{
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
setSelection(text.length)
},
DELAY_SHOW_SOFT_INPUT,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.emmsale.presentation.common.extension

import android.content.res.Resources

val Float.dp: Float
get() = (this * Resources.getSystem().displayMetrics.density)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.emmsale.presentation.common.recyclerView

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.emmsale.R
import com.emmsale.presentation.common.extension.dp

class DividerItemDecoration(
context: Context,
private val dividerHeight: Float = 0.5f.dp,
@ColorRes private val dividerColor: Int = R.color.light_gray,
) : RecyclerView.ItemDecoration() {
private val paint = Paint().apply {
color = ContextCompat.getColor(context, dividerColor)
style = Paint.Style.FILL
}

override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)

val left = parent.paddingStart.toFloat()
val right = parent.width - parent.paddingEnd.toFloat()
for (i in 0 until parent.childCount - 1) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams

val top = child.bottom.toFloat() + params.bottomMargin
val bottom = top + dividerHeight

val dividerRect = RectF(left, top, right, bottom)
c.drawRect(dividerRect, paint)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.emmsale.presentation.ui.feedDetail.recyclerView
package com.emmsale.presentation.common.recyclerView

import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.emmsale.presentation.common.extension.dp

class FeedDetailImageItemDecoration(
private val divWidth: Int = 10.dp,
class SpaceItemDecoration(
private val width: Int = 0,
private val height: Int = 0,
Copy link
Member

Choose a reason for hiding this comment

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

저번 리뷰에 남겼던 내용인데 아직 반영이 안 되었네요.
Float 타입을 사용하는 것은 어떤가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
Expand All @@ -17,6 +17,7 @@ class FeedDetailImageItemDecoration(
super.getItemOffsets(outRect, view, parent, state)

val position = parent.getChildAdapterPosition(view)
if (position > 0) outRect.left = divWidth
if (position > 0) outRect.left = width
if (position > 0) outRect.top = height
tmdgh1592 marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.emmsale.presentation.common.views

import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.use
import androidx.databinding.BindingAdapter
import com.emmsale.R
import com.emmsale.databinding.LayoutBasicInputWindowBinding
import com.emmsale.presentation.common.extension.dp
import com.emmsale.presentation.common.views.BasicTextInputWindow.OnSubmitListener
import kotlin.properties.Delegates

class BasicTextInputWindow @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : ConstraintLayout(context, attrs) {

private val binding: LayoutBasicInputWindowBinding by lazy {
LayoutBasicInputWindowBinding.inflate(LayoutInflater.from(context), this, false)
}

var isSubmitEnabled: Boolean by Delegates.observable(false) { _, _, newValue ->
binding.isSubmitEnabled = newValue
}

var onSubmitListener: OnSubmitListener by Delegates.observable(OnSubmitListener { }) { _, _, newValue ->
binding.onSubmitListener = newValue
}

init {
applyStyledAttributes(attrs)
setPadding(
BASIC_TEXT_INPUT_WINDOW_HORIZONTAL_PADDING,
BASIC_TEXT_INPUT_WINDOW_VERTICAL_PADDING,
BASIC_TEXT_INPUT_WINDOW_HORIZONTAL_PADDING,
BASIC_TEXT_INPUT_WINDOW_VERTICAL_PADDING,
)
isClickable = true
addView(binding.root)
}

private fun applyStyledAttributes(attrs: AttributeSet?) {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.BasicTextInputWindow,
0,
0,
).use {
binding.etBasicInput.hint = it.getString(R.styleable.BasicTextInputWindow_hint)
binding.tvSubmitButton.text =
it.getString(R.styleable.BasicTextInputWindow_submitButtonLabel)
}
}

fun clearText() {
binding.etBasicInput.text.clear()
}

fun interface OnSubmitListener {
fun onSubmit(text: String)
}

companion object {
private val BASIC_TEXT_INPUT_WINDOW_HORIZONTAL_PADDING = 17.dp
private val BASIC_TEXT_INPUT_WINDOW_VERTICAL_PADDING = 8.dp
}
}

@BindingAdapter("app:onSubmit")
fun BasicTextInputWindow.setOnSubmitListenerBA(onSubmitListener: OnSubmitListener) {
this.onSubmitListener = onSubmitListener
}

@BindingAdapter("app:isSubmitEnabled")
fun BasicTextInputWindow.setIsSubmitEnabled(isSubmitEnabled: Boolean) {
this.isSubmitEnabled = isSubmitEnabled
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ class InfoDialog(
private val title: String,
private val message: String,
private val buttonLabel: String = context.getString(R.string.all_okay),
private val onButtonClick: (() -> Unit)? = null,
private val cancelable: Boolean = true,
) : Dialog(context) {
private val binding: DialogInfoBinding by lazy { DialogInfoBinding.inflate(layoutInflater) }

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

setCancelable(cancelable)
initDialogWindow()
initDataBinding()
}
Expand All @@ -39,6 +42,7 @@ class InfoDialog(
binding.message = message
binding.buttonLabel = buttonLabel
binding.onButtonClick = {
onButtonClick?.invoke()
dismiss()
}
}
Expand Down
Loading