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

[refact/all]: App 추가화면 로직 수정 #158

Merged
merged 2 commits into from
Mar 7, 2024
Merged
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
Copy link
Collaborator

Choose a reason for hiding this comment

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

완전 깔끔해졌다.!! 고마워 의진아 ^_<

Copy link
Member Author

Choose a reason for hiding this comment

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

굳~!!

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.onEach

@AndroidEntryPoint
class AppAddActivity : AppCompatActivity() {

companion object {
const val SELECTED_APPS = "selected_apps"
const val GOAL_TIME = "goal_time"
Expand All @@ -25,56 +26,43 @@ class AppAddActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
initViewPager()
initNextButton()
initViews()
collectState()
setOnClickBackButton()
}

private fun setOnClickBackButton() {
binding.ivBack.setOnClickListener {
finish()
}
}
private fun initViews() {
binding.run {
vpAppAdd.adapter = AppAddViewPagerAdapter(this@AppAddActivity)
vpAppAdd.isUserInputEnabled = false

private fun collectState() {
viewModel.state.flowWithLifecycle(lifecycle).onEach {
binding.btAppSelection.isEnabled = it.selectedApp.isNotEmpty()
}.launchIn(lifecycleScope)
}
btAppSelection.setOnClickListener { handleNextClicked() }
ivBack.setOnClickListener { finish() }
Copy link
Collaborator

Choose a reason for hiding this comment

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

뒤로가기 버튼 눌렀을 때 이 액티비티를 종료하는 코드인 거 맞나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

넹 정답~!!


private fun initNextButton() {
binding.run {
btAppSelection.setOnClickListener {
handleNextClicked()
}
}

}

private fun ActivityAppAddBinding.handleNextClicked() {
when (vpAppAdd.currentItem) {
0 -> {
vpAppAdd.currentItem = 1
}
private fun collectState() {
viewModel.state.flowWithLifecycle(lifecycle)
.onEach { binding.btAppSelection.isEnabled = it.appSelectionList.isNotEmpty() }
.launchIn(lifecycleScope)
Copy link
Collaborator

Choose a reason for hiding this comment

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

이건 무슨 기능을 하는 코드인가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

UDA 구조 -> 즉 단방향 데이터 통신을 통해 현재 View의 상태를 최신화 할 수 있도록 ViewModel에서 관리하는 State를 참조하도록 하는 코드입니다.
안드로이드에서는 MVI패턴이라고 불려요

}

1 -> {
val intent = Intent().apply {
val selectedApps = viewModel.state.value.selectedApp
putExtra(SELECTED_APPS, selectedApps.toTypedArray())
putExtra(GOAL_TIME, viewModel.state.value.goalTime)
}
setResult(RESULT_OK, intent)
finish()
private fun handleNextClicked() {
binding.vpAppAdd.run {
when (currentItem) {
0 -> currentItem = 1
1 -> finishWithResults()
}

else -> Unit
}
}

private fun initViewPager() {
binding.vpAppAdd.run {
adapter = AppAddViewPagerAdapter(this@AppAddActivity)
isUserInputEnabled = false
private fun finishWithResults() {
val intent = Intent().apply {
putExtra(SELECTED_APPS, viewModel.state.value.selectedApps.toTypedArray())
putExtra(GOAL_TIME, viewModel.state.value.goalTime)
}
setResult(RESULT_OK, intent)
finish()
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,81 @@
package com.hmh.hamyeonham.challenge.appadd

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.hmh.hamyeonham.challenge.appadd.appselection.AppSelectionModel
import com.hmh.hamyeonham.challenge.usecase.GetInstalledAppUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

sealed interface AppAddEffect {
data class AppAdd(val selectedApp: List<String>, val goalTime: Long) : AppAddEffect
}
sealed interface AppAddEffect {}

data class AppAddState(
val selectedApp: List<String> = listOf(),
val installedApp: List<String> = emptyList(),
val selectedApps: List<String> = emptyList(),
val goalHour: Long = 0,
val goalMin: Long = 0,
) {
val goalTime = goalHour + goalMin
val appSelectionList = installedApp.map { AppSelectionModel(it, selectedApps.contains(it)) }
}

@HiltViewModel
class AppAddViewModel @Inject constructor() : ViewModel() {
class AppAddViewModel @Inject constructor(
private val getInstalledAppUseCase: GetInstalledAppUseCase
) : ViewModel() {

init {
getInstalledApps()
}

private val _state = MutableStateFlow(AppAddState())
val state = _state.asStateFlow()

private val _effect = MutableSharedFlow<AppAddEffect>()
private val _effect = MutableSharedFlow<AppAddEffect>(1)
val effect = _effect.asSharedFlow()

fun updateState(transform: AppAddState.() -> AppAddState) {
private fun updateState(transform: AppAddState.() -> AppAddState) {
val currentState = state.value
val newState = currentState.transform()
_state.value = newState
}

Comment on lines +42 to +47
Copy link
Member Author

Choose a reason for hiding this comment

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

상태 업데이트는 ViewModel에서만!

Copy link
Member

Choose a reason for hiding this comment

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

구웃

fun checkApp(packageName: String) {
updateState {
copy(selectedApps = selectedApps + packageName)
}

}

fun unCheckApp(packageName: String) {
updateState {
copy(selectedApps = selectedApps - packageName)
}
}

fun setGoalHour(goalHour: Long) {
updateState {
copy(goalHour = goalHour)
}
}

fun setGoalMin(goalMin: Long) {
updateState {
copy(goalMin = goalMin)
}
}
Comment on lines +61 to +71
Copy link
Member

Choose a reason for hiding this comment

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

시간이랑 분을 따로 업데이트 하는 거 보다는 한 함수로 합치는건 어떨까요??
시간따로 분따로 받을 일이 있을까요???

Copy link
Member Author

@kez-lab kez-lab Mar 7, 2024

Choose a reason for hiding this comment

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

그 리스너가 분리되어있습니다 입력되는 뷰가 달라서요!

Copy link
Member

Choose a reason for hiding this comment

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

아하! 넹~~


private fun getInstalledApps() {
viewModelScope.launch {
val installApps = getInstalledAppUseCase()
updateState {
copy(installedApp = installApps)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.hmh.hamyeonham.challenge.appadd.appselection

import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
Expand All @@ -11,64 +10,61 @@ import com.hmh.hamyeonham.common.view.ItemDiffCallback
import com.hmh.hamyeonham.feature.challenge.databinding.ItemAppBinding

class AppSelectionAdapter(
private val onAppCheckboxClicked: (String) -> Unit,
private val onAppCheckboxUnClicked: (String) -> Unit,
) :
ListAdapter<Pair<String, Boolean>, AppSelectionAdapter.AppSelectionViewHolder>(
ItemDiffCallback(onItemsTheSame = { oldItem, newItem ->
private val onAppChecked: (String) -> Unit,
private val onAppUnChecked: (String) -> Unit,
) : ListAdapter<AppSelectionModel, AppSelectionAdapter.AppSelectionViewHolder>(
ItemDiffCallback(
onItemsTheSame = { oldItem, newItem ->
oldItem == newItem
}, onContentsTheSame = { oldItem, newItem ->
oldItem == newItem
}),
) {
private val checkBoxStatus = SparseBooleanArray()
},
onContentsTheSame = { oldItem, newItem ->
oldItem.packageName == newItem.packageName
}
),
) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppSelectionViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ItemAppBinding.inflate(inflater, parent, false)
return AppSelectionViewHolder(
binding,
onAppCheckboxClicked = onAppCheckboxClicked,
onAppCheckboxUnClicked = onAppCheckboxUnClicked,
onAppChecked = onAppChecked,
onAppUnChecked = onAppUnChecked,
)
}

override fun onBindViewHolder(
holder: AppSelectionViewHolder,
position: Int,
) {
override fun onBindViewHolder(holder: AppSelectionViewHolder, position: Int) {
holder.onBind(currentList[position])
}

inner class AppSelectionViewHolder(
class AppSelectionViewHolder(
private val binding: ItemAppBinding,
private val onAppCheckboxClicked: (String) -> Unit,
private val onAppCheckboxUnClicked: (String) -> Unit,
private val onAppChecked: (String) -> Unit,
private val onAppUnChecked: (String) -> Unit,
) : RecyclerView.ViewHolder(binding.root) {
fun onBind(packagePair: Pair<String, Boolean>) {
val packageName = packagePair.first
val isChecked = packagePair.second
fun onBind(appSelectionModel: AppSelectionModel) {
val packageName = appSelectionModel.packageName
binding.run {
val context = binding.root.context
val context = root.context
tvAppname.text = context.getAppNameFromPackageName(packageName)
ivAppicon.setImageDrawable(context.getAppIconFromPackageName(packageName))
cbApp.isChecked = isChecked || checkBoxStatus[adapterPosition]
cbApp.isClickable = false
}
initAppSelectionListener(packageName)
updateCheckedAppListner(appSelectionModel)
}

private fun initAppSelectionListener(packageName: String) {
binding.root.setOnClickListener {
if (adapterPosition != RecyclerView.NO_POSITION) {
if (binding.cbApp.isChecked) {
binding.cbApp.isChecked = false
onAppCheckboxUnClicked(packageName)
checkBoxStatus.put(adapterPosition, false)
private fun updateCheckedAppListner(appSelectionModel: AppSelectionModel) {
binding.run {
cbApp.setOnCheckedChangeListener(null)
cbApp.isChecked = appSelectionModel.isChecked
root.setOnClickListener {
cbApp.isChecked = !cbApp.isChecked
}

cbApp.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
onAppChecked(appSelectionModel.packageName)
} else {
binding.cbApp.isChecked = true
onAppCheckboxClicked(packageName)
checkBoxStatus.put(adapterPosition, true)
onAppUnChecked(appSelectionModel.packageName)
}
}
}
Expand Down
Loading
Loading